I am a newbie to firebase. I need a way to verify an email address during the signup process. The user should NOT be automatically logged in without verification. What code can I add to allow me to check if the email is verified or not. If yes, the user can login. If not, they need to very first. Please see code below
async registerUser(customer: Customer, password: string) {
try {
const newUserCredential = await this.firebaseAuth.createUser(
customer.email,
password
);
// store user details in firestore
this.firebaseStore.storeUserDetails(newUserCredential, customer);
// send email verification
await newUserCredential.user.sendEmailVerification();
} catch (error) {
console.error(error);
throw new Error(error.message);
}
}
You just need to check the emailVerified property on the User object.
const {emailVerified} = this.firebaseAuth.currentUser
if (emailVerified) {
// Email is verified
} else {
console.log("Email is not verified")
// Alert user
}
I have implemented a Firebase SignUp with username, email and password. Basically what I am doing is:
1- Create user with email and password (if the username and email are not used by other users)
2- Add the username to the user
Like this:
firebase
.createUserWithEmailAndPassword(email, password)
.then((currentUser) => {
// Get the username from the input
const username = this.usernameInput.current.getText();
// Create a user with this username and the current user uid in the db
firebase.createUserWithUsername(username, currentUser.user.uid); // <----------
})
.catch((err) => {
// ...
});
And my createUserWithUsername function basically do this:
createUserWithUsername = (username, userId) => {
/*
Create a document in the usernames collection
which uid (of the document itself, not a field) is the given username.
*/
// Pass username to lowercase
username = username.toLowerCase();
// Initial user's data
const data = {
email: this.auth.currentUser.email,
username,
};
return this.db
.collection("usernames")
.doc(username)
.set({ userId })
.then(() => {
this.db.collection("users").doc(userId).set(data);
})
.catch((err) => {
console.log(err);
throw err;
});
/*
Pd: As firestore automatically removes empty documents, we have to
assign them a field. The user's id is a good option, because it will help us to
update usernames faster, acting like a 'Foreign Key' in a NoSql DB.
*/
};
My question is? Is it wrong to leave this code on the client side? Can it be a security problem? Do I need to move this to a cloud function / backend?
This is my firestore security rule for the usernames collection:
match /usernames/{username} {
function isUsernameAvailable() {
return !exists(/databases/$(database)/documents/usernames/$(username));
}
allow read: if true;
allow write, update: if isSignedIn() && isUsernameAvailable();
// TODO - Allow delete?
}
I would really appreciate any guide for this. Thank you.
I don't understand, why firebase.auth().sendSignInLinkToEmail(email, settings) sends a login email to an email address which has not been registered yet.
Moreover, when I then click the link, which opens the site where I check the email with firebase.auth().signInWithEmailLink(email, signInEmailLink), the email gets registered and the user is logged in!
I think that's curios, because I've build a separate registration process.
I would expect, that I get an error when I call firebase.auth().sendSignInLinkToEmail(email, settings) with an unregistered email.
What am I doing wrong?
That's my code for sending the email and signing in when user clicked the link.
async login(email) {
try {
const settings = {
handleCodeInApp: true,
url: encodeURI(`${location.protocol}//${location.host}/#!/user/verifizieren`),
};
await firebase.auth().sendSignInLinkToEmail(email, settings);
window.localStorage.setItem(storageKeyEmail, email);
} catch(error) {
console.error(error);
throw error;
}
},
async verify(email, link) {
const signInEmailLink = link || window.location.href;
if(!firebase.auth().isSignInWithEmailLink(signInEmailLink)) {
return Promise.reject('auth/link-invalid');
}
try {
await firebase.auth().signInWithEmailLink(email, signInEmailLink);
firebase.auth().currentUser.reload();
firebase.auth().currentUser.getIdToken(true);
window.localStorage.removeItem(storageKeyEmail);
} catch(error) {
console.error(error);
throw error;
}
},
Sign in with email link is designed to also work for new email accounts. The accounts are not required to already exist.
If you want to block the email sending, you can check if the account exists before hand:
firebase.auth().fetchSignInMethodsForEmail(email).then((signInMethods) => {
if (signInMethods.length === 0) {
// New user.
} else {
// Existing user.
}
});
However, the above is only client side enforced and thus can be bypassed by the user by calling the REST API.
I am getting the [object,Object ] in my server terminal instead of whole user data . I do not know what does that mean... I think I did all perfectly but still unable to get the whole data. I specified of sailjs server.
How to get whole user data instead of [object,Object]?
module.exports = {
/**
* Check the provided email address and password, and if they
* match a real user in the database, sign in to Medool.
*/
login: function (req, res) {
// Try to look up user using the provided email address
User.findOne({
email: req.param('email')
}, function foundUser(err, user) {
if (err)
return res.negotiate(err);
if (!user)
return res.notFound();
// Compare password attempt from the form params to the encrypted password
// from the database (`user.password`)
require('machinepack-passwords').checkPassword({
passwordAttempt: req.param('password'),
encryptedPassword: user.encryptedPassword
}).exec({
error: function (err) {
return res.negotiate(err);
},
/*
If the password from the form params doesn't checkout w/ the encrypted
password from the database...
*/
incorrect: function () {
return res.notFound();
},
success: function () {
// Store user id in the user session
console.log("User form the login check" +user)
req.session.me = user.helpsterId;
console.log(req.session.me);
// All done- let the client know that everything worked.
return res.ok();
}
});
});
}
};
Output when lifted server is [object, Object]in console
Try it,
console.log(user);
console.log(JSON.stringify(user));
console.log("User form the login check" +user);
and write the result for us.
I have a form where users enter their email address and password into a join form. This creates the account but I now want to develop it further.
client.js:
Template.joinForm.events({
'submit .form-join': function(e, t) {
e.preventDefault();
var email = t.find('#email').value,
password = t.find('#password').value,
username = Random.id(),
array = [],
profile = {
nameOfArray: array
};
Accounts.createUser({
email: email,
username: username,
password: password,
profile: profile
}, function(error) {
if (error) {
alert(error);
} else {
Router.go('/');
}
});
}
});
Before creating a user account, how do you:
Check if the email variable from the joinForm does not already exist in the Meteor.users collection. Processing this on the server?
If email does exist, then reject user creation?
I have seen the new function and wondering if I can use this http://docs.meteor.com/#/full/accounts_validatenewuser
Accounts.validateNewUser(function (user) {
// pseudo if statement code
if (email not exist) {
// 1. create the user account and then
Accounts.sendVerificationEmail(userId, [email])
} else {
throw new Meteor.Error(403, "email address is already registered");
}
});
Thank you for reading this.
I'm unclear as to whether to use Accounts.createUser or Accounts.onCreateUser and which code should be on the client, and which on the server. My aim is to build the account securely, therefore, deny any other modification privileges during this process from the console.
The extra empty array nameOfArrayis now created on the server if the account is allowed to be created, ie, passing the validateNewUser function. Of course, you can add more validation checks for example, password length.
client.js:
Template.joinForm.events({
'submit .form-join': function(e, t) {
e.preventDefault();
var email = t.find('#email').value,
password = t.find('#password').value,
username = Random.id();
Accounts.createUser({
email: email,
username: username,
password: password,
profile: profile
}, function(error) {
if (error) {
alert(error.reason);
} else {
Router.go('/');
}
});
}
});
server.js:
Accounts.onCreateUser(function(options, user) {
var newEmail = user.emails[0].address;
console.log(newEmail);
var emailAlreadyExist = Meteor.users
.find({"emails.address": newEmail}, {limit: 1})
.count() > 0;
console.log(emailAlreadyExist + ' already exists');
if (emailAlreadyExist === true) {
throw new Meteor.Error(403, "email already registered");
} else {
profile = options.profile;
profile.nameOfArray = [];
user.profile = profile;
return user;
}
});
I've found that Accounts.createUser has it's own validation built in and checks for already existing email/login.
Meteor docs: Accounts.createUser:
If there are existing users with a username or email only differing in
case, createUser will fail.
Thus Accounts.onCreateUser doesn't even fire if Accounts.createUser email/login validation throws error.
The Accounts.validateNewUser function requires users to validate their email after submitting. It's basically that step where, after you sign up for something, before you can sign in you have to enter a code that's sent to you on your email or mobile device -- basically, it ensures that the user is who they say they are. It's what might prevent you from signing up with the email totallyfake#totally_not_a_real_place.com.
If I'm reading your question right, you're more interested in seeing if an email is unique than in seeing if the user actually owns that email account. You can do this with Accounts.onCreateUser, which runs on the server:
Called whenever a new user is created. Return the new user object, or throw an Error to abort the creation.
The full process will look something like this. On the client, exactly what you have:
Template.joinForm.events({
'submit .form-join': function(e, t) {
e.preventDefault();
var email = t.find('#email').value,
password = t.find('#password').value,
username = Random.id(),
array = [],
profile = {
nameOfArray: array
};
Accounts.createUser({
email: email,
username: username,
password: password,
profile: profile
}, function(error) {
if (error) {
alert(error);
} else {
Router.go('/');
}
});
}
});
Then, on the server, before the user is actually created, it will run the user through your onCreateUser function, and whatever you return will be inserted into the users collection:
Accounts.onCreateUser(function(options, user) {
var email = user.emails[0];
if (!email) { throw new Meteor.Error("You must provide a non-empty email"); // this may actually not be necessary -- createUser might do it for you
if (Meteor.users.find({emails: email}) {
throw new Meteor.Error("A user with email " + email + " already exists!");
}
... // whatever other transformation you might want
return user;
});
You might also check out the accounts-ui package, since depending on how much you want to do vary from a vanilla implementation of user accounts, a lot may already be done for you.
The Accounts.validateNewUser is used to check if the fields of the user object comply with the desired format, returning true or false accordingly.
To check if the email is already registered, I think you should include this verification in the Accounts.onCreateUser function (http://docs.meteor.com/#/full/accounts_oncreateuser).
Without having tested the code, you can try something like this:
Accounts.validateNewUser(function (user) {
// Check compliance of the user object fields, using check http://docs.meteor.com/#/full/check
});
Accounts.onCreateUser(function(options, user) {
if (options.profile){
user.profile = options.profile;
}
if (Meteor.users.find({email: user.email}).fetch == 0) {
if(Accounts.validateNewUser(user)){
Accounts.sendVerificationEmail(userId, [email]);
return user;
} else {
throw new Meteor.Error(403, "Error checking user fields");
} else {
throw new Meteor.Error(403, "email address is already registered");
}
}