Security — Topic Index
Where every security concern lives, in code and in docs. This page is a map; the substance is in authentication.md (tokens, refresh, lockout, ALTCHA, RBAC), request-pipeline.md (the layered request defense, JWT internals, password hashing, headers), and hardening.md (validation, uploads, secrets, production checklist). The system-wide context is in ../architecture.md.
Threat model in one paragraph
The app is an internet-facing ERP holding payroll and HR data, deployed as one
isolated stack per client. What it actively defends against: credential
attacks (per-IP rate limiting, exponential account lockout, ALTCHA
proof-of-work in production), token theft (15-minute access tokens paired
with 14-day rotating refresh tokens with family reuse-detection), privilege
escalation (every endpoint authorizes server-side via @PreAuthorize against
DB-resolved permissions — the frontend's activeRole switcher is a UI lens
only, never a security boundary), XSS/clickjacking (CSP, X-Frame-Options: DENY at both backend and nginx), secret leakage (env-only secrets, gitleaks
in CI, fail-fast startup validation), and hostile uploads (global multipart
size caps). Accepted tradeoffs, stated plainly: tokens live in localStorage
(an XSS that defeats CSP could read them), rate-limit and ALTCHA-replay state
are in-memory (fine for the one-backend-per-client deployment model), and
access tokens cannot be revoked before their 15-minute expiry.
Concern → code → doc
| Concern | Enforced in code | Covered in |
|---|---|---|
| Authentication (JWT) | JwtTokenManager.java, JwtAuthenticationFilter.java, AuthController.java | authentication.md §Login; request-pipeline.md §The JWT in Detail |
Authorization (RBAC / @PreAuthorize) | SecurityConfiguration.java (@EnableMethodSecurity), PermissionConstants.java, UserDetailsServiceImpl.java | authentication.md §Method-Level Authorization + §RBAC Data Model; request-pipeline.md §Layer 4 |
| Password storage (Argon2id + BCrypt rehash) | PasswordEncoderConfiguration.java — Argon2PasswordEncoder(16, 32, 1, 19456, 2), legacy BCrypt transparently re-hashed on login | request-pipeline.md §Password Security |
| Refresh tokens & revocation | RefreshTokenService.java, V75__auth_hardening.sql | authentication.md §Refresh & Logout |
| Rate limiting (Bucket4j) | LoginRateLimitFilter.java — 10 req/min per IP+path, HTTP 429 | authentication.md §Account Lockout, Rate Limiting & ALTCHA |
| CAPTCHA (ALTCHA proof-of-work) | AltchaVerificationFilter.java, AltchaService.java, AltchaProperties.java; widget: altcha-captcha.tsx | authentication.md §Account Lockout, Rate Limiting & ALTCHA |
| Account lockout | LoginAttemptService.java — 5 failures → 15 min, doubling to a 24 h cap, HTTP 423 | authentication.md §Account Lockout, Rate Limiting & ALTCHA |
| CORS | SecurityConfiguration.java corsConfigurationSource() — env-driven allowlist (CORS_ALLOWED_ORIGINS), never * | request-pipeline.md §Layer 1 |
| CSRF (disabled — deliberately) | SecurityConfiguration.java .csrf(csrf -> csrf.disable()) — auth is Authorization: Bearer, not cookies, so the browser never auto-attaches credentials | request-pipeline.md §Best Practices #2 |
| Input validation | @Valid + Jakarta constraints on request DTOs; GlobalControllerAdvice.java maps violations to 400; Zod client-side is UX-only | hardening.md §Input validation |
| File-upload limits | application.yml — multipart max-file-size: 10MB / max-request-size: 12MB; upload endpoints in JobApplicantController.java and GovernmentFilingController.java | hardening.md §File-upload restrictions |
| Security headers (backend + nginx) | SecurityConfiguration.java .headers(...); frontend/nginx.conf per-location add_header | hardening.md §Security headers; request-pipeline.md §Security Headers |
| Secrets management | Env vars only; JwtProperties.java validateSecret() fail-fast; onboard-client.sh per-client openssl rand; gitleaks.yml CI scan | hardening.md §Secrets management |
| Audit logging | AuditService.java → user_log table; AuditInterceptor.java records 403 denials; auth events Auth.login, Auth.login.failed, Auth.login.lockedOut, Auth.refreshTokenReuseDetected | authentication.md §Refresh & Logout (reuse detection); audit surface itself is UI-browsable via AuditLogController.java |
| WebSocket auth | WebSocketAuthChannelInterceptor.java — validates the JWT from the STOMP CONNECT frame's Authorization header; connection refused otherwise | Not covered in a dedicated doc — the interceptor is the whole story: same JwtTokenManager validation as HTTP, applied at CONNECT time |
| Actuator surface | application.yml exposes only health,prometheus; each listed individually in PUBLIC_ENDPOINTS (never /actuator/**) | hardening.md §Actuator surface |
| Production checklist | docker-compose.client.template.yml (Swagger off, ALTCHA on), onboard-client.sh | hardening.md §Production checklist; ../deployment/vps-guide.md |
Related
- Seeded demo credentials (rotate them in production): ../reference/demo-users.md
- Auth metrics and the Grafana dashboard:
AuthMetrics.java
scraped from
/actuator/prometheusvia docker-compose.monitoring.yml