chore: build
Backend - Build & Lint / build (push) Failing after 29m5s
Frontend Client - EAS Build / build (push) Failing after 2h25m33s

This commit is contained in:
Nuxgrid
2026-07-14 18:34:58 +02:00
parent 8c3fa39d86
commit 734493363f
17 changed files with 2014 additions and 7 deletions
+6
View File
@@ -12,6 +12,7 @@ import { ThemeProvider, useTheme } from "./src/context/ThemeContext";
import HomeScreen from "./src/screens/HomeScreen";
import ClientLoginScreen from "./src/screens/auth/ClientLoginScreen";
import ClientTwoFAScreen from "./src/screens/auth/ClientTwoFAScreen";
import ChangePasswordScreen from "./src/screens/auth/ChangePasswordScreen";
import ClientNavigator from "./src/navigation/ClientNavigator";
import type { RootStackParamList, ChangePasswordStackParamList } from "./src/navigation/types";
@@ -39,6 +40,11 @@ function AuthStack() {
component={ClientLoginScreen}
options={{ title: "Connexion" }}
/>
<Stack.Screen
name="clientTwoFA"
component={ClientTwoFAScreen}
options={{ title: "Vérification" }}
/>
</Stack.Navigator>
);
}
+38
View File
@@ -27,6 +27,8 @@ export interface AuthResponse {
access_token?: string;
token_type?: string;
expires_in?: number;
requires_2fa?: boolean;
session_token?: string;
user?: {
id: number;
username: string;
@@ -48,6 +50,13 @@ export const loginUser = async (
username,
password,
});
if (data.requires_2fa) {
return {
success: false,
requires_2fa: true,
session_token: data.session_token,
};
}
if (!data.access_token) {
return { success: false, message: "Token non reçu du serveur" };
}
@@ -68,6 +77,35 @@ export const loginUser = async (
}
};
export const verifyClient2FA = async (
sessionToken: string,
code: string,
): Promise<AuthResponse> => {
try {
const { data } = await apiClient.post(`${V1}/auth/2fa/verify`, {
session_token: sessionToken,
code,
});
if (!data.access_token) {
return { success: false, message: "Token non reçu du serveur" };
}
return {
success: true,
message: "Connexion réussie",
access_token: data.access_token,
token_type: data.token_type,
expires_in: data.expires_in,
user: data.user,
};
} catch (error: any) {
const msg =
error.response?.data?.error ||
error.message ||
"Code invalide";
return { success: false, message: msg };
}
};
export const registerUser = async (
username: string,
password: string,
+1
View File
@@ -3,6 +3,7 @@ export type RootStackParamList = {
login: undefined;
register: undefined;
role: undefined;
clientTwoFA: { sessionToken: string };
};
export type ChangePasswordStackParamList = {
@@ -61,7 +61,11 @@ const LoginClient = () => {
formData.username,
formData.password,
);
if (result.success && result.access_token) {
if (result.requires_2fa && result.session_token) {
navigation.navigate("clientTwoFA", {
sessionToken: result.session_token,
});
} else if (result.success && result.access_token) {
await loginClient(result.access_token, result.user?.must_change_password ?? false);
} else {
const errorMessage =
@@ -0,0 +1,200 @@
import React, { useState, useMemo } from "react";
import {
View,
Text,
TextInput,
TouchableOpacity,
ActivityIndicator,
StyleSheet,
KeyboardAvoidingView,
ScrollView,
} from "react-native";
import { Feather } from "@expo/vector-icons";
import { verifyClient2FA } from "../../api/api";
import { useAuth } from "../../auth/AuthContext";
import { useNavigation, useRoute, type RouteProp } from "@react-navigation/native";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import type { RootStackParamList } from "../../navigation/types";
import { useTheme } from "../../context/ThemeContext";
type TwoFANavigationProp = NativeStackNavigationProp<
RootStackParamList,
"clientTwoFA"
>;
type TwoFARouteProp = RouteProp<RootStackParamList, "clientTwoFA">;
const ClientTwoFAScreen = () => {
const navigation = useNavigation<TwoFANavigationProp>();
const route = useRoute<TwoFARouteProp>();
const { sessionToken } = route.params;
const { loginClient } = useAuth();
const { colors } = useTheme();
const [code, setCode] = useState("");
const [apiError, setApiError] = useState("");
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async () => {
if (code.trim().length !== 6) {
setApiError("Le code doit contenir 6 chiffres");
return;
}
setIsLoading(true);
setApiError("");
try {
const result = await verifyClient2FA(sessionToken, code.trim());
if (result.success && result.access_token) {
await loginClient(result.access_token, result.user?.must_change_password ?? false);
} else {
setApiError(result.message || "Code invalide");
}
} catch (err) {
const message =
err instanceof Error ? err.message : "Erreur de connexion";
setApiError(message);
} finally {
setIsLoading(false);
}
};
const handleChangeCode = (value: string) => {
const digitsOnly = value.replace(/[^0-9]/g, "").slice(0, 6);
setCode(digitsOnly);
if (apiError) setApiError("");
};
const styles = useMemo(
() =>
StyleSheet.create({
container: { flex: 1, backgroundColor: colors.bgSecondary },
scrollContent: {
flexGrow: 1,
justifyContent: "center",
alignItems: "center",
padding: 16,
},
card: {
width: "100%",
maxWidth: 400,
borderRadius: 16,
padding: 24,
backgroundColor: colors.bgPrimary,
},
header: { alignItems: "center", marginBottom: 24 },
title: {
fontSize: 24,
fontWeight: "bold",
marginTop: 8,
color: colors.textPrimary,
},
subtitle: {
fontSize: 14,
marginTop: 4,
textAlign: "center",
color: colors.textMuted,
},
apiError: {
backgroundColor: "#fee2e2",
color: "#991b1b",
padding: 12,
borderRadius: 6,
marginBottom: 16,
textAlign: "center",
},
inputGroup: { marginBottom: 16 },
label: { marginBottom: 4, fontWeight: "500", color: colors.textSecondary },
inputWrapper: {
flexDirection: "row",
alignItems: "center",
borderWidth: 1,
borderRadius: 8,
paddingHorizontal: 12,
borderColor: colors.border,
backgroundColor: colors.bgInput,
},
icon: { marginRight: 8 },
input: {
flex: 1,
height: 48,
color: colors.textPrimary,
fontSize: 20,
letterSpacing: 8,
textAlign: "center",
},
submitButton: {
backgroundColor: colors.accent,
borderRadius: 8,
padding: 12,
alignItems: "center",
marginTop: 8,
},
submitText: { color: colors.white, fontWeight: "600", fontSize: 16 },
backButton: { alignItems: "center", marginTop: 16 },
backText: { color: colors.textMuted, fontSize: 14 },
}),
[colors],
);
return (
<KeyboardAvoidingView style={styles.container} behavior="padding">
<ScrollView
contentContainerStyle={styles.scrollContent}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
<View style={styles.card}>
<View style={styles.header}>
<Feather name="shield" size={48} color={colors.accent} />
<Text style={styles.title}>Vérification en deux étapes</Text>
<Text style={styles.subtitle}>
Entrez le code à 6 chiffres envoyé sur Telegram
</Text>
</View>
{apiError ? (
<Text style={styles.apiError}> {apiError}</Text>
) : null}
<View style={styles.inputGroup}>
<Text style={styles.label}>Code de vérification</Text>
<View style={styles.inputWrapper}>
<TextInput
style={styles.input}
placeholder="000000"
placeholderTextColor={colors.textMuted}
value={code}
onChangeText={handleChangeCode}
editable={!isLoading}
keyboardType="number-pad"
maxLength={6}
autoFocus
/>
</View>
</View>
<TouchableOpacity
style={[styles.submitButton, isLoading && { opacity: 0.6 }]}
onPress={handleSubmit}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator color={colors.white} />
) : (
<Text style={styles.submitText}>Vérifier</Text>
)}
</TouchableOpacity>
<TouchableOpacity
style={styles.backButton}
onPress={() => navigation.goBack()}
disabled={isLoading}
>
<Text style={styles.backText}>Retour à la connexion</Text>
</TouchableOpacity>
</View>
</ScrollView>
</KeyboardAvoidingView>
);
};
export default ClientTwoFAScreen;