diff --git a/packages/payload/src/auth/operations/forgotPassword.ts b/packages/payload/src/auth/operations/forgotPassword.ts index 1252cae8e6..c5d702ffae 100644 --- a/packages/payload/src/auth/operations/forgotPassword.ts +++ b/packages/payload/src/auth/operations/forgotPassword.ts @@ -5,6 +5,8 @@ import type { PayloadRequest } from '../../express/types' import { buildAfterOperation } from '../../collections/operations/utils' import { APIError } from '../../errors' +import { initTransaction } from '../../utilities/initTransaction' +import { killTransaction } from '../../utilities/killTransaction' export type Arguments = { collection: Collection @@ -19,7 +21,7 @@ export type Arguments = { export type Result = string -async function forgotPassword(incomingArgs: Arguments): Promise { +async function forgotPassword (incomingArgs: Arguments): Promise { if (!Object.prototype.hasOwnProperty.call(incomingArgs.data, 'email')) { throw new APIError('Missing email.', 400) } @@ -54,99 +56,109 @@ async function forgotPassword(incomingArgs: Arguments): Promise { req, } = args - // ///////////////////////////////////// - // Forget password - // ///////////////////////////////////// + try { + const shouldCommit = await initTransaction(req) - let token: Buffer | string = crypto.randomBytes(20) - token = token.toString('hex') + // ///////////////////////////////////// + // Forget password + // ///////////////////////////////////// - type UserDoc = { - id: number | string - resetPasswordExpiration?: Date - resetPasswordToken?: string - } + let token: Buffer | string = crypto.randomBytes(20) + token = token.toString('hex') - if (!data.email) { - throw new APIError('Missing email.') - } + type UserDoc = { + id: number | string + resetPasswordExpiration?: Date + resetPasswordToken?: string + } - let user = await payload.db.findOne({ - collection: collectionConfig.slug, - req, - where: { email: { equals: data.email.toLowerCase() } }, - }) + if (!data.email) { + throw new APIError('Missing email.') + } - if (!user) return null + let user = await payload.db.findOne({ + collection: collectionConfig.slug, + req, + where: { email: { equals: data.email.toLowerCase() } }, + }) - user.resetPasswordToken = token - user.resetPasswordExpiration = new Date(expiration || Date.now() + 3600000) // 1 hour + if (!user) return null - user = await payload.update({ - id: user.id, - collection: collectionConfig.slug, - data: user, - }) + user.resetPasswordToken = token + user.resetPasswordExpiration = new Date(expiration || Date.now() + 3600000) // 1 hour - if (!disableEmail) { - const serverURL = - config.serverURL !== null && config.serverURL !== '' - ? config.serverURL - : `${req.protocol}://${req.get('host')}` + user = await payload.update({ + id: user.id, + collection: collectionConfig.slug, + data: user, + req, + }) - let html = `${t('authentication:youAreReceivingResetPassword')} + if (!disableEmail) { + const serverURL = + config.serverURL !== null && config.serverURL !== '' + ? config.serverURL + : `${req.protocol}://${req.get('host')}` + + let html = `${t('authentication:youAreReceivingResetPassword')} ${serverURL}${config.routes.admin}/reset/${token} ${t('authentication:youDidNotRequestPassword')}` - if (typeof collectionConfig.auth.forgotPassword.generateEmailHTML === 'function') { - html = await collectionConfig.auth.forgotPassword.generateEmailHTML({ - req, - token, - user, + if (typeof collectionConfig.auth.forgotPassword.generateEmailHTML === 'function') { + html = await collectionConfig.auth.forgotPassword.generateEmailHTML({ + req, + token, + user, + }) + } + + let subject = t('authentication:resetYourPassword') + + if (typeof collectionConfig.auth.forgotPassword.generateEmailSubject === 'function') { + subject = await collectionConfig.auth.forgotPassword.generateEmailSubject({ + req, + token, + user, + }) + } + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + email({ + from: `"${emailOptions.fromName}" <${emailOptions.fromAddress}>`, + html, + subject, + to: data.email, }) } - let subject = t('authentication:resetYourPassword') + // ///////////////////////////////////// + // afterForgotPassword - Collection + // ///////////////////////////////////// - if (typeof collectionConfig.auth.forgotPassword.generateEmailSubject === 'function') { - subject = await collectionConfig.auth.forgotPassword.generateEmailSubject({ - req, - token, - user, - }) - } + await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook, hook) => { + await priorHook + await hook({ args, context: req.context }) + }, Promise.resolve()) - // eslint-disable-next-line @typescript-eslint/no-floating-promises - email({ - from: `"${emailOptions.fromName}" <${emailOptions.fromAddress}>`, - html, - subject, - to: data.email, + // ///////////////////////////////////// + // afterOperation - Collection + // ///////////////////////////////////// + + token = await buildAfterOperation({ + args, + operation: 'forgotPassword', + result: token, }) + + if (shouldCommit) await payload.db.commitTransaction(req.transactionID) + + return token + } catch (error: unknown) { + await killTransaction(req) + throw error } - - // ///////////////////////////////////// - // afterForgotPassword - Collection - // ///////////////////////////////////// - - await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook, hook) => { - await priorHook - await hook({ args, context: req.context }) - }, Promise.resolve()) - - // ///////////////////////////////////// - // afterOperation - Collection - // ///////////////////////////////////// - - token = await buildAfterOperation({ - args, - operation: 'forgotPassword', - result: token, - }) - - return token } export default forgotPassword diff --git a/packages/payload/src/auth/operations/verifyEmail.ts b/packages/payload/src/auth/operations/verifyEmail.ts index c2c4cf9027..c30d981ff3 100644 --- a/packages/payload/src/auth/operations/verifyEmail.ts +++ b/packages/payload/src/auth/operations/verifyEmail.ts @@ -4,6 +4,8 @@ import type { Collection } from '../../collections/config/types' import type { PayloadRequest } from '../../express/types' import { APIError } from '../../errors' +import { initTransaction } from '../../utilities/initTransaction' +import { killTransaction } from '../../utilities/killTransaction' export type Args = { collection: Collection @@ -11,35 +13,44 @@ export type Args = { token: string } -async function verifyEmail(args: Args): Promise { +async function verifyEmail (args: Args): Promise { const { collection, req, token } = args if (!Object.prototype.hasOwnProperty.call(args, 'token')) { throw new APIError('Missing required data.', httpStatus.BAD_REQUEST) } - const user = await req.payload.db.findOne({ - collection: collection.config.slug, - req, - where: { - _verificationToken: { equals: token }, - }, - }) + try { + const shouldCommit = await initTransaction(req) - if (!user) throw new APIError('Verification token is invalid.', httpStatus.BAD_REQUEST) - if (user && user._verified === true) - throw new APIError('This account has already been activated.', httpStatus.ACCEPTED) + const user = await req.payload.db.findOne({ + collection: collection.config.slug, + req, + where: { + _verificationToken: { equals: token }, + }, + }) - await req.payload.db.updateOne({ - id: user.id, - collection: collection.config.slug, - data: { - _verificationToken: null, - _verified: true, - }, - req, - }) + if (!user) throw new APIError('Verification token is invalid.', httpStatus.BAD_REQUEST) + if (user && user._verified === true) + throw new APIError('This account has already been activated.', httpStatus.ACCEPTED) - return true + await req.payload.db.updateOne({ + id: user.id, + collection: collection.config.slug, + data: { + _verificationToken: null, + _verified: true, + }, + req, + }) + + if (shouldCommit) await req.payload.db.commitTransaction(req.transactionID) + + return true + } catch (error: unknown) { + await killTransaction(req) + throw error + } } export default verifyEmail