ffdefd2c9c
Backend
- photos table + upload storage under DATA_DIR/uploads (magic-byte sniffing,
no multipart dep, SVG rejected, wx exclusive writes)
- POST/GET /api/photos, GET /api/photos/:id/file with nosniff + sandboxed CSP
- admin routes (ADMIN_EMAILS allowlist): list, delete one, clear all
- identity-keyed rate limits (login 20/15m, signup 5/h, upload 60/h)
- cookie gains Secure when the request is https (via trustProxy)
- /api/auth/me now 200 {user:null} instead of 401 when signed out
Frontend
- landing strip section: signed-in users upload straight from the reel,
guests get a /app?auth=1 link
- /admin page: grid of uploads with delete + clear all
- nginx: nosniff / X-Frame-Options / Referrer-Policy, forward
X-Forwarded-Proto so the API can mark cookies Secure behind TLS
Tests: docker/backend test/security.mjs (45 checks)
30 lines
929 B
YAML
30 lines
929 B
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:-}
|
|
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"
|