Firebase: Check through cloud functions if user email is verified - firebase

Is there a way to check if email is verified through cloud functions
i.e. if I have a uid for the user, can I check if the email is verified for that particular user. In my use case, I need to make sure that email is verified before transaction is executed. I want to check it server side
Sample cloud function:
exports.executeTransaction = functions.https.onCall((data,context)=>{
const userid = context.auth.uid
//Check if email is verified
//I want to use context variable to somehow extract whether email is verified. Is there a way to do it ?
//Execute Transaction if email is verified
})

Never Mind, I managed to figure it out.
See following for anyone with similar issue:
exports.executeTransaction = functions.https.onCall((data,context)=>{
const userid = context.auth.uid
//Check if email is verified
return admin.auth().getUser(context.auth.uid).then(user => {
//Check if email verified
if(user.emailVerified)
{
return "Verified"
}
else{
console.log("User Email not verified")
return "Not Verified"
}
}).catch(function(err){
console.log(err)
throw new functions.https.HttpsError('Error Validating', err.message, err)
})
})

According to the docs, context includes decodedIdToken, which already contains an email_verified field.
Thus all you need to do is this:
exports.executeTransaction = functions.https.onCall((data, context) => {
const { token } = context.auth;
if (!token.firebase.email_verified)
throw new functions.https.HttpsError(
"failed-precondition",
"The function must be called while authenticated."
);
// ...do stuff
})
https://firebase.google.com/docs/reference/functions/functions.https#.CallableContext
https://firebase.google.com/docs/reference/admin/node/admin.auth.DecodedIdToken#email_verified

Related

Is there a way to generate a firebase email verification link before a user is actually signed up?

I am currently implementing a MFA system with Firebase Authentication & Google Authenticator.
Since my users are not allowed to authenticate with a non-verified email address, I'd like to prevent them from signing-in if their Firebase Authentication email_verified is set to false. To do that, I am using Google Cloud Identity Provider blocking functions, this works perfectly.
However, when it comes to the registration beforeCreate blocking function hook, I can't find a way to generate an email verification link for the user currently being created, the documentation says:
Requiring email verification on registration The following example
shows how to require a user to verify their email after registering:
export.beforeCreate = authClient.functions().beforeCreateHandler((user, context) => {
const locale = context.locale;
if (user.email && !user.emailVerified) {
// Send custom email verification on sign-up.
return admin.auth()
.generateEmailVerificationLink(user.email)
.then((link) => {
return sendCustomVerificationEmail(
user.email, link, locale
);
});
}
});
export.beforeSignIn = authClient.functions().beforeSignInHandler((user, context) => {
if (user.email && !user.emailVerified) {
throw new gcipCloudFunctions.https.HttpsError(
'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
}
});
However, as far as I understand, generateEmailVerificationLink() can only be called to generate email verification link of an existing Firebase Authentication user. At this stage (while running beforeCreate blocking function), the user is not created yet.
Now I am wondering, I am missing something or is the Google documentation wrong?
No.
User data is created upon registration in the database.
Then, you may send an Email-Verification with a link automatically.
This Email-Verification just updates the field emaiVerified of said user data.
If you want to prevent users with unverified Emails from logging in, you need to adjust your Login page and check whether emaiVerified is true.
Important: Google will sign in a user right upon registration whether the email is verified or not, as this is the expected behavior from the perspective of a user. Email verification is ensured on the second, manual login.
(Also, please do not screenshot code.)
You can let a user sign in via email link at first, and call firebase.User.updatePassword() to set its password.
I am using Angular-Firebase, this is the logic code.
if (this.fireAuth.isSignInWithEmailLink(this.router.url)) {
const email = this.storage.get(SIGN_IN_EMAIL_KEY) as string;
this.storage.delete(SIGN_IN_EMAIL_KEY);
this.emailVerified = true;
this.accountCtrl.setValue(email);
from(this.fireAuth.signInWithEmailLink(email, this.router.url)).pipe(
catchError((error: FirebaseError) => {
const notification = this.notification;
notification.openError(notification.stripMessage(error.message));
this.emailVerified = false;
return of(null);
}),
filter((result) => !!result)
).subscribe((credential) => {
this.user = credential.user;
});
}
const notification = this.notification;
const info = form.value;
this.requesting = true;
form.control.disable();
(this.emailVerified ? from(this.user.updatePassword(info.password)) : from(this.fireAuth.signInWithEmailLink(info.account))).pipe(
catchError((error: FirebaseError) => {
switch (error.code) {
case AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY.POPUP_CLOSED_BY_USER:
break;
default:
console.log(error.code);
notification.openError(notification.stripMessage(error.message));
}
this.requesting = false;
form.control.enable();
return of(null);
}),
filter((result) => !!result)
).subscribe((result: firebase.auth.UserCredential) => {
if (this.emailVerified) {
if (result.user) {
notification.openError(`注册成功。`);
this.router.navigateByUrl(this.authService.redirectUrl || '');
} else {
notification.openError(`注册失败。`);
this.requesting = false;
form.control.enable();
}
} else {
this.storage.set(SIGN_IN_EMAIL_KEY, info.account);
}
});
Mate, if database won't create a new user using his email and password, and you send him email verification which will create his account, how the heck database will know his password? If it didn't create his account in the first step? Stop overthinking and just secure database using rules and routes in application if you don't want user to read some data while he didn't confirm email address.
It is that simple:
match /secretCollection/{docId} {
allow read, write: if isEmailVerified()
}
function isEmailVerified() {
return request.auth.token.email_verified
}
I think the blocking function documentation is wrong.
beforeCreate: "Triggers before a new user is saved to the Firebase Authentication database, and before a token is returned to your client app."
generateEmailVerificationLink: "To generate an email verification link, provide the existing user’s unverified email... The operation will resolve with the email action link. The email used must belong to an existing user."
Has anyone come up with a work around while still using blocking functions?
Using firebase rules to check for verification isn't helpful if the goal is to perform some action in the blocking function, such as setting custom claims.

Firebase with Google Cloud Function - Realtime Database Validation to check username not already taken

I have found various other posts that all seem to make me believe that my implementation should be working but something is still missing.
Here is my Firebase Realtime database rules:
"usernames": {
".read" : true,
".write" : "auth !== null",
"$username":{
".validate": "!root.child('usernames').hasChild($username)"
}
}
Inside my usernames node i have for example:
"usernames"
|---"johnDoe123" : "XrG34odsfla82343174094389"
|--- etc.....
I have a cloud function that I am calling by passing a token and when the cloud function runs it attempts to update the "usernames" node by adding the new requested username assuming the username is not yet taken.
Here is the portion of the function that I am using that writes to the Firebase usernames node:
let newUser = {}
newUser[req.query.username] = decoded.uid
admin.database()
.ref('usernames')
.update(newUser)
.then(() => {
console.log(`New username <${req.query.username}> added`)
res.status(200).send(decoded)
}).catch(e=>{
console.log('Error', e)
})
The cloud function is updating the usernames node but my problem is that I need to have it reject the update with the validation if another child node has the same username. As of now my rules continue to allow the update to occur despite having another child node with the same exact username which is associated to a different uid.
Any ideas what I'm doing wrong?
Not sure if this is the best way, but it works for my purpose which is to check the first time a user is getting setup within my app to determine if the username being requested is already taken. If so it sends back JSON response that it was already taken. If it was not taken then it creates the record and then sends back JSON response of success..
module.exports = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const tokenId = req.get('Authorization').split('Bearer ')[1];
return admin.auth().verifyIdToken(tokenId)
.then((decoded) => {
admin.database()
.ref('usernames')
.child(req.query.username)
.once('value', function(snapshot) {
if(snapshot.val() === null){
let newUser = {}
newUser[req.query.username] = decoded.uid
admin.database()
.ref('usernames')
.update(newUser)
.then(() => {
console.log(`New username <${req.query.username}> added`)
res.status(200).send({
success: true,
message: "Username created"
})
}).catch(err=>{
res.status(500).send(err)
})
}
else{
res.status(200).send({
success: false,
message: "Username already taken"
})
}
});
})
.catch((err) => {
res.status(401).send(err)
});
})
});
If you use the Firebase Authentication to register all users, then this system will tell the user automatically that the email address they have used to register with is already an account.
You could also make the username part of the tree path /users/AUserName so it would return null when queried

Get Firebase User.ProviderID on auth/wrong-password when using signInWithEmailAndPassword

I have an Application that uses Firebase for Authentification.
I allow users to sign in with either Google/Facebook/Twitter or using an Email and Password.
Additionally I have activated within Firebase that Users can only create one account per email.
I want to cover the Following case:
A users signs up with facebook and gets a user account created with their facebook email (e.G. facebookuser#gmail.com).
A few days later the user comes back to the app, but forgot that he signed up using facebook and tries loging in with their email adress facebookuser#gmail.com and their usual password instead.
The firebase.auth().signInWithEmailAndPassword(email, password) method throws an auth/wrong-password error, as no password was given using the facebook login method but the email does exist.
Instead of showing a useless "Wrong password or the account corresponding to the email does not have a password set." error I would like to check instead which provider was used for signing up and reminding the user to sign in with the provider instead.
Unfortunately there doesn't seem to exist a method to get the User.ProviderID for a given email or to understand if the auth/wrong-password error was given because the user typed in a wrong password or if there was no password given in first place as the user signed up with an OAuthProvider instead.
Okay, I have missed the fetchProvidersForEmail method.
Just for Reference: (not the final code I will be using)
Uses signInWithEmailAndPassword to check if a User exists
If it doesn't it creates it using createUserWithEmailAndPassword
If a user exits, it tries to login and catches errors
If the error is auth/wrong-password it checks with fetchProvidersForEmail if the user has used a Provider to sign up
firebase.auth().signInWithEmailAndPassword(email, password).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
// User not found? Create user.
if ( errorCode === 'auth/user-not-found' ) {
firebase.auth().createUserWithEmailAndPassword(email, password).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
if ( errorCode == 'email-already-in-use' ) {
alert('You already have an account with that email.');
} else if ( errorCode == 'auth/invalid-email' ) {
alert('Please provide a valid email');
} else if ( errorCode == 'auth/weak-password' ) {
alert('The password is too weak.');
} else {
alert(errorMessage);
}
console.log(error);
});
// Wrong Password Error
} else if ( errorCode === 'auth/wrong-password' ) {
// Check if User has signed up with a OAuthProvider
firebase.auth().fetchProvidersForEmail(email).then(function( result ){
// … show OAuthProvider Login Button
});
alert('Wrong password. Please try again');
} else {
alert( errorMessage );
}
console.log( error );
});

How to check if email already exist for a user (emails[0].address) before try to use it to change the email of another user?

I have a method to change the user's address (every user has only one address, so it's emails[0].address).
In the method, Accounts.addEmail(this.userId, newemail); block finely the adding of an emails[0].address if another user has the same. I receive in client the error.reason === 'Email already exists.' Great.
But before calling Accounts.addEmail(this.userId, newemail);, I need to call Accounts.removeEmail(this.userId, emailold); who will remove the old address and leave emails[0].address free for Accounts.addEmail(this.userId, newemail); (wich, when there is no email address in an account, use nicely by default emails[0].address).
So how can I handle and stop Accounts.removeEmail(this.userId, emailold); if newemail is used as emails[0].address by any other user?
Below my method.
Thanks
// Change Email Address of the User
Meteor.methods({
addNewEmail: function(emailold, newemail) {
// this function is executed in strict mode
'use strict';
// Consistency var check
check([emailold, newemail], [String]);
// Let other method calls from the same client start running,
// without waiting this one to complete.
this.unblock();
//Remove the old email address for the user (only one by default)
Accounts.removeEmail(this.userId, emailold);
//Add the new email address for the user: by default, setted to verified:false
Accounts.addEmail(this.userId, newemail);
// Send email verification to the new email address
Accounts.sendVerificationEmail(this.userId, newemail);
return true;
}
});
You can update the users collection directly, and handle any errors yourself. This is what I do:
Meteor.methods({
"users.changeEmail"(address) {
check(address, String);
const existingAddressCheck = Meteor.users.findOne({"emails.0.address": address});
if(existingAddressCheck) {
if(existingAddressCheck._id === Meteor.userId()) {
throw new Meteor.Error("users.changeEmail.sameEmail", "That's already your registered email address!");
} else {
throw new Meteor.Error("users.changeEmail.existing", "An account with that address already exists");
}
}
return Meteor.users.update({_id: Meteor.userId()}, {$set: {"emails.0": {address, verified: false}}});
}
});

In meteor how to verify user password before running a method? [duplicate]

There are some irreversible actions that user can do in my app. To add a level of security, I'd like to verify that the person performing such an action is actually the logged in user. How can I achieve it?
For users with passwords, I'd like a prompt that would ask for entering user password again. How can I later verify this password, without sending it over the wire?
Is a similar action possible for users logged via external service? If yes, how to achieve it?
I can help with the first question. As of this writing, meteor doesn't have a checkPassword method, but here's how you can do it:
On the client, I'm going to assume you have a form with an input called password and a button called check-password. The event code could look something like this:
Template.userAccount.events({
'click #check-password': function() {
var digest = Package.sha.SHA256($('#password').val());
Meteor.call('checkPassword', digest, function(err, result) {
if (result) {
console.log('the passwords match!');
}
});
}
});
Then on the server, we can implement the checkPassword method like so:
Meteor.methods({
checkPassword: function(digest) {
check(digest, String);
if (this.userId) {
var user = Meteor.user();
var password = {digest: digest, algorithm: 'sha-256'};
var result = Accounts._checkPassword(user, password);
return result.error == null;
} else {
return false;
}
}
});
For more details, please see my blog post. I will do my best to keep it up to date.
I haven't done this before, but I think you will need something like this on your server
Accounts.registerLoginHandler(function(loginRequest) {
console.log(loginRequest)
var userId = null;
var username = loginRequest.username;
// I'M NOT SURE HOW METEOR PASSWORD IS HASHED...
// SO YOU NEED TO DO A BIT MORE RESEARCH ON THAT SIDE
// BUT LET'S SAY YOU HAVE IT NOW
var password = loginRequest.password;
var user = Meteor.users.findOne({
$and: [
{username: username},
{password: password}
]
});
if(!user) {
// ERROR
} else {
// VERIFIED
}
});
then you can call this function from the client side like this:
// FETCH THE USERNAME AND PASSWORD SOMEHOW
var loginRequest = {username: username, password: password};
Accounts.callLoginMethod({
methodArguments: [loginRequest]
});
I have a project on github for different purpose, but you can get a sense of how it is structured: https://github.com/534N/apitest
Hope this helps,
I have found the best way to validate the users password is to use the Accounts.changePassword command and
pass in the same password for old and new password. https://docs.meteor.com/api/passwords.html#Accounts-changePassword
Accounts.changePassword(this.password, this.password, (error) => {
if(error) {
//The password provided was incorrect
}
})
If the password provided is wrong, you will get an error back and the users password will not be changed.
If the password is correct, the users password will be updated with the same password as is currently set.

Resources