Why does sendSignInLinkToEmail(email, settings) not check whether the given email exists? - javascript

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.

Related

Adding Email Authentication to a Login Service that uses React and Firebase

I currently have a file for authentication called AuthContext.js and a seperate file called Login.js for my login page. Firebase is only imported into the AuthContext file and it currently works, the method appears as follows
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password);
}
And the method is called in the Login file inside of an async function.
async function handleSubmit(e) {
e.preventDefault();
if (passwordRef.current.value !== passwordConfirmRef.current.value) {
return setError("Passwords do not match");
}
try {
setError("");
setLoading(true);
await signup(emailRef.current.value, passwordRef.current.value);
navigate("/");
} catch (err) {
setError("Failed to create an account");
}
setLoading(false);
}
I am now attempting to integrate email authentication into the function. I would like to get it to where, if an account remains unverified for a elongated duration then it will delete the account, but I think that Is likely in the Firebase settings.
I found this article on geeksforgeeks that suggested calling the method in the following manner:
const signup = ()=>{
   auth.createUserWithEmailAndPassword(email , password)
   .then((userCredential)=>{
       // send verification mail.
     userCredential.user.sendEmailVerification();
     auth.signOut();
     alert("Email sent");
   })
   .catch(alert);
}
However, when I attempted to return this function in the form
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password).then(
(userCredentials) => {
userCredentials.user.sendEmailVerification();
auth.signOut();
auth.alert("Please verify your email address before logging in.");
}
);
}
It does not work properly, and I get error messages every time I try to sign up a new account. How do I properly implement this to where the promise returns properly and displays the correct message? Is there a way for me to link them to the /login page with an alert already on it that says "Please verify your email address before logging in."? Thanks in advance for any help!

Firebase email verification flow

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
}

Firebase PhoneAuthentication not returning verification Code

I tried to create a phone authentication using firebase and Angular. After setting recaptcha verifier and phone number and passed to firebase.auth.signInWithPhoneNumber() function. It returns only the verification Id and not verification code and it not sending sms to my number as well.
onSignInSubmit(phoneNumber) {
this.recaptcha = new firebase.auth.RecaptchaVerifier('recaptcha-container', {
'size' : 'invisible',
'callback': function(response) {
// reCAPTCHA solved, allow signInWithPhoneNumber.
}
});
this.afauth.auth.signInWithPhoneNumber(phoneNumber, this.recaptcha).then((confiramtionResult) => {
console.log(confiramtionResult);
});
}
You have to ask the user for the SMS code.
After the user provides it, you call:
var code = getCodeFromUserInput();
confirmationResult.confirm(code).then(function (result) {
// User signed in successfully.
var user = result.user;
// ...
}).catch(function (error) {
// Error occurred.
});

Parse Server / JS SDK, error 206 when saving a user object

I am having trouble using the Parse Server JS SDK to edit and save a user.
I am signing in, logging in and retrieving the user just fine, I can call without exception user.set and add/edit any field I want, but when I try to save, even when using the masterKey, I get Error 206: Can t modify user <id>.
I also have tried to use save to direcly set the fields, same result.
A interesting thing is that in the DB, the User's Schema get updated with the new fields and types.
Here is my update function:
function login(user, callback) {
let username = user.email,
password = user.password;
Parse.User.logIn(username, password).then(
(user) => {
if(!user) {
callback('No user found');
} else {
callback(null, user);
}
},
(error) => {
callback(error.message, null);
}
);
}
function update(user, callback) {
login(user, (error, user) => {
if(error) {
callback('Can t find user');
} else {
console.log('save');
console.log('Session token: ' + user.getSessionToken());
console.log('Master key: ' + Parse.masterKey);
user.set('user', 'set');
user.save({key: 'test'}, {useMasterKey: true}).then(
(test) => {
console.log('OK - ' + test);
callback();
}, (err) => {
console.log('ERR - ' + require('util').inspect(err));
callback(error.message);
}
);
}
});
}
And a exemple of the error:
update
save
Session token: r:c29b35a48d144f146838638f6cbed091
Master key: <my master key>
ERR- ParseError { code: 206, message: 'cannot modify user NPubttVAYv' }
How can I save correctly my edited user?
I had the exact same problem when using Parse Server with migrated data from an existing app.
The app was created before March 2015 when the new Enhanced Sessions was introduced. The app was still using legacy session tokens and the migration to the new revocable sessions system was never made. Parse Server requires revocable sessions tokens and will fail when encountering legacy session tokens.
In the app settings panel, the Require revocable sessions setting was not enabled before the migration and users sessions were not migrated to the new system when switching to Parse Server. The result when trying to edit a user was a 400 Bad Request with the message cannot modify user xxxxx (Code: 206).
To fix the issue, I followed the Session Migration Tutorial provided by Parse which explain how to upgrade from legacy session tokens to revocable sessions. Multiple methods are described depending on your needs like enableRevocableSession() to enable these sessions on a mobile app, if you're only having a web app, you can enforce that any API requests with a legacy session token to return an invalid session token error, etc.
You should also check if you're handling invalid session token error correctly during the migration to prompt the user to login again and therefore obtain a new session token.
I had the same error and neither useMasterKey nor sessionToken worked for me either. :(
Here's my code:
console.log("### attempt 1 sessionToken: " + request.user.getSessionToken());
var p1 = plan.save();
var p2 = request.user.save(null, {sessionToken: request.user.getSessionToken()});
return Parse.Promise.when([p1, p2]).then(function(savedPlan) {
...
}
I see the matching session token in log output:
2016-08-21T00:19:03.318662+00:00 app[web.1]: ### attempt 1 sessionToken: r:506deaeecf8a0299c9a4678ccac47126
my user object has the correct ACL values:
"ACL":{"*":{"read":true},"PC7AuAVDLY":{"read":true,"write":true}}
I also see a bunch of beforeSave and afterSave logs with user being "undefined". not sure whether that's related.
beforeSave triggered for _User for user undefined:
I'm running latest parser-server version 2.2.18 on Heroku (tried it on AWS and results are the same)
function login(logInfo, callback) {
let username = logInfo.email,
password = logInfo.password;
Parse.User.logIn(username, password).then(
(user) => {
if(!user) {
callback('No user found');
} else {
callback(null, user);
}
},
(error) => {
callback(error.message, null);
}
);
}
function update(userInfo, data, callback) {
login(userInfo, (error, user) => {
if(error) {
callback('Can t find user');
} else {
getUpdatedData(user.get('data'), data, (error, updateData) => {
if(error) {
callback(error);
} else {
user.save({data: updateData}, /*{useMasterKey: true}*/ {sessionToken: user.get("sessionToken")}).then(
(test) => {
callback();
}, (err) => {
callback(error.message);
}
);
}
});
}
});
}
For some reason, retrying to use sessionToken worked.
This is not how asynchronous functions work in JavaScript. When createUser returns, the user has not yet been created. Calling user.save kicks off the save process, but it isn't finished until the success or error callback has been executed. You should have createUser take another callback as an argument, and call it from the user.save success callback.
Also, you can't create a user with save. You need to use Parse.User.signUp.
The function returns long before success or error is called.

Meteor: check email does not exist in Meteor.users collection before creating an account

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");
}
}

Categories