Files
Verso/services/web/app/src/Features/PasswordReset/PasswordResetHandler.mjs
T
b0419a86f2 [web] Add audit logs for clear_sessions_set_must_reconfirm script, "must-reset-password-set" and "must-reset-password-unset" (#21776)
* Promisify clear_sessions_set_must_reconfirm.mjs

* Add test on PasswordResetTests.mjs

* Add `must-reset-password-unset` audit log

* Add `must-reset-password-set` audit log

* Add test ClearSessionsSetMustReconfirmTests.mjs

* Fixup bad copy-paste in test: `must-reset-password-set` -> `must-reset-password-unset`

* Check `must_reconfirm` before calling `removeReconfirmFlag`

Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>

* Fix unit test

* Use `promiseMapWithLimit`

* Add `{ script: true }` to AuditLog. Also use `undefined` instead of `null` for consistency

---------

Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
GitOrigin-RevId: 522026c82196d263c196503d899b8c57b05b31dd
2024-11-15 09:05:21 +00:00

149 lines
3.7 KiB
JavaScript

import settings from '@overleaf/settings'
import UserAuditLogHandler from '../User/UserAuditLogHandler.js'
import UserGetter from '../User/UserGetter.js'
import OneTimeTokenHandler from '../Security/OneTimeTokenHandler.js'
import EmailHandler from '../Email/EmailHandler.js'
import AuthenticationManager from '../Authentication/AuthenticationManager.js'
import { callbackify, promisify } from 'node:util'
import PermissionsManager from '../Authorization/PermissionsManager.js'
const assertUserPermissions = PermissionsManager.promises.assertUserPermissions
const AUDIT_LOG_TOKEN_PREFIX_LENGTH = 10
async function generateAndEmailResetToken(email) {
const user = await UserGetter.promises.getUserByAnyEmail(email)
if (!user) {
return null
}
if (user.email !== email) {
return 'secondary'
}
await assertUserPermissions(user, ['change-password'])
const data = { user_id: user._id.toString(), email }
const token = await OneTimeTokenHandler.promises.getNewToken('password', data)
const emailOptions = {
to: email,
setNewPasswordUrl: `${
settings.siteUrl
}/user/password/set?passwordResetToken=${token}&email=${encodeURIComponent(
email
)}`,
}
await EmailHandler.promises.sendEmail('passwordResetRequested', emailOptions)
return 'primary'
}
function expirePasswordResetToken(token, callback) {
OneTimeTokenHandler.expireToken('password', token, err => {
return callback(err)
})
}
async function getUserForPasswordResetToken(token) {
let result
try {
result = await OneTimeTokenHandler.promises.peekValueFromToken(
'password',
token
)
} catch (err) {
if (err.name === 'NotFoundError') {
return
} else {
throw err
}
}
const { data, remainingPeeks } = result || {}
if (data == null || data.email == null) {
return { user: null, remainingPeeks }
}
const user = await UserGetter.promises.getUserByMainEmail(data.email, {
_id: 1,
'overleaf.id': 1,
email: 1,
must_reconfirm: 1,
})
await assertUserPermissions(user, ['change-password'])
if (user == null) {
return { user: null, remainingPeeks: 0 }
} else if (data.user_id != null && data.user_id === user._id.toString()) {
return { user, remainingPeeks }
} else if (
data.v1_user_id != null &&
user.overleaf != null &&
data.v1_user_id === user.overleaf.id
) {
return { user, remainingPeeks }
} else {
return { user: null, remainingPeeks: 0 }
}
}
async function setNewUserPassword(token, password, auditLog) {
const result =
await PasswordResetHandler.promises.getUserForPasswordResetToken(token)
const { user } = result || {}
if (!user) {
return {
found: false,
reset: false,
userId: null,
}
}
await UserAuditLogHandler.promises.addEntry(
user._id,
'reset-password',
auditLog.initiatorId,
auditLog.ip,
{ token: token.substring(0, AUDIT_LOG_TOKEN_PREFIX_LENGTH) }
)
const reset = await AuthenticationManager.promises.setUserPassword(
user,
password
)
await PasswordResetHandler.promises.expirePasswordResetToken(token)
return {
found: true,
reset,
userId: user._id,
mustReconfirm: user.must_reconfirm,
}
}
const PasswordResetHandler = {
generateAndEmailResetToken: callbackify(generateAndEmailResetToken),
setNewUserPassword: callbackify(setNewUserPassword),
getUserForPasswordResetToken: callbackify(getUserForPasswordResetToken),
expirePasswordResetToken,
}
PasswordResetHandler.promises = {
generateAndEmailResetToken,
getUserForPasswordResetToken,
expirePasswordResetToken: promisify(
PasswordResetHandler.expirePasswordResetToken
),
setNewUserPassword,
}
export default PasswordResetHandler