From 17ee682029cf261a557dc2e20d6375c41b12f721 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 22 Dec 2020 11:12:24 +0100 Subject: [PATCH] Implemented a password reset timeout ref #40 --- src/errors/AuthError.ts | 11 +++++++++++ src/models/actions/CreateResetToken.ts | 7 +++++-- src/models/entities/User.ts | 9 +++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/errors/AuthError.ts b/src/errors/AuthError.ts index c2eff13..fd78712 100644 --- a/src/errors/AuthError.ts +++ b/src/errors/AuthError.ts @@ -115,4 +115,15 @@ export class RefreshTokenCountInvalidError extends NotAcceptableError { @IsString() message = "Refresh token count is invalid." +} + +/** + * Error to throw when someone tryes to refresh a user's password more than once in 15 minutes. + */ +export class ResetAlreadyRequestedError extends NotAcceptableError { + @IsString() + name = "ResetAlreadyRequestedError" + + @IsString() + message = "You already requested a password reset in the last 15 minutes. \n Please wait until the old reset code expires before requesting a new one." } \ No newline at end of file diff --git a/src/models/actions/CreateResetToken.ts b/src/models/actions/CreateResetToken.ts index 4a5fe52..b8974d9 100644 --- a/src/models/actions/CreateResetToken.ts +++ b/src/models/actions/CreateResetToken.ts @@ -1,6 +1,6 @@ import { IsEmail, IsOptional, IsString } from 'class-validator'; import { getConnectionManager } from 'typeorm'; -import { UserNotFoundError } from '../../errors/AuthError'; +import { ResetAlreadyRequestedError, UserNotFoundError } from '../../errors/AuthError'; import { UsernameOrEmailNeededError } from '../../errors/UserErrors'; import { JwtCreator } from '../../jwtcreator'; import { User } from '../entities/User'; @@ -32,12 +32,15 @@ export class CreateResetToken { if (this.email === undefined && this.username === undefined) { throw new UsernameOrEmailNeededError(); } - let found_user = await getConnectionManager().get().getRepository(User).findOne({ relations: ['groups', 'permissions', 'actions'], where: [{ username: this.username }, { email: this.email }] }); + let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ username: this.username }, { email: this.email }] }); if (!found_user) { throw new UserNotFoundError(); } + if (found_user.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 15 * 60)) { throw new ResetAlreadyRequestedError(); } + found_user.refreshTokenCount = found_user.refreshTokenCount + 1; + found_user.resetRequestedTimestamp = Math.floor(Date.now() / 1000); await getConnectionManager().get().getRepository(User).save(found_user); //Create the reset diff --git a/src/models/entities/User.ts b/src/models/entities/User.ts index 4cafa8d..57bdf5c 100644 --- a/src/models/entities/User.ts +++ b/src/models/entities/User.ts @@ -111,6 +111,15 @@ export class User extends Principal { @IsOptional() profilePic?: string; + /** + * The last time the user requested a password reset. + * Used to prevent spamming of the password reset route. + */ + @Column({ nullable: true, unique: true }) + @IsString() + @IsOptional() + resetRequestedTimestamp?: number; + /** * The actions performed by this user. * For documentation purposes only, will be implemented later.