feat: add browser auth shell
CI / verify (pull_request) Successful in 38s
CI / infrastructure-smoke (pull_request) Successful in 12s
CI / migration-smoke (pull_request) Successful in 1m0s

This commit is contained in:
ekklesia
2026-08-23 22:01:26 -05:00
parent 91aa9c2930
commit a8c7c50b4d
26 changed files with 2702 additions and 194 deletions
+35 -13
View File
@@ -19,9 +19,11 @@ surface concern. Better Auth is an implementation detail contained entirely behi
Auth instance, session token, or raw database client.** This holds structurally today: modules
cannot import `core` at all under `scripts/check-boundaries.ts`'s boundary matrix, regardless of
what Core exposes.
- **Surfaces will eventually authenticate through Core-exposed HTTP/API and client contracts**,
never by querying Better Auth's tables directly. `apps/api` (Phase 4B, see [API](api.md)) is that
transport for server-side/API consumers; no Dashboard, Website, or Mobile Surface exists yet.
- **Surfaces authenticate through Core-exposed HTTP/API contracts**, never by querying Better
Auth's tables directly. `apps/api` (Phase 4B, see [API](api.md)) is that transport. The Dashboard
now owns its browser login/logout UX and uses only `/api/auth/sign-in/email`,
`/api/auth/sign-out`, and the narrow `/api/v1/principal` contract; modules receive none of those
controls. Website and Mobile authentication do not exist yet.
- Better Auth's `user` row is an authentication identity, not the future church-person/profile
model. Church membership, ministry roles, contact history, attendance, permissions, and other
module-specific fields must never be added to the identity tables; a future, separately designed
@@ -168,7 +170,8 @@ address passes this validation, and a firewall, network policy, or reverse proxy
parsing — is what actually determines who can reach a deployment.
Secret rotation (Better Auth's plural `secrets` list) and cross-subdomain cookies are deferred; see
ADR 0010.
ADR 0010. The Dashboard flow in ADR 0034 deliberately retains a host-only cookie: a future
deployment must not assume sibling subdomains share a session.
## The identity runtime (Core's Better Auth transport boundary)
@@ -184,6 +187,21 @@ exported from `@ekklesia/core/identity`, exposes `auth.api`, Better Auth's sessi
Drizzle adapter, the raw database, a session token, or Better Auth configuration. `apps/api` is the
only current consumer of this boundary; see [API](api.md).
Core still returns Better Auth's normal `Response` to that consumer. At the browser-facing API
composition boundary, [ADR 0034](../architecture/decisions/0034-host-owned-browser-authentication.md)
defines exactly two exceptions. First, any body-bearing successful `2xx` from
`POST /api/auth/sign-in/email` preserves Better Auth's status, headers, and HttpOnly `Set-Cookie`
but replaces its token/user JSON with fixed `{"ok":true}`. Second, the token-bearing native paths
`/api/auth/get-session`, `/api/auth/list-sessions`, `/api/auth/update-session`, and
`/api/auth/change-password` receive a sanitized `404` without reaching Better Auth. The last path
is denied even though password/account management is not built, because the pinned implementation
can return a new raw session token when revoking other sessions.
Failures and every other supported auth endpoint retain their native semantics. The Dashboard
ignores even the normalized sign-in body as identity and re-resolves `/api/v1/principal`, the only
supported browser session read; no session token is exposed to browser application JavaScript or
added to a module context.
Construction also explicitly configures four things the pinned release would otherwise leave on an
environment-sensitive or implicit default, all covered by [ADR 0012](../architecture/decisions/0012-identity-origin-csrf-protection-under-test.md)
and [API](api.md#logging):
@@ -233,13 +251,17 @@ database, and a query timeout therefore all produce a sanitized
logs, and error carry no relation name, column name, SQL, driver message, host, or stack. See
[ADR 0018](../architecture/decisions/0018-identity-session-resolution-and-database-transport.md).
## What is not operational yet
## What is and is not operational
No email verification, password recovery, invitation flow, email delivery, social or OAuth
provider, passkey, magic link, two-factor authentication, or roles/groups/teams/tenants model
exists. Public registration, sign-in, principal resolution, and sign-out over HTTP **are**
operational as of Phase 4B — see [API](api.md) for the transport and routes, and ADR 0011 for why
public registration is intentional and grants no elevation. Core's authorization foundation
(platform owners, explicit grants, an immutable permission registry) landed in Phase 4C and is
covered separately — see [Authorization](authorization.md) — not a Dashboard Surface, which still
depends on an authorization model that only now exists and has not yet been built on.
Email/password sign-in, principal resolution, sign-out, and the Dashboard's host-owned browser flow
are operational — see [API](api.md) and ADR 0034. Public registration remains a future product
policy, not a production capability: it is disabled by default, may be explicitly enabled only for
development/test, and production rejects it. No email verification, password recovery, invitation
flow, email delivery, social or OAuth provider, passkey, magic link, two-factor authentication, or
roles/groups/teams/tenants model exists.
Core's authorization foundation (platform owners, explicit grants, and an immutable permission
registry) is covered separately in [Authorization](authorization.md). Its bootstrap command grants
an existing identity; it does not create one. Because production signup is prohibited and no
operator enrollment command exists, creating the initial production identity needed for owner
bootstrap remains an explicit operational gap.