feat: add conditional bootstrap login + 23 admin login tests
- Add hash_password/verify_password to app/auth.py using bcrypt - Fix /login to reject bootstrap creds (403) when admin users exist - Add normal password-based login path for existing admin users - Eagerly load User.roles to avoid lazy-load outside async session - Fix test_user_models.py VARCHAR assertion (SQLAlchemy 2.0 uses 'string') - Use shared-cache SQLite in conftest for endpoint-level tests - Add 23 tests covering all 3 required scenarios plus edge cases: 1. Bootstrap login with hardcoded creds when no admins → PASS (200) 2. Bootstrap login with hardcoded creds when admins exist → FAIL (403) 3. Password login for existing admin with hashed password → PASS (200) - All 130 tests pass (107 existing + 23 new)
This commit is contained in:
+14
-1
@@ -7,13 +7,26 @@ import httpx
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from jose import JWTError, jwt
|
||||
from sqlalchemy import select
|
||||
from bcrypt import hashpw, checkpw, gensalt
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.config import settings
|
||||
from app.database import get_session
|
||||
from app.user_models import User
|
||||
|
||||
# ── Password hashing ───────────────────────────────────────────
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
"""Hash a plaintext password using bcrypt."""
|
||||
return hashpw(password.encode("utf-8"), gensalt()).decode("utf-8")
|
||||
|
||||
|
||||
def verify_password(plain: str, hashed: str) -> bool:
|
||||
"""Verify a plaintext password against a bcrypt hash."""
|
||||
return bool(checkpw(plain.encode("utf-8"), hashed.encode("utf-8")))
|
||||
|
||||
|
||||
class AuthBearer(HTTPBearer):
|
||||
"""HTTP Bearer scheme that doesn't auto-fail on missing token."""
|
||||
|
||||
Reference in New Issue
Block a user