Merge pull request #2975 from specklesystems/fabians/pwdreset-ioc-3
chore(server): pwdreset IoC 3 - finalizePasswordResetFactory
This commit is contained in:
@@ -8,3 +8,5 @@ export type GetPendingToken = (
|
||||
) => Promise<Optional<PasswordResetTokenRecord>>
|
||||
|
||||
export type CreateToken = (email: string) => Promise<PasswordResetTokenRecord>
|
||||
|
||||
export type DeleteTokens = (identity: EmailOrTokenId) => Promise<void>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { InvalidArgumentError } from '@/modules/shared/errors'
|
||||
import { Knex } from 'knex'
|
||||
import {
|
||||
CreateToken,
|
||||
DeleteTokens,
|
||||
EmailOrTokenId,
|
||||
GetPendingToken
|
||||
} from '@/modules/pwdreset/domain/operations'
|
||||
@@ -56,7 +57,8 @@ export const getPendingTokenFactory =
|
||||
* Delete all tokens that fit the specified identity
|
||||
*/
|
||||
export const deleteTokensFactory =
|
||||
(deps: { db: Knex }) => async (identity: EmailOrTokenId) => {
|
||||
(deps: { db: Knex }): DeleteTokens =>
|
||||
async (identity: EmailOrTokenId) => {
|
||||
const q = baseQueryFactory(deps)
|
||||
await q(identity).del()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { db } from '@/db/knex'
|
||||
import { deleteExistingAuthTokens } from '@/modules/auth/repositories'
|
||||
import { getUserByEmail } from '@/modules/core/repositories/users'
|
||||
import { getServerInfo } from '@/modules/core/services/generic'
|
||||
import { updateUserPassword } from '@/modules/core/services/users'
|
||||
import { renderEmail } from '@/modules/emails/services/emailRendering'
|
||||
import { sendEmail } from '@/modules/emails/services/sending'
|
||||
import {
|
||||
createTokenFactory,
|
||||
deleteTokensFactory,
|
||||
getPendingTokenFactory
|
||||
} from '@/modules/pwdreset/repositories'
|
||||
import { finalizePasswordReset } from '@/modules/pwdreset/services/finalize'
|
||||
import { finalizePasswordResetFactory } from '@/modules/pwdreset/services/finalize'
|
||||
import { requestPasswordRecoveryFactory } from '@/modules/pwdreset/services/request'
|
||||
import { ensureError } from '@/modules/shared/helpers/errorHelper'
|
||||
import { Express } from 'express'
|
||||
@@ -38,6 +41,14 @@ export default function (app: Express) {
|
||||
// Finalizes password recovery.
|
||||
app.post('/auth/pwdreset/finalize', async (req, res) => {
|
||||
try {
|
||||
const finalizePasswordReset = finalizePasswordResetFactory({
|
||||
getUserByEmail,
|
||||
getPendingToken: getPendingTokenFactory({ db }),
|
||||
deleteTokens: deleteTokensFactory({ db }),
|
||||
updateUserPassword,
|
||||
deleteExistingAuthTokens
|
||||
})
|
||||
|
||||
if (!req.body.tokenId || !req.body.password) throw new Error('Invalid request.')
|
||||
await finalizePasswordReset(req.body.tokenId, req.body.password)
|
||||
|
||||
|
||||
@@ -1,49 +1,64 @@
|
||||
import { db } from '@/db/knex'
|
||||
import { deleteExistingAuthTokens } from '@/modules/auth/repositories'
|
||||
import { getUserByEmail } from '@/modules/core/repositories/users'
|
||||
import { updateUserPassword } from '@/modules/core/services/users'
|
||||
import { DeleteTokens, GetPendingToken } from '@/modules/pwdreset/domain/operations'
|
||||
import { PasswordRecoveryFinalizationError } from '@/modules/pwdreset/errors'
|
||||
import {
|
||||
deleteTokensFactory,
|
||||
getPendingTokenFactory
|
||||
} from '@/modules/pwdreset/repositories'
|
||||
|
||||
async function initializeState(tokenId: string, password: string) {
|
||||
if (!tokenId && !password)
|
||||
throw new PasswordRecoveryFinalizationError('Both the token & password must be set')
|
||||
type InitializeStateDeps = {
|
||||
getUserByEmail: typeof getUserByEmail
|
||||
getPendingToken: GetPendingToken
|
||||
}
|
||||
|
||||
const token = await getPendingTokenFactory({ db })({ tokenId })
|
||||
if (!token)
|
||||
throw new PasswordRecoveryFinalizationError(
|
||||
'Invalid reset token, it may be expired'
|
||||
)
|
||||
const initializeStateFactory =
|
||||
(deps: InitializeStateDeps) => async (tokenId: string, password: string) => {
|
||||
if (!tokenId && !password)
|
||||
throw new PasswordRecoveryFinalizationError(
|
||||
'Both the token & password must be set'
|
||||
)
|
||||
|
||||
const user = await getUserByEmail(token.email)
|
||||
if (!user) {
|
||||
throw new PasswordRecoveryFinalizationError('Invalid finalization request')
|
||||
const token = await deps.getPendingToken({ tokenId })
|
||||
if (!token)
|
||||
throw new PasswordRecoveryFinalizationError(
|
||||
'Invalid reset token, it may be expired'
|
||||
)
|
||||
|
||||
const user = await deps.getUserByEmail(token.email)
|
||||
if (!user) {
|
||||
throw new PasswordRecoveryFinalizationError('Invalid finalization request')
|
||||
}
|
||||
|
||||
return { tokenId, password, token, user }
|
||||
}
|
||||
|
||||
return { tokenId, password, token, user }
|
||||
type FinalizationState = Awaited<ReturnType<ReturnType<typeof initializeStateFactory>>>
|
||||
|
||||
type FinalizeNewPasswordDeps = {
|
||||
deleteTokens: DeleteTokens
|
||||
updateUserPassword: typeof updateUserPassword
|
||||
deleteExistingAuthTokens: typeof deleteExistingAuthTokens
|
||||
}
|
||||
|
||||
type FinalizationState = Awaited<ReturnType<typeof initializeState>>
|
||||
const finalizeNewPasswordFactory =
|
||||
(deps: FinalizeNewPasswordDeps) => async (state: FinalizationState) => {
|
||||
const { user, password, tokenId } = state
|
||||
await deps.updateUserPassword({ id: user.id, newPassword: password })
|
||||
|
||||
async function finalizeNewPassword(state: FinalizationState) {
|
||||
const { user, password, tokenId } = state
|
||||
await updateUserPassword({ id: user.id, newPassword: password })
|
||||
const deleteTokens = deleteTokensFactory({ db })
|
||||
// Delete password reset tokens
|
||||
await Promise.all([
|
||||
deps.deleteTokens({ tokenId }),
|
||||
deps.deleteTokens({ email: user.email })
|
||||
])
|
||||
|
||||
// Delete password reset tokens
|
||||
await Promise.all([deleteTokens({ tokenId }), deleteTokens({ email: user.email })])
|
||||
|
||||
// Delete existing auth tokens
|
||||
await deleteExistingAuthTokens(user.id)
|
||||
}
|
||||
// Delete existing auth tokens
|
||||
await deps.deleteExistingAuthTokens(user.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to finalize an initiated password recovery flow
|
||||
*/
|
||||
export async function finalizePasswordReset(tokenId: string, password: string) {
|
||||
const state = await initializeState(tokenId, password)
|
||||
await finalizeNewPassword(state)
|
||||
}
|
||||
export const finalizePasswordResetFactory =
|
||||
(deps: InitializeStateDeps & FinalizeNewPasswordDeps) =>
|
||||
async (tokenId: string, password: string) => {
|
||||
const state = await initializeStateFactory(deps)(tokenId, password)
|
||||
await finalizeNewPasswordFactory(deps)(state)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user