feat(auth): email+password registration with two-gate activation (5a backend)
Add email+password auth alongside Google: argon2id hashing; users gains password_hash/email_verified/is_active and google_sub becomes nullable (migration 0022); a single-use, hashed auth_tokens table for email verification + password reset. Registration creates a pending (inactive, unverified) account + an access request; sign-in needs verified email AND admin approval. Anti-enumeration: uniform register/login/reset responses (a correct-password owner still gets a specific pending reason). allow_registration flag (DB-tunable). Admin users-list + role endpoint (guards: not self, not demo, not the last admin); approving access (or a manual whitelist add) now activates the matching pending account. current_user rejects deactivated accounts. Verified end-to-end via curl + DB.
This commit is contained in:
parent
62f46de301
commit
8f5e26d2ee
9 changed files with 398 additions and 6 deletions
|
|
@ -1,7 +1,25 @@
|
|||
from argon2 import PasswordHasher
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
from app.config import settings
|
||||
|
||||
# argon2id with the library defaults (sensible memory/time cost). One shared hasher instance.
|
||||
_ph = PasswordHasher()
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return _ph.hash(password)
|
||||
|
||||
|
||||
def verify_password(password: str, hashed: str | None) -> bool:
|
||||
if not hashed:
|
||||
return False
|
||||
try:
|
||||
return _ph.verify(hashed, password)
|
||||
except Exception:
|
||||
# Any failure (mismatch, malformed hash) is a non-match — never raise to the caller.
|
||||
return False
|
||||
|
||||
_fernet: Fernet | None = (
|
||||
Fernet(settings.token_encryption_key.encode()) if settings.token_encryption_key else None
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue