All checks were successful
CD / Build and push images (push) Successful in 2m45s
CI / Lint, typecheck, test (push) Successful in 1m56s
CI / Auth e2e pack (push) Successful in 2m1s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m9s
CD / Promote to Int (push) Successful in 12s
The api mints a short-lived (60 s) HS256 JWT per page open after an interim
permission check; the collab server authenticates every connection with it
(ADR 0003/0007 — the only JWTs in the system).
- packages/shared: browser-safe token schema/types in `collab-token`, and the
Node `crypto` sign/verify in `token-crypto` behind its own subpath export
(`@dorfteich/shared/token-crypto`) so the web bundle never pulls in
`node:crypto`. Only HS256 is produced/accepted; the signature is checked in
constant time before any untrusted field is read.
- api: `GET /pages/:id/collab-token` (auth-required) returns
{token, mode, expiresInSeconds}; `mode` is rw/ro via the interim access
service; issuance is logged at debug level without the token value.
- collab: `onAuthenticate` verifies the token, checks the pageId matches the
document name, stores {userId, mode} context, and enforces `ro` via
Hocuspocus' read-only connection flag. Hocuspocus' own signal handling is
disabled so index.ts remains the single shutdown owner.
- Shared COLLAB_TOKEN_SECRET env for api + collab (compose, dev overlay,
.env.example, stage docs); a dev default keeps native dev/test/CI running.
Tests: shared token round-trip/rejection; api endpoint e2e (auth required,
claims, 404 for non-members/unknown ids); collab integration via
HocuspocusProvider (valid token connects; expired/tampered/mismatched-page/
wrong-secret rejected; read-only writes dropped, verified with two clients).
Closes #34
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
3.8 KiB
YAML
103 lines
3.8 KiB
YAML
# Development overlay: hot-reloading web and api containers with the
|
|
# repository mounted, layered over the production stack definition.
|
|
#
|
|
# Full containerized dev environment (first start installs dependencies):
|
|
# cd deploy/compose && cp .env.example .env
|
|
# docker compose -f docker-compose.yml -f compose.dev.yml up
|
|
# → web http://localhost:5173, api http://localhost:3001, db localhost:5434
|
|
#
|
|
# Database-only (run web/api natively for the fastest feedback):
|
|
# docker compose -f docker-compose.yml -f compose.dev.yml up -d db
|
|
# DATABASE_URL=postgresql://dorfteich:dorfteich@localhost:5434/dorfteich \
|
|
# PORT=3001 pnpm --filter @dorfteich/api start:dev
|
|
# pnpm --filter @dorfteich/web dev
|
|
|
|
services:
|
|
web:
|
|
image: node:22.15-alpine
|
|
build: !reset null
|
|
working_dir: /repo
|
|
command: sh -c "npm i -g pnpm@11 && pnpm install && pnpm --filter @dorfteich/web dev -- --host 0.0.0.0"
|
|
environment:
|
|
VITE_API_PROXY_TARGET: http://api:3000
|
|
ports: !override
|
|
- '127.0.0.1:5173:5173'
|
|
volumes:
|
|
- ../..:/repo
|
|
# Container-local node_modules: the host directories contain
|
|
# macOS binaries and must not leak into the Linux containers.
|
|
- web-root-modules:/repo/node_modules
|
|
- web-app-modules:/repo/apps/web/node_modules
|
|
- shared-modules:/repo/packages/shared/node_modules
|
|
- pnpm-store:/root/.local/share/pnpm/store
|
|
depends_on: !reset []
|
|
|
|
api:
|
|
image: node:22.15-alpine
|
|
build: !reset null
|
|
working_dir: /repo
|
|
command: sh -c "npm i -g pnpm@11 && pnpm install && pnpm --filter @dorfteich/shared build && pnpm --filter @dorfteich/api start:dev"
|
|
environment:
|
|
NODE_ENV: development
|
|
PORT: '3000'
|
|
DATABASE_URL: postgresql://dorfteich:${POSTGRES_PASSWORD:-dorfteich}@db:5432/dorfteich
|
|
# Dev default; overrides the base stack's required form (issue #34).
|
|
COLLAB_TOKEN_SECRET: ${COLLAB_TOKEN_SECRET:-dev-insecure-collab-token-secret-change-me}
|
|
ports: !override
|
|
- '127.0.0.1:3001:3000'
|
|
volumes:
|
|
- ../..:/repo
|
|
- api-root-modules:/repo/node_modules
|
|
- api-app-modules:/repo/apps/api/node_modules
|
|
- api-shared-modules:/repo/packages/shared/node_modules
|
|
- pnpm-store:/root/.local/share/pnpm/store
|
|
|
|
collab:
|
|
image: node:22.15-alpine
|
|
build: !reset null
|
|
working_dir: /repo
|
|
command: sh -c "npm i -g pnpm@11 && pnpm install && pnpm --filter @dorfteich/shared build && pnpm --filter @dorfteich/collab start:dev"
|
|
environment:
|
|
NODE_ENV: development
|
|
PORT: '3000'
|
|
DATABASE_URL: postgresql://dorfteich:${POSTGRES_PASSWORD:-dorfteich}@db:5432/dorfteich
|
|
# Must match the api dev default so tokens verify across the two services.
|
|
COLLAB_TOKEN_SECRET: ${COLLAB_TOKEN_SECRET:-dev-insecure-collab-token-secret-change-me}
|
|
ports: !override
|
|
- '127.0.0.1:3002:3000'
|
|
volumes:
|
|
- ../..:/repo
|
|
- collab-root-modules:/repo/node_modules
|
|
- collab-app-modules:/repo/apps/collab/node_modules
|
|
- collab-shared-modules:/repo/packages/shared/node_modules
|
|
- pnpm-store:/root/.local/share/pnpm/store
|
|
# No healthcheck block here: the production image's HEALTHCHECK does not
|
|
# apply to this node:alpine dev image, and nothing depends on it.
|
|
healthcheck: !reset null
|
|
|
|
db:
|
|
ports:
|
|
# 5434 on the host to avoid colliding with other local PostgreSQL
|
|
# instances (5432 system, 5433 wochenplan-staging).
|
|
- '127.0.0.1:5434:5432'
|
|
|
|
# Local SMTP catcher: UI on http://localhost:8025, SMTP on 1025 —
|
|
# matches the api's SMTP_* defaults.
|
|
mailpit:
|
|
image: axllent/mailpit:latest
|
|
ports:
|
|
- '127.0.0.1:1025:1025'
|
|
- '127.0.0.1:8025:8025'
|
|
|
|
volumes:
|
|
web-root-modules:
|
|
web-app-modules:
|
|
shared-modules:
|
|
api-root-modules:
|
|
api-app-modules:
|
|
api-shared-modules:
|
|
collab-root-modules:
|
|
collab-app-modules:
|
|
collab-shared-modules:
|
|
pnpm-store:
|