Ekklesia could authenticate a person and, since Pass 1, send a transactional email — but it had no way to establish that an address belonged to whoever claimed it, and no way back in for someone who forgot their password. A verified address is now a precondition for holding a session. Better Auth enforces that after the password check and before any session exists, so an unverified sign-in returns 403 with no cookie. That is why no "unverified principal" was needed: a correct password on an unverified address carries exactly the authority of no password, and /api/v1/principal is unchanged. Better Auth keeps every token. Ekklesia adds no token table, no hashing, and no expiry clock. It supplies a recipient, a subject, and two bodies, and the mail provider supplies everything about SMTP. Verification and reset links are rebuilt from a validated Dashboard origin, taking only the token — never the URL Better Auth prebuilds from a caller-supplied callbackURL — so no request can steer where a recipient lands. Following a verification link mints no session, because mail infrastructure dereferences links. Reset revokes that person's other sessions and leaves managed devices alone. Recovery answers identically for an address with an account, without one, already verified, or undeliverable — including when SMTP is broken, which is why the delivery adapter never throws. The initial platform owner is the one exception: created already verified by an operator command with no mail prerequisite, so a fresh server can be signed in to before a relay exists. Migration 0007 extends that to owners of installations that already exist. Public signup stays disabled; verification is not a licence to self-register. Co-Authored-By: Claude Opus 5 <[email protected]>
99 lines
5.7 KiB
Bash
99 lines
5.7 KiB
Bash
# Local development only. Replace every credential outside a developer workstation.
|
|
#
|
|
# `pnpm doctor` loads this file's uncommitted `.env` counterpart and evaluates exactly the host
|
|
# infrastructure configuration `pnpm api:start` would use, so the readiness report reflects the real
|
|
# deployment without the API process running.
|
|
|
|
# Required for a local workstation, and only ever correct there. Anything else — including an
|
|
# unset value — is validated as production, which requires certificate-verified PostgreSQL TLS
|
|
# (EKKLESIA_DATABASE_TLS_MODE defaults to verify-full) and rejects loopback/http identity origins.
|
|
# The plaintext loopback DATABASE_URL below is therefore only usable with this set.
|
|
NODE_ENV=development
|
|
|
|
POSTGRES_DB=ekklesia
|
|
POSTGRES_USER=ekklesia
|
|
POSTGRES_PASSWORD=ekklesia-development-only
|
|
POSTGRES_PORT=5432
|
|
DATABASE_URL=postgresql://ekklesia:[email protected]:5432/ekklesia
|
|
|
|
# Ekklesia's object-storage provider (ADR 0030). Absent S3_ENDPOINT means this installation has no
|
|
# object storage, which is a complete deployment: only a module that declares a logical bucket in its
|
|
# manifest is affected, and it is reported blocked rather than breaking anything. Production requires
|
|
# https, exactly as the database transport requires verified TLS.
|
|
#
|
|
# These are host infrastructure. No module ever sees them: a module names a logical bucket and the
|
|
# host resolves the binding.
|
|
S3_ACCESS_KEY=ekklesia-development
|
|
S3_SECRET_KEY=ekklesia-development-secret
|
|
S3_ENDPOINT=http://127.0.0.1:8333
|
|
S3_PORT=8333
|
|
S3_REGION=us-east-1
|
|
S3_FORCE_PATH_STYLE=true
|
|
# The origin a *browser* reaches object storage on, used only when signing temporary read URLs for
|
|
# direct delivery (ADR 0031). Leave it unset whenever S3_ENDPOINT is itself reachable from a browser,
|
|
# which is the case for this development stack: Compose publishes the service on loopback, so the
|
|
# endpoint above serves both roles and direct delivery works with no extra configuration.
|
|
#
|
|
# Set it when Core reaches storage by a name a browser cannot resolve — a Compose service name such
|
|
# as http://object-storage:8333, for instance — and browsers reach it through a public origin:
|
|
# S3_DELIVERY_ENDPOINT=https://storage.example.org
|
|
# It must be the origin the browser actually requests. An S3-compatible signature covers the request
|
|
# Host, so a URL signed for one origin cannot be rewritten to another afterwards; a reverse proxy in
|
|
# front of storage must therefore pass the original Host header through unchanged. The scheme is not
|
|
# signed, so terminating TLS at that proxy and forwarding plaintext to the service is fine.
|
|
# S3_DELIVERY_ENDPOINT=
|
|
# Namespaces every physical bucket this installation owns, so several Ekklesia-compatible deployments
|
|
# can share one S3-compatible service. Optional; defaults to "ekklesia".
|
|
S3_BUCKET_PREFIX=ekklesia
|
|
# Consumed only by the development Compose service's own bootstrap, never by Ekklesia's provider —
|
|
# physical buckets are derived from a module's manifest declaration, not configured here.
|
|
S3_BUCKET=ekklesia-development
|
|
|
|
# Ekklesia's outbound email transport (ADR 0037). Absent EKKLESIA_SMTP_HOST means this installation
|
|
# sends no email, which is a complete deployment: nothing in the platform sends any yet, and a
|
|
# future feature that needs to is told so rather than crashing.
|
|
#
|
|
# These are host infrastructure. No module ever sees them, and no feature chooses its own sender:
|
|
# the From address below is the only one this platform can send as.
|
|
#
|
|
# EKKLESIA_SMTP_HOST is a bare hostname — never a URL, a port, or a connection string.
|
|
# EKKLESIA_SMTP_SECURITY is exactly one of:
|
|
# implicit-tls TLS from the first byte, conventionally port 465
|
|
# starttls plaintext connect then a *required* upgrade, conventionally 587 (the default)
|
|
# plaintext no TLS at all; development only, and production refuses it outright
|
|
# EKKLESIA_SMTP_PORT is optional and defaults to the conventional port for the mode.
|
|
#
|
|
# Credentials are optional **as a pair**, because a relay authorized by source IP has neither.
|
|
# Setting one without the other is refused rather than silently connecting anonymously. Never commit
|
|
# a real password: this file is in every clone.
|
|
#
|
|
# A Google Workspace SMTP Relay deployment typically looks like:
|
|
# EKKLESIA_SMTP_HOST=<the relay host from Google Workspace Admin>
|
|
# EKKLESIA_SMTP_PORT=587
|
|
# EKKLESIA_SMTP_SECURITY=starttls
|
|
# [email protected]
|
|
# Whether it also needs a username and password depends on the relay's authorization setting in
|
|
# Google Workspace Admin, which varies per deployment. See docs/development/email.md.
|
|
# EKKLESIA_SMTP_HOST=
|
|
# EKKLESIA_SMTP_PORT=587
|
|
# EKKLESIA_SMTP_SECURITY=starttls
|
|
# EKKLESIA_SMTP_USERNAME=
|
|
# EKKLESIA_SMTP_PASSWORD=
|
|
# [email protected]
|
|
# EKKLESIA_SMTP_FROM_NAME=Ekklesia
|
|
|
|
# Where an emailed verification or password-reset link sends a browser (ADR 0038).
|
|
#
|
|
# Absent means the Dashboard is served from EKKLESIA_IDENTITY_BASE_URL, which is the ordinary
|
|
# same-origin production shape. Set it only when a deployment separates the two, for example an API
|
|
# at https://api.example.org and the Dashboard at https://dash.example.org. It must also appear in
|
|
# EKKLESIA_IDENTITY_TRUSTED_ORIGINS, or the identity system refuses the emailed link — Ekklesia
|
|
# checks that at startup rather than letting every recipient discover it.
|
|
#
|
|
# This is the authenticated Dashboard host, never the public church website.
|
|
# EKKLESIA_DASHBOARD_ORIGIN=https://dash.example.org
|
|
#
|
|
# Verification and password recovery need a working relay above. The initial platform-owner
|
|
# bootstrap (pnpm identity:create-initial-owner) deliberately does not: it creates that one account
|
|
# already verified, so a fresh server can be signed in to before any mail exists.
|