97 lines
3.0 KiB
TypeScript
97 lines
3.0 KiB
TypeScript
import bcrypt from "bcryptjs";
|
|
import { getDb } from "./db";
|
|
import { localUsers } from "../drizzle/schema";
|
|
import { eq } 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");
|
|
|
|
const users = await db
|
|
.select()
|
|
.from(localUsers)
|
|
.where(eq(localUsers.email, email.toLowerCase().trim()))
|
|
.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!");
|
|
}
|
|
}
|