3.2 KiB
3.2 KiB
type, date, project, tags
| type | date | project | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|
| session | 2026-05-27 | cpas |
|
CPAS — Authentication, user management, and docs
Summary
Added a login popup, env-bootstrapped admin account, and admin-managed user CRUD to the CPAS Violation Tracker. Replaced the long-standing TODO [CRITICAL #1] (no auth on any route). Updated in-app docs and the Unraid install guide.
What shipped
- Backend (
auth.js,db/database.js,server.js)- scrypt password hashing (Node built-in
crypto, no new deps) usersandsessionstables (sessions = DB-backed, 7-day TTL, hex tokens)bootstrapAdmin()creates/syncs admin fromADMIN_USERNAME/ADMIN_PASSWORDon every startup → password owned by Docker envrequireAuth/requireAdminExpress middleware- Public:
/api/health,POST /api/auth/login. Everything else guarded byapp.use('/api', auth.requireAuth). - User mgmt:
GET/POST /api/users,PATCH /api/users/:id/password,DELETE /api/users/:id(all admin-only). Audit log entries for login attempts and user changes.
- scrypt password hashing (Node built-in
- Frontend (
client/src/auth.js,LoginModal.jsx,UserManagementModal.jsx,App.jsx)- axios interceptors inject
Bearertoken from localStorage, auto-logout on 401 - Auth gate in App: validates stored token via
/api/auth/meon load, shows LoginModal if no session - Nav: user badge, Logout, and admin-only Users button
- axios interceptors inject
- Docker —
ENV ADMIN_USERNAME=adminandENV ADMIN_PASSWORD=changeme(placeholder, must override) - Docs — in-app admin guide (
ReadmeModal.jsx) gained an "Authentication & User Accounts" section; auth moved into Roadmap → Shipped;README_UNRAID_INSTALL.mdadds the two new env-var rows + login verify step + troubleshooting for forgotten admin password.
Key decisions
- Used Node's built-in
crypto.scryptSyncto avoid adding bcrypt as a dependency. Storage format:scrypt$<saltHex>$<hashHex>. Constant-time compare viacrypto.timingSafeEqual. - DB-backed sessions instead of JWT or in-memory map → survives container restarts, single source of truth, easy admin revoke if needed.
- Admin password re-syncs from env on every startup. Trade-off: admin can't change their own password in UI (it would be reverted on restart), but rotating the env var rotates the live credential — clean operational story.
- Did not wrap
window.fetch. All component API calls go through axios; the lone barefetch('/version.json')hits a public static file. So axios interceptors are sufficient. - PDF downloads use
axios.get(..., { responseType: 'blob' }), not anchor links → interceptor still attaches the token, no special-case needed.
Loose ends / future work
- No password-change UI for non-admin users yet (admins reset for them).
- No expired-session cleanup job (lazy prune only).
- Only admin/user roles. Supervisor-scoped or read-only roles still open.
Verification status
node --checkpasses onauth.js,server.js,db/database.js.client && npm run buildsucceeds.- Could not run server locally —
better-sqlite3fails to compile against local Node v24. Builds fine in the Dockernode:20-alpineimage; full verification requires building and running the container.