add router handler for email reset password
This commit is contained in:
committed by
Jackson Harper
parent
53fd7f52df
commit
286d167769
@ -42,7 +42,11 @@ import {
|
||||
getClaimsByToken,
|
||||
hashPassword,
|
||||
} from '../../utils/auth'
|
||||
import { createUser, sendConfirmationEmail } from '../../services/create_user'
|
||||
import {
|
||||
createUser,
|
||||
sendConfirmationEmail,
|
||||
sendPasswordResetEmail,
|
||||
} from '../../services/create_user'
|
||||
import { isErrorWithCode } from '../../resolvers'
|
||||
import { initModels } from '../../server'
|
||||
import { getRepository } from '../../entity/utils'
|
||||
@ -302,7 +306,7 @@ export function authRouter() {
|
||||
async function handleSuccessfulLogin(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
user: UserData,
|
||||
user: UserData | User,
|
||||
newUser: boolean
|
||||
): Promise<void> {
|
||||
try {
|
||||
@ -363,6 +367,12 @@ export function authRouter() {
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const { email, password } = req.body
|
||||
|
||||
if (!email || !password) {
|
||||
return res.redirect(
|
||||
`${env.client.url}/email-login?errorCodes=${LoginErrorCode.InvalidCredentials}`
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const models = initModels(kx, false)
|
||||
const user = await models.user.getWhere({
|
||||
@ -426,6 +436,12 @@ export function authRouter() {
|
||||
cors<express.Request>(corsConfig),
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const { email, password, name, username, bio, pictureUrl } = req.body
|
||||
|
||||
if (!email || !password || !name || !username) {
|
||||
return res.redirect(
|
||||
`${env.client.url}/email-signup?errorCodes=INVALID_CREDENTIALS`
|
||||
)
|
||||
}
|
||||
const lowerCasedUsername = username.toLowerCase()
|
||||
|
||||
try {
|
||||
@ -491,7 +507,7 @@ export function authRouter() {
|
||||
)
|
||||
}
|
||||
|
||||
res.redirect(`${env.client.url}/email-login?message=EMAIL_VERIFIED`)
|
||||
await handleSuccessfulLogin(req, res, user, false)
|
||||
} catch (e) {
|
||||
logger.info('confirm-email exception:', e)
|
||||
if (e instanceof jwt.TokenExpiredError) {
|
||||
@ -505,5 +521,54 @@ export function authRouter() {
|
||||
}
|
||||
)
|
||||
|
||||
router.options(
|
||||
'/email-reset-password',
|
||||
cors<express.Request>({ ...corsConfig, maxAge: 600 })
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/email-reset-password',
|
||||
cors<express.Request>(corsConfig),
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const email = req.body.email
|
||||
if (!email) {
|
||||
return res.redirect(
|
||||
`${env.client.url}/email-reset-password?errorCodes=INVALID_EMAIL`
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await getRepository(User).findOneBy({
|
||||
email,
|
||||
})
|
||||
if (!user) {
|
||||
return res.redirect(
|
||||
`${env.client.url}/email-reset-password?errorCodes=USER_NOT_FOUND`
|
||||
)
|
||||
}
|
||||
|
||||
if (user.status === StatusType.Pending) {
|
||||
return res.redirect(
|
||||
`${env.client.url}/email-login?errorCodes=PENDING_VERIFICATION`
|
||||
)
|
||||
}
|
||||
|
||||
if (!(await sendPasswordResetEmail(user))) {
|
||||
return res.redirect(
|
||||
`${env.client.url}/email-reset-password?errorCodes=INVALID_EMAIL`
|
||||
)
|
||||
}
|
||||
|
||||
res.redirect(`${env.client.url}/email-reset-password?message=SUCCESS`)
|
||||
} catch (e) {
|
||||
logger.info('email-reset-password exception:', e)
|
||||
|
||||
res.redirect(
|
||||
`${env.client.url}/email-reset-password?errorCodes=UNKNOWN`
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
@ -143,3 +143,20 @@ export const sendConfirmationEmail = async (user: {
|
||||
text: `Hey ${user.name},\n\nPlease confirm your email by clicking the link below:\n\n${confirmationLink}\n\n`,
|
||||
})
|
||||
}
|
||||
|
||||
export const sendPasswordResetEmail = async (user: {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
}): Promise<boolean> => {
|
||||
// generate link
|
||||
const token = generateVerificationToken(user.id)
|
||||
const link = `${env.client.url}/reset-password/${token}`
|
||||
// send email
|
||||
return sendEmail({
|
||||
from: env.sender.message,
|
||||
to: user.email,
|
||||
subject: 'Reset your password',
|
||||
text: `Hey ${user.name},\n\nPlease reset your password by clicking the link below:\n\n${link}\n\n`,
|
||||
})
|
||||
}
|
||||
|
||||
@ -12,21 +12,21 @@ import { generateVerificationToken, hashPassword } from '../../src/utils/auth'
|
||||
|
||||
describe('auth router', () => {
|
||||
const route = '/api/auth'
|
||||
const signupRequest = (
|
||||
email: string,
|
||||
password: string,
|
||||
name: string,
|
||||
username: string
|
||||
): supertest.Test => {
|
||||
return request.post(`${route}/email-signup`).send({
|
||||
email,
|
||||
password,
|
||||
name,
|
||||
username,
|
||||
})
|
||||
}
|
||||
|
||||
describe('email signup', () => {
|
||||
const signupRequest = (
|
||||
email: string,
|
||||
password: string,
|
||||
name: string,
|
||||
username: string
|
||||
): supertest.Test => {
|
||||
return request.post(`${route}/email-signup`).send({
|
||||
email,
|
||||
password,
|
||||
name,
|
||||
username,
|
||||
})
|
||||
}
|
||||
const validPassword = 'validPassword'
|
||||
|
||||
let email: string
|
||||
@ -284,11 +284,9 @@ describe('auth router', () => {
|
||||
token = generateVerificationToken(user.id)
|
||||
})
|
||||
|
||||
it('redirects to email-login page', async () => {
|
||||
it('logs in and redirects to home page', async () => {
|
||||
const res = await confirmEmailRequest(token).expect(302)
|
||||
expect(res.header.location).to.endWith(
|
||||
'/email-login?message=EMAIL_VERIFIED'
|
||||
)
|
||||
expect(res.header.location).to.endWith('/home')
|
||||
})
|
||||
|
||||
it('sets user as active', async () => {
|
||||
@ -336,4 +334,122 @@ describe('auth router', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('email-reset-password', () => {
|
||||
const emailResetPasswordReq = (email: string): supertest.Test => {
|
||||
return request.post(`${route}/email-reset-password`).send({
|
||||
email,
|
||||
})
|
||||
}
|
||||
|
||||
let email: string
|
||||
|
||||
context('when email is not empty', () => {
|
||||
before(() => {
|
||||
email = `some_email@domain.app`
|
||||
})
|
||||
|
||||
context('when user exists', () => {
|
||||
let user: User
|
||||
|
||||
before(async () => {
|
||||
user = await createTestUser('test_user')
|
||||
email = user.email
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await deleteTestUser(user.name)
|
||||
})
|
||||
|
||||
context('when email is verified', () => {
|
||||
let fake: (msg: MailDataRequired) => Promise<boolean>
|
||||
|
||||
before(async () => {
|
||||
await getRepository(User).update(user.id, {
|
||||
status: StatusType.Active,
|
||||
})
|
||||
})
|
||||
|
||||
context('when reset password email sent', () => {
|
||||
before(() => {
|
||||
fake = sinon.replace(util, 'sendEmail', sinon.fake.resolves(true))
|
||||
})
|
||||
|
||||
after(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it('redirects to email-reset-password page with success message', async () => {
|
||||
const res = await emailResetPasswordReq(email).expect(302)
|
||||
expect(res.header.location).to.endWith(
|
||||
'/email-reset-password?message=SUCCESS'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
context('when reset password email not sent', () => {
|
||||
before(() => {
|
||||
fake = sinon.replace(
|
||||
util,
|
||||
'sendEmail',
|
||||
sinon.fake.resolves(false)
|
||||
)
|
||||
})
|
||||
|
||||
after(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it('redirects to sign up page with error code INVALID_EMAIL', async () => {
|
||||
const res = await emailResetPasswordReq(email).expect(302)
|
||||
expect(res.header.location).to.endWith(
|
||||
'/email-reset-password?errorCodes=INVALID_EMAIL'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('when email is not verified', () => {
|
||||
before(async () => {
|
||||
await getRepository(User).update(user.id, {
|
||||
status: StatusType.Pending,
|
||||
})
|
||||
})
|
||||
|
||||
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'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('when user does not exist', () => {
|
||||
before(() => {
|
||||
email = 'non_exists_email@domain.app'
|
||||
})
|
||||
|
||||
it('redirects to email-reset-password page with error code USER_NOT_FOUND', async () => {
|
||||
const res = await emailResetPasswordReq(email).expect(302)
|
||||
expect(res.header.location).to.endWith(
|
||||
'/email-reset-password?errorCodes=USER_NOT_FOUND'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('when email is empty', () => {
|
||||
before(() => {
|
||||
email = ''
|
||||
})
|
||||
|
||||
it('redirects to email-reset-password page with error code INVALID_EMAIL', async () => {
|
||||
const res = await emailResetPasswordReq(email).expect(302)
|
||||
expect(res.header.location).to.endWith(
|
||||
'/email-reset-password?errorCodes=INVALID_EMAIL'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user