Module supertokens_python.recipe.dashboard.utils
Expand source code
# Copyright (c) 2021, 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, Callable, Dict, Optional, Union, List
from typing_extensions import Literal
from supertokens_python.recipe.accountlinking.recipe import AccountLinkingRecipe
if TYPE_CHECKING:
from supertokens_python.framework.request import BaseRequest
from supertokens_python.recipe.emailpassword import EmailPasswordRecipe
from supertokens_python.recipe.passwordless import PasswordlessRecipe
from supertokens_python.recipe.thirdparty import ThirdPartyRecipe
from supertokens_python.types import User, RecipeUserId
from supertokens_python.utils import log_debug_message, normalise_email
from ...normalised_url_path import NormalisedURLPath
from .constants import (
DASHBOARD_ANALYTICS_API,
DASHBOARD_API,
SIGN_OUT_API,
SIGN_IN_API,
SEARCH_TAGS_API,
USER_API,
USER_EMAIL_VERIFY_API,
USER_EMAIL_VERIFY_TOKEN_API,
USER_METADATA_API,
USER_PASSWORD_API,
USER_SESSION_API,
USERS_COUNT_API,
USERS_LIST_GET_API,
VALIDATE_KEY_API,
)
if TYPE_CHECKING:
from .interfaces import APIInterface, RecipeInterface
class UserWithMetadata:
user: User
first_name: Optional[str] = None
last_name: Optional[str] = None
def from_user(
self,
user: User,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
):
self.first_name = first_name or ""
self.last_name = last_name or ""
self.user = user
return self
def to_json(self) -> Dict[str, Any]:
user_json = self.user.to_json()
user_json["firstName"] = self.first_name
user_json["lastName"] = self.last_name
return user_json
class InputOverrideConfig:
def __init__(
self,
functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None,
apis: Union[Callable[[APIInterface], APIInterface], None] = None,
):
self.functions = functions
self.apis = apis
class OverrideConfig:
def __init__(
self,
functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None,
apis: Union[Callable[[APIInterface], APIInterface], None] = None,
):
self.functions = functions
self.apis = apis
class DashboardConfig:
def __init__(
self,
api_key: Optional[str],
admins: Optional[List[str]],
override: OverrideConfig,
auth_mode: str,
):
self.api_key = api_key
self.admins = admins
self.override = override
self.auth_mode = auth_mode
def validate_and_normalise_user_input(
# app_info: AppInfo,
api_key: Union[str, None],
admins: Optional[List[str]],
override: Optional[InputOverrideConfig] = None,
) -> DashboardConfig:
if override is None:
override = InputOverrideConfig()
if api_key is not None and admins is not None:
log_debug_message(
"User Dashboard: Providing 'admins' has no effect when using an api key."
)
admins = [normalise_email(a) for a in admins] if admins is not None else None
return DashboardConfig(
api_key,
admins,
OverrideConfig(
functions=override.functions,
apis=override.apis,
),
"api-key" if api_key else "email-password",
)
def is_api_path(path: NormalisedURLPath, base_path: NormalisedURLPath) -> bool:
dashboard_recipe_base_path = base_path.append(NormalisedURLPath(DASHBOARD_API))
if not path.startswith(dashboard_recipe_base_path):
return False
path_without_dashboard_path = path.get_as_string_dangerous().split(DASHBOARD_API)[1]
if len(path_without_dashboard_path) > 0 and path_without_dashboard_path[0] == "/":
path_without_dashboard_path = path_without_dashboard_path[1:]
if path_without_dashboard_path.split("/")[0] == "api":
return True
return False
def get_api_if_matched(path: NormalisedURLPath, method: str) -> Optional[str]:
path_str = path.get_as_string_dangerous()
if path_str.endswith(VALIDATE_KEY_API) and method == "post":
return VALIDATE_KEY_API
if path_str.endswith(USERS_LIST_GET_API) and method == "get":
return USERS_LIST_GET_API
if path_str.endswith(USERS_COUNT_API) and method == "get":
return USERS_COUNT_API
if path_str.endswith(USER_API) and method in ("get", "delete", "put"):
return USER_API
if path_str.endswith(USER_EMAIL_VERIFY_API) and method in ("get", "put"):
return USER_EMAIL_VERIFY_API
if path_str.endswith(USER_METADATA_API) and method in ("get", "put"):
return USER_METADATA_API
if path_str.endswith(USER_SESSION_API) and method in ("get", "post"):
return USER_SESSION_API
if path_str.endswith(USER_PASSWORD_API) and method == "put":
return USER_PASSWORD_API
if path_str.endswith(USER_EMAIL_VERIFY_TOKEN_API) and method == "post":
return USER_EMAIL_VERIFY_TOKEN_API
if path_str.endswith(SIGN_IN_API) and method == "post":
return SIGN_IN_API
if path_str.endswith(SIGN_OUT_API) and method == "post":
return SIGN_OUT_API
if path_str.endswith(SEARCH_TAGS_API) and method == "get":
return SEARCH_TAGS_API
if path_str.endswith(DASHBOARD_ANALYTICS_API) and method == "post":
return DASHBOARD_ANALYTICS_API
return None
class GetUserForRecipeIdHelperResult:
def __init__(self, user: Optional[User] = None, recipe: Optional[str] = None):
self.user = user
self.recipe = recipe
class GetUserForRecipeIdResult:
def __init__(
self, user: Optional[UserWithMetadata] = None, recipe: Optional[str] = None
):
self.user = user
self.recipe = recipe
async def get_user_for_recipe_id(
recipe_user_id: RecipeUserId, recipe_id: str, user_context: Dict[str, Any]
) -> GetUserForRecipeIdResult:
user_response = await _get_user_for_recipe_id(
recipe_user_id, recipe_id, user_context
)
user = None
if user_response.user is not None:
user = UserWithMetadata().from_user(
user_response.user, first_name="", last_name=""
)
return GetUserForRecipeIdResult(user=user, recipe=user_response.recipe)
async def _get_user_for_recipe_id(
recipe_user_id: RecipeUserId, recipe_id: str, user_context: Dict[str, Any]
) -> GetUserForRecipeIdHelperResult:
recipe: Optional[Literal["emailpassword", "thirdparty", "passwordless"]] = None
user = await AccountLinkingRecipe.get_instance().recipe_implementation.get_user(
recipe_user_id.get_as_string(), user_context
)
if user is None:
return GetUserForRecipeIdHelperResult(user=None, recipe=None)
login_method = next(
(
m
for m in user.login_methods
if m.recipe_id == recipe_id
and m.recipe_user_id.get_as_string() == recipe_user_id.get_as_string()
),
None,
)
if login_method is None:
return GetUserForRecipeIdHelperResult(user=None, recipe=None)
if recipe_id == EmailPasswordRecipe.recipe_id:
try:
EmailPasswordRecipe.get_instance()
recipe = "emailpassword"
except Exception:
pass
elif recipe_id == ThirdPartyRecipe.recipe_id:
try:
ThirdPartyRecipe.get_instance()
recipe = "thirdparty"
except Exception:
pass
elif recipe_id == PasswordlessRecipe.recipe_id:
try:
PasswordlessRecipe.get_instance()
recipe = "passwordless"
except Exception:
pass
return GetUserForRecipeIdHelperResult(user=user, recipe=recipe)
async def validate_api_key(
req: BaseRequest, config: DashboardConfig, _user_context: Dict[str, Any]
) -> bool:
api_key_header_value = req.get_header("authorization")
if not api_key_header_value:
return False
# We receieve the api key as `Bearer API_KEY`, this retrieves just the key
api_key_header_value = api_key_header_value.split(" ")[1]
return api_key_header_value == config.api_key
def get_api_path_with_dashboard_base(path: str) -> str:
return DASHBOARD_API + path
Functions
def get_api_if_matched(path: NormalisedURLPath, method: str) ‑> Optional[str]
-
Expand source code
def get_api_if_matched(path: NormalisedURLPath, method: str) -> Optional[str]: path_str = path.get_as_string_dangerous() if path_str.endswith(VALIDATE_KEY_API) and method == "post": return VALIDATE_KEY_API if path_str.endswith(USERS_LIST_GET_API) and method == "get": return USERS_LIST_GET_API if path_str.endswith(USERS_COUNT_API) and method == "get": return USERS_COUNT_API if path_str.endswith(USER_API) and method in ("get", "delete", "put"): return USER_API if path_str.endswith(USER_EMAIL_VERIFY_API) and method in ("get", "put"): return USER_EMAIL_VERIFY_API if path_str.endswith(USER_METADATA_API) and method in ("get", "put"): return USER_METADATA_API if path_str.endswith(USER_SESSION_API) and method in ("get", "post"): return USER_SESSION_API if path_str.endswith(USER_PASSWORD_API) and method == "put": return USER_PASSWORD_API if path_str.endswith(USER_EMAIL_VERIFY_TOKEN_API) and method == "post": return USER_EMAIL_VERIFY_TOKEN_API if path_str.endswith(SIGN_IN_API) and method == "post": return SIGN_IN_API if path_str.endswith(SIGN_OUT_API) and method == "post": return SIGN_OUT_API if path_str.endswith(SEARCH_TAGS_API) and method == "get": return SEARCH_TAGS_API if path_str.endswith(DASHBOARD_ANALYTICS_API) and method == "post": return DASHBOARD_ANALYTICS_API return None
def get_api_path_with_dashboard_base(path: str) ‑> str
-
Expand source code
def get_api_path_with_dashboard_base(path: str) -> str: return DASHBOARD_API + path
async def get_user_for_recipe_id(recipe_user_id: RecipeUserId, recipe_id: str, user_context: Dict[str, Any]) ‑> GetUserForRecipeIdResult
-
Expand source code
async def get_user_for_recipe_id( recipe_user_id: RecipeUserId, recipe_id: str, user_context: Dict[str, Any] ) -> GetUserForRecipeIdResult: user_response = await _get_user_for_recipe_id( recipe_user_id, recipe_id, user_context ) user = None if user_response.user is not None: user = UserWithMetadata().from_user( user_response.user, first_name="", last_name="" ) return GetUserForRecipeIdResult(user=user, recipe=user_response.recipe)
def is_api_path(path: NormalisedURLPath, base_path: NormalisedURLPath) ‑> bool
-
Expand source code
def is_api_path(path: NormalisedURLPath, base_path: NormalisedURLPath) -> bool: dashboard_recipe_base_path = base_path.append(NormalisedURLPath(DASHBOARD_API)) if not path.startswith(dashboard_recipe_base_path): return False path_without_dashboard_path = path.get_as_string_dangerous().split(DASHBOARD_API)[1] if len(path_without_dashboard_path) > 0 and path_without_dashboard_path[0] == "/": path_without_dashboard_path = path_without_dashboard_path[1:] if path_without_dashboard_path.split("/")[0] == "api": return True return False
def validate_and_normalise_user_input(api_key: Union[str, None], admins: Optional[List[str]], override: Optional[InputOverrideConfig] = None) ‑> DashboardConfig
-
Expand source code
def validate_and_normalise_user_input( # app_info: AppInfo, api_key: Union[str, None], admins: Optional[List[str]], override: Optional[InputOverrideConfig] = None, ) -> DashboardConfig: if override is None: override = InputOverrideConfig() if api_key is not None and admins is not None: log_debug_message( "User Dashboard: Providing 'admins' has no effect when using an api key." ) admins = [normalise_email(a) for a in admins] if admins is not None else None return DashboardConfig( api_key, admins, OverrideConfig( functions=override.functions, apis=override.apis, ), "api-key" if api_key else "email-password", )
async def validate_api_key(req: BaseRequest, config: DashboardConfig, _user_context: Dict[str, Any]) ‑> bool
-
Expand source code
async def validate_api_key( req: BaseRequest, config: DashboardConfig, _user_context: Dict[str, Any] ) -> bool: api_key_header_value = req.get_header("authorization") if not api_key_header_value: return False # We receieve the api key as `Bearer API_KEY`, this retrieves just the key api_key_header_value = api_key_header_value.split(" ")[1] return api_key_header_value == config.api_key
Classes
class DashboardConfig (api_key: Optional[str], admins: Optional[List[str]], override: OverrideConfig, auth_mode: str)
-
Expand source code
class DashboardConfig: def __init__( self, api_key: Optional[str], admins: Optional[List[str]], override: OverrideConfig, auth_mode: str, ): self.api_key = api_key self.admins = admins self.override = override self.auth_mode = auth_mode
class GetUserForRecipeIdHelperResult (user: Optional[User] = None, recipe: Optional[str] = None)
-
Expand source code
class GetUserForRecipeIdHelperResult: def __init__(self, user: Optional[User] = None, recipe: Optional[str] = None): self.user = user self.recipe = recipe
class GetUserForRecipeIdResult (user: Optional[UserWithMetadata] = None, recipe: Optional[str] = None)
-
Expand source code
class GetUserForRecipeIdResult: def __init__( self, user: Optional[UserWithMetadata] = None, recipe: Optional[str] = None ): self.user = user self.recipe = recipe
class InputOverrideConfig (functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None)
-
Expand source code
class InputOverrideConfig: def __init__( self, functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None, ): self.functions = functions self.apis = apis
class OverrideConfig (functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None)
-
Expand source code
class OverrideConfig: def __init__( self, functions: Union[Callable[[RecipeInterface], RecipeInterface], None] = None, apis: Union[Callable[[APIInterface], APIInterface], None] = None, ): self.functions = functions self.apis = apis
class UserWithMetadata
-
Expand source code
class UserWithMetadata: user: User first_name: Optional[str] = None last_name: Optional[str] = None def from_user( self, user: User, first_name: Optional[str] = None, last_name: Optional[str] = None, ): self.first_name = first_name or "" self.last_name = last_name or "" self.user = user return self def to_json(self) -> Dict[str, Any]: user_json = self.user.to_json() user_json["firstName"] = self.first_name user_json["lastName"] = self.last_name return user_json
Class variables
var first_name : Optional[str]
var last_name : Optional[str]
var user : User
Methods
def from_user(self, user: User, first_name: Optional[str] = None, last_name: Optional[str] = None)
-
Expand source code
def from_user( self, user: User, first_name: Optional[str] = None, last_name: Optional[str] = None, ): self.first_name = first_name or "" self.last_name = last_name or "" self.user = user return self
def to_json(self) ‑> Dict[str, Any]
-
Expand source code
def to_json(self) -> Dict[str, Any]: user_json = self.user.to_json() user_json["firstName"] = self.first_name user_json["lastName"] = self.last_name return user_json