Skip to content

feat: Add reset password email functions #385

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

Merged
merged 2 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
13 changes: 3 additions & 10 deletions supertokens_python/recipe/emailpassword/api/implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
PasswordResetEmailTemplateVars,
PasswordResetEmailTemplateVarsUser,
)
from ..utils import get_password_reset_link
from supertokens_python.recipe.session.asyncio import create_new_session
from supertokens_python.utils import find_first_occurrence_in_list

Expand Down Expand Up @@ -95,16 +96,8 @@ async def generate_password_reset_token_post(
)
return GeneratePasswordResetTokenPostOkResult()

token = token_result.token
password_reset_link = (
api_options.app_info.website_domain.get_as_string_dangerous()
+ api_options.app_info.website_base_path.get_as_string_dangerous()
+ "/reset-password?token="
+ token
+ "&rid="
+ api_options.recipe_id
+ "&tenantId="
+ tenant_id
password_reset_link = get_password_reset_link(
api_options.app_info, token_result.token, api_options.recipe_id, tenant_id
)

log_debug_message("Sending password reset email to %s", email)
Expand Down
57 changes: 57 additions & 0 deletions supertokens_python/recipe/emailpassword/asyncio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@
from ..types import EmailTemplateVars, User
from ...multitenancy.constants import DEFAULT_TENANT_ID

from supertokens_python.recipe.emailpassword.interfaces import (
CreateResetPasswordWrongUserIdError,
CreateResetPasswordLinkUknownUserIdError,
CreateResetPasswordLinkOkResult,
SendResetPasswordEmailOkResult,
SendResetPasswordEmailUnknownUserIdError,
)
from supertokens_python.recipe.emailpassword.utils import get_password_reset_link
from supertokens_python.recipe.emailpassword.types import (
PasswordResetEmailTemplateVars,
PasswordResetEmailTemplateVarsUser,
)


async def update_email_or_password(
user_id: str,
Expand Down Expand Up @@ -117,3 +130,47 @@ async def send_email(
return await EmailPasswordRecipe.get_instance().email_delivery.ingredient_interface_impl.send_email(
input_, user_context
)


async def create_reset_password_link(
user_id: str,
tenant_id: Optional[str] = None,
user_context: Optional[Dict[str, Any]] = None,
):
token = await create_reset_password_token(user_id, tenant_id, user_context)
if isinstance(token, CreateResetPasswordWrongUserIdError):
return CreateResetPasswordLinkUknownUserIdError()

recipe_instance = EmailPasswordRecipe.get_instance()
return CreateResetPasswordLinkOkResult(
link=get_password_reset_link(
recipe_instance.get_app_info(),
recipe_instance.get_recipe_id(),
token.token,
tenant_id or DEFAULT_TENANT_ID,
)
)


async def send_reset_password_email(
user_id: str,
tenant_id: Optional[str] = None,
user_context: Optional[Dict[str, Any]] = None,
):
link = await create_reset_password_link(user_id, tenant_id, user_context)
if isinstance(link, CreateResetPasswordLinkUknownUserIdError):
return SendResetPasswordEmailUnknownUserIdError()

user = await get_user_by_id(user_id, user_context)
assert user is not None

await send_email(
PasswordResetEmailTemplateVars(
PasswordResetEmailTemplateVarsUser(user.user_id, user.email),
link.link,
tenant_id,
),
user_context,
)

return SendResetPasswordEmailOkResult()
17 changes: 17 additions & 0 deletions supertokens_python/recipe/emailpassword/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ class CreateResetPasswordWrongUserIdError:
pass


class CreateResetPasswordLinkOkResult:
def __init__(self, link: str):
self.link = link


class CreateResetPasswordLinkUknownUserIdError:
pass


class SendResetPasswordEmailOkResult:
pass


class SendResetPasswordEmailUnknownUserIdError:
pass


class ResetPasswordUsingTokenOkResult:
def __init__(self, user_id: Union[str, None]):
self.user_id = user_id
Expand Down
24 changes: 24 additions & 0 deletions supertokens_python/recipe/emailpassword/syncio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,27 @@ def send_email(
from supertokens_python.recipe.emailpassword.asyncio import send_email

return sync(send_email(input_, user_context))


def create_reset_password_link(
user_id: str,
tenant_id: Optional[str] = None,
user_context: Optional[Dict[str, Any]] = None,
):
from supertokens_python.recipe.emailpassword.asyncio import (
create_reset_password_link,
)

return sync(create_reset_password_link(user_id, tenant_id, user_context))


def send_reset_password_email(
user_id: str,
tenant_id: Optional[str] = None,
user_context: Optional[Dict[str, Any]] = None,
):
from supertokens_python.recipe.emailpassword.asyncio import (
send_reset_password_email,
)

return sync(send_reset_password_email(user_id, tenant_id, user_context))
15 changes: 15 additions & 0 deletions supertokens_python/recipe/emailpassword/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,18 @@ def get_email_delivery_config(
OverrideConfig(functions=override.functions, apis=override.apis),
get_email_delivery_config=get_email_delivery_config,
)


def get_password_reset_link(
app_info: AppInfo, token: str, recipe_id: str, tenant_id: str
) -> str:
return (
app_info.website_domain.get_as_string_dangerous()
+ app_info.website_base_path.get_as_string_dangerous()
+ "/reset-password?token="
+ token
+ "&rid="
+ recipe_id
+ "&tenantId="
+ tenant_id
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@
from ..types import EmailTemplateVars, User
from supertokens_python.recipe.multitenancy.constants import DEFAULT_TENANT_ID

from supertokens_python.recipe.thirdpartyemailpassword.interfaces import (
CreateResetPasswordWrongUserIdError,
CreateResetPasswordLinkUknownUserIdError,
CreateResetPasswordLinkOkResult,
SendResetPasswordEmailUnknownUserIdError,
SendResetPasswordEmailEmailOkResult,
)
from supertokens_python.recipe.emailpassword.utils import get_password_reset_link

from supertokens_python.recipe.thirdpartyemailpassword.types import (
PasswordResetEmailTemplateVars,
PasswordResetEmailTemplateVarsUser,
)


async def get_user_by_id(
user_id: str, user_context: Union[None, Dict[str, Any]] = None
Expand Down Expand Up @@ -168,3 +182,51 @@ async def send_email(
return await ThirdPartyEmailPasswordRecipe.get_instance().email_delivery.ingredient_interface_impl.send_email(
input_, user_context
)


async def create_reset_password_link(
user_id: str,
tenant_id: Optional[str] = None,
user_context: Optional[Dict[str, Any]] = None,
):
token = await create_reset_password_token(user_id, tenant_id, user_context)
if isinstance(token, CreateResetPasswordWrongUserIdError):
return CreateResetPasswordLinkUknownUserIdError()

recipe_instance = ThirdPartyEmailPasswordRecipe.get_instance()

user = await get_user_by_id(user_id, user_context)
assert user is not None

return CreateResetPasswordLinkOkResult(
link=get_password_reset_link(
recipe_instance.get_app_info(),
token.token,
recipe_instance.get_recipe_id(),
tenant_id or DEFAULT_TENANT_ID,
)
)


async def send_reset_password_email(
user_id: str,
tenant_id: Optional[str] = None,
user_context: Optional[Dict[str, Any]] = None,
):
link = await create_reset_password_link(user_id, tenant_id, user_context)
if isinstance(link, CreateResetPasswordLinkUknownUserIdError):
return SendResetPasswordEmailUnknownUserIdError()

user = await get_user_by_id(user_id, user_context)
assert user is not None

await send_email(
PasswordResetEmailTemplateVars(
PasswordResetEmailTemplateVarsUser(user.user_id, user.email),
link.link,
tenant_id,
),
user_context,
)

return SendResetPasswordEmailEmailOkResult()
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
# Exporting re-used classes
CreateResetPasswordOkResult = EPInterfaces.CreateResetPasswordOkResult
CreateResetPasswordWrongUserIdError = EPInterfaces.CreateResetPasswordWrongUserIdError
CreateResetPasswordLinkOkResult = EPInterfaces.CreateResetPasswordLinkOkResult
CreateResetPasswordLinkUknownUserIdError = (
EPInterfaces.CreateResetPasswordLinkUknownUserIdError
)
SendResetPasswordEmailEmailOkResult = EPInterfaces.SendResetPasswordEmailOkResult
SendResetPasswordEmailUnknownUserIdError = (
EPInterfaces.SendResetPasswordEmailUnknownUserIdError
)
EmailPasswordEmailExistsGetOkResult = EPInterfaces.EmailExistsGetOkResult
GeneratePasswordResetTokenPostOkResult = (
EPInterfaces.GeneratePasswordResetTokenPostOkResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,27 @@ def send_email(
from supertokens_python.recipe.thirdpartyemailpassword.asyncio import send_email

return sync(send_email(input_, user_context))


def create_reset_password_link(
user_id: str,
tenant_id: Optional[str] = None,
user_context: Optional[Dict[str, Any]] = None,
):
from supertokens_python.recipe.thirdpartyemailpassword.asyncio import (
create_reset_password_link,
)

return sync(create_reset_password_link(user_id, tenant_id, user_context))


def send_reset_password_email(
user_id: str,
tenant_id: Optional[str] = None,
user_context: Optional[Dict[str, Any]] = None,
):
from supertokens_python.recipe.thirdpartyemailpassword.asyncio import (
send_reset_password_email,
)

return sync(send_reset_password_email(user_id, tenant_id, user_context))
1 change: 1 addition & 0 deletions supertokens_python/recipe/thirdpartyemailpassword/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(
# Export:
EmailTemplateVars = ep_types.EmailTemplateVars
PasswordResetEmailTemplateVars = ep_types.PasswordResetEmailTemplateVars
PasswordResetEmailTemplateVarsUser = ep_types.PasswordResetEmailTemplateVarsUser

SMTPOverrideInput = SMTPServiceInterface[EmailTemplateVars]

Expand Down