BIG BIG BIG BIG BIG UPDATES

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
gabdevele
2026-05-04 23:59:48 +02:00
co-authored by Copilot
Unverified
parent 46f7cd6a5f
commit 93ba8e9ac2
88 changed files with 7591 additions and 447 deletions
+116
View File
@@ -0,0 +1,116 @@
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from typing import Annotated
from uuid import uuid4
from authlib.jose import jwt # type: ignore
from authlib.jose.errors import JoseError # type: ignore
from core.config import settings
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from models.auth import AccessTokenPayload, AuthUserDocument
from pwdlib import PasswordHash
from pwdlib.hashers.argon2 import Argon2Hasher
from repositories.auth import get_user_by_username, is_token_revoked
pwd_context = PasswordHash((Argon2Hasher(),))
bearer_scheme = HTTPBearer(auto_error=False)
@dataclass(slots=True)
class AuthContext:
token: str
user: AuthUserDocument
payload: AccessTokenPayload
class InvalidTokenError(Exception):
pass
def token_auth_error() -> HTTPException:
return HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain_password: str, password_hash: str) -> bool:
if hasattr(pwd_context, "verify"):
return pwd_context.verify(plain_password, password_hash)
return pwd_context.check(plain_password, password_hash)
def create_access_token(*, subject: str) -> str:
now = datetime.now(UTC)
expires_delta = timedelta(minutes=settings.JWT_EXPIRE_MINUTES)
expires_at = int((now + expires_delta).timestamp())
header = {
"alg": settings.JWT_ALGORITHM,
"typ": "JWT",
}
payload = {
"sub": subject,
"jti": str(uuid4()),
"exp": expires_at,
}
token = jwt.encode(header, payload, settings.JWT_SECRET_KEY)
if isinstance(token, bytes):
return token.decode("utf-8")
return token
def decode_access_token(token: str) -> AccessTokenPayload:
try:
claims = jwt.decode(token, settings.JWT_SECRET_KEY)
claims.validate()
return AccessTokenPayload.model_validate(dict(claims))
except JoseError, ValueError, TypeError:
raise InvalidTokenError from None
async def authenticate_token(token: str) -> AuthContext:
try:
payload = decode_access_token(token)
except InvalidTokenError:
raise InvalidTokenError from None
if await is_token_revoked(payload.jti):
raise InvalidTokenError
user = await get_user_by_username(payload.sub)
if user is None:
raise InvalidTokenError
return AuthContext(token=token, user=user, payload=payload)
async def get_auth_context(
credentials: Annotated[
HTTPAuthorizationCredentials | None,
Depends(bearer_scheme),
],
) -> AuthContext:
if credentials is None or credentials.scheme.lower() != "bearer":
raise token_auth_error()
try:
return await authenticate_token(credentials.credentials)
except InvalidTokenError:
raise token_auth_error() from None
async def require_authenticated_user(
auth_context: Annotated[AuthContext, Depends(get_auth_context)],
) -> AuthUserDocument:
return auth_context.user