feat: initialize Next.js project with Prisma, authentication, and rankings page

This commit is contained in:
2026-03-27 14:50:00 -07:00
parent 96f7cb86d3
commit 66e4baa643
208 changed files with 8522 additions and 9035 deletions
+131
View File
@@ -0,0 +1,131 @@
import NextAuth, { DefaultSession, DefaultUser } from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
import { prisma } from "./prisma"
import bcrypt from "bcryptjs"
// Extend the built-in session and user types
declare module "next-auth" {
interface Session {
user: {
id: string
playerId: number
role: string
} & DefaultSession["user"]
}
interface User {
id: string
playerId: number
role: string
}
}
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth({
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null
}
const email = credentials.email as string
const password = credentials.password as string
// Find user by email
const user = await prisma.user.findUnique({
where: { email },
include: { player: true },
})
if (!user) {
return null
}
// Check if account is locked
if (user.lockedUntil && user.lockedUntil > new Date()) {
throw new Error("Account locked. Please try again later.")
}
// Verify password
const isValid = await bcrypt.compare(password, user.passwordDigest)
if (!isValid) {
// Increment failed login attempts
const failedAttempts = (user.failedLoginAttempts || 0) + 1
if (failedAttempts >= 5) {
// Lock account for 15 minutes
const lockedUntil = new Date(Date.now() + 15 * 60 * 1000)
await prisma.user.update({
where: { id: user.id },
data: { failedLoginAttempts: failedAttempts, lockedUntil },
})
throw new Error("Account locked after 5 failed attempts. Please try again in 15 minutes.")
} else {
await prisma.user.update({
where: { id: user.id },
data: { failedLoginAttempts: failedAttempts },
})
}
return null
}
// Successful login - update last login and reset failed attempts
await prisma.user.update({
where: { id: user.id },
data: {
lastLoginAt: new Date(),
failedLoginAttempts: 0,
lockedUntil: null,
},
})
return {
id: user.id.toString(),
playerId: user.playerId,
email: user.email,
role: user.role,
name: user.player.name,
}
},
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id
token.playerId = user.playerId
token.role = user.role
}
return token
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string
session.user.playerId = token.playerId as number
session.user.role = token.role as string
}
return session
},
},
pages: {
signIn: "/auth/login",
signOut: "/auth/logout",
error: "/auth/error",
},
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
secret: process.env.NEXTAUTH_SECRET,
})
+9
View File
@@ -0,0 +1,9 @@
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma