diff --git a/packages/api/.nycrc b/packages/api/.nycrc index da90d3922..4a0a0dfb4 100644 --- a/packages/api/.nycrc +++ b/packages/api/.nycrc @@ -8,7 +8,7 @@ "reporter": [ "text-summary" ], - "branches": 0, + "branches": 40, "lines": 0, "functions": 0, "statements": 60 diff --git a/packages/api/src/routers/auth/auth_router.ts b/packages/api/src/routers/auth/auth_router.ts index 32fe75122..c6e7ce3d3 100644 --- a/packages/api/src/routers/auth/auth_router.ts +++ b/packages/api/src/routers/auth/auth_router.ts @@ -509,7 +509,7 @@ export function authRouter() { ) } - res.set('Message', 'CONFIRMATION_SUCCESS') + res.set('Message', 'EMAIL_CONFIRMED') await setAuthInCookie({ uid: user.id }, res) await handleSuccessfulLogin(req, res, user, false) } catch (e) { @@ -582,11 +582,6 @@ export function authRouter() { cors(corsConfig), async (req: express.Request, res: express.Response) => { const { token, password } = req.body - if (!token || !password) { - return res.redirect( - `${env.client.url}/reset-password?errorCodes=INVALID_CREDENTIALS` - ) - } try { // verify token @@ -597,6 +592,12 @@ export function authRouter() { ) } + if (!password) { + return res.redirect( + `${env.client.url}/reset-password?errorCodes=INVALID_PASSWORD` + ) + } + const user = await getRepository(User).findOneBy({ id: claims.uid }) if (!user) { return res.redirect( @@ -611,14 +612,17 @@ export function authRouter() { } const hashedPassword = await hashPassword(password) - await getRepository(User).update( + const updated = await getRepository(User).update( { id: user.id }, { password: hashedPassword } ) + if (!updated.affected) { + return res.redirect( + `${env.client.url}/reset-password?errorCodes=UNKNOWN` + ) + } - res.set('Message', 'PASSWORD_RESET_SUCCESS') - await setAuthInCookie({ uid: user.id }, res) - await handleSuccessfulLogin(req, res, user, false) + res.redirect(`${env.client.url}/reset-password?message=SUCCESS`) } catch (e) { logger.info('reset-password exception:', e) if (e instanceof jwt.TokenExpiredError) { diff --git a/packages/api/test/routers/auth.test.ts b/packages/api/test/routers/auth.test.ts index 54fd2a59f..106eb2f68 100644 --- a/packages/api/test/routers/auth.test.ts +++ b/packages/api/test/routers/auth.test.ts @@ -8,7 +8,11 @@ import { MailDataRequired } from '@sendgrid/helpers/classes/mail' import sinon from 'sinon' import * as util from '../../src/utils/sendEmail' import supertest from 'supertest' -import { generateVerificationToken, hashPassword } from '../../src/utils/auth' +import { + comparePassword, + generateVerificationToken, + hashPassword, +} from '../../src/utils/auth' describe('auth router', () => { const route = '/api/auth' @@ -284,9 +288,15 @@ describe('auth router', () => { token = generateVerificationToken(user.id) }) - it('logs in and redirects to home page', async () => { + it('set auth token in cookie', async () => { const res = await confirmEmailRequest(token).expect(302) - expect(res.header.location).to.endWith('/home') + expect(res.header['set-cookie']).to.be.an('array') + expect(res.header['set-cookie'][0]).to.contain('auth') + }) + + it('redirects to home page', async () => { + const res = await confirmEmailRequest(token).expect(302) + expect(res.header.location).to.endWith('/home?message=EMAIL_CONFIRMED') }) it('sets user as active', async () => { @@ -452,4 +462,90 @@ describe('auth router', () => { }) }) }) + + describe('reset-password', () => { + const resetPasswordRequest = ( + token: string, + password: string + ): supertest.Test => { + return request.post(`${route}/reset-password`).send({ + token, + password, + }) + } + + let user: User + let token: string + + before(async () => { + user = await createTestUser('test_user', undefined, 'test_password') + }) + + after(async () => { + await deleteTestUser(user.name) + }) + + context('when token is valid', () => { + before(async () => { + token = generateVerificationToken(user.id) + }) + + context('when password is not empty', () => { + it('redirects to reset-password page with success message', async () => { + const res = await resetPasswordRequest(token, 'new_password').expect( + 302 + ) + expect(res.header.location).to.endWith( + '/reset-password?message=SUCCESS' + ) + }) + + it('resets password', async () => { + const password = 'test_reset_password' + await resetPasswordRequest(token, password).expect(302) + const updatedUser = await getRepository(User).findOneBy({ + id: user?.id, + }) + expect(await comparePassword(password, updatedUser?.password!)).to.be + .true + }) + }) + + 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( + '/reset-password?errorCodes=INVALID_PASSWORD' + ) + }) + }) + }) + + context('when token is invalid', () => { + it('redirects to reset-password page with error code InvalidToken', async () => { + const res = await resetPasswordRequest( + 'invalid_token', + 'new_password' + ).expect(302) + expect(res.header.location).to.endWith( + '/reset-password?errorCodes=INVALID_TOKEN' + ) + }) + + context('when token is expired', () => { + before(() => { + token = generateVerificationToken(user.id, -1) + }) + + it('redirects to reset-password page with error code ExpiredToken', async () => { + const res = await resetPasswordRequest(token, 'new_password').expect( + 302 + ) + expect(res.header.location).to.endWith( + '/reset-password?errorCodes=TOKEN_EXPIRED' + ) + }) + }) + }) + }) })