Downgrade to NextAuth v4 to fix constructor error
This commit is contained in:
@@ -1,3 +0,0 @@
|
|||||||
import { handlers } from "@/auth"
|
|
||||||
|
|
||||||
export const { GET, POST } = handlers
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import type { NextAuthConfig } from "next-auth"
|
|
||||||
|
|
||||||
export const authConfig = {
|
|
||||||
trustHost: true,
|
|
||||||
session: {
|
|
||||||
strategy: "jwt",
|
|
||||||
maxAge: 30 * 24 * 60 * 60, // 30 days in seconds
|
|
||||||
updateAge: 24 * 60 * 60, // Update session every 24 hours
|
|
||||||
},
|
|
||||||
cookies: {
|
|
||||||
sessionToken: {
|
|
||||||
name: 'next-auth.session-token',
|
|
||||||
options: {
|
|
||||||
httpOnly: true, // Prevent JavaScript access to cookies (XSS protection)
|
|
||||||
sameSite: 'lax', // CSRF protection - prevents cookie from being sent in cross-site requests
|
|
||||||
path: '/',
|
|
||||||
secure: false, // Set to false for HTTP, true only if using HTTPS
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pages: {
|
|
||||||
signIn: "/auth/login",
|
|
||||||
signOut: "/auth/logout",
|
|
||||||
error: "/auth/error",
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
authorized({ auth, request: { nextUrl } }) {
|
|
||||||
const isLoggedIn = !!auth?.user
|
|
||||||
|
|
||||||
// Define protected routes
|
|
||||||
const isProtectedRoute =
|
|
||||||
nextUrl.pathname === "/" ||
|
|
||||||
nextUrl.pathname.startsWith("/list") ||
|
|
||||||
nextUrl.pathname.startsWith("/profile") ||
|
|
||||||
nextUrl.pathname.startsWith("/invitations")
|
|
||||||
|
|
||||||
// Define auth pages (login, register, etc.)
|
|
||||||
const isAuthPage = nextUrl.pathname.startsWith("/auth")
|
|
||||||
|
|
||||||
// Allow access to auth pages regardless of authentication status
|
|
||||||
if (isAuthPage) {
|
|
||||||
// If already logged in and trying to access login/register, redirect to home
|
|
||||||
if (isLoggedIn && (nextUrl.pathname === "/auth/login" || nextUrl.pathname === "/auth/register")) {
|
|
||||||
return Response.redirect(new URL("/", nextUrl))
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect to login if accessing protected route without authentication
|
|
||||||
if (isProtectedRoute && !isLoggedIn) {
|
|
||||||
return false // This will redirect to signIn page
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow access to public routes
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
async jwt({ token, user }) {
|
|
||||||
// Add user ID to token on sign in
|
|
||||||
if (user) {
|
|
||||||
token.id = user.id
|
|
||||||
}
|
|
||||||
return token
|
|
||||||
},
|
|
||||||
async session({ session, token }) {
|
|
||||||
// Add user ID to session from token
|
|
||||||
if (session.user && token.id) {
|
|
||||||
session.user.id = token.id as string
|
|
||||||
}
|
|
||||||
return session
|
|
||||||
},
|
|
||||||
},
|
|
||||||
providers: [], // Providers will be added in auth.ts
|
|
||||||
} satisfies NextAuthConfig
|
|
||||||
59
auth.ts
59
auth.ts
@@ -1,59 +0,0 @@
|
|||||||
import NextAuth from "next-auth"
|
|
||||||
import Credentials from "next-auth/providers/credentials"
|
|
||||||
import { authConfig } from "@/auth.config"
|
|
||||||
import { prisma } from "@/lib/db"
|
|
||||||
import bcrypt from "bcryptjs"
|
|
||||||
import { z } from "zod"
|
|
||||||
|
|
||||||
// Validation schemas
|
|
||||||
const loginSchema = z.object({
|
|
||||||
email: z.string().email(),
|
|
||||||
password: z.string().min(1),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const { handlers, signIn, signOut, auth } = NextAuth({
|
|
||||||
...authConfig,
|
|
||||||
trustHost: true,
|
|
||||||
providers: [
|
|
||||||
Credentials({
|
|
||||||
name: "credentials",
|
|
||||||
credentials: {
|
|
||||||
email: { label: "Email", type: "email" },
|
|
||||||
password: { label: "Password", type: "password" },
|
|
||||||
},
|
|
||||||
async authorize(credentials) {
|
|
||||||
try {
|
|
||||||
// Validate credentials format
|
|
||||||
const { email, password } = loginSchema.parse(credentials)
|
|
||||||
|
|
||||||
// Query user from database
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: { email },
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check if user exists
|
|
||||||
if (!user || !user.passwordHash) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify password with bcrypt
|
|
||||||
const isPasswordValid = await bcrypt.compare(password, user.passwordHash)
|
|
||||||
|
|
||||||
if (!isPasswordValid) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return user object (without password hash)
|
|
||||||
return {
|
|
||||||
id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
name: user.name,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Authorization error:", error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import NextAuth from "next-auth"
|
|
||||||
import { authConfig } from "@/auth.config"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Middleware for session validation, route protection, and security headers
|
|
||||||
*
|
|
||||||
* This middleware:
|
|
||||||
* - Validates session tokens on protected routes
|
|
||||||
* - Redirects unauthenticated users to login
|
|
||||||
* - Handles session expiration
|
|
||||||
* - Allows access to auth pages for unauthenticated users
|
|
||||||
* - Adds security headers (CSRF protection, XSS protection, etc.)
|
|
||||||
*
|
|
||||||
* Requirements: 3.3, 3.5, 9.4 (CSRF protection), 9.5 (security headers)
|
|
||||||
*/
|
|
||||||
const { auth } = NextAuth(authConfig)
|
|
||||||
|
|
||||||
export default auth((req) => {
|
|
||||||
// The authorization logic is handled in auth.config.ts authorized callback
|
|
||||||
// Here we just add security headers
|
|
||||||
|
|
||||||
const requestHeaders = new Headers(req.headers)
|
|
||||||
|
|
||||||
// Add security headers - Requirement 9.4, 9.5
|
|
||||||
// CSRF Protection - SameSite cookie attribute is set in auth.ts
|
|
||||||
// This header provides additional CSRF protection
|
|
||||||
requestHeaders.set('X-Content-Type-Options', 'nosniff')
|
|
||||||
|
|
||||||
// XSS Protection
|
|
||||||
requestHeaders.set('X-XSS-Protection', '1; mode=block')
|
|
||||||
|
|
||||||
// Prevent clickjacking attacks
|
|
||||||
requestHeaders.set('X-Frame-Options', 'DENY')
|
|
||||||
|
|
||||||
// Referrer Policy - don't leak referrer information
|
|
||||||
requestHeaders.set('Referrer-Policy', 'strict-origin-when-cross-origin')
|
|
||||||
|
|
||||||
// Content Security Policy - helps prevent XSS attacks
|
|
||||||
// Note: This is a basic CSP. You may need to adjust based on your needs
|
|
||||||
const csp = [
|
|
||||||
"default-src 'self'",
|
|
||||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval'", // unsafe-inline and unsafe-eval needed for Next.js
|
|
||||||
"style-src 'self' 'unsafe-inline'", // unsafe-inline needed for Tailwind
|
|
||||||
"img-src 'self' data: blob:",
|
|
||||||
"font-src 'self' data:",
|
|
||||||
"connect-src 'self'",
|
|
||||||
"frame-ancestors 'none'",
|
|
||||||
].join('; ')
|
|
||||||
requestHeaders.set('Content-Security-Policy', csp)
|
|
||||||
|
|
||||||
// Permissions Policy - restrict access to browser features
|
|
||||||
requestHeaders.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
|
|
||||||
|
|
||||||
return undefined // Let NextAuth handle the response
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Matcher configuration for middleware
|
|
||||||
* Applies middleware to all routes except:
|
|
||||||
* - API routes (handled separately)
|
|
||||||
* - Static files (_next/static)
|
|
||||||
* - Image optimization (_next/image)
|
|
||||||
* - Favicon and other public assets
|
|
||||||
*/
|
|
||||||
export const config = {
|
|
||||||
matcher: [
|
|
||||||
/*
|
|
||||||
* Match all request paths except for the ones starting with:
|
|
||||||
* - api (API routes)
|
|
||||||
* - _next/static (static files)
|
|
||||||
* - _next/image (image optimization files)
|
|
||||||
* - favicon.ico (favicon file)
|
|
||||||
* - public assets (images, icons, manifest, sw.js)
|
|
||||||
*/
|
|
||||||
"/((?!api|_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|json|js)$).*)",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"lucide-react": "^0.294.0",
|
"lucide-react": "^0.294.0",
|
||||||
"mysql2": "^3.15.3",
|
"mysql2": "^3.15.3",
|
||||||
"next": "14.0.4",
|
"next": "14.0.4",
|
||||||
"next-auth": "^5.0.0-beta.30",
|
"next-auth": "^4.24.5",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"zod": "^4.1.13"
|
"zod": "^4.1.13"
|
||||||
|
|||||||
52
pages/api/auth/[...nextauth].ts
Normal file
52
pages/api/auth/[...nextauth].ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import NextAuth from "next-auth"
|
||||||
|
import CredentialsProvider from "next-auth/providers/credentials"
|
||||||
|
import { prisma } from "@/lib/db"
|
||||||
|
import bcrypt from "bcryptjs"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
const loginSchema = z.object({
|
||||||
|
email: z.string().email(),
|
||||||
|
password: z.string().min(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default NextAuth({
|
||||||
|
providers: [
|
||||||
|
CredentialsProvider({
|
||||||
|
name: "credentials",
|
||||||
|
credentials: {
|
||||||
|
email: { label: "Email", type: "email" },
|
||||||
|
password: { label: "Password", type: "password" },
|
||||||
|
},
|
||||||
|
async authorize(credentials) {
|
||||||
|
try {
|
||||||
|
const { email, password } = loginSchema.parse(credentials)
|
||||||
|
const user = await prisma.user.findUnique({ where: { email } })
|
||||||
|
|
||||||
|
if (!user || !user.passwordHash) return null
|
||||||
|
|
||||||
|
const isValid = await bcrypt.compare(password, user.passwordHash)
|
||||||
|
if (!isValid) return null
|
||||||
|
|
||||||
|
return { id: user.id, email: user.email, name: user.name }
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
session: { strategy: "jwt" },
|
||||||
|
pages: {
|
||||||
|
signIn: "/auth/login",
|
||||||
|
error: "/auth/error",
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
async jwt({ token, user }) {
|
||||||
|
if (user) token.id = user.id
|
||||||
|
return token
|
||||||
|
},
|
||||||
|
async session({ session, token }) {
|
||||||
|
if (token.id) session.user.id = token.id
|
||||||
|
return session
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user