martian7777
feat: implement backend core with ORM models, authentication, and AI-driven telemetry diagnostics
e5be436 | """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) | |