Files
CollectFlow/src/lib/auth.ts
T
Claude 26b9364232 Lot 5 : changement de magasin instantané et contrôle des droits
Grille
- Corrige le lot 4 : au changement de magasin, les chiffres et le tri
  affichés suivent tout de suite (nouvelle liste), la sélection est gardée.
- Lignes toujours chargées « Nos 2 magasins » ; changement de magasin sans
  aller-retour serveur (URL mise à jour par l'historique du navigateur).
- Complément de l'API FF par magasin servi à part (/api/grid/rows/store-patch),
  appliqué à son arrivée, calcul identique à l'ancien (lib/store-patch.ts).
- La recherche ne reconstruit plus toutes les lignes à chaque frappe ; la
  sélection n'est remise à zéro qu'au changement de fournisseur.

Sécurité
- lib/authz.ts : session relue en base (cache 1 min), rôle de la base
  prioritaire ; variantes pour actions, routes et pages.
- Paramètres réservés aux administrateurs ; session vérifiée dans les actions
  Grille, Commandes, Historique et les routes ; diagnostics réservés admin.
- Mots de passe et clés d'IA plus envoyés au navigateur ; champ vide = mot de
  passe conservé ; plus de mot de passe dans le localStorage ; l'enregistrement
  de la base n'efface plus les clés d'IA.
- admin/admin créé seulement si aucun utilisateur ; fichier de secours utilisé
  uniquement si la base est injoignable ; data/ ignoré par git.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
2026-10-04 06:39:49 +00:00

74 lines
3.2 KiB
TypeScript

import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { db } from "@/db";
import { users } from "@/db/schema";
import { eq } from "drizzle-orm";
import { verifyPassword, getFallbackUsers } from "@/features/auth/logic/auth-logic";
import { ensureAdminExists } from "@/features/auth/logic/seed-admin";
import { authConfig } from "./auth.config";
export const { handlers, auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
name: "Credentials",
credentials: {
username: { label: "Utilisateur", type: "text" },
password: { label: "Mot de passe", type: "password" }
},
async authorize(credentials) {
await ensureAdminExists();
if (!credentials?.username || !credentials?.password) return null;
try {
console.log(`[AUTH] Checking user "${credentials.username}"...`);
let user: { id: number | string; username: string; passwordHash: string; role: string } | undefined;
// 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 {
// Base injoignable uniquement : un compte absent de la base ne doit
// pas pouvoir se connecter avec le fichier de secours (admin/admin).
console.warn("[AUTH] DB unreachable, falling back to JSON.");
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 anywhere.`);
return null;
}
const isValid = verifyPassword(credentials.password as string, user.passwordHash);
console.log(`[AUTH] Password valid for "${credentials.username}": ${isValid}`);
if (!isValid) {
console.warn(`[AUTH] Invalid password for user "${credentials.username}".`);
return null;
}
return {
id: user.id.toString(),
// `name` est repris dans la session : sans lui, l'en-tête ne
// pouvait pas afficher qui est connecté.
name: user.username,
username: user.username,
role: user.role,
};
} catch (err: any) {
console.error("[AUTH] CRITICAL ERROR during authorize callback:", err.message || err);
return null;
}
}
})
],
});