f2a12f9fed
v0.4 (Operator Tier — Cohort Dashboard + Auth + Postgres) milestone complete. Phases: ✓ P0 pre-execution (planning) → v0.1.6 ✓ P1 operator foundation (Postgres+auth+VC migration) → v0.1.7 ✓ P2 cohort dashboard + aggregation → v0.1.8 ✓ P3 final review + ship → v0.1.9 (= v0.4 milestone release) Requirements covered (8/8): REQ-MT-01 (Postgres store), REQ-MT-02 (aggregation pipeline), REQ-AUTH-01 (operator auth), REQ-DASH-01 (cohort dashboard), REQ-NFR-AUTH-01 (auth NFRs), REQ-NFR-MT-01 (Postgres-in-LXC), REQ-NFR-DASH-01 (k-anonymity ≥10), REQ-NFR-DASH-02 (freshness ≤24h) Grill MUSTs honored (6/6): G-008, G-011, G-027, G-031, G-038, G-041 Tests: 317 pytest pass, 36 skip (Postgres-requiring), 0 fail; 17/17 vitest pass Review: APPROVE_WITH_NOTES (6/6 personas, 0 P0, 8 P1+ carry-forward) Audit: HEALTHY (reconstruction PASS, 8/8 REQ, 6/6 grill) ---ci--- project: praxis phase: 3 milestone: v0.4 status: complete phase_role: final milestone_complete: true milestone_merged_to_main: true tag: v0.1.9 requirements: covered: [REQ-MT-01, REQ-MT-02, REQ-AUTH-01, REQ-DASH-01, REQ-NFR-AUTH-01, REQ-NFR-MT-01, REQ-NFR-DASH-01, REQ-NFR-DASH-02] partial: [] ---/ci---
44 lines
1.4 KiB
Python
44 lines
1.4 KiB
Python
"""Argon2id password hashing (TASK-03-01, D-041, REQ-NFR-AUTH-01).
|
|
|
|
Uses argon2-cffi PasswordHasher with defaults that exceed OWASP minimums
|
|
(time_cost=3, memory_cost=64MiB, parallelism=4 — RESEARCH-v0.4 §2.1).
|
|
Single operator, low-frequency logins → hashing latency < 1s is
|
|
acceptable (R-AUTH-02).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from argon2 import PasswordHasher
|
|
from argon2.exceptions import VerifyMismatchError
|
|
|
|
_ph = PasswordHasher()
|
|
|
|
|
|
def hash_password(plain: str) -> str:
|
|
"""Hash a plaintext password with argon2id. Returns the encoded hash string."""
|
|
return _ph.hash(plain)
|
|
|
|
|
|
def verify_password(stored_hash: str, plain: str) -> bool:
|
|
"""Verify a plaintext password against a stored argon2id hash.
|
|
|
|
Returns False on mismatch (no exception) so the login flow can apply a
|
|
uniform 401 + rate-limit-increment path on any auth failure.
|
|
"""
|
|
try:
|
|
_ph.verify(stored_hash, plain)
|
|
return True
|
|
except VerifyMismatchError:
|
|
return False
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def needs_rehash(stored_hash: str) -> bool:
|
|
"""True if the stored hash was produced with weaker params than the
|
|
current PasswordHasher defaults. The login flow rehashes + updates the
|
|
store when this returns True (param upgrades without forcing a reset)."""
|
|
return _ph.check_needs_rehash(stored_hash)
|
|
|
|
|
|
__all__ = ["hash_password", "verify_password", "needs_rehash"] |