From 6bfd0621ad8fdde282259e25c7bbd9e4727f38c0 Mon Sep 17 00:00:00 2001 From: Loewy Date: Mon, 5 Feb 2024 22:50:24 -0800 Subject: [PATCH] wip: connect firebase token to apollo headers --- App.tsx | 26 +++++++++--- auth/firebase-auth.tsx | 48 +++++++++++++++++++++ auth/index.ts | 15 +++++++ navigation/app-navigator.tsx | 8 ++-- package.json | 4 +- screens/login.tsx | 82 +++++++++++------------------------- 6 files changed, 114 insertions(+), 69 deletions(-) create mode 100644 auth/firebase-auth.tsx create mode 100644 auth/index.ts diff --git a/App.tsx b/App.tsx index 5026945..2a594f0 100644 --- a/App.tsx +++ b/App.tsx @@ -1,16 +1,30 @@ import { DEV_USER_ID } from "@env"; -import React from "react"; +import React, { useEffect } from "react"; import { ClientProvider, useAuthHeader } from "./graphql/client"; import AppNavigator from "./navigation/app-navigator"; +import { getCurrentUserToken } from "./auth"; +// TODO: can be done when we go with a src top level directory -- should live on cofig const SetAuthHeaderBasedOnEnv = () => { const { setAuthHeader } = useAuthHeader(); - React.useEffect(() => { - if (DEV_USER_ID) { - console.log("Setting fake authorization user to: ", DEV_USER_ID); - setAuthHeader({ key: "user_id", value: DEV_USER_ID }); - } + + useEffect(() => { + const setAuthAsync = async () => { + // if (DEV_USER_ID) { + // console.log("Setting fake authorization user to: ", DEV_USER_ID); + // setAuthHeader({ key: "user_id", value: DEV_USER_ID }); + // } else { + // Fetch token for authenticated users in production + const token = await getCurrentUserToken(); + if (token) { + console.log("Setting firebase auth token"); + setAuthHeader({ key: "Authorization", value: `Bearer ${token}` }); + } + // } + }; + + setAuthAsync(); }, [setAuthHeader]); return null; diff --git a/auth/firebase-auth.tsx b/auth/firebase-auth.tsx new file mode 100644 index 0000000..1f52e82 --- /dev/null +++ b/auth/firebase-auth.tsx @@ -0,0 +1,48 @@ +import auth, { FirebaseAuthTypes } from '@react-native-firebase/auth'; +import { Alert } from 'react-native'; + +export const signInWithPhoneNumber = async (phoneNumber: string): Promise => { + if (!phoneNumber) { + Alert.alert("Please enter a valid phone number with a country code"); + return; + } + try { + const confirmation = await auth().signInWithPhoneNumber(phoneNumber); + return confirmation; + } catch (err) { + console.warn(err); + Alert.alert("There was an error. Make sure you are using a country code (ex: +1 for US)"); + } +}; + +export const confirmCode = async (confirm: FirebaseAuthTypes.ConfirmationResult, code: string): Promise => { + try { + await confirm.confirm(code); + } catch { + Alert.alert("Invalid code, please try again."); + } +}; + +export const onAuthStateChanged = (callback: (user: FirebaseAuthTypes.User | null) => void) => { + return auth().onAuthStateChanged(callback); +}; + + +export const getCurrentUserToken = async (): Promise => { + const user = auth().currentUser; + if (user) { + return await user.getIdToken(); + } + return null; +}; + +export const signOut = async (): Promise => { + try { + auth().signOut() + // tie in to AppNav + } catch (err) { + console.error(err) + // toggle appnav state regardless + // Handle sign out error - have to look into best way to do this - asyncstorage? + } +} \ No newline at end of file diff --git a/auth/index.ts b/auth/index.ts new file mode 100644 index 0000000..ba5a593 --- /dev/null +++ b/auth/index.ts @@ -0,0 +1,15 @@ +import { + signInWithPhoneNumber, + confirmCode, + onAuthStateChanged, + getCurrentUserToken, + signOut +} from './firebase-auth' + +export { + signInWithPhoneNumber, + confirmCode, + onAuthStateChanged, + getCurrentUserToken, + signOut +} \ No newline at end of file diff --git a/navigation/app-navigator.tsx b/navigation/app-navigator.tsx index 32ea4ce..8469631 100644 --- a/navigation/app-navigator.tsx +++ b/navigation/app-navigator.tsx @@ -14,13 +14,13 @@ const Stack = createNativeStackNavigator(); const ScreensStack = () => ( diff --git a/package.json b/package.json index f245095..a8660a0 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "start:android": "expo start --android", "start:ios": "expo start --ios", "android": "expo run:android", + "android:prod": "cp .env.production .env && cat .env && NODE_ENV=production expo run:android", "ios": "expo run:ios", + "ios:prod": "cp .env.production .env && expo run:ios", "web": "expo start --web", "lint": "eslint . --ext .js,.ts,.tsx", "lint:fix": "eslint . --ext .ts,.tsx --fix", @@ -83,4 +85,4 @@ "@babel/core": "^7.20.2", "babel-loader": "^8.3.0" } -} +} \ No newline at end of file diff --git a/screens/login.tsx b/screens/login.tsx index 75e3e0a..d16de55 100644 --- a/screens/login.tsx +++ b/screens/login.tsx @@ -1,72 +1,34 @@ -import auth, { FirebaseAuthTypes } from "@react-native-firebase/auth"; -import React, { useEffect, useState } from "react"; +// Login.tsx +import React, { useEffect, useState } from 'react'; import { - Alert, Button, Keyboard, Text, TextInput, TouchableWithoutFeedback, View, -} from "react-native"; - -// This code is beginning of Auth Implementation - actual implementation will differ and involve more UI -// Does not have a restart or proper handling of code confirmation, should only be used for obtaining token/testing -// Currently working for Android builds, iOS has open issue #56 +} from 'react-native'; +import { signInWithPhoneNumber, confirmCode, onAuthStateChanged, signOut } from '../auth'; // Adjust the path as necessary +import { FirebaseAuthTypes } from '@react-native-firebase/auth'; +import { useAuthHeader } from '../graphql/client'; export default function Login() { - const [phoneNumber, setPhoneNumber] = useState(""); - const [code, setCode] = useState(""); - - const [user, setUser] = useState(null); - const [confirm, setConfirm] = - useState(null); - - async function onAuthStateChanged(user: any) { - setUser(user); - if (user) { - // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars - const token = await auth().currentUser?.getIdToken(); - // To debug/check token & user return, use these logs - // console.log(token) // token log - // console.log(user) // user log - } - } - - async function signInWithPhoneNumber(phoneNumber: string) { - if (!phoneNumber) { - return Alert.alert( - "Please enter a valid phone number with a country code", - ); - } - try { - const confirmation = await auth().signInWithPhoneNumber(phoneNumber); - setConfirm(confirmation); - } catch (err) { - // TODO: implement more robust error handling by parsing err message - console.warn(err); - Alert.alert( - "There was an error. Make sure you are using a country code (ex: +1 for US)", - ); - } - } - - async function confirmCode() { - try { - await confirm?.confirm(code); - } catch { - Alert.alert("Invalid code, please try again."); - } - } + const [phoneNumber, setPhoneNumber] = useState(''); + const [code, setCode] = useState(''); + const [user, setUser] = useState(null); + const [confirm, setConfirm] = useState(null); + const authHeader = useAuthHeader() useEffect(() => { - const subscriber = auth().onAuthStateChanged(onAuthStateChanged); - return subscriber; + const subscriber = onAuthStateChanged(setUser); + return subscriber; // This may need adjustment based on your specific implementation }, []); + + return ( Keyboard.dismiss()}> - + )}