fix(auth): resolve cookie authentication failure over HTTP
Cookies were set with secure flag based solely on NODE_ENV, causing 401 errors when accessing over HTTP with NODE_ENV=production. - Add COOKIE_SECURE env var for explicit control - Auto-detect HTTPS via X-Forwarded-Proto header in production - Extract isSecureCookie() utility to lib/auth/utils.ts - Document COOKIE_SECURE in README and .env.example Fixes #39
This commit is contained in:
@@ -12,6 +12,11 @@ SECRET=
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
|
||||
# Cookie Security (Optional)
|
||||
# Set to 'false' if accessing over HTTP (e.g., local LAN without HTTPS)
|
||||
# When unset, auto-detects HTTPS via X-Forwarded-Proto header
|
||||
# COOKIE_SECURE=false
|
||||
|
||||
# Timezone for logs (Optional)
|
||||
TZ=America/New_York
|
||||
|
||||
|
||||
@@ -102,6 +102,11 @@ PGID=1000
|
||||
# Optional - JWT Secret (auto-generated if not provided)
|
||||
# Generate with: openssl rand -base64 32
|
||||
SECRET=
|
||||
|
||||
# Optional - Cookie Security
|
||||
# Set to 'false' for HTTP access (e.g., local LAN without HTTPS)
|
||||
# When unset, auto-detects HTTPS via X-Forwarded-Proto header
|
||||
COOKIE_SECURE=false
|
||||
```
|
||||
|
||||
### User Permissions (PUID/PGID)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { generateAccessToken, generateRefreshToken, validateAdminCredentials } from '@/lib/auth/utils';
|
||||
import { generateAccessToken, generateRefreshToken, validateAdminCredentials, isSecureCookie } from '@/lib/auth/utils';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -36,7 +36,7 @@ export async function POST(request: NextRequest) {
|
||||
// Set cookies
|
||||
const cookieOptions = {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
secure: isSecureCookie(request),
|
||||
sameSite: 'lax' as const,
|
||||
path: '/',
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { generateAccessToken, generateRefreshToken, verifyRefreshToken } from '@/lib/auth/utils';
|
||||
import { generateAccessToken, generateRefreshToken, verifyRefreshToken, isSecureCookie } from '@/lib/auth/utils';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -49,7 +49,7 @@ export async function POST(request: NextRequest) {
|
||||
// Set new cookies
|
||||
const cookieOptions = {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
secure: isSecureCookie(request),
|
||||
sameSite: 'lax' as const,
|
||||
path: '/',
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { db, settings } from '@/lib/db';
|
||||
import crypto from 'crypto';
|
||||
import { isSecureCookie } from '@/lib/auth/utils';
|
||||
|
||||
// POST /api/lock - Verify password
|
||||
export async function POST(request: NextRequest) {
|
||||
@@ -41,12 +42,11 @@ export async function POST(request: NextRequest) {
|
||||
message: 'Password verified',
|
||||
});
|
||||
|
||||
// Set an unlock cookie that expires in 24 hours
|
||||
response.cookies.set('site_unlocked', 'true', {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
secure: isSecureCookie(request),
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24, // 24 hours
|
||||
maxAge: 60 * 60 * 24,
|
||||
path: '/',
|
||||
});
|
||||
|
||||
|
||||
@@ -128,3 +128,16 @@ export function validateAdminCredentials(username: string, password: string): bo
|
||||
|
||||
return username === adminUsername && password === adminPassword;
|
||||
}
|
||||
|
||||
export function isSecureCookie(request: { headers: { get(name: string): string | null }; url: string }): boolean {
|
||||
if (process.env.COOKIE_SECURE !== undefined) {
|
||||
return process.env.COOKIE_SECURE === 'true';
|
||||
}
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return (
|
||||
request.headers.get('x-forwarded-proto') === 'https' ||
|
||||
request.url.startsWith('https://')
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user