- Dockerfile: node:24-alpine, production deps only, runs as the node
user, healthcheck on /healthz. No volume, because the server keeps
no state.
- docker-compose.yml: example for the VPS. It publishes no ports,
since Docker's port publishing bypasses ufw and would skip NPM's IP
access list. It joins NPM's network instead.
- .gitea/workflows/docker.yml: on v*-prod* tags, builds on the
self-hosted runner and pushes v0idf1sh/sdp-dashboard:<tag> and
:latest using the DHUB_UNAME and DHUB_PASS secrets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each user now signs in with their own Zoho account through the web
OAuth flow (Server-based Application client), and every SDP query runs
with that user's token. Nobody who reaches the page can see data
without signing in.
- auth.js: authorization URL, code exchange, token renewal, revoke and
user info, replacing the device-code flow.
- session.js: tokens kept in an AES-256-GCM encrypted, HttpOnly,
SameSite=Lax cookie keyed by SESSION_SECRET. The access token renews
when due, concurrent calls share one renewal, and sessions end after
SESSION_DAYS.
- routes.js: /auth/login, /auth/callback (state checked), /auth/logout
(revokes at Zoho); /api/session, /api/closed, /api/workload and
/api/weather all need a sign-in.
- The server is stateless. boardService.js, tokenStore.js, ADMIN_KEY
and data/token.json are gone. The page runs the checks itself and
keeps the closed feed, backfilling 12 hours on first load.
- tickets.js: the closed feed query pages, and SDP 401s are rethrown
so the user is signed out.
- server.js: PUBLIC_URL, trust proxy, security headers, /healthz.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>