Storing user in database within Firestore - javascript

I am working with Firebase and I'm having some troubles. When creating a new user, I am able to store it in my database, but later, when accessing to another component it fails.
//Register a new user in our system
registerUserByEmail(email: string, pass: string) {
return this.afAuth.auth
.createUserWithEmailAndPassword(email, pass)
.then(res => {
this.email = res.user.email;
let user = {
email: this.email,
goal: "",
previousGoals: [],
progress: {
accomplishedToday: false,
completedGoals: 0,
daysInRow: 0,
unlockedBadges: []
}
};
// store in database
new Promise<any>((resolve, reject) => {
this.firestore
.collection("users")
.add(user)
.then(
res => {
console.log(res.id);
this.isAuthenticated = true;
this.router.navigate(["/dashboard"]);
},
err => reject(err)
);
});
});
}
I believe that this piece of code is basically registering as a user the email and storing it successfully into my database (checked it).
Nevertheless, when rendering home.component or /dashboard
home.component
ngOnInit() {
this.setAuthStatusListener();
this.getUser();
}
getUser() {
this.data.getUser().subscribe(user => {
this.user = user.payload.data();
});
}
data.service
getUser() {
return this.firestore
.collection("users")
.doc(this.currentUser.uid)
.snapshotChanges();
}
I get the following error
ERROR
TypeError: Cannot read property 'uid' of null

It looks like by the time you call getUser the user hasn't been authenticated yet.
The simple fix to get rid of the error is to check for this condition in your DataService's getUser:
getUser() {
if (this.currentUser) {
return this.firestore
.collection("users")
.doc(this.currentUser.uid)
.snapshotChanges();
}
}
Given this sequence of calls however, I think there may be a better way to handle your use-case:
ngOnInit() {
this.setAuthStatusListener();
this.getUser();
}
Since you're attaching an auth state listener, you probably want to only start watching the user's data in Firestore once the user has actually been authenticated.
Once simple way to do that is to call out to your DataService's getUser() method from within the auth state listener, once the user is authenticated. Something like this:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
this.getUser(); // call the DataService's getUser method
}
});

Related

userSession is null after Auth.signUp with "autoSignIn" enabled (AWS Cognito)

I need to get the jwtToken from the Auth.signUp. Is this possible if i enable autoSignIn:{enabled:true}?
const signUp = async () => {
await Auth.signUp({
username: email,
password,
attributes: {
email, // optional
name,
},
autoSignIn:{
enabled: true
}
})
.then((data) => {
console.log(data.user); //user.signInUserSession is null
})
.catch((err) => {
if (err.message) {
setInvalidMessage(err.message);
}
console.log(err);
});
await Auth.currentAuthenticatedUser()
.then(user =>{
console.log(user)
})
.catch(error => {
console.log(error) //"User is not authenticated"
})
};
I call I want the jwttoken from the userSession data for conditional rendering and I store the token in my router.js. The response object from Auth.signUp contains a CognitoUser which has a signInUserSession value but its's null.
EDIT: Tried to call Auth.currentAuthenticatedUser() after but yields an error that user is not authenticated. But when i restart my app, the user will be authenticated. I still cant authenticate user on the same app "instance"
import { Auth, Hub } from 'aws-amplify';
const listener = (data) => {
switch (data.payload.event) {
case 'autoSignIn':
console.log('auto sign in successful');
console.log(data.payload) //returns user data including session and tokens.
//other logic with user data
break;
}
};
Above is the code to initalize the Hub listener provided by amplify api. Ater user presses sign up, I called to get user session data when user is automatically signed in.
Hub.listen('auth', listener)

axios get request not working inside useEffect

I am using axios get request to check if user logged in or not with jwt, However, when app launched it keeps showing the loading state is it set to true in the first time the app launch then it supposes to make get request and validate the user then set loading state to false and navigate to a specific route. what i am getting is loading state is true all time and request not send to backend server.
here is the function to check if user logged in or not:
useEffect(() => {
const checkLoggedIn = async () => {
const Token = await AsyncStorage.getItem('Token');
if (Token) {
axios.get('http://localhost:3000/isUserAuth', {
headers: {
'x-access-token': Token
}
}).then((res) => {
setUser(res.data.user)
AsyncStorage.setItem("Token", res.token);
setIsLoading(false);
}).catch((error) => {
console.log(error)
setIsLoading(false);
});
} else {
setUser(null);
setIsLoading(false)
}
}
checkLoggedIn();
}, []);
and this is the backend:
app.get('/isUserAuth', verifyJWT, (req, res) => {
const token = req.headers['x-access-token'];
let sqlCheck = `SELECT * FROM users where id =?`;
CON.query(sqlCheck, req.user, (err, user) => {
if (user) {
console.log(user)
return res.status(400).json({ auth: true, user: user, Token: token })
}
})
})
Hope someone help me identifying the problem. thanks
If your loading state is not changing to false that signals to me that it's not a problem with your database call because even if your call is failing the else should trigger and still set loading to false.
Might consider narrowing down the function complexity and building up from there. Maybe something like the following to make sure your loading state is correctly updating:
useEffect(() => {
const checkLoggedIn = async () => {
setUser(null);
setIsLoading(false)
}
checkLoggedIn();
}, []);
What about setIsLoading to true when the component mounts, then run the async/await based on its value?
useEffect(() => {
setIsLoading(false);
if (!setIsLoading) {
try {
// fetch code
} catch(e) {
//some error handling
} finally {
// something to do whatever the outcome
}
}
})
// I found the solution
the problem is from the backend server where user returns undefined and the mistake i made that only check if(user) and didn't set else which not give a response back to font-end which indicate that state keep true
so backend code should be like:
CON.query(sqlCheck, req.user, (err, user) => {
if (user) {
return res.status(200).json({ auth: true, user: user, Token: token })
}else{ return res.status(400).json({ auth: false, user: null })}
})

How can I differentiate between authenticated users?

I am building an app that has both merchants and clients. Merchants offer their services and clients can book services from the merchants.
They BOTH are authenticated with Firebase and are on the Authentication list you can find on the Firebase Console.
On sign up, merchants' info go to a collection called 'businesses'. Clients go on a collection called 'users'.
This is how I create a 'user' document
async createUserProfileDocument(user, additionalData) {
if (!user) return
const userRef = this.firestore.doc(`users/${user.uid}`)
const snapshot = await userRef.get()
if (!snapshot.exists) {
const { displayName, email, photoURL, providerData } = user
const createdAt = moment().format('MMMM Do YYYY, h:mm:ss a')
try {
await userRef.set({
displayName,
email,
photoURL,
createdAt,
providerData: providerData[0].providerId, //provider: 'google.com', 'password'
...additionalData,
})
} catch (error) {
console.error('error creating user: ', error)
}
}
return this.getUserDocument(user.uid)
}
async getUserDocument(uid) {
if (!uid) return null
try {
const userDocument = await this.firestore.collection('users').doc(uid).get()
return { uid, ...userDocument.data() }
} catch (error) {
console.error('error getting user document: ', error)
}
}
This is how 'users' sign up
export const Register = () => {
const history = useHistory()
async function writeToFirebase(email, password, values) { //function is called below
try {
const { user } = await firebaseService.auth.createUserWithEmailAndPassword(email, password)
firebaseService.createUserProfileDocument(user, values)
} catch (error) {
console.error('error: ', error)
}
}
//Formik's onSubmit to submit a form
function onSubmit(values, { setSubmitting }) {
values.displayName = values.user.name
writeToFirebase(values.user.email, values.user.password, values) //function call
}
This is how a 'merchant' registers. They sign up with email + password and their info from a form go to a collection called 'businesses'
firebaseService.auth.createUserWithEmailAndPassword(values.user.email, values.user.password)
await firebaseService.firestore.collection('businesses').add(values) //values from a form
Here is where I would like to be able to differentiate between 'users' and 'merchants', so that I can write some logic with the 'merchant' data. so far it only works with 'users'
useEffect(() => {
firebaseService.auth.onAuthStateChanged(async function (userAuth) {
if (userAuth) {
//**how can I find out if this userAuth is a 'merchant' (business) or 'user' (client)
const user = await firebaseService.createUserProfileDocument(userAuth)
setUsername(user.displayName)
//if (userAuth IS A MERCHANT) setUserIsMerchant(true) **what I'd like to be able to do
} else {
console.log('no one signed in')
}
})
}, [])
The recommended way for implementing a role-based access control system is to use Custom Claims.
You will combine Custom Claims (and Firebase Authentication) together with Firebase Security Rules. As explained in the doc referred to above:
The Firebase Admin SDK supports defining custom attributes on user
accounts. This provides the ability to implement various access
control strategies, including role-based access control, in Firebase
apps. These custom attributes can give users different levels of
access (roles), which are enforced in an application's security rules.
Once you'll have assigned to your users a Custom Claim corresponding to their user role (e.g. a merchant or client Claim), you will be able to:
Adapt your Security Rules according to the claims;
Get the Claim in your front-end and act accordingly (e.g. route to specific app screens/pages, display specific UI elements, etc...)
More precisely, as explained in the doc, you could do something like:
useEffect(() => {
firebaseService.auth.onAuthStateChanged(userAuth => {
if (userAuth) {
userAuth.getIdTokenResult()
.then((idTokenResult) => {
// Confirm the user is a Merchant or a Client
if (!!idTokenResult.claims.merchant) {
// Do what needs to be done for merchants
} else if (!!idTokenResult.claims.client) {
// Do what needs to be done for clients
}
} else {
console.log('no one signed in')
}
})
}, [])
You may be interested by this article which presents "How to create an Admin module for managing users access and roles" (disclaimer, I'm the author).

Receiving [Error: Internal] in RN app when triggering a Cloud Function

I have a Google Cloud Function which I am calling from my RN app but it is returning
[Error: Internal]
I have set the permission to Unauthenticated users so anyone can call it - for testing purposes only. When I set to Authenticated users permission, it throws another error [Error: Unauthenticated] eventhough I am authenticated and I can get the currentUser id in my app.
Tried searching for this error but it didnt send me to any possible solutions so decided to post here and hopefully recieve responses that will help me fix it.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createUser = functions.region('europe-west1').https.onCall(async (data, context) => {
try {
//Checking that the user calling the Cloud Function is authenticated
if (!context.auth) {
throw new UnauthenticatedError('The user is not authenticated. Only authenticated Admin users can create new users.');
}
const newUser = {
email: data.email,
emailVerified: false,
password: data.password,
disabled: false
}
const role = data.role;
const userRecord = await admin
.auth()
.createUser(newUser);
const userId = userRecord.uid;
const claims = {};
claims[role] = true;
await admin.auth().setCustomUserClaims(userId, claims);
return { result: 'The new user has been successfully created.' };
} catch (error) {
if (error.type === 'UnauthenticatedError') {
throw new functions.https.HttpsError('unauthenticated', error.message);
} else if (error.type === 'NotAnAdminError' || error.type === 'InvalidRoleError') {
throw new functions.https.HttpsError('failed-precondition', error.message);
} else {
throw new functions.https.HttpsError('internal', error.message);
}
}
});
in my RN app I am calling it like this:
var user = {
role: role
}
const defaultApp = firebase.app();
const functionsForRegion = defaultApp.functions('europe-west1');
const createUser = await functionsForRegion.httpsCallable('createUser');
createUser(user)
.then((resp) => {
//Display success
});
console.log(resp.data.result);
})
.catch((error) => {
console.log("Error on register patient: ", error)
});
I think the way I am calling it in my RN app is correct because I have tested it with a testFunction and I returned a simple string. So, I believe the problem is somewhere in the function itself.
EDIT: I just tested by simply calling the function and returning the context and it always returns Internal error:
exports.registerNewPatient = functions.region('europe-west3').https.onCall((data, context) => {
return context; //this is returned as INTERNAL error.
}
I just cant get to understand whats going on here, why does it return Internal error when I am authenticated as a user and it should return the authenticated user data, isn't that right?
Try some console.log(context) ; console.log(data) statements in your registerNewPatient function and take a look at the logs. What do they say?
Some other things to consider might include that in your client code you use europe-west1 while your function code has europe-west3. Try to have those line up and see if it works? From my experience, if a specified function isn't found to exist, the client receives an INTERNAL error.

Create user in Firebase and save its data in Firestore at the same time in Angular

I'm doing a web application in Angular 6 where users can create an account. Within a form, they give their names, gender, birth, etc... I would like to save this data to Firestore when a new account is created.
Right now, the account is created correctly but, the data is not being saved in Firestore.
I have tried setting the function as async and the call to add data to Firestore as await but, it didn't work. I thought that the line of save data to Firestore was being ignored.
My user model:
export interface User {
firstName: string;
secondName: string;
firstSurname: string;
secondSurname: string;
emailAddress: string;
}
When you finished the form and you click on Submit:
onFormSubmit() {
const password = this.personalInformation.get('password').value;
const user: User = {
firstName: this.personalInformation.get('firstName').value,
secondName: this.personalInformation.get('secondName').value,
firstSurname: this.personalInformation.get('firstSurname').value,
secondSurname: this.personalInformation.get('secondSurname').value,
emailAddress: this.personalInformation.get('emailAddress').value
};
this.authService.register(user, password)
.then(res => {
}, err => {
console.log(err);
});
}
The method to create the user and add the data to Firestore:
register(user: User, password: string) {
return new Promise<any>((resolve, reject) => {
this.afAuth.auth.createUserWithEmailAndPassword(user.emailAddress, password)
.then(res => {
Object.assign(user, {
'dateOfCreationAccount': new Date(res.user.metadata.creationTime),
'lastSignInTime': new Date(res.user.metadata.lastSignInTime)
});
// Add user to firestore
this.firestoreService.addUser(res.user.uid, user);
// Send verification email
this.sendVerificationEmail();
// Logout user
this.logout();
resolve(res);
}, err => reject(err));
});
}
The same method with async/await:
register(user: User, password: string) {
return new Promise<any>((resolve, reject) => {
this.afAuth.auth.createUserWithEmailAndPassword(user.emailAddress, password)
.then(async res => {
Object.assign(user, {
'dateOfCreationAccount': new Date(res.user.metadata.creationTime),
'lastSignInTime': new Date(res.user.metadata.lastSignInTime)
});
// Add user to firestore
await this.firestoreService.addUser(res.user.uid, user);
// Send verification email
this.sendVerificationEmail();
// Logout user
this.logout();
resolve(res);
}, err => reject(err));
});
}
Method to add data to Firestore:
addUser(uid: string, data: User) {
this.usersCollection.doc(uid).set(data);
}
It seems that the line this.firestoreService.addUser is ignored. The idea is to save the user's data immediately to Firestore after creating its account.
Try to change this:
addUser(uid: string, data: User) {
this.usersCollection.doc(uid).set(data);
}
to that:
async addUser(uid: string, data: User) {
await this.usersCollection.doc(uid).set(data);
}
Edit 1
A bit more clarification - If you have some Promise and want to wait for value from that after resolve you can use await keyword but to use it you have to add async modifier to method/function and wherever you will use that you should add await before method/function execution. This await could be skipped and IDE probably warns you but to be sure that it will wait for that specific action to execute you should add await.

Categories

Resources