import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; import { z } from "zod"; import { eq } from "drizzle-orm"; import { db } from "@/db"; import { users } from "@/db/schema"; import { authConfig } from "./auth.config"; import { verifyPassword } from "@/lib/auth/password"; import { checkRateLimit, recordAttempt } from "@/lib/auth/rate-limit"; const credSchema = z.object({ email: z.string().email(), password: z.string().min(1), }); export const { handlers, auth, signIn, signOut } = NextAuth({ ...authConfig, providers: [ ...authConfig.providers, Credentials({ credentials: { email: {}, password: {} }, authorize: async (raw) => { const parsed = credSchema.safeParse(raw); if (!parsed.success) return null; const { email, password } = parsed.data; const key = `email:${email.toLowerCase()}`; // Rate-Limit greift für ALLE Credentials-Logins (default-deny). if (!(await checkRateLimit(key))) return null; const u = await db.query.users.findFirst({ where: eq(users.email, email), }); if (!u || !u.aktiv || u.authTyp !== "local" || !u.passwortHash) { await recordAttempt(key, "fail"); return null; } if (!(await verifyPassword(u.passwortHash, password))) { await recordAttempt(key, "fail"); return null; } await recordAttempt(key, "ok"); return { id: u.id, email: u.email, name: u.name, role: u.rolle, brigadeId: u.brigadeId, }; }, }), ], callbacks: { ...authConfig.callbacks, // Authentik-Login-Gate: nur vorgemerkte, aktive authentik-Konten zulassen. async signIn({ user, account }) { if (account?.provider === "authentik") { const email = user.email; if (!email) return false; const u = await db.query.users.findFirst({ where: eq(users.email, email), }); if (!u || !u.aktiv || u.authTyp !== "authentik") return false; user.role = u.rolle; user.brigadeId = u.brigadeId ?? null; } return true; }, }, });