From 1bd494f4914918c238a07854d86146dcee72f875 Mon Sep 17 00:00:00 2001 From: ZeroPath Date: Thu, 14 Aug 2025 05:00:09 +0000 Subject: [PATCH 1/3] fix: replace specific 401 response with generic error handling --- routes/resetPassword.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/resetPassword.ts b/routes/resetPassword.ts index 235be1b45ee..bd2ee81b06d 100644 --- a/routes/resetPassword.ts +++ b/routes/resetPassword.ts @@ -45,7 +45,7 @@ module.exports = function resetPassword () { next(error) }) } else { - res.status(401).send(res.__('Wrong answer to security question.')) + next(new Error('Blocked illegal activity by ' + connection.remoteAddress)) } }).catch((error: unknown) => { next(error) From 445322646b300c797b53e6daf6462eb27b93e5cf Mon Sep 17 00:00:00 2001 From: ZeroPath Date: Thu, 14 Aug 2025 05:00:51 +0000 Subject: [PATCH 2/3] fix: set dummy resetRequest cookie to prevent username/email enumeration --- routes/resetPassword.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/routes/resetPassword.ts b/routes/resetPassword.ts index bd2ee81b06d..c9da7e9eb1f 100644 --- a/routes/resetPassword.ts +++ b/routes/resetPassword.ts @@ -14,6 +14,10 @@ import challengeUtils = require('../lib/challengeUtils') const users = require('../data/datacache').users const security = require('../lib/insecurity') +function setResetRequestCookie(res: Response) { + res.cookie('resetRequest', '1', { maxAge: 600000, httpOnly: true, sameSite: 'Strict' }) +} + module.exports = function resetPassword () { return ({ body, connection }: Request, res: Response, next: NextFunction) => { const email = body.email @@ -21,10 +25,13 @@ module.exports = function resetPassword () { const newPassword = body.new const repeatPassword = body.repeat if (!email || !answer) { + setResetRequestCookie(res) next(new Error('Blocked illegal activity by ' + connection.remoteAddress)) } else if (!newPassword || newPassword === 'undefined') { + setResetRequestCookie(res) res.status(401).send(res.__('Password cannot be empty.')) } else if (newPassword !== repeatPassword) { + setResetRequestCookie(res) res.status(401).send(res.__('New and repeated password do not match.')) } else { SecurityAnswerModel.findOne({ @@ -37,6 +44,7 @@ module.exports = function resetPassword () { UserModel.findByPk(data.UserId).then((user: UserModel | null) => { user?.update({ password: newPassword }).then((user: UserModel) => { verifySecurityAnswerChallenges(user, answer) + setResetRequestCookie(res) res.json({ user }) }).catch((error: unknown) => { next(error) From 6ff6eeb7a863d2827514499d3b906d1dd0b3eaa2 Mon Sep 17 00:00:00 2001 From: ZeroPath Date: Thu, 14 Aug 2025 05:00:59 +0000 Subject: [PATCH 3/3] fix: correct search/replace block in resetPassword route handling --- routes/resetPassword.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/resetPassword.ts b/routes/resetPassword.ts index c9da7e9eb1f..317342e6107 100644 --- a/routes/resetPassword.ts +++ b/routes/resetPassword.ts @@ -56,6 +56,7 @@ module.exports = function resetPassword () { next(new Error('Blocked illegal activity by ' + connection.remoteAddress)) } }).catch((error: unknown) => { + setResetRequestCookie(res) next(error) }) }