How to update email and sensitive data properly in Firebase? - javascript

I have a code where the user can update his credentials/personal information however I encounter a problem and managed to fix it, it was saying first argument had to be an string and I found a solution however I got an error afterwards saying "This operation is sensitive and requires recent authentication. Log in again before retrying...
Afterwards I found in some of the comments where I found my first solution the following:
user.reauthenticateWithCredential(auth.EmailAuthProvider.credential(user.email, user.password)).then(() => user.updateEmail(email)) I tried to use this but is not working I get other error afterwards and wanted to know if this was either outdated or I'm just doing this wrong.
Code
I get my auth from my firebase.js
const db = firebase.firestore();
const auth = firebase.auth();
const storage = firebase.storage();
I get my user from my App.js and then if I need it I just send it just like this:
function App() {
const [user, setUser] = useState([]);
useEffect(() => {
auth.onAuthStateChanged((authUser) => {
if (authUser) {
setUser(authUser);
} else {
setUser(false);
}
})
}, [])
return (
....
<Route path = "/Update_Profile">
<Inicio user={user}/>
<UpdateProfile user={user}/>
</Route>
...
)}
export default App;
const updateEmail = (e) => {
e.preventDefault();
if (user && (email != "")) {
user.reauthenticateWithCredential(auth.EmailAuthProvider.credential(user.email, user.password))
.then(() => user.updateEmail(email))
const ref = db.collection("usuarios").doc(user.uid)
ref.update({
email: email
})
} else {
//User is not logged in, handle that case here
}
}

Because auth is an instance of the Auth module and not the namespace of the Auth module from the Firebase Web SDK (because you've used const auth = firebase.auth()), the following value is undefined:
auth.EmailAuthProvider
This is because the firebase.auth.Auth class does not define a property EmailAuthProvider. This then means JavaScript tries to call undefined.credential(...) and throws an error.
To access the EmailAuthProvider class, you need to access it from the firebase.auth namespace directly:
firebase.auth.EmailAuthProvider
In short,
firebase.auth !== firebase.auth()

Related

How to redirect on error page if the id parameter in url does not match the one in database?

I Have a MERN stack application which uses Axios library to establish the connection between the client and the server. I have a route '/blogs' which fetches all the blogs from the backend and displays it on the interface. Then there's a dynamic route '/blogs/:id' if the id passed in the id parameter matches the one in the database, that specific blog should be rendered. Which it does pretty nicely. My problem is that when I pass any other thing in the id parameter that does not match an id in the database, it should redirect the user to an error 404 page. But instead of that it displays nothing but a navbar as if it went on to render the single blog page anyways regardless of weather the server sent a blog or not. And I get this error in my server console.
D:\Projects\vite\server\node_modules\mongoose\lib\query.js:4913
const castError = new CastError();
^
CastError: Cast to ObjectId failed for value "dsfgfsf" (type string) at path "_id" for model "Blog"
at model.Query.exec (D:\Projects\vite\server\node_modules\mongoose\lib\query.js:4913:21)
at model.Query.Query.then (D:\Projects\vite\server\node_modules\mongoose\lib\query.js:5012:15)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
messageFormat: undefined,
stringValue: '"dsfgfsf"',
kind: 'ObjectId',
value: 'dsfgfsf',
path: '_id',
reason: BSONTypeError: Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer
at new BSONTypeError (D:\Projects\vite\server\node_modules\bson\lib\error.js:41:28)
at new ObjectId (D:\Projects\vite\server\node_modules\bson\lib\objectid.js:67:23)
at castObjectId (D:\Projects\vite\server\node_modules\mongoose\lib\cast\objectid.js:25:12)
at ObjectId.cast (D:\Projects\vite\server\node_modules\mongoose\lib\schema\objectid.js:246:12)
So what I think It is expecting in the id parameter is a string that is the same length of an object id that mongodb documents automatically are assigned. Anyway's here's my code.
export const getSingleBlog = async (req, res) => {
const singleblog = await blog.findOne({ _id: req.params.id})
if(!singleblog) {
res.send("No Blog Found for real")
} else {
res.send(singleblog);
}
}
This function is called back when a get request is made to '/blogs/:id' endpoint.
import React, { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import Navbar from '../../components/Navbar/Navbar'
import axios from 'axios'
import './SingleBlog.css'
import Error404 from '../Error404/Error404'
const SingleBlog = () => {
const {id} = useParams();
const navigate = useNavigate();
const [blog, setBlog] = useState({});
const fetchBlog = async () => {
const response = await axios.get(`http://localhost:5000/blogs/${id}`);
setBlog(response.data);
}
useEffect(() => {
fetchBlog();
}, []);
if (blog) {
return (
<>
<Navbar title="PEGASUS" op1="Compose" op2="About Us" op3="Contact Us" />
<div className="blog" key={blog._id}>
<h1>{blog.blogTitle}</h1>
<br />
<main>
<p>{blog.content}</p>
</main>
<div className="author">
<h4>- {blog.author}</h4>
</div>
</div>
</>
)
} else return (
<Error404 />
)
}
export default SingleBlog
And this is the react code.
The issue stems from here:
req.params.id
You need to perform a check to tell whether or not it's a number, and then tell Express what to do when id is not a number before Mongo gets the bad input.
There's a few ways to handle this but you might try is using the isNaN check:
const { id } = req.params;
if (isNaN(id)) {
throw new Error('id is not a number');
} else {
res.send();
}
Then your frontend component would want to catch this error being thrown and deal with it somehow:
const fetchBlog = async () => {
try {
const response = await axios.get(`http://localhost:5000/blogs/${id}`);
setBlog(response.data);
} catch (e) {
console.log(e);
}
}

(Firebase Web Persistence) After sign in, currentUser is null, but, upon inspection of the auth object, it isn't?

I'm trying to access the currently signed in user via getAuth(app).currentUser, but the result I get is super weird, it says the auth.currentUser is null, but, then, upon inspection of the object, it isn't???
I did try to manually change persistence to see if it changed anything (following the docs), but it didn't make any difference.
My code (TypeScript) is pretty much this (I'm using the Firebase Emulator):
// Global Variables
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
...
await signInWithEmailAndPassword(auth, username, password);
For those who wish to see the complete code... Here it is... It's a bit experimental, and, for this project, I'm trying to create the most minimalistic version of this that I can think of. I think the code below with a bit of HTML is pretty much what you need to reproduce it locally.
This is how I'm initializing my Firebase app and auth, which only need to exist in the admin page currently:
import { FirebaseApp, initializeApp } from "firebase/app";
import {
Auth,
// browserLocalPersistence,
connectAuthEmulator,
getAuth,
} from "firebase/auth";
import { EnvState, envState } from "../infra/env";
export const firebaseConfig = {...};
export let app: FirebaseApp;
export let auth: Auth;
let authInitialized = false;
export const initAuth = async () => {
try {
if (!authInitialized) {
app = initializeApp(firebaseConfig);
auth = getAuth(app);
// await auth.setPersistence(browserLocalPersistence);
if (envState === EnvState.dev)
connectAuthEmulator(auth, "http://localhost:9094", {
disableWarnings: true,
});
}
authInitialized = true;
} catch (error) {
authInitialized = false;
}
};
And this is the admin view (I'm usign HTML components in this project):
import { onAuthStateChanged, signInWithEmailAndPassword } from "firebase/auth";
import { auth, initAuth } from "../../infra/firebase_config";
export default class AdminView extends HTMLElement {
static readonly tag: string = "admin-view";
constructor() {
super();
// Could be elsewhere, doesn't seem to make a difference (not even with `await`).
initAuth();
}
async connectedCallback() {
document.title = "Admin";
// An attempt at trying to get the current user in a different way...
let currentUser;
console.log(auth);
onAuthStateChanged(auth, (user) => {
if (user) currentUser = user;
});
// *********************************************
// This is the log the picture refers to:
// *********************************************
console.log(currentUser);
if (auth.currentUser) {
this.innerHTML = `
<p>You're logged in as ${auth.currentUser}</p>
`;
} else {
this.signInForm();
}
}
signInForm = (): void => {
this.innerHTML = `
<form>
<fieldset>
<label for="username">Admin</label>
<input type="text" name="username" autofocus/>
</fieldset>
<fieldset>
<label for="password">Password</label>
<input type="text" name="password"/>
</fieldset>
<button type="submit">Sign in</button>
</form>
`;
const submitButton: HTMLButtonElement = this.querySelector("button")!;
submitButton.addEventListener("click", async (e: Event) => {
e.preventDefault();
const isLoggedIn = await this.signIn();
const msg = isLoggedIn
? "You're logged in!"
: "You weren't able to log in...";
this.innerHTML += `
<p>${msg}</p>
`;
});
};
signIn = async (): Promise<boolean> => {
const adminUserInput: HTMLInputElement = this.querySelector(
"input[name=username]"
)!;
const adminPasswordInput: HTMLInputElement = this.querySelector(
"input[name=password]"
)!;
const username = adminUserInput.value;
const password = adminPasswordInput.value;
try {
const cred = await signInWithEmailAndPassword(auth, username, password);
if (cred) return true;
else return false;
} catch (error) {
const e = error as Error;
console.log(e.message);
return false;
}
};
}
1. Conceptual Explanation
Even though the function signatures on some of the auth methods are not represented as asynchronous, they pretty much are. So, upon cold start of your app (or page refresh), the current user will be null. So, in the end, we have actually 3 states for the current user: unknown, signed in, not signed in. Check out Doug Stevenson's article for more info on this topic. That's why it's recommended to use a listener for tracking your current user, so it gets updated as soon as possible.
This asynchronicity is actually what causes the weirdness of your screenshot. The console.log is emitted when the user is in the unknown state and, thus is shown as null. However, when you inspect the object by clicking on the GUI's arrow, the current user has been updated in the meantime.
2. A Way of Making It Work
The console.log(currentUser); in your code would mostly be null as onAuthStateChanged() may not have loaded the auth state yet. It's best to add that observer when your page loads, perhaps in your constructor (or connectedCallback) like this:
constructor() {
super();
onAuthStateChanged(auth, (user) => {
if (user) {
this.innerHTML = `
<p>You're logged in as ${userInfo}</p>
`;
} else {
this.signInForm();
}
});
}

Firestore app gets missing or insufficient permissions even when not trying to access the data

I've been following along with a demo video on Youtube for learning Firebase auth that also uses firestore for the database. I've modified mine a bit to show a list of recipes when the user is logged in and only allows the user to create a new recipe if they are logged in. I hide the create button and the list of existing recipes if they are logged out, all they see when logged out is a message to log in. I also modified the rules in firestore to only allow access to the data if there is a user signed in.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
Thats just the basic code provided in their documentation. It was working well until I tried to create a recipe while logged out by showing the button through the dev tools and trying to submit a new recipe in order to test the rules and make sure it would not allow it. Now the page errors before fully loading once unless I remove the rules in firestore. If I allow access everything works as intended.
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const querySnapshot = await getDocs(collection(db, "Recipes"));
//listen for auth status
onAuthStateChanged(auth, (user) => {
console.log(user);
if (user) {
setupRecipes(querySnapshot);
setupUI(user);
} else {
setupRecipes([]);
setupUI();
}
});
const recipeList = document.querySelector(".recipes");
const loggedOutLinks = document.querySelectorAll(".logged-out");
const loggedInLinks = document.querySelectorAll(".logged-in");
const setupUI = (user) => {
if (user) {
loggedInLinks.forEach((item) => (item.style.display = "block"));
loggedOutLinks.forEach((item) => (item.style.display = "none"));
} else {
loggedInLinks.forEach((item) => (item.style.display = "none"));
loggedOutLinks.forEach((item) => (item.style.display = "block"));
}
};
const setupRecipes = (data) => {
let html = "";
let ingredientList = "";
data.forEach((doc) => {
const recipe = doc.data();
ingredientList = "";
recipe.ingredients.forEach((ingredient) => {
ingredientList += `<li>${ingredient}</li>`;
});
const li = `
<li>
<div class="collapsible-header grey lighten-4">${recipe.title}</div>
<div class="collapsible-body white">
<h4 style="margin: 0 0 10px 0;">Ingredients</h4>
<ul class="ingredientList">
${ingredientList}
</ul>
</div>
</li>
`;
html += li;
});
if (html.length !== 0) {
recipeList.innerHTML = html;
} else {
recipeList.innerHTML =
'<h4 class="center-align">Login to view recipes</h4>';
}
};
So I guess my question is: Is that bad request being held onto somehow or am I trying to access the data somewhere without realizing it? I have tried clearing my cache and restarting the local server I have the app running on but the only way to even get the page to load with the message telling the user to log in is to take away to the rule in firestore and allow access for all. When the rule is there I get the following immediately on page load: 127.0.0.1/:1 Uncaught FirebaseError: Missing or insufficient permissions. Any help would be much appreciated, I'm happy to provide more of the code if needed.
As pointed out in the comments above there was a query to the database that was happening before the user was signed in. I moved the getDocs call into the onAuthStateChanged function and only call it when a user exists which now allows the page to load while signed out since there is no attempt to retrieve anything unless an authorized user is signed in
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
//listen for auth status
onAuthStateChanged(auth, async(user) => {
console.log(user);
if (user) {
const querySnapshot = await getDocs(collection(db, "Recipes"));
setupRecipes(querySnapshot);
setupUI(user);
} else {
setupRecipes([]);
setupUI();
}
});

How can get email from current user logged in react-admin?

I am working in admin website using react-admin framework.
https://marmelab.com/react-admin/Readme.html
I need to show current user email that is logged. How can I get this functionality?
Regards
UPDATE:
There is a dedicated hook to retrieve this kind of data called useGetIdentity
ORIGINAL:
I have implemented a custom verb in the authProvier similarly to the Dwadelfri's answer. It gets pretty handy because you can access it through the built in hook useAuthProvider
inside authProvider.js
import decodeJwt from 'jwt-decode';
const getCurrentUser = () => {
// the place where you saved user's data on login
const user = JSON.parse(localStorage.getItem("user"));
const decodedToken = decodeJwt(user.token);
return {
...user,
...decodedToken,
};
}
export default {
login: loginHandler,
logout: logoutHandler,
checkAuth: checkAuthHandler,
checkError: checkErrorHandler,
getPermissions: getPermissionsHandler,
//custom verbs
signUp: signUpHandler,
getCurrentUser: getCurrentUser,
};
Then the code looks pretty neat when you decide to call it:
const authProvider = useAuthProvider();
const user = authProvider.getCurrentUser();
I only found this kinda hacky way, I'm looking for better alternative but for right now I'm using this to get the user id which could be used to get other stuff via the dataProvider.
import decodeJwt from 'jwt-decode';
const getUserId = () => {
return decodeJwt(localStorage.getItem("token")).sub;
}
this asumes you use jwt and your token includes the user id
const token = jwt.sign({ sub: user.id}, config.secret);
the solution that I got is the following:
Inside your Data page (posts.js for instance)
First import the following Firebase package
import firebase from 'firebase/app';
Then get the connected user straight from the firebase context as the following:
const email = firebase.auth().currentUser.email;
The filter part would be the following:
<List
{...props}
filter={{ createdby: email }}>```

How to change email in firebase auth?

I am trying to change/update a user's email address using :
firebase.auth().changeEmail({oldEmail, newEmail, password}, cb)
But I am getting ...changeEmail is not a function error. I found the reference here from the old firebase docu.
So how to I do it in the 3.x version? Because I cant find a reference in the new documentation.
You're looking for the updateEmail() method on the firebase.User object: https://firebase.google.com/docs/reference/js/firebase.User#updateEmail
Since this is on the user object, your user will already have to be signed in. Hence it only requires the password.
Simple usage:
firebase.auth()
.signInWithEmailAndPassword('you#domain.example', 'correcthorsebatterystaple')
.then(function(userCredential) {
userCredential.user.updateEmail('newyou#domain.example')
})
If someone is looking for updating a user's email via Firebase Admin, it's documented over here and can be performed with:
admin.auth().updateUser(uid, {
email: "modifiedUser#example.com"
});
FOR FIREBASE V9 (modular) USERS:
The accepted answer will not apply to you. Instead, you can do this, i.e., import { updateEmail } and use it like any other import. The following code was copy/pasted directly from the fb docs at https://firebase.google.com/docs/auth/web/manage-users
Happy coding!
import { getAuth, updateEmail } from "firebase/auth";
const auth = getAuth();
updateEmail(auth.currentUser, "user#example.com").then(() => {
// Email updated!
// ...
}).catch((error) => {
// An error occurred
// ...
});
You can do this directly with AngularFire2, you just need to add "currentUser" to your path.
this.af.auth.currentUser.updateEmail(email)
.then(() => {
...
});
You will also need to reauthenticate the login prior to calling this as Firebase requires a fresh authentication to perform certain account functions such as deleting the account, changing the email or the password.
For the project I just implemented this on, I just included the login as part of the change password/email forms and then called "signInWithEmailAndPassword" just prior to the "updateEmail" call.
To update the password just do the following:
this.af.auth.currentUser.updatePassword(password)
.then(() => {
...
});
updateEmail needs to happen right after sign in due to email being a security sensitive info
Example for Kotlin
// need to sign user in immediately before updating the email
auth.signInWithEmailAndPassword("currentEmail","currentPassword")
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success now update email
auth.currentUser!!.updateEmail(newEmail)
.addOnCompleteListener{ task ->
if (task.isSuccessful) {
// email update completed
}else{
// email update failed
}
}
} else {
// sign in failed
}
}
async updateEmail() {
const auth = firebase.auth();
try {
const usercred = await auth.currentUser.updateEmail(this.email.value);
console.log('Email updated!!')
} catch(err) {
console.log(err)
}
}
You can use this to update email with Firebase.
Firebase v9:
const changeEmail = (userInput) => {
const { newEmail, pass } = userInput
signInWithEmailAndPassword(auth, oldEmail, pass)
.then(cred => updateEmail(cred.user, newEmail))
}

Categories