d2e2ebe108
The browser cannot talk to Immich directly: the key must stay out of it, COEP blocks the origin, and the app has no place to keep a key per user. So the backend keeps it. `src/immich.ts` holds the whole surface — the user's servers live in a JSON column on `users` (additive migration), and every route reads the key from there and never takes a URL from the browser except when probing one. Albums, a page of assets, a thumbnail and an original, all behind the normal session check. `probe` is the only route that touches a URL the client named, and it validates it first (http/https only, no credentials, no path, no query, no hash) so the browser cannot turn the backend into a proxy to an arbitrary host. The key is masked down to its last four characters everywhere it comes back out, and no log line carries it. The share-link path is the same routes with `type: 'share'`, whose key travels as `?key=`, so there is one code path per call rather than two. test/immich.mjs runs a fake Immich on loopback — two keys with different albums, one of them without `asset.download` — and checks 59 things including that neither the responses nor the log leak a key.
44 lines
1.7 KiB
YAML
44 lines
1.7 KiB
YAML
# RecipesCam web — the whole stack. Copy this folder anywhere, then:
|
|
# cp .env.example .env
|
|
# docker compose up -d --build
|
|
name: recipescam-web
|
|
|
|
services:
|
|
# Accounts + saved recipes. Never sees a photo: every pixel of the render
|
|
# pipeline runs in the visitor's browser.
|
|
api:
|
|
build: ./backend
|
|
restart: unless-stopped
|
|
environment:
|
|
DATA_DIR: /data
|
|
# Who may moderate the contributed strip: a comma-separated email
|
|
# allowlist. Empty means nobody is an admin, which is the safe default.
|
|
ADMIN_EMAILS: ${ADMIN_EMAILS:-}
|
|
# The relay that mails the "prove this address" link. Leave SMTP_HOST
|
|
# empty and the link is written to this container's log instead — a dev
|
|
# box needs no mail server, and the operator can read / copy it.
|
|
SMTP_HOST: ${SMTP_HOST:-}
|
|
SMTP_PORT: ${SMTP_PORT:-587}
|
|
SMTP_USER: ${SMTP_USER:-}
|
|
SMTP_PASS: ${SMTP_PASS:-}
|
|
SMTP_FROM: ${SMTP_FROM:-}
|
|
SMTP_SECURE: ${SMTP_SECURE:-}
|
|
# Immich, offered as a second photo source in LIBRARY. This is only the
|
|
# address the "add an Immich server" dialog starts with: the URL and the
|
|
# key belong to each account, are typed in the app, and are stored in the
|
|
# users table. Empty simply means the field starts blank.
|
|
IMMICH_URL: ${IMMICH_URL:-}
|
|
volumes:
|
|
# SQLite (WAL) lives on the host so a rebuild never loses accounts.
|
|
- ./data:/data
|
|
|
|
# Static SPA + /api reverse proxy. "api" is the DNS name nginx.conf proxies to,
|
|
# so this service name is load-bearing.
|
|
frontend:
|
|
build: ./frontend
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- api
|
|
ports:
|
|
- "${WEB_PORT:-8090}:80"
|