From b1c93512dfccc746aa987293fd48602910f9c0a4 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sun, 2 Nov 2025 14:04:38 -0500 Subject: [PATCH] feat: support custom cookie options --- CHANGELOG.md | 1 + app/server/config/schema.ts | 4 ++++ app/server/index.ts | 4 ++-- app/server/web/sessions.ts | 3 ++- config.example.yaml | 8 ++++++++ 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b0f6ad..3e3bed7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Allow conditionally compiling the SSH WASM integration when building (closes [#337](https://github.com/tale/headplane/issues/337)). - Implemented the ability to customize the build with a custom script (see `./build.sh --help` for more information). - Attempt to warn against misconfigured cookie settings on the login page. +- Made `server.cookie_max_age` and `server.cookie_domain` configurable (closes [#348](https://github.com/tale/headplane/issues/348)). --- diff --git a/app/server/config/schema.ts b/app/server/config/schema.ts index 7c08629..a3648c5 100644 --- a/app/server/config/schema.ts +++ b/app/server/config/schema.ts @@ -23,6 +23,8 @@ const serverConfig = type({ cookie_secret: '(32 <= string <= 32)?', cookie_secret_path: 'string?', cookie_secure: stringToBool, + cookie_domain: 'string?', + cookie_max_age: 'number.integer = 86400', }) .narrow((obj: Record, ctx: any) => { const hasVal = obj.cookie_secret != null && `${obj.cookie_secret}` !== ''; @@ -49,6 +51,8 @@ const partialServerConfig = type({ cookie_secret: '32 <= string <= 32?', cookie_secret_path: 'string?', cookie_secure: stringToBool.optional(), + cookie_domain: 'string?', + cookie_max_age: 'number.integer?', }); const oidcConfig = type({ diff --git a/app/server/index.ts b/app/server/index.ts index f9b49ff..cc681ec 100644 --- a/app/server/index.ts +++ b/app/server/index.ts @@ -62,8 +62,8 @@ const appLoadContext = { cookie: { name: '_hp_auth', secure: config.server.cookie_secure, - maxAge: 60 * 60 * 24, // 24 hours - // domain: config.server.cookie_domain, + maxAge: config.server.cookie_max_age, + domain: config.server.cookie_domain, }, }), diff --git a/app/server/web/sessions.ts b/app/server/web/sessions.ts index 231b0eb..5b0500a 100644 --- a/app/server/web/sessions.ts +++ b/app/server/web/sessions.ts @@ -152,13 +152,14 @@ class Sessionizer { } async function createSession(payload: JWTSession, options: AuthSessionOptions) { + const now = Math.floor(Date.now() / 1000); const secret = createHash('sha256').update(options.secret, 'utf8').digest(); const jwt = await new EncryptJWT({ ...payload, }) .setProtectedHeader({ alg: 'dir', enc: 'A256GCM', typ: 'JWT' }) .setIssuedAt() - .setExpirationTime('1d') + .setExpirationTime(now + options.cookie.maxAge) .setIssuer('urn:tale:headplane') .setAudience('urn:tale:headplane') .setJti(ulid()) diff --git a/config.example.yaml b/config.example.yaml index 9d4b1e6..1439d91 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -12,6 +12,14 @@ server: # (I recommend this is true in production) cookie_secure: true + # The maximum age of the session cookie in seconds + cookie_max_age: 86400 # 1 day in seconds + + # This is not required, but if you want to restrict the cookie + # to a specific domain, set it here. Otherwise leave it commented out. + # This may not work as expected if not using a reverse proxy. + # cookie_domain: "" + # The path to persist Headplane specific data. All data going forward # is stored in this directory, including the internal database and # any cache related files.