From fe783301e744118888567e04774a46639c9b9ec0 Mon Sep 17 00:00:00 2001 From: Paul Bailey Date: Tue, 2 Dec 2025 13:56:57 +0000 Subject: [PATCH] Downgrade to NextAuth v4 to fix constructor error --- app/api/auth/[...nextauth]/route.ts | 3 -- auth.config.ts | 73 --------------------------- auth.ts | 59 ---------------------- middleware.ts | 77 ----------------------------- package.json | 2 +- pages/api/auth/[...nextauth].ts | 52 +++++++++++++++++++ 6 files changed, 53 insertions(+), 213 deletions(-) delete mode 100644 app/api/auth/[...nextauth]/route.ts delete mode 100644 auth.config.ts delete mode 100644 auth.ts delete mode 100644 middleware.ts create mode 100644 pages/api/auth/[...nextauth].ts diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts deleted file mode 100644 index 9879351..0000000 --- a/app/api/auth/[...nextauth]/route.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { handlers } from "@/auth" - -export const { GET, POST } = handlers diff --git a/auth.config.ts b/auth.config.ts deleted file mode 100644 index fc749a7..0000000 --- a/auth.config.ts +++ /dev/null @@ -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 diff --git a/auth.ts b/auth.ts deleted file mode 100644 index 272724d..0000000 --- a/auth.ts +++ /dev/null @@ -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 - } - }, - }), - ], -}) diff --git a/middleware.ts b/middleware.ts deleted file mode 100644 index 116c0fc..0000000 --- a/middleware.ts +++ /dev/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)$).*)", - ], -} diff --git a/package.json b/package.json index fab8d79..193f9d9 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/pages/api/auth/[...nextauth].ts b/pages/api/auth/[...nextauth].ts new file mode 100644 index 0000000..35e2330 --- /dev/null +++ b/pages/api/auth/[...nextauth].ts @@ -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 + }, + }, +}) \ No newline at end of file