Files
veille-reglementaire/server/localAuth.ts

104 lines
3.2 KiB
TypeScript

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<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
export async function generateLocalToken(userId: number, role: string): Promise<string> {
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!");
}
}