Compare commits
	
		
			2 Commits
		
	
	
		
			72862da3a6
			...
			b0c17db233
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b0c17db233 | |||
| 363dfa5c74 | 
| @@ -1,16 +1,16 @@ | ||||
| import * as SecureStore from '@/utils/SecureStore' | ||||
| import { useAuth } from '@/hooks/useAuth' | ||||
| import { useRouter } from 'expo-router' | ||||
| import { FAB, List, Surface } from 'react-native-paper' | ||||
|  | ||||
| export default function HistoryScreen() { | ||||
|   const router = useRouter() | ||||
|   const isLoggedIn = SecureStore.getItem("apiToken") !== null | ||||
|   const auth = useAuth() | ||||
|   return ( | ||||
|     <Surface | ||||
|       style={{ flex: 1 }}> | ||||
|       <List.Item | ||||
|           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')} />} | ||||
|           onPress={() => router.navigate('/login')} /> | ||||
|     </Surface> | ||||
|   | ||||
| @@ -1,42 +1,33 @@ | ||||
| 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 { StatusBar } from 'expo-status-bar' | ||||
| import { Provider as StoreProvider } from 'react-redux' | ||||
| import { MD3DarkTheme, MD3LightTheme, PaperProvider } from 'react-native-paper' | ||||
| import store from '@/utils/store' | ||||
| import * as SecureStore from '@/utils/SecureStore' | ||||
| import { useStartBackgroundFetchServiceEffect } from '@/utils/background' | ||||
| import { useStartGeolocationServiceEffect } from '@/utils/geolocation' | ||||
| import { useEffect } from 'react' | ||||
| import { useRouteInfo } from 'expo-router/build/hooks' | ||||
| import LoginProvider from '@/components/LoginProvider' | ||||
|  | ||||
| export default function RootLayout() { | ||||
|   useStartGeolocationServiceEffect() | ||||
|   useStartBackgroundFetchServiceEffect() | ||||
|   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 ( | ||||
|     <StoreProvider store={store}> | ||||
|       <PaperProvider theme={colorScheme === 'dark' ? MD3DarkTheme : MD3LightTheme}> | ||||
|         <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}> | ||||
|           <Stack> | ||||
|             <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> | ||||
|             <Stack.Screen name="login" options={{ headerShown: false }} /> | ||||
|             <Stack.Screen name="+not-found" /> | ||||
|           </Stack> | ||||
|           <StatusBar style="auto" /> | ||||
|         </ThemeProvider> | ||||
|       </PaperProvider> | ||||
|       <LoginProvider loginRedirect={'/login'}> | ||||
|         <PaperProvider theme={colorScheme === 'dark' ? MD3DarkTheme : MD3LightTheme}> | ||||
|           <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}> | ||||
|             <Stack> | ||||
|               <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> | ||||
|               <Stack.Screen name="login" options={{ headerShown: false }} /> | ||||
|               <Stack.Screen name="+not-found" /> | ||||
|             </Stack> | ||||
|             <StatusBar style="auto" /> | ||||
|           </ThemeProvider> | ||||
|         </PaperProvider> | ||||
|       </LoginProvider> | ||||
|     </StoreProvider> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import { useAuth, useAuthLogin, useAuthLogout } from "@/hooks/useAuth" | ||||
| import * as SecureStore from "@/utils/SecureStore" | ||||
| import { useRouter } from "expo-router" | ||||
| import { useRef, useState } from "react" | ||||
| @@ -6,9 +7,14 @@ import { Appbar, Button, Dialog, Portal, Surface, Text, TextInput } from "react- | ||||
|  | ||||
| export default function Login() { | ||||
|   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 [name, setName] = useState(SecureStore.getItem('apiName') ?? "") | ||||
|   const [name, setName] = useState(auth.name ?? "") | ||||
|   const [password, setPassword] = useState("") | ||||
|   const [errorDialogVisible, setErrorDialogVisible] = useState(false) | ||||
|   const [errorTitle, setErrorTitle] = useState("") | ||||
| @@ -45,6 +51,7 @@ export default function Login() { | ||||
|       return | ||||
|     } | ||||
|     setLoggingIn(false) | ||||
|     authLogin({ name: name, token: resp.accessToken }) | ||||
|     SecureStore.setItem("apiName", name) | ||||
|     if (Platform.OS !== "web") { | ||||
|       // 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() { | ||||
|     authLogout() | ||||
|     await SecureStore.deleteItemAsync("apiName") | ||||
|     await SecureStore.deleteItemAsync("apiPassword") | ||||
|     await SecureStore.deleteItemAsync("apiToken") | ||||
|     setIsLoggedIn(false) | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|   | ||||
							
								
								
									
										64
									
								
								client/components/LoginProvider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								client/components/LoginProvider.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| import { Href, useRouter } from 'expo-router' | ||||
| import { useRouteInfo } from 'expo-router/build/hooks' | ||||
| import { ReactNode, useEffect } from 'react' | ||||
| import { useAuth, useAuthLogin } from '@/hooks/useAuth' | ||||
| import * as SecureStore from '@/utils/SecureStore' | ||||
|  | ||||
| type Props = { | ||||
|   loginRedirect: Href | ||||
|   children: ReactNode | ||||
| } | ||||
|  | ||||
| export default function LoginProvider({ loginRedirect, children }: Props) { | ||||
|   const router = useRouter() | ||||
|   const route = useRouteInfo() | ||||
|   const auth = useAuth() | ||||
|   const authLogin = useAuthLogin() | ||||
|  | ||||
|   // Renouvellement auto du jeton d'authentification | ||||
|   useEffect(() => { | ||||
|     const { name, token } = auth | ||||
|     const password = SecureStore.getItem('apiPassword') | ||||
|     if (name === null || (password === null && token === null)) | ||||
|       return | ||||
|     let waitTime = 0 | ||||
|     if (token !== null && token !== undefined) { | ||||
|       const arrayToken = token.split('.') | ||||
|       const tokenPayload = JSON.parse(atob(arrayToken[1])) | ||||
|       const expTime: number = tokenPayload.exp * 1000 | ||||
|       const now: number = Math.floor(new Date().getTime()) | ||||
|       waitTime = expTime - now | ||||
|     } | ||||
|     const timeout = setTimeout(async () => { | ||||
|       const password = SecureStore.getItem('apiPassword') | ||||
|       if (password) { | ||||
|         await fetch(`${process.env.EXPO_PUBLIC_TRAINTRAPE_MOI_SERVER}/auth/login/`, { | ||||
|           method: "POST", | ||||
|           headers: { "Content-Type": "application/json" }, | ||||
|           body: JSON.stringify({ name: name, password: password }) | ||||
|         }) | ||||
|         .then(resp => resp.json()) | ||||
|         .then(resp => { | ||||
|           if (!resp.error) | ||||
|             authLogin({ name: name, token: resp.accessToken }) | ||||
|           else | ||||
|             authLogin({ name: name, token: null }) | ||||
|         }) | ||||
|       } | ||||
|       else { | ||||
|         authLogin({ name: name, token: null }) | ||||
|       } | ||||
|     }, waitTime) | ||||
|     return () => clearTimeout(timeout) | ||||
|   }, [auth]) | ||||
|  | ||||
|   // Si on est pas connecté⋅e, on reste sur la fenêtre de connexion | ||||
|   useEffect(() => { | ||||
|     if (!auth.loggedIn && route.pathname !== loginRedirect) | ||||
|       router.navigate(loginRedirect) | ||||
|   }, [auth, route, router]) | ||||
|  | ||||
|   return <> | ||||
|     {children} | ||||
|   </> | ||||
| } | ||||
							
								
								
									
										12
									
								
								client/hooks/useAuth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								client/hooks/useAuth.ts
									
									
									
									
									
										Normal 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()) | ||||
| } | ||||
| @@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from "./useStore" | ||||
| import { setLocation } from "@/utils/features/location/locationSlice" | ||||
|  | ||||
| export const useLocation = () => useAppSelector((state) => state.location.location) | ||||
| export const useSetLocation = () => (location: LocationObject) => { | ||||
| export const useSetLocation = () => { | ||||
|     const dispatch = useAppDispatch() | ||||
|     dispatch(setLocation(location)) | ||||
|     return (location: LocationObject) => dispatch(setLocation(location)) | ||||
| } | ||||
|   | ||||
							
								
								
									
										41
									
								
								client/utils/features/location/authSlice.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								client/utils/features/location/authSlice.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| 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, | ||||
|   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 = action.payload.token !== null | ||||
|       state.name = action.payload.name | ||||
|       state.token = action.payload.token | ||||
|       console.log(state) | ||||
|     }, | ||||
|     logout: (state) => { | ||||
|       state.loggedIn = false | ||||
|       state.name = null | ||||
|       state.token = null | ||||
|     } | ||||
|   }, | ||||
| }) | ||||
|  | ||||
| export const { login, logout } = authSlice.actions | ||||
|  | ||||
| export default authSlice.reducer | ||||
| @@ -1,8 +1,10 @@ | ||||
| import { configureStore } from '@reduxjs/toolkit' | ||||
| import authReducer from './features/location/authSlice' | ||||
| import locationReducer from './features/location/locationSlice' | ||||
|  | ||||
| const store = configureStore({ | ||||
|   reducer: { | ||||
|     auth: authReducer, | ||||
|     location: locationReducer, | ||||
|   }, | ||||
| }) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user