diff --git a/frontend/app/api/auth/login/route.ts b/frontend/app/api/auth/login/route.ts index e0ee425..41d0e2d 100644 --- a/frontend/app/api/auth/login/route.ts +++ b/frontend/app/api/auth/login/route.ts @@ -86,6 +86,13 @@ export async function POST(request: Request) { } clearAttempts(ip) - await createSession({ userId: user.id, username: user.username, role: user.role }) + + // Detect whether the client is on HTTPS (via reverse-proxy header or direct TLS). + // CasaOS and most self-hosted setups run over plain HTTP, so the cookie must + // NOT be marked Secure or the browser will silently drop it. + const proto = request.headers.get('x-forwarded-proto') ?? '' + const isHttps = proto.split(',')[0].trim() === 'https' + + await createSession({ userId: user.id, username: user.username, role: user.role }, isHttps) return NextResponse.json({ ok: true, username: user.username, role: user.role }) } diff --git a/frontend/lib/session.ts b/frontend/lib/session.ts index 16b2207..7915620 100644 --- a/frontend/lib/session.ts +++ b/frontend/lib/session.ts @@ -30,7 +30,7 @@ export function getSecretKey(): Uint8Array { } /** Creates and stores a session JWT in an httpOnly cookie. */ -export async function createSession(payload: SessionPayload): Promise { +export async function createSession(payload: SessionPayload, secure?: boolean): Promise { const expiresAt = new Date(Date.now() + SESSION_DURATION_MS) const token = await new SignJWT({ ...payload }) .setProtectedHeader({ alg: 'HS256' }) @@ -38,10 +38,14 @@ export async function createSession(payload: SessionPayload): Promise { .setExpirationTime(expiresAt) .sign(getSecretKey()) + // If the caller explicitly passes a value, honour it. + // Otherwise fall back to NODE_ENV (safe default for fully-HTTPS deploys). + const isSecure = secure !== undefined ? secure : process.env.NODE_ENV === 'production' + const cookieStore = await cookies() cookieStore.set(SESSION_COOKIE, token, { httpOnly: true, - secure: process.env.NODE_ENV === 'production', + secure: isSecure, sameSite: 'lax', expires: expiresAt, path: '/',