diff --git a/supertokens_python/recipe/dashboard/api/userroles/add_role_to_user.py b/supertokens_python/recipe/dashboard/api/userroles/add_role_to_user.py new file mode 100644 index 00000000..39f9b9d4 --- /dev/null +++ b/supertokens_python/recipe/dashboard/api/userroles/add_role_to_user.py @@ -0,0 +1,71 @@ +from typing import Any, Union +from typing_extensions import Literal +from supertokens_python.exceptions import raise_bad_input_exception +from supertokens_python.recipe.dashboard.interfaces import APIInterface, APIOptions +from supertokens_python.recipe.userroles.asyncio import add_role_to_user +from supertokens_python.recipe.userroles.interfaces import AddRoleToUserOkResult +from supertokens_python.recipe.userroles.recipe import UserRolesRecipe +from supertokens_python.types import APIResponse + + +class OkResponse(APIResponse): + def __init__(self, did_user_already_have_role: bool): + self.status: Literal["OK"] = "OK" + self.did_user_already_have_role = did_user_already_have_role + + def to_json(self): + return { + "status": self.status, + "didUserAlreadyHaveRole": self.did_user_already_have_role, + } + + +class FeatureNotEnabledErrorResponse(APIResponse): + def __init__(self): + self.status: Literal["FEATURE_NOT_ENABLED_ERROR"] = "FEATURE_NOT_ENABLED_ERROR" + + def to_json(self): + return {"status": self.status} + + +class UnknownRoleErrorResponse(APIResponse): + def __init__(self): + self.status: Literal["UNKNOWN_ROLE_ERROR"] = "UNKNOWN_ROLE_ERROR" + + def to_json(self): + return {"status": self.status} + + +async def add_role_to_user_api( + _: APIInterface, tenant_id: str, api_options: APIOptions, __: Any +) -> Union[OkResponse, FeatureNotEnabledErrorResponse, UnknownRoleErrorResponse]: + try: + UserRolesRecipe.get_instance() + except Exception: + return FeatureNotEnabledErrorResponse() + + request_body = await api_options.request.json() + if request_body is None: + raise_bad_input_exception("Request body is missing") + + user_id = request_body.get("userId") + role = request_body.get("role") + + if role is None or not isinstance(role, str): + raise_bad_input_exception( + "Required parameter 'role' is missing or has an invalid type" + ) + + if user_id is None or not isinstance(user_id, str): + raise_bad_input_exception( + "Required parameter 'userId' is missing or has an invalid type" + ) + + response = await add_role_to_user(tenant_id, user_id, role) + + if isinstance(response, AddRoleToUserOkResult): + return OkResponse( + did_user_already_have_role=response.did_user_already_have_role + ) + else: + return UnknownRoleErrorResponse() diff --git a/supertokens_python/recipe/dashboard/api/userroles/get_role_to_user.py b/supertokens_python/recipe/dashboard/api/userroles/get_role_to_user.py new file mode 100644 index 00000000..2cbd79bb --- /dev/null +++ b/supertokens_python/recipe/dashboard/api/userroles/get_role_to_user.py @@ -0,0 +1,46 @@ +from typing import Any, Union +from typing_extensions import Literal +from supertokens_python.exceptions import raise_bad_input_exception +from supertokens_python.recipe.dashboard.interfaces import APIInterface, APIOptions +from supertokens_python.recipe.userroles.asyncio import get_roles_for_user +from supertokens_python.recipe.userroles.recipe import UserRolesRecipe +from supertokens_python.types import APIResponse + + +class OkResponse(APIResponse): + def __init__(self, roles: list[str]): + self.status: Literal["OK"] = "OK" + self.roles = roles + + def to_json(self): + return { + "status": self.status, + "roles": self.roles, + } + + +class FeatureNotEnabledErrorResponse(APIResponse): + def __init__(self): + self.status: Literal["FEATURE_NOT_ENABLED_ERROR"] = "FEATURE_NOT_ENABLED_ERROR" + + def to_json(self): + return {"status": self.status} + + +async def get_roles_for_user_api( + _: APIInterface, tenant_id: str, api_options: APIOptions, __: Any +) -> Union[OkResponse, FeatureNotEnabledErrorResponse]: + try: + UserRolesRecipe.get_instance() + except Exception: + return FeatureNotEnabledErrorResponse() + + user_id = api_options.request.get_query_param("userId") + + if user_id is None: + raise_bad_input_exception( + "Required parameter 'userId' is missing or has an invalid type" + ) + + response = await get_roles_for_user(tenant_id, user_id) + return OkResponse(roles=response.roles) diff --git a/supertokens_python/recipe/dashboard/api/userroles/remove_user_role.py b/supertokens_python/recipe/dashboard/api/userroles/remove_user_role.py new file mode 100644 index 00000000..b110d79d --- /dev/null +++ b/supertokens_python/recipe/dashboard/api/userroles/remove_user_role.py @@ -0,0 +1,65 @@ +from typing import Any, Union +from typing_extensions import Literal +from supertokens_python.exceptions import raise_bad_input_exception +from supertokens_python.recipe.dashboard.interfaces import APIInterface, APIOptions +from supertokens_python.recipe.userroles.asyncio import remove_user_role +from supertokens_python.recipe.userroles.interfaces import RemoveUserRoleOkResult +from supertokens_python.recipe.userroles.recipe import UserRolesRecipe +from supertokens_python.types import APIResponse + + +class OkResponse(APIResponse): + def __init__(self, did_user_have_role: bool): + self.status: Literal["OK"] = "OK" + self.did_user_have_role = did_user_have_role + + def to_json(self): + return { + "status": self.status, + "didUserHaveRole": self.did_user_have_role, + } + + +class FeatureNotEnabledErrorResponse(APIResponse): + def __init__(self): + self.status: Literal["FEATURE_NOT_ENABLED_ERROR"] = "FEATURE_NOT_ENABLED_ERROR" + + def to_json(self): + return {"status": self.status} + + +class UnknownRoleErrorResponse(APIResponse): + def __init__(self): + self.status: Literal["UNKNOWN_ROLE_ERROR"] = "UNKNOWN_ROLE_ERROR" + + def to_json(self): + return {"status": self.status} + + +async def remove_user_role_api( + _: APIInterface, tenant_id: str, api_options: APIOptions, __: Any +) -> Union[OkResponse, FeatureNotEnabledErrorResponse, UnknownRoleErrorResponse]: + try: + UserRolesRecipe.get_instance() + except Exception: + return FeatureNotEnabledErrorResponse() + + user_id = api_options.request.get_query_param("userId") + role = api_options.request.get_query_param("role") + + if role is None: + raise_bad_input_exception( + "Required parameter 'role' is missing or has an invalid type" + ) + + if user_id is None: + raise_bad_input_exception( + "Required parameter 'userId' is missing or has an invalid type" + ) + + response = await remove_user_role(tenant_id, user_id, role) + + if isinstance(response, RemoveUserRoleOkResult): + return OkResponse(did_user_have_role=response.did_user_have_role) + else: + return UnknownRoleErrorResponse() diff --git a/supertokens_python/recipe/dashboard/api/userroles/roles/create_role_or_add_permissions.py b/supertokens_python/recipe/dashboard/api/userroles/roles/create_role_or_add_permissions.py new file mode 100644 index 00000000..e5a89b4f --- /dev/null +++ b/supertokens_python/recipe/dashboard/api/userroles/roles/create_role_or_add_permissions.py @@ -0,0 +1,54 @@ +from typing import Any, List, Union +from supertokens_python.exceptions import raise_bad_input_exception +from supertokens_python.recipe.dashboard.interfaces import APIInterface, APIOptions +from supertokens_python.recipe.userroles.asyncio import ( + create_new_role_or_add_permissions, +) +from supertokens_python.recipe.userroles.recipe import UserRolesRecipe +from supertokens_python.types import APIResponse + + +class OkResponse(APIResponse): + def __init__(self, created_new_role: bool): + self.status = "OK" + self.created_new_role = created_new_role + + def to_json(self): + return {"status": self.status, "createdNewRole": self.created_new_role} + + +class FeatureNotEnabledErrorResponse(APIResponse): + def __init__(self): + self.status = "FEATURE_NOT_ENABLED_ERROR" + + def to_json(self): + return {"status": self.status} + + +async def create_role_or_add_permissions_api( + _: APIInterface, __: str, api_options: APIOptions, ___: Any +) -> Union[OkResponse, FeatureNotEnabledErrorResponse]: + try: + UserRolesRecipe.get_instance() + except Exception: + return FeatureNotEnabledErrorResponse() + + request_body = await api_options.request.json() + if request_body is None: + raise_bad_input_exception("Request body is missing") + + role = request_body.get("role") + permissions: Union[List[str], None] = request_body.get("permissions") + + if role is None or not isinstance(role, str): + raise_bad_input_exception( + "Required parameter 'role' is missing or has an invalid type" + ) + + if permissions is None: + raise_bad_input_exception( + "Required parameter 'permissions' is missing or has an invalid type" + ) + + response = await create_new_role_or_add_permissions(role, permissions) + return OkResponse(created_new_role=response.created_new_role) diff --git a/supertokens_python/recipe/dashboard/api/userroles/roles/delete_role.py b/supertokens_python/recipe/dashboard/api/userroles/roles/delete_role.py new file mode 100644 index 00000000..77816d72 --- /dev/null +++ b/supertokens_python/recipe/dashboard/api/userroles/roles/delete_role.py @@ -0,0 +1,43 @@ +from typing import Any, Union +from typing_extensions import Literal +from supertokens_python.exceptions import raise_bad_input_exception +from supertokens_python.recipe.dashboard.interfaces import APIInterface, APIOptions +from supertokens_python.recipe.userroles.asyncio import delete_role +from supertokens_python.recipe.userroles.recipe import UserRolesRecipe +from supertokens_python.types import APIResponse + + +class OkResponse(APIResponse): + def __init__(self, did_role_exist: bool): + self.status: Literal["OK"] = "OK" + self.did_role_exist = did_role_exist + + def to_json(self): + return {"status": self.status, "didRoleExist": self.did_role_exist} + + +class FeatureNotEnabledErrorResponse(APIResponse): + def __init__(self): + self.status: Literal["FEATURE_NOT_ENABLED_ERROR"] = "FEATURE_NOT_ENABLED_ERROR" + + def to_json(self): + return {"status": self.status} + + +async def delete_role_api( + _: APIInterface, __: str, api_options: APIOptions, ___: Any +) -> Union[OkResponse, FeatureNotEnabledErrorResponse]: + try: + UserRolesRecipe.get_instance() + except Exception: + return FeatureNotEnabledErrorResponse() + + role = api_options.request.get_query_param("role") + + if role is None: + raise_bad_input_exception( + "Required parameter 'role' is missing or has an invalid type" + ) + + response = await delete_role(role) + return OkResponse(did_role_exist=response.did_role_exist) diff --git a/supertokens_python/recipe/dashboard/api/userroles/roles/get_all_roles.py b/supertokens_python/recipe/dashboard/api/userroles/roles/get_all_roles.py new file mode 100644 index 00000000..35fca684 --- /dev/null +++ b/supertokens_python/recipe/dashboard/api/userroles/roles/get_all_roles.py @@ -0,0 +1,35 @@ +from typing import Any, Union +from typing_extensions import Literal +from supertokens_python.recipe.dashboard.interfaces import APIInterface, APIOptions +from supertokens_python.recipe.userroles.asyncio import get_all_roles +from supertokens_python.recipe.userroles.recipe import UserRolesRecipe +from supertokens_python.types import APIResponse + + +class OkResponse(APIResponse): + def __init__(self, roles: list[str]): + self.status: Literal["OK"] = "OK" + self.roles = roles + + def to_json(self): + return {"status": self.status, "roles": self.roles} + + +class FeatureNotEnabledErrorResponse(APIResponse): + def __init__(self): + self.status: Literal["FEATURE_NOT_ENABLED_ERROR"] = "FEATURE_NOT_ENABLED_ERROR" + + def to_json(self): + return {"status": self.status} + + +async def get_all_roles_api( + _: APIInterface, __: str, api_options: APIOptions, ___: Any +) -> Union[OkResponse, FeatureNotEnabledErrorResponse]: + try: + UserRolesRecipe.get_instance() + except Exception: + return FeatureNotEnabledErrorResponse() + + response = await get_all_roles() + return OkResponse(roles=response.roles) diff --git a/supertokens_python/recipe/dashboard/constants.py b/supertokens_python/recipe/dashboard/constants.py index 71482e95..10c127b5 100644 --- a/supertokens_python/recipe/dashboard/constants.py +++ b/supertokens_python/recipe/dashboard/constants.py @@ -24,3 +24,5 @@ UNLINK_USER = "/api/user/unlink" USERROLES_PERMISSIONS_API = "/api/userroles/role/permissions" USERROLES_REMOVE_PERMISSIONS_API = "/api/userroles/role/permissions/remove" +USERROLES_ROLE_API = "/api/userroles/role" +USERROLES_USER_API = "/api/userroles/user/roles" diff --git a/supertokens_python/recipe/dashboard/recipe.py b/supertokens_python/recipe/dashboard/recipe.py index be54cc9b..b547593b 100644 --- a/supertokens_python/recipe/dashboard/recipe.py +++ b/supertokens_python/recipe/dashboard/recipe.py @@ -56,12 +56,30 @@ from supertokens_python.recipe.dashboard.api.userdetails.user_unlink_get import ( handle_user_unlink_get, ) +from supertokens_python.recipe.dashboard.api.userroles.add_role_to_user import ( + add_role_to_user_api, +) +from supertokens_python.recipe.dashboard.api.userroles.get_role_to_user import ( + get_roles_for_user_api, +) from supertokens_python.recipe.dashboard.api.userroles.permissions.get_permissions_for_role import ( get_permissions_for_role_api, ) from supertokens_python.recipe.dashboard.api.userroles.permissions.remove_permissions_from_role import ( remove_permissions_from_role_api, ) +from supertokens_python.recipe.dashboard.api.userroles.remove_user_role import ( + remove_user_role_api, +) +from supertokens_python.recipe.dashboard.api.userroles.roles.create_role_or_add_permissions import ( + create_role_or_add_permissions_api, +) +from supertokens_python.recipe.dashboard.api.userroles.roles.delete_role import ( + delete_role_api, +) +from supertokens_python.recipe.dashboard.api.userroles.roles.get_all_roles import ( + get_all_roles_api, +) from supertokens_python.recipe_module import APIHandled, RecipeModule from .api import ( @@ -128,6 +146,8 @@ UNLINK_USER, USERROLES_PERMISSIONS_API, USERROLES_REMOVE_PERMISSIONS_API, + USERROLES_ROLE_API, + USERROLES_USER_API, ) from .utils import ( InputOverrideConfig, @@ -430,6 +450,42 @@ def get_apis_handled(self) -> List[APIHandled]: USERROLES_REMOVE_PERMISSIONS_API, False, ), + APIHandled( + NormalisedURLPath(get_api_path_with_dashboard_base(USERROLES_ROLE_API)), + "put", + USERROLES_ROLE_API, + False, + ), + APIHandled( + NormalisedURLPath(get_api_path_with_dashboard_base(USERROLES_ROLE_API)), + "delete", + USERROLES_ROLE_API, + False, + ), + APIHandled( + NormalisedURLPath(get_api_path_with_dashboard_base(USERROLES_ROLE_API)), + "get", + USERROLES_ROLE_API, + False, + ), + APIHandled( + NormalisedURLPath(get_api_path_with_dashboard_base(USERROLES_USER_API)), + "get", + USERROLES_USER_API, + False, + ), + APIHandled( + NormalisedURLPath(get_api_path_with_dashboard_base(USERROLES_USER_API)), + "put", + USERROLES_USER_API, + False, + ), + APIHandled( + NormalisedURLPath(get_api_path_with_dashboard_base(USERROLES_USER_API)), + "delete", + USERROLES_USER_API, + False, + ), ] async def handle_api_request( @@ -541,6 +597,20 @@ async def handle_api_request( api_function = get_permissions_for_role_api elif request_id == USERROLES_REMOVE_PERMISSIONS_API: api_function = remove_permissions_from_role_api + elif request_id == USERROLES_ROLE_API: + if method == "put": + api_function = create_role_or_add_permissions_api + if method == "delete": + api_function = delete_role_api + if method == "get": + api_function = get_all_roles_api + elif request_id == USERROLES_USER_API: + if method == "get": + api_function = get_roles_for_user_api + if method == "put": + api_function = add_role_to_user_api + if method == "delete": + api_function = remove_user_role_api if api_function is not None: return await api_key_protector(