feat(mobile): identité user-first — login/password, SecureStore, scoping par compte

- api: DeviceRegistration device-only, login/changePassword/resolveUser, interception 401 (purge) exonérée sur le login ; type User aligné sur le fil (is_admin)
- SecureStore (expo-secure-store v57) : token + profil, jamais en SQLite ; miroir active_user_id
- AuthContext : statut loading/signedOut/signedIn, bootstrap register→restore, signIn/signOut, garde-fou switch de compte
- app/login.tsx + gate de routes dans _layout (Redirect signedOut) ; i18n fr/en
- DB v5 : user_id sur pending_operations + resource_permissions (UNIQUE par user), repos scopés (user_id IS ? OR IS NULL)
- syncOutbox : delta permissions par compte (Map), outbox poussée du compte actif uniquement
- tests: register/login/401/resolve/changePassword, scoping outbox+permissions, migrations v5 ; e2e live revert register→login admin
This commit is contained in:
m
2026-09-10 21:34:34 +02:00
parent c2c7dddba6
commit 0536fca6c3
25 changed files with 663 additions and 49 deletions
+27 -4
View File
@@ -1,7 +1,8 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Stack } from 'expo-router';
import { AuthProvider } from '../context/AuthContext';
import '../i18n';
import { Redirect, Stack } from 'expo-router';
import { ActivityIndicator, View } from 'react-native';
import { AuthProvider, useAuth } from '../context/AuthContext';
import i18n from '../i18n';
const queryClient = new QueryClient({
defaultOptions: {
@@ -12,11 +13,33 @@ const queryClient = new QueryClient({
},
});
function AuthGate() {
const { status } = useAuth();
if (status === 'loading') {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator size="large" />
</View>
);
}
return (
<>
<Stack>
<Stack.Screen name="login" options={{ title: i18n.t('login_title') }} />
</Stack>
{/* Toute route est protégée tant qu'aucun compte n'est connecté. */}
{status === 'signedOut' ? <Redirect href="/login" /> : null}
</>
);
}
export default function RootLayout() {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<Stack />
<AuthGate />
</AuthProvider>
</QueryClientProvider>
);