From 4dc97d5b6595c67ee5aca54b474c9b71ef3f12ce Mon Sep 17 00:00:00 2001 From: Johann150 Date: Wed, 26 Oct 2022 22:12:38 +0200 Subject: [PATCH] server: enhance reset-password endpoint - Add a rate limit analogous to request-reset-password. See also a0ef32f4f66a1de2c591edd88aba4288a1b794a5. - Delete an expired reset request if found. - Return a proper error. - Use time constants. Changelog: Changed --- .../server/api/endpoints/reset-password.ts | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index ea7df52c0..9c42e9ebc 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -1,5 +1,6 @@ import bcrypt from 'bcryptjs'; import { UserProfiles, PasswordResetRequests } from '@/models/index.js'; +import { MINUTE } from '@/const.js'; import define from '../define.js'; export const meta = { @@ -9,8 +10,17 @@ export const meta = { description: 'Complete the password reset that was previously requested.', - errors: { + limit: { + duration: HOUR, + max: 3, + }, + errors: { + noSuchResetRequest: { + message: 'No such reset request.', + code: 'NO_SUCH_RESET_REQUEST', + id: '6382759d-294c-43de-89b3-4e825006ca43', + }, }, } as const; @@ -25,13 +35,18 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const req = await PasswordResetRequests.findOneByOrFail({ + const req = await PasswordResetRequests.findOneBy({ token: ps.token, }); + if (req == null) throw new ApiError(meta.errors.noSuchResetRequest); - // 発行してから30分以上経過していたら無効 - if (Date.now() - req.createdAt.getTime() > 1000 * 60 * 30) { - throw new Error(); // TODO + // expires after 30 minutes + // This is a secondary check just in case the expiry task is broken, + // the expiry task is badly aligned with this expiration or something + // else strange is going on. + if (Date.now() - req.createdAt.getTime() > 30 * MINUTE) { + await PasswordResetRequests.delete(req.id); + throw new ApiError(meta.errors.noSuchResetRequest); } // Generate hash of password @@ -42,5 +57,5 @@ export default define(meta, paramDef, async (ps, user) => { password: hash, }); - PasswordResetRequests.delete(req.id); + await PasswordResetRequests.delete(req.id); });