Files
boc/landvex-admin/backend/app/core/security.py
T

104 lines
2.7 KiB
Python
Raw Normal View History

"""Security utilities for authentication and authorization."""
from datetime import datetime, timedelta
from typing import Any
from uuid import UUID
import jwt
from passlib.context import CryptContext
from app.config import settings
# Password hashing with Argon2id
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""Verify a plain password against a hash."""
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
"""Hash a password using Argon2id."""
return pwd_context.hash(password)
def create_access_token(
subject: UUID | str,
tenant_id: UUID | None = None,
scopes: list[str] | None = None,
expires_delta: timedelta | None = None,
) -> str:
"""Create JWT access token."""
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode: dict[str, Any] = {
"sub": str(subject),
"exp": expire,
"iat": datetime.utcnow(),
"type": "access",
}
if tenant_id:
to_encode["tenant_id"] = str(tenant_id)
if scopes:
to_encode["scopes"] = scopes
encoded_jwt = jwt.encode(
to_encode, settings.SECRET_KEY, algorithm=settings.JWT_ALGORITHM
)
return encoded_jwt
def create_refresh_token(subject: UUID | str) -> str:
"""Create JWT refresh token."""
expire = datetime.utcnow() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
to_encode: dict[str, Any] = {
"sub": str(subject),
"exp": expire,
"iat": datetime.utcnow(),
"type": "refresh",
}
encoded_jwt = jwt.encode(
to_encode, settings.SECRET_KEY, algorithm=settings.JWT_ALGORITHM
)
return encoded_jwt
def decode_token(token: str) -> dict[str, Any]:
"""Decode and validate JWT token."""
return jwt.decode(
token, settings.SECRET_KEY, algorithms=[settings.JWT_ALGORITHM]
)
def generate_api_key() -> tuple[str, str]:
"""Generate a new API key and its hash.
Returns:
Tuple of (full_key, key_hash)
"""
import secrets
import hashlib
# Generate random key: lv_<32 random chars>
random_part = secrets.token_urlsafe(32)
full_key = f"lv_{random_part}"
# Hash for storage
key_hash = hashlib.sha256(full_key.encode()).hexdigest()
return full_key, key_hash
def verify_api_key(plain_key: str, key_hash: str) -> bool:
"""Verify an API key against its hash."""
import hashlib
computed_hash = hashlib.sha256(plain_key.encode()).hexdigest()
return computed_hash == key_hash