Skip to content

Commit 9abfbcb

Browse files
Merge pull request #385 from supertokens/fix/reset-pass-email-mt
feat: Add reset password email functions
2 parents 0248eda + c519405 commit 9abfbcb

File tree

9 files changed

+211
-10
lines changed

9 files changed

+211
-10
lines changed

supertokens_python/recipe/emailpassword/api/implementation.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
PasswordResetEmailTemplateVars,
4141
PasswordResetEmailTemplateVarsUser,
4242
)
43+
from ..utils import get_password_reset_link
4344
from supertokens_python.recipe.session.asyncio import create_new_session
4445
from supertokens_python.utils import find_first_occurrence_in_list
4546

@@ -95,16 +96,8 @@ async def generate_password_reset_token_post(
9596
)
9697
return GeneratePasswordResetTokenPostOkResult()
9798

98-
token = token_result.token
99-
password_reset_link = (
100-
api_options.app_info.website_domain.get_as_string_dangerous()
101-
+ api_options.app_info.website_base_path.get_as_string_dangerous()
102-
+ "/reset-password?token="
103-
+ token
104-
+ "&rid="
105-
+ api_options.recipe_id
106-
+ "&tenantId="
107-
+ tenant_id
99+
password_reset_link = get_password_reset_link(
100+
api_options.app_info, token_result.token, api_options.recipe_id, tenant_id
108101
)
109102

110103
log_debug_message("Sending password reset email to %s", email)

supertokens_python/recipe/emailpassword/asyncio/__init__.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
from ..types import EmailTemplateVars, User
1919
from ...multitenancy.constants import DEFAULT_TENANT_ID
2020

21+
from supertokens_python.recipe.emailpassword.interfaces import (
22+
CreateResetPasswordWrongUserIdError,
23+
CreateResetPasswordLinkUknownUserIdError,
24+
CreateResetPasswordLinkOkResult,
25+
SendResetPasswordEmailOkResult,
26+
SendResetPasswordEmailUnknownUserIdError,
27+
)
28+
from supertokens_python.recipe.emailpassword.utils import get_password_reset_link
29+
from supertokens_python.recipe.emailpassword.types import (
30+
PasswordResetEmailTemplateVars,
31+
PasswordResetEmailTemplateVarsUser,
32+
)
33+
2134

2235
async def update_email_or_password(
2336
user_id: str,
@@ -117,3 +130,47 @@ async def send_email(
117130
return await EmailPasswordRecipe.get_instance().email_delivery.ingredient_interface_impl.send_email(
118131
input_, user_context
119132
)
133+
134+
135+
async def create_reset_password_link(
136+
user_id: str,
137+
tenant_id: Optional[str] = None,
138+
user_context: Optional[Dict[str, Any]] = None,
139+
):
140+
token = await create_reset_password_token(user_id, tenant_id, user_context)
141+
if isinstance(token, CreateResetPasswordWrongUserIdError):
142+
return CreateResetPasswordLinkUknownUserIdError()
143+
144+
recipe_instance = EmailPasswordRecipe.get_instance()
145+
return CreateResetPasswordLinkOkResult(
146+
link=get_password_reset_link(
147+
recipe_instance.get_app_info(),
148+
recipe_instance.get_recipe_id(),
149+
token.token,
150+
tenant_id or DEFAULT_TENANT_ID,
151+
)
152+
)
153+
154+
155+
async def send_reset_password_email(
156+
user_id: str,
157+
tenant_id: Optional[str] = None,
158+
user_context: Optional[Dict[str, Any]] = None,
159+
):
160+
link = await create_reset_password_link(user_id, tenant_id, user_context)
161+
if isinstance(link, CreateResetPasswordLinkUknownUserIdError):
162+
return SendResetPasswordEmailUnknownUserIdError()
163+
164+
user = await get_user_by_id(user_id, user_context)
165+
assert user is not None
166+
167+
await send_email(
168+
PasswordResetEmailTemplateVars(
169+
PasswordResetEmailTemplateVarsUser(user.user_id, user.email),
170+
link.link,
171+
tenant_id,
172+
),
173+
user_context,
174+
)
175+
176+
return SendResetPasswordEmailOkResult()

supertokens_python/recipe/emailpassword/interfaces.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,23 @@ class CreateResetPasswordWrongUserIdError:
5757
pass
5858

5959

60+
class CreateResetPasswordLinkOkResult:
61+
def __init__(self, link: str):
62+
self.link = link
63+
64+
65+
class CreateResetPasswordLinkUknownUserIdError:
66+
pass
67+
68+
69+
class SendResetPasswordEmailOkResult:
70+
pass
71+
72+
73+
class SendResetPasswordEmailUnknownUserIdError:
74+
pass
75+
76+
6077
class ResetPasswordUsingTokenOkResult:
6178
def __init__(self, user_id: Union[str, None]):
6279
self.user_id = user_id

supertokens_python/recipe/emailpassword/syncio/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,27 @@ def send_email(
109109
from supertokens_python.recipe.emailpassword.asyncio import send_email
110110

111111
return sync(send_email(input_, user_context))
112+
113+
114+
def create_reset_password_link(
115+
user_id: str,
116+
tenant_id: Optional[str] = None,
117+
user_context: Optional[Dict[str, Any]] = None,
118+
):
119+
from supertokens_python.recipe.emailpassword.asyncio import (
120+
create_reset_password_link,
121+
)
122+
123+
return sync(create_reset_password_link(user_id, tenant_id, user_context))
124+
125+
126+
def send_reset_password_email(
127+
user_id: str,
128+
tenant_id: Optional[str] = None,
129+
user_context: Optional[Dict[str, Any]] = None,
130+
):
131+
from supertokens_python.recipe.emailpassword.asyncio import (
132+
send_reset_password_email,
133+
)
134+
135+
return sync(send_reset_password_email(user_id, tenant_id, user_context))

supertokens_python/recipe/emailpassword/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,18 @@ def get_email_delivery_config(
324324
OverrideConfig(functions=override.functions, apis=override.apis),
325325
get_email_delivery_config=get_email_delivery_config,
326326
)
327+
328+
329+
def get_password_reset_link(
330+
app_info: AppInfo, token: str, recipe_id: str, tenant_id: str
331+
) -> str:
332+
return (
333+
app_info.website_domain.get_as_string_dangerous()
334+
+ app_info.website_base_path.get_as_string_dangerous()
335+
+ "/reset-password?token="
336+
+ token
337+
+ "&rid="
338+
+ recipe_id
339+
+ "&tenantId="
340+
+ tenant_id
341+
)

supertokens_python/recipe/thirdpartyemailpassword/asyncio/__init__.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@
2121
from ..types import EmailTemplateVars, User
2222
from supertokens_python.recipe.multitenancy.constants import DEFAULT_TENANT_ID
2323

24+
from supertokens_python.recipe.thirdpartyemailpassword.interfaces import (
25+
CreateResetPasswordWrongUserIdError,
26+
CreateResetPasswordLinkUknownUserIdError,
27+
CreateResetPasswordLinkOkResult,
28+
SendResetPasswordEmailUnknownUserIdError,
29+
SendResetPasswordEmailEmailOkResult,
30+
)
31+
from supertokens_python.recipe.emailpassword.utils import get_password_reset_link
32+
33+
from supertokens_python.recipe.thirdpartyemailpassword.types import (
34+
PasswordResetEmailTemplateVars,
35+
PasswordResetEmailTemplateVarsUser,
36+
)
37+
2438

2539
async def get_user_by_id(
2640
user_id: str, user_context: Union[None, Dict[str, Any]] = None
@@ -168,3 +182,51 @@ async def send_email(
168182
return await ThirdPartyEmailPasswordRecipe.get_instance().email_delivery.ingredient_interface_impl.send_email(
169183
input_, user_context
170184
)
185+
186+
187+
async def create_reset_password_link(
188+
user_id: str,
189+
tenant_id: Optional[str] = None,
190+
user_context: Optional[Dict[str, Any]] = None,
191+
):
192+
token = await create_reset_password_token(user_id, tenant_id, user_context)
193+
if isinstance(token, CreateResetPasswordWrongUserIdError):
194+
return CreateResetPasswordLinkUknownUserIdError()
195+
196+
recipe_instance = ThirdPartyEmailPasswordRecipe.get_instance()
197+
198+
user = await get_user_by_id(user_id, user_context)
199+
assert user is not None
200+
201+
return CreateResetPasswordLinkOkResult(
202+
link=get_password_reset_link(
203+
recipe_instance.get_app_info(),
204+
token.token,
205+
recipe_instance.get_recipe_id(),
206+
tenant_id or DEFAULT_TENANT_ID,
207+
)
208+
)
209+
210+
211+
async def send_reset_password_email(
212+
user_id: str,
213+
tenant_id: Optional[str] = None,
214+
user_context: Optional[Dict[str, Any]] = None,
215+
):
216+
link = await create_reset_password_link(user_id, tenant_id, user_context)
217+
if isinstance(link, CreateResetPasswordLinkUknownUserIdError):
218+
return SendResetPasswordEmailUnknownUserIdError()
219+
220+
user = await get_user_by_id(user_id, user_context)
221+
assert user is not None
222+
223+
await send_email(
224+
PasswordResetEmailTemplateVars(
225+
PasswordResetEmailTemplateVarsUser(user.user_id, user.email),
226+
link.link,
227+
tenant_id,
228+
),
229+
user_context,
230+
)
231+
232+
return SendResetPasswordEmailEmailOkResult()

supertokens_python/recipe/thirdpartyemailpassword/interfaces.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
# Exporting re-used classes
1818
CreateResetPasswordOkResult = EPInterfaces.CreateResetPasswordOkResult
1919
CreateResetPasswordWrongUserIdError = EPInterfaces.CreateResetPasswordWrongUserIdError
20+
CreateResetPasswordLinkOkResult = EPInterfaces.CreateResetPasswordLinkOkResult
21+
CreateResetPasswordLinkUknownUserIdError = (
22+
EPInterfaces.CreateResetPasswordLinkUknownUserIdError
23+
)
24+
SendResetPasswordEmailEmailOkResult = EPInterfaces.SendResetPasswordEmailOkResult
25+
SendResetPasswordEmailUnknownUserIdError = (
26+
EPInterfaces.SendResetPasswordEmailUnknownUserIdError
27+
)
2028
EmailPasswordEmailExistsGetOkResult = EPInterfaces.EmailExistsGetOkResult
2129
GeneratePasswordResetTokenPostOkResult = (
2230
EPInterfaces.GeneratePasswordResetTokenPostOkResult

supertokens_python/recipe/thirdpartyemailpassword/syncio/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,27 @@ def send_email(
171171
from supertokens_python.recipe.thirdpartyemailpassword.asyncio import send_email
172172

173173
return sync(send_email(input_, user_context))
174+
175+
176+
def create_reset_password_link(
177+
user_id: str,
178+
tenant_id: Optional[str] = None,
179+
user_context: Optional[Dict[str, Any]] = None,
180+
):
181+
from supertokens_python.recipe.thirdpartyemailpassword.asyncio import (
182+
create_reset_password_link,
183+
)
184+
185+
return sync(create_reset_password_link(user_id, tenant_id, user_context))
186+
187+
188+
def send_reset_password_email(
189+
user_id: str,
190+
tenant_id: Optional[str] = None,
191+
user_context: Optional[Dict[str, Any]] = None,
192+
):
193+
from supertokens_python.recipe.thirdpartyemailpassword.asyncio import (
194+
send_reset_password_email,
195+
)
196+
197+
return sync(send_reset_password_email(user_id, tenant_id, user_context))

supertokens_python/recipe/thirdpartyemailpassword/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def __init__(
4343
# Export:
4444
EmailTemplateVars = ep_types.EmailTemplateVars
4545
PasswordResetEmailTemplateVars = ep_types.PasswordResetEmailTemplateVars
46+
PasswordResetEmailTemplateVarsUser = ep_types.PasswordResetEmailTemplateVarsUser
4647

4748
SMTPOverrideInput = SMTPServiceInterface[EmailTemplateVars]
4849

0 commit comments

Comments
 (0)