60 lines
2.0 KiB
Python
60 lines
2.0 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
from datetime import UTC, datetime, timedelta
|
|
from typing import Any
|
|
|
|
from jose import JWTError, jwt
|
|
from passlib.context import CryptContext
|
|
|
|
from app.core.config import settings
|
|
|
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
|
|
|
|
def hash_password(password: str) -> str:
|
|
"""Hash a password using bcrypt."""
|
|
return pwd_context.hash(password)
|
|
|
|
|
|
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
"""Verify a plain password against a hashed password."""
|
|
return pwd_context.verify(plain_password, hashed_password)
|
|
|
|
|
|
def create_access_token(data: dict[str, Any], expires_delta: timedelta | None = None) -> str:
|
|
"""Create a JWT access token."""
|
|
to_encode = data.copy()
|
|
expire = datetime.now(UTC) + (expires_delta or timedelta(minutes=settings.jwt_access_token_expire_minutes))
|
|
to_encode.update({"exp": expire, "type": "access"})
|
|
return jwt.encode(to_encode, settings.jwt_secret_key, algorithm=settings.jwt_algorithm)
|
|
|
|
|
|
def create_refresh_token(data: dict[str, Any], expires_delta: timedelta | None = None) -> str:
|
|
"""Create a JWT refresh token."""
|
|
to_encode = data.copy()
|
|
expire = datetime.now(UTC) + (expires_delta or timedelta(days=settings.jwt_refresh_token_expire_days))
|
|
to_encode.update({
|
|
"exp": expire,
|
|
"type": "refresh",
|
|
"jti": str(uuid.uuid4()),
|
|
})
|
|
return jwt.encode(to_encode, settings.jwt_secret_key, algorithm=settings.jwt_algorithm)
|
|
|
|
|
|
def decode_token(token: str) -> dict[str, Any]:
|
|
"""Decode and validate a JWT token."""
|
|
try:
|
|
payload = jwt.decode(token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm])
|
|
return payload
|
|
except JWTError as e:
|
|
raise InvalidTokenError(str(e)) from e
|
|
|
|
|
|
class InvalidTokenError(Exception):
|
|
"""Raised when a JWT token is invalid or expired."""
|
|
|
|
def __init__(self, detail: str = "Invalid or expired token") -> None:
|
|
self.detail = detail
|
|
super().__init__(self.detail)
|