Skip to content

Commit 099fe24

Browse files
committed
optional password validation in update_email_or_password
1 parent 9e60f3d commit 099fe24

File tree

11 files changed

+91
-16
lines changed

11 files changed

+91
-16
lines changed

supertokens_python/recipe/emailpassword/asyncio/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ async def update_email_or_password(
2323
email: Union[str, None] = None,
2424
password: Union[str, None] = None,
2525
user_context: Union[None, Dict[str, Any]] = None,
26+
apply_password_policy: Union[bool, None] = None,
2627
):
2728
if user_context is None:
2829
user_context = {}
2930
return await EmailPasswordRecipe.get_instance().recipe_implementation.update_email_or_password(
30-
user_id, email, password, user_context
31+
user_id, email, password, user_context, apply_password_policy
3132
)
3233

3334

supertokens_python/recipe/emailpassword/interfaces.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ class UpdateEmailOrPasswordUnknownUserIdError:
7878
pass
7979

8080

81+
class UpdateEmailOrPasswordPasswordPolicyViolationError:
82+
failure_reason: str
83+
84+
def __init__(self, failure_reason: str):
85+
self.failure_reason = failure_reason
86+
87+
8188
class RecipeInterface(ABC):
8289
def __init__(self):
8390
pass
@@ -127,10 +134,12 @@ async def update_email_or_password(
127134
email: Union[str, None],
128135
password: Union[str, None],
129136
user_context: Dict[str, Any],
137+
apply_password_policy: Union[bool, None],
130138
) -> Union[
131139
UpdateEmailOrPasswordOkResult,
132140
UpdateEmailOrPasswordEmailAlreadyExistsError,
133141
UpdateEmailOrPasswordUnknownUserIdError,
142+
UpdateEmailOrPasswordPasswordPolicyViolationError,
134143
]:
135144
pass
136145

supertokens_python/recipe/emailpassword/recipe.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
InputResetPasswordUsingTokenFeature,
6565
InputSignUpFeature,
6666
validate_and_normalise_user_input,
67+
EmailPasswordConfig,
6768
)
6869

6970

@@ -92,7 +93,13 @@ def __init__(
9293
override,
9394
email_delivery,
9495
)
95-
recipe_implementation = RecipeImplementation(Querier.get_instance(recipe_id))
96+
97+
def get_emailpassword_config() -> EmailPasswordConfig:
98+
return self.config
99+
100+
recipe_implementation = RecipeImplementation(
101+
Querier.get_instance(recipe_id), get_emailpassword_config
102+
)
96103
self.recipe_implementation = (
97104
recipe_implementation
98105
if self.config.override.functions is None

supertokens_python/recipe/emailpassword/recipe_implementation.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# under the License.
1414
from __future__ import annotations
1515

16-
from typing import TYPE_CHECKING, Any, Dict, Union
16+
from typing import TYPE_CHECKING, Any, Dict, Union, Callable
1717

1818
from supertokens_python.normalised_url_path import NormalisedURLPath
1919

@@ -30,17 +30,25 @@
3030
UpdateEmailOrPasswordEmailAlreadyExistsError,
3131
UpdateEmailOrPasswordOkResult,
3232
UpdateEmailOrPasswordUnknownUserIdError,
33+
UpdateEmailOrPasswordPasswordPolicyViolationError,
3334
)
3435
from .types import User
36+
from .utils import EmailPasswordConfig
37+
from .constants import FORM_FIELD_PASSWORD_ID
3538

3639
if TYPE_CHECKING:
3740
from supertokens_python.querier import Querier
3841

3942

4043
class RecipeImplementation(RecipeInterface):
41-
def __init__(self, querier: Querier):
44+
def __init__(
45+
self,
46+
querier: Querier,
47+
get_emailpassword_config: Callable[[], EmailPasswordConfig],
48+
):
4249
super().__init__()
4350
self.querier = querier
51+
self.get_emailpassword_config = get_emailpassword_config
4452

4553
async def get_user_by_id(
4654
self, user_id: str, user_context: Dict[str, Any]
@@ -139,15 +147,28 @@ async def update_email_or_password(
139147
email: Union[str, None],
140148
password: Union[str, None],
141149
user_context: Dict[str, Any],
150+
apply_password_policy: Union[bool, None],
142151
) -> Union[
143152
UpdateEmailOrPasswordOkResult,
144153
UpdateEmailOrPasswordEmailAlreadyExistsError,
145154
UpdateEmailOrPasswordUnknownUserIdError,
155+
UpdateEmailOrPasswordPasswordPolicyViolationError,
146156
]:
147157
data = {"userId": user_id}
148158
if email is not None:
149159
data = {"email": email, **data}
150160
if password is not None:
161+
if apply_password_policy is None or apply_password_policy:
162+
form_fields = (
163+
self.get_emailpassword_config().sign_up_feature.form_fields
164+
)
165+
if form_fields is not None:
166+
password_field = list(
167+
filter(lambda x: x.id == FORM_FIELD_PASSWORD_ID, form_fields)
168+
)[0]
169+
error = await password_field.validate(password)
170+
if error is not None:
171+
return UpdateEmailOrPasswordPasswordPolicyViolationError(error)
151172
data = {"password": password, **data}
152173
response = await self.querier.send_put_request(
153174
NormalisedURLPath("/recipe/user"), data

supertokens_python/recipe/emailpassword/syncio/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,15 @@ def update_email_or_password(
2424
email: Union[str, None] = None,
2525
password: Union[str, None] = None,
2626
user_context: Union[None, Dict[str, Any]] = None,
27+
apply_password_policy: Union[bool, None] = None,
2728
):
2829
from supertokens_python.recipe.emailpassword.asyncio import update_email_or_password
2930

30-
return sync(update_email_or_password(user_id, email, password, user_context))
31+
return sync(
32+
update_email_or_password(
33+
user_id, email, password, user_context, apply_password_policy
34+
)
35+
)
3136

3237

3338
def get_user_by_id(

supertokens_python/recipe/thirdpartyemailpassword/asyncio/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,12 @@ async def update_email_or_password(
101101
email: Union[None, str] = None,
102102
password: Union[None, str] = None,
103103
user_context: Union[None, Dict[str, Any]] = None,
104+
apply_password_policy: Union[bool, None] = None,
104105
):
105106
if user_context is None:
106107
user_context = {}
107108
return await ThirdPartyEmailPasswordRecipe.get_instance().recipe_implementation.update_email_or_password(
108-
user_id, email, password, user_context
109+
user_id, email, password, user_context, apply_password_policy
109110
)
110111

111112

supertokens_python/recipe/thirdpartyemailpassword/interfaces.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
UpdateEmailOrPasswordUnknownUserIdError = (
4444
EPInterfaces.UpdateEmailOrPasswordUnknownUserIdError
4545
)
46+
UpdateEmailOrPasswordPasswordPolicyViolationError = (
47+
EPInterfaces.UpdateEmailOrPasswordPasswordPolicyViolationError
48+
)
4649

4750
AuthorisationUrlGetOkResult = ThirdPartyInterfaces.AuthorisationUrlGetOkResult
4851
ThirdPartySignInUpPostNoEmailGivenByProviderResponse = (
@@ -134,10 +137,12 @@ async def update_email_or_password(
134137
email: Union[str, None],
135138
password: Union[str, None],
136139
user_context: Dict[str, Any],
140+
apply_password_policy: Union[bool, None],
137141
) -> Union[
138142
UpdateEmailOrPasswordOkResult,
139143
UpdateEmailOrPasswordEmailAlreadyExistsError,
140144
UpdateEmailOrPasswordUnknownUserIdError,
145+
UpdateEmailOrPasswordPasswordPolicyViolationError,
141146
]:
142147
pass
143148

supertokens_python/recipe/thirdpartyemailpassword/recipe.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666

6767
from ..emailpassword.interfaces import APIInterface as EmailPasswordAPIInterface
6868
from ..emailpassword.interfaces import RecipeInterface as EmailPasswordRecipeInterface
69+
from ..emailpassword.utils import EmailPasswordConfig
6970
from ..thirdparty.interfaces import APIInterface as ThirdPartyAPIInterface
7071
from ..thirdparty.interfaces import RecipeInterface as ThirdPartyRecipeInterface
7172
from .exceptions import SupertokensThirdPartyEmailPasswordError
@@ -103,9 +104,13 @@ def __init__(
103104
email_delivery,
104105
)
105106

107+
def get_emailpassword_config() -> EmailPasswordConfig:
108+
return self.email_password_recipe.config
109+
106110
recipe_implementation = RecipeImplementation(
107111
Querier.get_instance(EmailPasswordRecipe.recipe_id),
108112
Querier.get_instance(ThirdPartyRecipe.recipe_id),
113+
get_emailpassword_config,
109114
)
110115
self.recipe_implementation: RecipeInterface = (
111116
recipe_implementation
@@ -120,7 +125,7 @@ def __init__(
120125
)
121126

122127
ep_recipe_implementation = EmailPasswordRecipeImplementation(
123-
self.recipe_implementation
128+
self.recipe_implementation, get_emailpassword_config
124129
)
125130

126131
email_delivery_ingredient = ingredients.email_delivery

supertokens_python/recipe/thirdpartyemailpassword/recipeimplementation/email_password_recipe_implementation.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# under the License.
1414
from __future__ import annotations
1515

16-
from typing import Any, Dict, Union
16+
from typing import Any, Dict, Union, Callable
1717

1818
from supertokens_python.recipe.emailpassword.interfaces import (
1919
CreateResetPasswordOkResult,
@@ -28,8 +28,10 @@
2828
UpdateEmailOrPasswordEmailAlreadyExistsError,
2929
UpdateEmailOrPasswordOkResult,
3030
UpdateEmailOrPasswordUnknownUserIdError,
31+
UpdateEmailOrPasswordPasswordPolicyViolationError,
3132
)
3233
from supertokens_python.recipe.emailpassword.types import User
34+
from supertokens_python.recipe.emailpassword.utils import EmailPasswordConfig
3335

3436
from ..interfaces import (
3537
RecipeInterface as ThirdPartyEmailPasswordRecipeInterface,
@@ -39,9 +41,14 @@
3941

4042

4143
class RecipeImplementation(RecipeInterface):
42-
def __init__(self, recipe_implementation: ThirdPartyEmailPasswordRecipeInterface):
44+
def __init__(
45+
self,
46+
recipe_implementation: ThirdPartyEmailPasswordRecipeInterface,
47+
get_emailpassword_config: Callable[[], EmailPasswordConfig],
48+
):
4349
super().__init__()
4450
self.recipe_implementation = recipe_implementation
51+
self.get_emailpassword_config = get_emailpassword_config
4552

4653
async def get_user_by_id(
4754
self, user_id: str, user_context: Dict[str, Any]
@@ -114,11 +121,13 @@ async def update_email_or_password(
114121
email: Union[str, None],
115122
password: Union[str, None],
116123
user_context: Dict[str, Any],
124+
apply_password_policy: Union[bool, None],
117125
) -> Union[
118126
UpdateEmailOrPasswordOkResult,
119127
UpdateEmailOrPasswordEmailAlreadyExistsError,
120128
UpdateEmailOrPasswordUnknownUserIdError,
129+
UpdateEmailOrPasswordPasswordPolicyViolationError,
121130
]:
122131
return await self.recipe_implementation.update_email_or_password(
123-
user_id, email, password, user_context
132+
user_id, email, password, user_context, apply_password_policy
124133
)

supertokens_python/recipe/thirdpartyemailpassword/recipeimplementation/implementation.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# under the License.
1414
from __future__ import annotations
1515

16-
from typing import TYPE_CHECKING, Any, Dict, List, Union
16+
from typing import TYPE_CHECKING, Any, Dict, List, Union, Callable
1717

1818
import supertokens_python.recipe.emailpassword.interfaces as EPInterfaces
1919

@@ -41,6 +41,7 @@
4141
UpdateEmailOrPasswordEmailAlreadyExistsError,
4242
UpdateEmailOrPasswordOkResult,
4343
UpdateEmailOrPasswordUnknownUserIdError,
44+
UpdateEmailOrPasswordPasswordPolicyViolationError,
4445
)
4546
from ..types import User
4647
from .email_password_recipe_implementation import (
@@ -49,15 +50,19 @@
4950
from .third_party_recipe_implementation import (
5051
RecipeImplementation as DerivedThirdPartyImplementation,
5152
)
53+
from supertokens_python.recipe.emailpassword.utils import EmailPasswordConfig
5254

5355

5456
class RecipeImplementation(RecipeInterface):
5557
def __init__(
56-
self, emailpassword_querier: Querier, thirdparty_querier: Union[Querier, None]
58+
self,
59+
emailpassword_querier: Querier,
60+
thirdparty_querier: Union[Querier, None],
61+
get_emailpassword_config: Callable[[], EmailPasswordConfig],
5762
):
5863
super().__init__()
5964
emailpassword_implementation = EmailPasswordImplementation(
60-
emailpassword_querier
65+
emailpassword_querier, get_emailpassword_config
6166
)
6267

6368
self.ep_get_user_by_id = emailpassword_implementation.get_user_by_id
@@ -74,7 +79,7 @@ def __init__(
7479
emailpassword_implementation.update_email_or_password
7580
)
7681

77-
derived_ep = DerivedEmailPasswordImplementation(self)
82+
derived_ep = DerivedEmailPasswordImplementation(self, get_emailpassword_config)
7883
emailpassword_implementation.create_reset_password_token = (
7984
derived_ep.create_reset_password_token
8085
)
@@ -263,10 +268,12 @@ async def update_email_or_password(
263268
email: Union[None, str],
264269
password: Union[None, str],
265270
user_context: Dict[str, Any],
271+
apply_password_policy: Union[bool, None],
266272
) -> Union[
267273
UpdateEmailOrPasswordOkResult,
268274
UpdateEmailOrPasswordEmailAlreadyExistsError,
269275
UpdateEmailOrPasswordUnknownUserIdError,
276+
UpdateEmailOrPasswordPasswordPolicyViolationError,
270277
]:
271278
user = await self.get_user_by_id(user_id, user_context)
272279
if user is None:
@@ -276,5 +283,5 @@ async def update_email_or_password(
276283
"Cannot update email or password of a user who signed up using third party login."
277284
)
278285
return await self.ep_update_email_or_password(
279-
user_id, email, password, user_context
286+
user_id, email, password, user_context, apply_password_policy
280287
)

supertokens_python/recipe/thirdpartyemailpassword/syncio/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,17 @@ def update_email_or_password(
105105
email: Union[None, str] = None,
106106
password: Union[None, str] = None,
107107
user_context: Union[None, Dict[str, Any]] = None,
108+
apply_password_policy: Union[bool, None] = None,
108109
):
109110
from supertokens_python.recipe.thirdpartyemailpassword.asyncio import (
110111
update_email_or_password,
111112
)
112113

113-
return sync(update_email_or_password(user_id, email, password, user_context))
114+
return sync(
115+
update_email_or_password(
116+
user_id, email, password, user_context, apply_password_policy
117+
)
118+
)
114119

115120

116121
def get_users_by_email(

0 commit comments

Comments
 (0)