first commit
This commit is contained in:
70
backend/app/application/auth/context.py
Normal file
70
backend/app/application/auth/context.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from app.domain.permissions import build_permission_context
|
||||
from app.repositories.directus.client import DirectusClient
|
||||
from app.schemas.auth import AuthenticatedUser, PermissionContextRead
|
||||
|
||||
|
||||
class AuthContextService:
|
||||
"""Validates Directus-issued tokens and normalizes user context.
|
||||
|
||||
FastAPI remains the formal API entrypoint, but Directus is still the source
|
||||
of truth for login/session/role data. This service is the seam between them.
|
||||
"""
|
||||
|
||||
# Keep the first auth bootstrap intentionally conservative.
|
||||
# Directus `/users/me` on this project reliably returns the core user/role
|
||||
# fields below, while deeper `user_group.*` expansions can collapse the
|
||||
# payload back to only `{id}`. We can layer group/domain permissions back in
|
||||
# later once the base admin flow is verified end to end.
|
||||
me_fields = [
|
||||
"email",
|
||||
"first_name",
|
||||
"id",
|
||||
"role.id",
|
||||
"role.name",
|
||||
"status",
|
||||
]
|
||||
|
||||
def __init__(self, client: DirectusClient | None = None) -> None:
|
||||
self.client = client or DirectusClient()
|
||||
|
||||
async def get_authenticated_user(self, access_token: str) -> AuthenticatedUser:
|
||||
if not access_token:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Missing access token.",
|
||||
)
|
||||
|
||||
me = await self.client.get_current_user(
|
||||
access_token=access_token,
|
||||
fields=self.me_fields,
|
||||
)
|
||||
if not me:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Unable to resolve current user from Directus.",
|
||||
)
|
||||
|
||||
user_group = me.get("user_group") or {}
|
||||
permissions = user_group.get("domain_permissions") or []
|
||||
normalized_permissions = [str(permission) for permission in permissions]
|
||||
role = me.get("role") or {}
|
||||
permission_context = build_permission_context(
|
||||
role_name=role.get("name"),
|
||||
domain_permissions=normalized_permissions,
|
||||
)
|
||||
|
||||
# Keep the normalized payload explicit so downstream services do not need
|
||||
# to know the exact nested Directus response shape.
|
||||
return AuthenticatedUser.model_validate(
|
||||
{
|
||||
**me,
|
||||
"domain_permissions": normalized_permissions,
|
||||
"permissions": PermissionContextRead.model_validate(asdict(permission_context)),
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user