security: implement tickets #28-#35 + architecture decision #30
#28 - TOTP rate limiting (verifyTotp): added totpRateLimiter (10 req/30s), throws TOO_MANY_REQUESTS before DB hit; 16 unit tests including rate-limit exceeded + userId key isolation. #29 - /api/reports/allocations role check: only ADMIN/MANAGER/CONTROLLER may access; returns 403 otherwise; 9 unit tests (401 unauthenticated, 403 for USER/VIEWER, 200 for allowed roles + xlsx format). #31 - pgAdmin credentials moved out of docker-compose.yml into env vars; PGADMIN_PASSWORD is now required (:?) to prevent accidental plaintext exposure in committed files. #34 - Server-side HTML sanitization for comment bodies via stripHtml(): strips all tags + decodes safe entities before persistence; 16 unit tests covering passthrough, injection patterns, entity decoding. #35 - MFA setup prompt banner (MfaPromptBanner): shown to ADMIN/MANAGER users without TOTP enabled; user-scoped localStorage snooze (7 days); links to /account/security; accessibility role=alert; 7 structural unit tests. #33 - Auth anomaly alerting cron (/api/cron/auth-anomaly-check): detects HIGH_GLOBAL_FAILURE_RATE and CONCENTRATED_FAILURES in 30-minute window; CRITICAL notification to ADMINs; fail-closed via verifyCronSecret; 10 unit tests. #32 - MFA enforcement policy: added requireMfaForRoles field to SystemSettings schema + Prisma migration; auth.ts blocks login with MFA_REQUIRED_SETUP signal if role is enforced but TOTP not set up; signin page redirects to /account/security?mfa_required=1; settings schema + view model updated; 11 unit tests. #30 - API keys architecture decision documented in LEARNINGS.md; no code written — product decision required before implementation. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -387,3 +387,24 @@ prisma.user.upsert({ where: ..., update: { passwordHash: adminHash }, create: {
|
||||
- **Bug fix:** new widgets are now appended at `getNextDashboardWidgetY(existingWidgets)` rather than using `Infinity`, so persisted layouts remain JSON-safe.
|
||||
- **Regression coverage:** added `packages/shared/src/__tests__/dashboard-layout.test.ts` for default fallback, invalid-coordinate repair, duplicate-ID normalization, and next-row calculation.
|
||||
**TypeScript note:** `exactOptionalPropertyTypes` required building option objects with conditional spreads rather than passing `{ title: undefined }` into helper APIs. This matters for any future shared normalizer helpers.
|
||||
|
||||
### 2026-04-01 | Architecture Decision | API Keys — no implementation without explicit product decision
|
||||
|
||||
**Context (Ticket #30):** The OWASP audit surfaced the absence of a programmatic API key system. A number of potential approaches were considered.
|
||||
|
||||
**Decision: No code is written until the product decision is made.**
|
||||
|
||||
The core trade-off is:
|
||||
- **Short-lived JWTs (current approach):** Zero DB footprint, automatic expiry, no revocation surface. Works well for a single-tenant SaaS where all clients are browser sessions. No additional attack surface.
|
||||
- **Long-lived API keys stored in DB:** Enables CLI tooling, CI/CD pipelines, and machine-to-machine workflows. Requires: secure token generation (crypto.randomBytes, bcrypt hash stored, raw key shown once), per-key scopes, revocation endpoint, key rotation policy, audit log for key usage. Significantly larger attack surface and ops burden.
|
||||
- **Short-lived API tokens (OAuth-style):** Suitable if CapaKraken exposes a public API. Over-engineered for an internal tool with no current integration story.
|
||||
|
||||
**Engineering guidance for when the decision is made:**
|
||||
1. Store only the SHA-256 or bcrypt hash of the key, never the raw token.
|
||||
2. Enforce per-key scopes aligned with the `SystemRole` permission model.
|
||||
3. Add `keyUsedAt` tracking and hard expiry via TTL field on the DB row.
|
||||
4. Rate-limit key-authenticated requests via `apiRateLimiter` (already exists in `packages/api/src/middleware/rate-limit.ts`).
|
||||
5. Expose revocation via `DELETE /api/keys/:id` (protected procedure, owner or ADMIN only).
|
||||
6. Log all key-authenticated requests to `AuditLog` with `source: "api-key"`.
|
||||
|
||||
**Action required:** Product owner must confirm whether external integrations are in scope before this ticket can proceed.
|
||||
|
||||
Reference in New Issue
Block a user