feat: local accounts password auth (argon2 + lockout + reset)
Scope
Implement real local-account authentication: argon2id password storage, lockout after N failed attempts, password-reset flow, session creation parity with OIDC sign-in.
Background
Stage 4 (#494 (closed)) ships a /auth/local-login GET stub that returns 501 when [local_accounts].enabled = true in idp.toml. This issue replaces the stub with a real implementation.
Acceptance
-
local_accountstable migration (forward-only per ADR-016): email, argon2id hash, salt, failed_count, locked_until, created_at -
GET /auth/local-loginrenders password form (Stage 4 stub becomes a real handler) -
POST /auth/local-loginaccepts credentials, argon2id verify, session creation parity with OIDC path - Lockout policy (N failures → locked for T minutes) — values configurable
- Password-reset flow (email token → reset form)
- ATO/IRS-1075 review for canopy-internal credential storage
- CHANGELOG entry