# Security Architecture — CapaKraken > Version: 1.0 | Date: 2026-03-27 --- ## 1. Authentication - **Auth.js v5** (NextAuth) with Credentials provider - **Password hashing**: Argon2id via `@node-rs/argon2` (memory cost 65536, time cost 3) - **Multi-Factor Authentication**: TOTP (RFC 6238) via `otpauth` library - Configurable per user (enable/disable via admin or self-service) - 30-second window, SHA-1, 6-digit codes with 1-step tolerance - **Rate limiting**: 5 login attempts per 15 minutes per email address (in-memory sliding window) - **Session strategy**: JWT with server-side validation - Absolute timeout: 8 hours (configurable via `sessionMaxAge`) - Idle timeout: 30 minutes (configurable via `sessionIdleTimeout`) - **Concurrent session limit**: configurable `maxConcurrentSessions` (default 3), kick-oldest strategy - **Login/logout audit**: all authentication events (success, failure, rate-limit, invalid TOTP, logout) are recorded in the audit log ## 2. Authorization ### Role-Based Access Control (RBAC) Five-level role hierarchy: | Role | Level | Capabilities | | ---------- | ----- | ---------------------------------------------------------- | | ADMIN | 5 | Full system access, user management, system settings | | MANAGER | 4 | Project management, resource allocation, vacation approval | | CONTROLLER | 3 | Financial views, budget management, reporting | | USER | 2 | Self-service (own vacations, own resource profile) | | VIEWER | 1 | Read-only access to permitted areas | ### Per-User Permission Overrides - `permissionOverrides` JSONB field on User model - `resolvePermissions(role, overrides)` computes effective permissions - `requirePermission(ctx, key)` enforced on every tRPC procedure - Granular `PermissionKey` enum covering all domain actions ### tRPC Middleware Stack ``` publicProcedure -> protectedProcedure (requires authenticated session) -> controllerProcedure (ADMIN + MANAGER + CONTROLLER) -> managerProcedure (ADMIN + MANAGER) -> adminProcedure (ADMIN only) ``` ## 3. Data Protection ### Database Security - **PostgreSQL** with TLS in production - **Prisma ORM**: parameterized queries by default — no SQL injection risk - Database not exposed to the internet (Docker internal network only) - All monetary values stored as integer cents (no floating-point precision issues) ### Data at Rest - Passwords: Argon2id hash (never stored in plaintext) - TOTP secrets: stored in DB (encrypted at-rest via PostgreSQL TDE when available) - Runtime secrets now resolve env-first for AI, Gemini, SMTP, and anonymization seed values. Database-backed `SystemSettings` values remain transitional compatibility storage, not the preferred production source of truth. - Recommended runtime overrides: `OPENAI_API_KEY`, `AZURE_OPENAI_API_KEY`, `AZURE_DALLE_API_KEY`, `GEMINI_API_KEY`, `SMTP_PASSWORD`, `ANONYMIZATION_SEED` - Admin settings reads expose only presence flags (`hasApiKey`, `hasSmtpPassword`, `hasGeminiApiKey`) instead of returning secret values to the browser, and those flags also reflect environment-backed runtime overrides - The admin settings mutation no longer persists new secret values into `SystemSettings`; secret inputs must be provisioned through environment or a deployment-time secret manager, and legacy database copies can be cleared explicitly - The admin UI now exposes runtime secret source/status plus an explicit "clear legacy DB secrets" cleanup path so operators can complete the migration without direct database writes - Production startup now validates Auth.js runtime configuration and refuses to boot if `AUTH_SECRET`/`NEXTAUTH_SECRET` is missing, left on a known development placeholder, paired with a non-HTTPS public auth URL, shorter than 32 characters, or failing a Shannon-entropy check (≥ 3.5 bits/char) - User passwords: minimum 12 characters, maximum 128 characters; single `PASSWORD_MIN_LENGTH` / `PASSWORD_MAX_LENGTH` constant (`@capakraken/shared/constants`) is imported by every client-side pre-submit validator and server-side Zod schema — prevents client/server policy drift #### Secret rotation - **`AUTH_SECRET` / `NEXTAUTH_SECRET`** is the signing key for all JWT session cookies. Rotation forces every user to re-authenticate on their next request. - Generate replacement: `openssl rand -base64 32` - Deploy path: 1. Update the secret in the deployment secret store (not in repo). 2. Roll all application containers — existing JWTs signed under the old key fail verification and the user is redirected to sign-in. 3. There is no multi-key transition window: this is a hard cut on purpose, because a compromised signing key must be retired immediately. - Recommended cadence: quarterly, or immediately on suspected compromise. - **`POSTGRES_PASSWORD`** rotation is coordinated across postgres container init, the app container's `DATABASE_URL`, and any external replication consumers — follow the deployment runbook. ### Anonymization - Configurable global anonymization for VIEWER role - Resource names, emails replaced with deterministic pseudonyms (seeded hash) - Anonymization domain and mode configurable in SystemSettings ## 4. Session Management - **Server-side JWT** with `SameSite=Strict` cookies - `httpOnly` cookies prevent XSS-based session theft - `secure` flag enforced in production (HTTPS only) - CSRF protection via Auth.js built-in CSRF token - Configurable session timeouts (absolute + idle) via SystemSettings - Active session registry with concurrent session limit enforcement ## 5. Input Validation - **Zod schemas** on every tRPC procedure input - Strict TypeScript (`strict: true`, `exactOptionalPropertyTypes: true`) - Blueprint dynamic fields validated at runtime against stored Zod schema definitions - File uploads validated by: - MIME type whitelist (`image/png`, `image/jpeg`, `image/webp`, `image/tiff`, `image/bmp`). SVG is explicitly rejected — XML markup could carry `