Tighten §3 comments (todo §3); drop stale 'next §3 item' forward-refs, condense compose/Ory/bootstrap headers
This commit is contained in:
35
compose.yml
35
compose.yml
@@ -10,9 +10,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
CACHE_TEMPLATES: "true"
|
CACHE_TEMPLATES: "true"
|
||||||
REQUIRE_SECURE_SECRETS: "true"
|
REQUIRE_SECURE_SECRETS: "true"
|
||||||
# Wait for the identity/permission services the app talks to (config.ts: kratos + keto)
|
# Wait for the services config.ts talks to (kratos + keto) + the one-shot bootstrap
|
||||||
# and for the one-shot bootstrap to seed the admin + JWKS. Hydra is post-MVP (§6) and
|
# (admin + JWKS seed). Hydra is post-MVP (§6), not in config.ts, so web skips it.
|
||||||
# absent from config.ts, so web doesn't gate on it.
|
|
||||||
depends_on:
|
depends_on:
|
||||||
bootstrap:
|
bootstrap:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
@@ -20,8 +19,8 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
keto:
|
keto:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
# Read the session-JWT verify key from the same tokenizer JWKS Kratos signs with
|
# §4 verifier reads the same tokenizer JWKS Kratos signs with (config.ts JWKS_URL).
|
||||||
# (config.ts JWKS_URL default; §4 verifier). Read-only — bootstrap is the only writer.
|
# Read-only — bootstrap is the only writer.
|
||||||
volumes:
|
volumes:
|
||||||
- ./ory/kratos/tokenizer:/etc/config/kratos/tokenizer:ro
|
- ./ory/kratos/tokenizer:/etc/config/kratos/tokenizer:ro
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -45,8 +44,8 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# Ory Kratos — identity & self-service auth. Config + identity schema in ory/kratos/.
|
# Ory Kratos — identity & self-service auth. Config + schema in ory/kratos/; DSN is
|
||||||
# DSN is the per-service `kratos` DB (init.sql); supply POSTGRES_* via env in prod.
|
# its own `kratos` DB (init.sql), POSTGRES_* via env in prod.
|
||||||
kratos-migrate:
|
kratos-migrate:
|
||||||
image: oryd/kratos:v26.2.0
|
image: oryd/kratos:v26.2.0
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -76,8 +75,8 @@ services:
|
|||||||
retries: 20
|
retries: 20
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# Ory Keto — authorization (ReBAC). Permission model in ory/keto/namespaces.keto.ts (OPL).
|
# Ory Keto — authorization (ReBAC); OPL model in ory/keto/namespaces.keto.ts. DSN is
|
||||||
# DSN is the per-service `keto` DB (init.sql). The web app calls its read/write APIs (config.ts).
|
# its own `keto` DB (init.sql). web calls its read/write APIs (config.ts).
|
||||||
keto-migrate:
|
keto-migrate:
|
||||||
image: oryd/keto:v26.2.0
|
image: oryd/keto:v26.2.0
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -107,11 +106,9 @@ services:
|
|||||||
retries: 20
|
retries: 20
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# One-command bootstrap (§3, the MVP bar): a one-shot that seeds first-boot state, then
|
# One-shot first-boot seed (§3, the MVP bar); see src/bootstrap.ts. Idempotent, re-runs
|
||||||
# exits — generate the JWKS if absent, create the demo admin (admin@plainpages.local /
|
# cleanly. Runs once kratos+keto are healthy; web waits for it. Tokenizer dir mounted
|
||||||
# admin) in Kratos, grant it the `admin` role in Keto. Idempotent, so it re-runs cleanly.
|
# read-write (the only writer) so the absent-JWKS safety net can land the key.
|
||||||
# Runs once kratos+keto are healthy; web waits for it to complete. Tokenizer dir is
|
|
||||||
# mounted read-write (the only writer) so the absent-JWKS safety net can land the key.
|
|
||||||
bootstrap:
|
bootstrap:
|
||||||
build: .
|
build: .
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -129,15 +126,13 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./ory/kratos/tokenizer:/etc/config/kratos/tokenizer
|
- ./ory/kratos/tokenizer:/etc/config/kratos/tokenizer
|
||||||
command: node src/bootstrap.ts
|
command: node src/bootstrap.ts
|
||||||
# Bounded retry: the seed is idempotent (409-create + idempotent PUT), so transient Ory
|
# Bounded retry: the seed is idempotent, so transient Ory blips recover — but a permanent
|
||||||
# blips recover — but a permanent error must give up, not loop forever and hang `web`
|
# error must give up, not loop forever and hang `web` (gates on completion).
|
||||||
# (which gates on service_completed_successfully).
|
|
||||||
restart: "on-failure:5"
|
restart: "on-failure:5"
|
||||||
|
|
||||||
# Ory Hydra — OAuth2/OIDC provider (other apps log in *through* plainpages; README).
|
# Ory Hydra — OAuth2/OIDC provider (other apps log in *through* plainpages; README).
|
||||||
# DSN is the per-service `hydra` DB (init.sql). Issuer + login/consent/logout run at
|
# DSN is its own `hydra` DB (init.sql); config in ory/hydra/hydra.yml, handlers are §6.
|
||||||
# our app routes (ory/hydra/hydra.yml); the handlers that drive them are §6. Dev
|
# Dev permits the http issuer via --dev (compose.override.yml); prod sets an https
|
||||||
# permits the http issuer via --dev (compose.override.yml); prod supplies an https
|
|
||||||
# issuer via env (URLS_SELF_ISSUER).
|
# issuer via env (URLS_SELF_ISSUER).
|
||||||
hydra-migrate:
|
hydra-migrate:
|
||||||
image: oryd/hydra:v26.2.0
|
image: oryd/hydra:v26.2.0
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Ory Kratos — identity & self-service auth. Identity schema (email, name) +
|
# Ory Kratos — identity & self-service auth. Identity schema (email, name) +
|
||||||
# password login; recovery & verification run on email codes. Every self-service
|
# password login; recovery & verification run on email codes. Every self-service
|
||||||
# flow returns the browser to our own themed routes (§4 renders the fields). DSN +
|
# flow returns to our own themed routes (§4 renders the fields). DSN + prod
|
||||||
# prod courier/secrets come from the env. The session→JWT tokenizer is wired below;
|
# courier/secrets come from the env. Session→JWT tokenizer wired below (signing
|
||||||
# its JWKS signing key is generated/mounted by the next §3 item.
|
# key in tokenizer/jwks.json).
|
||||||
serve:
|
serve:
|
||||||
public:
|
public:
|
||||||
base_url: http://127.0.0.1:4433/
|
base_url: http://127.0.0.1:4433/
|
||||||
@@ -20,11 +20,9 @@ selfservice:
|
|||||||
enabled: true
|
enabled: true
|
||||||
code: # email one-time code — powers recovery + verification (not login)
|
code: # email one-time code — powers recovery + verification (not login)
|
||||||
enabled: true
|
enabled: true
|
||||||
# Social sign-in (Google, Microsoft, or SAML via an OIDC bridge like Ory Polis —
|
# Social sign-in, OFF by default → clean clone is password-only. Activate via env only
|
||||||
# OSS Kratos has no native SAML). OFF by default → a clean clone is password-only.
|
# (no code; the whole-array form is the only env-settable one Kratos offers); §4 derives
|
||||||
# Activate WITHOUT code changes by supplying env (the whole-array form is the only
|
# the buttons from this list. SAML isn't in OSS Kratos — bridge it as OIDC (README).
|
||||||
# env-settable one Kratos offers); providers reference the committed claims mapper,
|
|
||||||
# and §4 derives the buttons from this list:
|
|
||||||
# SELFSERVICE_METHODS_OIDC_ENABLED=true
|
# SELFSERVICE_METHODS_OIDC_ENABLED=true
|
||||||
# SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS=[{"id":"google","provider":"google",
|
# SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS=[{"id":"google","provider":"google",
|
||||||
# "client_id":"…","client_secret":"…","scope":["openid","email","profile"],
|
# "client_id":"…","client_secret":"…","scope":["openid","email","profile"],
|
||||||
@@ -90,7 +88,7 @@ session:
|
|||||||
# Session→JWT tokenizer (§4): whoami(tokenize_as: plainpages) mints a short-lived,
|
# Session→JWT tokenizer (§4): whoami(tokenize_as: plainpages) mints a short-lived,
|
||||||
# locally-verifiable JWT so the hot path never calls Ory. Claims come from the
|
# locally-verifiable JWT so the hot path never calls Ory. Claims come from the
|
||||||
# committed Jsonnet mapper (sub = identity id, email from traits, roles from the
|
# committed Jsonnet mapper (sub = identity id, email from traits, roles from the
|
||||||
# metadata_admin projection); signed with the JWKS the next §3 item generates/mounts.
|
# metadata_admin projection); signed with tokenizer/jwks.json.
|
||||||
whoami:
|
whoami:
|
||||||
tokenizer:
|
tokenizer:
|
||||||
templates:
|
templates:
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
// One-command bootstrap (todo §3, the MVP bar). Runs as the one-shot `bootstrap` compose
|
// One-command bootstrap (todo §3, the MVP bar). One-shot compose service: runs after
|
||||||
// service after kratos+keto are healthy; `web` waits for it to finish. Idempotent — safe
|
// kratos+keto are healthy (web waits on it), idempotent on every `docker compose up`:
|
||||||
// to re-run on every `docker compose up`:
|
|
||||||
// 1. generate the JWKS signing key if absent (committed dev key makes this a safety net);
|
// 1. generate the JWKS signing key if absent (committed dev key makes this a safety net);
|
||||||
// 2. seed a demo admin identity (admin@plainpages.local / admin) in Kratos;
|
// 2. seed a demo admin (admin@plainpages.local / admin) in Kratos;
|
||||||
// 3. grant it the `admin` role in Keto so menu/permission checks resolve out of the box.
|
// 3. grant it the `admin` role in Keto so menu/permission checks resolve out of the box.
|
||||||
// On finish it prints a first-run banner (login URL + creds + change-before-prod warning).
|
// Then prints a first-run banner; fails loud on any unexpected upstream error.
|
||||||
// Fails loud on any unexpected upstream error.
|
|
||||||
import { existsSync, writeFileSync } from "node:fs";
|
import { existsSync, writeFileSync } from "node:fs";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { generateJwks, type JwkSet } from "./gen-jwks.ts";
|
import { generateJwks, type JwkSet } from "./gen-jwks.ts";
|
||||||
|
|||||||
@@ -68,10 +68,9 @@ export function loadConfig(env: Env = process.env): Config {
|
|||||||
cacheTemplates: readBool(env, "CACHE_TEMPLATES", false),
|
cacheTemplates: readBool(env, "CACHE_TEMPLATES", false),
|
||||||
cookieSecret: readSecret(env, "COOKIE_SECRET", "dev-insecure-cookie-secret", requireSecure),
|
cookieSecret: readSecret(env, "COOKIE_SECRET", "dev-insecure-cookie-secret", requireSecure),
|
||||||
csrfSecret: readSecret(env, "CSRF_SECRET", "dev-insecure-csrf-secret", requireSecure),
|
csrfSecret: readSecret(env, "CSRF_SECRET", "dev-insecure-csrf-secret", requireSecure),
|
||||||
// The session JWT is signed by the Kratos tokenizer key (kratos.yml jwks_url); the §4
|
// §4 verifier reads the same key the Kratos tokenizer signs with (kratos.yml jwks_url).
|
||||||
// verifier reads that same key. Kratos does not republish it over HTTP, so default to a
|
// Kratos doesn't republish it over HTTP, so default to a file:// of the tokenizer JWKS
|
||||||
// file:// of the tokenizer JWKS mounted into the web container (compose.yml) — not a
|
// mounted into web (compose.yml). Prod overrides with a real key (README: rotation).
|
||||||
// well-known endpoint. Prod overrides with a real key (README: JWT signing key & rotation).
|
|
||||||
jwksUrl: readUrl(env, "JWKS_URL", "file:///etc/config/kratos/tokenizer/jwks.json"),
|
jwksUrl: readUrl(env, "JWKS_URL", "file:///etc/config/kratos/tokenizer/jwks.json"),
|
||||||
ketoReadUrl: readUrl(env, "KETO_READ_URL", "http://keto:4466"),
|
ketoReadUrl: readUrl(env, "KETO_READ_URL", "http://keto:4466"),
|
||||||
ketoWriteUrl: readUrl(env, "KETO_WRITE_URL", "http://keto:4467"),
|
ketoWriteUrl: readUrl(env, "KETO_WRITE_URL", "http://keto:4467"),
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { generateKeyPairSync, randomUUID } from "node:crypto";
|
import { generateKeyPairSync, randomUUID } from "node:crypto";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
// ES256 signing JWKS for the Kratos session tokenizer (§3). Ory recommends ES* over the
|
// ES256 signing JWKS for the Kratos session tokenizer (§3) — Ory-recommended and the
|
||||||
// symmetric HS family; ES256 is also our verifier's preferred alg (src/jwt.ts). Kratos
|
// verifier's preferred alg (src/jwt.ts). Rotation runbook: README, JWT signing key.
|
||||||
// signs with the FIRST key in the set and the app verifies by `kid` (§4) — so rotation is
|
// (Re)generate the committed dev key (prod supplies its own):
|
||||||
// prepend a fresh key, keep the old one ~one TTL (10m) for in-flight tokens, then drop it.
|
// docker compose run --rm -T --no-deps web node src/gen-jwks.ts > ory/kratos/tokenizer/jwks.json
|
||||||
// (Re)generate the committed dev key (prod supplies its own — see README):
|
|
||||||
// docker compose run --rm -T web node src/gen-jwks.ts > ory/kratos/tokenizer/jwks.json
|
|
||||||
|
|
||||||
export interface SigningJwk {
|
export interface SigningJwk {
|
||||||
kid: string;
|
kid: string;
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ test("session settings: branded cookie, bounded lifespan, sliding refresh", () =
|
|||||||
|
|
||||||
test("session tokenizer template 'plainpages' mints a short-lived signed JWT", () => {
|
test("session tokenizer template 'plainpages' mints a short-lived signed JWT", () => {
|
||||||
// whoami(tokenize_as: plainpages) → a locally-verifiable JWT, so the hot path never
|
// whoami(tokenize_as: plainpages) → a locally-verifiable JWT, so the hot path never
|
||||||
// calls Ory (§4). The JWKS signer is generated/mounted by the next §3 item.
|
// calls Ory (§4). Signed with the committed tokenizer/jwks.json (gen-jwks.ts).
|
||||||
assert.match(kratosYml, /tokenizer:\s*\n\s*templates:\s*\n\s*plainpages:/, "plainpages template defined");
|
assert.match(kratosYml, /tokenizer:\s*\n\s*templates:\s*\n\s*plainpages:/, "plainpages template defined");
|
||||||
assert.match(kratosYml, /ttl:\s*10m/, "~10m TTL — re-minted on refresh");
|
assert.match(kratosYml, /ttl:\s*10m/, "~10m TTL — re-minted on refresh");
|
||||||
assert.match(kratosYml, /subject_source:\s*id/, "sub = the Kratos identity id");
|
assert.match(kratosYml, /subject_source:\s*id/, "sub = the Kratos identity id");
|
||||||
|
|||||||
2
todo.md
2
todo.md
@@ -71,7 +71,7 @@ everything via Docker.
|
|||||||
- [x] First-run banner / log line printing the login URL + seeded admin creds, with a clear "change these before production" warning. → `firstRunBanner()` in `src/bootstrap.ts` (pure, testable) renders a boxed banner — login URL · seeded email/password · "⚠ change before production" — that `main()` prints after seeding. Login URL from `APP_URL` (compose default `http://localhost:3000`, overridable per deployment); creds reuse the seeded `ADMIN_EMAIL`/`ADMIN_PASSWORD`. Tests-first (`bootstrap.test.ts`: asserts URL + creds + warning present); README **Development** notes the banner. Live-verified: rebuilt bootstrap prints the banner after the admin line; typecheck + 152 units green; stack torn down.
|
- [x] First-run banner / log line printing the login URL + seeded admin creds, with a clear "change these before production" warning. → `firstRunBanner()` in `src/bootstrap.ts` (pure, testable) renders a boxed banner — login URL · seeded email/password · "⚠ change before production" — that `main()` prints after seeding. Login URL from `APP_URL` (compose default `http://localhost:3000`, overridable per deployment); creds reuse the seeded `ADMIN_EMAIL`/`ADMIN_PASSWORD`. Tests-first (`bootstrap.test.ts`: asserts URL + creds + warning present); README **Development** notes the banner. Live-verified: rebuilt bootstrap prints the banner after the admin line; typecheck + 152 units green; stack torn down.
|
||||||
- [x] Document the *only* things that can't be auto-generated: third-party **SSO provider** client id/secret (optional — password login works without them) and **production secrets** (real cookie/CSRF secret + signing key, supplied via env, replacing the dev throwaways). Everything else must work from a clean clone. → New README **What you must supply (the only manual prep)** subsection (under Configuration) consolidates the previously-scattered facts into one authoritative list: a clean clone needs nothing; exactly two production-only things can't be auto-generated — (1) production secrets (`COOKIE_SECRET`/`CSRF_SECRET` + the JWT signing key, with `REQUIRE_SECURE_SECRETS=true` refusing throwaways) and (2) optional SSO provider creds (no creds ⇒ no button). States everything else (Ory migrations, dev signing key, demo admin + Keto roles, OPL model) is generated/seeded on first boot. Cross-links the existing SSO + JWT-rotation subsections (no duplication) and adds a pointer from **Production / deployment**. All four anchors verified; docs-only — typecheck + 152 units green.
|
- [x] Document the *only* things that can't be auto-generated: third-party **SSO provider** client id/secret (optional — password login works without them) and **production secrets** (real cookie/CSRF secret + signing key, supplied via env, replacing the dev throwaways). Everything else must work from a clean clone. → New README **What you must supply (the only manual prep)** subsection (under Configuration) consolidates the previously-scattered facts into one authoritative list: a clean clone needs nothing; exactly two production-only things can't be auto-generated — (1) production secrets (`COOKIE_SECRET`/`CSRF_SECRET` + the JWT signing key, with `REQUIRE_SECURE_SECRETS=true` refusing throwaways) and (2) optional SSO provider creds (no creds ⇒ no button). States everything else (Ory migrations, dev signing key, demo admin + Keto roles, OPL model) is generated/seeded on first boot. Cross-links the existing SSO + JWT-rotation subsections (no duplication) and adds a pointer from **Production / deployment**. All four anchors verified; docs-only — typecheck + 152 units green.
|
||||||
- [x] Run the architecture _and_ the stability reviewer agents on the _whole_ project, not just the latest changes, and address their issues. → Ran both on the whole project (weighted to the §3 Ory stack). Verdict: architecture sound + disciplined, no Critical; both independently flagged the *same* top issue. **Fixed now:** (1) HIGH (both agents) — `JWKS_URL` default was `http://kratos:4433/.well-known/jwks.json`, but Kratos does **not** republish the session-tokenizer key there (no OIDC discovery on Kratos — that's Hydra), so the §4 verifier would have fetched the wrong/empty set and *no one* could be authorized. Repointed the default to `file:///etc/config/kratos/tokenizer/jwks.json` — the exact key Kratos signs with (`kratos.yml` `jwks_url`) — and mounted that tokenizer dir **read-only into `web`** (`compose.yml`) so the verifier resolves the live key in dev *and* prod (same file bootstrap regenerates). `config.test.ts` now locks the default to the tokenizer file + asserts the committed key is a real ES256 JWKS carrying a `kid` (the regression the old `/jwks/` match missed). (2) MEDIUM (stability) — `bootstrap` had uncapped `restart: on-failure`; a *permanent* seed error would loop forever and silently hang `web` (gates on `service_completed_successfully`). Capped to `on-failure:5` (seed is idempotent — 409-create + idempotent PUT — so transient Ory blips still recover, permanent ones give up loud). (3) §3's new `web` `depends_on` made the documented `docker compose run --rm web …` typecheck/test/gen-jwks commands drag up the whole Ory stack — added `--no-deps` (README + AGENTS.md). **Deferred (reviewer-scoped, not §3):** extract `buildShellContext` out of `dashboard.ts` + route built-in screens through `matchRoute`/`isAuthorized` → §5 (forcing function arrives with the 2nd/3rd screen); seed the demo admin's `metadata_admin.roles` projection so first login is non-empty → §4 (the login-completion projection owns it); enforce Ory `*.yml` prod secrets + self-service return-URLs via env → §9 (ops). typecheck + 153 units green; both compose files validated.
|
- [x] Run the architecture _and_ the stability reviewer agents on the _whole_ project, not just the latest changes, and address their issues. → Ran both on the whole project (weighted to the §3 Ory stack). Verdict: architecture sound + disciplined, no Critical; both independently flagged the *same* top issue. **Fixed now:** (1) HIGH (both agents) — `JWKS_URL` default was `http://kratos:4433/.well-known/jwks.json`, but Kratos does **not** republish the session-tokenizer key there (no OIDC discovery on Kratos — that's Hydra), so the §4 verifier would have fetched the wrong/empty set and *no one* could be authorized. Repointed the default to `file:///etc/config/kratos/tokenizer/jwks.json` — the exact key Kratos signs with (`kratos.yml` `jwks_url`) — and mounted that tokenizer dir **read-only into `web`** (`compose.yml`) so the verifier resolves the live key in dev *and* prod (same file bootstrap regenerates). `config.test.ts` now locks the default to the tokenizer file + asserts the committed key is a real ES256 JWKS carrying a `kid` (the regression the old `/jwks/` match missed). (2) MEDIUM (stability) — `bootstrap` had uncapped `restart: on-failure`; a *permanent* seed error would loop forever and silently hang `web` (gates on `service_completed_successfully`). Capped to `on-failure:5` (seed is idempotent — 409-create + idempotent PUT — so transient Ory blips still recover, permanent ones give up loud). (3) §3's new `web` `depends_on` made the documented `docker compose run --rm web …` typecheck/test/gen-jwks commands drag up the whole Ory stack — added `--no-deps` (README + AGENTS.md). **Deferred (reviewer-scoped, not §3):** extract `buildShellContext` out of `dashboard.ts` + route built-in screens through `matchRoute`/`isAuthorized` → §5 (forcing function arrives with the 2nd/3rd screen); seed the demo admin's `metadata_admin.roles` projection so first login is non-empty → §4 (the login-completion projection owns it); enforce Ory `*.yml` prod secrets + self-service return-URLs via env → §9 (ops). typecheck + 153 units green; both compose files validated.
|
||||||
- [ ] Go over all comments in the code and the README and try to make it shorter and more information dense. Remove not strictly needed stuff.
|
- [x] Go over all comments in the code and the README and try to make it shorter and more information dense. Remove not strictly needed stuff. → Pass over the §3 Ory accretion. Killed the now-stale "the next §3 item generates/mounts" forward-refs (the JWKS shipped) in `kratos.yml` (×2) + `kratos.test.ts`. Tightened the verbose service/header blocks in `compose.yml` (web depends_on/JWKS-mount, the three Ory headers, the bootstrap block) and the `bootstrap.ts`/`gen-jwks.ts` module headers — dropping prose the README/`src/bootstrap.ts` already carry, keeping the security/stability rationale (read-only mount, bounded retry). Trimmed `config.ts`'s JWKS comment and the `kratos.yml` SSO block (kept the concrete env example), and aligned the `gen-jwks.ts` command with the README's `--no-deps`. Net −12 lines; typecheck + 153 units green. The §3 README sections (Development / What you must supply / SSO / JWT rotation) were already authored concise in §3 (todo lines 70–72) and left intact.
|
||||||
- [ ] Go over all tests and combine/unify ones that cover the same stuff or are very related and could be combined in a good way. Remove tests that aren't helping, we only want tests that are actually helpful to us.
|
- [ ] Go over all tests and combine/unify ones that cover the same stuff or are very related and could be combined in a good way. Remove tests that aren't helping, we only want tests that are actually helpful to us.
|
||||||
|
|
||||||
## 4. Auth — identity, session JWT, guards
|
## 4. Auth — identity, session JWT, guards
|
||||||
|
|||||||
Reference in New Issue
Block a user