From cf5c7d98b9c736631fd156770afe6be3dfa2e09d Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 25 Jul 2022 21:46:27 -0700 Subject: [PATCH 01/21] Add confirm email landing page --- packages/web/pages/confirm-email/[token].tsx | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 packages/web/pages/confirm-email/[token].tsx diff --git a/packages/web/pages/confirm-email/[token].tsx b/packages/web/pages/confirm-email/[token].tsx new file mode 100644 index 000000000..235d3b199 --- /dev/null +++ b/packages/web/pages/confirm-email/[token].tsx @@ -0,0 +1,60 @@ +import { useEffect, useState } from 'react' +import { useRouter } from 'next/router' +import { Toaster } from 'react-hot-toast' + +import { applyStoredTheme } from '../../lib/themeUpdater' + +import { PrimaryLayout } from '../../components/templates/PrimaryLayout' + +import { HStack, SpanBox } from '../../components/elements/LayoutPrimitives' +import { Loader } from '../../components/templates/SavingRequest' +import { fetchEndpoint } from '../../lib/appConfig' +import { LoadingView } from '../../components/patterns/LoadingView' + +export default function ConfirmEmail(): JSX.Element { + const router = useRouter() + const [errorMessage, setErrorMessage] = useState(undefined) + + applyStoredTheme(false) + + useEffect(() => { + if (!router || !router.isReady) { + return + } + + const token = router.query.token + fetch(`${fetchEndpoint}/auth/confirm-email`, { + method: 'POST', + redirect: 'follow', + mode: 'cors', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ token }), + }).then((response) => { + if (response.status === 200) { + window.localStorage.setItem('authVerified', 'true') + window.location.href = '/' + } else { + setErrorMessage('Error confirming email') + } + }) + + }, [router]) + + return ( + + + + {errorMessage ? ( + {errorMessage} + ) : } + + + ) +} From 29ed803ae578dc6bdc2239a0ca6713a030345522 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 26 Jul 2022 13:31:36 +0800 Subject: [PATCH 02/21] Fix RLS issue of updating user table --- packages/api/src/routers/auth/auth_router.ts | 32 +++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index c6e7ce3d3..93a6c03d2 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -45,8 +45,8 @@ import { } from '../../utils/auth' import { createUser } from '../../services/create_user' import { isErrorWithCode } from '../../resolvers' -import { initModels } from '../../server' -import { getRepository } from '../../entity/utils' +import { AppDataSource, initModels } from '../../server' +import { getRepository, setClaims } from '../../entity/utils' import { User } from '../../entity/user' import { sendConfirmationEmail, @@ -503,10 +503,21 @@ export function authRouter() { } if (user.status === StatusType.Pending) { - await getRepository(User).update( - { id: user.id }, - { status: StatusType.Active } + const updated = await AppDataSource.transaction( + async (entityManager) => { + await setClaims(entityManager, user.id) + return getRepository(User).update( + { id: user.id }, + { status: StatusType.Active } + ) + } ) + + if (!updated.affected) { + return res.redirect( + `${env.client.url}/confirm-email?errorCodes=UNKNOWN` + ) + } } res.set('Message', 'EMAIL_CONFIRMED') @@ -612,9 +623,14 @@ export function authRouter() { } const hashedPassword = await hashPassword(password) - const updated = await getRepository(User).update( - { id: user.id }, - { password: hashedPassword } + const updated = await AppDataSource.transaction( + async (entityManager) => { + await setClaims(entityManager, user.id) + return getRepository(User).update( + { id: user.id }, + { password: hashedPassword } + ) + } ) if (!updated.affected) { return res.redirect( From dca9066f19708caeca071a439c2d642a9510207c Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 26 Jul 2022 13:41:02 +0800 Subject: [PATCH 03/21] Continue fix RLS issue of updating user table --- packages/api/src/routers/auth/auth_router.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index 93a6c03d2..ad19c9b1e 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -506,10 +506,9 @@ export function authRouter() { const updated = await AppDataSource.transaction( async (entityManager) => { await setClaims(entityManager, user.id) - return getRepository(User).update( - { id: user.id }, - { status: StatusType.Active } - ) + return entityManager + .getRepository(User) + .update({ id: user.id }, { status: StatusType.Active }) } ) @@ -626,10 +625,9 @@ export function authRouter() { const updated = await AppDataSource.transaction( async (entityManager) => { await setClaims(entityManager, user.id) - return getRepository(User).update( - { id: user.id }, - { password: hashedPassword } - ) + return entityManager + .getRepository(User) + .update({ id: user.id }, { password: hashedPassword }) } ) if (!updated.affected) { From 20dad1bd3f80b4772af3733b35aae266d70444d8 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 25 Jul 2022 22:58:45 -0700 Subject: [PATCH 04/21] Use form to submit so we redirect to sso endpoint correctly --- packages/web/pages/confirm-email/[token].tsx | 34 +++++++------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/packages/web/pages/confirm-email/[token].tsx b/packages/web/pages/confirm-email/[token].tsx index 235d3b199..5a0d9754e 100644 --- a/packages/web/pages/confirm-email/[token].tsx +++ b/packages/web/pages/confirm-email/[token].tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useRouter } from 'next/router' import { Toaster } from 'react-hot-toast' @@ -12,36 +12,19 @@ import { fetchEndpoint } from '../../lib/appConfig' import { LoadingView } from '../../components/patterns/LoadingView' export default function ConfirmEmail(): JSX.Element { + const authForm = useRef(null) const router = useRouter() const [errorMessage, setErrorMessage] = useState(undefined) applyStoredTheme(false) useEffect(() => { - if (!router || !router.isReady) { + if (!router || !router.isReady || !authForm.current) { return } - const token = router.query.token - fetch(`${fetchEndpoint}/auth/confirm-email`, { - method: 'POST', - redirect: 'follow', - mode: 'cors', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ token }), - }).then((response) => { - if (response.status === 200) { - window.localStorage.setItem('authVerified', 'true') - window.location.href = '/' - } else { - setErrorMessage('Error confirming email') - } - }) - - }, [router]) + authForm.current?.submit() + }, [router, authForm]) return ( @@ -50,6 +33,13 @@ export default function ConfirmEmail(): JSX.Element { top: '5rem', }} /> +
+ +
{errorMessage ? ( {errorMessage} From cb23613cdc1214bd98c00756aa3c9e61cfe57b0e Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 26 Jul 2022 14:06:11 +0800 Subject: [PATCH 05/21] Add Set-Cookie header --- packages/api/src/routers/auth/auth_router.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index ad19c9b1e..693e3861c 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -420,6 +420,13 @@ export function authRouter() { // set auth cookie in response header await setAuthInCookie({ uid: user.id }, res) + const cookie = res.get('cookie') + if (!cookie) { + return res.redirect( + `${env.client.url}/email-login?errorCodes=${LoginErrorCode.AuthFailed}` + ) + } + res.setHeader('set-cookie', cookie) await handleSuccessfulLogin(req, res, user, false) } catch (e) { logger.info('email-login exception:', e) From 2d9f4a01945a71670bafee2088d400d0b63e7f2c Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 25 Jul 2022 23:08:48 -0700 Subject: [PATCH 06/21] Redirect to the verify-email informational page after sign up --- packages/api/src/routers/auth/auth_router.ts | 2 +- packages/web/pages/verify-email.tsx | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 packages/web/pages/verify-email.tsx diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index 693e3861c..e2b7418b4 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -469,7 +469,7 @@ export function authRouter() { pendingConfirmation: true, }) - res.redirect(`${env.client.url}/email-login?message=SIGNUP_SUCCESS`) + res.redirect(`${env.client.url}/verify-email?message=SIGNUP_SUCCESS`) } catch (e) { logger.info('email-signup exception:', e) if (isErrorWithCode(e)) { diff --git a/packages/web/pages/verify-email.tsx b/packages/web/pages/verify-email.tsx new file mode 100644 index 000000000..c25d7dff4 --- /dev/null +++ b/packages/web/pages/verify-email.tsx @@ -0,0 +1,12 @@ +import { PageMetaData } from '../components/patterns/PageMetaData' +import { VerifyEmail } from '../components/templates/VerifyEmail' + +export default function VerifyEmailPage(): JSX.Element { + return ( + <> + + +
+ + ) +} From 072b2dd9c5fe4682eb96a71194cab7cc8ca4c03b Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 26 Jul 2022 14:12:37 +0800 Subject: [PATCH 07/21] Revert "Add Set-Cookie header" This reverts commit cb23613cdc1214bd98c00756aa3c9e61cfe57b0e. --- packages/api/src/routers/auth/auth_router.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index e2b7418b4..892fdc40c 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -420,13 +420,6 @@ export function authRouter() { // set auth cookie in response header await setAuthInCookie({ uid: user.id }, res) - const cookie = res.get('cookie') - if (!cookie) { - return res.redirect( - `${env.client.url}/email-login?errorCodes=${LoginErrorCode.AuthFailed}` - ) - } - res.setHeader('set-cookie', cookie) await handleSuccessfulLogin(req, res, user, false) } catch (e) { logger.info('email-login exception:', e) From a335bfacfcdad25e5bcf2070168bea2c1a839dd6 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 25 Jul 2022 23:29:22 -0700 Subject: [PATCH 08/21] Add verify email component, make email reset url match backend naming --- .../web/components/templates/VerifyEmail.tsx | 53 +++++++++++++++++++ packages/web/pages/forgot-password.tsx | 36 +++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 packages/web/components/templates/VerifyEmail.tsx create mode 100644 packages/web/pages/forgot-password.tsx diff --git a/packages/web/components/templates/VerifyEmail.tsx b/packages/web/components/templates/VerifyEmail.tsx new file mode 100644 index 000000000..28a9103c5 --- /dev/null +++ b/packages/web/components/templates/VerifyEmail.tsx @@ -0,0 +1,53 @@ +import { Box, HStack, MediumBreakpointBox, SpanBox, VStack } from '../elements/LayoutPrimitives' +import type { LoginFormProps } from './LoginForm' +import { OmnivoreNameLogo } from '../elements/images/OmnivoreNameLogo' +import { theme } from '../tokens/stitches.config' + +export function VerifyEmail(props: LoginFormProps): JSX.Element { + return ( + <> + + +

Verify your email address

+ + We sent a verification link to the email you provided. Click the link to verify your email. You may need to check your spam folder. + +
+
+ + + + + + + ) +} diff --git a/packages/web/pages/forgot-password.tsx b/packages/web/pages/forgot-password.tsx new file mode 100644 index 000000000..3ad70ee63 --- /dev/null +++ b/packages/web/pages/forgot-password.tsx @@ -0,0 +1,36 @@ +import { PageMetaData } from '../components/patterns/PageMetaData' +import { ProfileLayout } from '../components/templates/ProfileLayout' +import { EmailResetPassword } from '../components/templates/EmailResetPassword' +import { useEffect } from 'react' +import { useRouter } from 'next/router' +import toast, { Toaster } from 'react-hot-toast' +import { showSuccessToast } from '../lib/toastHelpers' + +export default function ForgotPassword(): JSX.Element { + const router = useRouter() + + useEffect(() => { + if (router && router.isReady && router.query.message === 'SUCCESS') { + showSuccessToast('Reset password email sent') + setTimeout(() => { + window.location.href = '/email-login' + }, 2000) + } + }, [router]) + + + return ( + <> + + + + + +
+ + ) +} From acd36fe9af07a536e4ecf5cb9c8bd1d81d44c13a Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 10:31:45 -0700 Subject: [PATCH 09/21] Move auth routes into /auth folder --- packages/api/src/routers/auth/auth_router.ts | 60 +++++++++---------- packages/api/src/services/send_emails.ts | 4 +- packages/api/test/routers/auth.test.ts | 8 +-- .../web/components/templates/EmailLogin.tsx | 6 +- .../web/components/templates/EmailSignup.tsx | 2 +- .../web/components/templates/LoginForm.tsx | 27 --------- packages/web/locales/en/messages.ts | 1 + .../{ => auth}/confirm-email/[token].tsx | 11 ++-- packages/web/pages/auth/email-login.tsx | 15 +++++ packages/web/pages/auth/email-signup.tsx | 15 +++++ .../web/pages/{ => auth}/forgot-password.tsx | 12 ++-- packages/web/pages/auth/reset-password.tsx | 15 +++++ packages/web/pages/auth/verify-email.tsx | 12 ++++ packages/web/pages/email-login.tsx | 56 ----------------- packages/web/pages/email-reset-password.tsx | 15 ----- packages/web/pages/email-signup.tsx | 15 ----- packages/web/pages/verify-email.tsx | 12 ---- 17 files changed, 109 insertions(+), 177 deletions(-) rename packages/web/pages/{ => auth}/confirm-email/[token].tsx (73%) create mode 100644 packages/web/pages/auth/email-login.tsx create mode 100644 packages/web/pages/auth/email-signup.tsx rename packages/web/pages/{ => auth}/forgot-password.tsx (66%) create mode 100644 packages/web/pages/auth/reset-password.tsx create mode 100644 packages/web/pages/auth/verify-email.tsx delete mode 100644 packages/web/pages/email-login.tsx delete mode 100644 packages/web/pages/email-reset-password.tsx delete mode 100644 packages/web/pages/email-signup.tsx delete mode 100644 packages/web/pages/verify-email.tsx diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index 892fdc40c..9a618279a 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -340,7 +340,7 @@ export function authRouter() { ) } return res.redirect( - `${env.client.url}/settings/installation/extensions` + `${env.client.url}/home` ) } @@ -377,7 +377,7 @@ export function authRouter() { if (!email || !password) { return res.redirect( - `${env.client.url}/email-login?errorCodes=${LoginErrorCode.InvalidCredentials}` + `${env.client.url}/auth/email-login?errorCodes=${LoginErrorCode.InvalidCredentials}` ) } @@ -388,7 +388,7 @@ export function authRouter() { }) if (!user?.id) { return res.redirect( - `${env.client.url}/email-login?errorCodes=${LoginErrorCode.UserNotFound}` + `${env.client.url}/auth/email-login?errorCodes=${LoginErrorCode.UserNotFound}` ) } @@ -399,14 +399,14 @@ export function authRouter() { name: user.name, }) return res.redirect( - `${env.client.url}/email-login?errorCodes=PENDING_VERIFICATION` + `${env.client.url}/auth/email-login?errorCodes=PENDING_VERIFICATION` ) } if (!user?.password) { // user has no password, so they need to set one return res.redirect( - `${env.client.url}/email-login?errorCodes=${LoginErrorCode.WrongSource}` + `${env.client.url}/auth/email-login?errorCodes=${LoginErrorCode.WrongSource}` ) } @@ -414,7 +414,7 @@ export function authRouter() { const validPassword = await comparePassword(password, user.password) if (!validPassword) { return res.redirect( - `${env.client.url}/email-login?errorCodes=${LoginErrorCode.InvalidCredentials}` + `${env.client.url}/auth/email-login?errorCodes=${LoginErrorCode.InvalidCredentials}` ) } @@ -423,7 +423,7 @@ export function authRouter() { await handleSuccessfulLogin(req, res, user, false) } catch (e) { logger.info('email-login exception:', e) - res.redirect(`${env.client.url}/email-login?errorCodes=AUTH_FAILED`) + res.redirect(`${env.client.url}/auth/email-login?errorCodes=AUTH_FAILED`) } } ) @@ -441,7 +441,7 @@ export function authRouter() { if (!email || !password || !name || !username) { return res.redirect( - `${env.client.url}/email-signup?errorCodes=INVALID_CREDENTIALS` + `${env.client.url}/auth/email-signup?errorCodes=INVALID_CREDENTIALS` ) } const lowerCasedUsername = username.toLowerCase() @@ -462,15 +462,15 @@ export function authRouter() { pendingConfirmation: true, }) - res.redirect(`${env.client.url}/verify-email?message=SIGNUP_SUCCESS`) + res.redirect(`${env.client.url}/auth/verify-email?message=SIGNUP_SUCCESS`) } catch (e) { logger.info('email-signup exception:', e) if (isErrorWithCode(e)) { return res.redirect( - `${env.client.url}/email-signup?errorCodes=${e.errorCode}` + `${env.client.url}/auth/email-signup?errorCodes=${e.errorCode}` ) } - res.redirect(`${env.client.url}/email-signup?errorCodes=UNKNOWN`) + res.redirect(`${env.client.url}/auth/email-signup?errorCodes=UNKNOWN`) } } ) @@ -491,14 +491,14 @@ export function authRouter() { const claims = await getClaimsByToken(token) if (!claims) { return res.redirect( - `${env.client.url}/confirm-email?errorCodes=INVALID_TOKEN` + `${env.client.url}/auth/confirm-email?errorCodes=INVALID_TOKEN` ) } const user = await getRepository(User).findOneBy({ id: claims.uid }) if (!user) { return res.redirect( - `${env.client.url}/confirm-email?errorCodes=USER_NOT_FOUND` + `${env.client.url}/auth/confirm-email?errorCodes=USER_NOT_FOUND` ) } @@ -514,7 +514,7 @@ export function authRouter() { if (!updated.affected) { return res.redirect( - `${env.client.url}/confirm-email?errorCodes=UNKNOWN` + `${env.client.url}/auth/confirm-email?errorCodes=UNKNOWN` ) } } @@ -526,11 +526,11 @@ export function authRouter() { logger.info('confirm-email exception:', e) if (e instanceof jwt.TokenExpiredError) { return res.redirect( - `${env.client.url}/confirm-email?errorCodes=TOKEN_EXPIRED` + `${env.client.url}/auth/confirm-email?errorCodes=TOKEN_EXPIRED` ) } - res.redirect(`${env.client.url}/confirm-email?errorCodes=INVALID_TOKEN`) + res.redirect(`${env.client.url}/auth/confirm-email?errorCodes=INVALID_TOKEN`) } } ) @@ -547,7 +547,7 @@ export function authRouter() { const email = req.body.email if (!email) { return res.redirect( - `${env.client.url}/forgot-password?errorCodes=INVALID_EMAIL` + `${env.client.url}/auth/forgot-password?errorCodes=INVALID_EMAIL` ) } @@ -557,27 +557,27 @@ export function authRouter() { }) if (!user) { return res.redirect( - `${env.client.url}/forgot-password?errorCodes=USER_NOT_FOUND` + `${env.client.url}/auth/forgot-password?errorCodes=USER_NOT_FOUND` ) } if (user.status === StatusType.Pending) { return res.redirect( - `${env.client.url}/email-login?errorCodes=PENDING_VERIFICATION` + `${env.client.url}/auth/email-login?errorCodes=PENDING_VERIFICATION` ) } if (!(await sendPasswordResetEmail(user))) { return res.redirect( - `${env.client.url}/forgot-password?errorCodes=INVALID_EMAIL` + `${env.client.url}/auth/forgot-password?errorCodes=INVALID_EMAIL` ) } - res.redirect(`${env.client.url}/forgot-password?message=SUCCESS`) + res.redirect(`${env.client.url}/auth/forgot-password?message=SUCCESS`) } catch (e) { logger.info('forgot-password exception:', e) - res.redirect(`${env.client.url}/forgot-password?errorCodes=UNKNOWN`) + res.redirect(`${env.client.url}/auth/forgot-password?errorCodes=UNKNOWN`) } } ) @@ -598,26 +598,26 @@ export function authRouter() { const claims = await getClaimsByToken(token) if (!claims) { return res.redirect( - `${env.client.url}/reset-password?errorCodes=INVALID_TOKEN` + `${env.client.url}/auth/reset-password?errorCodes=INVALID_TOKEN` ) } if (!password) { return res.redirect( - `${env.client.url}/reset-password?errorCodes=INVALID_PASSWORD` + `${env.client.url}/auth/reset-password?errorCodes=INVALID_PASSWORD` ) } const user = await getRepository(User).findOneBy({ id: claims.uid }) if (!user) { return res.redirect( - `${env.client.url}/reset-password?errorCodes=USER_NOT_FOUND` + `${env.client.url}/auth/reset-password?errorCodes=USER_NOT_FOUND` ) } if (user.status === StatusType.Pending) { return res.redirect( - `${env.client.url}/email-login?errorCodes=PENDING_VERIFICATION` + `${env.client.url}/auth/email-login?errorCodes=PENDING_VERIFICATION` ) } @@ -632,21 +632,21 @@ export function authRouter() { ) if (!updated.affected) { return res.redirect( - `${env.client.url}/reset-password?errorCodes=UNKNOWN` + `${env.client.url}/auth/reset-password?errorCodes=UNKNOWN` ) } - res.redirect(`${env.client.url}/reset-password?message=SUCCESS`) + res.redirect(`${env.client.url}/auth/reset-password?message=SUCCESS`) } catch (e) { logger.info('reset-password exception:', e) if (e instanceof jwt.TokenExpiredError) { return res.redirect( - `${env.client.url}/reset-password?errorCodes=TOKEN_EXPIRED` + `${env.client.url}/auth/reset-password?errorCodes=TOKEN_EXPIRED` ) } res.redirect( - `${env.client.url}/reset-password?errorCodes=INVALID_TOKEN` + `${env.client.url}/auth/reset-password?errorCodes=INVALID_TOKEN` ) } } diff --git a/packages/api/src/services/send_emails.ts b/packages/api/src/services/send_emails.ts index a6fbf6d42..2ebefd3d1 100644 --- a/packages/api/src/services/send_emails.ts +++ b/packages/api/src/services/send_emails.ts @@ -9,7 +9,7 @@ export const sendConfirmationEmail = async (user: { }): Promise => { // generate confirmation link const token = generateVerificationToken(user.id) - const link = `${env.client.url}/confirm-email/${token}` + const link = `${env.client.url}/auth/confirm-email/${token}` // send email const dynamicTemplateData = { name: user.name, @@ -31,7 +31,7 @@ export const sendPasswordResetEmail = async (user: { }): Promise => { // generate link const token = generateVerificationToken(user.id) - const link = `${env.client.url}/reset-password/${token}` + const link = `${env.client.url}/auth/reset-password/${token}` // send email const dynamicTemplateData = { name: user.name, diff --git a/packages/api/test/routers/auth.test.ts b/packages/api/test/routers/auth.test.ts index 526ccce02..fa14521be 100644 --- a/packages/api/test/routers/auth.test.ts +++ b/packages/api/test/routers/auth.test.ts @@ -499,7 +499,7 @@ describe('auth router', () => { 302 ) expect(res.header.location).to.endWith( - '/reset-password?message=SUCCESS' + '/auth/reset-password?message=SUCCESS' ) }) @@ -518,7 +518,7 @@ describe('auth router', () => { it('redirects to reset-password page with error code INVALID_PASSWORD', async () => { const res = await resetPasswordRequest(token, '').expect(302) expect(res.header.location).to.endWith( - '/reset-password?errorCodes=INVALID_PASSWORD' + '/auth/reset-password?errorCodes=INVALID_PASSWORD' ) }) }) @@ -531,7 +531,7 @@ describe('auth router', () => { 'new_password' ).expect(302) expect(res.header.location).to.endWith( - '/reset-password?errorCodes=INVALID_TOKEN' + '/auth/reset-password?errorCodes=INVALID_TOKEN' ) }) @@ -545,7 +545,7 @@ describe('auth router', () => { 302 ) expect(res.header.location).to.endWith( - '/reset-password?errorCodes=TOKEN_EXPIRED' + '/auth/reset-password?errorCodes=TOKEN_EXPIRED' ) }) }) diff --git a/packages/web/components/templates/EmailLogin.tsx b/packages/web/components/templates/EmailLogin.tsx index f644974dd..4c2f0248f 100644 --- a/packages/web/components/templates/EmailLogin.tsx +++ b/packages/web/components/templates/EmailLogin.tsx @@ -45,7 +45,7 @@ export function EmailLogin(): JSX.Element { return (
- Login + Login Email @@ -111,7 +111,7 @@ export function EmailLogin(): JSX.Element { }} > Don't have an account? {' '} - + Sign up @@ -126,7 +126,7 @@ export function EmailLogin(): JSX.Element { }} > Forgot your password? {' '} - + Click here diff --git a/packages/web/components/templates/EmailSignup.tsx b/packages/web/components/templates/EmailSignup.tsx index 8c12b5b30..113e16f05 100644 --- a/packages/web/components/templates/EmailSignup.tsx +++ b/packages/web/components/templates/EmailSignup.tsx @@ -173,7 +173,7 @@ export function EmailSignup(): JSX.Element { }} > Already have an account? {' '} - + Login instead diff --git a/packages/web/components/templates/LoginForm.tsx b/packages/web/components/templates/LoginForm.tsx index c48ef75f2..9af721277 100644 --- a/packages/web/components/templates/LoginForm.tsx +++ b/packages/web/components/templates/LoginForm.tsx @@ -106,33 +106,6 @@ export function LoginForm(props: LoginFormProps): JSX.Element { /> )} -{/* - - - Use your email address to{' '} - - - Login - - {' '} - or{' '} - - - Signup - - {' '} - with your email address. - - */} - diff --git a/packages/web/locales/en/messages.ts b/packages/web/locales/en/messages.ts index 1efc879fc..8a1d2f65f 100644 --- a/packages/web/locales/en/messages.ts +++ b/packages/web/locales/en/messages.ts @@ -24,6 +24,7 @@ const errorMessages: Record = { 'error.EXPIRED_TOKEN': "Your sign up page has timed out, you'll be redirected to Google sign in page to authenticate again.", 'error.USER_EXISTS': 'User with this email exists already', + 'error.UNKNOWN': 'An unknown error occurred', } const loginPageMessages: Record = { diff --git a/packages/web/pages/confirm-email/[token].tsx b/packages/web/pages/auth/confirm-email/[token].tsx similarity index 73% rename from packages/web/pages/confirm-email/[token].tsx rename to packages/web/pages/auth/confirm-email/[token].tsx index 5a0d9754e..8a44f40b7 100644 --- a/packages/web/pages/confirm-email/[token].tsx +++ b/packages/web/pages/auth/confirm-email/[token].tsx @@ -2,14 +2,13 @@ import { useEffect, useRef, useState } from 'react' import { useRouter } from 'next/router' import { Toaster } from 'react-hot-toast' -import { applyStoredTheme } from '../../lib/themeUpdater' +import { applyStoredTheme } from '../../../lib/themeUpdater' -import { PrimaryLayout } from '../../components/templates/PrimaryLayout' +import { PrimaryLayout } from '../../../components/templates/PrimaryLayout' -import { HStack, SpanBox } from '../../components/elements/LayoutPrimitives' -import { Loader } from '../../components/templates/SavingRequest' -import { fetchEndpoint } from '../../lib/appConfig' -import { LoadingView } from '../../components/patterns/LoadingView' +import { HStack, SpanBox } from '../../../components/elements/LayoutPrimitives' +import { fetchEndpoint } from '../../../lib/appConfig' +import { LoadingView } from '../../../components/patterns/LoadingView' export default function ConfirmEmail(): JSX.Element { const authForm = useRef(null) diff --git a/packages/web/pages/auth/email-login.tsx b/packages/web/pages/auth/email-login.tsx new file mode 100644 index 000000000..aadf9913f --- /dev/null +++ b/packages/web/pages/auth/email-login.tsx @@ -0,0 +1,15 @@ +import { PageMetaData } from '../../components/patterns/PageMetaData' +import { ProfileLayout } from '../../components/templates/ProfileLayout' +import { EmailLogin } from '../../components/templates/EmailLogin' + +export default function EmailLoginPage(): JSX.Element { + return ( + <> + + + + +
+ + ) +} diff --git a/packages/web/pages/auth/email-signup.tsx b/packages/web/pages/auth/email-signup.tsx new file mode 100644 index 000000000..819fe0b16 --- /dev/null +++ b/packages/web/pages/auth/email-signup.tsx @@ -0,0 +1,15 @@ +import { PageMetaData } from '../../components/patterns/PageMetaData' +import { ProfileLayout } from '../../components/templates/ProfileLayout' +import { EmailSignup } from '../../components/templates/EmailSignup' + +export default function EmailRegistrationPage(): JSX.Element { + return ( + <> + + + + +
+ + ) +} diff --git a/packages/web/pages/forgot-password.tsx b/packages/web/pages/auth/forgot-password.tsx similarity index 66% rename from packages/web/pages/forgot-password.tsx rename to packages/web/pages/auth/forgot-password.tsx index 3ad70ee63..d1ee0a922 100644 --- a/packages/web/pages/forgot-password.tsx +++ b/packages/web/pages/auth/forgot-password.tsx @@ -1,10 +1,10 @@ -import { PageMetaData } from '../components/patterns/PageMetaData' -import { ProfileLayout } from '../components/templates/ProfileLayout' -import { EmailResetPassword } from '../components/templates/EmailResetPassword' +import { PageMetaData } from '../../components/patterns/PageMetaData' +import { ProfileLayout } from '../../components/templates/ProfileLayout' +import { EmailResetPassword } from '../../components/templates/EmailResetPassword' import { useEffect } from 'react' import { useRouter } from 'next/router' import toast, { Toaster } from 'react-hot-toast' -import { showSuccessToast } from '../lib/toastHelpers' +import { showSuccessToast } from '../../lib/toastHelpers' export default function ForgotPassword(): JSX.Element { const router = useRouter() @@ -21,7 +21,7 @@ export default function ForgotPassword(): JSX.Element { return ( <> - + -
+
) } diff --git a/packages/web/pages/auth/reset-password.tsx b/packages/web/pages/auth/reset-password.tsx new file mode 100644 index 000000000..f2dff55ba --- /dev/null +++ b/packages/web/pages/auth/reset-password.tsx @@ -0,0 +1,15 @@ +import { PageMetaData } from '../../components/patterns/PageMetaData' +import { ProfileLayout } from '../../components/templates/ProfileLayout' +import { EmailResetPassword } from '../../components/templates/EmailResetPassword' + +export default function EmailRegistrationPage(): JSX.Element { + return ( + <> + + + + +
+ + ) +} diff --git a/packages/web/pages/auth/verify-email.tsx b/packages/web/pages/auth/verify-email.tsx new file mode 100644 index 000000000..0606e88df --- /dev/null +++ b/packages/web/pages/auth/verify-email.tsx @@ -0,0 +1,12 @@ +import { PageMetaData } from '../../components/patterns/PageMetaData' +import { VerifyEmail } from '../../components/templates/VerifyEmail' + +export default function VerifyEmailPage(): JSX.Element { + return ( + <> + + +
+ + ) +} diff --git a/packages/web/pages/email-login.tsx b/packages/web/pages/email-login.tsx deleted file mode 100644 index 6011118f0..000000000 --- a/packages/web/pages/email-login.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { PageMetaData } from '../components/patterns/PageMetaData' -import { ProfileLayout } from '../components/templates/ProfileLayout' -import { EmailLogin } from '../components/templates/EmailLogin' - -export default function EmailLoginPage(): JSX.Element { - return ( - <> - - - - -
- - ) -} - -// export default function EmailLogin(): JSX.Element { -// const [errorMessage, setErrorMessage] = useState( -// undefined -// ) -// const [message, setMessage] = useState(undefined) -// const router = useRouter() - -// useEffect(() => { -// if (!router.isReady) return -// const errorCode = parseErrorCodes(router.query) -// const errorMsg = errorCode -// ? formatMessage({ id: `error.${errorCode}` }) -// : undefined -// setErrorMessage(errorMsg) - -// const message = router.query.message -// ? formatMessage({ id: `login.${router.query.message}` }) -// : undefined -// setMessage(message) -// }, [router.isReady, router.query]) - -// return ( -// -// {message && {message}} -//

Email Login

-// -//
-// -// -//
-//
-// -// -//
-// {errorMessage && {errorMessage}} -// -// -//
-// ) -// } diff --git a/packages/web/pages/email-reset-password.tsx b/packages/web/pages/email-reset-password.tsx deleted file mode 100644 index 88b0e4b8c..000000000 --- a/packages/web/pages/email-reset-password.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { PageMetaData } from '../components/patterns/PageMetaData' -import { ProfileLayout } from '../components/templates/ProfileLayout' -import { EmailResetPassword } from '../components/templates/EmailResetPassword' - -export default function EmailRegistrationPage(): JSX.Element { - return ( - <> - - - - -
- - ) -} diff --git a/packages/web/pages/email-signup.tsx b/packages/web/pages/email-signup.tsx deleted file mode 100644 index 5fa84d37b..000000000 --- a/packages/web/pages/email-signup.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { PageMetaData } from '../components/patterns/PageMetaData' -import { ProfileLayout } from '../components/templates/ProfileLayout' -import { EmailSignup } from '../components/templates/EmailSignup' - -export default function EmailRegistrationPage(): JSX.Element { - return ( - <> - - - - -
- - ) -} diff --git a/packages/web/pages/verify-email.tsx b/packages/web/pages/verify-email.tsx deleted file mode 100644 index c25d7dff4..000000000 --- a/packages/web/pages/verify-email.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { PageMetaData } from '../components/patterns/PageMetaData' -import { VerifyEmail } from '../components/templates/VerifyEmail' - -export default function VerifyEmailPage(): JSX.Element { - return ( - <> - - -
- - ) -} From dc268a2216f193ee5ae14bdd9fc5ccac561f4774 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 12:33:56 -0700 Subject: [PATCH 10/21] Handle resetting passwords --- packages/api/src/routers/auth/auth_router.ts | 18 ++-- .../templates/EmailForgotPassword.tsx | 92 +++++++++++++++++++ .../templates/EmailResetPassword.tsx | 61 +++++------- .../web/components/templates/ResetSent.tsx | 33 +++++++ .../web/components/templates/VerifyEmail.tsx | 24 +---- packages/web/locales/en/messages.ts | 1 + packages/web/pages/auth/forgot-password.tsx | 16 +--- packages/web/pages/auth/reset-password.tsx | 15 --- .../web/pages/auth/reset-password/[token].tsx | 15 +++ packages/web/pages/auth/reset-sent.tsx | 15 +++ 10 files changed, 194 insertions(+), 96 deletions(-) create mode 100644 packages/web/components/templates/EmailForgotPassword.tsx create mode 100644 packages/web/components/templates/ResetSent.tsx delete mode 100644 packages/web/pages/auth/reset-password.tsx create mode 100644 packages/web/pages/auth/reset-password/[token].tsx create mode 100644 packages/web/pages/auth/reset-sent.tsx diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index 9a618279a..e46bcde8a 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -557,13 +557,13 @@ export function authRouter() { }) if (!user) { return res.redirect( - `${env.client.url}/auth/forgot-password?errorCodes=USER_NOT_FOUND` + `${env.client.url}/auth/auth/reset-sent` ) } if (user.status === StatusType.Pending) { return res.redirect( - `${env.client.url}/auth/email-login?errorCodes=PENDING_VERIFICATION` + `${env.client.url}/auth/auth/reset-sent` ) } @@ -573,7 +573,7 @@ export function authRouter() { ) } - res.redirect(`${env.client.url}/auth/forgot-password?message=SUCCESS`) + res.redirect(`${env.client.url}/auth/reset-sent`) } catch (e) { logger.info('forgot-password exception:', e) @@ -598,20 +598,20 @@ export function authRouter() { const claims = await getClaimsByToken(token) if (!claims) { return res.redirect( - `${env.client.url}/auth/reset-password?errorCodes=INVALID_TOKEN` + `${env.client.url}/auth/reset-password/${token}?errorCodes=INVALID_TOKEN` ) } - if (!password) { + if (!password || password.length < 8) { return res.redirect( - `${env.client.url}/auth/reset-password?errorCodes=INVALID_PASSWORD` + `${env.client.url}/auth/reset-password/${token}?errorCodes=INVALID_PASSWORD` ) } const user = await getRepository(User).findOneBy({ id: claims.uid }) if (!user) { return res.redirect( - `${env.client.url}/auth/reset-password?errorCodes=USER_NOT_FOUND` + `${env.client.url}/auth/reset-password/${token}?errorCodes=USER_NOT_FOUND` ) } @@ -632,11 +632,11 @@ export function authRouter() { ) if (!updated.affected) { return res.redirect( - `${env.client.url}/auth/reset-password?errorCodes=UNKNOWN` + `${env.client.url}/auth/reset-password/${token}?errorCodes=UNKNOWN` ) } - res.redirect(`${env.client.url}/auth/reset-password?message=SUCCESS`) + await handleSuccessfulLogin(req, res, user, false) } catch (e) { logger.info('reset-password exception:', e) if (e instanceof jwt.TokenExpiredError) { diff --git a/packages/web/components/templates/EmailForgotPassword.tsx b/packages/web/components/templates/EmailForgotPassword.tsx new file mode 100644 index 000000000..b82ba9674 --- /dev/null +++ b/packages/web/components/templates/EmailForgotPassword.tsx @@ -0,0 +1,92 @@ +import { SpanBox, VStack } from '../elements/LayoutPrimitives' +import { Button } from '../elements/Button' +import { StyledText } from '../elements/StyledText' +import { useEffect, useState } from 'react' +import { FormInput } from '../elements/FormElements' +import { fetchEndpoint } from '../../lib/appConfig' +import { logoutMutation } from '../../lib/networking/mutations/logoutMutation' +import { styled } from '@stitches/react' +import { useRouter } from 'next/router' +import { formatMessage } from '../../locales/en/messages' +import { parseErrorCodes } from '../../lib/queryParamParser' + +const BorderedFormInput = styled(FormInput, { + height: '40px', + paddingLeft: '6px', + borderRadius: '6px', + background: 'white', + color: '$omnivoreGray', + border: `1px solid 1px solid rgba(0, 0, 0, 0.06)`, +}) + +const FormLabel = styled('label', { + fontSize: '16px', + color: '$omnivoreGray', +}) + +export function EmailForgotPassword(): JSX.Element { + const router = useRouter() + const [email, setEmail] = useState('') + const [errorMessage, setErrorMessage] = useState(undefined) + + useEffect(() => { + if (!router.isReady) return + const errorCode = parseErrorCodes(router.query) + const errorMsg = errorCode + ? formatMessage({ id: `error.${errorCode}` }) + : undefined + setErrorMessage(errorMsg) + }, [router.isReady, router.query]) + + return ( +
+ + Reset your password + + + Email + { e.preventDefault(); setEmail(e.target.value); }} + /> + + + + {errorMessage && ( + {errorMessage} + )} + + + +
+ ) +} diff --git a/packages/web/components/templates/EmailResetPassword.tsx b/packages/web/components/templates/EmailResetPassword.tsx index 4d4b256db..af9a69431 100644 --- a/packages/web/components/templates/EmailResetPassword.tsx +++ b/packages/web/components/templates/EmailResetPassword.tsx @@ -10,14 +10,14 @@ import { styled } from '@stitches/react' import { useRouter } from 'next/router' import { formatMessage } from '../../locales/en/messages' import { parseErrorCodes } from '../../lib/queryParamParser' - -const StyledTextSpan = styled('span', StyledText) +import { LoadingView } from '../patterns/LoadingView' const BorderedFormInput = styled(FormInput, { height: '40px', paddingLeft: '6px', borderRadius: '6px', background: 'white', + color: '$omnivoreGray', border: `1px solid 1px solid rgba(0, 0, 0, 0.06)`, }) @@ -28,7 +28,8 @@ const FormLabel = styled('label', { export function EmailResetPassword(): JSX.Element { const router = useRouter() - const [email, setEmail] = useState(undefined) + const [token, setToken] = useState(undefined) + const [password, setPassword] = useState('') const [errorMessage, setErrorMessage] = useState(undefined) useEffect(() => { @@ -37,24 +38,34 @@ export function EmailResetPassword(): JSX.Element { const errorMsg = errorCode ? formatMessage({ id: `error.${errorCode}` }) : undefined + + console.log('errorCode', errorCode, errorMsg) + setErrorMessage(errorMsg) + setToken(router.query.token as string) }, [router.isReady, router.query]) + if (!token) { + return + } + return ( -
+ - Reset your password - Email + Enter new password { e.preventDefault(); setEmail(e.target.value); }} + type="password" + key="password" + name="password" + value={password} + placeholder="Password" + onChange={(e) => { e.preventDefault(); setPassword(e.target.value); }} /> + (Password must be at least 8 chars) + + @@ -62,31 +73,7 @@ export function EmailResetPassword(): JSX.Element { {errorMessage} )} -
diff --git a/packages/web/components/templates/ResetSent.tsx b/packages/web/components/templates/ResetSent.tsx new file mode 100644 index 000000000..5fd4741ae --- /dev/null +++ b/packages/web/components/templates/ResetSent.tsx @@ -0,0 +1,33 @@ +import { Box, HStack } from '../elements/LayoutPrimitives' +import type { LoginFormProps } from './LoginForm' + +export function ResetSent(props: LoginFormProps): JSX.Element { + return ( + <> + + +

Reset email sent

+ + If there is an account assosciated with the email specified we sent a + password reset link. Click the link to reset your password. You may need + to check your spam folder. + +
+
+ + ) +} diff --git a/packages/web/components/templates/VerifyEmail.tsx b/packages/web/components/templates/VerifyEmail.tsx index 28a9103c5..f57a8f7d0 100644 --- a/packages/web/components/templates/VerifyEmail.tsx +++ b/packages/web/components/templates/VerifyEmail.tsx @@ -24,30 +24,12 @@ export function VerifyEmail(props: LoginFormProps): JSX.Element { }}>

Verify your email address

- We sent a verification link to the email you provided. Click the link to verify your email. You may need to check your spam folder. + We sent a verification link to the email you provided. + Click the link to verify your email. You may need to check + your spam folder. - - - - - ) } diff --git a/packages/web/locales/en/messages.ts b/packages/web/locales/en/messages.ts index 8a1d2f65f..306e427c1 100644 --- a/packages/web/locales/en/messages.ts +++ b/packages/web/locales/en/messages.ts @@ -25,6 +25,7 @@ const errorMessages: Record = { "Your sign up page has timed out, you'll be redirected to Google sign in page to authenticate again.", 'error.USER_EXISTS': 'User with this email exists already', 'error.UNKNOWN': 'An unknown error occurred', + 'error.INVALID_PASSWORD': 'Invalid password. Password must be at least 8 chars.' } const loginPageMessages: Record = { diff --git a/packages/web/pages/auth/forgot-password.tsx b/packages/web/pages/auth/forgot-password.tsx index d1ee0a922..4257a80f7 100644 --- a/packages/web/pages/auth/forgot-password.tsx +++ b/packages/web/pages/auth/forgot-password.tsx @@ -1,24 +1,12 @@ import { PageMetaData } from '../../components/patterns/PageMetaData' import { ProfileLayout } from '../../components/templates/ProfileLayout' -import { EmailResetPassword } from '../../components/templates/EmailResetPassword' +import { EmailForgotPassword } from '../../components/templates/EmailForgotPassword' import { useEffect } from 'react' import { useRouter } from 'next/router' import toast, { Toaster } from 'react-hot-toast' import { showSuccessToast } from '../../lib/toastHelpers' export default function ForgotPassword(): JSX.Element { - const router = useRouter() - - useEffect(() => { - if (router && router.isReady && router.query.message === 'SUCCESS') { - showSuccessToast('Reset password email sent') - setTimeout(() => { - window.location.href = '/email-login' - }, 2000) - } - }, [router]) - - return ( <> @@ -28,7 +16,7 @@ export default function ForgotPassword(): JSX.Element { }} /> - +
diff --git a/packages/web/pages/auth/reset-password.tsx b/packages/web/pages/auth/reset-password.tsx deleted file mode 100644 index f2dff55ba..000000000 --- a/packages/web/pages/auth/reset-password.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { PageMetaData } from '../../components/patterns/PageMetaData' -import { ProfileLayout } from '../../components/templates/ProfileLayout' -import { EmailResetPassword } from '../../components/templates/EmailResetPassword' - -export default function EmailRegistrationPage(): JSX.Element { - return ( - <> - - - - -
- - ) -} diff --git a/packages/web/pages/auth/reset-password/[token].tsx b/packages/web/pages/auth/reset-password/[token].tsx new file mode 100644 index 000000000..df307ee1e --- /dev/null +++ b/packages/web/pages/auth/reset-password/[token].tsx @@ -0,0 +1,15 @@ +import { PageMetaData } from '../../../components/patterns/PageMetaData' +import { ProfileLayout } from '../../../components/templates/ProfileLayout' +import { EmailResetPassword } from '../../../components/templates/EmailResetPassword' + +export default function EmailRegistrationPage(): JSX.Element { + return ( + <> + + + + +
+ + ) +} diff --git a/packages/web/pages/auth/reset-sent.tsx b/packages/web/pages/auth/reset-sent.tsx new file mode 100644 index 000000000..47c054d55 --- /dev/null +++ b/packages/web/pages/auth/reset-sent.tsx @@ -0,0 +1,15 @@ +import { PageMetaData } from '../../components/patterns/PageMetaData' +import { ProfileLayout } from '../../components/templates/ProfileLayout' +import { ResetSent } from '../../components/templates/ResetSent' + +export default function EmailResetSent(): JSX.Element { + return ( + <> + + + + +
+ + ) +} From 074ec781a2c5e70f32ffcb24e17fbaa32d719ce4 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 12:46:13 -0700 Subject: [PATCH 11/21] Set auth cookie after reseting password --- packages/api/src/routers/auth/auth_router.ts | 29 +++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index e46bcde8a..c4dd93dbd 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -339,9 +339,7 @@ export function authRouter() { url.resolve(env.client.url, decodeURIComponent(redirectUri)) ) } - return res.redirect( - `${env.client.url}/home` - ) + return res.redirect(`${env.client.url}/home`) } return res.redirect( @@ -423,7 +421,9 @@ export function authRouter() { await handleSuccessfulLogin(req, res, user, false) } catch (e) { logger.info('email-login exception:', e) - res.redirect(`${env.client.url}/auth/email-login?errorCodes=AUTH_FAILED`) + res.redirect( + `${env.client.url}/auth/email-login?errorCodes=AUTH_FAILED` + ) } } ) @@ -462,7 +462,9 @@ export function authRouter() { pendingConfirmation: true, }) - res.redirect(`${env.client.url}/auth/verify-email?message=SIGNUP_SUCCESS`) + res.redirect( + `${env.client.url}/auth/verify-email?message=SIGNUP_SUCCESS` + ) } catch (e) { logger.info('email-signup exception:', e) if (isErrorWithCode(e)) { @@ -530,7 +532,9 @@ export function authRouter() { ) } - res.redirect(`${env.client.url}/auth/confirm-email?errorCodes=INVALID_TOKEN`) + res.redirect( + `${env.client.url}/auth/confirm-email?errorCodes=INVALID_TOKEN` + ) } } ) @@ -556,15 +560,11 @@ export function authRouter() { email, }) if (!user) { - return res.redirect( - `${env.client.url}/auth/auth/reset-sent` - ) + return res.redirect(`${env.client.url}/auth/auth/reset-sent`) } if (user.status === StatusType.Pending) { - return res.redirect( - `${env.client.url}/auth/auth/reset-sent` - ) + return res.redirect(`${env.client.url}/auth/auth/reset-sent`) } if (!(await sendPasswordResetEmail(user))) { @@ -577,7 +577,9 @@ export function authRouter() { } catch (e) { logger.info('forgot-password exception:', e) - res.redirect(`${env.client.url}/auth/forgot-password?errorCodes=UNKNOWN`) + res.redirect( + `${env.client.url}/auth/forgot-password?errorCodes=UNKNOWN` + ) } } ) @@ -636,6 +638,7 @@ export function authRouter() { ) } + await setAuthInCookie({ uid: user.id }, res) await handleSuccessfulLogin(req, res, user, false) } catch (e) { logger.info('reset-password exception:', e) From 1666fbe4d5926c9248d257e6eb001a6f8c958a2a Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 14:21:33 -0700 Subject: [PATCH 12/21] Update auth components directory --- .../{ => auth}/EmailForgotPassword.tsx | 16 +++++++-------- .../templates/{ => auth}/EmailLogin.tsx | 18 ++++++++--------- .../{ => auth}/EmailResetPassword.tsx | 20 +++++++++---------- .../templates/{ => auth}/EmailSignup.tsx | 20 +++++++++---------- .../templates/{ => auth}/ResetSent.tsx | 4 ++-- .../templates/{ => auth}/VerifyEmail.tsx | 8 ++++---- packages/web/pages/auth/email-login.tsx | 2 +- packages/web/pages/auth/email-signup.tsx | 2 +- packages/web/pages/auth/forgot-password.tsx | 2 +- .../web/pages/auth/reset-password/[token].tsx | 2 +- packages/web/pages/auth/reset-sent.tsx | 2 +- packages/web/pages/auth/verify-email.tsx | 2 +- 12 files changed, 49 insertions(+), 49 deletions(-) rename packages/web/components/templates/{ => auth}/EmailForgotPassword.tsx (83%) rename packages/web/components/templates/{ => auth}/EmailLogin.tsx (87%) rename packages/web/components/templates/{ => auth}/EmailResetPassword.tsx (78%) rename packages/web/components/templates/{ => auth}/EmailSignup.tsx (89%) rename packages/web/components/templates/{ => auth}/ResetSent.tsx (86%) rename packages/web/components/templates/{ => auth}/VerifyEmail.tsx (78%) diff --git a/packages/web/components/templates/EmailForgotPassword.tsx b/packages/web/components/templates/auth/EmailForgotPassword.tsx similarity index 83% rename from packages/web/components/templates/EmailForgotPassword.tsx rename to packages/web/components/templates/auth/EmailForgotPassword.tsx index b82ba9674..b572ec2d4 100644 --- a/packages/web/components/templates/EmailForgotPassword.tsx +++ b/packages/web/components/templates/auth/EmailForgotPassword.tsx @@ -1,14 +1,14 @@ -import { SpanBox, VStack } from '../elements/LayoutPrimitives' -import { Button } from '../elements/Button' -import { StyledText } from '../elements/StyledText' +import { SpanBox, VStack } from '../../elements/LayoutPrimitives' +import { Button } from '../../elements/Button' +import { StyledText } from '../../elements/StyledText' import { useEffect, useState } from 'react' -import { FormInput } from '../elements/FormElements' -import { fetchEndpoint } from '../../lib/appConfig' -import { logoutMutation } from '../../lib/networking/mutations/logoutMutation' +import { FormInput } from '../../elements/FormElements' +import { fetchEndpoint } from '../../../lib/appConfig' +import { logoutMutation } from '../../../lib/networking/mutations/logoutMutation' import { styled } from '@stitches/react' import { useRouter } from 'next/router' -import { formatMessage } from '../../locales/en/messages' -import { parseErrorCodes } from '../../lib/queryParamParser' +import { formatMessage } from '../../../locales/en/messages' +import { parseErrorCodes } from '../../../lib/queryParamParser' const BorderedFormInput = styled(FormInput, { height: '40px', diff --git a/packages/web/components/templates/EmailLogin.tsx b/packages/web/components/templates/auth/EmailLogin.tsx similarity index 87% rename from packages/web/components/templates/EmailLogin.tsx rename to packages/web/components/templates/auth/EmailLogin.tsx index 4c2f0248f..cca01788c 100644 --- a/packages/web/components/templates/EmailLogin.tsx +++ b/packages/web/components/templates/auth/EmailLogin.tsx @@ -1,15 +1,15 @@ -import { HStack, SpanBox, VStack } from '../elements/LayoutPrimitives' -import { Button } from '../elements/Button' -import { StyledText } from '../elements/StyledText' +import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' +import { Button } from '../../elements/Button' +import { StyledText } from '../../elements/StyledText' import { useEffect, useState } from 'react' -import { FormInput } from '../elements/FormElements' -import { TermAndConditionsFooter } from './LoginForm' -import { fetchEndpoint } from '../../lib/appConfig' -import { logoutMutation } from '../../lib/networking/mutations/logoutMutation' +import { FormInput } from '../../elements/FormElements' +import { TermAndConditionsFooter } from '../LoginForm' +import { fetchEndpoint } from '../../../lib/appConfig' +import { logoutMutation } from '../../../lib/networking/mutations/logoutMutation' import { styled } from '@stitches/react' import { useRouter } from 'next/router' -import { parseErrorCodes } from '../../lib/queryParamParser' -import { formatMessage } from '../../locales/en/messages' +import { parseErrorCodes } from '../../../lib/queryParamParser' +import { formatMessage } from '../../../locales/en/messages' import Link from 'next/link' const StyledTextSpan = styled('span', StyledText) diff --git a/packages/web/components/templates/EmailResetPassword.tsx b/packages/web/components/templates/auth/EmailResetPassword.tsx similarity index 78% rename from packages/web/components/templates/EmailResetPassword.tsx rename to packages/web/components/templates/auth/EmailResetPassword.tsx index af9a69431..43b8e65d8 100644 --- a/packages/web/components/templates/EmailResetPassword.tsx +++ b/packages/web/components/templates/auth/EmailResetPassword.tsx @@ -1,16 +1,16 @@ -import { SpanBox, VStack } from '../elements/LayoutPrimitives' -import { Button } from '../elements/Button' -import { StyledText } from '../elements/StyledText' +import { SpanBox, VStack } from '../../elements/LayoutPrimitives' +import { Button } from '../../elements/Button' +import { StyledText } from '../../elements/StyledText' import { useEffect, useState } from 'react' -import { FormInput } from '../elements/FormElements' -import { TermAndConditionsFooter } from './LoginForm' -import { fetchEndpoint } from '../../lib/appConfig' -import { logoutMutation } from '../../lib/networking/mutations/logoutMutation' +import { FormInput } from '../../elements/FormElements' +import { TermAndConditionsFooter } from '../LoginForm' +import { fetchEndpoint } from '../../../lib/appConfig' +import { logoutMutation } from '../../../lib/networking/mutations/logoutMutation' import { styled } from '@stitches/react' import { useRouter } from 'next/router' -import { formatMessage } from '../../locales/en/messages' -import { parseErrorCodes } from '../../lib/queryParamParser' -import { LoadingView } from '../patterns/LoadingView' +import { formatMessage } from '../../../locales/en/messages' +import { parseErrorCodes } from '../../../lib/queryParamParser' +import { LoadingView } from '../../patterns/LoadingView' const BorderedFormInput = styled(FormInput, { height: '40px', diff --git a/packages/web/components/templates/EmailSignup.tsx b/packages/web/components/templates/auth/EmailSignup.tsx similarity index 89% rename from packages/web/components/templates/EmailSignup.tsx rename to packages/web/components/templates/auth/EmailSignup.tsx index 113e16f05..dc4948100 100644 --- a/packages/web/components/templates/EmailSignup.tsx +++ b/packages/web/components/templates/auth/EmailSignup.tsx @@ -1,16 +1,16 @@ -import { HStack, SpanBox, VStack } from '../elements/LayoutPrimitives' -import { Button } from '../elements/Button' -import { StyledText } from '../elements/StyledText' +import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' +import { Button } from '../../elements/Button' +import { StyledText } from '../../elements/StyledText' import { useCallback, useEffect, useMemo, useState } from 'react' -import { FormInput } from '../elements/FormElements' -import { TermAndConditionsFooter } from './LoginForm' -import { fetchEndpoint } from '../../lib/appConfig' -import { useValidateUsernameQuery } from '../../lib/networking/queries/useValidateUsernameQuery' -import { logoutMutation } from '../../lib/networking/mutations/logoutMutation' +import { FormInput } from '../../elements/FormElements' +import { TermAndConditionsFooter } from '../LoginForm' +import { fetchEndpoint } from '../../../lib/appConfig' +import { useValidateUsernameQuery } from '../../../lib/networking/queries/useValidateUsernameQuery' +import { logoutMutation } from '../../../lib/networking/mutations/logoutMutation' import { styled } from '@stitches/react' import { useRouter } from 'next/router' -import { formatMessage } from '../../locales/en/messages' -import { parseErrorCodes } from '../../lib/queryParamParser' +import { formatMessage } from '../../../locales/en/messages' +import { parseErrorCodes } from '../../../lib/queryParamParser' import Link from 'next/link' const StyledTextSpan = styled('span', StyledText) diff --git a/packages/web/components/templates/ResetSent.tsx b/packages/web/components/templates/auth/ResetSent.tsx similarity index 86% rename from packages/web/components/templates/ResetSent.tsx rename to packages/web/components/templates/auth/ResetSent.tsx index 5fd4741ae..6c64dc3af 100644 --- a/packages/web/components/templates/ResetSent.tsx +++ b/packages/web/components/templates/auth/ResetSent.tsx @@ -1,5 +1,5 @@ -import { Box, HStack } from '../elements/LayoutPrimitives' -import type { LoginFormProps } from './LoginForm' +import { Box, HStack } from '../../elements/LayoutPrimitives' +import type { LoginFormProps } from '../LoginForm' export function ResetSent(props: LoginFormProps): JSX.Element { return ( diff --git a/packages/web/components/templates/VerifyEmail.tsx b/packages/web/components/templates/auth/VerifyEmail.tsx similarity index 78% rename from packages/web/components/templates/VerifyEmail.tsx rename to packages/web/components/templates/auth/VerifyEmail.tsx index f57a8f7d0..b7f0279f5 100644 --- a/packages/web/components/templates/VerifyEmail.tsx +++ b/packages/web/components/templates/auth/VerifyEmail.tsx @@ -1,7 +1,7 @@ -import { Box, HStack, MediumBreakpointBox, SpanBox, VStack } from '../elements/LayoutPrimitives' -import type { LoginFormProps } from './LoginForm' -import { OmnivoreNameLogo } from '../elements/images/OmnivoreNameLogo' -import { theme } from '../tokens/stitches.config' +import { Box, HStack, MediumBreakpointBox, SpanBox, VStack } from '../../elements/LayoutPrimitives' +import type { LoginFormProps } from '../LoginForm' +import { OmnivoreNameLogo } from '../../elements/images/OmnivoreNameLogo' +import { theme } from '../../tokens/stitches.config' export function VerifyEmail(props: LoginFormProps): JSX.Element { return ( diff --git a/packages/web/pages/auth/email-login.tsx b/packages/web/pages/auth/email-login.tsx index aadf9913f..ae36ba62b 100644 --- a/packages/web/pages/auth/email-login.tsx +++ b/packages/web/pages/auth/email-login.tsx @@ -1,6 +1,6 @@ import { PageMetaData } from '../../components/patterns/PageMetaData' import { ProfileLayout } from '../../components/templates/ProfileLayout' -import { EmailLogin } from '../../components/templates/EmailLogin' +import { EmailLogin } from '../../components/templates/auth/EmailLogin' export default function EmailLoginPage(): JSX.Element { return ( diff --git a/packages/web/pages/auth/email-signup.tsx b/packages/web/pages/auth/email-signup.tsx index 819fe0b16..7c2d24d8d 100644 --- a/packages/web/pages/auth/email-signup.tsx +++ b/packages/web/pages/auth/email-signup.tsx @@ -1,6 +1,6 @@ import { PageMetaData } from '../../components/patterns/PageMetaData' import { ProfileLayout } from '../../components/templates/ProfileLayout' -import { EmailSignup } from '../../components/templates/EmailSignup' +import { EmailSignup } from '../../components/templates/auth/EmailSignup' export default function EmailRegistrationPage(): JSX.Element { return ( diff --git a/packages/web/pages/auth/forgot-password.tsx b/packages/web/pages/auth/forgot-password.tsx index 4257a80f7..05e0c26da 100644 --- a/packages/web/pages/auth/forgot-password.tsx +++ b/packages/web/pages/auth/forgot-password.tsx @@ -1,6 +1,6 @@ import { PageMetaData } from '../../components/patterns/PageMetaData' import { ProfileLayout } from '../../components/templates/ProfileLayout' -import { EmailForgotPassword } from '../../components/templates/EmailForgotPassword' +import { EmailForgotPassword } from '../../components/templates/auth/EmailForgotPassword' import { useEffect } from 'react' import { useRouter } from 'next/router' import toast, { Toaster } from 'react-hot-toast' diff --git a/packages/web/pages/auth/reset-password/[token].tsx b/packages/web/pages/auth/reset-password/[token].tsx index df307ee1e..f43e772e6 100644 --- a/packages/web/pages/auth/reset-password/[token].tsx +++ b/packages/web/pages/auth/reset-password/[token].tsx @@ -1,6 +1,6 @@ import { PageMetaData } from '../../../components/patterns/PageMetaData' import { ProfileLayout } from '../../../components/templates/ProfileLayout' -import { EmailResetPassword } from '../../../components/templates/EmailResetPassword' +import { EmailResetPassword } from '../../../components/templates/auth/EmailResetPassword' export default function EmailRegistrationPage(): JSX.Element { return ( diff --git a/packages/web/pages/auth/reset-sent.tsx b/packages/web/pages/auth/reset-sent.tsx index 47c054d55..3800fc357 100644 --- a/packages/web/pages/auth/reset-sent.tsx +++ b/packages/web/pages/auth/reset-sent.tsx @@ -1,6 +1,6 @@ import { PageMetaData } from '../../components/patterns/PageMetaData' import { ProfileLayout } from '../../components/templates/ProfileLayout' -import { ResetSent } from '../../components/templates/ResetSent' +import { ResetSent } from '../../components/templates/auth/ResetSent' export default function EmailResetSent(): JSX.Element { return ( diff --git a/packages/web/pages/auth/verify-email.tsx b/packages/web/pages/auth/verify-email.tsx index 0606e88df..461f62bba 100644 --- a/packages/web/pages/auth/verify-email.tsx +++ b/packages/web/pages/auth/verify-email.tsx @@ -1,5 +1,5 @@ import { PageMetaData } from '../../components/patterns/PageMetaData' -import { VerifyEmail } from '../../components/templates/VerifyEmail' +import { VerifyEmail } from '../../components/templates/auth/VerifyEmail' export default function VerifyEmailPage(): JSX.Element { return ( From e939f12eb197dd47134873654db3019b206da77f Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 14:28:39 -0700 Subject: [PATCH 13/21] Remove unneeded branches --- packages/api/src/routers/auth/auth_router.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index c4dd93dbd..b581aed89 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -347,15 +347,7 @@ export function authRouter() { ) } - if (env.server.apiEnv && !env.dev.isLocal && IntercomClient) { - if (newUser) { - redirect(res) - } else { - redirect(res) - } - } else { - redirect(res) - } + redirect(res) } catch (error) { logger.info('handleSuccessfulLogin exception:', error) return res.redirect(`${env.client.url}/login?errorCodes=AUTH_FAILED`) From 769c9b23b06bcfd9ca3cc67b361de30ef349d3fa Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 14:48:24 -0700 Subject: [PATCH 14/21] Use SSO redirection to set both client and server cookies with email login --- packages/api/src/routers/auth/auth_router.ts | 77 ++++++++++---------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index b581aed89..33d0146ef 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -52,6 +52,8 @@ import { sendConfirmationEmail, sendPasswordResetEmail, } from '../../services/send_emails' +import { createWebAuthToken } from './jwt_helpers' +import { createSsoToken, ssoRedirectURL } from '../../utils/sso' const logger = buildLogger('app.dispatch') const signToken = promisify(jwt.sign) @@ -311,43 +313,48 @@ export function authRouter() { newUser: boolean ): Promise { try { - const redirect = (res: express.Response): void => { - let redirectUri: string | null = null - if (req.query.state) { - // Google login case: redirect_uri is in query state param. - try { - const state = JSON.parse((req.query?.state || '') as string) - redirectUri = state?.redirect_uri - } catch (err) { - console.warn( - 'handleSuccessfulLogin: failed to parse redirect query state param', - err - ) - } - } - - const message = res.get('Message') - if (message) { - return res.redirect( - `${env.client.url}/home?message=${encodeURIComponent(message)}` + let redirectUri: string | null = null + if (req.query.state) { + // Google login case: redirect_uri is in query state param. + try { + const state = JSON.parse((req.query?.state || '') as string) + redirectUri = state?.redirect_uri + } catch (err) { + console.warn( + 'handleSuccessfulLogin: failed to parse redirect query state param', + err ) } - - if (newUser) { - if (redirectUri && redirectUri !== '/') { - return res.redirect( - url.resolve(env.client.url, decodeURIComponent(redirectUri)) - ) - } - return res.redirect(`${env.client.url}/home`) - } - - return res.redirect( - url.resolve(env.client.url, decodeURIComponent(redirectUri || 'home')) - ) } - redirect(res) + if (newUser) { + if (redirectUri && redirectUri !== '/') { + redirectUri = url.resolve(env.client.url, decodeURIComponent(redirectUri)) + } else { + redirectUri = `${env.client.url}/home` + } + } + + redirectUri = redirectUri ? redirectUri : `${env.client.url}/home` + + const message = res.get('Message') + if (message) { + const u = new URL(redirectUri) + u.searchParams.append('message', message); + redirectUri = u.toString() + } + + // If we do have an auth token, we want to try redirecting to the + // sso endpoint which will set a cookie for the client domain (omnivore.app) + // after we set a cookie for the API domain (api-prod.omnivore.app) + const authToken = await createWebAuthToken(user.id) + if (authToken) { + const ssoToken = createSsoToken(authToken, redirectUri) + redirectUri = ssoRedirectURL(ssoToken) + + } + + return res.redirect(redirectUri) } catch (error) { logger.info('handleSuccessfulLogin exception:', error) return res.redirect(`${env.client.url}/login?errorCodes=AUTH_FAILED`) @@ -408,8 +415,6 @@ export function authRouter() { ) } - // set auth cookie in response header - await setAuthInCookie({ uid: user.id }, res) await handleSuccessfulLogin(req, res, user, false) } catch (e) { logger.info('email-login exception:', e) @@ -514,7 +519,6 @@ export function authRouter() { } res.set('Message', 'EMAIL_CONFIRMED') - await setAuthInCookie({ uid: user.id }, res) await handleSuccessfulLogin(req, res, user, false) } catch (e) { logger.info('confirm-email exception:', e) @@ -630,7 +634,6 @@ export function authRouter() { ) } - await setAuthInCookie({ uid: user.id }, res) await handleSuccessfulLogin(req, res, user, false) } catch (e) { logger.info('reset-password exception:', e) From 6cad41ec1ece3c3006d171e23c6f599bc810b299 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 15:00:31 -0700 Subject: [PATCH 15/21] Linting fixes --- packages/api/src/routers/auth/auth_router.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index 33d0146ef..392f4ca38 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -329,7 +329,10 @@ export function authRouter() { if (newUser) { if (redirectUri && redirectUri !== '/') { - redirectUri = url.resolve(env.client.url, decodeURIComponent(redirectUri)) + redirectUri = url.resolve( + env.client.url, + decodeURIComponent(redirectUri) + ) } else { redirectUri = `${env.client.url}/home` } @@ -340,7 +343,7 @@ export function authRouter() { const message = res.get('Message') if (message) { const u = new URL(redirectUri) - u.searchParams.append('message', message); + u.searchParams.append('message', message) redirectUri = u.toString() } @@ -351,7 +354,6 @@ export function authRouter() { if (authToken) { const ssoToken = createSsoToken(authToken, redirectUri) redirectUri = ssoRedirectURL(ssoToken) - } return res.redirect(redirectUri) From 8b072ab094e8c672fdc35868ff617562e4ba9c1e Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 15:17:55 -0700 Subject: [PATCH 16/21] Update expected URLs for tests - after email is sent the user is redirected to an information page - after login the user is redirected through the sso page --- packages/api/test/routers/auth.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/api/test/routers/auth.test.ts b/packages/api/test/routers/auth.test.ts index fa14521be..55759d686 100644 --- a/packages/api/test/routers/auth.test.ts +++ b/packages/api/test/routers/auth.test.ts @@ -61,7 +61,7 @@ describe('auth router', () => { sinon.restore() }) - it('redirects to login page', async () => { + it('redirects to verify email', async () => { const res = await signupRequest( email, password, @@ -69,7 +69,7 @@ describe('auth router', () => { username ).expect(302) expect(res.header.location).to.endWith( - '/email-login?message=SIGNUP_SUCCESS' + '/verify-email?message=SIGNUP_SUCCESS' ) }) @@ -173,9 +173,9 @@ describe('auth router', () => { password = correctPassword }) - it('redirects to home page', async () => { + it('redirects to sso page', async () => { const res = await loginRequest(email, password).expect(302) - expect(res.header.location).to.endWith('/home') + expect(res.header.location).to.contain('/api/client/auth?tok') }) it('set auth token in cookie', async () => { From 3c442c06bac1af4181b66d551d2b9ba6e7733fbd Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 15:56:40 -0700 Subject: [PATCH 17/21] Set auth token cookie on success login --- packages/api/src/routers/auth/auth_router.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index 392f4ca38..8411f9431 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -19,7 +19,6 @@ import express from 'express' import axios from 'axios' import { env } from '../../env' import url from 'url' -import { IntercomClient } from '../../utils/intercom' import { kx } from '../../datalayer/knex_config' import UserModel from '../../datalayer/user' import { buildLogger } from '../../utils/logger' @@ -356,6 +355,8 @@ export function authRouter() { redirectUri = ssoRedirectURL(ssoToken) } + await setAuthInCookie({ uid: user.id }, res) + return res.redirect(redirectUri) } catch (error) { logger.info('handleSuccessfulLogin exception:', error) From 65689ca538d5c7bf99aed6fd6f1f9fbfb3930f83 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 15:57:18 -0700 Subject: [PATCH 18/21] Remove extra /auth in paths --- packages/api/src/routers/auth/auth_router.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index 8411f9431..ba7b11a4d 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -559,11 +559,11 @@ export function authRouter() { email, }) if (!user) { - return res.redirect(`${env.client.url}/auth/auth/reset-sent`) + return res.redirect(`${env.client.url}/auth/reset-sent`) } if (user.status === StatusType.Pending) { - return res.redirect(`${env.client.url}/auth/auth/reset-sent`) + return res.redirect(`${env.client.url}/auth/reset-sent`) } if (!(await sendPasswordResetEmail(user))) { From 779ff1f02023f8705cc66d3b8e5d19cdbabeec0c Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 15:57:40 -0700 Subject: [PATCH 19/21] Reset password is a directory now --- packages/api/src/routers/auth/auth_router.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index ba7b11a4d..85321680f 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -642,12 +642,12 @@ export function authRouter() { logger.info('reset-password exception:', e) if (e instanceof jwt.TokenExpiredError) { return res.redirect( - `${env.client.url}/auth/reset-password?errorCodes=TOKEN_EXPIRED` + `${env.client.url}/auth/reset-password/?errorCodes=TOKEN_EXPIRED` ) } res.redirect( - `${env.client.url}/auth/reset-password?errorCodes=INVALID_TOKEN` + `${env.client.url}/auth/reset-password/?errorCodes=INVALID_TOKEN` ) } } From c80270ae81371bec0ee26c815ae319ff4f41a629 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 15:58:38 -0700 Subject: [PATCH 20/21] Update expected redirect URLs in tests --- packages/api/test/routers/auth.test.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/api/test/routers/auth.test.ts b/packages/api/test/routers/auth.test.ts index 55759d686..721f19c55 100644 --- a/packages/api/test/routers/auth.test.ts +++ b/packages/api/test/routers/auth.test.ts @@ -297,9 +297,9 @@ describe('auth router', () => { expect(res.header['set-cookie'][0]).to.contain('auth') }) - it('redirects to home page', async () => { + it('redirects to sso page', async () => { const res = await confirmEmailRequest(token).expect(302) - expect(res.header.location).to.endWith('/home?message=EMAIL_CONFIRMED') + expect(res.header.location).to.contain('/api/client/auth?tok') }) it('sets user as active', async () => { @@ -395,7 +395,7 @@ describe('auth router', () => { it('redirects to forgot-password page with success message', async () => { const res = await emailResetPasswordReq(email).expect(302) expect(res.header.location).to.endWith( - '/forgot-password?message=SUCCESS' + '/auth/reset-sent' ) }) }) @@ -432,7 +432,7 @@ describe('auth router', () => { it('redirects to email-login page with error code PENDING_VERIFICATION', async () => { const res = await emailResetPasswordReq(email).expect(302) expect(res.header.location).to.endWith( - '/email-login?errorCodes=PENDING_VERIFICATION' + '/auth/reset-sent' ) }) }) @@ -446,7 +446,7 @@ describe('auth router', () => { it('redirects to forgot-password page with error code USER_NOT_FOUND', async () => { const res = await emailResetPasswordReq(email).expect(302) expect(res.header.location).to.endWith( - '/forgot-password?errorCodes=USER_NOT_FOUND' + '/auth/reset-sent' ) }) }) @@ -498,8 +498,8 @@ describe('auth router', () => { const res = await resetPasswordRequest(token, 'new_password').expect( 302 ) - expect(res.header.location).to.endWith( - '/auth/reset-password?message=SUCCESS' + expect(res.header.location).to.contain( + '/api/client/auth?tok' ) }) @@ -517,8 +517,8 @@ describe('auth router', () => { context('when password is empty', () => { it('redirects to reset-password page with error code INVALID_PASSWORD', async () => { const res = await resetPasswordRequest(token, '').expect(302) - expect(res.header.location).to.endWith( - '/auth/reset-password?errorCodes=INVALID_PASSWORD' + expect(res.header.location).to.match( + /.*\/auth\/reset-password\/(.*)?\?errorCodes=INVALID_PASSWORD/g ) }) }) @@ -530,8 +530,8 @@ describe('auth router', () => { 'invalid_token', 'new_password' ).expect(302) - expect(res.header.location).to.endWith( - '/auth/reset-password?errorCodes=INVALID_TOKEN' + expect(res.header.location).to.match( + /.*\/auth\/reset-password\/(.*)?\?errorCodes=INVALID_TOKEN/g ) }) @@ -545,7 +545,7 @@ describe('auth router', () => { 302 ) expect(res.header.location).to.endWith( - '/auth/reset-password?errorCodes=TOKEN_EXPIRED' + '/auth/reset-password/?errorCodes=TOKEN_EXPIRED' ) }) }) From b7190647ca3d59e539be2f218879f30ffd515047 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 26 Jul 2022 17:23:05 -0700 Subject: [PATCH 21/21] Consistent UI on the confirm email page --- .../web/pages/auth/confirm-email/[token].tsx | 47 ++++++++----------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/packages/web/pages/auth/confirm-email/[token].tsx b/packages/web/pages/auth/confirm-email/[token].tsx index 8a44f40b7..892fb7c39 100644 --- a/packages/web/pages/auth/confirm-email/[token].tsx +++ b/packages/web/pages/auth/confirm-email/[token].tsx @@ -1,21 +1,15 @@ import { useEffect, useRef, useState } from 'react' import { useRouter } from 'next/router' -import { Toaster } from 'react-hot-toast' -import { applyStoredTheme } from '../../../lib/themeUpdater' - -import { PrimaryLayout } from '../../../components/templates/PrimaryLayout' - -import { HStack, SpanBox } from '../../../components/elements/LayoutPrimitives' +import { HStack } from '../../../components/elements/LayoutPrimitives' import { fetchEndpoint } from '../../../lib/appConfig' import { LoadingView } from '../../../components/patterns/LoadingView' +import { PageMetaData } from '../../../components/patterns/PageMetaData' +import { ProfileLayout } from '../../../components/templates/ProfileLayout' export default function ConfirmEmail(): JSX.Element { const authForm = useRef(null) const router = useRouter() - const [errorMessage, setErrorMessage] = useState(undefined) - - applyStoredTheme(false) useEffect(() => { if (!router || !router.isReady || !authForm.current) { @@ -26,24 +20,21 @@ export default function ConfirmEmail(): JSX.Element { }, [router, authForm]) return ( - - -
- -
- - {errorMessage ? ( - {errorMessage} - ) : } - -
+ <> + + +
+ +
+ + + +
+
+ ) }