Verify user emails#1151
Conversation
e91bbd0 to
6983493
Compare
…dle expired tokens
6983493 to
63e97a0
Compare
| new URL(`/not-found?from=${encodeURIComponent(redirectTo)}`, env.PUBPUB_URL), | ||
| opts | ||
| ); | ||
| const url = createRedirectUrl(redirectTo, opts?.searchParams); |
There was a problem hiding this comment.
Moved this to a helper file since I found I needed it elsewhere (mostly to easily tack on search params). but I think @tefkah 's lil routes lib would be a lot nicer
|
|
||
| const { user: tokenUser, authTokenType } = tokenSettled.value; | ||
|
|
||
| if (!tokenUser.isVerified) { |
There was a problem hiding this comment.
by putting this here in the magic-link route, we automatically mark users who go through this route as verified since they would've come from their email. this lets us get the requirement "Forgot password also functions as verification, in case where user signs up, never completes verification, and deletes verification email" for free
| }; | ||
|
|
||
| return ( | ||
| <SubmitButton |
There was a problem hiding this comment.
very handy button!!
| <Toaster /> | ||
| <Suspense> | ||
| <RootToaster /> | ||
| </Suspense> |
There was a problem hiding this comment.
hopefully this is okay—I wasn't sure how else to get the toast after redirect to work. it needs Suspense since RootToaster needs to useSearchParams in order to figure out whether to render the 'verified' toast
There was a problem hiding this comment.
I think this is correct! Not sure if their are any repercussions from this, but for now it seems fine
| } | ||
|
|
||
| redirect(`/c/${communitySlug}/stages`); | ||
| redirect(createRedirectUrl(`/c/${communitySlug}/stages`, params).toString()); |
There was a problem hiding this comment.
the regular pages weren't passing on search params. I needed it to so that ?verified=true could get passed along to arbitrary pages
tefkah
left a comment
There was a problem hiding this comment.
i think it looks great! very elegant solution of making magic-links set the verified to true.
I made some optional suggestions which don't really need to be implemented if you don't feel like it, but there's one situation we do still need to cover.
if a user is invited through the email action, they are prompted to fill in their information+create a password. they can pick a different email during signup. i think in that case, we should force them to verify their email. this happens here:
https://github.com/pubpub/platform/blob/a54eb9b77fd202346153a294e3775c9dc9eb9bfb/core/lib/authentication/actions.ts#L493-L529
i think we'll get rid of this route as a special case soon, but we should still cover it for now!
greate work!
| <Toaster /> | ||
| <Suspense> | ||
| <RootToaster /> | ||
| </Suspense> |
There was a problem hiding this comment.
I think this is correct! Not sure if their are any repercussions from this, but for now it seems fine
| const router = useRouter(); | ||
| const searchParams = useSearchParams(); | ||
| const pathname = usePathname(); | ||
|
|
||
| useEffect(() => { | ||
| if (searchParams.has(VERIFIED_PARAM)) { | ||
| toast({ | ||
| title: "Verified", | ||
| description: ( | ||
| <span className="flex items-center gap-1"> | ||
| <CircleCheck size="16" /> Your email is now verified | ||
| </span> | ||
| ), | ||
| variant: "success", | ||
| }); | ||
|
|
||
| // Remove the param so we don't see the popover again | ||
| const params = new URLSearchParams(searchParams); | ||
| params.delete(VERIFIED_PARAM); | ||
| router.replace(`${pathname}?${params.toString()}`); | ||
| } | ||
| }, [searchParams]); |
There was a problem hiding this comment.
i think it may be nicer to use nuqs here. replacing the VERIFIED_PARAM causes another refresh of all the data on the server, which i don't think is what we want as the effect of the ?verified param is (i think) only relevant client side.
if you use nuqs, it by default uses shallow updating of the searchParams, which don't cause a server refresh. see here for how to do something like that
There was a problem hiding this comment.
might be nice to just wrap up all of this logic in a hook, eg usePersistedToasts or something, and define all the possible params in some configuration object, eg
const PERSISTED_TOAST = {
'verified': {
title: "Verified",
description: <span...>,
variant: "success"
}
} as const satisfies ...| slug?: string; | ||
| existing?: false; | ||
| /** | ||
| * @default true |
| return buildSend(async () => { | ||
| const magicLink = await createMagicLink( | ||
| { | ||
| type: AuthTokenType.generic, |
There was a problem hiding this comment.
could you maybe add a small comment here why this needs to be AuthTokenType.generic vs AuthTokenType.verifyEmail? I think it's bc /magic-link will set their email to be verified, but i think that's easy to forget!
|
Thanks @tefkah , did not realize the legacy signup needed this too! added, and also implemented using nuqs, great idea! Screen.Recording.2025-04-14.at.3.35.18.PM.mov |
Issue(s) Resolved
Closes #1121
High-level Explanation of PR
Adds a verify email flow.
Test Plan
I don't think this will work in the preview environment since we need to use emails.
Happy path:
Other things to test:
/verifypage, but the "Forgot password" flow is still accessibleScreenshots (if applicable)
Screen.Recording.2025-04-10.at.2.49.30.PM.mov
Notes