Scope (locked in via planning Q&A):
- Identity: local accounts only (PG users table) + existing bearer
tokens for headless callers.
- Transport: httpOnly cookie session for browser, Bearer for API.
- RBAC: admin / editor / viewer roles, plus an orthogonal
is_client flag for external (agency, talent, customer) accounts.
- Bootstrap: ADMIN_BOOTSTRAP_USER + ADMIN_BOOTSTRAP_PASSWORD env
seed the first admin on a clean install. Set ADMIN_BOOTSTRAP_RESET
to force-reset the named user (break-glass).
- Rate limit: in-memory, 10 fails per 15min per (IP, username).
- Password policy: \u22658 chars, mixed case, digit, symbol; small
blocklist of common passwords; cannot equal username.
- Self-service: change own display name + password. Everything
else (role, is_client, other-user mgmt) is admin only.
- Audit log: append-only table, indexed by actor + event_type +
created_at, populated by every auth/admin event.
Files added:
- services/mam-api/src/db/migrations/022-auth-rework.sql
users.is_client + last_login_at + failed_attempts; audit_log
table with FK to users (ON DELETE SET NULL).
- services/mam-api/src/middleware/audit.js
Fire-and-forget audit() helper. Caller never awaits, failure
logs but never throws — auditing cannot break the request
that triggered it.
- services/mam-api/src/middleware/passwordPolicy.js
Shared checkPassword(pw, { username }) used by setup, user
create/update, and self-service password change.
- services/mam-api/src/tasks/bootstrapAdmin.js
Runs after migrations. No-ops unless ADMIN_BOOTSTRAP_USER +
ADMIN_BOOTSTRAP_PASSWORD are set AND (users table empty OR
ADMIN_BOOTSTRAP_RESET=true).
- services/mam-api/src/routes/audit.js
Admin-only GET /audit (paginated, filter by event_type /
actor / target / date) and GET /audit/event-types.
- services/web-ui/public/modal-account-settings.jsx
Profile + Password tabs. Triggered by sidebar user button.
Files rewritten:
- services/mam-api/src/routes/auth.js
- POST /login: regenerate(), no manual save(); audit success/
fail/lockout; updates last_login_at + failed_attempts.
- POST /logout: destroys session, audits logout.
- GET /me: returns is_client + last_login_at. Synthetic admin
when AUTH_ENABLED=false.
- GET /setup-status: drives login.html UI state.
- POST /setup: blocked once any user exists; password policy.
- POST /password: self-service. Requires current pw, runs
policy, audits, invalidates other sessions implicitly via
users.js if changed by admin.
- PATCH /me: self-service display_name update.
- services/mam-api/src/routes/users.js
- is_client field in create/update/list/get.
- Guardrails: cannot delete or demote last admin, cannot
delete self, admins cannot be flagged is_client.
- Password change invalidates all sessions for that user
(DELETE FROM sessions WHERE sess->>'userId' = id).
- Audit on every mutation.
- Password policy enforced.
- services/mam-api/src/middleware/auth.js
- requireAuth now exposes req.user.is_client.
- New requireRole(["admin","editor"], { rejectClients: true })
helper. Applied to cluster, sdk, capture routes (infra).
- Synthetic user when AUTH_ENABLED=false has is_client=false.
- services/mam-api/src/index.js
- Loads bootstrap admin after migrations.
- Wires /api/v1/audit.
- Cleans up an earlier comment block.
- services/web-ui/public/login.html
- Password hint added next to setup-mode password field.
- services/web-ui/public/shell.jsx
- Sidebar user footer is a button that opens AccountSettings.
- CLIENT badge next to role when is_client=true.
- Nav filters: clients lose ingest tree + jobs + editor;
viewers lose ingest + editor; only admins see the Admin
section. Power button hidden when synthetic user.
- services/web-ui/public/screens-admin.jsx
- Users table: new Client column with inline toggle.
- InviteUserModal: Client checkbox + password hint, gated
off when role=admin.
- Last login column replaces Created in primary view.
- CSV export includes client + last_login.
- services/web-ui/public/data.jsx
- ZAMPP_DATA.ME carries is_client + display_name.
- services/web-ui/public/index.html
- Loads dist/modal-account-settings.js.
- services/web-ui/public/styles-rest.css
- .user-row grid widened to 6 columns.
- docker-compose.yml
- Plumbs SESSION_COOKIE_SECURE + ADMIN_BOOTSTRAP_* env vars.
Deploy:
cd /opt/wild-dragon
git pull origin main
# In .env:
# AUTH_ENABLED=true
# SESSION_SECRET=<openssl rand -hex 48>
# ADMIN_BOOTSTRAP_USER=admin
# ADMIN_BOOTSTRAP_PASSWORD=<strong>
docker compose build mam-api web-ui
docker compose up -d --force-recreate --no-deps mam-api web-ui
137 lines
3.6 KiB
YAML
137 lines
3.6 KiB
YAML
services:
|
|
db:
|
|
image: postgres:16
|
|
environment:
|
|
POSTGRES_DB: ${POSTGRES_DB}
|
|
POSTGRES_USER: ${POSTGRES_USER}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
ports:
|
|
- "${PORT_DB:-5432}:5432"
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
- ./services/mam-api/src/db/schema.sql:/docker-entrypoint-initdb.d/001-schema.sql:ro
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 5
|
|
networks:
|
|
- wild-dragon
|
|
|
|
queue:
|
|
image: redis:7-alpine
|
|
ports:
|
|
- "${PORT_REDIS:-6379}:6379"
|
|
volumes:
|
|
- redis_data:/data
|
|
networks:
|
|
- wild-dragon
|
|
|
|
mam-api:
|
|
build: ./services/mam-api
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
queue:
|
|
condition: service_started
|
|
ports:
|
|
- "${PORT_MAM_API:-7432}:3000"
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
- /mnt/NVME/MAM/wild-dragon-live:/live
|
|
- /mnt/NVME/MAM/wild-dragon-growing:/growing
|
|
- /mnt/NVME/MAM/sdk:/sdk
|
|
- /dev/shm:/dev/shm
|
|
- /run/dbus:/run/dbus
|
|
- /run/systemd:/run/systemd
|
|
- /usr/bin/nvidia-smi:/usr/bin/nvidia-smi:ro
|
|
environment:
|
|
DATABASE_URL: ${DATABASE_URL}
|
|
REDIS_URL: ${REDIS_URL}
|
|
S3_ENDPOINT: ${S3_ENDPOINT}
|
|
S3_BUCKET: ${S3_BUCKET}
|
|
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
|
|
S3_SECRET_KEY: ${S3_SECRET_KEY}
|
|
S3_REGION: ${S3_REGION:-us-east-1}
|
|
SESSION_SECRET: ${SESSION_SECRET}
|
|
AUTH_ENABLED: ${AUTH_ENABLED:-false}
|
|
SESSION_COOKIE_SECURE: ${SESSION_COOKIE_SECURE:-}
|
|
ADMIN_BOOTSTRAP_USER: ${ADMIN_BOOTSTRAP_USER:-}
|
|
ADMIN_BOOTSTRAP_PASSWORD: ${ADMIN_BOOTSTRAP_PASSWORD:-}
|
|
ADMIN_BOOTSTRAP_DISPLAY_NAME: ${ADMIN_BOOTSTRAP_DISPLAY_NAME:-}
|
|
ADMIN_BOOTSTRAP_RESET: ${ADMIN_BOOTSTRAP_RESET:-}
|
|
DOCKER_NETWORK: wild-dragon_wild-dragon
|
|
NODE_IP: ${NODE_IP}
|
|
NODE_HOSTNAME: ${NODE_HOSTNAME:-}
|
|
deploy:
|
|
resources:
|
|
reservations:
|
|
devices:
|
|
- driver: nvidia
|
|
count: all
|
|
capabilities: [gpu]
|
|
networks:
|
|
- wild-dragon
|
|
|
|
capture:
|
|
build: ./services/capture
|
|
depends_on:
|
|
- mam-api
|
|
ports:
|
|
- "${PORT_CAPTURE:-7433}:3001"
|
|
- "${PORT_RTMP:-1935}:1935" # RTMP ingest (listener mode)
|
|
- "${PORT_SRT:-9000}:9000/udp" # SRT ingest (listener mode)
|
|
privileged: true
|
|
environment:
|
|
S3_ENDPOINT: ${S3_ENDPOINT}
|
|
S3_BUCKET: ${S3_BUCKET}
|
|
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
|
|
S3_SECRET_KEY: ${S3_SECRET_KEY}
|
|
S3_REGION: ${S3_REGION:-us-east-1}
|
|
MAM_API_URL: ${MAM_API_URL:-http://mam-api:3000}
|
|
volumes:
|
|
- /mnt/NVME/MAM/wild-dragon-live:/live
|
|
- /dev/shm:/dev/shm
|
|
- /run/dbus:/run/dbus
|
|
- /run/systemd:/run/systemd
|
|
networks:
|
|
- wild-dragon
|
|
|
|
worker:
|
|
build: ./services/worker
|
|
depends_on:
|
|
- queue
|
|
- db
|
|
environment:
|
|
REDIS_URL: ${REDIS_URL}
|
|
DATABASE_URL: ${DATABASE_URL}
|
|
S3_ENDPOINT: ${S3_ENDPOINT}
|
|
S3_BUCKET: ${S3_BUCKET}
|
|
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
|
|
S3_SECRET_KEY: ${S3_SECRET_KEY}
|
|
S3_REGION: ${S3_REGION:-us-east-1}
|
|
GROWING_PATH: /growing
|
|
volumes:
|
|
- /mnt/NVME/MAM/wild-dragon-growing:/growing
|
|
networks:
|
|
- wild-dragon
|
|
|
|
web-ui:
|
|
build: ./services/web-ui
|
|
ports:
|
|
- "${PORT_WEB_UI:-7434}:80"
|
|
volumes:
|
|
- /mnt/NVME/MAM/wild-dragon-live:/live
|
|
- /dev/shm:/dev/shm
|
|
- /run/dbus:/run/dbus
|
|
- /run/systemd:/run/systemd
|
|
networks:
|
|
- wild-dragon
|
|
|
|
volumes:
|
|
postgres_data:
|
|
redis_data:
|
|
|
|
networks:
|
|
wild-dragon:
|
|
driver: bridge
|