diff --git a/src/features/auth/logic/auth-logic.ts b/src/features/auth/logic/auth-logic.ts index 2cfff19..100b79c 100644 --- a/src/features/auth/logic/auth-logic.ts +++ b/src/features/auth/logic/auth-logic.ts @@ -1,4 +1,15 @@ import { scryptSync, randomBytes, timingSafeEqual } from "crypto"; +import fs from "fs"; +import path from "path"; + +const USERS_JSON_PATH = path.join(process.cwd(), "data", "users.json"); + +export interface UserJson { + id: string; + username: string; + passwordHash: string; + role: string; +} /** * Hashes a password using scrypt. @@ -26,3 +37,40 @@ export function verifyPassword(password: string, storedHash: string): boolean { return false; } } + +/** + * Reads users from local JSON fallback. + */ +export function getFallbackUsers(): UserJson[] { + try { + if (!fs.existsSync(USERS_JSON_PATH)) { + // Initial seed if file doesn't exist + const defaultAdmin: UserJson = { + id: "0", + username: "admin", + passwordHash: hashPassword("admin"), + role: "admin" + }; + saveFallbackUsers([defaultAdmin]); + return [defaultAdmin]; + } + const data = fs.readFileSync(USERS_JSON_PATH, "utf-8"); + return JSON.parse(data); + } catch (e) { + console.error("[AUTH] Error reading users.json:", e); + return []; + } +} + +/** + * Saves users to local JSON fallback. + */ +export function saveFallbackUsers(users: UserJson[]) { + try { + const dir = path.dirname(USERS_JSON_PATH); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(USERS_JSON_PATH, JSON.stringify(users, null, 2)); + } catch (e) { + console.error("[AUTH] Error writing users.json:", e); + } +} diff --git a/src/features/auth/logic/seed-admin.ts b/src/features/auth/logic/seed-admin.ts index 4e48e2a..fee76dd 100644 --- a/src/features/auth/logic/seed-admin.ts +++ b/src/features/auth/logic/seed-admin.ts @@ -1,6 +1,6 @@ import { db } from "@/db"; import { users } from "@/db/schema"; -import { hashPassword } from "./auth-logic"; +import { hashPassword, getFallbackUsers, saveFallbackUsers } from "./auth-logic"; import { count, eq, sql } from "drizzle-orm"; /** @@ -9,13 +9,23 @@ import { count, eq, sql } from "drizzle-orm"; * Also ensures the table exists (auto-repair). */ export async function ensureAdminExists() { - try { - console.log("[AUTH] Testing database connection..."); - await db.execute(sql`SELECT 1`); - console.log("[AUTH] Database connection OK."); + // 1. Always ensure local JSON fallback has at least one admin + const fallbackUsers = getFallbackUsers(); + if (fallbackUsers.length === 0) { + console.log("[AUTH] Initializing local JSON fallback with admin/admin..."); + saveFallbackUsers([{ + id: "0", + username: "admin", + passwordHash: hashPassword("admin"), + role: "admin" + }]); + } - // 1. Auto-repair: Ensure table exists (Fallback if db-init failed) - console.log("[AUTH] Checking/Creating users table..."); + try { + console.log("[AUTH] Testing database connection for seeding..."); + await db.execute(sql`SELECT 1`); + + // 2. Auto-repair: Ensure table exists await db.execute(sql` CREATE TABLE IF NOT EXISTS "users" ( id SERIAL PRIMARY KEY, @@ -25,32 +35,24 @@ export async function ensureAdminExists() { created_at TIMESTAMP DEFAULT NOW() ); `); - console.log("[AUTH] users table verified/created."); + // 3. Sync admin to DB const adminUser = await db.select() .from(users) .where(eq(users.username, "admin")) .limit(1); if (adminUser.length === 0) { - console.log("[AUTH] Admin user not found. Seeding default (admin/admin)..."); + console.log("[AUTH] Seeding admin to DB..."); await db.insert(users).values({ username: "admin", passwordHash: hashPassword("admin"), role: "admin" }); - console.log("[AUTH] Default admin seeded successfully."); return true; - } else { - console.log("[AUTH] Admin user already exists."); } } catch (err: any) { - console.error("[AUTH] !!! CRITICAL DATABASE ERROR !!!"); - console.error("- Message:", err.message); - console.error("- PG Code:", err.code); - console.error("- Detail:", err.detail); - console.error("- Hint:", err.hint); - if (err.internalQuery) console.error("- Query:", err.internalQuery); + console.warn("[AUTH] Database seeding failed (expected if DB not configured yet):", err.message); } return false; } diff --git a/src/lib/auth.ts b/src/lib/auth.ts index fbbcf5c..f82464b 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -3,7 +3,7 @@ import Credentials from "next-auth/providers/credentials"; import { db } from "@/db"; import { users } from "@/db/schema"; import { eq } from "drizzle-orm"; -import { verifyPassword } from "@/features/auth/logic/auth-logic"; +import { verifyPassword, getFallbackUsers } from "@/features/auth/logic/auth-logic"; import { ensureAdminExists } from "@/features/auth/logic/seed-admin"; import { authConfig } from "./auth.config"; @@ -22,15 +22,30 @@ export const { handlers, auth, signIn, signOut } = NextAuth({ if (!credentials?.username || !credentials?.password) return null; try { - console.log(`[AUTH] Checking user "${credentials.username}" in database...`); - const [user] = await db.select() - .from(users) - .where(eq(users.username, credentials.username as string)); + console.log(`[AUTH] Checking user "${credentials.username}"...`); + + let user: any = null; - console.log(`[AUTH] User found in DB: ${!!user}`); + // Try DB first + try { + const [dbUser] = await db.select() + .from(users) + .where(eq(users.username, credentials.username as string)); + user = dbUser; + console.log(`[AUTH] DB check: User found: ${!!user}`); + } catch (dbErr) { + console.warn("[AUTH] DB unreachable, falling back to JSON."); + } + + // Fallback to JSON if DB failed or user not found + if (!user) { + const fallbackUsers = getFallbackUsers(); + user = fallbackUsers.find(u => u.username === credentials.username); + console.log(`[AUTH] JSON check: User found: ${!!user}`); + } if (!user) { - console.warn(`[AUTH] User "${credentials.username}" not found.`); + console.warn(`[AUTH] User "${credentials.username}" not found anywhere.`); return null; } @@ -49,7 +64,6 @@ export const { handlers, auth, signIn, signOut } = NextAuth({ }; } catch (err: any) { console.error("[AUTH] CRITICAL ERROR during authorize callback:", err.message || err); - // Propage l'erreur pour que Next-Auth la gère (mais authorize doit retourner null ou l'utilisateur) return null; } }