TypeScript 50.6%
JavaScript 46.1%
SQL 2.1%
Shell 0.6%
CSS 0.5%
1// MFA forte pour les rôles élevés : TOTP (RFC 6238) via otpauth ; secret chiffré en base ; vérification récente exigée (30 min) pour toute action admin.2import * as OTPAuth from "otpauth";3import { eq } from "drizzle-orm";4import { db } from "@/db/client";5import { users, sessions } from "@/db/schema";6import { encrypt, decrypt } from "@/lib/auth/crypto";7import { AuthError, type SessionUser } from "@/lib/auth/session";8import { audit } from "@/lib/auth/audit";9const MFA_WINDOW_MS = 30 * 60000;10export async function totpStatus(userId: string): Promise<boolean> { const [u] = await db().select({ s: users.totpSecretEncrypted }).from(users).where(eq(users.id, userId)); return !!u?.s; }11export function newSecret(email: string) { const secret = new OTPAuth.Secret({ size: 20 }); const totp = new OTPAuth.TOTP({ issuer: "Toveli", label: email, algorithm: "SHA1", digits: 6, period: 30, secret }); return { base32: secret.base32, uri: totp.toString() }; }12export async function enroll(userId: string, base32: string, code: string): Promise<boolean> { const totp = new OTPAuth.TOTP({ issuer: "Toveli", digits: 6, period: 30, secret: OTPAuth.Secret.fromBase32(base32) }); if (totp.validate({ token: code, window: 1 }) === null) return false; await db().update(users).set({ totpSecretEncrypted: encrypt(base32), updatedAt: new Date() }).where(eq(users.id, userId)); await audit("mfa.enroll", { actorId: userId }); return true; }13export async function verify(user: SessionUser, code: string): Promise<boolean> { const [u] = await db().select({ s: users.totpSecretEncrypted }).from(users).where(eq(users.id, user.id)); if (!u?.s) return false; const totp = new OTPAuth.TOTP({ issuer: "Toveli", digits: 6, period: 30, secret: OTPAuth.Secret.fromBase32(decrypt(u.s)) }); const ok = totp.validate({ token: code, window: 1 }) !== null; if (ok) await db().update(sessions).set({ mfaAt: new Date() }).where(eq(sessions.id, user.sessionId)); await audit(ok ? "mfa.ok" : "mfa.fail", { actorId: user.id }); return ok; }14/** Garde des actions admin : rôle + TOTP vérifié dans la fenêtre. */15export function requireFreshMfa(u: SessionUser) { if (!u.mfaAt || Date.now() - u.mfaAt.getTime() > MFA_WINDOW_MS) throw new AuthError(403, "Vérification TOTP requise"); }1617export function isFreshMfa(mfaAt: Date | null | undefined): boolean { return !!mfaAt && Date.now() - mfaAt.getTime() <= MFA_WINDOW_MS; }18