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?
Related
I am facing an issue in my React app when implementing Google Auth using Firebase. After a successful login, I need to check if the user exists in my Firebase database. If the user does not exist, I log them out. The issue I am facing is that after logging out, I am unable to sign in again as the authentication popup does not appear. I believe this can be due to caching of the current user in the browser.
Here's my current signInWithGoogle function:
const signInWithGoogle = async () => {
const result = await signInWithPopup(auth, provider);
console.log(result.user)
const mailId = result.user.email ? result.user.email:""
const Isvalid = await validate(mailId);
if (Isvalid) {
console.log('validated user')
navigate('/dashboard')
} else {
signOut(auth).then(() => {
console.log('Signout Successful');
}).catch((err) => {
console.log('Error', err);
})
alert('not a valid user')
}
}
I want to add the functionality for admins to disable end users access if necessary. It works just fine with non-SSO users. The check will prevent the user from logging in and show them a 'user is not active error'. When a non-active user tries to use Azure SSO to log in, the Azure SSO is still successful and displaying a spinner because there is not an active user. It should not allow them to 'log in' and redirect them to the home page with a displayed error that says 'user is not active'
Here is the function to change the user's isActive status on the backend
const changeUserStatus = asyncHandler(async (req, res) => {
const currentUser = await User.findById(req.user._id);
if (!currentUser) {
res.status(401);
throw new Error('User not found');
}
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true,
});
console.log(user);
res.status(201).json(user);
});
From the backend as well, here is the check for a user's isActive status in the normal login function
//check isActive status
if (user.isActive === false) {
res.status(400);
throw new Error('Not an active user');
}
Here is the check in the Azure SSO log in
if (!user.isActive) {
errors.azure = 'User is no longer permitted to access this application';
res.status(400);
throw new Error(errors.azure);
// console.log(errors);
// return res.status(401).json(errors);
}
Here is my authService.js
// Login user
const login = async (userData) => {
const response = await axios.post(API_URL + 'login', userData);
if (response.data) {
localStorage.setItem('user', JSON.stringify(response.data));
}
return response.data;
};
const azureLogin = async () => {
const response = await axios.get(API_URL + 'az-login');
return response.data;
};
Here is my authSlice
// Login user
export const login = createAsyncThunk('auth/login', async (user, thunkAPI) => {
try {
return await authService.login(user);
} catch (error) {
return thunkAPI.rejectWithValue(extractErrorMessage(error));
}
});
// Login user using AAD - this action sends the user to the AAD login page
export const azureLogin = createAsyncThunk(
'users/azureLogin',
async (thunkAPI) => {
try {
return await authService.azureLogin();
} catch (error) {
return thunkAPI.rejectWithValue(extractErrorMessage(error));
}
}
);
// Login user using AAD - this action redirects the user from the AAD login page
// back to the app with a code
export const azureRedirect = createAsyncThunk(
'users/azureRedirect',
async (code, thunkAPI) => {
try {
return await authService.azureRedirect(code);
} catch (error) {
return thunkAPI.rejectWithValue(extractErrorMessage(error));
}
}
);
And here is the AzureRedirect.jsx component. This is the component that receives the flow from the Microsoft/AAD login page. It is the re-entry point of the application, so to speak.
useEffect(() => {
const code = {
code: new URLSearchParams(window.location.search).get('code'),
};
if (user) {
toast.success(`Logged in as ${user.firstName} ${user.lastName}`);
navigate('/');
} else if (code) {
// This CANNOT run more than once
const error = dispatch(azureRedirect(code));
console.log(error);
} else {
console.log('No code found in URL');
}
}, [dispatch, navigate, user]);
if (!user) {
displayedOutput = <Spinner />;
} else {
displayedOutput = (
<div>
An error has been encountered, please contact your administrator.
<br />
<Link to='/login'>Return to Login</Link>
</div>
);
}
return <div className='pt-4'>{displayedOutput}</div>;
Somewhat randomly my login flow stops working for some users when using google as a login provider (possibly facebook too, unsure) on iOS. They are able to log in on desktop using google login with the same app bundle. I think this is happening for ~1/10 users.
After a user selects which google account to use, the google redirects back to my app. The onAuthStateChange triggers, but the user is null. This is after successfully "logging in" with google. I think this may have to do with users trying to use multiple auth providers and getting stuck in a weird state. I'm not receiving any error or console logs when this happens, it's as if it's a fresh page load instead of a redirect.
I haven't been able to reproduce the issue using my own account until recently. I tried reverting back to earlier builds when the issue wasn't present for my account, with no luck. I'm now unable to log into my own app 😂
in UserProvider.tsx:
firebaseConfig.apiKey = process.env.REACT_APP_AUTH_API_KEY;
firebaseConfig.authDomain = process.env.REACT_APP_AUTH_DOMAIN;
firebaseConfig.projectId = "redacted";
firebaseConfig.storageBucket = "redacted.appspot.com";
firebaseConfig.messagingSenderId = "redacted";
firebaseConfig.appId = "redacted";
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize Firebase Authentication and get a reference to the service
const auth = initializeAuth(app, {
popupRedirectResolver: browserPopupRedirectResolver,
persistence: [indexedDBLocalPersistence, browserLocalPersistence, inMemoryPersistence],
errorMap: debugErrorMap
});
const UserContextProvider = (props) => {
const signInWithGoogle = () => {
signInWithRedirect(auth, googleAuthProvider);
}
const handleUserCredential = async (result: UserCredential) => {
if (result.user) {
const additionalInfo = getAdditionalUserInfo(result)
dispatch({
type: 'SET_ADDITIONAL_USER_INFO', data: {
additionalInfo: additionalInfo,
}
});
}
}
useEffect(() => {
dispatch({ type: 'LOGIN_INITIATED' });
const handleRedirectResult = async () => {
alert("handling redirect result");
const result = await getRedirectResult(auth);
alert(`redirect result: ${result}`);
if (result) {
handleUserCredential(result)
}
}
onAuthStateChanged(auth, async (user: User | null) => {
alert(`on auth change fired: ${user}`);
if (user) {
// set the token now
const token = await getIdToken(user);
const refreshToken = async () => {
return await getIdToken(user, true);
}
onLoginSuccess(user, token, refreshToken);
} else {
onLogoutSuccess();
}
}, (error) => {
onError(error)
});
handleRedirectResult();
}, [])
onLogoutSuccess() is triggering because user is null.
PS: I spammed a bunch of alerts in there because I'm struggling to debug on my device, those aren't in my production build.
In my reactJS app, I am using firebase authentication. I can login successfully. I tried to update user profile, like photoUrl and displayName as follows. Data is updated but I can't get the latest update data unless refresh the page.
Please ignore updateCurrentUser function. it is an API call to update display name and photo URL to database.
after updateCurrentUser function callback, I call again
const auth = getAuth();
const currentUser = getAuth().currentUser;
But updated display name and photo is still old data in currentUser. I want to know how can I update profile. I am showing the user info in the header section as well.
updateUser function
async function updateUser(updateUserId, displayName, photoUrl) {
const auth = getAuth();
const currentUser = getAuth().currentUser;
return await updateProfile(auth.currentUser, {
displayName: displayName,
photoUrl: photoUrl,
})
.then(async function () {
const auth = getAuth();
if (auth.currentUser != null) {
updateCurrentUser(updateUserId, displayName, photoUrl)
.then((updatedUser) => {
if (updatedUser) {
const auth = getAuth();
const currentUser = getAuth().currentUser;
setUser(currentUser);
setAccessToken(currentUser.getIdToken(true));
store.dispatch(saveUser(currentUser));
} else {
setUser(null);
setAccessToken(null);
store.dispatch(saveUser(null));
}
})
.catch((error) => {
throw error;
});
}
})
.catch((error) => {
throw error;
});
}
Firebase show updates of user profile only after refresh and automatically after login/logout operation. The only way you can do this will be by using the same values you submitted
async function updateUser(updateUserId, displayName, photoUrl) {
const auth = getAuth();
const currentUser = getAuth().currentUser;
return await updateProfile(auth.currentUser, {
displayName: displayName,
photoUrl: photoUrl,
})
.then(async function () {
const auth = getAuth();
if (auth.currentUser != null) {
updateCurrentUser(updateUserId, displayName, photoUrl)
.then((updatedUser) => {
if (updatedUser) {
const auth = getAuth();
let currentUser = getAuth().currentUser;
currentUser.displayName = displayName;
currentUser.photoUrl = photoUrl
setUser(currentUser);
setAccessToken(currentUser.getIdToken(true));
store.dispatch(saveUser(currentUser));
} else {
setUser(null);
setAccessToken(null);
store.dispatch(saveUser(null));
}
})
.catch((error) => {
throw error;
});
}
})
.catch((error) => {
throw error;
});
}
I make a silly mistake. It is wrong spelling. It should be photoURL instead of photoUrl. It is working when refresh because, at api server side, need to update user info again. At server side, variable name is correct.
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
// ...
}
});