Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import GitHubProvider from "next-auth/providers/github"; import { PrismaAdapter } from "@auth/prisma-adapter"; import { prisma } from "@/lib/prisma"; import bcryptjs from "bcryptjs"; import { isAccountLocked, getRemainingLockoutTime, recordFailedLoginAttempt, resetFailedLoginAttempts } from "./account-lockout"; import { logLoginAttempt } from "@/lib/audit-logger"; export const { handlers, signIn, signOut, auth } = NextAuth({ adapter: PrismaAdapter(prisma), providers: [ CredentialsProvider({ name: "Credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }}, async authorize(credentials) { if (!credentials?.email || !credentials?.password) { throw new Error("Invalid credentials"); } const email = (credentials.email as string).toLowerCase(); const user = await prisma.user.findUnique({ where: { email }}); if (!user || !user.password) { // Log failed attempt for non-existent user (don't reveal user doesn't exist) await logLoginAttempt( new Request("http://localhost"), null, false, email ); throw new Error("Invalid credentials"); } // Check if account is locked if (await isAccountLocked(user.id)) { const remainingTime = await getRemainingLockoutTime(user.id); const minutes = remainingTime ? Math.ceil(remainingTime / 60) : 15; throw new Error( `Account locked. Please try again in ${minutes} minute${minutes !== 1 ? "s" : ""}.` ); } const isPasswordValid = await bcryptjs.compare( credentials.password as string, user.password ); if (!isPasswordValid) { // Record failed login attempt const wasLocked = await recordFailedLoginAttempt(user.id); await logLoginAttempt( new Request("http://localhost"), user.id, false, email ); if (wasLocked) { throw new Error( "Account locked due to too many failed attempts. Please try again in 15 minutes." ); } throw new Error("Invalid credentials"); } // Successful login - reset failed attempts await resetFailedLoginAttempts(user.id); await logLoginAttempt( new Request("http://localhost"), user.id, true, email ); return { id: user.id, email: user.email, name: user.name, image: user.image}; }}), // GitHub OAuth Provider GitHubProvider({ clientId: process.env.GITHUB_ID!, clientSecret: process.env.GITHUB_SECRET!, }), ], pages: { signIn: "/signin"}, callbacks: { async signIn() { // Allow all sign-ins - role defaults are handled by the database schema return true; }, async jwt({ token, user }) { if (user) { // Convert user.id to number (NextAuth serializes it as string) const userId = typeof user.id === 'string' ? parseInt(user.id, 10) : user.id as number; token.id = userId; // Fetch user from database to get the role const dbUser = await prisma.user.findUnique({ where: { id: userId }}); if (dbUser) { token.role = dbUser.role || "CUSTOMER"; } } return token; }, async session({ session, token }) { if (session.user && typeof token.id === 'number') { (session.user as { id: number; role?: string }).id = token.id; (session.user as { id: number; role?: string }).role = token.role as string; } return session; }}, session: { strategy: "jwt"}, secret: process.env.NEXTAUTH_SECRET}); |