All checks were successful
Deploy to Production / deploy (push) Successful in 1m9s
- Replace passlib with direct bcrypt usage to eliminate the 'module bcrypt has no attribute __about__' warning - Change Query(regex=) to Query(pattern=) per FastAPI deprecation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
61 lines
1.7 KiB
Python
61 lines
1.7 KiB
Python
"""
|
|
Security utilities for authentication.
|
|
"""
|
|
import hashlib
|
|
from datetime import datetime, timedelta, timezone
|
|
from typing import Optional
|
|
|
|
import bcrypt
|
|
import jwt
|
|
from jwt.exceptions import PyJWTError
|
|
|
|
from app.core.config import get_settings
|
|
|
|
settings = get_settings()
|
|
|
|
|
|
def sha256_hash(password: str) -> str:
|
|
"""SHA-256 hash a raw password (matches client-side hashing)."""
|
|
return hashlib.sha256(password.encode()).hexdigest()
|
|
|
|
|
|
def verify_password(sha256_password: str, hashed_password: str) -> bool:
|
|
"""Verify a SHA-256 hashed password against its bcrypt hash."""
|
|
return bcrypt.checkpw(
|
|
sha256_password.encode(), hashed_password.encode()
|
|
)
|
|
|
|
|
|
def get_password_hash(raw_password: str) -> str:
|
|
"""Hash a raw password: SHA-256 then bcrypt."""
|
|
return bcrypt.hashpw(
|
|
sha256_hash(raw_password).encode(), bcrypt.gensalt()
|
|
).decode()
|
|
|
|
|
|
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
|
"""Create a JWT access token."""
|
|
to_encode = data.copy()
|
|
if expires_delta:
|
|
expire = datetime.now(timezone.utc) + expires_delta
|
|
else:
|
|
expire = datetime.now(timezone.utc) + timedelta(
|
|
minutes=settings.access_token_expire_minutes
|
|
)
|
|
to_encode.update({"exp": expire})
|
|
encoded_jwt = jwt.encode(
|
|
to_encode, settings.jwt_secret, algorithm=settings.jwt_algorithm
|
|
)
|
|
return encoded_jwt
|
|
|
|
|
|
def decode_access_token(token: str) -> Optional[dict]:
|
|
"""Decode and verify a JWT access token."""
|
|
try:
|
|
payload = jwt.decode(
|
|
token, settings.jwt_secret, algorithms=[settings.jwt_algorithm]
|
|
)
|
|
return payload
|
|
except PyJWTError:
|
|
return None
|