Instead of serving the full dashboard HTML (which exposes the UI shell
when the auth overlay is deleted), serve a minimal page that only loads
the auth JS and CSS. Deleting the overlay now reveals a blank page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The PIN overlay was client-side only — deleting the DOM element bypassed
auth entirely. Add global chi middleware that returns 401 on protected
endpoints when no valid session cookie is present. Static files pass
through so the login page renders. During onboarding (no PIN set), all
routes remain open.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix Viz3D exports to include flow visualization functions
- Export setFlowLayerVisible, setDwellLayerVisible, setCorridorLayerVisible
- Export setFlowTimeFilter, setFlowData, setDwellData, setCorridorData
- Remove duplicate setDwellLayerVisible function definition
This completes the crowd flow visualization feature that was
already implemented in the backend (flow.go) and frontend
(crowdflow.js, viz3d.js) but had missing exports in the Viz3D module.
- Backend: Add POST /api/auth/change-pin endpoint
- Requires valid session; body: {old_pin, new_pin}
- Verifies old PIN against bcrypt hash; returns 403 on mismatch
- Hashes new PIN with bcrypt cost=12
- Existing sessions remain valid after PIN change
- Returns {ok:true} on success
- Dashboard: Security section in settings panel
- Add "Security" section with Change PIN button
- Modal form: old PIN → new PIN → confirm new PIN → Submit
- Inline error display for incorrect current PIN (403)
- Success toast notification on PIN change
- Validation: 4-8 digits, numeric only, PINs must match, new ≠ old
- Tests: Add comprehensive tests for change PIN endpoint
- Success case: old PIN verified, new PIN works
- Wrong old PIN: returns 403, original PIN still works
- Unauthenticated: returns 401
- Invalid new PIN: validation for length, digits, etc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auto-generate 256-bit installation secret on first run using crypto/rand,
print it exactly once to stdout, and store in SQLite for subsequent startups.
Support SPAXEL_INSTALL_SECRET env var override. Expose via GET /api/auth/install-secret
endpoint (admin session or first-run state). Derive per-node provisioning tokens
via HMAC-SHA256(install_secret, node_mac).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend (mothership/internal/auth/):
- SQLite auth table with pin_bcrypt and install_secret (singleton row)
- GET /api/auth/status — return {pin_configured: bool}
- POST /api/auth/setup — sets PIN (bcrypt cost 12) on first run only
- POST /api/auth/login — verifies PIN, issues session cookie (7-day expiry)
- POST /api/auth/logout — clears cookie and deletes session from SQLite
- Session middleware: all /api/* and /ws/* require valid session
- Rolling window: extends session by 7 days if within 24h of expiry
- Install secret generation for node token derivation
Dashboard (dashboard/js/auth.js):
- On load: GET /api/auth/status check
- First-run setup page: enter PIN + confirm PIN → POST /api/auth/setup → reload
- Login page: shown on 401; PIN entry → POST /api/auth/login → reload
- Logout button in settings panel → POST /api/auth/logout → redirect
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>