Firebase Auth: managing users with the Admin SDK - javascript

I have to write a firebase function that receives a JSON with a list of users and has to manage them with the following rules. For each user in the received list:
If the user is already registered (email/password) in firebase, I update it.
If the user is not registered yet, I create it
If a user is registered in firebase but it's not present in the received list, I disable it.
Now, I came up with the following solution: I iterate for each user in the received list. I call admin.auth().createUser() method so that if the user is not registered it will be created, otherwise the method throws an error and in the catch() block I call admin.auth().updateUser().
For the second part, I retrieve all the users registered with admin.auth().listUsers() and for each of them I check whether it's present in the received list: if don't so, I disable it.
For some reason, the correctness of this solution is uncertain: sometimes it doesn't work at all, other times when I call the function once it doesn't work but the second time a call the function it works, idk why is that.
This only happens when I send to the function a lot of users (about 400). If I send only few users it works fine.
Could anyone suggest to me maybe a better solution? Thanks a lot for your answer.
This is the function:
exports.addClients = functions.https.onRequest(async (req, res) => {
// fetch recevied list from payload
var receivedClients = req.body.clients;
// create or update user
receivedClients.forEach(client => {
admin.auth().createUser({
uid: client.id,
email: client.email,
emailVerified: true,
password: client.password,
})
.catch(err => {
// update user
admin.auth().updateUser(client.id, {
email: client.email
}).catch(err => {
// error updating user
log("Error updating user: " + err);
});
})
});
// disabling users not present in the received list
listUsers = await admin.auth().listUsers();
userRecords = listUsers.users;
userRecords.forEach(record => {
if (!receivedClients.some(client => client.id === record.uid)) {
// disable user
admin.auth().updateUser(record.uid, {
disabled: true
})
.catch(err => {
// error disabling user
log("Error disaling user: " + err);
});
}
});
// send response
res.sendStatus(200);
});

Related

why old Email returns after updating it in firebase and VUE.JS?

am trying to allow users to change their primary email in my VUE App which uses firebase as authentication,
the code am using works fine and it gives me that the email has been updated, however after the email is updated I can log with the new email for one time only and once I have logged out then like it has never been changed, and the old email is working again.
What is am doing wrong that keeps getting the old email assigned with the user
currently am using the following code :
firebase.auth()
.signInWithEmailAndPassword(oldEmailAddress, currentPass)
.then(
() => {
firebase.auth().currentUser.updateEmail(newEmailAddress).then(() => {
console.log('Email Updated');
}).catch((error) => {
console.log('Email Error updating user:', error);
});
},
(err) => {
console.log('log in user error:', err);
}
);
try using this function from firebase/auth as the docs say:
const auth = getAuth();
updateEmail(auth.currentUser, "user#example.com").then((result) = { console.log(result) })

Send verification email before logging in

This is the code that i'm practicing in to create a new user. I can receive the email verification and confirm it however, the site will still logged me in even if I have not yet confirmed my email yet.
try{
const { user } = await auth.createUserWithEmailAndPassword(email,password);
await user.sendEmailVerification();
await handleUserProfile(user, { displayName});
this.setState({
...initialSate
});
}catch(err){
console.log(err);
}
}
This is the handleUserProfile in another js file.
export const handleUserProfile = async (userAuth, additionalData) => {
if (!userAuth) return;
const {uid} = userAuth;
const userRef = firestore.doc(`users/${uid}`);
//create new user
const snapshot = await userRef.get();
if (!snapshot.exists){
const { displayName, email} = userAuth;
const timestamp = new Date();
//if the user exist does not exist
try{
await userRef.set({
displayName,
email,
createdDate: timestamp,
...additionalData
});
}catch(err){
console.log(err);
}
}
return userRef;
};
Everything is explained in the firebase documentation.
There you have the corresponding code snippets to try.
You would need to narrow down your question with some of this trials.
Even you have the chance to check if user opens the link from a differenc device from which waas signed up.
I think this is the snippet you might need:
// Confirm the link is a sign-in with email link.
if (firebase.auth().isSignInWithEmailLink(window.location.href)) {
// Additional state parameters can also be passed via URL.
// This can be used to continue the user's intended action before triggering
// the sign-in operation.
// Get the email if available. This should be available if the user completes
// the flow on the same device where they started it.
var email = window.localStorage.getItem('emailForSignIn');
if (!email) {
// User opened the link on a different device. To prevent session fixation
// attacks, ask the user to provide the associated email again. For example:
email = window.prompt('Please provide your email for confirmation');
}
// The client SDK will parse the code from the link for you.
firebase.auth().signInWithEmailLink(email, window.location.href)
.then((result) => {
// Clear email from storage.
window.localStorage.removeItem('emailForSignIn');
// You can access the new user via result.user
// Additional user info profile not available via:
// result.additionalUserInfo.profile == null
// You can check if the user is new or existing:
// result.additionalUserInfo.isNewUser
})
.catch((error) => {
// Some error occurred, you can inspect the code: error.code
// Common errors could be invalid email and invalid or expired OTPs.
});
}
The site will still logged me in even if I have not yet confirmed my
email yet.
Yes this is how it is implemented in Firebase: there is nothing, out of the box, that prevents a user with a non-verified email to authenticate to your app.
You should manage that yourself, by:
Checking the email is verified in the back-end security rules (Firestore, Cloud Storage, etc..). For example with a function like:
function isVerifiedEmailUser() {
return request.auth.token.email_verified == true;
}
Possibly redirect and logout the user from your app if his/her email is not verified. For example, right after signing-up, as follows:
try {
const { user } = await auth.createUserWithEmailAndPassword(email,password);
await user.sendEmailVerification();
if (user.emailVerified) {
// display the content, redirect to another page, etc...
} else {
auth.signOut(); // Maybe call that after showing an error message
}
} catch(err){
console.log(err);
}
}
plus, potentially, something similar with signInWithEmailAndPassword() and onAuthStateChanged().

Checkout page signs out the user

The only way I check if user if logged in to my web app is using the following on the frontend
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
However, I have a cloud-function that I use in order to process a payment, it looks like the following
app.post("/payment", function (req, res) {
const orderId = new Date().getTime();
mollieClient.payments
.create({
amount: {
value: req.body.amount.amount,
currency: req.body.amount.currency,
},
description: req.body.description,
//redirectUrl: `http://localhost:5500/project/order.html?id=${orderId}`,
redirectUrl: `https://......web.app/order.html?id=${orderId}`,
webhookUrl: .....
})
.then((payment) => {
res.send({
redirectUrl: payment.getCheckoutUrl(),
});
return payment.getCheckoutUrl();
})
My problem is regarding the redirect URL, the redirect to order page is supposed to display information about that order, but it does not display anything because I set that only logged in users can see it. My question is why does it log out the user. I tried both to redirect to 'localhost' and 'URL-of-deployed-firebase-app' and in both cases it logs out the user. I thought by intuition that Firebase stores auth information to local storage because I can enter to any page I want without have to login. But I think this is not the case here. I am not using any let token = result.credential.accessToken tokens to keep track of auth status. What is the problem here and how should I proceed?
Here is Order page
function getOrderDetails() {
const url = new URL(window.location.href);
let orderId = url.searchParams.get("id");
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
console.log("User logged in " + user);
firebase
.firestore()
.doc(`orders/${orderId}`)
.get()
.then((doc) => {
let orderSelected = {
category: doc.data().category,
delivery: doc.data().delivery,
description: doc.data().description,
price: doc.data().price,
title: doc.data().title,
quantity: doc.data().quantity,
};
// set textContent of divs
} else {
console.log("User not logged in " + user);
}
});
}
getOrderDetails();
On a new page it takes time to restore the user credentials, as it may require a call to the server. So it is normal that your onAuthStateChanged() listener first is called with null and only after that with the actual user. So you will have to handle the flow in a way that deals with the initial null, for example by waiting a few seconds before assuming that the user session isn't restored.
Alternatively, you can store a marker value in local storage yourself to indicate the was signed in recently, and then on the next page use that to assume the restore will succeed.

Cloud Functions for Firebase HTTP timeout

I'm so close with this one.
I have written a Cloud Function that takes information sent from an Azure token to custom mint a Firebase token and send this token back to the client.
The token is created correctly, but isn't returned on my HTTP-request.
Unfortunately my Firebase app causes a timeout.
Function execution took 60002 ms, finished with status: 'timeout'
I can't really wrap my head around why that is, hence this post. Is there something wrong with my code, or is it me that's calling the HTTP-request wrong?
Here is the log I get from the Firebase Functions console.
Here's my code
// Create a Firebase token from any UID
exports.createFirebaseToken = functions.https.onRequest((req, res) => {
// The UID and other things we'll assign to the user.
const uid = req.body.uid;
const additionalClaims = {
name: req.body.name,
email: req.body.email
};
// Create or update the user account.
const userCreationTask = admin.auth().updateUser(uid, additionalClaims).catch(error => {
// If user does not exists we create it.
if (error.code === 'auth/user-not-found') {
console.log(`Created user with UID:${uid}, Name: ${additionalClaims.name} and e-mail: ${additionalClaims.email}`);
return admin.auth().createUser({
uid: uid,
displayName: displayName,
email: email,
});
}
throw error;
console.log('Error!');
});
// Wait for all async tasks to complete, then generate and return a custom auth token.
return Promise.all([userCreationTask]).then(() => {
console.log('Function create token triggered');
// Create a Firebase custom auth token.
return admin.auth().createCustomToken(uid, additionalClaims).then((token) => {
console.log('Created Custom token for UID "', uid, '" Token:', token);
return token;
});
});
});
When I'm making this HTTP-request, all i'm sending in is a JSON that looks like this:
parameters = [
"uid" : id,
"email" : mail,
"name" : name
]
Cloud Functions triggered by HTTP requests need to be terminated by ending them with a send(), redirect(), or end(), otherwise they will continue running and reach the timeout.
From the terminate HTTP functions section of the documentation on HTTP triggers:
Always end an HTTP function with send(), redirect(), or end(). Otherwise, your function might to continue to run and be forcibly terminated by the system. See also Sync, Async and Promises.
After retrieving and formatting the server time using the Node.js moment module, the date() function concludes by sending the result in the HTTP response:
const formattedDate = moment().format(format);
console.log('Sending Formatted date:', formattedDate);
res.status(200).send(formattedDate);
So, within your code, you could send the token back in the response with send(), for example:
// ...
// Create a Firebase custom auth token.
return admin.auth().createCustomToken(uid, additionalClaims).then((token) => {
console.log('Created Custom token for UID "', uid, '" Token:', token);
res.status(200).send(token);
return token;
});
// ...

How to stop AngularFire2 function?

I am using AngularFire2 Authentication. All is working fine. Now what I wanted to achieve is, check it user is already in the userlist, then just update lastLogin the next time the facebook login button is clicked.
Else create a new user.
This works fine. But I can't get to stop the updateLogin() / _addUser() function to stop after updating the database.
It just keeps going. (89...)auth.service.ts:98 successfully logged in!
Here is the updateLogin()
private _updateLogIn(thisurl, data) {
return thisurl.update({
lastLogIn: firebase.database.ServerValue.TIMESTAMP,
avatar: data.photoURL
}).then((success) => {
console.log("successfully logged in!");
}); ;
}
I call this with the authentication() function as follow:
login(provider: string) {
this.af.auth.login({
provider: this._getProvider(provider)
}).then(
(success) => {
this.authenticate(success);
})
return;
}
authenticate(user: any): any {
if(!user) {
return {};
} else {
let data = user.auth.providerData[0];
this.api_url = this.af.database.object(`${this.path}/${user.auth.uid}`);
this._isUsers().subscribe(value => {
var filtered = value.filter(function(item) {
return item.uid === user.auth.uid;
});
if(filtered.length > 0){this._updateLogIn(this.api_url, data); return; }
else{ this._addUser(this.api_url, user, data); return;
}
});
return;
}
}
_isUsers(){
return this.usersList.map(snapshot => {
return snapshot;
});
}
private _addUser(thisurl, user, data){
return thisurl.set({
name: data.displayName,
username: data.displayName.replace(/ /g,"."),
avatar: data.photoURL,
email: data.email,
provider: data.providerId,
uid: user.auth.uid,
lastLogIn: firebase.database.ServerValue.TIMESTAMP
}).then((success) => {
console.log("successfully signed up!");
return;
});
}
How I stop the function execution to just once?
This basically hacks my browser tab.
(89...)auth.service.ts:98 successfully logged in!
The problem is most like related to the observable returned by _isUsers(). Although it's not clear from the question's code, it seems likely that the this.usersList returned by that function is an AngularFire2 list of all users.
AngularFire2 list and object observables emit data whenever the database changes, so it's likely that your setting the user data in _updateLogIn results in another list of users being emitted, etc.
To solve the problem, you could use the first operator:
import 'rxjs/add/operator/first';
this._isUsers().first().subscribe(...)
However, it's not clear why you need to subscribe to that observable in the first place. You might want to consider refactoring the code in your question, as it seems overly complicated.
It's generally a good idea to avoid as many subscribe calls as possible. You might want to read RxJS: Don’t Unsubscribe.

Categories

Resources