14 Commits

Author SHA1 Message Date
Hartmut fe79810a85 security: MFA backup codes — issue on enable, redeem at login, regenerate on demand (#43)
CI / Architecture Guardrails (push) Successful in 6m1s
CI / Assistant Split Regression (push) Successful in 6m52s
CI / Lint (push) Successful in 8m40s
CI / Typecheck (push) Successful in 9m45s
CI / Unit Tests (push) Successful in 7m28s
CI / Build (push) Failing after 10m16s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
Adds a one-time-use backup code set so users with a lost authenticator are not
locked out. Codes are Crockford base32 (XXXXX-XXXXX), hashed with argon2id, and
redeemed under a WHERE-guarded delete so a concurrent replay race fails closed.

- New MfaBackupCode model + migration
- Issue 10 codes inside the enable transaction; show plaintext exactly once
- Sign-in page accepts TOTP or backup code, reporting remaining count
- regenerateBackupCodes tRPC mutation wipes + reissues atomically
- Unit coverage for generator, normalizer, verify, redeem, and race path

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 18:47:18 +02:00
Hartmut c098cedf06 perf(db): add missing indexes, fix N+1 batch delete, add pagination limits
- Add indexes on Resource(blueprintId, roleId), DemandRequirement(roleId),
  Assignment(roleId) — commonly filtered FK columns that were missing indexes
- Replace N+1 batch delete pattern (2N queries) with findAllocationEntries()
  that does 2 total queries via findMany({ id: { in: ids } })
- Add take/skip pagination with default limit of 500 to listDemands and
  listAssignments to prevent unbounded result sets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 08:09:39 +02:00
Hartmut afabaa0b7a fix(security): prevent TOTP replay attacks and fix user enumeration in verifyTotp
Adds lastTotpAt timestamp to User model. After a successful TOTP validation,
the timestamp is recorded. Any reuse of the same code within the 30-second
window is rejected as a replay attack.

verifyTotp now returns a single generic UNAUTHORIZED error regardless of
whether the user ID is invalid or TOTP is not enabled, preventing enumeration
of user IDs and MFA status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 21:41:09 +02:00
Hartmut 43de66e982 feat(api): add audit helpers, tool registry, shared tool manifest types, and UI primitives
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 21:14:26 +02:00
Hartmut 4eac7b1888 fix(infra): apply missing migrations, fix Dockerfile.dev ui package reference 2026-04-09 20:29:21 +02:00
Hartmut 1a8ea11331 feat(db): add deletedAt audit timestamp to soft-deletable models
Add deletedAt DateTime? to User, Client, Role, Resource, and Blueprint
models for GDPR-compliant deactivation audit trail. Soft-delete mutations
now stamp deletedAt: new Date() on deactivation and clear it on
reactivation. Migration and test assertions updated accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 20:03:38 +02:00
Hartmut 96679f5fec Merge branch 'worktree-agent-acdb74a3' 2026-04-09 14:18:27 +02:00
Hartmut aebe5bc57d fix(db): add SetNull cascade on Assignment→DemandRequirement + composite indexes
Prevents orphaned Assignment rows when a DemandRequirement is deleted.
Adds (resourceId, status, endDate) and (projectId, status, endDate)
indexes to support capacity range queries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:03:11 +02:00
Hartmut 41eb722369 feat: user invite flow, deactivate/delete, favicon, dashboard loading fix, admin full-width
- Invite flow: admin can invite users by email with role selection; accept-invite page
  sets password and creates the account; 72-hour token expiry; E2E tests
- User deactivate/reactivate/delete: new tRPC procedures + UI buttons; deactivation
  revokes all active sessions immediately; delete cascades vacation/broadcast records;
  isActive field added via migration 20260402000000_user_isactive
- Auth: block login for inactive users with audit entry
- Favicon: SVG favicon + ICO/PNG fallbacks (16, 32, 180, 192, 512px); manifest updated
- Dashboard: GridLayout dynamic-import loading skeleton prevents blank dark area
  on first login before react-grid-layout chunk is cached
- Admin users: remove max-w-5xl constraint so table uses full page width
- Dev: docker container restart workflow documented in LEARNINGS.md; Prisma generate
  must run inside the container after schema changes (named node_modules volume)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-02 20:19:26 +02:00
Hartmut 5bc7cace26 fix(auth): make active-session check fail-open; add missing DB migration
The active_sessions table was never migrated to production — the model
was added to the Prisma schema via db push only. prisma migrate deploy
was a no-op because no migration directories existed.

Without the table, prisma.activeSession.findUnique() throws P2021,
crashing the tRPC handler with 500 on every authenticated request.
This silently emptied all admin pages (users, system-roles, etc.).

Changes:
- Wrap the jti ActiveSession lookup in try-catch so the tRPC handler
  degrades gracefully (fail-open) if the table is temporarily missing
- Add packages/db/prisma/migrations/20260401000000_active_sessions/
  so prisma migrate deploy creates the table on next production deploy
  (idempotent via IF NOT EXISTS — safe if table already exists)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-01 18:38:05 +02:00
Hartmut 8c5be51251 feat(platform): checkpoint current implementation state 2026-04-01 07:42:03 +02:00
Hartmut 93c4374973 feat(auth): introduce explicit planning read permission 2026-03-30 09:15:07 +02:00
Hartmut 819345acfa feat(platform): harden access scoping and delivery baseline 2026-03-30 00:27:31 +02:00
Hartmut dd55d0e78b chore(repo): initialize planarchy workspace 2026-03-14 14:31:09 +01:00