Stockage du jeton d'authentification dans le store local, permettant l'utilisation de hooks

This commit is contained in:
Emmy D'Anello 2024-12-10 18:56:50 +01:00
parent 72862da3a6
commit 363dfa5c74
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
8 changed files with 108 additions and 31 deletions

View File

@ -1,16 +1,16 @@
import * as SecureStore from '@/utils/SecureStore' import { useAuth } from '@/hooks/useAuth'
import { useRouter } from 'expo-router' import { useRouter } from 'expo-router'
import { FAB, List, Surface } from 'react-native-paper' import { FAB, List, Surface } from 'react-native-paper'
export default function HistoryScreen() { export default function HistoryScreen() {
const router = useRouter() const router = useRouter()
const isLoggedIn = SecureStore.getItem("apiToken") !== null const auth = useAuth()
return ( return (
<Surface <Surface
style={{ flex: 1 }}> style={{ flex: 1 }}>
<List.Item <List.Item
title="Connexion au serveur" title="Connexion au serveur"
description={isLoggedIn ? "Vous êtes déjà connecté⋅e" : "Vous n'êtes pas connecté⋅e"} description={auth.loggedIn ? "Vous êtes déjà connecté⋅e" : "Vous n'êtes pas connecté⋅e"}
right={() => <FAB icon="login" size="small" onPress={() => router.navigate('/login')} />} right={() => <FAB icon="login" size="small" onPress={() => router.navigate('/login')} />}
onPress={() => router.navigate('/login')} /> onPress={() => router.navigate('/login')} />
</Surface> </Surface>

View File

@ -1,42 +1,33 @@
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native' import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'
import { Stack, useRouter } from "expo-router" import { Stack } from "expo-router"
import { useColorScheme } from '@/hooks/useColorScheme' import { useColorScheme } from '@/hooks/useColorScheme'
import { StatusBar } from 'expo-status-bar' import { StatusBar } from 'expo-status-bar'
import { Provider as StoreProvider } from 'react-redux' import { Provider as StoreProvider } from 'react-redux'
import { MD3DarkTheme, MD3LightTheme, PaperProvider } from 'react-native-paper' import { MD3DarkTheme, MD3LightTheme, PaperProvider } from 'react-native-paper'
import store from '@/utils/store' import store from '@/utils/store'
import * as SecureStore from '@/utils/SecureStore'
import { useStartBackgroundFetchServiceEffect } from '@/utils/background' import { useStartBackgroundFetchServiceEffect } from '@/utils/background'
import { useStartGeolocationServiceEffect } from '@/utils/geolocation' import { useStartGeolocationServiceEffect } from '@/utils/geolocation'
import { useEffect } from 'react' import LoginProvider from '@/components/LoginProvider'
import { useRouteInfo } from 'expo-router/build/hooks'
export default function RootLayout() { export default function RootLayout() {
useStartGeolocationServiceEffect() useStartGeolocationServiceEffect()
useStartBackgroundFetchServiceEffect() useStartBackgroundFetchServiceEffect()
const colorScheme = useColorScheme() const colorScheme = useColorScheme()
const router = useRouter()
const route = useRouteInfo()
// Si on est pas connecté⋅e, on reste sur la fenêtre de connexion
useEffect(() => {
const isLoggedIn = SecureStore.getItem("apiToken") !== null
if (!isLoggedIn && route.pathname !== "/login")
router.navigate("/login")
}, [route, router])
return ( return (
<StoreProvider store={store}> <StoreProvider store={store}>
<PaperProvider theme={colorScheme === 'dark' ? MD3DarkTheme : MD3LightTheme}> <LoginProvider loginRedirect={'/login'}>
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}> <PaperProvider theme={colorScheme === 'dark' ? MD3DarkTheme : MD3LightTheme}>
<Stack> <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} /> <Stack>
<Stack.Screen name="login" options={{ headerShown: false }} /> <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" /> <Stack.Screen name="login" options={{ headerShown: false }} />
</Stack> <Stack.Screen name="+not-found" />
<StatusBar style="auto" /> </Stack>
</ThemeProvider> <StatusBar style="auto" />
</PaperProvider> </ThemeProvider>
</PaperProvider>
</LoginProvider>
</StoreProvider> </StoreProvider>
) )
} }

View File

@ -1,3 +1,4 @@
import { useAuth, useAuthLogin, useAuthLogout } from "@/hooks/useAuth"
import * as SecureStore from "@/utils/SecureStore" import * as SecureStore from "@/utils/SecureStore"
import { useRouter } from "expo-router" import { useRouter } from "expo-router"
import { useRef, useState } from "react" import { useRef, useState } from "react"
@ -6,9 +7,14 @@ import { Appbar, Button, Dialog, Portal, Surface, Text, TextInput } from "react-
export default function Login() { export default function Login() {
const router = useRouter() const router = useRouter()
const [isLoggedIn, setIsLoggedIn] = useState(SecureStore.getItem("apiToken") !== null)
const auth = useAuth()
const authLogin = useAuthLogin()
const authLogout = useAuthLogout()
const isLoggedIn = auth.loggedIn
const [loggingIn, setLoggingIn] = useState(false) const [loggingIn, setLoggingIn] = useState(false)
const [name, setName] = useState(SecureStore.getItem('apiName') ?? "") const [name, setName] = useState(auth.name ?? "")
const [password, setPassword] = useState("") const [password, setPassword] = useState("")
const [errorDialogVisible, setErrorDialogVisible] = useState(false) const [errorDialogVisible, setErrorDialogVisible] = useState(false)
const [errorTitle, setErrorTitle] = useState("") const [errorTitle, setErrorTitle] = useState("")
@ -45,6 +51,7 @@ export default function Login() {
return return
} }
setLoggingIn(false) setLoggingIn(false)
authLogin({ name: name, token: resp.accessToken })
SecureStore.setItem("apiName", name) SecureStore.setItem("apiName", name)
if (Platform.OS !== "web") { if (Platform.OS !== "web") {
// Le stockage navigateur n'est pas sûr, on évite de stocker un mot de passe à l'intérieur // Le stockage navigateur n'est pas sûr, on évite de stocker un mot de passe à l'intérieur
@ -58,10 +65,10 @@ export default function Login() {
} }
async function logout() { async function logout() {
authLogout()
await SecureStore.deleteItemAsync("apiName") await SecureStore.deleteItemAsync("apiName")
await SecureStore.deleteItemAsync("apiPassword") await SecureStore.deleteItemAsync("apiPassword")
await SecureStore.deleteItemAsync("apiToken") await SecureStore.deleteItemAsync("apiToken")
setIsLoggedIn(false)
} }
return ( return (

View File

@ -0,0 +1,25 @@
import { useAuth } from "@/hooks/useAuth"
import { Href, useRouter } from "expo-router"
import { useRouteInfo } from "expo-router/build/hooks"
import { ReactNode, useEffect } from "react"
type Props = {
loginRedirect: Href
children: ReactNode
}
export default function LoginProvider({ loginRedirect, children }: Props) {
const router = useRouter()
const route = useRouteInfo()
// Si on est pas connecté⋅e, on reste sur la fenêtre de connexion
const auth = useAuth()
useEffect(() => {
if (!auth.loggedIn && route.pathname !== loginRedirect)
router.navigate(loginRedirect)
}, [auth, route, router])
return <>
{children}
</>
}

12
client/hooks/useAuth.ts Normal file
View File

@ -0,0 +1,12 @@
import { useAppDispatch, useAppSelector } from "./useStore"
import { AuthPayload, login, logout } from "@/utils/features/location/authSlice"
export const useAuth = () => useAppSelector((state) => state.auth)
export const useAuthLogin = () => {
const dispath = useAppDispatch()
return (payload: AuthPayload) => dispath(login(payload))
}
export const useAuthLogout = () => {
const dispatch = useAppDispatch()
return () => dispatch(logout())
}

View File

@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from "./useStore"
import { setLocation } from "@/utils/features/location/locationSlice" import { setLocation } from "@/utils/features/location/locationSlice"
export const useLocation = () => useAppSelector((state) => state.location.location) export const useLocation = () => useAppSelector((state) => state.location.location)
export const useSetLocation = () => (location: LocationObject) => { export const useSetLocation = () => {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
dispatch(setLocation(location)) return (location: LocationObject) => dispatch(setLocation(location))
} }

View File

@ -0,0 +1,40 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import * as SecureStore from '@/utils/SecureStore'
interface AuthState {
loggedIn: boolean,
name: string | null,
token: string | null,
}
export interface AuthPayload {
name: string | null,
token: string | null,
}
const initialState: AuthState = {
loggedIn: SecureStore.getItem('apiName') !== null && SecureStore.getItem('apiToken') !== null,
name: SecureStore.getItem('apiName'),
token: SecureStore.getItem('apiToken'),
}
export const authSlice = createSlice({
name: 'auth',
initialState: initialState,
reducers: {
login: (state, action: PayloadAction<AuthPayload>) => {
state.loggedIn = true
state.name = action.payload.name
state.token = action.payload.token
},
logout: (state) => {
state.loggedIn = false
state.name = null
state.token = null
}
},
})
export const { login, logout } = authSlice.actions
export default authSlice.reducer

View File

@ -1,8 +1,10 @@
import { configureStore } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit'
import authReducer from './features/location/authSlice'
import locationReducer from './features/location/locationSlice' import locationReducer from './features/location/locationSlice'
const store = configureStore({ const store = configureStore({
reducer: { reducer: {
auth: authReducer,
location: locationReducer, location: locationReducer,
}, },
}) })