feat: v8 - skill itinova-user-management (3 profils admin/standard/readonly, logo FEHAP, login/email)
This commit is contained in:
68
server/db.ts
68
server/db.ts
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user