"""REST service-manager dependencies and auth helpers."""from__future__importannotationsimportasynciofromcollectionsimportdefaultdictfromuuidimportUUIDfromfastapiimportHTTPExceptionfromtangl.rest.dependenciesimportget_persistencefromtangl.serviceimportServiceAccess,ServiceManager,UserAuthInfo,build_service_manager,user_id_by_key_service_manager:ServiceManager|None=None_user_locks:defaultdict[UUID,asyncio.Lock]=defaultdict(asyncio.Lock)_api_key_index:dict[str,UUID]={}def_build_service_manager()->ServiceManager:returnbuild_service_manager(get_persistence())
[docs]defget_service_manager()->ServiceManager:"""Return the process-wide service-manager singleton."""global_service_managerif_service_managerisNone:_service_manager=_build_service_manager()return_service_manager
def_resolve_user_auth_from_key(api_key:str,*,service_manager:ServiceManager|None=None,)->UserAuthInfo:manager=service_managerorget_service_manager()auth=user_id_by_key(api_key,manager.persistence,reverse_index=_api_key_index,)ifauthisNone:raiseValueError("Invalid API key")returnauth
[docs]defget_user_locks()->dict[UUID,asyncio.Lock]:"""Provide per-user asyncio locks for story routes."""return_user_locks
[docs]defresolve_user_auth(api_key:str,*,service_manager:ServiceManager|None=None,)->UserAuthInfo:"""Resolve API key to user auth context for route handlers."""try:return_resolve_user_auth_from_key(api_key,service_manager=service_manager)exceptValueErrorasexc:raiseHTTPException(status_code=401,detail=str(exc))fromexc
[docs]defrequire_service_access(method_name:str,*,user_auth:UserAuthInfo|None=None,)->None:"""Enforce service-method access metadata for transport calls."""try:spec=ServiceManager.get_service_methods()[method_name]exceptKeyErrorasexc:# pragma: no cover - programmer errorraiseRuntimeError(f"Unknown service method: {method_name}")fromexcifspec.access==ServiceAccess.PUBLIC:returnifuser_authisNone:raiseHTTPException(status_code=401,detail="Authentication required")ifspec.access==ServiceAccess.DEVandnotuser_auth.is_privileged:raiseHTTPException(status_code=403,detail="Access denied")