mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-22 14:38:10 +02:00
feat: Implement NextAuth authentication with credentials, database and JSON fallback, including admin seeding and password management.
This commit is contained in:
1 parent
4e80f228fe
commit
69e2f9c325
3 files changed
+90
-26
No files matched your search
@@ -1,4 +1,15 @@
|
|||||||
import { scryptSync, randomBytes, timingSafeEqual } from "crypto";
|
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.
|
* Hashes a password using scrypt.
|
||||||
@@ -26,3 +37,40 @@ export function verifyPassword(password: string, storedHash: string): boolean {
|
|||||||
return false;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import { users } from "@/db/schema";
|
import { users } from "@/db/schema";
|
||||||
import { hashPassword } from "./auth-logic";
|
import { hashPassword, getFallbackUsers, saveFallbackUsers } from "./auth-logic";
|
||||||
import { count, eq, sql } from "drizzle-orm";
|
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).
|
* Also ensures the table exists (auto-repair).
|
||||||
*/
|
*/
|
||||||
export async function ensureAdminExists() {
|
export async function ensureAdminExists() {
|
||||||
try {
|
// 1. Always ensure local JSON fallback has at least one admin
|
||||||
console.log("[AUTH] Testing database connection...");
|
const fallbackUsers = getFallbackUsers();
|
||||||
await db.execute(sql`SELECT 1`);
|
if (fallbackUsers.length === 0) {
|
||||||
console.log("[AUTH] Database connection OK.");
|
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)
|
try {
|
||||||
console.log("[AUTH] Checking/Creating users table...");
|
console.log("[AUTH] Testing database connection for seeding...");
|
||||||
|
await db.execute(sql`SELECT 1`);
|
||||||
|
|
||||||
|
// 2. Auto-repair: Ensure table exists
|
||||||
await db.execute(sql`
|
await db.execute(sql`
|
||||||
CREATE TABLE IF NOT EXISTS "users" (
|
CREATE TABLE IF NOT EXISTS "users" (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
@@ -25,32 +35,24 @@ export async function ensureAdminExists() {
|
|||||||
created_at TIMESTAMP DEFAULT NOW()
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
console.log("[AUTH] users table verified/created.");
|
|
||||||
|
|
||||||
|
// 3. Sync admin to DB
|
||||||
const adminUser = await db.select()
|
const adminUser = await db.select()
|
||||||
.from(users)
|
.from(users)
|
||||||
.where(eq(users.username, "admin"))
|
.where(eq(users.username, "admin"))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (adminUser.length === 0) {
|
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({
|
await db.insert(users).values({
|
||||||
username: "admin",
|
username: "admin",
|
||||||
passwordHash: hashPassword("admin"),
|
passwordHash: hashPassword("admin"),
|
||||||
role: "admin"
|
role: "admin"
|
||||||
});
|
});
|
||||||
console.log("[AUTH] Default admin seeded successfully.");
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
console.log("[AUTH] Admin user already exists.");
|
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("[AUTH] !!! CRITICAL DATABASE ERROR !!!");
|
console.warn("[AUTH] Database seeding failed (expected if DB not configured yet):", err.message);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
+22
-8
@@ -3,7 +3,7 @@ import Credentials from "next-auth/providers/credentials";
|
|||||||
import { db } from "@/db";
|
import { db } from "@/db";
|
||||||
import { users } from "@/db/schema";
|
import { users } from "@/db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
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 { ensureAdminExists } from "@/features/auth/logic/seed-admin";
|
||||||
import { authConfig } from "./auth.config";
|
import { authConfig } from "./auth.config";
|
||||||
|
|
||||||
@@ -22,15 +22,30 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
|||||||
if (!credentials?.username || !credentials?.password) return null;
|
if (!credentials?.username || !credentials?.password) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[AUTH] Checking user "${credentials.username}" in database...`);
|
console.log(`[AUTH] Checking user "${credentials.username}"...`);
|
||||||
const [user] = await db.select()
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.username, credentials.username as string));
|
|
||||||
|
|
||||||
console.log(`[AUTH] User found in DB: ${!!user}`);
|
let user: any = null;
|
||||||
|
|
||||||
|
// 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) {
|
if (!user) {
|
||||||
console.warn(`[AUTH] User "${credentials.username}" not found.`);
|
console.warn(`[AUTH] User "${credentials.username}" not found anywhere.`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +64,6 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
|||||||
};
|
};
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("[AUTH] CRITICAL ERROR during authorize callback:", err.message || err);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user