Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add multitenancy for dashboard recipe #382

Merged
merged 6 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions supertokens_python/recipe/dashboard/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from .users_count_get import handle_users_count_get_api
from .users_get import handle_users_get_api
from .validate_key import handle_validate_key_api
from .list_tenants import handle_list_tenants_api

__all__ = [
"handle_dashboard_api",
Expand All @@ -53,4 +54,5 @@
"handle_emailpassword_signout_api",
"handle_get_tags",
"handle_analytics_post",
"handle_list_tenants_api",
]
5 changes: 4 additions & 1 deletion supertokens_python/recipe/dashboard/api/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@


async def handle_analytics_post(
_: APIInterface, api_options: APIOptions, _user_context: Dict[str, Any]
_: APIInterface,
_tenant_id: str,
api_options: APIOptions,
_user_context: Dict[str, Any],
) -> AnalyticsResponse:
if not Supertokens.get_instance().telemetry:
return AnalyticsResponse()
Expand Down
7 changes: 5 additions & 2 deletions supertokens_python/recipe/dashboard/api/api_key_protector.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@

async def api_key_protector(
api_implementation: APIInterface,
tenant_id: str,
api_options: APIOptions,
api_function: Callable[
[APIInterface, APIOptions, Dict[str, Any]], Awaitable[APIResponse]
[APIInterface, str, APIOptions, Dict[str, Any]], Awaitable[APIResponse]
],
user_context: Dict[str, Any],
) -> Optional[BaseResponse]:
Expand All @@ -47,5 +48,7 @@ async def api_key_protector(
"Unauthorised access", 401, api_options.response
)

response = await api_function(api_implementation, api_options, user_context)
response = await api_function(
api_implementation, tenant_id, api_options, user_context
)
return send_200_response(response.to_json(), api_options.response)
39 changes: 39 additions & 0 deletions supertokens_python/recipe/dashboard/api/list_tenants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved.
#
# This software is licensed under the Apache License, Version 2.0 (the
# "License") as published by the Apache Software Foundation.
#
# You may not use this file except in compliance with the License. You may
# obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Dict

if TYPE_CHECKING:
from supertokens_python.recipe.dashboard.interfaces import (
APIOptions,
APIInterface,
)
from supertokens_python.types import APIResponse

from supertokens_python.recipe.multitenancy.asyncio import list_all_tenants
from supertokens_python.recipe.dashboard.interfaces import (
DashboardListTenantsGetResponse,
)


async def handle_list_tenants_api(
_api_implementation: APIInterface,
_tenant_id: str,
_api_options: APIOptions,
user_context: Dict[str, Any],
) -> APIResponse:
tenants = await list_all_tenants(user_context)
return DashboardListTenantsGetResponse(tenants.tenants)
2 changes: 1 addition & 1 deletion supertokens_python/recipe/dashboard/api/search/getTags.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@


async def handle_get_tags(
_: APIInterface, __: APIOptions, _user_context: Dict[str, Any]
_: APIInterface, _tenant_id: str, __: APIOptions, _user_context: Dict[str, Any]
) -> SearchTagsOK:
response = await Querier.get_instance().send_get_request(
NormalisedURLPath("/user/search/tags")
Expand Down
5 changes: 4 additions & 1 deletion supertokens_python/recipe/dashboard/api/signout.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@


async def handle_emailpassword_signout_api(
_: APIInterface, api_options: APIOptions, _user_context: Dict[str, Any]
_: APIInterface,
_tenant_id: str,
api_options: APIOptions,
_user_context: Dict[str, Any],
) -> SignOutOK:
if api_options.config.auth_mode == "api-key":
return SignOutOK()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@


async def handle_user_delete(
_api_interface: APIInterface, api_options: APIOptions, _user_context: Dict[str, Any]
_api_interface: APIInterface,
_tenant_id: str,
api_options: APIOptions,
_user_context: Dict[str, Any],
) -> UserDeleteAPIResponse:
user_id = api_options.request.get_query_param("userId")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@


async def handle_user_email_verify_get(
_api_interface: APIInterface, api_options: APIOptions, user_context: Dict[str, Any]
_api_interface: APIInterface,
_tenant_id: str,
api_options: APIOptions,
user_context: Dict[str, Any],
) -> Union[UserEmailVerifyGetAPIResponse, FeatureNotEnabledError]:
req = api_options.request
user_id = req.get_query_param("userId")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@


async def handle_user_email_verify_put(
_api_interface: APIInterface, api_options: APIOptions, user_context: Dict[str, Any]
_api_interface: APIInterface,
tenant_id: str,
api_options: APIOptions,
user_context: Dict[str, Any],
) -> UserEmailVerifyPutAPIResponse:
request_body: Dict[str, Any] = await api_options.request.json() # type: ignore
user_id = request_body.get("userId")
Expand All @@ -37,7 +40,7 @@ async def handle_user_email_verify_put(

if verified:
token_response = await create_email_verification_token(
user_id, user_context=user_context
user_id, tenant_id=tenant_id, user_context=user_context
)

if isinstance(
Expand All @@ -46,7 +49,7 @@ async def handle_user_email_verify_put(
return UserEmailVerifyPutAPIResponse()

verify_response = await verify_email_using_token(
token_response.token, user_context=user_context
token_response.token, tenant_id, user_context=user_context
)

if isinstance(verify_response, VerifyEmailUsingTokenInvalidTokenError):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@


async def handle_email_verify_token_post(
_api_interface: APIInterface, api_options: APIOptions, user_context: Dict[str, Any]
_api_interface: APIInterface,
tenant_id: str,
api_options: APIOptions,
user_context: Dict[str, Any],
) -> Union[
UserEmailVerifyTokenPostAPIOkResponse,
UserEmailVerifyTokenPostAPIEmailAlreadyVerifiedErrorResponse,
Expand All @@ -49,7 +52,7 @@ async def handle_email_verify_token_post(
raise Exception("Should not come here")

email_verification_token = await create_email_verification_token(
user_id, user_context=user_context
user_id, tenant_id=tenant_id, user_context=user_context
)

if isinstance(
Expand All @@ -59,8 +62,6 @@ async def handle_email_verify_token_post(

assert isinstance(email_verification_token, CreateEmailVerificationTokenOkResult)

# TODO: Pass tenant id
tenant_id = "pass-tenant-id"
email_verify_link = get_email_verify_link(
api_options.app_info, email_verification_token.token, user_id, tenant_id
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@


async def handle_user_get(
_api_interface: APIInterface, api_options: APIOptions, _user_context: Dict[str, Any]
_api_interface: APIInterface,
_tenant_id: str,
api_options: APIOptions,
_user_context: Dict[str, Any],
) -> Union[
UserGetAPINoUserFoundError,
UserGetAPIOkResponse,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@


async def handle_metadata_get(
_api_interface: APIInterface, api_options: APIOptions, user_context: Dict[str, Any]
_api_interface: APIInterface,
_tenant_id: str,
api_options: APIOptions,
user_context: Dict[str, Any],
) -> Union[UserMetadataGetAPIOkResponse, FeatureNotEnabledError]:
user_id = api_options.request.get_query_param("userId")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@


async def handle_metadata_put(
_api_interface: APIInterface, api_options: APIOptions, user_context: Dict[str, Any]
_api_interface: APIInterface,
_tenant_id: str,
api_options: APIOptions,
user_context: Dict[str, Any],
) -> UserMetadataPutAPIResponse:
request_body: Dict[str, Any] = await api_options.request.json() # type: ignore
user_id = request_body.get("userId")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@


async def handle_user_password_put(
_api_interface: APIInterface, api_options: APIOptions, user_context: Dict[str, Any]
_api_interface: APIInterface,
tenant_id: str,
api_options: APIOptions,
user_context: Dict[str, Any],
) -> Union[UserPasswordPutAPIResponse, UserPasswordPutAPIInvalidPasswordErrorResponse]:
request_body: Dict[str, Any] = await api_options.request.json() # type: ignore
user_id = request_body.get("userId")
Expand Down Expand Up @@ -100,9 +103,8 @@ async def reset_password(
password_validation_error
)

# TODO: Pass tenant id
password_reset_token = await create_reset_password_token(
"pass-tenant-id", user_id, user_context
user_id, tenant_id, user_context
)

if isinstance(password_reset_token, CreateResetPasswordWrongUserIdError):
Expand All @@ -111,7 +113,7 @@ async def reset_password(
raise Exception("Should never come here")

password_reset_response = await reset_password_using_token(
"pass-tenant-id", password_reset_token.token, new_password, user_context
password_reset_token.token, new_password, tenant_id, user_context
)

if isinstance(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,10 @@ async def update_phone_for_recipe_id(


async def handle_user_put(
_api_interface: APIInterface, api_options: APIOptions, user_context: Dict[str, Any]
_api_interface: APIInterface,
_tenant_id: str,
api_options: APIOptions,
user_context: Dict[str, Any],
) -> Union[
UserPutAPIOkResponse,
UserPutAPIInvalidEmailErrorResponse,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@


async def handle_sessions_get(
_api_interface: APIInterface, api_options: APIOptions, user_context: Dict[str, Any]
_api_interface: APIInterface,
_tenant_id: str,
api_options: APIOptions,
user_context: Dict[str, Any],
) -> UserSessionsGetAPIResponse:
user_id = api_options.request.get_query_param("userId")

if user_id is None:
raise_bad_input_exception("Missing required parameter 'userId'")

session_handles = await get_all_session_handles_for_user(user_id, user_context)
session_handles = await get_all_session_handles_for_user(
user_id, None, user_context
)
sessions: List[Optional[SessionInfo]] = [None for _ in session_handles]

async def call_(i: int, session_handle: str):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@


async def handle_user_sessions_post(
_api_interface: APIInterface, api_options: APIOptions, _user_context: Dict[str, Any]
_api_interface: APIInterface,
_tenant_id: str,
api_options: APIOptions,
_user_context: Dict[str, Any],
) -> UserSessionsPostAPIResponse:
request_body = await api_options.request.json() # type: ignore
session_handles: Optional[List[str]] = request_body.get("sessionHandles") # type: ignore
Expand Down
7 changes: 5 additions & 2 deletions supertokens_python/recipe/dashboard/api/users_count_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@


async def handle_users_count_get_api(
_: APIInterface, _api_options: APIOptions, _user_context: Dict[str, Any]
_: APIInterface,
tenant_id: str,
_api_options: APIOptions,
_user_context: Dict[str, Any],
) -> UserCountGetAPIResponse:
count = await Supertokens.get_instance().get_user_count(include_recipe_ids=None)
count = await Supertokens.get_instance().get_user_count(None, tenant_id)
return UserCountGetAPIResponse(count=count)
2 changes: 2 additions & 0 deletions supertokens_python/recipe/dashboard/api/users_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

async def handle_users_get_api(
api_implementation: APIInterface,
tenant_id: str,
api_options: APIOptions,
user_context: Dict[str, Any],
) -> APIResponse:
Expand All @@ -58,6 +59,7 @@ async def handle_users_get_api(
pagination_token=pagination_token,
include_recipe_ids=None,
query=api_options.request.get_query_params(),
tenant_id=tenant_id,
)

# user metadata bulk fetch with batches:
Expand Down
1 change: 1 addition & 0 deletions supertokens_python/recipe/dashboard/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
EMAIL_PASSSWORD_SIGNOUT = "/api/signout"
SEARCH_TAGS_API = "/api/search/tags"
DASHBOARD_ANALYTICS_API = "/api/analytics"
TENANTS_LIST_API = "/api/tenants/list"
11 changes: 11 additions & 0 deletions supertokens_python/recipe/dashboard/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ def to_json(self) -> Dict[str, Any]:
}


from supertokens_python.recipe.multitenancy.interfaces import ListAllTenantsOkResult


class DashboardListTenantsGetResponse(APIResponse, ListAllTenantsOkResult):
def to_json(self):
return {
"status": self.status,
"tenants": [t.to_json() for t in self.tenants],
}


class UserCountGetAPIResponse(APIResponse):
status: str = "OK"

Expand Down
Loading
Loading