import NextAuth, { NextAuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import bcrypt from "bcryptjs"; import { getDb } from "@/lib/db"; import { isRateLimited } from "@/lib/rate-limit"; type UserRow = { id: string; email: string; password_hash: string }; export const authOptions: NextAuthOptions = { providers: [ CredentialsProvider({ name: "Credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, }, async authorize(credentials, req) { if (!credentials?.email || !credentials?.password) return null; // Throttle login attempts per IP — this is the password brute-force surface. if (isRateLimited(req, "login", 10, 15 * 60 * 1000)) { throw new Error("Too many attempts. Try again later."); } const db = getDb(); const user = db .prepare("SELECT id, email, password_hash FROM users WHERE email = ?") .get(credentials.email) as UserRow | undefined; if (!user) return null; const valid = await bcrypt.compare(credentials.password, user.password_hash); if (!valid) return null; return { id: user.id, email: user.email }; }, }), ], pages: { signIn: "/login", }, session: { strategy: "jwt", }, secret: process.env.NEXTAUTH_SECRET, }; export default NextAuth(authOptions);