Firebase denies permission after first time then works on second time - reactjs

Firebase denies permission after the first time to push data to realtime database... but then works when I push data on the second time...
export const writeClass = async (
courseId,
topicName,
classIntro,
youtubeLinkId,
classNoteLink
) => {
const dbRef = ref(getDatabase());
try {
const totalClasses = await get(
child(dbRef, `Courses/${courseId}/totalClasses`)
);
var totalClassesNum = parseInt(totalClasses.val()) + 1;
console.log(totalClasses.val());
const msg = await update(
ref(db, `Courses/${courseId}/classes/${totalClassesNum}`),
{
classIntro: classIntro,
topicName: topicName,
youtubeLinkId: youtubeLinkId,
classNoteLink: classNoteLink,
}
)
.then(() => {
update(ref(db, `Courses/${courseId}`), {
totalClasses: totalClassesNum,
});
console.log("Section added");
return true;
})
.catch((error) => {
console.log(error.message);
// alert(error.message)
return error.message;
});
return msg;
} catch (error) {
// alert(error.message)
console.log(error);
}
};
The firebase rules :
".write" :
"root.child('specialUsers').child(auth.uid).child('is_admin').val()
=== true

Firebase automatically restores the signed-in user when the page reloads/app restarts, but this requires it to call the server (to check a.o. whether the account was disabled) and that may take some time.
This means that the first time your code runs the user may not be signed in yet, and you need to check for that in your code. It's a bit unclear exactly where in your code the problem occurs, but you'll want to wrap that in:
if (firebase.auth().currentUser !== null) {
...
}
Even better would be to react to when the user is signed in or out, which you can do as shown in the first snippet in the documentation on getting the currently signed in user:
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
const uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});

Related

Update user details on Firestore Database - web V9

I've been trying to figure out this problem for the last few weeks - I've read the full documentation for Firebase and searched from the previously answered questions on here with no luck.
I have a web application made in React.
The 'getUserDetails' and 'updateUserDetails' functions are on a page called SignUp.js.
The 'button' where the user clicks is on a page called 'Profile.js'.
The 'getUserDetails' function works fine and returns the data correctly. However, when I try to update a user's age, height.. etc., I get one of a few errors when I play around with it:
"Error updating user details"
"Blocked by DevTools" XHR error
"Error updating user details: FirebaseError: Expected type 'ya', but it was: a custom Ia object'
My goal here is just to enable a user to be able to update their details in the firebase DB from their 'profile' page.
// SignUp.js
const getUserDetails = async (uid) => {
const user = auth.currentUser;
if (user) {
const userRef = doc(collection(db, "users"), uid);
const detailsRef = collection(userRef, "details");
return getDocs(detailsRef);
} else {
console.log("No user logged in");
}
}
const updateUserDetails = async (uid, updatedDetails) => {
try {
const userRef = doc(collection(db, "users"), uid);
const detailsRef = collection(userRef, "details");
const docSnap = await getDoc(detailsRef);
console.log(detailsRef);
if (docSnap.exists()) {
await updateDoc(detailsRef, updatedDetails);
console.log("User details updated in Firestore!");
return true;
} else {
console.log("No such document!");
return false;
}
} catch (e) {
console.error("Error updating user details: ", e);
return false;
}
}
The below is the handleUpdate that is call when the button is clicked.
// Profile.js
const handleUpdate = () => {
const auth = getAuth();
const uid = auth.currentUser.uid;
updateUserDetails(uid, userDetails).then(r => {
if (r) {
console.log("User details updated successfully!");
} else {
console.log("Error updating user details");
}
});
}
Can anyone see where I might be going wrong?

LocalStorage updating inconsistently with setInterval

After calling the refresh token endpoint to refresh the user's auth tokens, the local storage does not update the token field consistently. Sometimes, the local storage is updated properly and the app works well, other times the token and admin/student fields are deleted from local storage despite no error being logged and the endpoint returning a success response. How do I fix this? Code below
import { parseTokens, parseAdmin, parseUser } from "../utils/auth-parser";
import { adminAuthFetch } from "../config/axios/axios-admin.config";
import { studentAuthFetch } from "../config/axios/axios-user.config";
export const refresher = async () => {
const admin = parseAdmin();
const student = parseUser();
const token = parseTokens();
if (!admin && !student) {
return;
}
if (admin && !student) {
console.log(
"==========================refreshing token==================",
new Date().getMilliseconds()
);
try {
const response = await adminAuthFetch.post(`/auth/refresh-tokens`, {
refresh_token: token.refresh,
});
const data = response?.data;
console.log(data);
localStorage.setItem(
"tokens",
JSON.stringify({
access: data?.data?.auth_token,
refresh: data?.data?.refresh_token,
})
);
} catch (error) {
console.log(error);
localStorage.removeItem("tokens");
localStorage.removeItem("admin");
}
} else if (student && !admin) {
console.log(
"==========================refreshing student token==================",
new Date().getMilliseconds()
);
try {
const response = await studentAuthFetch.post(`/auth/refresh-tokens`, {
refresh_token: token.refresh,
});
const data = response?.data;
console.log(data);
localStorage.setItem(
"tokens",
JSON.stringify({
access: data?.data?.auth_token,
refresh: data?.data?.refresh_token,
})
);
} catch (error) {
console.log(error)
localStorage.removeItem("tokens");
localStorage.removeItem("student");
}
}
};
Here's the Effect that is called from the root app
const refreshFunction = () => {
if (!refreshRef.current) {
refreshRef.current = true;
refresher();
} else {
refreshRef.current = false;
}
};
useEffect(() => {
const timer = setInterval(refreshFunction, 1000 * 60 * 2);
return () => clearInterval(timer);
}, []);
Despite receiving a success response from the endpoint and ensuring refresher function is called only once with the useref check, the token field in the local storage doesn't update consistently. Sometimes the values are updated, sometimes they are deleted without an error being logged to the console. Tried removing strict mode but it still does not work
Without being certain about how everything in your code works, it's possible that despite your best intentions, the refresher function is rendering twice.
Could you share more context around the React version you're using? If you're using version 17, try doing something like this:
let log = console.log
at the top level of your code, and use it for logging instead. My working theory is that some form of console.log suppression is happening on a second render, which is why you're not getting the logs, even though the localStorage removeItem call is still executing.
Let me know the React version, and we can continue debugging.

Failing to call two APIs one after another and redirecting the user to another page

I'm using Reactjs for my frontend and Springboot and Firebase for my backend. Basically, when a user registers for an account, I will call two APIs to post the account to both Firebase and my Springboot server, the reason being that I need to make use of the user data collected inside my own Springboot server.
The problem I'm facing is that after a user registers for an account, the Springboot API is not called after Firebase API is called. (I call the Firebase API first and then the Springboot one) It seems to me that after calling the Firebase API, everything stops and the code doesn't go any further, thus calling no more API.
How do I make sure that I can call both APIs one after another and then redirect the user to the next page without any interference?
Registration on submit in Reactjs:
const handleOnSubmit=(event: React.FormEvent<HTMLFormElement>)=> {
if (password !== secondPassword) {
setPasswordsMatched(false);
console.log("passwords matched when password!==secondPassword:" + passwordsMatched);
} else if(!username){
setUsernameExists(false);
}else if(!email){
setEmailExists(false);
}else if(!password||!secondPassword){
setPasswordExists(false);
}else{
if(subscribedStatus){
let subscribed:string="subscribed";
firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}else{
let subscribed:string="unsubscribed";
firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}
}
}
//This is the callback function put inside the Firebase API to see if Firebase accepts the registration. If yes, the user is redirected to "/verification-email"
const handleSignupSuccess=(signupStatus:boolean)=>{
setSignupSuccess(signupStatus);
if(signupStatus){
firebaseAuthServiceEmailVerification(setEmailVerificationSent);
navigate("/verification-email");
}
}
Firebase API:
export const firebaseAuthServiceSignUpWithEmailAndPassword= (username:string,email: string, password: string, subscribed:string,callback: (isSuccess:boolean)=>void) =>{
const auth = getAuth();
createUserWithEmailAndPassword(auth, email, password)
.then(async ( userCredential) => {
// Signed in
const user = userCredential.user;
await postAccount(username, email, password, user.uid, subscribed);
callback(true);
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
callback(false);
// ..
});
}
Springboot API:
export const postAccount=(username:string,email:string,password:string,firebaseUid:string,subscribedStatus:string)=>{
axios.post(`http://localhost:8080/user/${username}/${email}/${password}/${firebaseUid}/${subscribedStatus}`
)
.then((res)=>{
}).catch((error)=>{
console.log(error);
})
}
You mostly need a return statement in postAccount function
Quick fix
export const postAccount=(username:string,email:string,password:string,firebaseUid:string,subscribedStatus:string)=>{
// return here
return axios.post(`http://localhost:8080/user/${username}/${email}/${password}/${firebaseUid}/${subscribedStatus}`
)
.then((res)=>{
return res; // may be return here too
}).catch((error)=>{
console.log(error);
})
}
// async function
const handleOnSubmit= async (event: React.FormEvent<HTMLFormElement>)=> {
if (password !== secondPassword) {
setPasswordsMatched(false);
console.log("passwords matched when password!==secondPassword:" + passwordsMatched);
} else if(!username){
setUsernameExists(false);
}else if(!email){
setEmailExists(false);
}else if(!password||!secondPassword){
setPasswordExists(false);
}else{
if(subscribedStatus){
let subscribed:string="subscribed";
// wait till completion
await firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}else{
let subscribed:string="unsubscribed";
// wait till completion
await firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}
}
}
Slightly better fixs:
With multiple API call its better to use async calls
export const firebaseAuthServiceSignUpWithEmailAndPassword = async (username:string, ...) => {
try {
const auth = getAuth();
const userCredentials = await createUserWithEmailAndPassword(auth, email, password)
const user = userCredential.user;
const res = await postAccount(username, email, password, user.uid, subscribed);
// may be some extra checks
//if (res.success) {
// callback(true);
//}
callback(true);
} catch(error: any) {
// handle error
callback(false);
}
}
export const postAccount = async (username: string, ...) => {
return await axios.post(`http://localhost:8080/user/${username}/${email}/${password}/${firebaseUid}/${subscribedStatus}`)
}
Hope it helps in someway

Persist auth state in react/react native for Firebase

I am using react native for an ios app and firebase for authentication. Every time I leave the app and come back, it asks for a login. I want to persist the firebase login but don't really know where to put it.
I know I need to put this in:
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
I have the following signIn function that runs when the login button is pressed on the signInScreen:
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
isMounted.current && setLoading(false);
}
};
I have the following signIn stuff in my firebaseContext:
const Firebase = {
getCurrentUser: () => {
return firebase.auth().currentUser;
},
signIn: async (email, password) => {
return firebase.auth().signInWithEmailAndPassword(email, password);
},
getUserInfo: async (uid) => {
try {
const user = await db.collection("users").doc(uid).get();
if (user.exists) {
return user.data();
}
} catch (error) {
console.log("Error #getUserInfo", error);
}
},
logOut: async () => {
return firebase
.auth()
.signOut()
.then(() => {
return true;
})
.catch((error) => {
console.log("Error #logout", error);
});
},
};
Where do I put the persist code I listed above from the docs?
Thanks!
When do you check if someon is signed in or not?
From the code shown it looks like you check it manuelly by calling currentUser. You have to consider that the persistance of auth state is asynchronous. That means if you call currentUser on auth before the localy saved auth state is loaded you would get there null and thing that the user is not signed in.
To get the auth state Firebase recommend to use the onAuthStateChanges event listener. With that you can listen to auth state changes no matter if you logged in or the persistet auth state is loaded.
The usage is very simple:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
That is the reson I asked where you check if someon is signed in or not. If I could see that code I could help you adopt it to use that event listener.

How to type response from jwt.decode ()

I am using React app and using my JWT information recorded in my localstorage. The problem is that in Typescipt, I can't access my object's response, even if I put an interface.
Where am I going wrong?
This is my code:
// auth.ts
import jwt from 'jsonwebtoken';
...
export const getUser = () => {
const tokenBearer = getToken();
const hashToken = tokenBearer ? tokenBearer.split(' ') : false;
if(!hashToken)
return false;
const decode = jwt.decode(hashToken[1]);
return decode;
}
//component
...
import { isAuthenticated, getUser } from '../../services/auth'
interface User {
uid: number,
data: {
id: number,
username: string,
nome_completo: string,
avatar: string,
role: string
}
}
function Topbar () {
const user: User = getUser();
return(
<p> {user.uid} </p>
);
There are multiple potential points of failure here which need to be handled. Even with proper typing, you will not be able to simply assign const user: User = getUser(); because getUser will not always return a valid user. You're already written that it returns boolean false if there is no token. There will be an uncaught error on decoding hashToken[1] if there was no space in the tokenBearer since it would have length 1 (did you mean hashToken[0]?). And, depending on the setup of your app and what getToken() does, there's a possibility that the decoded object is something other than a User. The return type of jwt.decode says that it can return null or string in addition to a keyed object.
My personal design strategy would be to declare that getUser will either return a User or throw an Error. Then you can wrap it in a try/catch block in TopBar.
const isUser = (user: any): user is User => {
return typeof user === "object" && "uid" in user && "data" in user;
}
export const getUser = (): User => {
const tokenBearer = getToken();
if (!tokenBearer) {
throw new Error("no token found");
}
const hashToken = tokenBearer.split(' ');
if (!hashToken[1]) {
throw new Error("invalid token length");
}
const decode = jwt.decode(hashToken[1]);
if (!isUser(decode)) {
throw new Error("invalid token object");
}
return decode;
}
function Topbar() {
try {
const user = getUser();
return (
<p>{user.uid}</p>
);
} catch (error) {
console.error(error);
return null;
// but maybe return a login component instead of null
}
}
Playground Link
If you have multiple components which access getUser, you want to isolate the logic for catching invalid responses somewhere else and separate your display logic into components like RenderTopBar which require that a User be passed in as a prop.
In this case, we could also catch by providing a dummy user or uid variable since we are only accessing user.uid.
function Topbar() {
let uidString = "";
try {
const user = getUser();
uidString = user.uid.toString();
} catch (error) {
console.error(error);
}
return (
<p>{uidString}</p>
);
}

Resources