renovate-bot
002b968cc0
fix(deps): update dependency ejs to v6
...
renovate/artifacts Artifact file update failure
CI / full-gate (push) Failing after 13s
Release-Bump: major
2026-07-29 04:18:34 +00:00
renovate-bot
194c090bd1
chore(deps): update renovate/renovate docker tag to v43.285.3
...
CI / full-gate (push) Successful in 2m37s
Mirror / github-mirror (push) Successful in 5s
Release-Bump: minor
v0.0.10
2026-07-28 04:17:59 +00:00
renovate-bot
ff5094f7e9
chore(deps): update renovate/renovate docker tag to v43.281.1
...
CI / full-gate (push) Successful in 2m33s
Mirror / github-mirror (push) Successful in 5s
Release-Bump: minor
v0.0.9
2026-07-27 04:18:12 +00:00
renovate-bot
419ee1750b
chore(deps): update renovate/renovate docker tag to v43.280.5
...
CI / full-gate (push) Successful in 2m35s
Mirror / github-mirror (push) Successful in 5s
Release-Bump: patch
v0.0.8
2026-07-26 04:18:07 +00:00
renovate-bot
cedac950be
chore(deps): update renovate/renovate docker tag to v43.280.4
...
CI / full-gate (push) Successful in 2m33s
Mirror / github-mirror (push) Successful in 4s
Release-Bump: minor
v0.0.7
2026-07-25 04:17:42 +00:00
renovate-bot
ff455f1ef2
chore(deps): update axllent/mailpit docker tag to v1.30.5
...
CI / full-gate (push) Successful in 3m59s
Mirror / github-mirror (push) Successful in 4s
Release-Bump: patch
v0.0.6
2026-07-25 01:02:53 +00:00
renovate-bot
5bd26d773d
chore(deps): update renovate/renovate docker tag to v43.270.0
...
CI / full-gate (push) Successful in 2m33s
Mirror / github-mirror (push) Successful in 2s
Release-Bump: minor
v0.0.5
2026-07-19 04:17:22 +00:00
renovate-bot
4f60bad119
chore(deps): update axllent/mailpit docker tag to v1.30.4
...
CI / full-gate (push) Successful in 2m39s
Mirror / github-mirror (push) Successful in 2s
Release-Bump: patch
v0.0.4
2026-07-18 21:13:02 +00:00
renovate-bot
aea568ea1c
chore(deps): update renovate/renovate docker tag to v43.252.1
...
CI / full-gate (push) Successful in 2m26s
Mirror / github-mirror (push) Successful in 3s
Release-Bump: minor
v0.0.3
2026-07-06 04:17:39 +00:00
lilleman
145db5b4cd
CI: auto-release a version tag when Renovate merges a dependency update
CI / full-gate (push) Successful in 2m33s
Mirror / github-mirror (push) Successful in 2s
2026-07-05 20:49:49 +02:00
renovate-bot
7c39056188
chore(deps): update node.js to v24.18.0
CI / full-gate (push) Successful in 2m28s
Mirror / github-mirror (push) Successful in 3s
2026-07-05 07:02:10 +00:00
renovate-bot
9719586f51
chore(deps): update axllent/mailpit docker tag to v1.30.3
CI / full-gate (push) Successful in 2m29s
Mirror / github-mirror (push) Successful in 3s
2026-07-05 06:56:22 +00:00
lilleman
67d8a095a5
CI: Renovate for gated, self-hosted dependency updates
CI / full-gate (push) Successful in 2m30s
Mirror / github-mirror (push) Successful in 3s
2026-07-04 21:51:04 +02:00
lilleman
476ef6fce2
README-dockerhub: clone-free quick start - self-contained compose, Ory config extracted from the image
CI / full-gate (push) Successful in 2m30s
Mirror / github-mirror (push) Successful in 0s
2026-07-04 17:45:44 +02:00
lilleman
93fa751d6d
Add README-dockerhub.md - the hand-maintained Docker Hub repo description
CI / full-gate (push) Successful in 2m29s
2026-07-04 17:37:02 +02:00
lilleman
cc886936ed
CI: sync release images to Docker Hub on vX.Y.Z tag push
CI / full-gate (push) Successful in 2m31s
Mirror / github-mirror (push) Successful in 2s
Release / retag-image (push) Successful in 14s
v0.0.2
2026-07-04 17:08:31 +02:00
lilleman
e3e582afef
README: document the one-time org-package-to-repo link for the Packages tab
CI / full-gate (push) Successful in 2m29s
Mirror / github-mirror (push) Successful in 0s
2026-07-04 09:39:10 +02:00
lilleman
0644ec8f5a
CI: nightly registry cleanup - prune hash images not release-tagged nor a branch head
CI / full-gate (push) Successful in 2m40s
Mirror / github-mirror (push) Successful in 0s
2026-07-04 09:18:07 +02:00
lilleman
058280934b
CI: re-tag the gated image as semver + latest on vX.Y.Z tag push
CI / full-gate (push) Successful in 3m36s
Mirror / github-mirror (push) Successful in 2s
Release / retag-image (push) Successful in 2s
v0.0.1
2026-07-04 08:35:48 +02:00
lilleman
50006dd1a7
CI: gate builds + pushes app image to Gitea registry tagged with the commit hash
CI / full-gate (push) Successful in 2m32s
Mirror / github-mirror (push) Successful in 0s
2026-07-03 16:50:37 +02:00
lilleman
6e60df7008
CI: mirror main + tags to GitHub after every merge to main; note true home in README
CI / full-gate (push) Successful in 2m28s
Mirror / github-mirror (push) Successful in 2s
2026-07-03 15:47:56 +02:00
lilleman
c8981c12d3
CLAUDE.md: import AGENTS.md so Claude Code loads it every session
CI / full-gate (push) Successful in 2m28s
2026-07-03 14:55:13 +02:00
lilleman
7a3161d3ff
README: document the merge gate on main (PR-only, required CI, fast-forward-only); check off todo
CI / full-gate (push) Successful in 2m33s
2026-07-03 14:50:54 +02:00
lilleman
c78770a713
AGENTS.md: document the todo.md task workflow (interview scope, stability-review loop, PR flow)
2026-07-03 14:50:54 +02:00
lilleman
94654a2adc
CI: Gitea Actions runs the full gate (ci.sh) on push to any branch except main; document runner setup
CI / full-gate (push) Failing after 0s
2026-07-02 14:24:19 +02:00
lilleman
8afaeccf2c
AGENTS.md: sharpen simplicity/typing/comment guidance, prefer state-in-URL over POST
2026-07-02 13:05:54 +02:00
lilleman
535902e69b
Split handleRequest into pipeline + internal route table; auth/OAuth2 endpoints become named handlers in src/auth/routes.ts
2026-07-02 13:05:54 +02:00
lilleman
8621cf24b6
todo: note the missing e2e for the admin plugin's OAuth2-clients (Hydra) screen
2026-07-02 09:10:55 +02:00
lilleman
e8ea911b80
Move admin screens (users/groups/roles/oauth2-clients) into a drop-in example plugin; add the ctx.system capability surface
2026-07-02 08:01:15 +02:00
lilleman
2202bdbaa0
config/ becomes an empty drop-in mount; examples/ mirrors the mount dirs; plugins & config import the host via #plugin-api/#menu-config subpath imports
2026-07-01 23:19:48 +02:00
lilleman
4adf14f386
docs: restructure README around the plugin-author path, add examples/README, genericize the plugin walkthrough
2026-06-30 23:30:13 +02:00
lilleman
fe97c3854a
Move reference plugin to examples/scheduling-plugin; plugins/ ships empty as a drop-in mount point, e2e bind-mounts the example
2026-06-27 00:02:26 +02:00
lilleman
d8cf257940
Move plugin-contract.md into README's Building plugins section; remove docs/, repoint all references
2026-06-26 23:12:40 +02:00
lilleman
2b88bf1c0d
Add todo.md: src reorg done, plus logger redundancy + i18n follow-ups
2026-06-24 00:24:52 +02:00
lilleman
de22f51c12
Organize src/ into concern folders (http, auth, admin, plugin-host, ui); co-locate tests, move plugin-api barrel into plugin-host, sync docs + AGENTS layout
2026-06-24 00:23:55 +02:00
lilleman
6d316c4888
Drop .mjs: rename e2e/dev mock servers to .ts, update refs, document the convention in AGENTS.md
2026-06-23 23:54:42 +02:00
lilleman
913bd6813a
Consolidate E2E into e2e-tests/ (Dockerfile + compose.{visual,auth,oauth,full,devstack}.yml, WORKDIR /e2e-tests); move ci.sh to repo root
2026-06-23 23:48:05 +02:00
lilleman
bb612baa2c
Reorganize README for two readers: first-run Quick start + nested ToC, overview-first ordering; document the convention in AGENTS.md
2026-06-23 23:15:24 +02:00
lilleman
a9f25a7692
Remove completed todo.md + html-css-foundation mockups; strip dead §N phase refs from comments/docs (simplify visual E2E to drop the mockup-comparison oracle)
2026-06-23 22:49:28 +02:00
lilleman
e22d24aa8a
§10 - one menu everywhere (buildPluginChrome) + shell on every page; instructional starter dashboard; Kratos-native email docs
...
Collapse the three nav builders into buildPluginChrome: chrome.bestHref does longest-prefix matching so deep admin routes mark their leaf. Delete adminNav; buildConfirmModel and the admin model builders take the resolved nav. The same role-filtered sidebar now renders signed in or out, collapsing to a burger on narrow screens.
shell.ejs gains menu (default true; menu:false -> single-column .app-bare), docTitle (separate <title> from the topbar, so the body keeps the single <h1>), and hideSignIn (suppress the footer Sign-in on auth pages to avoid a login loop). auth/home/landing now render inside the shell.
Dashboard is a replaceable instructional starter (definePlugin snippet, no mock data). Email stays delegated to Kratos: documented its built-in courier.template_override_path instead of adding web-side SMTP.
2026-06-23 21:26:00 +02:00
lilleman
af097a8885
Verification/recovery: guard the OTP code field against a pasted space (numeric inputmode + digits-only pattern + one-time-code autofill); Kratos doesn't trim, so a space-padded code was rejected as 'invalid or already used'
2026-06-21 23:26:23 +02:00
lilleman
166f38d5dd
Merge pull request 'appurl-canonical-host' ( #1 ) from appurl-canonical-host into main
...
Reviewed-on: #1
2026-06-21 22:18:46 +02:00
lilleman
c8b4c3c23b
Fix 500 on /login when a Kratos session exists but no app JWT (session_already_available)
...
Repro: register a new account, then click back from the password/verification step to /login →
500. Registration's `session` hook signs the user in at Kratos but routes to the verification UI,
not /auth/complete, so they hold a Kratos session with NO app JWT — ctx.user is null, the "already
signed in -> /dashboard" short-circuit can't fire, and initialising a login flow makes Kratos return
400 `session_already_available`. The flow-init catch only handled 403/404/410 and 5xx, so the 400
fell through to `throw` -> catch-all 500.
Recover instead: on `session_already_available` (already authenticated at Kratos), 303 to
/auth/complete to mint the JWT from the live session, preserving return_to. A genuinely unexpected
Kratos 400 still surfaces as 500. Verified live: /login (Kratos session, no JWT) now 303s to
/auth/complete, which mints plainpages_jwt and lands on /dashboard.
Also default LOG_LEVEL to debug in the dev override (compose.override.yml) for verbose local logs.
Tests-first: app.test asserts the session-race recovers to /auth/complete (return_to carried) while
an unrelated 400 stays a 500. typecheck + 361 units green.
2026-06-21 22:04:55 +02:00
lilleman
1d198acc97
Canonical host via APP_URL: stop login dumping users on /error from a host mismatch
...
The from-scratch dev login was broken: open the banner's http://localhost:3000 , sign
in as the seeded admin, and you landed on http://127.0.0.1:3000/error "Page not found".
Root cause: the banner/APP_URL said localhost but kratos.yml hard-coded 127.0.0.1, and a
host-scoped Kratos CSRF cookie can't cross localhost<->127.0.0.1, so the cross-host login
POST lost it; Kratos redirected to its error sink, which the app had no route for (404).
Make APP_URL the single source of truth for the public host:
- Canonical-host redirect (app.ts): when APP_URL is set, an off-host GET/HEAD visitor is
308'd to it (path+query kept) before a flow starts, so the browser, the themed forms and
the cross-origin Kratos POST share one cookie host. After /public/ so static/health
checks stay host-agnostic; GET/HEAD only so a 308 never replays a cross-host POST.
- Opt-in (no NODE_ENV / no magic default): unset => no redirect, so a prod deploy that
forgets APP_URL can't bounce real users to a stale default. The dev stack sets it.
- kratos.yml browser URLs default to localhost (match APP_URL's dev value) and derive from
${APP_URL} via compose.override.yml; SERVE_PUBLIC_BASE_URL keeps the dev Ory port.
- Real /error page (views/error.ejs) replaces the catch-all 404 for genuine flow errors.
Tests-first: config (opt-in/validated), app (308 on mismatch, no-redirect on match, static
host-agnostic, POST untouched, /error page), updated kratos.test host pins. New devstack
regression (e2e/devstack-login.spec + compose.e2e-devstack.yml) drives the plain
docker-compose-up topology on the host network: login from localhost works and 127.0.0.1 is
canonicalised; wired into scripts/ci.sh. typecheck + 360 units + full ci.sh (visual 10 ·
auth 1 · oauth 2 · full 7 · devstack 2) green.
2026-06-21 21:52:20 +02:00
lilleman
58398481ca
§10 review pass: address the architecture + product reviewers (todo §10); hide the gated Dashboard nav node from anonymous visitors in buildPluginChrome (a no-permission link to /dashboard only dead-ended them at /login) and dedup it into a shared DASHBOARD_NAV (admin-nav.ts, reused by chrome + adminNav). New chrome.signInHref bakes the current page in as return_to for the shell's anonymous Sign-in link (shell.ejs + reference overview.ejs), mirrored as optional ShellModel.signInHref so the typed builder is complete. ctx.chrome is now a lazy, memoized getter (context.ts chrome option = a factory) so a json/redirect handler or the public "/" with a standalone home never composes the global menu — app.ts passes the app-level memoized factory at every site. Default /dashboard prints a "Starter dashboard" note framing the mock-data home as a replaceable demo (signals its inert affordances); stale "until §4" comments fixed. RESERVED_PLUGIN_IDS drift-guard test derives the built-in segments from AUTH_FLOWS + ADMIN_*_BASE + host literals (home stays deliberately unreserved). Refreshed the stale plugin-contract status blurb and documented the chrome.*→partials/shell mapping. Reviewers: architecture + product APPROVE (no addressable findings remain), stability APPROVE (no Critical/High/Medium). typecheck + 356 units + visual(10) + full-flow(7) E2E green.
2026-06-21 01:19:40 +02:00
lilleman
7bdeb24b7f
§10 public pages + menu items, the blessed explicit alias (todo §10); a plugin may mark a page and its menu option public. A no-permission route/nav node is already anonymous-reachable, so per the human's pick this BLESSES that as a first-class, explicit choice (keep the default; add an explicit alias — not a secure-by-default flip). New optional public?: boolean on Route (src/plugin.ts) + NavNode (src/nav.ts) = "open to everyone, signed in or not", honored outright in isAuthorized (router.ts) + filterByRoles (nav.ts), and MUTUALLY EXCLUSIVE with permission — discovery shapeError recursively rejects a route/nav node setting both, failing the boot loud (never silently picks one). public is filter-only (toRenderNode never emits it). The shell (views/partials/shell.ejs) now renders a Sign in link instead of the profile/sign-out block for an anonymous visitor, so a public page in the native shell (ctx.chrome; ctx.user may be null) isn't a broken "Guest / Sign out". Reference plugin demos it: a public /scheduling Overview route + a public "Overview" nav child (the "Scheduling" header now shows for everyone), the shifts list still behind scheduling:read. Hardened the latent gap the shell newly leans on: claimsToUser rejects an empty email like it does an empty sub. Tests-first (348 → 354 units): router/nav/discovery (public open + reject-both + loads), shell (anon → Sign in, no logout form), app (public route anon-200), shifts (overview handler), jwt-middleware (empty email). Docs: plugin-contract.md ("Public pages & menu items" + route shape + shape-error note) + README (menu system + reference snippet). E2E: visual.spec asserts the public Overview is anon-200 + shown in the member's nav while the gated Shifts redirects/filters. stability-reviewer: APPROVE, no Critical/High/Medium (addressed its one Low — the empty-email hardening). typecheck + 354 units + full scripts/ci.sh gate (visual 10 · auth 1 · oauth 2 · full 7) green.
2026-06-20 18:12:46 +02:00
lilleman
7787ed4ea4
§10 split landing into a public "/" + gated "/dashboard", both plugin-replaceable (todo §10 follow-up); per human feedback, "/" is now an ungated public landing (default views/home.ejs: brand + intro + prominent Log in / Create account links, or "go to dashboard" when signed in) and "/dashboard" is the gated post-login app home (anonymous → /login?return_to=/dashboard). Both are fully replaceable via two optional RouteHandlers on PluginManifest — home? (public /) and dashboard? (gated /dashboard) — rendered against the plugin's own views with the native shell via ctx.chrome (full route parity: HEAD, void-return, response hooks, fresh CSRF cookie; a home handler is public so ctx.user may be null). Single-slot + loud: findConflicts errors on >1 owner of either slot (new "home"/"dashboard" kinds), discovery rejects a non-function handler, and "dashboard" is reserved so a plugin folder can't shadow it ("/" can't be shadowed — route paths carry the /<id> prefix). Post-login + already-signed-in redirects and the global Dashboard/People nav hrefs moved to /dashboard. Tests-first (348 units): public-/ + gated-/dashboard + dual plugin-override in app.test; per-slot conflict in plugin.test; non-function/reserved/two-owners in discovery.test. Docs: plugin-contract "The landing pages" section + README. E2E: visual.spec plants a session for /dashboard design-system tests + a cookie-free public-landing test; full-flow repointed to /dashboard. stability-reviewer: APPROVE, no Critical/High/Medium. typecheck + 348 units + visual(10) + full-flow(7) green.
2026-06-20 17:43:01 +02:00
lilleman
2eb5b84ccf
§10 gate the dashboard + make "/" replaceable by a plugin (todo §10); "/" is now gated to a signed-in session (anonymous → /login via loginRedirect, query preserved as return_to) and fully replaceable via a new optional home?: RouteHandler on PluginManifest — a handler with the same signature as any route (the most ergonomic shape). The app.ts "/" branch gates first, then renders the single home plugin's handler against its own views/ with the native shell via ctx.chrome (HEAD / void-return / response-hook parity with a plugin route), else the built-in mock-data People list. home mounts at the root above the /<id> namespace, so it can't shadow or be shadowed by a built-in route. Single-slot + loud: findConflicts errors on >1 home (new "home" kind), discovery rejects a non-function home — never last-write-wins. Tests-first (338 → 344 units): app.test.ts gate + home-override; plugin.test.ts home conflict; discovery.test.ts home validation. Docs: plugin-contract.md (manifest table + "The dashboard (home)" section + conflict row), README. E2E: visual.spec plants a dev-signed session (the anonymous plugin-gate probe uses the cookie-free request fixture); all e2e web/gateway healthchecks repointed from the gated "/" to /public/css/styles.css. stability-reviewer: APPROVE, no Critical/High/Medium. typecheck + 344 units + visual(9) + full-flow(7) E2E green.
2026-06-20 17:18:30 +02:00
lilleman
df53106a5a
§9 test cleanup (todo §9); dropped the one genuine §9-era test overlap. app.test.ts had two /login?return_to= tests for the same surface — the §6 "bakes the return target into the Kratos flow init (OAuth bounce)" and the §9 "first-party deep link wrapped through /auth/complete; absolute target passes through as-is". The §9 test subsumes it: its middle assertion already proves an absolute /oauth2/login?login_challenge= target is handed to initBrowserFlow unchanged (the exact §6 OAuth-bounce contract, labeled as such in the test name + inline comment), plus the new host-relative-wrap + protocol-relative cases. Removed the redundant standalone §6 test, zero coverage lost. The §9 unit files (security-headers/denylist/logger/safe-url + gen-jwks rotateJwks) and the per-field config toggles (SERVICE_NAME/LOG_*/OTLP_*/REVOCATION_*/JWT_CLOCK_SKEW/ORY_TIMEOUT) are one-concern matrices following the file's per-field pattern — no fat (§3 don't-merge-across-distinct-concerns rule). Tests-only, no production code (per the §6/§7/§8 precedent, no stability reviewer). 339 → 338 units; typecheck + tests green.
2026-06-20 16:50:26 +02:00
lilleman
c28b3056ca
§9 comment-density pass over the §9 accretion; two targeted dedupes (the rest authored dense, per the §6/§7/§8 precedent). (1) logger.ts: the requestStore declaration comment restated the file header (ambient per-request Log → deep modules join the trace without threading) and the accessor docs below it — collapsed to a one-line label. (2) security-headers.ts: dropped the redundant 'applied to every response' note above securityHeaders() (the file header + function name already say it). No stale forward-refs remain; README §9 sections left untouched (concise, _(planned)_ already swept in §8). Docs/comments-only; typecheck + 339 units green.
2026-06-20 16:45:58 +02:00