Firebase Sign Up with username. Cliend Side + Security Rules or Backend? - javascript

I have implemented a Firebase SignUp with username, email and password. Basically what I am doing is:
1- Create user with email and password (if the username and email are not used by other users)
2- Add the username to the user
Like this:
firebase
.createUserWithEmailAndPassword(email, password)
.then((currentUser) => {
// Get the username from the input
const username = this.usernameInput.current.getText();
// Create a user with this username and the current user uid in the db
firebase.createUserWithUsername(username, currentUser.user.uid); // <----------
})
.catch((err) => {
// ...
});
And my createUserWithUsername function basically do this:
createUserWithUsername = (username, userId) => {
/*
Create a document in the usernames collection
which uid (of the document itself, not a field) is the given username.
*/
// Pass username to lowercase
username = username.toLowerCase();
// Initial user's data
const data = {
email: this.auth.currentUser.email,
username,
};
return this.db
.collection("usernames")
.doc(username)
.set({ userId })
.then(() => {
this.db.collection("users").doc(userId).set(data);
})
.catch((err) => {
console.log(err);
throw err;
});
/*
Pd: As firestore automatically removes empty documents, we have to
assign them a field. The user's id is a good option, because it will help us to
update usernames faster, acting like a 'Foreign Key' in a NoSql DB.
*/
};
My question is? Is it wrong to leave this code on the client side? Can it be a security problem? Do I need to move this to a cloud function / backend?
This is my firestore security rule for the usernames collection:
match /usernames/{username} {
function isUsernameAvailable() {
return !exists(/databases/$(database)/documents/usernames/$(username));
}
allow read: if true;
allow write, update: if isSignedIn() && isUsernameAvailable();
// TODO - Allow delete?
}
I would really appreciate any guide for this. Thank you.

Related

How to implement the change password API in typescript with jwt?

I created login api and implemented jwt in that. Now I am trying to implement change_password with jwt authentication with cookies or local storage. I tried and not able to do that. Can anybody help me with the change password api and also i attached my Login API.
export let login=async(req:Request,resp:Response)=>{
var {email , password} = req.body;
try{
const user=await User.findOne({email}).lean();
if(!user){
return resp.json({message:"user not found"})
}
if(await bcryptjs.compare(password,user.password)){
const token=jwt.sign(
{
id:user._id,
username:user.email
},config.token.secret)
return resp.json({status:'ok',data:token})
}
return resp.json({status:'error',data:'comming soon'})
}catch(error){
console.log(error);
}
}
You can create a different route for change password and there can be multiple ways for doing this. Let keep it simple.
You can ask user the username and existing password and a new password. Check if the existing password matches the password in DB and hash the new password and save it.
app.post('/passwordChange', (req, res) => {
const {username, password, newPassword} = req.body;
const user = User.findOne({username});
//check the password matches and then update the User with new password
const matched = await bcryptjs.compare(password,user.password);
if (matched) {
User.updateOne({}) // go your things
}
else {
// do tell your to update correct password
}
})
There can be multiple simple and complex ways to achieve it. The above code is a pseudocode, modify to use it in the code.

How to create user auth without closing the current firebase session [duplicate]

This question already has answers here:
Firebase kicks out current user
(19 answers)
Closed 1 year ago.
I want to make a system where the administrator can create user auth from an email. I have developed as the documentation says but the current session is closed. I only want to create the auth to get the uid and then create a user in the database with the data I want to store.
This is what I have:
var email = emailInput.value;
var password = "Abcd1234";
firebase.auth().createUserWithEmailAndPassword(email, password).then((userCredential) => {
var user = userCredential.user;
//user.uid contains the id I want to create the instance on ref usuarios
database.ref("usuarios/"+ user.uid).set({...});
});
Edit:
You cannot create new users using client SDK. By that I mean a user creating new users as required. You need to use Firebase Admin SDK (which must be in a secure server environment - like Firebase Cloud Functions).
You can write a cloud function like this:
exports.createNewUser = functions.https.onCall((data, context) => {
if (isAdmin(context.auth.uid)) {
return admin.auth().createUser({
email: data.email,
password: data.password,
displayName: data.name
}).then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully created new user:', userRecord.uid);
return { uid: userRecord.uid }
}).catch((error) => {
console.log('Error creating new user:', error);
return { error: "Something went wrong" }
});
}
return {error: "unauthorized" }
})
Now there are multiple ways you could verify that the user who is calling this function is an admin. First one would be using Firebase Custom Claims which are somewhat like roles you assign to users. Another option would be storing UID of using in database and checking the UID exists in admin node of db. Just make sure only you can edit that part of the database.
To call the function from client:
const createNewUser = firebase.functions().httpsCallable('createNewUser');
createNewUser({ name: "test", email: "test#test.test", password: "122345678" })
.then((result) => {
// Read result of the Cloud Function.
var response = result.data;
});

Firebase Security Rule - Access a field in other document

Introduction
I have this structure on my db
C- usernames
D- paola
-> userId: 7384-aaL732-8923dsnio92202-peesK
D- alex
-> userId: ...
D- adam
-> userId: ...
C- users
D- userId of paola
-> username: "paola"
-> ...
D- userId of alex
-> username: "alex"
-> ...
D- userId of adam
-> username: "adam"
-> ...
I am signing up users in the client side so I have had to write some security rules...
In my client code I do:
Add the username (document id) with the userId (document data) to the usernames collection
Create a user document in the users collection with the username and other stuff.
Security Rules
So, my security rules look like this:
function isUsernameOwner(username) {
return get(/databases/$(database)/documents/usernames/$(username)).data.userId == request.auth.uid;
}
match /users/{userId} {
// Every people can read the users collection (might be in the sign in form)
allow read: if true;
// As the users creation is made in the client side, we have to make sure
// it meets these requirements
allow write: if isSignedIn() &&
isSameUser(userId) &&
request.resource.data.keys().hasOnly(['email', 'username', 'name', 'birthday']) &&
isValidUsername(request.resource.data.username) &&
isUsernameOwner(request.resource.data.username); // <------- If I remove this all works fine
}
Problem
When I try to sign up, I get "Missing or insufficent permissions"... I think the problem is in the function isUsernameOwner() but I don't know what am I doing wrong... Am I accessing incorrectly the field userId in the username document? If not, is it possible that the batched write doesn't happen sequentially?
Pd: The signup process is made using a batched write (first write the username, then the user)
UPDATE
This is the javascript code in which I make the batched write:
// Firebase.js
createUser = (email, password, username, name, birthday) => {
return this.auth
.createUserWithEmailAndPassword(email, password)
.then((currentUser) => {
// Get the user id
const userId = currentUser.user.uid;
// Get a new Firestore batched write
const batch = this.db.batch();
// Create the new username document in the usernames collection with the user's id as field
const usernameRef = this.db.collection("usernames").doc(username);
batch.set(usernameRef, { userId });
// Create the new user document in the users collection with all the relevant information
const userRef = this.db.collection("users").doc(userId);
birthday = firebase.firestore.Timestamp.fromDate(new Date(birthday)); // It is neccessary to convert the birthday to a valid Firebase Timestamp
const data = {
email,
username,
name,
birthday,
};
batch.set(userRef, data);
// Commit the batch
return batch.commit();
})
.catch((err) => {
throw err;
});
I think the problem is that you are using get() in your security rule global function. Make it local and use getAfter instead to wait until the 'termination' of the batched write.
Here you can see a post which might be useful for your case: Firebase security rules difference between get() and getAfter()
Just see the Doug answer, he explains the differences between get and getAfer.

Firebase Document for each user?

I am wondering how to make a document for each user as they create their account (with Firebase Web). I have Firebase Authentication enabled and working, and I'd like each user then to have a document in Cloud Firestore in a collection named users. How would I get the UID and then automatically create a document for each user? (I am doing this so that calendar events can be saved into an array field in the document, but I need a document for the user to start with). I am aware and know how to make security rules for access, I just don't know how to make the document in the first place.
Thanks!
While it is definitely possible to create a user profile document through Cloud Functions, as Renaud and guillefd suggest, also consider creating the document directly from your application code. The approach is fairly similar, e.g. if you're using email+password sign-in:
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(function(user) {
// get user data from the auth trigger
const userUid = user.uid; // The UID of the user.
const email = user.email; // The email of the user.
const displayName = user.displayName; // The display name of the user.
// set account doc
const account = {
useruid: userUid,
calendarEvents: []
}
firebase.firestore().collection('accounts').doc(userUid).set(account);
})
.catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
Aside from running directly from the web app, this code also creates the document with the user's UID as the key, which makes subsequent lookups a bit simpler.
You´ll have to set a firebase function triggered by the onCreate() Auth trigger.
1. create the function trigger
2. get the user created data
3. set the account data.
4. add the account data to the collection.
functions/index.js
// Firebase function
exports.createAccountDocument = functions.auth.user().onCreate((user) => {
// get user data from the auth trigger
const userUid = user.uid; // The UID of the user.
//const email = user.email; // The email of the user.
//const displayName = user.displayName; // The display name of the user.
// set account doc
const account = {
useruid: userUid,
calendarEvents: []
}
// write new doc to collection
return admin.firestore().collection('accounts').add(account);
});
If you are using Firebase UI to simplify your life a lil, you can add a User document to a "/users" collection in Firestore only when that user first signs up by using authResult.additionalUserInfo.isNewUser from the signInSuccessWithAuthResult in your UI config.
I'm doing something like this in my project:
let uiConfig = {
...
callbacks: {
signInSuccessWithAuthResult: (authResult) => {
// this is a new user, add them to the firestore users collection!
if (authResult.additionalUserInfo.isNewUser) {
db.collection("users")
.doc(authResult.user.uid)
.set({
displayName: authResult.user.displayName,
photoURL: authResult.user.photoURL,
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
})
.then(() => {
console.log("User document successfully written!");
})
.catch((error) => {
console.error("Error writing user document: ", error);
});
}
return false;
},
},
...
}
...
ui.start("#firebaseui-auth-container", uiConfig);
The signInSuccessWithAuthResult gives you an authResult and a redirectUrl.
from the Firebase UI Web Github README:
// ...
signInSuccessWithAuthResult: function(authResult, redirectUrl) {
// If a user signed in with email link, ?showPromo=1234 can be obtained from
// window.location.href.
// ...
return false;
}

Firebase user.updateProfile({...}) not working in React App

So, I have this ReactJS app, there is a user database,
The function for creating the user is this
import { ref, firebaseAuth } from './../Components/config'
export function auth (email, pw) {
return firebaseAuth().createUserWithEmailAndPassword(email, pw)
.then(saveUser)
}
export function saveUser (user) {
return ref.child(`users/${user.uid}/info`)
.set({
email: user.email,
uid: user.uid,
number: "" //custom
})
.then(() => user)
}
as you see the user is made of 3 properties, email, uid, and a custom number property which initially is "",
I have a
changeNumberToNew = (n) =>{
var user = firebase.auth().currentUser;
if (user != null) {
user.updateProfile({
number: n
}).then(() => {
console.log("Number changer");
}).catch((error) => {
console.log(error);
});
} else {
console.log("No user")
}
};
and a button to call the function
<button onClick={this.changeNumberToNew(4)}>Click to change number</button>
When i click the button the promise is resolver leading to the execution of
console.log("Number changer")
but when I go and look at the firebase database object .. nothing changes, even if a reload and wait still nothing changes
I think the problem here is that you are confusing the user object in your database with the user in your authentication module. They are not the same.
You save a 'copy' of your user to the database when you say the following in the first chunk.
ref.child(`users/${user.uid}/info`)
.set({
email: user.email,
uid: user.uid,
number: ""
})
Then in the second chunk of code you try and update the current user in your authentication module. Not good. You should be updating your database, not your authentication module.
var user = firebase.**auth()**.currentUser
if (user != null) {
user.updateProfile({...})
}
I don't think you can create a custom field on the current User in the authentication module. The updateProfile() is used to update the fields you get by default from the provider, such as email, display name, photoURL etc. You can't create new ones.
You should update the copy of the user in your database and then reference that when you need the value of 'number'.
You change function should probably be more like...
changeNumberToNew = (n) => {
var user = firebase.auth().currentUser;
if (user) {
ref.child(`users/${user.uid}/info`).update({number: n})
.then(() => console.log("Number changer"))
.catch(error => console.log(error))
} else {
console.log("No user")
}
}
Firebase Auth updateProfile only supports displayName and photoURL. It does not support client custom attributes. For admin custom attributes, you would need to use the admin SDK: https://firebase.google.com/docs/auth/admin/custom-claims#set_and_validate_custom_user_claims_via_the_admin_sdk
You are probably better off in this case saving these arbitrary custom fields in the database only (provided they do not require admin privileges).

Categories