83 lines
3.5 KiB
Python
83 lines
3.5 KiB
Python
from typing import List, Optional
|
|||
|
|
from fastapi import Depends, HTTPException, status
|
||
|
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||
|
|
|
||
|
|
from app.core.auth.models import UserSession
|
||
|
|
from app.core.auth.security import decode_session_token
|
||
|
|
|
||
|
|
security_scheme = HTTPBearer(auto_error=False)
|
||
|
|
|
||
|
|
|
||
|
|
async def get_current_user(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security_scheme)) -> UserSession:
|
||
|
|
"""Extract and validate current authenticated user from session token."""
|
||
|
|
if not credentials or not credentials.credentials:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
|
|
detail="Otentikasi diperlukan. Silakan login terlebih dahulu.",
|
||
|
|
headers={"WWW-Authenticate": "Bearer"},
|
||
|
|
)
|
||
|
|
|
||
|
|
payload = decode_session_token(credentials.credentials)
|
||
|
|
if not payload:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
|
|
detail="Token sesi tidak valid atau telah kedaluwarsa. Silakan login kembali.",
|
||
|
|
headers={"WWW-Authenticate": "Bearer"},
|
||
|
|
)
|
||
|
|
|
||
|
|
user_data = payload.get("user")
|
||
|
|
if not user_data:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
|
|
detail="Struktur data sesi tidak valid.",
|
||
|
|
headers={"WWW-Authenticate": "Bearer"},
|
||
|
|
)
|
||
|
|
|
||
|
|
return UserSession(**user_data)
|
||
|
|
|
||
|
|
|
||
|
|
async def get_optional_current_user(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security_scheme)) -> Optional[UserSession]:
|
||
|
|
"""Extract current user if token is present, otherwise return None."""
|
||
|
|
if not credentials or not credentials.credentials:
|
||
|
|
return None
|
||
|
|
payload = decode_session_token(credentials.credentials)
|
||
|
|
if not payload or not payload.get("user"):
|
||
|
|
return None
|
||
|
|
try:
|
||
|
|
return UserSession(**payload["user"])
|
||
|
|
except Exception:
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def require_capabilities(allowed_caps: List[str]):
|
||
|
|
"""Enforce that the current user possesses one of the allowed capabilities."""
|
||
|
|
async def capability_checker(current_user: UserSession = Depends(get_current_user)) -> UserSession:
|
||
|
|
if current_user.capability not in allowed_caps and not current_user.is_admin:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
||
|
|
detail=f"Akses ditolak: Akun Anda memiliki peran '{current_user.capability}'. Tindakan ini memerlukan hak akses '{'/'.join(allowed_caps)}'."
|
||
|
|
)
|
||
|
|
return current_user
|
||
|
|
return capability_checker
|
||
|
|
|
||
|
|
|
||
|
|
# Shortcuts
|
||
|
|
async def require_admin(current_user: UserSession = Depends(get_current_user)) -> UserSession:
|
||
|
|
"""Enforce Administrator role (Full access + Settings control)."""
|
||
|
|
if not current_user.is_admin and current_user.capability != "admin":
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
||
|
|
detail="Akses ditolak: Menu Pengaturan dan kontrol IAM hanya dapat diakses oleh Administrator / Superadmin."
|
||
|
|
)
|
||
|
|
return current_user
|
||
|
|
|
||
|
|
|
||
|
|
async def require_editor(current_user: UserSession = Depends(get_current_user)) -> UserSession:
|
||
|
|
"""Enforce Editor capability (Data Engineer / Full Canvas Access)."""
|
||
|
|
if not current_user.can_edit and not current_user.is_admin:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
||
|
|
detail="Akses ditolak: Akun Anda memiliki peran Read-Only (Pimpinan/Auditor). Perubahan atau deployment DAG hanya dapat dilakukan oleh Data Engineer."
|
||
|
|
)
|
||
|
|
return current_user
|