diff --git a/src/middleware.ts b/src/middleware.ts index 0a48526..bf12286 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -33,10 +33,22 @@ function isRateLimited(ip: string): boolean { function hasValidOrigin(req: NextRequest): boolean { const origin = req.headers.get('origin') if (!origin) return true // non-browser (curl, server-to-server) - const expected = process.env.NEXTAUTH_URL - ? new URL(process.env.NEXTAUTH_URL).origin - : req.nextUrl.origin - return origin === expected + // Accept the host the browser actually used to reach this server. + const host = req.headers.get('x-forwarded-host') ?? req.headers.get('host') + const proto = req.headers.get('x-forwarded-proto') ?? 'http' + const requestOrigin = host ? `${proto}://${host}` : null + if (requestOrigin && origin === requestOrigin) return true + // Also accept the statically configured NEXTAUTH_URL origin (reverse-proxy setups). + if (process.env.NEXTAUTH_URL && origin === new URL(process.env.NEXTAUTH_URL).origin) return true + return false +} + +// Build an absolute URL using the Host header the browser sent, not the +// internal hostname Next.js resolves to inside Docker. +function siteUrl(req: NextRequest, path: string): URL { + const host = req.headers.get('x-forwarded-host') ?? req.headers.get('host') ?? 'localhost:3000' + const proto = req.headers.get('x-forwarded-proto') ?? 'http' + return new URL(path, `${proto}://${host}`) } export default auth((req) => { @@ -72,13 +84,13 @@ export default auth((req) => { if (pathname.startsWith('/api/')) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } - const loginUrl = new URL('/login', req.url) + const loginUrl = siteUrl(req, '/login') loginUrl.searchParams.set('callbackUrl', pathname) return NextResponse.redirect(loginUrl) } if (pathname === '/login') { - return NextResponse.redirect(new URL('/dashboard', req.url)) + return NextResponse.redirect(siteUrl(req, '/dashboard')) } return NextResponse.next()