feat: v8 - skill itinova-user-management (3 profils admin/standard/readonly, logo FEHAP, login/email)

This commit is contained in:
Manus Deploy
2026-04-21 06:51:07 -04:00
parent 65e345459c
commit a8b1784e28
14 changed files with 1356 additions and 219 deletions

View File

@@ -421,9 +421,14 @@ import { nanoid } from "nanoid";
/** Crée un utilisateur local (sans openId OAuth) avec un mot de passe hashé. */
export async function createLocalUser(data: {
name: string;
name?: string;
firstName?: string;
lastName?: string;
login?: string;
email: string;
sonumRole: "referent" | "gestionnaire" | "adherent";
role?: "admin" | "standard" | "readonly";
isActive?: boolean;
password: string;
}) {
const db = await getDb();
@@ -433,16 +438,31 @@ export async function createLocalUser(data: {
const existing = await db.select().from(users).where(eq(users.email, data.email)).limit(1);
if (existing.length > 0) throw new Error("EMAIL_EXISTS");
// Vérifier unicité login si fourni
if (data.login) {
const existingLogin = await db.select().from(users).where(eq(users.login, data.login)).limit(1);
if (existingLogin.length > 0) throw new Error("LOGIN_EXISTS");
}
// openId synthétique pour les comptes locaux
const syntheticOpenId = `local_${nanoid(16)}`;
const passwordHash = await bcrypt.hash(data.password, 12);
// Dériver name si non fourni
const fullName = data.name ??
(data.firstName && data.lastName ? `${data.firstName} ${data.lastName}` : data.email);
const insertResult = await db.insert(users).values({
openId: syntheticOpenId,
name: data.name,
login: data.login ?? null,
name: fullName,
firstName: data.firstName ?? null,
lastName: data.lastName ?? null,
email: data.email,
loginMethod: "local",
sonumRole: data.sonumRole,
role: data.role ?? "standard",
isActive: data.isActive !== undefined ? data.isActive : true,
cguAccepted: false,
lastSignedIn: new Date(),
});
@@ -455,24 +475,37 @@ export async function createLocalUser(data: {
return userId;
}
/** Authentifie un utilisateur par email + mot de passe. Retourne l'utilisateur ou null. */
export async function authenticateLocalUser(email: string, password: string) {
/** Authentifie un utilisateur par login (email ou login court) + mot de passe. Retourne l'utilisateur ou null. */
export async function authenticateLocalUser(loginOrEmail: string, password: string) {
const db = await getDb();
if (!db) return null;
const result = await db
.select({
user: users,
passwordHash: localCredentials.passwordHash,
})
// Chercher par email OU par login court
const byEmail = await db
.select({ user: users, passwordHash: localCredentials.passwordHash })
.from(users)
.innerJoin(localCredentials, eq(localCredentials.userId, users.id))
.where(eq(users.email, email))
.where(eq(users.email, loginOrEmail))
.limit(1);
let result = byEmail;
if (!result.length) {
const byLogin = await db
.select({ user: users, passwordHash: localCredentials.passwordHash })
.from(users)
.innerJoin(localCredentials, eq(localCredentials.userId, users.id))
.where(eq(users.login, loginOrEmail))
.limit(1);
result = byLogin;
}
if (!result.length) return null;
const { user, passwordHash } = result[0];
// Vérifier que le compte est actif
if (!user.isActive) return null;
const valid = await bcrypt.compare(password, passwordHash);
if (!valid) return null;
@@ -506,12 +539,25 @@ export async function updateLocalPassword(userId: number, newPassword: string) {
/** Met à jour les informations d'un utilisateur. */
export async function updateUser(userId: number, data: {
name?: string;
firstName?: string;
lastName?: string;
login?: string;
email?: string;
sonumRole?: "referent" | "gestionnaire" | "adherent";
role?: "admin" | "standard" | "readonly";
isActive?: boolean;
}) {
const db = await getDb();
if (!db) return;
await db.update(users).set({ ...data, updatedAt: new Date() }).where(eq(users.id, userId));
// Dériver name si firstName/lastName fournis
const updateData: Record<string, unknown> = { ...data, updatedAt: new Date() };
if (data.firstName !== undefined || data.lastName !== undefined) {
const current = await db.select().from(users).where(eq(users.id, userId)).limit(1);
const fn = data.firstName ?? current[0]?.firstName ?? "";
const ln = data.lastName ?? current[0]?.lastName ?? "";
if (fn || ln) updateData.name = `${fn} ${ln}`.trim();
}
await db.update(users).set(updateData as any).where(eq(users.id, userId));
}
/** Supprime un utilisateur et ses credentials locaux. */