Impersonate user in Meteor - meteor

I've built impersonate method in my Meteor application in order to login as another user, based on following article: https://dweldon.silvrback.com/impersonating-a-user. I also have Intercom integration (chat widget and user tracking). I would like to be able to disable on client side that Intercom widget, in order to avoid any tracking from Intercom application, when I am logged as another user (impersonating). I was thinking about creating on user profile impersonate boolean property, and updating it to true, when I am triggering that impersonate method for any user. The problem is that, I have no idea how to set it to false when impersonate method is finished. According to article, you can stop impersonating when you refresh the browser manually. Could you please help me, and find the best approach?

We can solve this in two parts:
When we start impersonating a user, keep track of who is impersonating who. Let's do this by first extending the impersonate method in the tutorial:
Meteor.methods({
impersonate: function(userId) {
check(userId, String);
if (!Meteor.users.findOne(userId))
throw new Meteor.Error(404, 'User not found');
if (!Meteor.user().isAdmin)
throw new Meteor.Error(403, 'Permission denied');
Meteor.users.update(this.userId, { $set: { 'profile.impersonating': userId }});
this.setUserId(userId);
}
});
Next we need to listen for a new login (which should happen on a browser refresh)
Meteor.onLogin(() => {
Meteor.call('clearImpersonation', (err, result) => {
if (err) console.log('Error clearing impersonation: ',err);
});
});
Meteor.methods({
clearImpersonation(){
const user = Meteor.users.findOne(this.userId);
if (user && user.profile && user.profile.impersonating) Meteor.users.update(user._id,{ $unset: 'profile.impersonating' });
return;
}
});
Now in your UI you can disable Intercom by checking for the existence of Meteor.user().profile.impersonating

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.

Meteor - On page refresh, get user data in triggersEnter

The application I am building is designed so user's register with just an email address and password, but when they login, it is required that they then fill in a username and birthday.
I have created a route group using FlowRouter for authenticated users:
var authRoutes = FlowRouter.group({
name: 'auth',
triggersEnter: [function(context, redirect) {
// Is the user logging in or already logged in?
if(Meteor.loggingIn() || Meteor.userId()) {
//They are, so track when user is available
Tracker.autorun(function() {
if(Meteor.user()) {
// User is available
}
});
} else {
// They are not
FlowRouter.redirect('/login');
}
}],
});
However, this seems like the wrong way to go about this (having to track when the user is available in the route group). Is there a different way to achieve the same thing?

How to make sure user in app's session and google's session are the same when using accounts-google

I am using accounts-google on my app and I'd like to solve rather odd authentication scenario.
A logs in so now as an app session and a google session
A switches to gmail and logs out there.
Now, mind you that, A is actually still logged in on the meteor app.
B comes along, logs in to Gmail using his account.
Switches to the meteor app to see that he's logged in, but oddly, logged in with A's account.
This scenario leads to lots of confusions and people unknowingly using other users' accounts where they share computers.
So, basically, I need to users in the meteor session and google session to be the same, and if not, ensure that the current meteor session is invalidated and loginWithGoogle() is called again.
How can I solve this?
It seems impossible with Meteor's current accounts package, although one could create a new one using Google's latest googleplus api.
But there seems to exist a workaround by:
1) Set up onBeforeAction hooks on your router to login the user automatically (which asks for credentials if user is not logged in to external service)
var loginWithGoogle = function() {
if (Meteor.isClient) {
Session.set('loginError', undefined);
Meteor.loginWithGoogle({
loginStyle : "redirect",
requestPermissions : ['profile', 'email'],
requestOfflineToken: true
}, function (err) {
if (err)
Session.set('loginError', 'reason: ' + err.reason + ' message: ' + err.message || 'Unknown error');
});
}
}
var requireLogin = function() {
if (! Meteor.user()) {
if (Meteor.loggingIn()) {
this.render(this.loadingTemplate);
} else {
console.log('The app is automatically asking for you to log in.');
loginWithGoogle();
}
} else {
this.next();
}
}
Router.onBeforeAction(requireLogin, {except: ['some-special-public-route']});
2) Log the user out when they are navigating away from every page (caveat: login/logout gets called everytime the user navigates within the app)
Meteor.startup(function(){
$(window).bind('beforeunload', function() {
closingWindow();
});
});
closingWindow = function(){
console.log('The app is automatically logging you out because you are leaving.');
Meteor.logout();
}
3) improvement area: set a session variable to track user's navigation within the app and run the unload event depending on the variable.

How do I remember a logged user using AngularFire and Firebase Simple Login?

This is what I'm using for Authentication:
new FirebaseSimpleLogin(new Firebase("firebaseURL"), function(error, user) {
if (error) {
} else if (user) {
// angular ngCookies service
$cookies.user = args.user.email;
} else {
}
});
To store all the todos per user, I'm simply storing his email ID against each todo. The problem with this approach is that I can modify the cookie replacing with someone else's email then I could see their todos.
Is there a way to know who has logged in using Firebase simple login instead of looking at the cookie? Is there any better way?
You can use angularFireAuth, which will bind a model to user authentication state:
function MyController($scope, angularFireAuth) {
var ref = new Firebase("https://<my-firebase>.firebaseio.com/");
angularFireAuth.initialize(ref, {scope: $scope, name: "user"});
}
$scope.user will then be null if the user is logged out, and set to a user object when the user is logged in. Learn more at http://angularfire.com/documentation.html#authentication

Logging in via Firebase Email/Password

I am trying to build a basic web application w/ user authentication via email/password registration using Firebase.
My setup right now includes a main.js file that consists of the following:
var dbRef = new Firebase('https://url.firebaseIO.com');
var authClient = new FirebaseAuthClient(dbRef, function(error, user) {
if (error) {
// an error occurred while attempting login
console.log(error);
} else if (user) {
// user authenticated with Firebase
console.log('User ID: ' + user.id + ', Provider: ' + user.provider);
} else {
// user is logged out
console.log('logged out!');
}
});
function next(){
window.location = 'index.html';
}
function test(){
authClient.login('password', {
email: email,
password: password,
rememberMe: true
},next());
// window.location = 'index.html';
}
I obtain email/password values from a form and login. That works. But as soon as I include a callback function to then redirect them to a new authenticated page, it no longer works. In fact, most of the time I get an "UNKOWN ERROR" response.
When I get to the next page, I am no longer logged in. If I remove the next() function and stay on the same page, it works - even if I then trigger the next function from the console. Is there a different way you are supposed to proceed to another page?
I'm pretty sure there is some sort of communication issue (possibly the login does not get a return before the page is switched?) because if I add a 1s timeout before the next function, it then works. But surely this is not best practice?
Thanks!
Per https://www.firebase.com/docs/security/simple-login-email-password.html, the authClient.login() method does not actually accept a callback, so the problem you're seeing is likely the result of navigating away from the current page before the callback is returned, as you suggested.
I would recommend doing the redirect in the callback you're passing during the instantiation of the auth client. (new FirebaseAuthClient(ref, callback)) and redirect if you detect a logged-in user. This callback will be invoked once upon instantiation with the current authentication state of the user, and then again any time the user's authentication state changes (such as on login or logout).

Resources