0abbc48b2f
GitOrigin-RevId: dede8acf46bb457fd85ad811a41a2576713e5be9
250 lines
6.8 KiB
JavaScript
250 lines
6.8 KiB
JavaScript
import PasswordResetHandler from './PasswordResetHandler.mjs'
|
|
import AuthenticationController from '../Authentication/AuthenticationController.js'
|
|
import AuthenticationManager from '../Authentication/AuthenticationManager.js'
|
|
import SessionManager from '../Authentication/SessionManager.js'
|
|
import UserGetter from '../User/UserGetter.js'
|
|
import UserUpdater from '../User/UserUpdater.js'
|
|
import UserSessionsManager from '../User/UserSessionsManager.js'
|
|
import OError from '@overleaf/o-error'
|
|
import EmailsHelper from '../Helpers/EmailHelper.js'
|
|
import { expressify } from '@overleaf/promise-utils'
|
|
import { z, validateReq } from '../../infrastructure/Validation.js'
|
|
|
|
const setNewUserPasswordSchema = z.object({
|
|
body: z.object({
|
|
email: z.string().optional(),
|
|
password: z.string(),
|
|
passwordResetToken: z.string(),
|
|
}),
|
|
})
|
|
|
|
async function setNewUserPassword(req, res, next) {
|
|
let user
|
|
const { body } = validateReq(req, setNewUserPasswordSchema)
|
|
let { passwordResetToken, password, email } = body
|
|
if (!passwordResetToken || !password) {
|
|
return res.status(400).json({
|
|
message: {
|
|
key: 'invalid-password',
|
|
},
|
|
})
|
|
}
|
|
|
|
const err = AuthenticationManager.validatePassword(password, email)
|
|
if (err) {
|
|
const message = AuthenticationManager.getMessageForInvalidPasswordError(
|
|
err,
|
|
req
|
|
)
|
|
return res.status(400).json({ message })
|
|
}
|
|
|
|
passwordResetToken = passwordResetToken.trim()
|
|
|
|
const initiatorId = SessionManager.getLoggedInUserId(req.session)
|
|
// password reset via tokens can be done while logged in, or not
|
|
const auditLog = {
|
|
initiatorId,
|
|
ip: req.ip,
|
|
}
|
|
|
|
try {
|
|
const result = await PasswordResetHandler.promises.setNewUserPassword(
|
|
passwordResetToken,
|
|
password,
|
|
auditLog
|
|
)
|
|
const { found, reset, userId, mustReconfirm } = result
|
|
if (!found) {
|
|
return res.status(404).json({
|
|
message: {
|
|
key: 'token-expired',
|
|
},
|
|
})
|
|
}
|
|
if (!reset) {
|
|
return res.status(500).json({
|
|
message: req.i18n.translate('error_performing_request'),
|
|
})
|
|
}
|
|
await UserSessionsManager.promises.removeSessionsFromRedis({ _id: userId })
|
|
if (mustReconfirm) {
|
|
await UserUpdater.promises.removeReconfirmFlag(userId, auditLog)
|
|
}
|
|
if (!req.session.doLoginAfterPasswordReset) {
|
|
return res.sendStatus(200)
|
|
}
|
|
user = await UserGetter.promises.getUser(userId)
|
|
} catch (error) {
|
|
if (error.name === 'NotFoundError') {
|
|
return res.status(404).json({
|
|
message: {
|
|
key: 'token-expired',
|
|
},
|
|
})
|
|
} else if (error.name === 'InvalidPasswordError') {
|
|
return res.status(400).json({
|
|
message: {
|
|
key: 'invalid-password',
|
|
},
|
|
})
|
|
} else if (error.name === 'PasswordMustBeDifferentError') {
|
|
return res.status(400).json({
|
|
message: {
|
|
key: 'password-must-be-different',
|
|
},
|
|
})
|
|
} else if (error.name === 'PasswordReusedError') {
|
|
return res.status(400).json({
|
|
message: {
|
|
key: 'password-must-be-strong',
|
|
},
|
|
})
|
|
} else {
|
|
return res.status(500).json({
|
|
message: req.i18n.translate('error_performing_request'),
|
|
})
|
|
}
|
|
}
|
|
AuthenticationController.setAuditInfo(req, {
|
|
method: 'Password reset, set new password',
|
|
})
|
|
AuthenticationController.finishLogin(user, req, res, next)
|
|
}
|
|
|
|
const requestResetSchema = z.object({
|
|
body: z.object({
|
|
email: z.string(),
|
|
}),
|
|
})
|
|
|
|
async function requestReset(req, res, next) {
|
|
const { body } = validateReq(req, requestResetSchema)
|
|
const email = EmailsHelper.parseEmail(body.email)
|
|
if (!email) {
|
|
return res.status(400).json({
|
|
message: req.i18n.translate('must_be_email_address'),
|
|
})
|
|
}
|
|
|
|
let status
|
|
try {
|
|
status =
|
|
await PasswordResetHandler.promises.generateAndEmailResetToken(email)
|
|
} catch (err) {
|
|
OError.tag(err, 'failed to generate and email password reset token', {
|
|
email,
|
|
})
|
|
|
|
if (
|
|
err.message ===
|
|
'user does not have one or more permissions within change-password'
|
|
) {
|
|
return res.status(403).json({
|
|
message: {
|
|
key: 'no-password-allowed-due-to-sso',
|
|
},
|
|
})
|
|
}
|
|
throw err
|
|
}
|
|
|
|
if (status === 'primary') {
|
|
return res.status(200).json({
|
|
message: req.i18n.translate('password_reset_email_sent'),
|
|
})
|
|
} else if (status === 'secondary') {
|
|
return res.status(404).json({
|
|
message: req.i18n.translate('secondary_email_password_reset'),
|
|
})
|
|
} else {
|
|
return res.status(404).json({
|
|
message: req.i18n.translate('cant_find_email'),
|
|
})
|
|
}
|
|
}
|
|
|
|
const renderSetPasswordFormSchema = z.object({
|
|
query: z.object({
|
|
email: z.string(),
|
|
passwordResetToken: z.string().optional(),
|
|
}),
|
|
})
|
|
|
|
async function renderSetPasswordForm(req, res, next) {
|
|
const { query } = validateReq(req, renderSetPasswordFormSchema)
|
|
|
|
if (query.passwordResetToken != null) {
|
|
try {
|
|
const result =
|
|
await PasswordResetHandler.promises.getUserForPasswordResetToken(
|
|
query.passwordResetToken
|
|
)
|
|
|
|
const { user, remainingPeeks } = result || {}
|
|
if (!user || remainingPeeks <= 0) {
|
|
return res.redirect('/user/password/reset?error=token_expired')
|
|
}
|
|
req.session.resetToken = query.passwordResetToken
|
|
let emailQuery = ''
|
|
|
|
if (typeof query.email === 'string') {
|
|
const email = EmailsHelper.parseEmail(query.email)
|
|
if (email) {
|
|
emailQuery = `?email=${encodeURIComponent(email)}`
|
|
}
|
|
}
|
|
|
|
return res.redirect('/user/password/set' + emailQuery)
|
|
} catch (err) {
|
|
if (err.name === 'ForbiddenError') {
|
|
return next(err)
|
|
}
|
|
return res.redirect('/user/password/reset?error=token_expired')
|
|
}
|
|
}
|
|
|
|
if (req.session.resetToken == null) {
|
|
return res.redirect('/user/password/reset')
|
|
}
|
|
|
|
const email = EmailsHelper.parseEmail(query.email)
|
|
|
|
// clean up to avoid leaking the token in the session object
|
|
const passwordResetToken = req.session.resetToken
|
|
delete req.session.resetToken
|
|
|
|
res.render('user/setPassword', {
|
|
title: 'set_password',
|
|
email,
|
|
passwordResetToken,
|
|
})
|
|
}
|
|
|
|
const renderRequestResetFormSchema = z.object({
|
|
query: z.object({
|
|
error: z.string().optional(),
|
|
}),
|
|
})
|
|
|
|
async function renderRequestResetForm(req, res) {
|
|
const { query } = validateReq(req, renderRequestResetFormSchema)
|
|
const errorQuery = query.error
|
|
let error = null
|
|
if (errorQuery === 'token_expired') {
|
|
error = 'password_reset_token_expired'
|
|
}
|
|
|
|
res.render('user/passwordReset', {
|
|
title: 'reset_password',
|
|
error,
|
|
})
|
|
}
|
|
|
|
export default {
|
|
renderRequestResetForm: expressify(renderRequestResetForm),
|
|
requestReset: expressify(requestReset),
|
|
renderSetPasswordForm: expressify(renderSetPasswordForm),
|
|
setNewUserPassword: expressify(setNewUserPassword),
|
|
}
|