- Adjusted inconsistent formatting for better readability across components and services. - Enhanced type safety by adding placeholders for ignored error parameters and improving types across services. - Improved component organization by reordering imports consistently and applying formatting updates in UI components.
253 lines
7.2 KiB
TypeScript
253 lines
7.2 KiB
TypeScript
"use client";
|
|
|
|
import { Loader2, Shield, ShieldAlert, ShieldCheck } from "lucide-react";
|
|
import { useState } from "react";
|
|
import { toast } from "sonner";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardFooter,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import {
|
|
InputOTP,
|
|
InputOTPGroup,
|
|
InputOTPSeparator,
|
|
InputOTPSlot,
|
|
} from "@/components/ui/input-otp";
|
|
import { useAuth } from "@/providers/auth-provider";
|
|
import { AuthService } from "@/services/auth.service";
|
|
|
|
export function TwoFactorSetup() {
|
|
const { user, refreshUser } = useAuth();
|
|
const [step, setStep] = useState<"idle" | "setup" | "verify">("idle");
|
|
const [qrCode, setQrCode] = useState<string | null>(null);
|
|
const [secret, setSecret] = useState<string | null>(null);
|
|
const [otpValue, setOtpValue] = useState("");
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const handleSetup = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const data = await AuthService.setup2fa();
|
|
setQrCode(data.qrCodeUrl);
|
|
setSecret(data.secret);
|
|
setStep("setup");
|
|
} catch (_error) {
|
|
toast.error("Erreur lors de la configuration de la 2FA.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleEnable = async () => {
|
|
if (otpValue.length !== 6) return;
|
|
setIsLoading(true);
|
|
try {
|
|
await AuthService.enable2fa(otpValue);
|
|
toast.success("Double authentification activée !");
|
|
await refreshUser();
|
|
setStep("idle");
|
|
setOtpValue("");
|
|
} catch (_error) {
|
|
toast.error("Code invalide. Veuillez réessayer.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleDisable = async () => {
|
|
if (otpValue.length !== 6) return;
|
|
setIsLoading(true);
|
|
try {
|
|
await AuthService.disable2fa(otpValue);
|
|
toast.success("Double authentification désactivée.");
|
|
await refreshUser();
|
|
setStep("idle");
|
|
setOtpValue("");
|
|
} catch (_error) {
|
|
toast.error("Code invalide. Veuillez réessayer.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
// Note: We need a way to know if 2FA is enabled.
|
|
// Assuming user object might have twoFactorEnabled property or similar.
|
|
// For now, let's assume it's on the user object (we might need to add it to the type).
|
|
const isEnabled = (user as any)?.twoFactorEnabled;
|
|
|
|
if (step === "idle") {
|
|
return (
|
|
<Card className="border-none shadow-sm">
|
|
<CardHeader className="pb-4">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<Shield className="h-5 w-5 text-primary" />
|
|
<CardTitle>Double Authentification (2FA)</CardTitle>
|
|
</div>
|
|
<CardDescription>
|
|
Ajoutez une couche de sécurité supplémentaire à votre compte en utilisant
|
|
une application d'authentification.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-center gap-4 p-4 rounded-lg bg-zinc-50 dark:bg-zinc-900 border">
|
|
{isEnabled ? (
|
|
<>
|
|
<div className="bg-green-100 dark:bg-green-900/30 p-2 rounded-full">
|
|
<ShieldCheck className="h-6 w-6 text-green-600 dark:text-green-400" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<p className="font-bold">La 2FA est activée</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
Votre compte est protégé par un code temporaire.
|
|
</p>
|
|
</div>
|
|
<Button variant="outline" size="sm" onClick={() => setStep("verify")}>
|
|
Désactiver
|
|
</Button>
|
|
</>
|
|
) : (
|
|
<>
|
|
<div className="bg-zinc-200 dark:bg-zinc-800 p-2 rounded-full">
|
|
<ShieldAlert className="h-6 w-6 text-zinc-500" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<p className="font-bold">La 2FA n'est pas activée</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
Activez la 2FA pour mieux protéger votre compte.
|
|
</p>
|
|
</div>
|
|
<Button
|
|
variant="default"
|
|
size="sm"
|
|
onClick={handleSetup}
|
|
disabled={isLoading}
|
|
>
|
|
{isLoading ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
"Configurer"
|
|
)}
|
|
</Button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (step === "setup") {
|
|
return (
|
|
<Card className="border-none shadow-sm">
|
|
<CardHeader>
|
|
<CardTitle>Configurer la 2FA</CardTitle>
|
|
<CardDescription>
|
|
Scannez le QR Code ci-dessous avec votre application d'authentification
|
|
(Google Authenticator, Authy, etc.).
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col items-center gap-6">
|
|
{qrCode && (
|
|
<div className="bg-white p-4 rounded-xl border-4 border-zinc-100">
|
|
<img src={qrCode} alt="QR Code 2FA" className="w-48 h-48" />
|
|
</div>
|
|
)}
|
|
<div className="w-full space-y-2">
|
|
<p className="text-sm font-medium text-center">
|
|
Ou entrez ce code manuellement :
|
|
</p>
|
|
<code className="block p-2 bg-muted text-center rounded text-xs font-mono break-all">
|
|
{secret}
|
|
</code>
|
|
</div>
|
|
<div className="flex flex-col items-center gap-4 w-full border-t pt-6">
|
|
<p className="text-sm font-medium">
|
|
Entrez le code à 6 chiffres pour confirmer :
|
|
</p>
|
|
<InputOTP maxLength={6} value={otpValue} onChange={setOtpValue}>
|
|
<InputOTPGroup>
|
|
<InputOTPSlot index={0} />
|
|
<InputOTPSlot index={1} />
|
|
<InputOTPSlot index={2} />
|
|
</InputOTPGroup>
|
|
<InputOTPSeparator />
|
|
<InputOTPGroup>
|
|
<InputOTPSlot index={3} />
|
|
<InputOTPSlot index={4} />
|
|
<InputOTPSlot index={5} />
|
|
</InputOTPGroup>
|
|
</InputOTP>
|
|
</div>
|
|
</CardContent>
|
|
<CardFooter className="flex justify-between">
|
|
<Button variant="ghost" onClick={() => setStep("idle")}>
|
|
Annuler
|
|
</Button>
|
|
<Button
|
|
onClick={handleEnable}
|
|
disabled={otpValue.length !== 6 || isLoading}
|
|
>
|
|
{isLoading ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
"Activer la 2FA"
|
|
)}
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (step === "verify") {
|
|
return (
|
|
<Card className="border-none shadow-sm">
|
|
<CardHeader>
|
|
<CardTitle>Désactiver la 2FA</CardTitle>
|
|
<CardDescription>
|
|
Veuillez entrer le code de votre application pour désactiver la double
|
|
authentification.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col items-center gap-6">
|
|
<InputOTP maxLength={6} value={otpValue} onChange={setOtpValue}>
|
|
<InputOTPGroup>
|
|
<InputOTPSlot index={0} />
|
|
<InputOTPSlot index={1} />
|
|
<InputOTPSlot index={2} />
|
|
</InputOTPGroup>
|
|
<InputOTPSeparator />
|
|
<InputOTPGroup>
|
|
<InputOTPSlot index={3} />
|
|
<InputOTPSlot index={4} />
|
|
<InputOTPSlot index={5} />
|
|
</InputOTPGroup>
|
|
</InputOTP>
|
|
</CardContent>
|
|
<CardFooter className="flex justify-between">
|
|
<Button variant="ghost" onClick={() => setStep("idle")}>
|
|
Annuler
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
onClick={handleDisable}
|
|
disabled={otpValue.length !== 6 || isLoading}
|
|
>
|
|
{isLoading ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
"Confirmer la désactivation"
|
|
)}
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|