Skip to content

Commit

Permalink
more fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
rishabhpoddar committed Oct 24, 2024
1 parent b469058 commit 7917df6
Showing 1 changed file with 136 additions and 53 deletions.
189 changes: 136 additions & 53 deletions v2/thirdpartypasswordless/common-customizations/change-email.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,20 @@ func isValidEmail(email string) bool {
```python
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.passwordless.syncio import update_user, get_user_by_id
from supertokens_python.recipe.passwordless.interfaces import UpdateUserOkResult, UpdateUserEmailAlreadyExistsError
from supertokens_python.recipe.passwordless.syncio import update_user
from supertokens_python.syncio import get_user
from supertokens_python.recipe.passwordless.interfaces import (
UpdateUserOkResult,
UpdateUserEmailAlreadyExistsError,
EmailChangeNotAllowedError,
)
from flask import g, request, Flask
from re import fullmatch

app = Flask(__name__)


@app.route('/change-email', methods=['POST']) # type: ignore
@app.route("/change-email", methods=["POST"]) # type: ignore
@verify_session()
def change_email():
# highlight-start
Expand All @@ -290,15 +295,30 @@ def change_email():
return

# check that the account is not a social account
user_id = session.get_user_id()
user_info = get_user_by_id(user_id)
user_info = get_user(session.get_user_id())

if user_info is None:
# TODO handle error, cannot update email for social account
return

login_method = next(
(
lm
for lm in user_info.login_methods
if lm.recipe_user_id.get_as_string()
== session.get_recipe_user_id().get_as_string()
),
None,
)

assert login_method is not None

if login_method.recipe_id == "thirdparty":
# TODO: handle error, cannot update email for third party account
return

# update the users email
update_response = update_user(user_id, email=email)
update_response = update_user(session.get_recipe_user_id(), email=email)

if isinstance(update_response, UpdateUserOkResult):
# TODO send successful email update response
Expand All @@ -308,11 +328,17 @@ def change_email():
# TODO handle error, email already exists
return

if isinstance(update_response, EmailChangeNotAllowedError):
# This is possible if you have enabled account linking.
# See our docs for account linking to know more about this.
# TODO: tell the user to contact support.
return

raise Exception("Should never reach here")
# highlight-end


async def is_valid_email(value: str) -> bool:
def is_valid_email(value: str) -> bool:
return (
fullmatch(
r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,'
Expand Down Expand Up @@ -634,29 +660,32 @@ func isValidEmail(email string) bool {
```python
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.passwordless.syncio import (
update_user
)
from supertokens_python.recipe.passwordless.syncio import update_user
from supertokens_python.recipe.passwordless.interfaces import (
UpdateUserEmailAlreadyExistsError,
UpdateUserOkResult
UpdateUserOkResult,
)

from supertokens_python.recipe.emailverification.syncio import (
is_email_verified,
send_email_verification_email,
send_email_verification_email,
)

from flask import g, request, Flask
from re import fullmatch
from supertokens_python.syncio import get_user
from supertokens_python.recipe.accountlinking.syncio import is_email_change_allowed
from supertokens_python.syncio import list_users_by_account_info
from supertokens_python.types import AccountInfo

app = Flask(__name__)

@app.route('/change-email', methods=['POST']) # type: ignore

@app.route("/change-email", methods=["POST"]) # type: ignore
@verify_session()
def change_email():
# highlight-start
session: SessionContainer = g.supertokens # type: ignore
session: SessionContainer = g.supertokens # type: ignore

request_body = request.get_json()

Expand All @@ -669,23 +698,71 @@ def change_email():
# TODO: handle invalid email error
return

# check that the account is not a social account
user_info = get_user(session.get_user_id())

if user_info is None:
# TODO handle error, cannot update email for social account
return

login_method = next(
(
lm
for lm in user_info.login_methods
if lm.recipe_user_id.get_as_string()
== session.get_recipe_user_id().get_as_string()
),
None,
)

assert login_method is not None

if login_method.recipe_id == "thirdparty":
# TODO: handle error, cannot update email for third party account
return

# check that the account is not a social account
user_id = session.get_user_id()

# Then, we check if the email is verified for this user ID or not.
# It is important to understand that SuperTokens stores email verification
# status based on the user ID AND the email, and not just the email.
is_verified = is_email_verified(user_id, request_body["email"])
is_verified = is_email_verified(session.get_recipe_user_id(), request_body["email"])

if not is_verified:
if not is_email_change_allowed(
session.get_recipe_user_id(), request_body["email"], False
):
# Email change is not allowed, send a 400 error
return {"error": "Email change not allowed"}, 400
# Before sending a verification email, we check if the email is already
# being used by another user. If it is, we throw an error.
user = get_user(user_id)

if user is not None:
for tenant_id in user.tenant_ids:
users_with_same_email = list_users_by_account_info(
tenant_id, AccountInfo(email=request_body["email"])
)
for curr_user in users_with_same_email:
# Since one user can be shared across many tenants, we need to check if
# the email already exists in any of the tenants that belongs to this user.
if curr_user.id != user_id:
# TODO handle error, email already exists with another user.
return

# Create and send the email verification link to the user for the new email.
send_email_verification_email(session.get_tenant_id(), user_id, request_body["email"])

# TODO send successful email verification response
return

send_email_verification_email(
session.get_tenant_id(),
user_id,
session.get_recipe_user_id(),
request_body["email"],
)

# update the users email
update_response = update_user(user_id, email=request_body["email"])
update_response = update_user(
session.get_recipe_user_id(), email=request_body["email"]
)

if isinstance(update_response, UpdateUserOkResult):
# TODO send successful email update response
Expand All @@ -699,7 +776,7 @@ def change_email():
# highlight-end


async def is_valid_email(value: str) -> bool:
def is_valid_email(value: str) -> bool:
return (
fullmatch(
r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,'
Expand Down Expand Up @@ -843,68 +920,74 @@ from supertokens_python import init, InputAppInfo, SupertokensConfig
from supertokens_python.recipe import passwordless, emailverification
from supertokens_python.recipe.emailverification.interfaces import (
APIInterface,
APIOptions
APIOptions,
)

from supertokens_python.recipe.passwordless.asyncio import (
update_user
)
from supertokens_python.recipe.passwordless.asyncio import update_user

from supertokens_python.recipe.emailverification.interfaces import (
EmailVerifyPostOkResult
EmailVerifyPostOkResult,
)

from supertokens_python.recipe.session.interfaces import (
SessionContainer
)
from supertokens_python.recipe.session.interfaces import SessionContainer
from supertokens_python import (
InputAppInfo,
SupertokensConfig,
)

from supertokens_python.recipe.passwordless import (
ContactEmailOrPhoneConfig
)
from supertokens_python.recipe.passwordless import ContactEmailOrPhoneConfig

from typing import Optional, Dict, Any


def override_email_verification_apis(original_implementation: APIInterface):
original_email_verification_verify_email_post = original_implementation.email_verify_post

async def email_verify_post(token: str, session: Optional[SessionContainer], tenant_id: str, api_options: APIOptions, user_context: Dict[str, Any] ):

original_email_verification_verify_email_post = (
original_implementation.email_verify_post
)

async def email_verify_post(
token: str,
session: Optional[SessionContainer],
tenant_id: str,
api_options: APIOptions,
user_context: Dict[str, Any],
):

# highlight-start
verification_response = await original_email_verification_verify_email_post(token, session, tenant_id, api_options, user_context)

verification_response = await original_email_verification_verify_email_post(
token, session, tenant_id, api_options, user_context
)

if isinstance(verification_response, EmailVerifyPostOkResult):
await update_user(verification_response.user.user_id, verification_response.user.email)

await update_user(
verification_response.user.recipe_user_id,
verification_response.user.email,
)

return verification_response
# highlight-end

original_implementation.email_verify_post = email_verify_post
return original_implementation


init(
supertokens_config=SupertokensConfig(
connection_uri="..."
),
supertokens_config=SupertokensConfig(connection_uri="..."),
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework="flask",
recipe_list=[
passwordless.init(
flow_type="USER_INPUT_CODE_AND_MAGIC_LINK",
contact_config=ContactEmailOrPhoneConfig()),
contact_config=ContactEmailOrPhoneConfig(),
),
emailverification.init(
"REQUIRED",
override=emailverification.InputOverrideConfig(
# highlight-start
apis=override_email_verification_apis
# highlight-end
"REQUIRED",
override=emailverification.InputOverrideConfig(
# highlight-start
apis=override_email_verification_apis
# highlight-end
),
),
),
],
)
```
Expand Down

0 comments on commit 7917df6

Please sign in to comment.