Firebase Simple Login - Can I watch event when user remove his account? - firebase

In my app any user (signed up via Firebase Simple Login) can has 1 of 3 possible statuses at a time, that is one of active, deleted and suspended. I kept it like this:
"users": {
"simplelogin:1": {
"username": "user1",
"email": "user1#example.com",
"status": "active"
},
// ...
}
Any activity that involves any user will check his/her status, if it is deleted or suspended the action cannot be done.
Normally when user choose to remove his account from my app it would be like this:
var ref = new Firebase("https://my-app.firebaseio.com");
ref.child('users/simplelogin:1/status').set('deleted', function (error) {
if (!error) {
ref.removeUser({
email: "user1#example.com",
password: "secretpassword"
});
}
});
The problem is that user can remove his/her account without updating status to deleted first. For example this can be done by the user on his own browser console or on Node.js:
var ref = new Firebase("https://my-app.firebaseio.com");
ref.removeUser({
email: "user1#example.com",
password: "secretpassword"
});
This way the user will not has his account on Firebase but the status of this user that I kept above will remain active, and so my app will allow any activity for that user (such as someone sending him a gift, which shouldn't be allowed if his status has updated to deleted correctly.) That could be a problem. How can I update the status to deleted when user remove his/her account?
The solution I thought of is to regularly run cron job to check all the users in Firebase, try to sign up for users that does not has status deleted with random password and check if receive EMAIL_TAKEN error. If it can sign up, that means the user has already removed his/her account but the status in Firebase hasn't updated to deleted. In this case it has to update status to something (such as suspicious or deleted) and remove this newly signed up account again. I think this might be implemented like this:
// Assume this ref authenticated with admin privilege.
ref.child('users').on('child_added', function (snap) {
var user = snap.val();
if (user.status !== 'deleted') {
// Assume _generateRandomPassword gives generated random password.
var password = _generateRandomPassword();
var credentials = {
email: user.email,
password: password
};
ref.createUser(credentials, function (error) {
if (!error) {
snap.ref().child('status').set('deleted');
ref.removeUser(credentials);
}
});
}
});
I think this might not be a good solution since the system has to run for every user (that does not has deleted status), e.g. every night. Is there a better solution to this problem?

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.

Linking social provider with anonymous user on two devices

When a user start using our app we log him in using Firebase anonymous login.
We later allow them to login with social providers like Apple. We use the "auth().currentUser?.linkWithCredential" to link the social credentials with the anonymous user id.
We encountered a situation we are not sure how to solve:
User install the app on the device and use Sign in with Apple to sign in. We link the anonymous account to the Apple login and everything works just fine.
But now the user buys a new device. He installs the app and start it. He gets a new anonymous uid. He then try to sign in with Apple. Now if we try to call linkWithCredential we get an error:
"auth/credential-already-in-use] This credential is already associated with a different user account"
This is of course true, as the Apple credentials were associated with the anonymous user on the old device.
So how do we allow a user to sign in again from a new device?
We thought to catch the error, and then call signInWithCredential instead of linkWithCredential. But then we get an error:
Duplicate credential received. Please try again with a new credential.
It seems you can't use the Apple credentials for more than one call.
So again - we are stuck with no way to allow a user to login in two devices.
let appleAuthRequestResponse = null;
try {
appleAuthRequestResponse = await appleAuth.performRequest({
requestedOperation: AppleAuthRequestOperation.LOGIN,
requestedScopes: [
AppleAuthRequestScope.EMAIL,
AppleAuthRequestScope.FULL_NAME,
],
});
} catch (err) {
// TODO: decide what to do
return;
}
const {
identityToken,
nonce
} = appleAuthRequestResponse;
const appleCredential = auth.AppleAuthProvider.credential(
identityToken,
nonce
);
let userCredentials = null;
try {
userCredentials = await auth().currentUser ? .linkWithCredential(
appleCredential
);
// This will work on the first device but fail on the second one
console.log(userCredentials);
} catch (err) {
// This will fail as well with error: Duplicate credential received
await auth().signInWithCredential(appleCredential)
}

Firebase authentication (email/password) how to set user's uid?

I am authenticating using email/password like so:
firebase.auth().createUserWithEmailAndPassword(email, password).catch(function(error) {
// Handle Errors here.
});
And listening to auth here:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
console.log(user);
} else {
// No user is signed in.
console.log("Not signed in");
}
});
Which works fine. On examining the user object that auth returns, the uid is a random string, is there a way to set this uid when I create the account? For example, uid="someUserName"?
Thanks
Firebase Authentication is not like a database where you can add properties and such. It handles UID's and such for you. What you can do, is in your Firebase Database add a users directory and store additional info there (such as a username) with the UID as the key.
Here's an example:
If you're going to use this often, it's probably a good idea to go into your database rules and add an index on this username:

Firebase / AngularFire create user information

I'm creating a new user with AngularFire. But when I sign the user up I also ask for first name and last name and I add that info after registration.
$firebaseSimpleLogin(fbRef).$createUser($scope.signupData.email, $scope.signupData.password).then(function (user) {
// Add additional information for current user
$firebase(fbRef.child('users').child(user.id).child("name")).$set({
first: $scope.signupData.first_name,
last: $scope.signupData.last_name
}).then(function () {
$rootScope.user = user;
});
});
The above code works, it creates node fin Firebase (users/user.id/ ...).
The problem
When I login with the new user I get the user default information: id, email, uid, etc. but no name. How can I associate that data automatically to the user?
You can't. Firebase hides the complexity of login management by storing the login details in its own datastore. This process knows nothing of your app's forge, which means it doesn't know if or where you're storing any additional user information. It returns the data that it does know about as a convenience (id, uid, email, md5_hash, provider, firebaseAuthToken).
It's up to your app to then take the [u]id and grab whatever app specific user information you need (such as first name, last name). For an Angular app, you'd want to have a UserProfile service which retrieves the data you're looking for once you get the authentication success broadcast.
Also, in your snippet, consider changing
.child(user.id)
to
.child(user.uid)
This will come in handy if you ever support Facebook/Twitter/Persona authentication later on. uid looks like "simplelogin:1" - it helps to avoid unlikely but possible id clashes across providers.
I have the same issue on this and feel like noone actually has a clear answer (2 years on). But here is the rough structure of how such a service could look like:
app.factory('Auth', function(FURL, $firebaseAuth, $firebaseObject, $rootScope, $window){
​
var ref = new Firebase(FURL);
var auth = $firebaseAuth(ref);
​
var Auth = {
user: {},
​
login: function(user){
return auth.$authWithPassword({
email: user.email,
password: user.password
});
},
​
signedIn: function(){
return !!Auth.user.provider;
},
​
logout: function(){
return auth.$unauth;
}
};
​
// When user auths, store auth data in the user object
auth.$onAuth(function(authData){
if(authData){
angular.copy(authData, Auth.user);
// Set the profile
Auth.user.profile = $firebaseObject(ref.child('profile').child(authData.uid));
Auth.user.profile.$loaded().then(function(profile){
$window.localStorage['gym-key'] = profile.gym.toString();
});
} else {
if(Auth.user && Auth.user.profile){
Auth.user.profile.$destroy();
}
​
}
});
​
return Auth;
});

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