import bcrypt from "bcryptjs"; import { getDb } from "./db"; import { localUsers } from "../drizzle/schema"; import { eq, or } from "drizzle-orm"; import { SignJWT, jwtVerify } from "jose"; import { ENV } from "./_core/env"; const SALT_ROUNDS = 12; const JWT_EXPIRY = "7d"; const LOCAL_AUTH_COOKIE = "veille_local_auth"; export async function hashPassword(password: string): Promise { return bcrypt.hash(password, SALT_ROUNDS); } export async function verifyPassword(password: string, hash: string): Promise { return bcrypt.compare(password, hash); } export async function generateLocalToken(userId: number, role: string): Promise { const secret = new TextEncoder().encode(ENV.cookieSecret); return new SignJWT({ sub: String(userId), role, type: "local" }) .setProtectedHeader({ alg: "HS256" }) .setIssuedAt() .setExpirationTime(JWT_EXPIRY) .sign(secret); } export async function verifyLocalToken(token: string): Promise<{ userId: number; role: string } | null> { try { const secret = new TextEncoder().encode(ENV.cookieSecret); const { payload } = await jwtVerify(token, secret); if (payload.type !== "local" || !payload.sub) return null; return { userId: parseInt(payload.sub), role: payload.role as string }; } catch { return null; } } export async function loginLocalUser(email: string, password: string) { const db = await getDb(); if (!db) throw new Error("Base de données indisponible"); // Recherche par e-mail (insensible à la casse) OU par identifiant exact const identifier = email.trim(); const users = await db .select() .from(localUsers) .where( or( eq(localUsers.email, identifier.toLowerCase()), eq(localUsers.email, identifier) ) ) .limit(1); const user = users[0]; if (!user || !user.isActive) { throw new Error("Identifiants incorrects ou compte désactivé"); } const valid = await verifyPassword(password, user.passwordHash); if (!valid) throw new Error("Identifiants incorrects ou compte désactivé"); // Mise à jour lastSignedIn await db .update(localUsers) .set({ lastSignedIn: new Date() }) .where(eq(localUsers.id, user.id)); const token = await generateLocalToken(user.id, user.role); return { token, user: { id: user.id, name: user.name, email: user.email, role: user.role } }; } export async function getLocalUserById(id: number) { const db = await getDb(); if (!db) return null; const users = await db.select().from(localUsers).where(eq(localUsers.id, id)).limit(1); return users[0] ?? null; } export async function ensureAdminExists() { const db = await getDb(); if (!db) return; const admins = await db .select({ id: localUsers.id }) .from(localUsers) .where(eq(localUsers.role, "admin")) .limit(1); if (admins.length === 0) { const hash = await hashPassword("Admin@Itinova2024!"); await db.insert(localUsers).values({ name: "Administrateur", email: "admin@itinova.fr", passwordHash: hash, role: "admin", isActive: true, }); console.log("[LocalAuth] Compte admin par défaut créé : admin@itinova.fr / Admin@Itinova2024!"); } }