Meteor account enrollment - meteor

I'm looking for a way to customize logins to allow for an administrator to provide a user with an account and a one-time-use temporary password (via email), that enables the user to log into a site (the user can't create an account on their own).
The first time the user logs in, they are instructed to change the temporary password given by the administrator.
I have accounts-ui and accounts-password working, but the user can create a new account at will.
If it matters, I plan to use Autoform and Iron Router for this.
I search the Meteor docs for "enroll", but the information is sparse IMO. Is there a fully working example somewhere to help me get started?

To disable the usual way of creating an account, use Accounts.config:
forbidClientAccountCreation Boolean
Calls to createUser from the client will be rejected. In addition, if you are using accounts-ui, the "Create account" link will not be
available.
Then, instead of having a temporary password, I think you should create the account without password and then use Accounts.sendEnrollmentEmail to send the user an email to choose one.
To create an account without a password on the server and still let
the user pick their own password, call createUser with the email
option and then call Accounts.sendEnrollmentEmail. This will send the
user an email with a link to set their initial password.
So, something like that:
Accounts.config({forbidClientAccountCreation: true});
Meteor.methods({
adminCreateAccount: function (accountAttributes) {
if(Meteor.user() && Meteor.user().role == "admin") {
var accountId = Accounts.createUser({
'username': accountAttributes.username,
'email': accountAttributes.emailAddress
});
Accounts.sendEnrollmentEmail(accountId);
}
}
});

What you can do is
let the admin create user (Accounts.createUser)
add a marker ( eg user.profile.changedInitialPwd) which will be set when the user
changed his pwd)
use some verification logic to make sure, the user has changed his password before he is allowed to sign in
E.g.
Accounts.validateLoginAttempt(function(attempt){
if (attempt.user && !attempt.user.profile.changedInitialPwd ) {
console.log('Initial password not changed');
return false; // the login is aborted
}
return true;
});

Related

Firebase auth overriding user providers (email + password, phone) when signing-in with Google

As we have seen in another posts related to this situation and github issues, the expected behavior is that Google IDP overrides other non trusted providers related to the same email as an example, another account with the same email + password (non-verified).
Trying to understand Firebase Authentication one account per email address and trusted providers
Firebase Overwrites Signin with Google Account
https://github.com/firebase/firebase-ios-sdk/issues/5344
https://groups.google.com/g/firebase-talk/c/ms_NVQem_Cw/m/8g7BFk1IAAAJ
So, ok, according to google that's the expected behavior.
Our questions comes when we go to the documentation and there's an example of a user login in with google and getting this error auth/account-exists-with-different-credential just because there's another account created with email+password with the same email. Then, they recommend to catch the error, check the user email related login methods and ask the user to login with the other provider and then link to google.
Does this make sense ? If they say the expected behavior is that google as a trusted provider will override the others (this is what happens to us) how is possible that the case of the code example would even occur ?
https://firebase.google.com/docs/auth/web/google-signin#expandable-1
// Step 1.
// User tries to sign in to Google.
auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).catch(function(error) {
// An error happened.
if (error.code === 'auth/account-exists-with-different-credential') {
// Step 2.
// User's email already exists.
// The pending Google credential.
var pendingCred = error.credential;
// The provider account's email address.
var email = error.email;
// Get sign-in methods for this email.
auth.fetchSignInMethodsForEmail(email).then(function(methods) {
// Step 3.
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (methods[0] === 'password') {
// Asks the user their password.
// In real scenario, you should handle this asynchronously.
var password = promptUserForPassword(); // TODO: implement promptUserForPassword.
auth.signInWithEmailAndPassword(email, password).then(function(result) {
// Step 4a.
return result.user.linkWithCredential(pendingCred);
}).then(function() {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
return;
}
// All the other cases are external providers.
// Construct provider object for that provider.
// TODO: implement getProviderForProviderId.
var provider = getProviderForProviderId(methods[0]);
// At this point, you should let the user know that they already have an account
// but with a different provider, and let them validate the fact they want to
// sign in with this provider.
// Sign in to provider. Note: browsers usually block popup triggered asynchronously,
// so in real scenario you should ask the user to click on a "continue" button
// that will trigger the signInWithPopup.
auth.signInWithPopup(provider).then(function(result) {
// Remember that the user may have signed in with an account that has a different email
// address than the first one. This can happen as Firebase doesn't control the provider's
// sign in flow and the user is free to login using whichever account they own.
// Step 4b.
// Link to Google credential.
// As we have access to the pending credential, we can directly call the link method.
result.user.linkAndRetrieveDataWithCredential(pendingCred).then(function(usercred) {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
});
});
}
});
There's another example with the same structure in the flutter docs:
https://firebase.google.com/docs/auth/flutter/errors#handling_account-exists-with-different-credential_errors
Is this a contradiction in the documentation ? Again, if Firebase will always give priority to the trusted IDP (Google email) in this case, how is it possible to get this error if the other provider will be deleted (at least when having account linking activated - single account per email activated)
At least this is our case. We create an account with email & password and then try to login with google with the same email and what happens is that the email&password account is overwritten by the new google provider.
Unfortunately, you can't change it. If a user with #gmail.com email and password authentication updates their profile picture and then later logins with Google then the profile picture and any other information will be overwritten with the data from Google. The only option is to create a user record in the database that gets populated with the user data (displayName, photoURL etc) when the user is created for the first time. You then always use the data from this record instead of the default user object that is returned by the authentication.
The other advantage of creating a record is that you can attach a listener to it. That way if the user changes their details then it gets reflected everywhere.

Firebase email/password authentication - how to require email verification?

Whenever I use the email/password authentication provider in Firebase, the provider sends a bearer token upon successful sign-up even though the emailVerified is false. Is there a way, out of the box, to configure the email/password auth provider to not send a bearer token (and return a 403 error) until the user has verified their email address?
Note that I'm aware of how to create a user, sign in a user, send a verification email, etc... using firebase v9.x via the methods createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, sendEmailVerification from firebase/auth. I'm just asking if there is a way to set the behavior of the provider without having to write my own handler function for this. I'd like this to behave like Cognito does whenever the email verification is required.
There is no way to require the user's email address to be verified before they can sign in to Firebase Authentication.
The closest you can get is by using email-link sign-in, which combines signing in and verifying the user's email address in one action.
But this is how you'll typically want to implement this in your application code:
User enters their credentials
You sign them in to Firebase with those credentials
You check whether their email address is verified
If not, you stop them from further using the app - and (optionally) send them a verification email.
Same with data access: if you have a custom backend code, you can check whether the email address is verified in the ID token there too, as well as in Firebase's server-side security rules.
As per the documentation, you can use blocking functions to require email verification for registration (only that it doesn't work):
exports.beforeCreate = functions.auth.user().beforeCreate((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);
});
}
});
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
if (user.email && !user.emailVerified) {
throw new functions.auth.HttpsError(
'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
}
});
generateEmailVerificationLink always returns the following error:
"err": {
"message": "There is no user record corresponding to the provided identifier.",
"code": "auth/user-not-found"
},
but the user is created anyway given that beforeCreate don't return an exception.
If you want to check by yourself just log the error:
return admin.auth().generateEmailVerificationLink(user.email)
.then((link) => {
functions.logger.info("link", {user: user, context: context, link: link})
})
.catch((err) => {
functions.logger.info("error", {user: user, context: context, err: err});
});
The createUserWithEmailAndPassword() will sign in user right after the account is created. Also there isn't any way to prevent users from logging in even if their email is not verified but you can actually check if email is verified in security rules or using Admin SDK to prevent users with unverified email from accessing your resources. You can use this rule in Firestore:
allow read, write: if request.auth.token.email_verified == true;
One workaround would be creating users using a Cloud function and Admin SDK which won't sign in users but do note that users can sign in.
If you want to prevent login unless the email is verified strictly, then you can disable account right after it is created. Now you may not be able to use sendEmailVerification() which requires user to be signed in at first place, you can always create your own solution for verifying email. The process might look something like:
Create a user and disable the account in a Cloud function
Generate some token or identifier for verifying email and send an email to user from same cloud function
Once the user visits that link and verifies the email you can enable it
Additionally, users can still create accounts by using REST API but you can disable sign ups so users can be created via Cloud function only which disables the user immediately.

How can I make a function in which password is changing when a user is not logged in?

onChangePasswordPress = () => {
var user = firebase.auth().currentUser;
user.updatePassword(this.state.newPassword).then(function(){
console.log('Password is changed');
}).catch(function(error){
console.log(error.message)
})
it says currentUser in the 2nd line means only a user who is logged in can change his/her password.So how can we change a password of a user when a user is not logged in his/her account . How to make a function of changing a password when a user is not logged in his/her account in react native
For a user to be able to update their password, they need to have recently signed in. This is a security requirement, as allowing changing a password without being signed in would be a huge security risk.
If the user forgot their password, you can send them a password reset email. This email contains a link they then click that allows them to set a new password.
If you want to allow an application admin to change a user's password, you can build that using the Admin SDK. But to prevent the security risk outlined before, this can only be done from a trusted environment, such as your development machine, a server you control, or Cloud Functions.

Revert auth state to last anonymous

I have a website where authentication isn't mandatory to be able to use the website.
To be able to persist user data even when not signed in, I'm using signInAnonymously() in my AppComponent.
When the user wants to register to the website, I'm following these steps:
Ask for oauth.
Check if this oauth is already in my database.
If the oauth is not already in the db, revert to the last anonymous state.
If it's already in the db, get user data.
But here is the issue: once I called signInWithPopup(), the last auth state is lost and because it's an anonymous one, I can't sign in to it again.
Because the anonymous state contains data I don't want the user to loose, I have to be able to revert to the last anonymous state if the registration process fails.
I still don't understand your use case but anyway here goes.
You sign the user anonymously on the default App instance.
var app = firebase.initializeApp(config);
app.auth().signInAnonymously()....
You then create another Auth instance with same config:
var appTemp = firebase.initializeApp(config, 'temp');
// Set persistence to none in case you forget to clear this.
appTemp.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
// Sign in the user with popup and save the credential.
var cred = null;
appTemp.auth().signInWithPopup(provider).then(function(result) {
cred = result.credential;
// Check the user email is registered in your DB.
return isRegistered(result.user.email);
}).then(function(registered) {
if (registered) {
// User is registered. Delete and relink to original anonymous.
return appTemp.auth().currentUser.delete().then(function() {
return app.auth().currentUser.linkWithCredential(cred);
});
} else {
// User not registered, just delete. The anonymous user remains.
return appTemp.auth().currentUser.delete();
}
})...
Not sure if this is exactly what you want but it will point you in the right direction using the temp auth instance copy technique.

How to prove to the server that I as a client am logged in with the given uid?

Heres my problem:
I wan't to be able to create new users for my website, from my website. This is only aloud though, if I have the "isAdmin" flag set to true in the realtime db under /users/myid.
Generally I would have done this with a security rule but the problem here is that I need to use the admin SDK, since the normal "createNewUser" method signs in to the newly created user automatically. I as an admin though, want to be able to create a new user and stay logged in as myself. So what I wan't to do is use ajax post request to my server with my uid und the new userdata which is to be created. My server then checks if the given uid has the isAdmin flag and if so creates the user with the firebase admin SDK which provides such a method.
But, anyone, if they have an admins uid, could hit up that request and create a new user. (My clients definetely get uid's from other users).
So how would I go about proving to the server that I am actually logged in with that uid.
From my understanding, tokens are used to be able to write to the database, but I don't need that permission, I just need to prove that I'm actually logged in with that uid.
Is there something I'm missing? Thanks a lot guys!
Was easier then I thought. This will generate a token on the client side:
firebase.auth().currentUser.getToken(true).then(function(token) {
// send request to server with generated token
}).catch(function(error) {
// handle error
});
Which I can then verify on the server like so:
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
var uid = decodedToken.uid;
// user is logged in
}).catch(function(error) {
// user is not logged in, or other error occured
});
Taken from https://firebase.google.com/docs/auth/admin/verify-id-tokens

Resources