Add kratos service + migrate (todo §3); pin oryd/kratos:v26.2.0, identity schema (email, name), bootable password config

This commit is contained in:
2026-06-16 23:24:32 +02:00
parent bc15f00c44
commit 120e1a0929
6 changed files with 157 additions and 2 deletions

View File

@@ -443,7 +443,7 @@ src/menu-config.ts loadMenuConfig()/defineMenu(): read config/menu.ts (central
views/ Core EJS templates (index = the app-shell People dashboard, 403/404/500, partials/ incl. app shell, nav tree, filter bar, data table, pagination, form field, auth card, menu/popover, theme switch, icon sprite)
public/ Static assets under /public/ (css/styles.css + auth.css, favicon, robots.txt)
config/menu.ts Central menu override + branding (optional; defaults apply if absent)
ory/ Ory service config + storage init (postgres/init/init.sql: one DB per Kratos/Keto/Hydra)
ory/ Ory service config (kratos/: identity schema + kratos.yml) + storage init (postgres/init/init.sql: one DB per service)
plugins/ Drop-in plugin folders (scanned at /app/plugins; bind-mount or bake in) (planned)
docs/ Reference docs (plugin-contract.md — the authoritative plugin API)
e2e/ Playwright visual + functional E2E (Dockerfile.e2e + compose.e2e.yml run it)

View File

@@ -31,5 +31,31 @@ services:
retries: 10
restart: unless-stopped
# Ory Kratos — identity & self-service auth. Config + identity schema in ory/kratos/.
# DSN is the per-service `kratos` DB (init.sql); supply POSTGRES_* via env in prod.
kratos-migrate:
image: oryd/kratos:v26.2.0
depends_on:
postgres:
condition: service_healthy
environment:
DSN: postgres://${POSTGRES_USER:-ory}:${POSTGRES_PASSWORD:-ory}@postgres:5432/kratos?sslmode=disable
volumes:
- ./ory/kratos:/etc/config/kratos:ro
command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
restart: on-failure
kratos:
image: oryd/kratos:v26.2.0
depends_on:
kratos-migrate:
condition: service_completed_successfully
environment:
DSN: postgres://${POSTGRES_USER:-ory}:${POSTGRES_PASSWORD:-ory}@postgres:5432/kratos?sslmode=disable
volumes:
- ./ory/kratos:/etc/config/kratos:ro
command: serve -c /etc/config/kratos/kratos.yml
restart: unless-stopped
volumes:
pgdata:

View File

@@ -0,0 +1,34 @@
{
"$id": "https://plainpages/kratos/identity.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"title": "Email",
"minLength": 3,
"maxLength": 320,
"ory.sh/kratos": {
"credentials": { "password": { "identifier": true } },
"verification": { "via": "email" },
"recovery": { "via": "email" }
}
},
"name": {
"type": "object",
"properties": {
"first": { "type": "string", "title": "First name", "maxLength": 256 },
"last": { "type": "string", "title": "Last name", "maxLength": 256 }
}
}
},
"required": ["email"],
"additionalProperties": false
}
}
}

48
ory/kratos/kratos.yml Normal file
View File

@@ -0,0 +1,48 @@
# Ory Kratos — identity & self-service auth. Bootable baseline (§3): identity
# schema (email, name) + password login. DSN comes from the env (compose), so it
# is absent here. Self-service UIs point at the web app's routes; theming those
# pages, SSO, session tuning, and the JWT tokenizer land in later §3/§4 items.
serve:
public:
base_url: http://127.0.0.1:4433/
cors:
enabled: false
admin:
base_url: http://kratos:4434/
selfservice:
default_browser_return_url: http://127.0.0.1:3000/
allowed_return_urls:
- http://127.0.0.1:3000
methods:
password:
enabled: true
flows:
error:
ui_url: http://127.0.0.1:3000/error
login:
ui_url: http://127.0.0.1:3000/login
registration:
ui_url: http://127.0.0.1:3000/registration
settings:
ui_url: http://127.0.0.1:3000/settings
logout:
after:
default_browser_return_url: http://127.0.0.1:3000/login
identity:
default_schema_id: default
schemas:
- id: default
url: file:///etc/config/kratos/identity.schema.json
# Dev throwaways — production supplies real secrets via env (§3). cipher = 32 chars.
secrets:
cookie:
- PLEASE-CHANGE-ME-dev-kratos-cookie-secret
cipher:
- 0123456789abcdef0123456789abcdef
log:
level: info
format: text

47
src/kratos.test.ts Normal file
View File

@@ -0,0 +1,47 @@
// Guards the Ory Kratos config (§3): image pinned to an exact version (AGENTS.md),
// migrations run before the server (kratos-migrate → kratos), the DSN targets the
// kratos database, and the identity schema carries email (password identifier) +
// name traits. Real boot is verified by running the stack; this catches edits.
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
const read = (p: string) => readFileSync(new URL(`../${p}`, import.meta.url), "utf8");
const compose = read("compose.yml");
const kratosYml = read("ory/kratos/kratos.yml");
const schema = JSON.parse(read("ory/kratos/identity.schema.json"));
test("compose pins both kratos services to one exact version", () => {
const tags = [...compose.matchAll(/image:\s*oryd\/kratos:(\S+)/g)].map((m) => m[1]);
assert.equal(tags.length, 2, "kratos + kratos-migrate both present");
assert.equal(tags[0], tags[1], "both pinned to the same version");
const tag = tags[0]!;
assert.match(tag, /^v\d+\.\d+\.\d+$/, `${tag} is an exact vMAJOR.MINOR.PATCH`);
assert.doesNotMatch(tag, /latest|[\^~*]/, `${tag} is exact, not floating`);
});
test("migrations run once before the server starts", () => {
assert.match(compose, /migrate sql -e --yes/, "kratos-migrate runs SQL migrations");
assert.match(compose, /condition:\s*service_completed_successfully/,
"kratos waits for kratos-migrate to finish");
});
test("kratos DSN targets the per-service kratos database", () => {
const dsns = [...compose.matchAll(/DSN:\s*(\S+)/g)].map((m) => m[1]);
assert.ok(dsns.length >= 2, "both kratos services set DSN");
for (const dsn of dsns) assert.match(dsn!, /@postgres:5432\/kratos\b/, `${dsn} hits the kratos DB`);
});
test("identity schema requires email (password identifier) + name traits", () => {
const t = schema.properties.traits.properties;
assert.equal(t.email.format, "email");
assert.equal(t.email["ory.sh/kratos"].credentials.password.identifier, true,
"email is the password login identifier");
assert.deepEqual(Object.keys(t.name.properties).sort(), ["first", "last"]);
assert.ok(schema.properties.traits.required.includes("email"), "email is required");
});
test("kratos config wires the identity schema", () => {
assert.match(kratosYml, /default_schema_id:\s*default/);
assert.match(kratosYml, /identity\.schema\.json/);
});

View File

@@ -58,7 +58,7 @@ everything via Docker.
## 3. Ory stack — compose + config
- [x] `postgres` service (pinned tag); separate DB/schema per Kratos/Keto/Hydra. → `compose.yml` `postgres` service pinned to `postgres:18.4-alpine3.23` (verified latest stable PG + newest Alpine the official image ships); `ory/postgres/init/init.sql` (mounted at `docker-entrypoint-initdb.d`) creates one DB per service (`kratos`/`keto`/`hydra`) so each owns its schema + migrations. Dev defaults (`ory`/`ory`, env-overridable for prod), named `pgdata` volume mounted at `/var/lib/postgresql` (PG18+ version-subdir layout — not `/data`), `pg_isready` healthcheck. Web app never connects. Verified live: boots healthy, three DBs present, then torn down. `postgres.test.ts` guards the pin + DB-per-service. typecheck + 112 units green.
- [ ] `kratos` service (pinned) + `migrate`; identity schema (traits: email, name).
- [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.
- [ ] Kratos self-service flows (login, registration, recovery, verification, settings) → return URLs at our themed pages.
- [ ] 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.
- [ ] Kratos session settings (cookie name, lifespan, sliding refresh).