feat(auth): gate module SSO by user assignment

This commit is contained in:
Inamul-hasan-tec
2026-09-02 15:11:09 +05:30
parent 1e9da77555
commit 552fcd7be5
3 changed files with 44 additions and 11 deletions
+4 -2
View File
@@ -18,7 +18,9 @@ class ModuleController:
@staticmethod
def get_available_modules(db: Session, current_user) -> List[ModuleResponse]:
from app.schemas.auth.module_schema import ModuleAvailableResponse
results = ModuleService.get_available_modules(db, current_user.tenant_id)
results = ModuleService.get_available_modules(
db, current_user.tenant_id, current_user.role_id
)
response = []
for item in results:
@@ -46,4 +48,4 @@ class ModuleController:
@staticmethod
def delete_module(db: Session, module_id: str):
ModuleService.delete_module(db, module_id)
return {"message": "Module deleted successfully"}
return {"message": "Module deleted successfully"}
+26 -3
View File
@@ -5,6 +5,8 @@ from typing import List, Optional
import uuid
from app.models.auth.tenant_module_model import TenantModule
from app.models.auth.module_model import Module
from app.models.auth.module_access_model import ModuleAccess
from app.models.auth.role_module_access_model import RoleModuleAccess
from app.schemas.auth.module_schema import ModuleCreate, ModuleUpdate
class ModuleService:
@@ -39,15 +41,25 @@ class ModuleService:
return module
@staticmethod
def get_available_modules(db: Session, tenant_id: Optional[uuid.UUID]) -> List[dict]:
def get_available_modules(
db: Session,
tenant_id: Optional[uuid.UUID],
role_id: Optional[uuid.UUID] = None,
) -> List[dict]:
if tenant_id:
results = db.query(Module, TenantModule.is_active).join(
TenantModule,
(TenantModule.module_id == Module.id) & (TenantModule.tenant_id == tenant_id)
).join(
ModuleAccess, ModuleAccess.module_id == Module.id
).join(
RoleModuleAccess,
(RoleModuleAccess.module_access_id == ModuleAccess.id) &
(RoleModuleAccess.role_id == role_id)
).filter(
Module.status == "active",
TenantModule.is_active == True
).order_by(Module.display_order).all()
).distinct().order_by(Module.display_order).all()
return [{"module": mod, "is_active": is_active} for mod, is_active in results]
else:
@@ -58,6 +70,17 @@ class ModuleService:
def get_module_by_module_id(db: Session, module_id_str: str) -> Optional[Module]:
return db.query(Module).filter(Module.module_id == module_id_str).first()
@staticmethod
def role_can_launch_module(db: Session, role_id: Optional[uuid.UUID], module_id: uuid.UUID) -> bool:
if not role_id:
return False
return db.query(RoleModuleAccess.id).join(
ModuleAccess, ModuleAccess.id == RoleModuleAccess.module_access_id
).filter(
RoleModuleAccess.role_id == role_id,
ModuleAccess.module_id == module_id,
).first() is not None
@staticmethod
def update_module(db: Session, module_id: str, module_data: ModuleUpdate) -> Module:
module = ModuleService.get_module(db, module_id)
@@ -74,4 +97,4 @@ class ModuleService:
def delete_module(db: Session, module_id: str):
module = ModuleService.get_module(db, module_id)
db.delete(module)
db.commit()
db.commit()
+14 -6
View File
@@ -18,6 +18,7 @@ import time
from app.services.auth.subscription_entitlement_service import (
SubscriptionEntitlementService,
)
from app.services.auth.module_service import ModuleService
logger = logging.getLogger(__name__)
@@ -50,6 +51,10 @@ class SSOService:
if not tm or not tm.is_active:
raise HTTPException(status_code=403, detail="Tenant does not have access to this module")
user = db.query(User).filter(User.id == user_id).first()
if not user or not ModuleService.role_can_launch_module(db, user.role_id, module.id):
raise HTTPException(status_code=403, detail="User is not assigned to this module")
if tm.assigned_environment_slug:
environment_slug = tm.assigned_environment_slug
@@ -114,7 +119,12 @@ class SSOService:
environment_slug = "prod"
if tenant_id:
tm = db.query(TenantModule).filter(TenantModule.tenant_id == tenant_id, TenantModule.module_id == module.id).first()
if tm and tm.is_active and tm.assigned_environment_slug:
if not tm or not tm.is_active:
raise HTTPException(status_code=403, detail="Tenant does not have access to this module")
user = db.query(User).filter(User.id == user_id).first()
if not user or not ModuleService.role_can_launch_module(db, user.role_id, module.id):
raise HTTPException(status_code=403, detail="User is not assigned to this module")
if tm.assigned_environment_slug:
environment_slug = tm.assigned_environment_slug
env = db.query(ModuleEnvironment).filter(ModuleEnvironment.module_id == module.id, ModuleEnvironment.slug == environment_slug).first()
@@ -128,11 +138,9 @@ class SSOService:
if not user:
raise HTTPException(status_code=404, detail="User not found")
permissions = sorted(
SubscriptionEntitlementService.get_effective_module_access_codes(
db, user, module.id
)
)
# SaaS controls identity and module entry. Operational authorization is
# deliberately owned by the destination module.
permissions = []
timestamp = int(time.time() * 1000)
subscription_details = SubscriptionEntitlementService.get_subscription_summary(