feat(appliance): initial release of MERICA standalone appliance v1.0.0

This commit is contained in:
2026-10-21 17:16:46 +07:00
commit 851be0cda7
144 changed files with 31135 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
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