i'm trying to delete a user from firebase auth using a cloud function which is triggered when i delete the document of the user. this is a workaround to enable one client "admin" permissions to delete other users without admin SDK. in the document i store the users email. how do i delete the user from auth using the email?
it should be something like this:
exports.sendDelVolunteer = functions.firestore.document('Users/{messageId}').onDelete((snap, context) => {
const doc = snap.data();
const user = admin.auth().getUserByEmail(doc.email).then(function(userRecord) {
return admin.auth().deleteUser(userRecord.uid).then(function() {
console.log('Successfully deleted user');
})
.catch(function(error) {
console.log('Error deleting user:', error);
});
}).catch(function(error) {
console.log('Error fetching user data:', error);
});
});
currently i get the following error: "error Each then() should return a value or throw"
thanks!!!
enter image description here
You're missing a top-level return statement.
exports.sendDelVolunteer = functions.firestore.document('Users/{messageId}').onDelete((snap, context) => {
const doc = snap.data();
return admin.auth().getUserByEmail(doc.email).then(function(userRecord) {
return admin.auth().deleteUser(userRecord.uid)
});
});
Aside from that this looks like a good approach. Just make sure that only your administrator(s( can delete these documents, as otherwise you still have a security hole.
Related
I am following the firebase documentation here to set custom auth claims for users logging into my app for the first time using firebase auth + identify platform but it does not seem to be working.
When a user logs in for the first time, I want them to get the admin custom claim. I have created the following blocking function and have verified from the logs that it runs when I log in for the first time to my app using sign-in with google:
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
return {
customClaims: {
admin: true,
},
};
});
I would expect this to create the admin custom claim in the user's token. However, when I get a list of claims using another cloud function the admin claim does not appear.
exports.getclaims = functions.https.onRequest(async (req, res) => {
const uid = req.query.uid as string;
if (uid) {
const user = await admin.auth().getUser(uid);
res.send(user.customClaims);
} else {
res.sendStatus(500);
}
});
If I set the claim using the admin SDK directly using the below cloud function, the admin claim does appear.
exports.setclaim = functions.https.onRequest(async (req, res) => {
const uid = req.query.uid as string;
if (uid) {
await admin.auth().setCustomUserClaims(uid, {admin: true});
res.sendStatus(200);
} else {
res.sendStatus(500);
}
});
What am I doing wrong in the beforeCreate function?
There's an open GitHub issue regarding that. See sessionClaims content not getting added to the decoded token. Also, there's a fix that has been recently merged regarding this issue.
From the snippet you provided, there does not appear to be anything wrong with beforeCreate as coded.
You may want to check you do not have a beforeSignIn that is overwriting the customClaims directly or via sessionClaims.
https://firebase.google.com/docs/auth/extend-with-blocking-functions#modifying_a_user
Try to use onCreate method instead of beforeCreate how it is shown on the official docs
functions.auth.user().onCreate(async (user) => {
try {
// Set custom user claims on this newly created user.
await getAuth().setCustomUserClaims(user.uid, {admin: true});
// Update real-time database to notify client to force refresh.
const metadataRef = getDatabase().ref('metadata/' + user.uid);
// Set the refresh time to the current UTC timestamp.
// This will be captured on the client to force a token refresh.
await metadataRef.set({refreshTime: new Date().getTime()});
} catch (error) {
console.log(error);
}
}
});
The main point here is that you need to create the user at first and then update claims and make the force update of the token at the client side:
firebase.auth().currentUser.getIdToken(true);
I wanted to implement a feature in my app. Where an Admin can delete the user. So basically the delete is working fine but somehow i cannot logout the logged in user. Let me explain it more briefly, Suppose there is a User A which is currently using my app and the admin decided to remove that user from the app so they can't no longer access the features of the app. To remove the user i can call an API and delete that user but if i completely delete the user it loses all the access to the API's call coz user with the certain ID isn't available anymore and the app breaks coz the API call will fail for that deleted User. So I was wondering is there anyway to logout the user after admin deletes it.
The Frontend is on ReactJs and Backend is on NodeJs. And i am using JWT for authentication. Any help will be appreciated and if this question isn't clear enough please let me know so i can explain it more.
In backend in every protected route you should verify the token and token should contain user id or email using that you will verify the token. After deleting the user throw error with no user found and in frontend make sure if there are the error no user found then it will delete the JWT token.
What comes into my mind is to put a middleware between your requests and server. By doing so, instead of trying to log out from all devices, we will not allow any action if user does not exist; in this very example, we will prevent the user to delete a place and toast a message on the front end. I will share an example of that, but you need to tweak the code according to your needs.
Http Error Model
class HttpError extends Error {
constructor(message, errorCode) {
super(message);
this.code = errorCode;
}
}
module.exports = HttpError;
Middleware
const HttpError = require('../models/http-error');
module.exports = (req, res, next) => {
try {
// Check user if exists
User.findById(req.userData.userId).exec(function (error, user) {
if (error) {
throw new Error('Authentication failed!');
}
else {
return next();
}
});
}
catch (error) {
return next(new HttpError('Authentication failed!', 403));
}
};
Route
const express = require('express');
const router = express.Router();
const checkAuth = require('../middleware/check-auth');
router.use(checkAuth);
// Put after any routes that you want the user to be logged in
router.delete('/:placeId', placesControllers.deletePlace); //e.x.
...
module.exports = router;
E.x. controller (with MongoDB)
const deletePlace = async (req, res, next) => {
const placeId = req.params.placeId;
let foundPlace;
try {
foundPlace = await Place.findById(placeId).populate('userId').exec();
}
catch (error) {
return next(new HttpError('Could not find the place, please try again', 500));
}
// Delete place
res.status(200).json({message: 'Deleted place'});
};
FRONT END PART
import toastr from 'toastr';
....
try {
const response = await fetch(url, {method, body, headers});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message);
}
}
catch(error) {
// handle the error, user not found
console.log(error.message);
toastr.error(error.message, 'Error', {
closeButton: true,
positionClass: 'toast-top-right',
timeOut: 2000,
extendedTimeOut: 1,
});
}
I am learning by building. I am building a blog CMS with Nodejs, reactjs, and mongodb.
I have two roles: users and admin. I would like admin to be able to delete any user. I wrote codes that enabled a user to delete his/her own account. How do I go about making the admin to be able to delete any user by clicking a button next to that user?
Here are my codes so far:
code for a user to delete him/her self. Once a user deletes him/her self, everything associated with the user also gets deleted. This is working fine.
//delete logic
router.delete("/:id", async (req, res) =>{
if(req.body.userId === req.params.id){//we checked if the user id matched
try{
const user = await User.findById(req.params.id)//get the user and assign it to user variable
try{
await Post.deleteMany({username: user._id})//deleting user posts once the username matches with the variable user object .username
await Comment.deleteMany({author: user._id})//delete user's comment by checking the comment author's id.
await Reply.deleteMany({author: user._id})//deletes user's replies
await User.findByIdAndDelete(req.params.id)//delete the user
res.status(200).json("User has been deleted")
} catch(err){
res.status(500).json(err) //this handles the error if there is one from the server
}
}catch(err){
res.status(404).json("User not found")
}
} else{
res.status(401).json("You can only update your account!")
}
});
How I tried to write the code for admin to be able to delete a user:
/delete a user by an admin
router.delete("/:id", async (req, res) =>{
if(req.body.userId === req.params.id){
const user = await User.findOne({username: req.body.username})
if(user && user.role === "admin"){
try{
const regUser = await User.findById(req.params.id)//get the user and assign it to user variable
try{
await Post.deleteMany({username: regUser._id})//deleting user posts once the username matches with the variable user object .username
await Comment.deleteMany({author: regUser._id})//delete user's comment by checking the comment author's id.
await Reply.deleteMany({author: regUser._id})//deletes user's replies
await User.findByIdAndDelete(req.params.id)//delete the user
res.status(200).json("User has been deleted")
} catch(err){
res.status(500).json(err) //this handles the error if there is one from the server
}
}catch(err){
res.status(404).json("User not found")
}
}else{
res.status(401).json("You do not have the permission")
}
}
});
When I tried this code on postman, it kept on loading and didn't deliver anything.
I know that I am not writing the function properly. Kindly provide me with any help to enable me achieve this. Thank you
I tried to reverse-engineer your request body of your API.
And I think it's the following:
{
body: {
userId: string
userName: string
}
params: {
id: string
}
}
So, trying to reverse engineer what each value is intended for:
the params-id obviously is just the parameter which is contained in the URL. So, that's the id of the user which you're trying to delete.
So, what are the userId and userName in your body ?
The issue of security
Judging from your code, the userName and/or userId refer to the user who's logged in and who's performing the operation. Surely, that can't be secure.
You are aware that every user can hit F12 in his webbrowser and see all in/out going requests. It's really easy to modify them and put in the ID of a different user. So, surely, you need more security than just that.
What you need is a "context" which keeps track of the logged in user. e.g. sometimes the entire user object of the logged in user is added on req.context.me.
I've searched for a tutorial that illustrates this, and found this one. It's not entirely what I meant, but it's similar. They store the userId on the req object. Making it available as req.userId.
Aside from security
Having written all that, what you were trying to do is probably the following.
router.delete("/:id", async(req, res) => {
const loggedInUser = await User.findById(req.body.userId);
if (loggedInUser && loggedInUser.role === "admin") {
try {
const regUser = await User.findById(req.params.id);
if (regUser == null) {
throw new Error("user not found");
}
await Post.deleteMany({
username: regUser._id
}) //deleting user posts once the username matches with the variable user object .username
await Comment.deleteMany({
author: regUser._id
}) //delete user's comment by checking the comment author's id.
await Reply.deleteMany({
author: regUser._id
}) //deletes user's replies
await User.findByIdAndDelete(req.params.id) //delete the user
res.status(200).json("User has been deleted")
} catch (err) {
res.status(500).json(err) //this handles the error if there is one from the server
}
} else {
res.status(401).json("You do not have the permission")
}
}
As you can see, you don't need the username.
DELETE does not support a body
Whether a DELETE can have a body or not is actually a point of discussion. Some clients/servers support it and some don't. You can find more about this here:
body is empty when parsing DELETE request with express and body-parser
Again, this means that you really shouldn't pass the logged in user through the body.
I am successfully deleting accounts with an account UID in a Firebase Function, but is it possible to delete an authenticated account via an email address vs. their UID?
This is what I have now:
admin.auth().getUserByEmail(userEmail).then(function(userRecord) {
// See the UserRecord reference doc for the conteenter code herents of userRecord.
console.log('Successfully fetched user data:', userRecord.toJSON());
admin.auth().deleteUser(userRecord)
.then(function
() {
console.log('This is the ID being used!', userRecord);
console.log('Successfully deleted user');
})
.catch(function(error) {
console.log('This is the ID being used!', userRecord);
console.log('Error deleting user:', error);
});
})
.catch(function(error) {
console.log('Error fetching user data:', error);
});
});
Thank you!
You can use deleteUser(uid) to delete the user account as a second operation after you find the account with getUserByEmail(). The UserRecord it returns has a uid field in it.
So, instead of what you're doing now:
admin.auth().deleteUser(userRecord)
Do this instead:
admin.auth().deleteUser(userRecord.uid)
The admin sdk don't have a method to delete by user so It's not posible to delete user with email. You could use the way that you mention.
I am trying to fire my cloud function if theres a document update on users/{userId}
I have a signIn method that's fired everytime a user logs in
signin: (email, password, setErrors) => {
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(() => {
const isVerified = firebase.auth().currentUser.emailVerified
const userUid = firebase.auth().currentUser.uid
const db = firebase.firestore()
if (isVerified) {
db.collection('/users')
.doc(userUid)
.update({ isVerified: true })
}
})
.catch(err => {
setErrors(prev => [...prev, err.message])
})
},
Nothing fancy, aside from the basic log in stuff, it also checks if the user has verified their email, if it is verified it will update the collection for that user. Everything is working as intended here.
However I can't seem to get my cloud function to fire.
Basically, it's listening for changes on ther user collection. If users/{userId} document has isVerified and the user email address ends with xxxx it should grant them admin privledges.
exports.updateUser = functions.firestore.document('users/{userId}').onUpdate((change, context) => {
const after = change.after.data()
if (after.isVerified) {
firebase.auth().onAuthStateChanged(user => {
if (user.emailVerified && user.email.endsWith('#xxxx')) {
const customClaims = {
admin: true,
}
return admin
.auth()
.setCustomUserClaims(user.uid, customClaims)
.then(() => {
console.log('Cloud function fired')
const metadataRef = admin.database().ref('metadata/' + user.uid)
return metadataRef.set({ refreshTime: new Date().getTime() })
})
.catch(error => {
console.log(error)
})
}
})
}
})
Right now the function is not firing, any ideas?
Code in Cloud Functions runs as an administrative account, which cannot be retrieved with the regular Firebase Authentication SDK. In fact, you should only be using Firebase Admin SDKs in your Cloud Functions code, in which the onAuthStateChanged method doesn't exist.
It's not entirely clear what you want this code to do with the user, but if you want to check whether the user whose uid is in the path has a verified email address, you can load that user by their UID with the Admin SDK.
To ensure that the uid in the path is the real UID of the user who performed the operation you can use security rules, as shown in the documentation on securing user data.
I support what Frank said.
You can fetch users with admin in this way:
if (after.isVerified) {
admin.auth().getUser(userId)
.then((userData) => { ...