Athena — feature-toggle-and-user-management-proposal.md

Feature-Toggle & User Management — Architecture Proposal

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)


1. Context and Research Basis

This document is based on:

  • Athena (current repo): a financial audit tool with zero auth, zero feature-flag infrastructure, all endpoints publicly accessible. Only has WebConfig (CORS) and a completely unprotected AdminController at DELETE /api/admin/wipe-all.
  • InterimPlaza (sibling repo): a multi-tenant recruitment SaaS with a full JWT auth stack, ADMIN/RECRUITER/USER roles, rate limiting, MFA/TOTP, and a static env-var feature-gate interceptor for the CV feature.

2. Feature-Toggle System

2.1 How InterimPlaza does it

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:

  • A feature_flags database table.
  • A backend API to read/write flags at runtime.
  • An admin UI panel to toggle flags without a redeploy.

2.2 What Athena should adopt

Option A — Port InterimPlaza verbatim (env-var only, no DB)

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.
  • The interceptor pattern: one class per guarded feature group implementing HandlerInterceptor.
  • WebMvcConfig using addInterceptors() to bind each interceptor to its URL patterns.
  • Application property naming convention: app.features.<name>.enabled / env <NAME>_FEATURE_ENABLED.

Athena-specific adaptation:

  • Rename the interceptor to match the first feature being gated (e.g. PdfExportFeatureGateInterceptor or DossierFeatureGateInterceptor).
  • The config/ package already exists (nl.glorylabs.athena.config). Both the interceptor and WebMvcConfig live there.

Option B — DB-backed toggles with admin UI (runtime flip, no redeploy)

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).
  • The interceptor reads from FeatureFlagService instead of a @Value.

Frontend:

  • Admin page section listing all flags as toggle switches.
  • Calls 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.

2.3 Recommendation

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.


3. User Management & Authentication

3.1 How InterimPlaza does it

InterimPlaza has a complete production-grade auth stack:

Entity & roles:

  • User JPA entity implements Spring Security's UserDetails. Stored in users table.
  • Roles stored in a user_roles join table as an @ElementCollection of an inner Role enum: ADMIN, RECRUITER, USER.
  • Additional fields: emailVerified, emailVerificationToken, totpSecret/totpEnabled (MFA), passwordResetToken, tokensValidFrom (per-user token revocation epoch), accountNonLocked, active, lastLoginAt.
  • PII fields (phoneNumber, company, position) use an EncryptedStringConverter.

Security stack:

  • SecurityConfig with @EnableWebSecurity and @EnableMethodSecurity.
  • Stateless sessions (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).
  • Method-level security via @PreAuthorize on sensitive controller methods.
  • URL-level rules: /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.
  • Email verification and password reset flows.
  • MFA: TOTP setup/enable/disable, backup codes.

Admin user management UI:

  • Angular component (UsersComponent) with paginated list, search, role filter, status filter.
  • Calls: 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}.
  • Edit modal for changing a user's role.

3.2 What Athena currently has

  • Zero Spring Security. spring-boot-starter-security is not in pom.xml.
  • Zero auth on any endpoint. DELETE /api/admin/wipe-all is callable by any anonymous HTTP client.
  • No SecurityConfig. Only WebConfig (CORS).
  • No user concept at all. Dossiers and customers are not owned by any user.
  • The Angular admin page only calls the wipe-all endpoint.

3.3 What is directly portable from InterimPlaza

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 |

3.4 What needs new design (Athena-specific)

3.4.1 Roles

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?

3.4.2 User creation model

InterimPlaza supports self-service registration (anyone can sign up). Athena is a professional tool for accounting firms — self-service registration is probably wrong. Instead:

  • An ADMIN user creates accounts for all AUDITOR/REVIEWER users.
  • The admin sets an initial password (or triggers a password-set email).
  • No public /register endpoint.
  • Open decision B2: Should user provisioning be admin-only (no self-register), or is self-register with invite code acceptable?

3.4.3 PII encryption and MFA

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?

3.4.4 Token revocation

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.

3.4.5 Dossier / Customer ownership

Currently Customer and Dossier have no userId or organizationId FK. Once users exist, dossiers must be owned. Two models:

  • Per-user ownership: Each dossier belongs to the AUDITOR who created it. Other users cannot see it unless explicitly shared.
  • Firm-level ownership: All users in the same firm share all dossiers (multi-tenant by organization, not by user).

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.

3.4.6 Upload-link auth

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.

3.5 Admin UI for user management

The Angular admin page (/pages/admin/) currently only has the wipe-all button. Once auth exists, it needs:

  • User list (paginated, filterable by role/status) — can port UsersComponent from InterimPlaza.
  • Create user form (email, name, role, initial password or invite flow).
  • Edit user (change role, enable/disable account).

This is a straight port of InterimPlaza's admin users UI with the role names swapped.


4. Open Decisions Requiring Owner Sign-Off

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 |


5. Phased Implementation Plan

The phases below assume owner sign-off on the open decisions above.

Phase 1 — Feature Flags (env-var, Option A)

Scope: 3 new files, ~1 day.

  1. Add FeatureDisabledException to nl.glorylabs.athena.analysis.exception (or a new nl.glorylabs.athena.featureflag.exception).
  2. Wire it to 503 in the global exception handler.
  3. Create the first HandlerInterceptor for whichever feature the owner wants to gate first (e.g. PDF export, dossier persistence).
  4. Update WebConfig (or add a new WebMvcConfig) to register the interceptor.

No DB migration needed. No security dependency needed.

Phase 2 — Auth Foundation (if B5 decision is "yes, add auth now")

Scope: ~1–2 weeks.

  1. Add Maven deps: spring-boot-starter-security, jjwt-api, jjwt-impl, jjwt-jackson.
  2. Create nl.glorylabs.athena.security package: port JwtTokenProvider, JwtAuthenticationFilter, UserPrincipal, JwtAuthenticationEntryPoint, JwtAccessDeniedHandler.
  3. Create User entity with Athena roles (ADMIN / AUDITOR / REVIEWER), Flyway migration V3__users.sql.
  4. Create UserRepository, UserDetailsService implementation.
  5. Create SecurityConfig with URL rules matching Athena's actual API surface:
    • permitAll: /api/auth/**, /api/public/**, /actuator/health/**.
    • ADMIN: /api/admin/**.
    • authenticated: all other /api/**.
  6. Create admin-only POST /api/auth/users endpoint (create user) — no self-register.
  7. Create POST /api/auth/login and POST /api/auth/refresh (port from InterimPlaza).

Phase 3 — Dossier Ownership Migration

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.

Phase 4 — Admin UI

Scope: ~1–2 days.

  1. Port interimplaza's UsersComponent to Athena's Angular stack with Athena roles.
  2. Add user management section to the admin page.
  3. If Phase 1 Option B was chosen: add feature-flag toggle UI.

6. Summary Table

| 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

Nog geen reacties