martian7777
feat: implement backend core with ORM models, authentication, and AI-driven telemetry diagnostics
e5be436
Raw
History Blame Contribute Delete
1.92 kB
"""Password hashing and JWT token utilities."""
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from typing import Any
import bcrypt
from jose import JWTError, jwt
from app.core.config import settings
from app.core.exceptions import AuthenticationError
# bcrypt operates on at most 72 bytes; longer inputs must be truncated
# explicitly (bcrypt 5.x raises rather than silently truncating).
_BCRYPT_MAX_BYTES = 72
def _to_bcrypt_bytes(password: str) -> bytes:
return password.encode("utf-8")[:_BCRYPT_MAX_BYTES]
def hash_password(plain_password: str) -> str:
hashed = bcrypt.hashpw(_to_bcrypt_bytes(plain_password), bcrypt.gensalt())
return hashed.decode("utf-8")
def verify_password(plain_password: str, hashed_password: str) -> bool:
try:
return bcrypt.checkpw(_to_bcrypt_bytes(plain_password), hashed_password.encode("utf-8"))
except (ValueError, TypeError):
return False
def create_access_token(
subject: str,
expires_delta: timedelta | None = None,
extra_claims: dict[str, Any] | None = None,
) -> str:
expire = datetime.now(UTC) + (
expires_delta or timedelta(minutes=settings.access_token_expire_minutes)
)
to_encode: dict[str, Any] = {"sub": subject, "exp": expire, "type": "access"}
if extra_claims:
to_encode.update(extra_claims)
return jwt.encode(to_encode, settings.jwt_secret_key, algorithm=settings.jwt_algorithm)
def decode_access_token(token: str) -> str:
"""Return the subject (user id) from a valid access token or raise."""
try:
payload = jwt.decode(token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm])
except JWTError as exc:
raise AuthenticationError("Invalid or expired token") from exc
subject = payload.get("sub")
if not subject:
raise AuthenticationError("Invalid token payload")
return str(subject)