-
-
Notifications
You must be signed in to change notification settings - Fork 142
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
feat: allow users update their account email address with proper validation. #4520
base: main
Are you sure you want to change the base?
Conversation
@apsinghdev is attempting to deploy a commit to the polar-sh Team on Vercel. A member of the Team first needs to authorize it. |
@frankie567 I've raised this draft PR that contains backend code. please have a look and let me know if it make sense. (There are further improvements like tags that I'll make this week) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's great work @apsinghdev 👍 Let's change a few things to make it really tailored for email verification :)
Be sure also to run linters:
uv run task lint && uv run task lint_types
from polar.models.user import User | ||
|
||
def get_expires_at() -> datetime: | ||
return utc_now() + timedelta(seconds=settings.MAGIC_LINK_TTL_SECONDS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's have a dedicated setting for that (keep the same default as magic link)
from .schemas import EmailUpdateCreate | ||
|
||
|
||
TOKEN_PREFIX = "polar_ml_" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prefix needs to be changed. Let's say polar_ev_
.
email_update_record = EmailVerification(**email_update_create.model_dump(exclude_unset=True)) | ||
|
||
session.add(email_update_record) | ||
await session.commit() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use await session.flush()
instead of committing here. The difference is here:
- With
flush
, the changes are sent to the DB (so we have integrity checks triggered and default values generated) but the transaction is not committed. It means that if something goes wrong after that in the request, the changes won't be saved to the database, ensuring it's consistent. - With
commit
, all of the above is done but the transaction is committed, meaning the changes are definitely written to the DB. If something goes wrong after that, it's over, there is no going back.
PS: I see we do that in the magic link service, but that's bad and we should fix it 😜
user.email = email_update_record.email | ||
|
||
await session.delete(email_update_record) | ||
await session.commit() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above regarding commit
. In this case, we can even have just await session.delete(...)
and nothing else: commit
is called automatically by a middleware after the response is sent to the client.
to_email_addr=email_update_record.email, subject=subject, html_content=body | ||
) | ||
|
||
async def authenticate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should call the method differently, let's say verify
.
from polar.routing import APIRouter | ||
|
||
from .schemas import EmailUpdateRequest | ||
from .service import email_update as email_upate_service |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: email_update_service
email_update_record, token = await email_upate_service.request_email_update( | ||
email_update_request.email, | ||
session, | ||
user_id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit of a nitpick, but let's pass the auth_subject
directly to the method.
e.message, e.status_code, return_to=return_to | ||
) from e | ||
|
||
await loops_service.user_update(session, user, emailLogin=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
emailLogin
property is not relevant here (it's not a login). We just need to update the user email.
await loops_service.user_update(session, user)
|
||
{% block body %} | ||
<h1>Hi,</h1> | ||
<p>Here is the magic link to update your email. Click the button below to complete the update process. <strong>This link |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's write:
Here is the verification link to update your email. Click the button below to complete the update process.
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation"> | ||
<tr> | ||
<td align="center"> | ||
<a href="{{ url | safe }}" class="f-fallback button">Sign in to Polar</a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update my email
@frankie567 Thanks for the reviews. I'll fix these very soon. |
fixes: #4237
This PR implements the feature to allow users to update their email. (Draft)