I have a bit of an odd issue. My users need to logout and then login to ‘trigger’ the Firebase security roles correctly upon first sign up. Here is the flow: User submits details to a cloud function Cloud function uses admin Auth to create user and return response. On client side, when response is received the user is logged in using the credentials provided, and then receives a notification that they need to verify their email. After they click the verification link, it refreshes client side until it recognizes the email is verified. At that point it makes a call to my real-time database, and this is where permission is denied. However, if they logout and log back in, security rules recognize everything correctly. Here are my rules:
"rules": {
"sendRequests":{
".read": false,
"$requests":{
".write": "auth!=null && data.exists()",
}
},
"Notify":{
".read":false,
"$userCardID":{
".write": "auth!=null"
}
},
"$users":{
".read": "auth != null && auth.token.email_verified===true",
".write": "auth.uid === $users",
".indexOn":["Name"],
"Roster":{
"$cardID":{
".validate": "root.child(auth.uid+'-admin').child('Collection').child(newData.child('ID').val()).exists()"
}
},
"Completed":{
"$cardID":{
".validate": "root.child(auth.uid+'-admin').child('Collection').child(newData.child('ID').val()).exists()"
}
},
"ToDos":{
"$cardID":{
".validate": "root.child(auth.uid+'-admin').child('Collection').child(newData.child('ID').val()).exists()"
}
}
}
}
}
Curious if this is by design or if I’m missing a step here. I have checked that the user is being created and data is being added to the database under the correct user ID BEFORE the email verification.
EDIT: I should also mention that after receiving the response from the cloud function the user is logged in programmatically using the stored email and password, they don't actually 'click' a login button. Not sure if that changes anything.
EDIT: Here is the client-side code:
//This function is trigger when the user clicks on the 'Submit' button on the client form.
//searchString(username),eml(email),and pwd(password) or all grabbed from the 'input' values of the client form.
//It generates a random 'code' and then submits the data to a firestore doc, this triggers the cloud function to pull the data.
const usersRef = db2.ref();
function createRequest() {
return new Promise((resolve, reject) => {
let prefix = "Req"
let code = prefix;
for (let it = 0; it < 2; it++) {
let number = Math.floor(Math.random() * (9999 - 1000) + 1000);
code = code.concat(number)
if (it === 1) {
return resolve(code);
}
}
})
}
createRequest().then((code) => {
dataStore.collection('NewUserRequests').doc(code).set({
displayName: searchString,
email: eml,
password: pwd,
ReqID: code
});
//Then begin listening for the response ('true' or 'error message'). On 'true' it logs in the user.
dataStore.collection('NewUserResponses').doc(code).onSnapshot((doc) => {
log.debug("New user response is: " + doc.data());
//I have verified on the console this is being received correctly.
if (doc.data().Response === true) {
login_user(eml, pwd);
} else {
alertNative(doc.data().Response);
}
})
});
//the login function for firebase
function login_user(email, pwd) {
firebase.auth().signInWithEmailAndPassword(email, pwd).then(function(user) {
newLogin = true;
}, function(error) {
log.error("User login error: " + errorMessage);
});
}
//After login, the firebase auth state change gets triggered:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
log.info("User Auth Detected")
//I use the photoURL field to determine if a user is 'new'. I know its not elegant but it works.
let newNameCheck = firebase.auth().currentUser.photoURL
if (newNameCheck !== null) {
if (newNameCheck.indexOf("http://www.example.com/12345678/newUser.png") !== (-1)) {
createNewUser = true;
firebase.auth().currentUser.sendEmailVerification().then(function() {
log.info('Email Verification sent.') // Email sent.
}).catch(function(error) {
log.error('There was an issue sending the verification email: ' + error)
});
}
}
if (user.emailVerified === false) {
log.warn('This user is not verified!')
let checkEmail = setInterval(function() {
firebase.auth().currentUser.reload().then(check => {
if (firebase.auth().currentUser.emailVerified === true) {
//FINISH LOGGING IN/BUILDING NEW USER
clearInterval(checkEmail);
finishLoginSequence();
}
})
}, 2000)
} else {
log.warn('This user is verified!')
finishLoginSequence()
}
}
//ALL code up to this point triggers normally as expected. When it hits finishLogin Sequence is when permission is denied.
function finishLoginSequence(){
log.warn(firebase.auth().currentUser.emailVerified);
firebase.auth().currentUser.updateProfile({
photoURL: ""
}).then(function() {
log.info('Succesfully update new user profile.')
}).catch(function(error) {
log.error('There was an error updating the new user profile: '+error)
});
//THIS is approved, and I see the in the console that the profile has been updated.
currentUser = user.uid;
log.warn(firebase.auth().currentUser.emailVerified);
usersRef.child(currentUser+'-admin').child('Collection').once('value',function(snapshot){
let collection = snapshot.val()
fullCollect.push(collection)
//It never gets here as permission is denied!!
})
}
});
LOG FILE OUTPUT
[2021-05-13 19:06:00] Sess7967g4164k [info] User Auth Detected Caller: lobby.js:1590:11)
[2021-05-13 19:06:00] Sess7967g4164k [warn] This user is not verified! Caller: lobby.js:1637:13)
[2021-05-13 19:06:00] Sess7967g4164k [info] Email Verification sent. Caller: lobby.js:1597:17
[2021-05-13 19:06:08] Sess7967g4164k [info] Email verification has been received. Caller: lobby.js:1642:19
[2021-05-13 19:06:11] Sess7967g4164k [warn] true Caller: lobby.js:1676:15)
[2021-05-13 19:06:11] Sess7967g4164k [info] New user detected. Begin building new user account. Caller: lobby.js:1698:17)
[2021-05-13 19:06:11] Sess7967g4164k [warn] true Caller: lobby.js:1734:17)
[2021-05-13 19:06:11] Sess7967g4164k [error] UnhandledRejection Error: Permission denied
at https://www.gstatic.com/firebasejs/8.3.2/firebase.js:1:394396 Caller: catchErrors.js:35:15)
[2021-05-13 19:06:12] Sess7967g4164k [info] Succesfully update new user profile. Caller: lobby.js:1702:19
RULES FOR THE FIDDLE TEST DB:
{
"rules": {
"UserID999":{
".read": "auth != null && auth.token.email_verified===true",
".write": "false"
},
"UserID888":{
".read": "auth != null",
".write": "false"
}
}
}