Pass Data to Firebase auth.user().onCreate Cloud Function - reactjs

I'm testing out firebase cloud functions and I'm curious as to how to pass data from my form to the cloud function. I've created a registration form that accepts several fields - my plan is to use firebase to create an auth account and have a cloud function that runs whenever a auth account is created. This function will create a user document inside Firestore.
Code below gives an idea of my frontend setup
import React from "react"
import firebase from "firebase"
// create some config for firebase
const app = firebase.initializeApp(config)
const auth = app.auth()
function Form({onSubmit}){
function handleSubmit(e){
e.preventDefault()
const {email, password, firstName, lastName, age} = e.target.elements
const user = {email: email.value, password: password.value, firstName: firstName.value, lastName: lastName.value, age: age.value}
onSubmit().then(() => {}, (err) => {})
}
return <form onSubmit={handleSubmite}>
{/*form fields..*/}
</form>
}
// Register function for firebase
function register(email, password){
return auth.createUserWithEmailAndPassword(email, password)
}
// Parent for form component
function Container(){
return <Form onSubmit={register}/>
}
This is what my cloud function looks like:
const functions = require("firebase-functions")
const admin = require("firebase-admin")
admin.initializeApp()
const db = admin.firestore()
const createUser = (userData, context) => {
const { email } = userData
console.log(userData)
return db
.collection("users")
.doc()
.set({
email,
firstName: "TEST",
})
.catch(console.error)
}
module.exports = {
authOnCreate: functions.auth.user().onCreate(createUser),
}
Any idea how to do this? Am I thinking about this incorrectly? I'm a bit new to firebase.
TLDR; How do I send custom data from a form to a cloud function that will create a user in firestore on a new user being created in auth?

my plan is to use firebase to create an auth account and have a cloud function that runs whenever a auth account is created. This function will create a user document inside Firestore.
This is not possible. Since your Cloud Functions triggers on auth.user().onCreate it only has access to the information that is available with that event. You can't pass additional information to a background function.
The most common solutions are:
Write the document after the user is created, either directly from the client or by having the client call another Cloud Function directly.
Pass all data that is needed to create the user and document to a Cloud Function, and then let the Cloud Function handle both the creation of the user account and the document.

Related

How do I get an object from local storage after json.stringify()

I am currently creating a login page with ReactJS. This page utilises an API which has an 'authorise' function confirming whether the entered details are authorised to use the API. To handle the API calls, I have created a class which has a function for each possible API call.
If the user is able to login successfully (if the authorise function returns a status of 200) they are redirected to a 'ProjectSelect' page/component. From this Project Select page the user will be able to select a specific project from their project list.
Now, here is where the problem lies. I need to now use the API instance from the 'Login' component in the 'ProjectSelect' component. The way in which I am currently trying to do this involves storing it in local storage as such:
Login.jsx:
import {API} from 'filepath';
const api = new API();
async function loginUser(email, password, appid) {
return api.authorise(email, password, appid);
}
export default function Login() {
const classes = useStyles();
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const [appid, setAppId] = useState();
const handleSubmit = async e => {
e.preventDefault();
const response = await loginUser(
email,
password,
appid
);
console.log(response);
if (response.status === 200) {
swal("Success", "Logged In", "success", {
buttons: false,
timer: 2000,
})
.then((value) => {
localStorage.setItem('access_token', response.data.access_token);
localStorage.setItem('api', JSON.stringify(api));
window.location.href = "/bimpluslogin";
});
} else {
swal("Failed", "Login Failed", "error");
}
}
ProjectSelect.jsx:
const api = JSON.parse(localStorage.getItem('api'));
let allProjects = api.getAllProjects(); // Error occurs here ('getAllProjects()' does not exist)
I am aware that if you JSON.stringify() an object it will only take the key values leaving it basically unusable when parsed back through the JSON.
What I've tried:
I have tried to create a new API instance in the ProjectSelect component and storing the auth details in local storage however that causes a lot of issues and doesn't allow me to use all of the functions.
I would appreciate any suggestions on how to store an object from one component and then use that exact same object in another component. Thanks in advance.

Firebase auth causes error when trying to validate users premium status via custom claims offline

I have an app that I want premium users to be able to access a "pro dashboard".
This is how I currently get the premium user:
export default async function isUserPremium(): Promise<boolean> {
await auth.currentUser?.getIdToken(true);
const decodedToken = await auth.currentUser?.getIdTokenResult();
return decodedToken?.claims?.stripeRole ? true : false;
}
export default function usePremiumStatus(
user: UseQueryResult<User | null, AuthError>
) {
const [premiumStatus, setPremiumStatus] = useState<boolean>(false);
useEffect(() => {
if (user) {
const checkPremiumStatus = async function () {
setPremiumStatus(await isUserPremium());
};
checkPremiumStatus();
}
}, [user]);
return premiumStatus;
}
And this is how I get the status in my react component:
const isPremium = usePremiumStatus(user);
2 issues:
The state, doesn't persist and whenever I mount my component, even premium users will see basic dashboard screen for a split second, due to conditional rendering.
Everytime, I go offline, I get this error message that crashes the app.
What can I do to prevent this from happening? Or should I store users premium status in firestore database and if user goes offline, the app would use cached data, but in that case if users won't refresh the page, how can I ensure that the clients premium status is synced and is always updated with the most recent firestore data?

How to get current provider of session in Next-auth

In Next-auth, We can get session related info like user: {name, email .. etc}
something as follows:
import { useSession } from "next-auth/client"
export default function Component() {
const [session, loading] = useSession()
if (session) {
return <p>Signed in as {session.user.email}</p>
}
return Sign in
}
I want to get also the current provider name in my component to be used. For you your information, next-auth supports login by many providers as Facebook, Twitter .. etc
For example, if the user logged in via Twitter Api, I want to get this piece of info and print it in his profile page.
Sources:
https://next-auth.js.org/v3/getting-started/client#usesession
Callbacks can be used for passing additional data to session object.
Provider details are provided the first time user signs in.
You can use jwt callback to store data in jwt cookie.
In [...nextauth].js :
const callbacks = {}
callbacks.jwt = async function jwt(token, user , account) {
if (user) {
token = { id: user.id , provider:account.provider , ...moreData}
}
return token
}
const options = {
providers,
callbacks
}
Note: You also need to use session callback for passing the token data to useSession hook.
callbacks.session = async function session(session, token) {
session.user = {
provider : token.provider,
id: dbUser.id,
profile: dbUser.profile,
}
return session
}
export default (req, res) => NextAuth(req, res, options)
This way provider will be stored in the token for subsequent requests.
Learn more about callbacks here : Callbacks

How to show user information (name or avatar) on appBar?

I want to show logged user information (name, avatar, role) on appBar. I created some custom components : layout, appBar and userMenu How can I pass the logged user data to the userMenu or appBar component?
I believe that in the getIdentity method of your Auth provider you want to return an object with this information in your Promise.resolve. So it is somewhat indirect and not done within a custom AppBar itself.
Here is a snippet example from an auth provider:
getIdentity: () => {
return Promise.resolve({
id: userName,
fullName: userFullName,
avatar: <Avatar />
});
The Avatar aspect is optional of course and React-Admin will place its own avatar there if you leave that out.
One approach would be retrieving the user information from the authentication storage you are using on the client-side.
In my case, I'm showing user initials instead of the standard avatar in the component by pulling the information from the JWT token stored on localStorage.
decodeJwt(localStorage.getItem('token')).fullname
Probably not the best approach but worked for my use case.
See https://github.com/marmelab/react-admin/issues/7042
This is what worked for me in my AuthProvider. I am using session-based authentication and don't have a jwt client side - so I expose an API on the server that provides the logged in user info. I don't have an avatar so I pass the user initials (only the first letter appears on the page)
getIdentity: async () => {
const userinfo = await UserService.getUserInfo();
const {id: id, userName: fullName, firstLastInitials: avatar} = userinfo;
console.log(`getIdentity UserService user name is ${fullName}'`);
return Promise.resolve({ id, fullName, avatar });
}
and
const getUserInfo = async () => {
const apiUri = HostApiServer(); // GET SERVER URI
const mainResponse = await fetch(`${apiUri}/loggedInUser`, {
method: 'GET'
});
const resp = await mainResponse.json();
console.log(`UserService - GOT user info from ${apiUri}`);
console.log(JSON.stringify(resp));
return resp;
};
const UserService = {
doLogin,
doLogout,
isLoggedIn,
getToken,
getUserInfo
};

Call yield put() inside a success callback

I am brand new to React and Redux.
I am using react-redux to call the AWS Cognito service which takes an object containing a success and failure callback. I get my JWT back from AWS Cognito when I console.log inside my success callback; however, how can I yield put() inside this callback since it's not a generator function (function*).
Here's some code:
export function* getAWSToken(){
// username, password and userPool come from react state
// not showing code for brevity.
const userData = {
Username: username,
Pool: userPool,
};
const authenticationData = {
Username : username,
Password : password,
};
const cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
const authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
// This here is the callback
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess(result){
yield put(cognitoTokenObtained(result.getIdToken().getJwtToken())
},
onFailure(err){}
});
}
If you're using redux-saga (which is awesome), you can use the call effect to transform an asyc callback like cognitoUser.authenticateUser into a set of instructions to be executed by the middlewhere.
When the middleware resolves the call it will step through the generator to the next yield statement, you can assign the return result to a variable that you can then place in your state using a put effect.
export function* getAWSToken(){
// username, password and userPool come from react state
// not showing code for brevity.
const userData = {
Username: username,
Pool: userPool,
};
const authenticationData = {
Username : username,
Password : password,
};
const cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
const authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
// Note: since you're invoking an object method
// you'll want to pass the object's context (this) with the call via an array
const token = yield apply(cognitoUser, cognitoUser.authenticateUser, [authenticationDetails, { onSuccess(response){return response}, onFailure(){} }]);
yield put(cognitoTokenObtained(token.getIdToken().getJwtToken());
}
There's also this incredible tutorial I'd highly recommend.
Edit: You left out some code for brevity, but I'd highly recommend wrapping the code inside the generator in try catches since you're depending on external IO from an API.

Resources