Meteor Accounts-ui to wait for email verification before user access - meteor

A Meteor web app on the local machine uses Accounts-password and Accounts-ui.
The user enters email and password in order to create a new account.
It needs to be configured so that the user gets a userId (thus become a currentUser) only after verifying the creation of the accounts via email.
The app has an smtp variable set up. It send the verification email fine. but it failed to prevent the user to 'login' before the verification. How can it be made so that it only gives a valid userId after the verification? Thanks
//server
Accounts.onCreateUser(function(options, user) {
Accounts.config({
sendVerificationEmail: true
});
return user;
});

It's not quite as simple as all that. Meteor needs a user to have an ID in order to send them an email. What you want to do is probably not actually prevent a login, but prevent the In order to do what you want, you'll have to prevent them from seeing anything when they are logged in. Something like this in your top-level template:
{{#if userVerified}}
// Body of your app here
{{> Template.dynamic template=main}}
{{else}}
You are not yet verified
{{/if}}
And this in the corresponding .js file:
Template.body.helpers({
userVerified () {
const user = Meteor.user();
return user.emails[0].verified;
}
});

I define a global helper:
Template.registerHelper('isVerified',function(){ // return a true or false depending on whether the referenced user's email has been verified
if ( Meteor.user() && Meteor.user().emails ) return Meteor.user().emails[0].verified; // look at the current user
else return false;
});
And then in any template (typically my master template) I can do:
{{#if isVerified}}
content that only verified users should see
{{else}}
Please check your email for your verification link!
{{/if}}

As #Season has pointed out, the verification requires that a userId be created in order to link it to the verification email.
What you could do is prevent a user from logging in if they haven't verified their email. See this answer to another question which accomplishes that:
https://stackoverflow.com/a/24940581/3512709

Related

Resending Firebase auth verification emails when the user's email is no longer accessible

So far in my project, I have set up a basic user management system. However, I'm unable to figure out a way to resend verification link after the user registers.
For example: When the user signs up, createUserWithEmailAndPassword is called which returns a promise. The promise is now resolved using then (or await) to which sendEmailVerification is called. This is all fine.
Note: The above flow is what I currently have implemented to for user management on the client side with Firebase Auth.
However, what if the user happens to delete this email or for whatever reason has no access to it at all. I want to be able to resend the link.
This uses Firebase Admin SDK on the backend and is an example of how to generate the verification email on the server-side. However, it appears that it is used in conjunction with account creation. In addition, it appears that Firebase Auth follows the same set of restrictions.
Not too sure where to go next and was wondering if there are any suitable workarounds.
Thanks.
Add a link to the login page to resend the verification email.
Then, trigger something along these lines:
sendEmailVerification() async {
await FirebaseAuth.instance.currentUser?.sendEmailVerification();
}
Another option is to check during the login process whether the user verified the email. If not, resend it. Along these lines:
signInWithEmailAndPassword(
String email,
String password,
) async {
try {
final credential = await FirebaseAuth.instance
.signInWithEmailAndPassword(email: email, password: password);
if (credential.user!.emailVerified == false) {
await _sendEmailVerification();
return ... // not verified, but email sent
}
return ... // success
} on FirebaseAuthException catch (e) {
return ... // error
} catch (e) {
return ... // error
}
}
The problem described here I think is as follows ( I am facing it as well):
=> some new User enters his Email and Password to create an account
=> we call createUserWithEmailAndPassword(email, password) => account can be found in firebase console under "Authentication" => in the app Auth.currentUser is NOT NULL. This is because acc. to documentatoin of "createUserWithEmailAndPassword" we read:
"#remarks
On successful creation of the user account, this user will also be signed in to your application."
=> Then we call sendEmailVerification(Auth.currentUser) - everything works, User Auth.currentUser gets his Email with verification link.
BUT. What if he does not click this link (maybe it went to spam)? He searcehs it, time passes, he maybe switches off the PC or closes the App. And after some time tries again: opens the App, tries to register again...
=> as he enters again the same E-mail he entered when he tried to register for the first time, our createUserWithEmailAndPassword() will give an error (because the User with such E-mail, even though it is not verified, is already in the database) => Auth.currentUser will be NULL(!!!)
=> if now you try to "REsend the verification E-Mail" with sendEmailVerification(Auth.currentUser) - it will not send anything. Because the user is null.
So, the way out of this situation is to generate verficication link based on the E-mail only and send it somehow to the User via E-mail: https://firebase.google.com/docs/auth/admin/email-action-links#java_2

Firebase Authentication : Lookup a user by Email

I am using Firebase Authentication with Email and Password
I would like to know if i can 'lookup' a user by email only while also not being signed in as a user
The reason I'd like to do this is, is to simply identify if I am already a user of the system, using an email address only
I looked at this older thread but it appears to be the previous version of Firebase
Is it possible to do this in the current Firebase, or my alternative would be to keep this information available (and open to all?) to find out if a given email is part of my system?
I use fetchProvidersForEmail(email)
and if the result return as empty array then, this email hasn't been use to sign up.
firebase.auth().fetchProvidersForEmail(email)
.then(providers => {
if (providers.length === 0) {
// this email hasn't signed up yet
} else {
// has signed up
}
});
You can look up user information by email:
firebase.auth().getUserByEmail(email)
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully fetched user data:', userRecord.toJSON());
})
.catch(function(error) {
console.log('Error fetching user data:', error);
});
I'd like to make things more clear that this method does not exist — one could be looking for it in the firebase client library, in which it has never been available in the first place and it wouldn't be a good idea to have anyway. This method is part of the admin SDK, so in order to call the method, you need to run it on the server, and invoke it from the client. OP didn't scope the question to firebase client library, so my answer is still correct.
Retrieve user data
The new method of creating users with email password returns a value whether the given email address is already in use. See here
import { fetchSignInMethodsForEmail } from 'firebase/auth'
fetchSignInMethodsForEmail(auth, registerEamil).then((result) =>{
console.log("result", result);
if (result.length === 0) {
Navigate("/authentication/select_role/" +
registerEamil)
} else {
Navigate('/')
}
Server side option:
https://cloud.google.com/identity-platform/docs/reference/rest/v1/projects.accounts/lookup
POST https://identitytoolkit.googleapis.com/v1/projects/{targetProjectId}/accounts:lookup
{ "email": ["rodneydangerfield#stackoverflow.kom"] }
As for today 2021 Nov. 18th, there is no way provided by the Firebase SDK to fetch a user by email.

Meteor.loginWithPassword: Amend verification

I'm using accounts-password and Meteor.loginWithPassword to authenticate a user. I want to prevent the user from being able to login unless the email address is verified.
Currently I do this using a Meteor.method and a callback. The callback will only trigger the call to Meteor.loginWithPassword() if the relevant user has a verified email address and trigger the current form to display a validation error. if not. However a tech-savvy user can still trigger Meteor.loginWithPassword() directly in the console. Is there a best practice way to prevent this?
You can prevent logins for users with an email address which has not been verified by using accountsServer.validateLoginAttempt(func), for example:
if (Meteor.isServer) {
Accounts.validateLoginAttempt(function(attempt) {
var user = attempt.user;
if (!user.emails[0].verified) throw new Meteor.Error(403, 'E-Mail address not verified.');
return true;
});
}
If you return false or throw an exception, the login will be aborted.

Meteor.js google account : filter email and force account choser

In my Meteor.js application, I'm using the accounts-google package in order to be connected with a google account. I have two questions about it.
First, is there a simple way to filter the account used? I would like that the users can connect only with google accounts belonging to my company. Our google account mails end with #mycompany.com. So it would be a simple mail filtering.
I already done that with some post log in hooks but I was wondering if there was a simpler way for doing it.
My second question is how to force the opening of the google account choser. For now, if I try to connect with a wrong google account, and if I only added this account (like in gmail, drive, etc), the google choser doesn't pop and automatically connect with this wrong account. So, in this case, the user is totally blocked (my application disconnect him if he tries to log in with a wrong account but the google account module doesn't propose him to connect with another account).
Thank you for your help.
In order to restrict signup/login to your domain, simply do on the server:
var checkEmailAgainstAllowed = function(email) {
var allowedDomains = ['mycompanydomain.com'];
var allowedEmails = ['otheruser#fromotherdomain.com','anotheruser#fromanotherdomain.com'];
var domain = email.replace(/.*#/,'').toLowerCase();
email = email.toLowerCase();
return _.contains(allowedEmails, email) || _.contains(allowedDomains, domain);
};
Accounts.config({
restrictCreationByEmailDomain: function(email) {
if (!email) {
throw new Meteor.Error(403,'This email address is not allowed');
}
if (!checkEmailAgainstAllowed(email)) {
throw new Meteor.Error(403,'This email domain is not allowed');
}
return true;
}
});
And to login, you'll need on the client:
Meteor.loginWithGoogle({
forceApprovalPrompt: true, //this is what you want, to rerequest approval each time that prompts the google login prompt
loginStyle : "redirect", //or not, depending on your need
requestPermissions : ['profile', 'email'],
requestOfflineToken: true
}, function (err) {
if (err)
// set a session variable to display later if there is a login error
Session.set('loginError', 'reason: ' + err.reason + ' message: ' + err.message || 'Unknown error');
});
Side note:
Alternatively, you can set up your routes so that every time a new route is called, you login, and every time a route is destroyed or on windows's unload, you call logout. This causes login/logout roundtrip everytime the route changes, but you'll make sure that the new user always has a fresh session
Edit:
When you log out of your meteor app, you don't log out of google. That's how oauth works. So, basically, if you want a meteor log out to also log the user out of their google account, so that the next time they come back, they need to provide credentials again, you should do:
Meteor.logout(function(e) {
if (e) {
console.log("Could not log the user out")
} else {
window.location.replace('https://accounts.google.com/Logout');
}
});
This uses the callback of Meteor.logout() so that when the logout is successfull, the user is redirected to google's central account logout url where the user is also logged out of all google services.

Linking Robomongo to an automatic email sending service?

I have an application running on meteor.js and mongo.db. I am using robomongo as a tool for mongo.db. Now I'd like to do the following:
1. Somebody registers with my service (adding email to db)
2. I want to send an automatic welcome email to that person.
Is there any possibility how to do it?
You need an email server (SMTP), and then use the meteor email library. If you don't have an email server and don't want to create one, use a commercial solution. (Example)
Full working example you can find here: http://meteorpad.com/pad/iNMBHtNsv7XKHeq44
Notice it creates new users from within Meteor app, but the same effect will be when you use Robomongo or any other way of updating MongoDB.
First install package Email to be able to use Email.send.
In below example I assume that adding new user to collection Meteor.users should fire sending "invitation" email.
In very similar way you can detect if email was added to user object
(user.emails.length was changed) and then send email.
Then take a look at code:
// SERVER SIDE CODE:
Meteor.startup(function () {
// clean users on app resetart
// Meteor.users.remove({});
if(Meteor.users.find().count() === 0){
console.log("Create users");
Accounts.createUser({
username:"userA",
email:"userA#example.com",
profile:{
invitationEmailSend:false
}
}) ;
Accounts.createUser({
username:"userB",
email:"userB#example.com",
profile:{
invitationEmailSend:false
}
})
}
Meteor.users.find().observe({
added:function(user){
console.log(user.username, user.profile.invitationEmailSend)
if(!user.profile.invitationEmailSend){
Email.send({
from: "from#mailinator.com",
to: user.emails[0].address,
subject: "Welcome",
text: "Welcome !"
});
// set flag 'invitationEmailSend' to true, so email won't be send twice in the future ( ex. during restart of app)
Meteor.users.update({_id:user._id},{$set:{"profile.invitationEmailSend":true}});
}
}
})
});
Above code will send email to users who don't have flag equal to true in profile.invitationEmailSend. After e-mail is sent server updates user document in db and set user.profile.invitationEmailSend to true.
Whenever you add users to mongoDB (using Robomongo or any other way), then added function is executed and e-mail is send only to new users.

Resources