Module supertokens_python.recipe.dashboard.api.multitenancy.utils

Expand source code
from typing import List
from supertokens_python.recipe.multifactorauth.recipe import MultiFactorAuthRecipe
from supertokens_python.recipe.multifactorauth.types import FactorIds
from supertokens_python.recipe.multifactorauth.utils import (
    is_factor_configured_for_tenant,
)
from supertokens_python.recipe.multitenancy.interfaces import TenantConfig
from supertokens_python.recipe.multitenancy.recipe import MultitenancyRecipe


def get_normalised_first_factors_based_on_tenant_config_from_core_and_sdk_init(
    tenant_details_from_core: TenantConfig,
) -> List[str]:
    first_factors: List[str]

    mt_instance = MultitenancyRecipe.get_instance()

    if tenant_details_from_core.first_factors is not None:
        first_factors = (
            tenant_details_from_core.first_factors
        )  # highest priority, config from core
    elif mt_instance.static_first_factors is not None:
        first_factors = mt_instance.static_first_factors  # next priority, static config
    else:
        # Fallback to all available factors (de-duplicated)
        first_factors = list(set(mt_instance.all_available_first_factors))

    # we now filter out all available first factors by checking if they are valid because
    # we want to return the ones that can work. this would be based on what recipes are enabled
    # on the core and also first_factors configured in the core and supertokens.init
    # Also, this way, in the front end, the developer can just check for first_factors for
    # enabled recipes in all cases irrespective of whether they are using MFA or not
    valid_first_factors: List[str] = []
    for factor_id in first_factors:
        if is_factor_configured_for_tenant(
            all_available_first_factors=mt_instance.all_available_first_factors,
            first_factors=first_factors,
            factor_id=factor_id,
        ):
            valid_first_factors.append(factor_id)

    return valid_first_factors


def get_factor_not_available_message(
    factor_id: str, available_factors: List[str]
) -> str:
    recipe_name = factor_id_to_recipe(factor_id)
    if recipe_name != "Passwordless":
        return f"Please initialise {recipe_name} recipe to be able to use this login method"

    passwordless_factors = [
        FactorIds.LINK_EMAIL,
        FactorIds.LINK_PHONE,
        FactorIds.OTP_EMAIL,
        FactorIds.OTP_PHONE,
    ]
    passwordless_factors_not_available = [
        f for f in passwordless_factors if f not in available_factors
    ]

    if len(passwordless_factors_not_available) == 4:
        return (
            "Please initialise Passwordless recipe to be able to use this login method"
        )

    flow_type, contact_method = factor_id.split("-")
    return f"Please ensure that Passwordless recipe is initialised with contactMethod: {contact_method.upper()} and flowType: {'USER_INPUT_CODE' if flow_type == 'otp' else 'MAGIC_LINK'}"


def factor_id_to_recipe(factor_id: str) -> str:
    factor_id_to_recipe_map = {
        "emailpassword": "Emailpassword",
        "thirdparty": "ThirdParty",
        "otp-email": "Passwordless",
        "otp-phone": "Passwordless",
        "link-email": "Passwordless",
        "link-phone": "Passwordless",
        "totp": "Totp",
    }

    return factor_id_to_recipe_map.get(factor_id, "")


async def get_normalised_required_secondary_factors_based_on_tenant_config_from_core_and_sdk_init(
    tenant_details_from_core: TenantConfig,
) -> List[str]:
    mfa_instance = MultiFactorAuthRecipe.get_instance()

    if mfa_instance is None:
        return []

    secondary_factors = await mfa_instance.get_all_available_secondary_factor_ids(
        tenant_details_from_core
    )
    secondary_factors = [
        factor_id
        for factor_id in secondary_factors
        if factor_id in (tenant_details_from_core.required_secondary_factors or [])
    ]

    return secondary_factors

Functions

def factor_id_to_recipe(factor_id: str) ‑> str
Expand source code
def factor_id_to_recipe(factor_id: str) -> str:
    factor_id_to_recipe_map = {
        "emailpassword": "Emailpassword",
        "thirdparty": "ThirdParty",
        "otp-email": "Passwordless",
        "otp-phone": "Passwordless",
        "link-email": "Passwordless",
        "link-phone": "Passwordless",
        "totp": "Totp",
    }

    return factor_id_to_recipe_map.get(factor_id, "")
def get_factor_not_available_message(factor_id: str, available_factors: List[str]) ‑> str
Expand source code
def get_factor_not_available_message(
    factor_id: str, available_factors: List[str]
) -> str:
    recipe_name = factor_id_to_recipe(factor_id)
    if recipe_name != "Passwordless":
        return f"Please initialise {recipe_name} recipe to be able to use this login method"

    passwordless_factors = [
        FactorIds.LINK_EMAIL,
        FactorIds.LINK_PHONE,
        FactorIds.OTP_EMAIL,
        FactorIds.OTP_PHONE,
    ]
    passwordless_factors_not_available = [
        f for f in passwordless_factors if f not in available_factors
    ]

    if len(passwordless_factors_not_available) == 4:
        return (
            "Please initialise Passwordless recipe to be able to use this login method"
        )

    flow_type, contact_method = factor_id.split("-")
    return f"Please ensure that Passwordless recipe is initialised with contactMethod: {contact_method.upper()} and flowType: {'USER_INPUT_CODE' if flow_type == 'otp' else 'MAGIC_LINK'}"
def get_normalised_first_factors_based_on_tenant_config_from_core_and_sdk_init(tenant_details_from_core: TenantConfig) ‑> List[str]
Expand source code
def get_normalised_first_factors_based_on_tenant_config_from_core_and_sdk_init(
    tenant_details_from_core: TenantConfig,
) -> List[str]:
    first_factors: List[str]

    mt_instance = MultitenancyRecipe.get_instance()

    if tenant_details_from_core.first_factors is not None:
        first_factors = (
            tenant_details_from_core.first_factors
        )  # highest priority, config from core
    elif mt_instance.static_first_factors is not None:
        first_factors = mt_instance.static_first_factors  # next priority, static config
    else:
        # Fallback to all available factors (de-duplicated)
        first_factors = list(set(mt_instance.all_available_first_factors))

    # we now filter out all available first factors by checking if they are valid because
    # we want to return the ones that can work. this would be based on what recipes are enabled
    # on the core and also first_factors configured in the core and supertokens.init
    # Also, this way, in the front end, the developer can just check for first_factors for
    # enabled recipes in all cases irrespective of whether they are using MFA or not
    valid_first_factors: List[str] = []
    for factor_id in first_factors:
        if is_factor_configured_for_tenant(
            all_available_first_factors=mt_instance.all_available_first_factors,
            first_factors=first_factors,
            factor_id=factor_id,
        ):
            valid_first_factors.append(factor_id)

    return valid_first_factors
async def get_normalised_required_secondary_factors_based_on_tenant_config_from_core_and_sdk_init(tenant_details_from_core: TenantConfig) ‑> List[str]
Expand source code
async def get_normalised_required_secondary_factors_based_on_tenant_config_from_core_and_sdk_init(
    tenant_details_from_core: TenantConfig,
) -> List[str]:
    mfa_instance = MultiFactorAuthRecipe.get_instance()

    if mfa_instance is None:
        return []

    secondary_factors = await mfa_instance.get_all_available_secondary_factor_ids(
        tenant_details_from_core
    )
    secondary_factors = [
        factor_id
        for factor_id in secondary_factors
        if factor_id in (tenant_details_from_core.required_secondary_factors or [])
    ]

    return secondary_factors