diff --git a/server/databaseMigrations.ts b/server/databaseMigrations.ts index 29d6522..870050f 100644 --- a/server/databaseMigrations.ts +++ b/server/databaseMigrations.ts @@ -1,6 +1,35 @@ import { migrate } from "drizzle-orm/mysql2/migrator"; import { drizzle } from "drizzle-orm/mysql2"; +import { eq } from "drizzle-orm"; import { createPool } from "mysql2/promise"; +import bcrypt from "bcryptjs"; +import { users } from "../drizzle/schema"; + +const INITIAL_ADMIN_LOGIN = "adminItinova"; +const INITIAL_ADMIN_PASSWORD = process.env.INITIAL_ADMIN_PASSWORD ?? "Itinova69!"; + +async function ensureInitialAdmin(pool: ReturnType) { + const database = drizzle(pool); + const existingAdmin = await database + .select({ id: users.id }) + .from(users) + .where(eq(users.login, INITIAL_ADMIN_LOGIN)) + .limit(1); + + if (existingAdmin.length > 0) return; + + const passwordHash = await bcrypt.hash(INITIAL_ADMIN_PASSWORD, 12); + await database.insert(users).values({ + login: INITIAL_ADMIN_LOGIN, + email: "adminItinova@santinova-soft.org", + firstName: "Admin", + lastName: "Itinova", + passwordHash, + role: "admin", + isActive: true, + }); + console.log("[Database] Compte administrateur initial créé."); +} /** * Synchronise la base avec les migrations versionnées avant d'exposer l'API. @@ -16,7 +45,9 @@ export async function runDatabaseMigrations() { const pool = createPool(databaseUrl); try { - await migrate(drizzle(pool), { migrationsFolder: "./drizzle" }); + const database = drizzle(pool); + await migrate(database, { migrationsFolder: "./drizzle" }); + await ensureInitialAdmin(pool); console.log("[Database] Migrations Drizzle synchronisées."); } finally { await pool.end(); diff --git a/server/initialAdminBootstrap.test.ts b/server/initialAdminBootstrap.test.ts new file mode 100644 index 0000000..b532478 --- /dev/null +++ b/server/initialAdminBootstrap.test.ts @@ -0,0 +1,14 @@ +import { readFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("bootstrap du compte administrateur", () => { + it("crée le compte administrateur requis après migration sans stocker de mot de passe en clair", async () => { + const bootstrap = await readFile(resolve(process.cwd(), "server/databaseMigrations.ts"), "utf8"); + + expect(bootstrap).toContain('const INITIAL_ADMIN_LOGIN = "adminItinova"'); + expect(bootstrap).toContain("await ensureInitialAdmin(pool)"); + expect(bootstrap).toContain("await bcrypt.hash(INITIAL_ADMIN_PASSWORD"); + expect(bootstrap).not.toContain("passwordHash: INITIAL_ADMIN_PASSWORD,"); + }); +}); diff --git a/todo.md b/todo.md index f82e4f8..827fb31 100644 --- a/todo.md +++ b/todo.md @@ -216,3 +216,4 @@ - [x] Créer et synchroniser le dépôt Gitea production Budget SI avant le déploiement du serveur de production - [ ] Initialiser et vérifier le schéma de la base Budget SI de production avant la validation administrateur - [ ] Exécuter les migrations Drizzle au démarrage du conteneur pour les futurs déploiements de production +- [ ] Créer le compte administrateur initial requis lorsque la base Budget SI est vide