From 11c4411f68be86c4756e269ec3ed417ea233abc0 Mon Sep 17 00:00:00 2001 From: Paul Bailey Date: Tue, 2 Dec 2025 01:38:26 +0000 Subject: [PATCH] fix: Resolve Prisma Edge Runtime error in middleware - Move all auth configuration to auth.config.ts to avoid Prisma imports in Edge Runtime - Update middleware to use NextAuth with edge-compatible config - Consolidate session, cookies, and callback configuration in auth.config.ts - Update auth.ts to extend authConfig and only add Prisma-dependent providers - This fixes the 'PrismaClient is not configured to run in Edge Runtime' error The middleware now properly works with NextAuth v5 beta without trying to load Prisma in the Edge Runtime. --- auth.config.ts | 56 ++++++++++++++++++++++++++++++++++++++++----- auth.ts | 48 ++------------------------------------- middleware.ts | 61 ++++++++++++-------------------------------------- 3 files changed, 66 insertions(+), 99 deletions(-) diff --git a/auth.config.ts b/auth.config.ts index a8e2b4c..f570814 100644 --- a/auth.config.ts +++ b/auth.config.ts @@ -1,6 +1,22 @@ import type { NextAuthConfig } from "next-auth" export const authConfig = { + session: { + strategy: "jwt", + maxAge: 30 * 24 * 60 * 60, // 30 days in seconds + updateAge: 24 * 60 * 60, // Update session every 24 hours + }, + cookies: { + sessionToken: { + name: `${process.env.NODE_ENV === 'production' ? '__Secure-' : ''}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: process.env.NODE_ENV === 'production', // Only send cookie over HTTPS in production + }, + }, + }, pages: { signIn: "/auth/login", signOut: "/auth/logout", @@ -9,20 +25,48 @@ export const authConfig = { callbacks: { authorized({ auth, request: { nextUrl } }) { const isLoggedIn = !!auth?.user - const isOnAuthPage = nextUrl.pathname.startsWith("/auth") - // Allow access to auth pages when not logged in - if (isOnAuthPage) { + // 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 not authenticated - if (!isLoggedIn) { - return false + // 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 index 161a484..c6daa4c 100644 --- a/auth.ts +++ b/auth.ts @@ -1,5 +1,6 @@ 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" @@ -11,22 +12,7 @@ const loginSchema = z.object({ }) export const { handlers, signIn, signOut, auth } = NextAuth({ - session: { - strategy: "jwt", - maxAge: 30 * 24 * 60 * 60, // 30 days in seconds - updateAge: 24 * 60 * 60, // Update session every 24 hours - }, - cookies: { - sessionToken: { - name: `${process.env.NODE_ENV === 'production' ? '__Secure-' : ''}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: process.env.NODE_ENV === 'production', // Only send cookie over HTTPS in production - }, - }, - }, + ...authConfig, providers: [ Credentials({ name: "credentials", @@ -69,34 +55,4 @@ export const { handlers, signIn, signOut, auth } = NextAuth({ }, }), ], - callbacks: { - async jwt({ token, user, trigger }) { - // Add user ID to token on sign in - if (user) { - token.id = user.id - } - - // Handle session expiration - // Token will be automatically invalidated by NextAuth when it expires - // based on the maxAge setting in session config - - 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 - } - - // Session validation happens automatically via JWT expiration - // If token is expired, this callback won't be called and user will be redirected - - return session - }, - }, - pages: { - signIn: "/auth/login", - signOut: "/auth/logout", - error: "/auth/error", - }, }) diff --git a/middleware.ts b/middleware.ts index 1b9d4b7..116c0fc 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,6 +1,5 @@ -import { auth } from "@/auth" -import { NextResponse } from "next/server" -import type { NextRequest } from "next/server" +import NextAuth from "next-auth" +import { authConfig } from "@/auth.config" /** * Middleware for session validation, route protection, and security headers @@ -14,59 +13,27 @@ import type { NextRequest } from "next/server" * * Requirements: 3.3, 3.5, 9.4 (CSRF protection), 9.5 (security headers) */ +const { auth } = NextAuth(authConfig) + export default auth((req) => { - const { nextUrl } = req - const isLoggedIn = !!req.auth?.user + // The authorization logic is handled in auth.config.ts authorized callback + // Here we just add security headers - // 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") - - // Create response - let response: NextResponse - - // 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")) { - response = NextResponse.redirect(new URL("/", req.url)) - } else { - response = NextResponse.next() - } - } - // Redirect to login if accessing protected route without authentication - else if (isProtectedRoute && !isLoggedIn) { - const loginUrl = new URL("/auth/login", req.url) - // Add callback URL to redirect back after login - loginUrl.searchParams.set("callbackUrl", nextUrl.pathname) - response = NextResponse.redirect(loginUrl) - } - // Allow access to public routes - else { - response = NextResponse.next() - } + const requestHeaders = new Headers(req.headers) // Add security headers - Requirement 9.4, 9.5 - const headers = response.headers - // CSRF Protection - SameSite cookie attribute is set in auth.ts // This header provides additional CSRF protection - headers.set('X-Content-Type-Options', 'nosniff') + requestHeaders.set('X-Content-Type-Options', 'nosniff') // XSS Protection - headers.set('X-XSS-Protection', '1; mode=block') + requestHeaders.set('X-XSS-Protection', '1; mode=block') // Prevent clickjacking attacks - headers.set('X-Frame-Options', 'DENY') + requestHeaders.set('X-Frame-Options', 'DENY') // Referrer Policy - don't leak referrer information - headers.set('Referrer-Policy', 'strict-origin-when-cross-origin') + 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 @@ -79,12 +46,12 @@ export default auth((req) => { "connect-src 'self'", "frame-ancestors 'none'", ].join('; ') - headers.set('Content-Security-Policy', csp) + requestHeaders.set('Content-Security-Policy', csp) // Permissions Policy - restrict access to browser features - headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()') + requestHeaders.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()') - return response + return undefined // Let NextAuth handle the response }) /**