When the api runs against a database without the setup.completedAt
marker, a global SetupGuard answers every non-exempt route with 503
setup_required; only /setup/*, health probes, and the session routes
stay reachable. The wizard steps (POST /setup/admin|instance|smtp|
registration|complete) write straight to their production homes; the
Site Admin step signs its creator in, later steps require that session.
Completing sets the marker and locks every step permanently (410, also
across restarts, and not reopenable via PATCH /admin/settings).
SMTP entered in the wizard is verified with a live delivery test first
(failure blocks the step with the transport error as detail) and then
persisted to the new env-backed secret store: a mode-600 dotenv file on
the new `secrets` volume (SECRETS_FILE). Explicit container env always
wins over the store; empty compose-passed strings count as unset. The
mail transport now resolves lazily through SmtpConfigService so wizard
changes apply without a restart.
SETUP_ADMIN_* env pre-seeds the whole wizard at boot for automated
deploys; a backfill migration marks instances that already have a Site
Admin as completed, and seed/vitest global-setup do the same for
fixture databases. The setup e2e suite provisions its own fresh
database (CREATE DATABASE + migrate deploy) per run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
Every route now declares its access rule explicitly and is enforced
through the shared resolution algorithm (permissions.md):
- PermissionGuard + decorators (@RequiresPondRole, @RequiresPagePermission,
@RequiresAttachmentPermission, @AuthenticatedOnly) applied to every
route; a route-enumeration test proves full coverage alongside
@Public()/Site-Admin-guarded routes.
- 404/403 policy (documented in README conventions): denied reads answer
404 (existence hiding), denied writes on readable things answer 403;
trash views need write capability (ADR 0013).
- PermissionService resolves page/pond questions via the shared resolver,
with an in-process pond-context cache (grants + label parents) that is
invalidated on every grant/label-tree change and TTL-bounded as a
multi-process safety net. Grant changes also fire pond_access_changed
for collab revalidation (#39/#53).
- shared: pond-scope resolution (hasPondRole, canSeePond) next to the
page resolver; grant wire schemas + GrantView.
- Owner Pond-Admin grants: migration backfill for all existing ponds,
created transactionally with every new pond (shared + personal + seed).
- Grant CRUD under /ponds/:id/grants (pond_admin-gated) with structural
and referential validation, last-admin protection, audit logs.
- InterimAccessService deleted; page lists, search, backlinks, phantom
links, and trash listings are filtered per page through the resolver;
collab tokens are now truly ro for readers.
- Fixture-matrix e2e (reader/editor/pond admin/foreign, label-deny,
authenticated-subject, revoke-then-immediate-deny cache test).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
The web app grows its account surface: login (with next-redirect,
unverified-hint + resend), signup (react-hook-form + shared Zod
schemas, field-level api errors, closed-registration state fed by the
new public GET /auth/registration), e-mail verification, forgot/reset
password; a settings page with profile (locale applies immediately),
password change, and active-session management; a Site-Admin page for
instance name, default locale, and registration mode. AuthProvider
holds /auth/me, applies the profile locale, and backs route guards
(RequireAuth/RequireAnonymous/RequireSiteAdmin); the top bar gains a
user menu. All strings ship in the new auth/settings namespaces (de+
en); the exception filter now preserves handler-specific error codes.
Verified live: signup → Mailpit → verify → login → profile through
the Vite proxy.
Closes#16Closes#17Closes#18Closes#19
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AuthModule implements the M1 core as one coherent unit:
Signup (#13): POST signup/verify-email/resend-verification with shared
Zod validation (field-level error details), double opt-in via hashed
single-use tokens (24h, superseding reissue), registration_mode
enforcement, and per-IP rate limits.
Sessions (#14): opaque 32-byte cookie tokens stored as SHA-256 row
ids, sliding 30-day expiry (refresh at most hourly), global AuthGuard
with @Public() opt-out attaching the user to every request, CSRF
origin check on mutating requests, per-account login backoff (5/15min,
reset on success), generic 401 for wrong-vs-unknown credentials,
logout with immediate invalidation, GET /auth/me.
Reset (#15): forgot-password without account enumeration, one-hour
single-use tokens, reset destroys all existing sessions.
A 14-case supertest e2e suite drives every flow against the test
database, reading verification/reset links from the mail outbox.
Closes#13Closes#14Closes#15
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>