From 0313f481123eaa9d52cfc891c1fbe0bfe8a41020 Mon Sep 17 00:00:00 2001 From: lilleman Date: Wed, 17 Jun 2026 11:27:56 +0200 Subject: [PATCH] =?UTF-8?q?Configure=20Kratos=20session=20settings=20(todo?= =?UTF-8?q?=20=C2=A73);=20branded=20cookie,=20720h=20lifespan,=2024h=20sli?= =?UTF-8?q?ding-refresh=20window?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ory/kratos/kratos.yml | 12 ++++++++++++ src/kratos.test.ts | 6 ++++++ todo.md | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ory/kratos/kratos.yml b/ory/kratos/kratos.yml index b419238..5d18bc7 100644 --- a/ory/kratos/kratos.yml +++ b/ory/kratos/kratos.yml @@ -76,6 +76,18 @@ identity: - id: default url: file:///etc/config/kratos/identity.schema.json +# "Stay signed in" backbone: a long-lived Kratos session that the app re-mints the +# short-lived (~10m) JWT off (§4). Sliding refresh — an active session is extended +# back to full lifespan only once it's within earliest_possible_extend of expiry, +# so frequent users never lapse without a DB write per request. +session: + lifespan: 720h # 30 days + earliest_possible_extend: 24h + cookie: + name: plainpages_session + persistent: true # survive browser restarts + same_site: Lax + # Dev throwaways — production supplies real secrets via env (§3). cipher = 32 chars. secrets: cookie: diff --git a/src/kratos.test.ts b/src/kratos.test.ts index 4639a85..4a26098 100644 --- a/src/kratos.test.ts +++ b/src/kratos.test.ts @@ -62,6 +62,12 @@ test("recovery + verification run on email code, delivered by a courier", () => assert.match(compose, /--watch-courier/, "kratos dispatches queued mail (else codes never send)"); }); +test("session settings: branded cookie, bounded lifespan, sliding refresh", () => { + assert.match(kratosYml, /name:\s*plainpages_session/, "branded session cookie name"); + assert.match(kratosYml, /lifespan:\s*720h/, "session has a bounded lifespan"); + assert.match(kratosYml, /earliest_possible_extend:\s*24h/, "sliding-refresh window is set"); +}); + test("social sign-in is off by default — a clean clone stays password-only", () => { // The oidc method ships present-but-disabled with no providers; operators activate it // purely via env (SELFSERVICE_METHODS_OIDC_*) — no code change, no baked-in creds. diff --git a/todo.md b/todo.md index 142d4a3..e1d407b 100644 --- a/todo.md +++ b/todo.md @@ -61,7 +61,7 @@ everything via Docker. - [x] `kratos` service (pinned) + `migrate`; identity schema (traits: email, name). → `compose.yml` adds `kratos`/`kratos-migrate` pinned to `oryd/kratos:v26.2.0` (verified latest stable); `kratos-migrate` runs `migrate sql -e --yes` against the per-service `kratos` DB after postgres is healthy, `kratos` waits for it (`service_completed_successfully`). `ory/kratos/identity.schema.json` = email (password identifier, verification/recovery via email) + `name {first,last}`, email required. `ory/kratos/kratos.yml` = bootable baseline: password login, self-service UIs pointing at the web routes (themed in §4), serve URLs, dev-throwaway secrets (prod via env, §3), identity schema wired; DSN via env. Themed flows/SSO/session/tokenizer/JWKS are the next §3/§4 items. Tests-first (`kratos.test.ts`: version pin + migrate-before-serve + DSN→kratos DB + schema traits + schema wiring). Boot-verified: migrate exits 0, kratos serves `/health/ready` 200, serves the identity schema, inits a password login flow; torn down. typecheck + 117 units green. - [x] Kratos self-service flows (login, registration, recovery, verification, settings) → return URLs at our themed pages. → `ory/kratos/kratos.yml`: all five flows enabled, each `ui_url` (+ after/return URLs) points at our web routes (`/login`, `/registration`, `/recovery`, `/verification`, `/settings`; §4 renders the fields). Recovery + verification run on the email `code` method (login stays password-only — `code.passwordless_enabled` left default-off); registration after-hooks `session` + `show_verification_ui`; settings gets `privileged_session_max_age` + `required_aal: highest_available`. Added a `courier` (SMTP) sending to a pinned dev mail catcher — **mailpit** (`axllent/mailpit:v1.30.1`) in `compose.override.yml`, web UI on `:8025`; prod overrides `COURIER_SMTP_CONNECTION_URI`. Kratos `serve` now runs `--watch-courier` so queued codes actually dispatch (without it they sit "queued"). Tests-first (`kratos.test.ts`: five flow ui_urls → our pages, recovery/verification use `code` + courier + `--watch-courier`, mailpit pin). Boot-verified end-to-end: all four public browser-flows 303 → `127.0.0.1:3000/?flow=…`, a registration delivered a real "Use code … to verify your account" email to mailpit (queue → `sent`); torn down. typecheck + 120 units green. - [x] Kratos OIDC/SSO providers (Google/Microsoft/SAML) config (secrets via env). **None enabled by default** — a clean clone runs password-only; a provider activates purely by supplying its env creds. → `ory/kratos/kratos.yml` adds the `oidc` method present-but-disabled with an empty `providers: []` (clean clone = password-only, boots clean). Activation is pure env, no code/rebuild: `SELFSERVICE_METHODS_OIDC_ENABLED=true` + `SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS=[…]` (the whole-array override is the only env-settable form Kratos offers — nested-field env vars aren't supported). Providers (`google`/`microsoft`/OIDC bridges) carry their `client_id`/`client_secret` and reference the committed shared claims mapper `ory/kratos/oidc/claims.jsonnet` (provider claims → `email` + `name{first,last}`). **SAML isn't in OSS Kratos** (Enterprise/Network/Polis only) — documented: front it with an OIDC bridge (Ory Polis) and register that bridge as a generic OIDC provider. README **Social sign-in (SSO)** section documents activation; §4 will derive the buttons from the live provider list. Tests-first (`kratos.test.ts`: oidc disabled + empty by default, mapper maps email/name). Boot-verified both halves: clean stack → login flow has only `default`+`password` groups; a one-off kratos with the SSO env → login flow gains an `oidc` group + a `google` button, no boot errors; torn down. typecheck + 122 units green. -- [ ] Kratos session settings (cookie name, lifespan, sliding refresh). +- [x] Kratos session settings (cookie name, lifespan, sliding refresh). → `ory/kratos/kratos.yml` adds a `session` block: branded cookie `name: plainpages_session` (`persistent: true`, `same_site: Lax`), `lifespan: 720h` (30d "stay signed in" backbone the app re-mints the ~10m JWT off, §4), and sliding refresh via `earliest_possible_extend: 24h` (an active session extends back to full lifespan only once within 24h of expiry — no DB write per request). Tests-first (`kratos.test.ts`: cookie name + lifespan + extend window). Boot-verified: kratos serves `/health/ready` 200 with the block; a real browser registration (one-off `--dev` kratos, since Secure cookies don't ride plain http — that's the line-69 split) issued `Set-Cookie: plainpages_session=…; Max-Age=2591999; Expires=…; HttpOnly; SameSite=Lax` — name/persistent/lifespan all as configured; torn down. typecheck + 123 units green. - [ ] Kratos tokenizer template `plainpages`: claims `{ sub, email, roles }`, `ttl ≈ 10m`, `jwks_url` signer, `claims_mapper_url` (Jsonnet reading `metadata_admin.roles`). - [ ] Generate + mount the JWT signing JWKS; document key rotation. - [ ] `keto` service (pinned) + `migrate`; namespaces in OPL (`role`, `group`, resource permissions).