Status: Proposal only — no implementation code. Requires owner sign-off before building.
Research date: 2026-09-02
Author: Swarm research agent (athena-featuretoggle-usermgmt-research)
This document is based on:
WebConfig (CORS) and a completely unprotected AdminController at DELETE /api/admin/wipe-all.InterimPlaza uses a static, environment-variable-backed feature gate. There is no database table for feature flags and no admin UI to toggle them at runtime — every flip requires a config change + redeploy.
Key components:
app.features.cv.enabled (env CV_FEATURE_ENABLED, default false) — a single @Value-injected boolean per feature.CvFeatureGateInterceptor — a Spring MVC HandlerInterceptor that runs before every request to CV-related URLs. When the flag is false, it throws FeatureDisabledException.FeatureDisabledException — a RuntimeException mapped to HTTP 503 Service Unavailable by the global exception handler. The 503 is intentional: the endpoint exists and will return once the flag is re-enabled, making 503 semantically correct vs. 404.WebMvcConfig.addInterceptors() — registers the interceptor against a hard-coded list of URL patterns (e.g. /api/v1/cv-profiles/**, /api/public/cv/**).What InterimPlaza does NOT have:
feature_flags database table.Directly portable. Requires no new DB schema. Flip a feature by updating an env var and redeploying.
Port as-is:
FeatureDisabledException class and its global exception handler mapping to 503.HandlerInterceptor.WebMvcConfig using addInterceptors() to bind each interceptor to its URL patterns.app.features.<name>.enabled / env <NAME>_FEATURE_ENABLED.Athena-specific adaptation:
PdfExportFeatureGateInterceptor or DossierFeatureGateInterceptor).config/ package already exists (nl.glorylabs.athena.config). Both the interceptor and WebMvcConfig live there.Suitable if the owner wants to demo features to different clients without redeploying.
Additional pieces needed (not in InterimPlaza, needs new design):
Backend:
feature_flags table: id, key (unique string), enabled (boolean), description, updated_at.FeatureFlagRepository (JPA).FeatureFlagService — reads from DB, with a short in-process cache (e.g. 30 s Caffeine) to avoid DB round-trips on every request.FeatureFlagAdminController at GET/PUT /api/admin/feature-flags — returns all flags, allows toggling. Must be behind whatever auth gate is introduced (see Section 3).FeatureFlagService instead of a @Value.Frontend:
PUT /api/admin/feature-flags/{key} with { "enabled": true/false }.Open decision A1: Does the owner want runtime-toggle capability (Option B) or is env-var + redeploy acceptable (Option A)? Option A is far simpler and sufficient for the current demo phase. Option B is only needed if clients need to toggle features without owner intervention.
Start with Option A (direct port of InterimPlaza's env-var approach). It is a 3-file addition (interceptor, exception, WebMvcConfig update) and unblocks gating features immediately. Add Option B only when there is a concrete need for runtime toggling without redeploy.
InterimPlaza has a complete production-grade auth stack:
Entity & roles:
User JPA entity implements Spring Security's UserDetails. Stored in users table.user_roles join table as an @ElementCollection of an inner Role enum: ADMIN, RECRUITER, USER.emailVerified, emailVerificationToken, totpSecret/totpEnabled (MFA), passwordResetToken, tokensValidFrom (per-user token revocation epoch), accountNonLocked, active, lastLoginAt.phoneNumber, company, position) use an EncryptedStringConverter.Security stack:
SecurityConfig with @EnableWebSecurity and @EnableMethodSecurity.SessionCreationPolicy.STATELESS).DaoAuthenticationProvider + BCryptPasswordEncoder.JwtAuthenticationFilter (reads Bearer token, loads UserPrincipal, sets SecurityContext).JwtTokenProvider (sign/verify JWTs, check tokensValidFrom for revocation).RateLimitingFilter on all auth endpoints.JwtAuthenticationEntryPoint (401 on unauthenticated) and JwtAccessDeniedHandler (403 on insufficient role).@PreAuthorize on sensitive controller methods./api/auth/** public; /api/admin/** ADMIN-only; /api/v1/admin/** ADMIN or RECRUITER with carve-outs; all other endpoints authenticated.User creation & auth flow:
POST /api/auth/register — self-service registration.POST /api/auth/login — returns accessToken + refreshToken.Admin user management UI:
UsersComponent) with paginated list, search, role filter, status filter.GET /api/v1/admin/users, PUT /api/v1/admin/users/{id}/role, PUT /api/v1/admin/users/{id}/status, DELETE /api/v1/admin/users/{id}.spring-boot-starter-security is not in pom.xml.DELETE /api/admin/wipe-all is callable by any anonymous HTTP client.SecurityConfig. Only WebConfig (CORS).These components are framework-level and domain-agnostic — copy with minimal changes:
| Component | Location in InterimPlaza | Change needed for Athena |
|---|---|---|
| SecurityConfig | nl.glorylabs.config.SecurityConfig | Rewrite URL rules (Athena has different paths); keep same structure |
| JwtAuthenticationFilter | nl.glorylabs.security.JwtAuthenticationFilter | Port as-is |
| JwtTokenProvider | nl.glorylabs.security.JwtTokenProvider | Port as-is |
| UserPrincipal | nl.glorylabs.security.UserPrincipal | Port as-is |
| JwtAuthenticationEntryPoint | nl.glorylabs.security.JwtAuthenticationEntryPoint | Port as-is |
| JwtAccessDeniedHandler | nl.glorylabs.security.JwtAccessDeniedHandler | Port as-is |
| BCryptPasswordEncoder bean | Inside SecurityConfig | Port as-is |
| DaoAuthenticationProvider bean | Inside SecurityConfig | Port as-is |
| Auth endpoints (login/refresh/logout) | nl.glorylabs.controller.AuthController | Port login/refresh/logout; drop register (admin-created users only — see below) |
| pom.xml security deps | spring-boot-starter-security, jjwt-* | Add to Athena pom.xml |
InterimPlaza's roles (ADMIN, RECRUITER, USER) are recruitment-domain specific. Athena is a financial audit tool and needs different roles:
| Athena Role | Description |
|---|---|
| ADMIN | Full access: manage users, toggle feature flags, wipe data, all analysis features |
| AUDITOR | Full read/write access to dossiers, analyses, PDF export — the primary user role |
| REVIEWER | Read-only access to dossiers and analyses; cannot upload or delete (e.g. client-side reviewer) |
Open decision B1: Are these the right roles? In particular: is a REVIEWER role needed in the current phase, or are all users AUDITOR?
InterimPlaza supports self-service registration (anyone can sign up). Athena is a professional tool for accounting firms — self-service registration is probably wrong. Instead:
ADMIN user creates accounts for all AUDITOR/REVIEWER users./register endpoint.InterimPlaza encrypts PII fields (phoneNumber, company) using an EncryptedStringConverter. This is not needed for Athena's user model (only name + email + role stored).
InterimPlaza has TOTP MFA. Athena does not need MFA in the initial phase.
Open decision B3: Should MFA be planned for or explicitly out of scope?
InterimPlaza uses a tokensValidFrom column on User to mass-revoke all of a user's tokens on password reset. Athena should adopt this pattern from day one since it is a security baseline, not a feature.
Currently Customer and Dossier have no userId or organizationId FK. Once users exist, dossiers must be owned. Two models:
AUDITOR who created it. Other users cannot see it unless explicitly shared.Open decision B4: Should dossiers be scoped per-user or per-organization (firm)? This is the most architectural decision of the whole auth effort — it determines whether a organizations table is needed (full multi-tenant) or just a user_id FK on dossier.
Athena has a public upload endpoint (/api/public/{token}/...) where external parties can upload files via a short-lived token. This must remain public (unauthenticated) — the token in the URL is the access control. SecurityConfig must explicitly permit /api/public/** as InterimPlaza permits its equivalent.
The Angular admin page (/pages/admin/) currently only has the wipe-all button. Once auth exists, it needs:
UsersComponent from InterimPlaza.This is a straight port of InterimPlaza's admin users UI with the role names swapped.
These must be answered before any implementation starts:
| ID | Question | Impact |
|---|---|---|
| A1 | Env-var feature flags (port InterimPlaza verbatim) vs. DB-backed runtime toggles? | Determines scope of feature-flag work |
| B1 | What roles does Athena need? Suggested: ADMIN / AUDITOR / REVIEWER. Correct? | Determines User entity and SecurityConfig rules |
| B2 | Admin-only user creation (no self-register) vs. invite-code self-register? | Determines which auth endpoints to build |
| B3 | Is MFA in scope for initial implementation? | Complexity + TOTP library dependency |
| B4 | Per-user dossier ownership or per-organization (firm) ownership? | Most consequential: determines if a multi-tenant organizations table is needed |
| B5 | Should Athena require login at all in the current demo phase? If this is still a single-org internal demo, adding auth may be premature. If external clients will access it, auth is a prerequisite. | Determines whether to start now or defer |
The phases below assume owner sign-off on the open decisions above.
Scope: 3 new files, ~1 day.
FeatureDisabledException to nl.glorylabs.athena.analysis.exception (or a new nl.glorylabs.athena.featureflag.exception).HandlerInterceptor for whichever feature the owner wants to gate first (e.g. PDF export, dossier persistence).WebConfig (or add a new WebMvcConfig) to register the interceptor.No DB migration needed. No security dependency needed.
Scope: ~1–2 weeks.
spring-boot-starter-security, jjwt-api, jjwt-impl, jjwt-jackson.nl.glorylabs.athena.security package: port JwtTokenProvider, JwtAuthenticationFilter, UserPrincipal, JwtAuthenticationEntryPoint, JwtAccessDeniedHandler.User entity with Athena roles (ADMIN / AUDITOR / REVIEWER), Flyway migration V3__users.sql.UserRepository, UserDetailsService implementation.SecurityConfig with URL rules matching Athena's actual API surface:permitAll: /api/auth/**, /api/public/**, /actuator/health/**.ADMIN: /api/admin/**./api/**.POST /api/auth/users endpoint (create user) — no self-register.POST /api/auth/login and POST /api/auth/refresh (port from InterimPlaza).Scope: ~0.5–1 day, but depends on B4 decision.
If per-user: add user_id FK to dossier table (Flyway V4__dossier_user_ownership.sql); update DossierRepository queries to filter by authenticated user.
If per-organization: add organizations table, organization_id FK on users and dossier, update all queries — this is a significantly larger effort.
Scope: ~1–2 days.
UsersComponent to Athena's Angular stack with Athena roles.| Concern | InterimPlaza approach | Port to Athena | New design needed |
|---|---|---|---|
| Feature flags | Env-var @Value + HandlerInterceptor | Yes — entire pattern | Only if DB-backed toggles chosen (Option B) |
| FeatureDisabledException → 503 | GlobalExceptionHandler mapping | Yes — direct port | No |
| User entity | JPA + UserDetails, user_roles join table | Mostly — strip MFA, PII encryption, recruiter-specific fields | New role enum (ADMIN/AUDITOR/REVIEWER) |
| JWT auth stack | JwtAuthenticationFilter, JwtTokenProvider, UserPrincipal | Yes — direct port | No |
| SecurityConfig | @EnableWebSecurity, stateless, URL rules | Structure yes; URL rules must be rewritten for Athena paths | Athena-specific path rules |
| Auth endpoints | Self-register + login + MFA + email verify | Login + refresh only | Drop self-register; add admin-create-user |
| Admin user management UI | Angular UsersComponent, role/status edit | Yes — port with role names changed | No |
| Dossier ownership | N/A (no equivalent in InterimPlaza) | N/A | Yes — per-user or per-org FK |
| MFA/TOTP | Full TOTP + backup codes | Skip initially | Decision B3 |
| Rate limiting | RateLimitingFilter | Optional — port if DDoS/abuse is a concern in phase 2 | No |
Reacties