| Metadata | Value |
|---|---|
| Status | Completed |
| Version | 2.0.0 |
| Last Updated | 2026-06-13 |
| Author | Sangeetha Grantha Team |
| Priority | P0 — Blocker (north-star N1) |
| Epic | TRACK-109 (W6 Security) |
| Decisions | D3 (decision log) |
| Depends on | none |
| Blocks | TRACK-110 (admin-user bootstrap hash) |
| Split out to | TRACK-119 (interactive login, rehash-on-login wiring, throttling, shared-token/roles fix) |
Replace the plaintext hashPassword() (UserManagementService.kt:143, returned its input verbatim with a // NOT SECURE TODO) with argon2id, so no password is stored in plaintext at rest, and expose a shared PasswordHasher helper that TRACK-110’s admin bootstrap reuses. Estimated ~0.5 day; execute first.
Re-scoped 2026-06-13. Investigation found that login does not verify a password —
POST /v1/auth/token(AuthRoutes.kt) is gated by a sharedADMIN_TOKENand issues a JWT with caller-suppliedroles, checking no credential. The N1 framing (“JWT issuance sits on top of plaintext credentials”) therefore does not match the code, and the real escalation risk is the shared-token + self-assigned-roles path. Given the chosen future direction is passwordless (OAuth/OTP), a full password-login flow would be throwaway work. This track is scoped to securing the hash at rest only; interactive login, rehash-on-login wiring, login throttling, and the shared-token / caller-roles fix are split out to TRACK-119. See [[auth-future-direction]].
North-star finding N1 (Blocker): credentials stored in plaintext at rest. Decision D3 selected argon2id. The shared PasswordHasher also ships verifyAllowingLegacy (returns a needsRehash flag) so the future interactive login (TRACK-119) can do rehash-on-login without redesign — but nothing verifies passwords today, so that path is intentionally dormant.
password4j 1.8.2 to gradle/libs.versions.toml (chosen over de.mkammerer:argon2-jvm for the cleaner API; Critical Rule #2).PasswordHasher.hash / verify with argon2id; cost params documented (m=19456 KiB, t=2, p=1, 32-byte hash — OWASP minimum) in support/PasswordHasher.kt.object (not private) so login and the TRACK-110 admin bootstrap (D15) produce identical PHC-format hashes.hashPassword in UserManagementService.createUser / updateUser.verifyAllowingLegacy rehash-flag paths.verifyAllowingLegacy; no login verifies a password yet).roles; retire the shared ADMIN_TOKEN login exchange.application_documentation/02-architecture/decisions/ADR-004-authentication-strategy.md (note argon2id).application_documentation/00-meta/current-versions.md (new library).