Firebase Auth: Edit UID - javascript

Is it possible to to change a user's UID in Firebase programmatically? There can't seem to be a way to do so manually within Firebase's console.

TL;DR: If you need to specify the UID, you'll need to create a new user with that UID.
You can't directly change the UID, but I was able to hack something together using the firebase admin API (docs)
My use case was that I needed to change a user's email address. I tried update email with "Update a User", but this actually ended up changing the UID under the hood. In my app, the UID is tied to so much stuff, that I'd have to do a huge architecture change, so this wasn't an option.
The general way I did this with the API was:
Pull Down a user using admin.auth().getUserByEmail
Delete the user with admin.auth().deleteUser
Create a new user with admin.auth().createUser, using relevant data from the getUserByEmail call above, replacing the email address with the new email.
"reset password" in the firebase admin console (I think there's a way to do this programmatically too)
User gets an email to reset their password and they have a new account with their old UID.
Unlike admin.auth().updateUser, createUser actually lets you specify a UID.

Building on the answer by RoccoB, the below is a complete set of instructions for changing a user's UID:
Create a new folder, and run npm init with default values.
Run npm install firebase-admin.
Create a NodeJS script file (eg. UpdateUserUID.js), with this code:
let admin = require("firebase-admin");
// config
let email = "XXX";
let serviceAccountData = require("XXX.json");
let adminConfig = {
credential: admin.credential.cert(serviceAccountData),
databaseURL: "https://XXX.firebaseio.com",
};
let newUserOverrides = {
uid: "XXX",
};
Start();
async function Start() {
console.log("Initializing firebase. databaseURL:", adminConfig.databaseURL);
admin.initializeApp(adminConfig);
console.log("Starting update for user with email:", email);
let oldUser = await admin.auth().getUserByEmail(email);
console.log("Old user found:", oldUser);
await admin.auth().deleteUser(oldUser.uid);
console.log("Old user deleted.");
let dataToTransfer_keys = ["disabled", "displayName", "email", "emailVerified", "phoneNumber", "photoURL", "uid"];
let newUserData = {};
for (let key of dataToTransfer_keys) {
newUserData[key] = oldUser[key];
}
Object.assign(newUserData, newUserOverrides);
console.log("New user data ready: ", newUserData);
let newUser = await admin.auth().createUser(newUserData);
console.log("New user created: ", newUser);
}
Replace email and adminConfig.databaseURL with the correct values.
Replace newUserOverrides.uid with the desired new uid. (you can change some other fields too)
Generate/download a private key for your project's Firebase Admin service account: https://firebase.google.com/docs/admin/setup (can skip to the "Initialize the SDK" section)
Update the serviceAccountData variable's import to point to the key json-file from the previous step.
Run node ./UpdateUserUID.js.
If applicable (I didn't seem to need it), use the "reset password" option in the Firebase Admin Console, to have a password-reset email sent to the user, apparently completing the account update. (Perhaps I didn't need this step since I don't use the accounts/authentications for anything besides sign-in on my website...)

The UID of a user is controlled by the identity provider that creates that user. This means that you can't change the UID for any of the built-in providers.
But you can control the UID if you create a custom identity provider. Note that this is quite a bit more involved than changing something in the Firebase console. It requires you to write code that runs in a secure/trusted environment, such as a server you control, or Cloud Functions.

You can't, since is the main tree node of possibles more entries inside it, you can get it, modify and then put it inside the same UID (or create a new one) but you can have things inside, for example take this.
You create your main UID which will hold user data (name, phone, email etc) lets say the structure is this:
-9GJ02kdj2GKS55kg
-Name:
-Phone:
-Email:
so, you can get the main user UID 9GJ02kdj2GKS55kg with mAuth.getCurrentUser().getUid(); and then change it and set a new value inside 9GJ02kdj2GKS55kg, this new value should be the same UID you got but changed, and then inside your main UID you can still have the same structure
-9GJ02kdj2GKS55kg
-6GL02kZj2GKS55kN (this is your changed UID)
-Name:
-Phone:
-Email:
or you can get that changed UID and make a new child, and that will be your parent node with custom UID for the data.

Piggybacking on #Vinrynx's post.
I recently created a migration tool where I am migrating collections from 1 Firebase Project to another and it required that after I insert users to "users" collection I also create an authentication record with the same doc.id
Variables in the functions below:
outCollData : Data that I am inserting for the user (contains the email inside it)
sourceDBApp : output of the admin.initializeApp({/*service-account.json file location for source firebase project */});
destDBApp : output of the admin.initializeApp({/*service-account.json file location for destination firebase project */});
async function updateUsersUID(
outCollData: any,
sourceDBApp: admin.app.App | undefined,
destDBApp: admin.app.App | undefined
) {
if (destDBApp === undefined) return;
const admin = destDBApp;
const email = outCollData.personali.email ? outCollData.personali.email : "";
console.log("Email is ", email);
if (email === "" || email === undefined) return;
console.log("Inside updateUsersUID");
let newUserOverrides = {
uid: outCollData._id,
};
let oldUser: any;
try {
console.log("Starting update for user with email:", email);
oldUser = await admin.auth().getUserByEmail(email!);
//console.log("Old user found:", oldUser);
if (oldUser.uid === outCollData._id) {
console.log(
"User " +
email +
" already exists in the destination DB with UID " +
outCollData._id
);
return;
}
await admin.auth().deleteUser(oldUser.uid);
console.log("Old user deleted.");
} catch (e) {
console.log("User not found in destination DB ", email);
console.log("Copying the user data from source DB");
oldUser = await sourceDBApp?.auth().getUserByEmail(email);
}
let dataToTransfer_keys = [
"disabled",
"displayName",
"email",
"emailVerified",
"phoneNumber",
"photoURL",
"uid",
"providerData",
];
let newUserData: any = {};
for (let key of dataToTransfer_keys) {
newUserData[key] = oldUser[key];
}
Object.assign(newUserData, newUserOverrides);
//console.log("New user data ready: ", newUserData);
let newUser = await admin.auth().createUser(newUserData);
console.log("New user created ");
}

Related

Firestore Delete All References of A User

I have a chat app build in react native. When a user decides to delete their profile, I want to remove all references of them from the database.
The DB has references to their user id in the "matches" table, the "chat" table, and the "messages" table for each of the people the deleted user was chatting with.
I am using firebase functions to handle the deletion of the user doc data and auth but I am not sure what the best way to go about removing all of these references would be. My question is: what is the best way to remove all references of an ID out of a somewhat complex database? I assume this will be taxing to loop through every single user in the DB to search for this one ID.
deleteAccount = () => {
var db = firebase.firestore();
firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
//delete user data
db.collection("Users")
.doc(user.uid)
.delete();
} else {
console.log("user needs to reauth");
return false;
}
});
};
firebase functions
exports.deleteUser = functions.firestore
.document("Users/{userID}")
.onDelete((snap, context) => {
const deletedValue = snap.data();
// Delete the images
try {
admin.auth().deleteUser(deletedValue.id);
const imgRef1 = firebase.storage().ref(user.uid + "/images/0")
? firebase.storage().ref(user.uid + "/images/0")
: null;
const imgRef2 = firebase.storage().ref(user.uid + "/images/1")
? firebase.storage().ref(user.uid + "/images/1")
: null;
const imgRef3 = firebase.storage().ref(user.uid + "/images/2")
? firebase.storage().ref(user.uid + "/images/2")
: null;
imgRef1.delete().then(function() {
imgRef2.delete().then(function() {
imgRef3.delete().then(function() {});
});
});
} catch (e) {
console.log("no images to delete");
}
});
Firebase products such as the databases and storage have no implicit knowledge of what data belongs to what user. That relation only exists because your application code made it.
For that reason you will also have to look up/traverse the relations when deleting the user, to find (and delete) their data. There are no shortcuts in the product here, although there is a open-source library that contains an implementation that works from a configuration file: user-data-protection
Edit: I just realized there's actually an Extension to Delete User Data, which does pretty much the same as the library linked above. It might be worth to have a look if that suits your needs

Wait for firebase.auth initialization before reading another function

I am very new with firebase and javascript.
My project: Build a private messaging app. To do that, I want to define a sub collection in firestore for private messaging using the current user id and the destination user id.
Here is the function that allows this:
// generate the right SubCollection depending on current User and the User he tries to reach
function dmCollection(toUid) {
if (toUid === null) {
// If no destination user is definer, we set it to the below value
toUid = 'fixed_value';
};
const idPair = [firebase.auth().currentUser.uid, toUid].join('_').sort();
return firebase.firestore().collection('dms').doc(idPair).collection('messages');
};
My problem: I want to use the firebase.auth().currentUser.uid attribute, but it looks like the function is not waiting for firebase.auth initialization. How can I fix this problem?
Additional information:
I have two functions that are calling the first one (dmCollection):
// retrieve DMs
function messagesWith(uid) {
return dmCollection(uid).orderBy('sent', 'desc').get();
};
// send a DM
function sendDM(toUid, messageText) {
return dmCollection(toUid).add({
from: firebase.auth().currentUser.uid,
text: messageText,
sent: firebase.firestore.FieldValue.serverTimestamp(),
});
};
If I correctly understand your problem ("it looks like the function is not waiting for firebase.auth initialization"), you have two possible solutions:
Solution 1: Set an observer on the Auth object
As explained in the documentation, you can set an observer on the Auth object with the onAuthStateChanged() method:
By using an observer, you ensure that the Auth object isn't in an
intermediate state—such as initialization—when you get the current
user.
So you would modify your code as follows:
// retrieve DMs
function messagesWith(uid) {
return dmCollection(uid).orderBy('sent', 'desc').get();
};
// send a DM
function sendDM(toUid, messageText) {
return dmCollection(toUid).add({
from: firebase.auth().currentUser.uid,
text: messageText,
sent: firebase.firestore.FieldValue.serverTimestamp(),
});
};
// generate the right SubCollection depending on current User and the User he tries to reach
function dmCollection(toUid) {
if (toUid === null) {
// If no destination user is definer, we set it to the below value
toUid = 'fixed_value';
};
const idPair = [firebase.auth().currentUser.uid, toUid].join('_').sort();
return firebase.firestore().collection('dms').doc(idPair).collection('messages');
};
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
var messageText = '....';
sendDM(user.uid, messageText)
} else {
// No user is signed in.
}
});
Solution 2: Use the currentUser property
You could also "get the currently signed-in user by using the currentUser property" as explained in the same doc. "If a user isn't signed in, currentUser is null".
In this case you would do:
var user = firebase.auth().currentUser;
if (user) {
var messageText = '....';
sendDM(user.uid, messageText);
} else {
// No user is signed in.
// Ask the user to sign in, e.g. redirect to a sign in page
}
Which solution to choose?
It depends how you want to call the function(s) based on the user uid.
If you want to call the function(s) immediately after the user is signed in, use Solution 1.
If you want to call the function(s) at another specific moment (e.g. following a user action), use Solution 2.

Input Value doesn't save, when pushing onto array gives undefined value

I am trying to update the user account details in firebase but I have noticed that the input value for one of my fields keeps coming up as undefined even when I console.log it. I am working in two files one is a loginjs file in which I am defining the user input.
signUpForm.addEventListener('click', function (e) {
e.preventDefault();
isSigningUp = true;
var email = signUpEmailInput.value;
var password = signUpPasswordInput.value;
var displayNameUser = displayNameInput.value;
var userPrivateKey = signUpPrivateKey.value;
var user = firebase.auth().currentUser;
var photoURL = "https://www.gravatar.com/avatar/" + md5(email);
if (signUpPasswordInput.value !== signUpPasswordConfirmInput.value) {
setSignUpError('Passwords do not match!');
} else if (!displayNameUser) {
setSignUpError("Display Name is required!");
} else if (!userPrivateKey) {
setSignUpError('You need to set a Private Key!');
} else {
auth.createUserWithEmailAndPassword(email, password)
.then(function (user) {
user.updateProfile({
displayName: displayNameUser,
photoURL: photoURL,
privateKey: userPrivateKey
}).then(function () {
// Update successful.
window.location.href = 'chat.html';
}).catch(function (error) {
// An error happened.
window.alert("Some unexpected error happened!");
});
user.sendEmailVerification().then(function () {
// Email sent.
}).catch(function (error) {
// An error happened.
window.alert("Email was not able to send!");
});
})
.catch(function (error) {
// Display error messages
setSignUpError(error.message);
});
}});
The weird thing is that the user input for my displayname and photoURL are working just fine, but when it comes to my private key user input it registers the input when it goes to the chat page and I do a console.log(user.privatekey) It says it is undefined.
In my chatjs file, thats when I am pushing the all the user profile information. The chatjs file basically allows a user to send a message, the message and all the user profile information gets stored onto the firebase database.
messages.push({
displayName: displayName,
userId: userId,
pic: userPic,
text: myString.toString(),
privatekey: user.privatekey,
timestamp: new Date().getTime() // unix timestamp in milliseconds
})
.then(function () {
messageStuff.value = "";
})
.catch(function (error) {
windows.alert("Your message was not sent!");
messageStuff;
});
The thing again is that the privatekey does not get stored at all, which is what I am not understanding, since it is registering user input in the loginjs file but when I go to the chatjs file it keeps saying the value is undefiend. I have googled everywhere and I still haven't found a solution to it. Any help would be greatly appricated!
It's because the Firebase user object you receive from Firebase is not customizable. When you call the createUserWithEmailAndPassword(email, password) method, it returns a specifically defined user object back to you - check out the docs for the properties of this object.
The properties displayName and photoURL both work because they are already properties of the user returned. privateKey is not an existing property of the Firebase user object, and Firebase doesn't know how to handle an update call for a property that isn't defined. Check out this question & answer where Frank explains that Users in Firebase aren't customizable - you need to store any extra info separately.

Firebase total user count

Is there a way to get all the users' count in firebase? (authenticated via password, facebook, twitter, etc.) Total of all social and email&password authenticated users.
There's no built-in method to do get the total user count.
You can keep an index of userIds and pull them down and count them. However, that would require downloading all of the data to get a count.
{
"userIds": {
"user_one": true,
"user_two": true,
"user_three": true
}
}
Then when downloading the data you can call snapshot.numChildren():
var ref = new Firebase('<my-firebase-app>/userIds');
ref.once('value', function(snap) {
console.log(snap.numChildren());
});
If you don't want to download the data, you can maintain a total count using transactions.
var ref = new Firebase('<my-firebase-app>');
ref.createUser({ email: '', password: '', function() {
var userCountRef = ref.child('userCount');
userCountRef.transaction(function (current_value) {
// increment the user count by one
return (current_value || 0) + 1;
});
});
Then you can listen for users in realtime:
var ref = new Firebase('<my-firebase-app>/userCount');
ref.on('value', function(snap) {
console.log(snap.val());
});
Using Cloud Functions:
exports.updateUserCount = functions.auth.user().onCreate(user => {
return admin.database().ref('userCount').transaction(userCount => (userCount || 0) + 1);
});
Just note that a Cloud Functions event is not triggered when a user is created using custom tokens. In that case, you would need to do something like this:
exports.updateUserCount = functions.database.ref('users/{userId}').onCreate(() => {
return admin.database().ref('userCount').transaction(userCount => (userCount || 0) + 1);
});
Update 2021
I stumbled on this question and wanted to share three methods to get total number of signed-up users.
👀 Looking in the console
Go to the console, under Authentication tab, you can directly read the number of users under the list of users:
56 users! yay!
📜 Using the admin SDK
For programmatic access to the number of users with potential filter on provider type, registration date, last connection date... you can write a script leveraging listUsers from the admin SDK.
For example, to count users registered since March 16:
const admin = require("firebase-admin");
const serviceAccount = require("./path/to/serviceAccountKey.json");
admin.initializeApp({ credential: admin.credential.cert(serviceAccount) });
async function countUsers(count, nextPageToken) {
const listUsersResult = await admin.auth().listUsers(1000, nextPageToken);
listUsersResult.users.map(user => {
if (new Date(user.metadata.creationTime) > new Date("2021-03-16T00:00:00")) {
count++;
}
});
if (listUsersResult.pageToken) {
count = await countUsers(count, listUsersResult.pageToken);
}
return count;
}
countUsers(0).then(count => console.log("total: ", count));
💾 Storing users in a DB
Your app maybe already stores user documents in Firestore, or the Realtime Database, or any other database. You can count these records to get the total number of registered users. (If you use Firestore, you can read my article on how to count documents)
Its mid 2022 now, and as far as I can tell, the required capability is still not the Node.js admin SDK, but it is available from the identity toolkit REST api.
The suggestion from Louis Coulet of looking at the Firebase console is what tipped me off. Looking at the console's API calls, we can see there is a "query" endpoint that can return the number of accounts.
The endpoint is documented here : https://cloud.google.com/identity-platform/docs/reference/rest/v1/projects.accounts/query
The admin SDK can provide the required access token to call the endpoint. See https://firebase.google.com/docs/reference/admin/node/firebase-admin.app.credential.md#credentialgetaccesstoken
firebase.initializeApp();
firebase.app().options.credential.getAccessToken().then(the_token => ...)
As the console does, we provide an empty query expression and set the returnUserInfo flag to false
curl --request POST \
--url 'https://identitytoolkit.googleapis.com/v1/projects/your_project_goes_here/accounts:query?alt=json' \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer the_token' \
--data '{
"returnUserInfo": false,
"expression": []
}'
The query result is the number of accounts
{
"recordsCount": "1223"
}
Here is a javascript Module for this purpose - https://gist.github.com/ajaxray/17d6ec5107d2f816cc8a284ce4d7242e
In single line, what it does is -
Keep list (and count) of online users in a Firebase web app - by isolated rooms or globally
For counting all users using this module -
firebase.initializeApp({...});
var onlineUsers = new Gathering(firebase.database());
gathering.join();
// Attach a callback function to track updates
// That function will be called (with the user count and array of users) every time user list updated
gathering.onUpdated(function(count, users) {
// Do whatever you want
});

How to addUniqueObject to non-current user class using cloud code in Parse.com?

I want to add an array of current user object ID in to a user's column called "followers". Due to Parse's security reason not allowing modification to non-current user, I'm forced to use cloud code. The problem is I know nothing about JavaScript, so I need help here.
Here's what I would code if no security issue mentioned above:
//add the current user ID to the user(userPassed) that the current user liked
[userPassed addUniqueObject:[PFUser currentUser].objectId forKey:#"followers"];
[userPassed saveInBackground];
To be very specific, I just want to know how to code the above in cloud code. Thanks.
Here you go:
Parse.Cloud.define('functionName', function(request, response) {
var userId = request.params.userId;
var me = Parse.User.current();
var user = new Parse.User();
user.id = userId;
user.addUnique('followers', me);
return user.save(null, {useMasterKey: true}).then(function(user) {
response.success('Succeed');
}, function(error) {
console.error(error);
response.error('Failed');
});
});

Categories