mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
The setup wizard and the admin sign-in set their session cookies by two different rules. The wizard matches Secure to the request - X-Forwarded- Proto from the proxy, else the URL scheme - because browsers drop Secure cookies on plain HTTP and an unconditional Secure broke setup over HTTP. The admin cookie kept `secure: NODE_ENV === 'production'`, so on the same HTTP deployment the wizard worked and admin sign-in failed silently: the cookie was set, the browser threw it away, every call came back 401. Both now share one helper, lib/security/request-protocol.ts, and the admin route passes its request in. Behind a TLS proxy that forwards the proto nothing changes: the cookie is Secure as before. Callers that pass no request keep the production default.
41 lines
1.6 KiB
TypeScript
41 lines
1.6 KiB
TypeScript
import { cookies } from 'next/headers';
|
|
import type { NextRequest } from 'next/server';
|
|
import { verifySetupToken } from './token';
|
|
import { isHttpsRequest } from '@/lib/security/request-protocol';
|
|
|
|
export const SETUP_COOKIE = 'bulwark_setup_token';
|
|
const COOKIE_MAX_AGE = 60 * 60; // 1 hour, matches token TTL
|
|
|
|
/**
|
|
* The wizard "session" is just the setup token itself, set as an HttpOnly
|
|
* cookie after the operator pastes it into step 1. Subsequent step calls
|
|
* re-verify the cookie value against the .setup-token file. When the wizard
|
|
* finishes, the token file is deleted and any cookies become useless.
|
|
*
|
|
* No JWT, no separate signing key, no rotating session id. The lifecycle of
|
|
* the wizard maps 1:1 to the lifecycle of the token file.
|
|
*/
|
|
|
|
export async function authenticateWizardRequest(): Promise<boolean> {
|
|
const jar = await cookies();
|
|
const token = jar.get(SETUP_COOKIE)?.value;
|
|
if (!token) return false;
|
|
return verifySetupToken(token);
|
|
}
|
|
|
|
export function buildSessionCookieAttributes(request?: NextRequest) {
|
|
// Match Secure to the actual request protocol. Browsers drop Secure cookies
|
|
// on plain HTTP, so unconditionally setting Secure in production breaks
|
|
// setup over HTTP - the operator gets "Wizard session required" on every
|
|
// step. The wizard surfaces a cleartext-credentials warning in the UI when
|
|
// HTTPS isn't in use.
|
|
return {
|
|
name: SETUP_COOKIE,
|
|
httpOnly: true,
|
|
sameSite: 'lax' as const,
|
|
secure: request ? isHttpsRequest(request) : process.env.NODE_ENV === 'production',
|
|
path: '/',
|
|
maxAge: COOKIE_MAX_AGE,
|
|
};
|
|
}
|