From bef6d8e4dcbd32636da56d4a424bc6d7bf8399d7 Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Sun, 5 Jul 2026 10:42:32 +0200 Subject: [PATCH] Make stage mail + base URL configurable; seed password overrides - compose: pass APP_BASE_URL and SMTP_* through to the api container so stages can use a real relay (defaults still match the dev Mailpit overlay); document the new keys in .env.example and stages.md - seed: FIXTURE_ADMIN_PASSWORD / FIXTURE_USER_PASSWORD env overrides so shared stages get non-public fixture passwords; credential is re-hashed on every run so re-seeding applies a changed password Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01UpQz6ypHJsLfMf4S6fyQEB --- apps/api/prisma/seed.ts | 15 ++++++++++++--- deploy/compose/.env.example | 14 ++++++++++++++ deploy/compose/docker-compose.yml | 10 ++++++++++ deploy/stages.md | 17 +++++++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/apps/api/prisma/seed.ts b/apps/api/prisma/seed.ts index 20d96fb..e2c71ab 100644 --- a/apps/api/prisma/seed.ts +++ b/apps/api/prisma/seed.ts @@ -8,7 +8,9 @@ * fixture-pending registered but e-mail not verified * * All fixture accounts share the password below — they exist only on - * dev machines and disposable CI/Test databases. + * dev machines and disposable CI/Test databases. On shared stages + * (test/int), set FIXTURE_ADMIN_PASSWORD / FIXTURE_USER_PASSWORD to give + * those two accounts non-public passwords. */ import { PrismaClient, UserStatus } from '@prisma/client'; @@ -16,6 +18,11 @@ import { hashPassword } from '../src/users/password'; export const FIXTURE_PASSWORD = 'fixture passwort 123'; +const PASSWORD_OVERRIDES: Record = { + 'fixture-admin': process.env.FIXTURE_ADMIN_PASSWORD, + 'fixture-user': process.env.FIXTURE_USER_PASSWORD, +}; + const prisma = new PrismaClient(); interface FixtureUser { @@ -54,15 +61,17 @@ async function upsertFixtureUser(fixture: FixtureUser): Promise { isSiteAdmin: fixture.isSiteAdmin, }, }); + // Re-hash on every run so a changed override takes effect on re-seed. + const credential = await hashPassword(PASSWORD_OVERRIDES[fixture.username] ?? FIXTURE_PASSWORD); await prisma.userIdentity.upsert({ where: { provider_subject: { provider: 'password', subject: user.id } }, create: { userId: user.id, provider: 'password', subject: user.id, - credential: await hashPassword(FIXTURE_PASSWORD), + credential, }, - update: {}, + update: { credential }, }); } diff --git a/deploy/compose/.env.example b/deploy/compose/.env.example index 2207d8d..baa595e 100644 --- a/deploy/compose/.env.example +++ b/deploy/compose/.env.example @@ -24,3 +24,17 @@ LOG_LEVEL=info # Compose project name; set per stage (dorfteich-test, dorfteich-int, …). COMPOSE_PROJECT_NAME=dorfteich + +# --- public URL + mail -------------------------------------------------------- +# Public base URL of the stage (scheme + host). E-mail links and the CSRF +# origin check are derived from it — it must match what browsers use. +APP_BASE_URL=https://test.dorfteich.cloud + +# SMTP relay for outgoing mail (verification, password reset). Leave unset +# to keep the Mailpit dev defaults; real stages need a real relay. +SMTP_HOST=mail.example.com +SMTP_PORT=465 +SMTP_SECURE=true +SMTP_USER=wiki@example.com +SMTP_PASS=change-me +SMTP_FROM=Dorfteich diff --git a/deploy/compose/docker-compose.yml b/deploy/compose/docker-compose.yml index ebf8c76..07d9196 100644 --- a/deploy/compose/docker-compose.yml +++ b/deploy/compose/docker-compose.yml @@ -45,6 +45,16 @@ services: PORT: '3000' LOG_LEVEL: ${LOG_LEVEL:-info} DATABASE_URL: postgresql://dorfteich:${POSTGRES_PASSWORD:?set in .env}@db:5432/dorfteich + # Public URL of this stage — e-mail links and the CSRF origin check + # depend on it matching what browsers actually use. + APP_BASE_URL: ${APP_BASE_URL:-http://localhost:5173} + # SMTP relay; defaults are only useful with the dev Mailpit overlay. + SMTP_HOST: ${SMTP_HOST:-localhost} + SMTP_PORT: ${SMTP_PORT:-1025} + SMTP_SECURE: ${SMTP_SECURE:-false} + SMTP_USER: ${SMTP_USER:-} + SMTP_PASS: ${SMTP_PASS:-} + SMTP_FROM: ${SMTP_FROM:-Dorfteich } ports: - '127.0.0.1:${API_PORT:-8101}:3000' networks: [frontend, internal] diff --git a/deploy/stages.md b/deploy/stages.md index 8bf5592..4c70e05 100644 --- a/deploy/stages.md +++ b/deploy/stages.md @@ -29,6 +29,23 @@ each stage directory. Set per stage in `.env` (mode 600): - `WEB_PORT`/`API_PORT`: 8100/8101 (test), 8110/8111 (int) - `IMAGE_PREFIX=gitea.101010.cloud/stwaidele/dorfteich` - `TAG`: managed by the CD pipeline (`` on test, `int` on int) +- `APP_BASE_URL`: `https://test.dorfteich.cloud` / `https://int.dorfteich.cloud` + — e-mail links and the CSRF origin check depend on it +- `SMTP_HOST`/`SMTP_PORT`/`SMTP_SECURE`/`SMTP_USER`/`SMTP_PASS`/`SMTP_FROM`: + real relay credentials (both non-prod stages share one mailbox); without + them, signup/reset mails queue up and fail + +Fixture accounts on the stages are created with the regular seed, but with +stage-specific passwords (never the public dev password): + +```sh +FIXTURE_ADMIN_PASSWORD=… FIXTURE_USER_PASSWORD=… \ + DATABASE_URL=postgresql://dorfteich:…@localhost:/dorfteich \ + pnpm --filter @dorfteich/api db:seed +``` + +(The stage db is not published; tunnel to the db container, e.g. +`ssh -L 15432::5432 root@188.245.116.44`.) ## 2. Reverse proxy vhosts **[root]**