The following code works, and I get an error back if there's an issue.
Note, this is vue code. Hence the .value
import { getAuth, createUserWithEmailAndPassword } from 'firebase/auth'
const register = async () => {
loading.value = true
let response
try {
const auth = getAuth()
response = await createUserWithEmailAndPassword(
auth,
form.value.email,
form.value.password
)
} catch (err) {
console.log(err.message)
}
loading.value = false
return response
}
However, here's an example of what error.message returns:
Firebase: Password should be at least 6 characters (auth/weak-password).
My question. Is there a way to get a clean message back? By that, I mean without Firebase: and (auth/weak-password).
Or am I missing something here? Is there another way I'm supposed to deal with Firebases error object? Perhaps I'm supposed to take the error.code and write a custom message myself for every scenario?
Let me know if any other information is needed, and I'll update the question :)
I'm currently using helper function, but don't think it's the best solution. Is there any better one?
function getRefinedFirebaseAuthErrorMessage(errorMesssage: string): string {
return errorMesssage
.replace('Firebase: ', '')
.replace(/\(auth.*\)\.?/, '');
}
Notice that to force Firebase 9 to return an error message as Firebase: {real, descriptive error message}. (auth/{code}). it is necessary to init it with errorMap option set to debugErrorMap:
import { FirebaseApp, initializeApp } from 'firebase/app';
import { Auth, initializeAuth, debugErrorMap } from 'firebase/auth';
const app: FirebaseApp = initializeApp(firebaseConfig);
const auth: Auth = initializeAuth(app, { errorMap: debugErrorMap });
Otherwise, Firebase will return only error codes.
import { getAuth, createUserWithEmailAndPassword } from 'firebase/auth'
const register = async () => {
loading.value = true
let response
try {
if(form.value.password.length <= 6 ){
return "Password should be at least 6 characters (auth/weak-password)"
}
const auth = getAuth()
response = await createUserWithEmailAndPassword(
auth,
form.value.email,
form.value.password
)
} catch (err) {
console.log(err.message)
}
loading.value = false
return response
}
Related
I'm trying to learn react native along with firebase firestore and I'm racking my brain trying to figure this situation out. Below is my code. I'm trying to pull user information stored in firebase. The console.log in the function returns the array with all of the values such as Document data: {"admin": true, "email": "bob#bob.com", "first": "Bob", "last": "BobLastName", "phone": "555-555-5555"}, however if I try to console.log the return outside of the function or try to assign it to a variable I'm getting. LOG {"_A": null, "_x": 0, "_y": 0, "_z": null}. Any help would be much appreciated before I chuck my laptop out of the window.
import { Pressable, Text } from 'react-native';
import { getAuth} from 'firebase/auth';
import { doc, getDoc, setDoc, getDocs, collection, query, where, Firestore } from 'firebase/firestore';
import { db } from '../components/FirebaseConfig';
import { useEffect, useId, useState } from 'react';
import { async } from '#firebase/util';
import { FlatList } from 'react-native-gesture-handler';
function HomeScreen() {
//Create an async function that will pull user details from firebase and return them as an object
async function getUserDetails() {
const user = getAuth().currentUser;
const userRef = doc(db, "users", user.uid);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
console.log("Document data:", docSnap.data());
return docSnap.data();
} else {
console.log("No such document!");
}
}
//Call the getUserDetails function and store the returned object in a variable
const userDetails = getUserDetails();
//log userdetails to the console
console.log(userDetails);
//getUserDetails()
return <Text>Welcome HomeScreen</Text>;
}
export default HomeScreen
I've tried to use the above code to get the data to work. It seems like I am not extracting the data properly.
You are getting this kind of behavior because you are calling getUserDetails() functions directly but it should be called with await or used with .then() because it is returning a Promise<DocumentData> refer this
Basically it will return a promise that will eventually resolve with the data.
To Solve this you can either use
const userDetails = await getUserDetails();
console.log(userDetails);
OR
getUserDetails().then((data) => {
console.log(data);
});
Although using useEffect here is also not a bad option it will look something like this:
function HomeScreen() {
const [userDetails, setUserDetails] = useState(null);
useEffect(() => {
async function getUserDetails() {
const user = getAuth().currentUser;
const userRef = doc(db, "users", user.uid);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
const data = docSnap.data();
console.log("Document data:", data);
setUserDetails(data);
} else {
console.log("No such document!");
}
}
getUserDetails();
}, []);
if (!userDetails) {
return <Text>Loading...</Text>;
}
return <Text>Welcome {userDetails.first}!</Text>;
}
Refer getDoc
I am using NextJS and created three layers that separate the logic.
The purpose is to minimise the error handling to the getServerSideProps section. I want to get all the lists from the database.
In the first layer, in the API route I created a folder called get-all-lists and a file [userId].js. The get request will be 'http://localhost:3000/api/get-all-lists/iudga937gr8'. Bellow there is the api route that get all the lists with the help of Prsima. It is working perfectly
import prisma from '../../../lib/prisma'
export default async function handler(req, res) {
const { userId } = req.query;
if (req.method === 'GET') {
try {
const shoppingLists = await prisma.List.findMany({ where: { userId: userId }});
res.status(200).json({lists: shoppingLists});
}
catch (error) {
console.log(error);
res.status(500).json({ message: 'Something went wrong. Please try again'});
}
}
else {
res.status(500).json({message: 'Invalid method requested!'});
}
}
The next layer, is the abstraction one which sent the final result to getServerSideProps. I created this because I need to fetch alot of requests and it would be too messy...
export const getAllLists = async userId => {
try {
const lists = await axios.get(`/api/get-all-lists/${userId}`);
return lists;
}
catch (error) {
console.log('Abstraction layer error: ', error);
return 'Something went wrong. Please try again later';
}
}
The problem arise here. In the postman I have the right result. In postman I use http://localhost:3000/api/get-all-lists/clbcpc0hi0002sb1wsiea3q5d and the server sent me the array specified.
But this function does not work and send me this error:
Abstraction layer error: TypeError [ERR_INVALID_URL]: Invalid URL
at new NodeError (node:internal/errors:371:5)
at onParseError (node:internal/url:552:9)
at new URL (node:internal/url:628:5)
at dispatchHttpRequest (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/adapters/http.js:176:20)
at new Promise (<anonymous>)
at http (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/adapters/http.js:112:10)
at Axios.dispatchRequest (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/dispatchRequest.js:51:10)
at Axios.request (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/Axios.js:142:33)
at Axios.<computed> [as get] (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/core/Axios.js:168:17)
at Function.wrap [as get] (file:///Users/sasdaniel/Desktop/massage/node_modules/axios/lib/helpers/bind.js:5:15) {
input: '/api/get-all-lists/clbcpc0hi0002sb1wsiea3q5d',
code: 'ERR_INVALID_URL'
}
I also tried to paste the localhost in the browser and it have no problem.
You could extract the functionality into /lib/getAllList.js:
import prisma from './prisma';
export default async function getAllLists(userId) {
return await prisma.List.findMany({ where: { userId: userId }});
}
Then use it in your API route:
import getAllLists from '../../../lib/getAllLists';
export default async function handler(req, res) {
const { userId } = req.query;
if (req.method === 'GET') {
try {
const shoppingLists = await getAllLists(userId);
res.status(200).json({lists: shoppingLists});
}
catch (error) {
console.log(error);
res.status(500).json({ message: 'Something went wrong. Please try again'});
}
}
else {
res.status(500).json({message: 'Invalid method requested!'});
}
}
Then use it in getServerSideProps:
import getAllLists from 'path/to/lib/getAllLists';
export async function getServerSideProps(context) {
const { userId } = context.params;
const shoppingLists = await getAllLists(userId);
return {
props: {
shoppingLists,
},
};
}
This question already has answers here:
Passing variables from middleware to page in Next.js 12 new middleware api
(4 answers)
Closed 5 months ago.
i am using the new stable nextjs 12 middleware and try to pass params from this middleware to the route handler.
My use case: I am using the middleware for authentication with a json web token. This token includes for example the company id and I want to pass this company id to the following route handler to verify the user input.
My code looks like this:
export async function middleware(req: NextRequest) {
if (request.nextUrl.pathname.startsWith("/user")) {
let {valid, token} = (await authVerify(
req.cookies.get("token")
))
if (valid) {
return NextResponse.next();
}
return NextResponse.json({ valid: false, message: "invalid token" });
}
}
export default async function getAllUserHandler(
req: NextApiRequest,
res: NextApiResponse
)
const companyId = //req.company_id or something
return await userQuery(companyId);
}
I'm not shure if it works but i would try this. See the line below // Maybe this also works.
export async function middleware(req: NextRequest) {
if (request.nextUrl.pathname.startsWith("/user")) {
const { valid, token } = await authVerify(req.cookies.get("token"));
if (valid) {
const response = NextResponse.next();
// You can add cookies
response.cookies.set('vercel', 'fast', { path: '/test' });
// Maybe this also works
response.company_id = token.company_id;
return response;
}
return NextResponse.json({ valid: false, message: "invalid token" });
}
}
export default async function getAllUserHandler(
req: NextApiRequest,
res: NextApiResponse
) {
const companyId = req.company_id;
return await userQuery(companyId);
}
As per documentation we can add appcheck as below,
exports.yourCallableFunction = functions.https.onCall((data, context) => {
// context.app will be undefined if the request doesn't include a valid
// App Check token.
if (context.app == undefined) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called from an App Check verified app.')
}
});
My question right now is how do I need to add app-check for below scenario?
exports.date = functions.https.onRequest((req, res) => {
});
In the client, get an appCheck token from Firebase. Send it in a header to your function. Get the token from the req object's headers. Verify the the token with firebase-admin. I'll include the documentation for the client below, then the gist of how I implemented it client side with Apollo-client graphql. Then I'll include the documentation for the backend, then the gist of how I implemented the backend, again with Apollo.
client (from the documentation):
const { initializeAppCheck, getToken } = require('firebase/app-check');
const appCheck = initializeAppCheck(
app,
{ provider: provider } // ReCaptchaV3Provider or CustomProvider
);
const callApiWithAppCheckExample = async () => {
let appCheckTokenResponse;
try {
appCheckTokenResponse = await getToken(appCheck, /* forceRefresh= */ false);
} catch (err) {
// Handle any errors if the token was not retrieved.
return;
}
// Include the App Check token with requests to your server.
const apiResponse = await fetch('https://yourbackend.example.com/yourApiEndpoint', {
headers: {
'X-Firebase-AppCheck': appCheckTokenResponse.token,
}
});
// Handle response from your backend.
};
client (gist from my implementation)
import { setContext } from "#apollo/client/link/context";
import { app } from '../firebase/setup';
import { initializeAppCheck, ReCaptchaV3Provider, getToken } from "firebase/app-check"
let appCheck
let appCheckTokenResponse
const getAppCheckToken = async () => {
const appCheckTokenResponsePromise = await getToken(appCheck, /* forceRefresh= */ false)
appCheckTokenResponse = appCheckTokenResponsePromise
}
const authLink = setContext(async (_, { headers }) => {
if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_ENV === 'production') {
appCheck = initializeAppCheck(app, {
provider: new ReCaptchaV3Provider('my_public_key_from_recaptcha_V3'),
isTokenAutoRefreshEnabled: true
})
await getAppCheckToken()
}
return {
headers: {
...headers,
'X-Firebase-AppCheck': appCheckTokenResponse?.token,
},
}
})
backend / server (from the documentation)
const express = require('express');
const app = express();
const firebaseAdmin = require('firebase-admin');
const firebaseApp = firebaseAdmin.initializeApp();
const appCheckVerification = async (req, res, next) => {
const appCheckToken = req.header('X-Firebase-AppCheck');
if (!appCheckToken) {
res.status(401);
return next('Unauthorized');
}
try {
const appCheckClaims = await firebaseAdmin.appCheck().verifyToken(appCheckToken);
// If verifyToken() succeeds, continue with the next middleware
// function in the stack.
return next();
} catch (err) {
res.status(401);
return next('Unauthorized');
}
}
app.get('/yourApiEndpoint', [appCheckVerification], (req, res) => {
// Handle request.
});
backend / server (gist from my implementation)
import { https } from 'firebase-functions'
import gqlServer from './graphql/server'
const functions = require('firebase-functions')
const env = process.env.ENV || functions.config().config.env
const server = gqlServer()
const api = https.onRequest((req, res) => {
server(req, res)
})
export { api }
. . .
import * as admin from 'firebase-admin';
const functions = require('firebase-functions');
const env = process.env.ENV || functions.config().config.env
admin.initializeApp()
appCheckVerification = async (req: any, res: any) => {
const appCheckToken = req.header('X-Firebase-AppCheck')
if (!appCheckToken) {
return false
}
try {
const appCheckClaims = await admin.appCheck().verifyToken(appCheckToken);
return true
} catch (error) {
console.error(error)
return false
}
}
. . .
const apolloServer = new ApolloServer({
introspection: isDevelopment,
typeDefs: schema,
resolvers,
context: async ({ req, res }) => {
if (!isDevelopment && !isTest) {
const appCheckVerification = await appCheckVerification(req, res)
if (!appCheckVerification) throw Error('Something went wrong with verification')
}
return { req, res, }
}
If you enforce app check in Cloud Functions it will only allow calls from apps that are registered in your project.
I'm not sure if that is sufficient for your use-case though, as I doubt most apps where you can provide a web hook will have implemented app attestation - which is how App Check recognizes valid requests.
You can generate an app check token in the client and verify the token in the server using firebase admin SDK. Here is the firebase documentation for the same
Firebase enable App check enforcement documentation teaches you that to validate the caller from your function you just need to check the context.app then gives you an example like this
exports.EXAMPLE = functions.https.onCall((data, context) => {});
https://firebase.google.com/docs/app-check/cloud-functions?authuser=0
But when you are deploying your function in the google cloud dashboard, you select HTTP FUNCTION -> nodejs 14 -> then you are directed to code like this
/**
* Responds to any HTTP request.
*
* #param {!express:Request} req HTTP request context.
* #param {!express:Response} res HTTP response context.
*/
exports.helloWorld = (req, res) => {
let message = req.query.message || req.body.message || 'Hello World!';
res.status(200).send(message);
};
My question when I saw this was: "How am i going to get a context if I only have request/response"
The answer is simple. YOU MUST SWITCH THE CONSTRUCTORS
You must re-write your function in a way that instead of dealing with req/res like any express function you are dealing with context/data
http functions are different of callable functions (the ones that deals with context/data)
IT IS SIMILAR BUT NOT EXACTLY EQUAL AND SOME MODIFICATIONS WILL BE NECESSARY.
mainly if your function deals with async stuff and have a delayed response you are going to need to rewrite many stuff
check this tutorial
https://firebase.google.com/docs/functions/callable
I am triggering an httpscallable function in GoogleCloud but receiving this error back which I could not find anywhere in documentation what is it:
"firebase.functions(app)" arg expects a FirebaseApp instance or undefined.
Ensure the arg provided is a Firebase app instance; or no args to use
the default Firebase app.
Here is my code in RN app:
import { firebase } from '#react-native-firebase/functions';
...
try {
await firebase.functions('europe-west1').httpsCallable('createUserTest')();
}
catch (httpsError) {
console.log(httpsError.message);
}
And my Cloud Function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createUserTest = functions.region('europe-west1').https.onCall(async (data, context) => {
try {
const callerUid = context.auth.uid;
const callerUserRecord = await admin.auth().getUser(callerUid);
return { result: callerUserRecord.customClaims };
} catch (error) {
return { error: error };
}
});
I am using this function for testing purposes just to see if I can receive back the current user custom claims or not, however, its returning that error.
It looks like you're not using the provided client API correctly. I suggest reviewing the documentation, especially example 3. You'll want to do this instead:
const defaultApp = firebase.app();
const functionsForRegion = defaultApp.functions('europe-west1');
await functionsForRegion.httpsCallable("createUserTest")()