196 lines
5.9 KiB
Python
196 lines
5.9 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Annotated, Any
|
|
|
|
from bson import ObjectId
|
|
from fastapi import Depends, HTTPException, Request, status
|
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
from motor.motor_asyncio import AsyncIOMotorDatabase
|
|
|
|
from fastcheck_api.app.core.mongodb import get_db
|
|
from fastcheck_api.app.core.security import AuthError, decode_token
|
|
|
|
|
|
_bearer_scheme = HTTPBearer(auto_error=False)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CurrentUser:
|
|
id: str
|
|
email: str
|
|
role: str
|
|
tenant: str
|
|
|
|
|
|
async def get_current_user(
|
|
request: Request,
|
|
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(_bearer_scheme)] = None,
|
|
db: AsyncIOMotorDatabase = Depends(get_db),
|
|
) -> CurrentUser:
|
|
if credentials is None or not credentials.credentials:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="No token provided")
|
|
|
|
token = credentials.credentials
|
|
try:
|
|
payload = decode_token(token)
|
|
except AuthError as e:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e)) from e
|
|
|
|
user_id = str(payload.get("id") or "")
|
|
email = str(payload.get("email") or "")
|
|
role = str(payload.get("role") or "")
|
|
tenant = str(payload.get("tenant") or "")
|
|
if not user_id or not email or not role or not tenant:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload")
|
|
|
|
try:
|
|
user_oid = ObjectId(user_id)
|
|
except Exception:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid user id")
|
|
|
|
user_doc = await db["users"].find_one({"_id": user_oid}, projection={"isActive": 1, "tenant": 1})
|
|
if not user_doc or not user_doc.get("isActive", False):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail={"message": "Account not activated. Please wait for superadmin approval.", "code": "ACCOUNT_NOT_ACTIVATED"},
|
|
)
|
|
|
|
stored_tenant = user_doc.get("tenant")
|
|
stored_tenant_id = str(stored_tenant) if stored_tenant is not None else ""
|
|
if stored_tenant_id and stored_tenant_id != tenant:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload")
|
|
|
|
if role != "superuser":
|
|
try:
|
|
tenant_oid = ObjectId(tenant)
|
|
except Exception:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid tenant id")
|
|
|
|
tenant_doc = await db["tenants"].find_one({"_id": tenant_oid}, projection={"isActive": 1})
|
|
if not tenant_doc or not tenant_doc.get("isActive", False):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail={"message": "Tenant not activated. Please wait for superadmin approval.", "code": "TENANT_NOT_ACTIVATED"},
|
|
)
|
|
|
|
current = CurrentUser(id=user_id, email=email, role=role, tenant=tenant)
|
|
request.state.current_user = current
|
|
request.state.tenant_id = tenant
|
|
return current
|
|
|
|
|
|
Permission = str
|
|
|
|
|
|
ROLE_PERMISSIONS: dict[str, set[Permission]] = {
|
|
"superuser": {
|
|
"user:create",
|
|
"user:read",
|
|
"user:update",
|
|
"user:delete",
|
|
"evaluation:create",
|
|
"evaluation:read",
|
|
"evaluation:update",
|
|
"evaluation:delete",
|
|
"evaluation:bulk",
|
|
"company:read",
|
|
"company:update",
|
|
"rut:lookup",
|
|
"rut:read",
|
|
"sheriff:logs:read",
|
|
"monitoring:create",
|
|
"monitoring:read",
|
|
"monitoring:update",
|
|
"monitoring:delete",
|
|
"monitoring:execute",
|
|
"tenant:read",
|
|
"tenant:update",
|
|
"tenant:users:manage",
|
|
"admin:dashboard",
|
|
"admin:settings",
|
|
"admin:logs",
|
|
"notification:read",
|
|
"notification:create",
|
|
},
|
|
"tenant_admin": {
|
|
"user:create",
|
|
"user:read",
|
|
"user:update",
|
|
"user:delete",
|
|
"evaluation:create",
|
|
"evaluation:read",
|
|
"evaluation:update",
|
|
"evaluation:delete",
|
|
"evaluation:bulk",
|
|
"company:read",
|
|
"company:update",
|
|
"rut:lookup",
|
|
"rut:read",
|
|
"sheriff:logs:read",
|
|
"monitoring:create",
|
|
"monitoring:read",
|
|
"monitoring:update",
|
|
"monitoring:delete",
|
|
"monitoring:execute",
|
|
"tenant:read",
|
|
"tenant:update",
|
|
"tenant:users:manage",
|
|
"admin:dashboard",
|
|
"notification:read",
|
|
"notification:create",
|
|
},
|
|
"evaluator": {
|
|
"user:read",
|
|
"evaluation:create",
|
|
"evaluation:read",
|
|
"evaluation:update",
|
|
"evaluation:delete",
|
|
"evaluation:bulk",
|
|
"company:read",
|
|
"rut:lookup",
|
|
"rut:read",
|
|
"sheriff:logs:read",
|
|
"monitoring:create",
|
|
"monitoring:read",
|
|
"monitoring:update",
|
|
"monitoring:execute",
|
|
"tenant:read",
|
|
"notification:read",
|
|
},
|
|
"read_only": {
|
|
"user:read",
|
|
"evaluation:read",
|
|
"company:read",
|
|
"rut:read",
|
|
"sheriff:logs:read",
|
|
"monitoring:read",
|
|
"tenant:read",
|
|
"notification:read",
|
|
},
|
|
"write_only": {
|
|
"evaluation:create",
|
|
"evaluation:read",
|
|
"company:read",
|
|
"rut:lookup",
|
|
"monitoring:create",
|
|
"monitoring:execute",
|
|
"notification:create",
|
|
"tenant:read",
|
|
"tenant:update",
|
|
},
|
|
}
|
|
|
|
|
|
def require_permission(permission: Permission):
|
|
async def _dep(user: Annotated[CurrentUser, Depends(get_current_user)]) -> CurrentUser:
|
|
perms = ROLE_PERMISSIONS.get(user.role, set())
|
|
if permission not in perms:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail={"message": "Forbidden: Insufficient permissions", "required": permission, "userRole": user.role},
|
|
)
|
|
return user
|
|
|
|
return _dep
|