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",
|
||||
"mysql2": "^3.15.3",
|
||||
"next": "14.0.4",
|
||||
"next-auth": "^5.0.0-beta.30",
|
||||
"next-auth": "^4.24.5",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"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