diff --git a/api/src/passports/mfa.strategy.ts b/api/src/passports/mfa.strategy.ts index 8caf5ba687..19287e1998 100644 --- a/api/src/passports/mfa.strategy.ts +++ b/api/src/passports/mfa.strategy.ts @@ -17,7 +17,7 @@ import { defaultValidationPipeOptions } from '../utilities/default-validation-pi import { Login } from '../dtos/auth/login.dto'; import { MfaType } from '../enums/mfa/mfa-type-enum'; import { - isUserLockedOut, + checkUserLockout, singleUseCodePresent, singleUseCodeInvalid, } from '../utilities/passport-validator-utilities'; @@ -57,7 +57,8 @@ export class MfaStrategy extends PassportStrategy(Strategy, 'mfa') { `user ${dto.email} attempted to log in, but does not exist`, ); } - isUserLockedOut( + //check if user is locked out and update failed login attempts count + rawUser.failedLoginAttemptsCount = checkUserLockout( rawUser.lastLoginAt, rawUser.failedLoginAttemptsCount, Number(process.env.AUTH_LOCK_LOGIN_AFTER_FAILED_ATTEMPTS), diff --git a/api/src/passports/single-use-code.strategy.ts b/api/src/passports/single-use-code.strategy.ts index f04d367abe..76acdc9c3e 100644 --- a/api/src/passports/single-use-code.strategy.ts +++ b/api/src/passports/single-use-code.strategy.ts @@ -14,7 +14,7 @@ import { defaultValidationPipeOptions } from '../utilities/default-validation-pi import { LoginViaSingleUseCode } from '../dtos/auth/login-single-use-code.dto'; import { OrderByEnum } from '../enums/shared/order-by-enum'; import { - isUserLockedOut, + checkUserLockout, singleUseCodePresent, singleUseCodeInvalid, } from '../utilities/passport-validator-utilities'; @@ -86,7 +86,7 @@ export class SingleUseCodeStrategy extends PassportStrategy( ); } - isUserLockedOut( + rawUser.failedLoginAttemptsCount = checkUserLockout( rawUser.lastLoginAt, rawUser.failedLoginAttemptsCount, Number(process.env.AUTH_LOCK_LOGIN_AFTER_FAILED_ATTEMPTS), diff --git a/api/src/utilities/passport-validator-utilities.ts b/api/src/utilities/passport-validator-utilities.ts index 1b7030e994..e89cac71ed 100644 --- a/api/src/utilities/passport-validator-utilities.ts +++ b/api/src/utilities/passport-validator-utilities.ts @@ -6,14 +6,14 @@ import { HttpException, HttpStatus } from '@nestjs/common'; * @param failedLoginAttemptsCount the number of times the user failed to log in (stored in db) * @param maxAttempts the maximum number of attempts before user is considered locked out (env variable) * - * @returns throws error if user is already locked out + * @returns updated value of failed login attempts */ -export function isUserLockedOut( +export function checkUserLockout( lastLoginAt: Date, failedLoginAttemptsCount: number, maxAttempts: number, cooldown: number, -): void { +): number { if (lastLoginAt && failedLoginAttemptsCount >= maxAttempts) { // if a user has logged in, but has since gone over their max failed login attempts const retryAfter = new Date(lastLoginAt.getTime() + cooldown); @@ -33,6 +33,7 @@ export function isUserLockedOut( ); } } + return failedLoginAttemptsCount; } /** diff --git a/api/test/unit/passports/passport-validator-utilities.spec.ts b/api/test/unit/passports/passport-validator-utilities.spec.ts new file mode 100644 index 0000000000..23e0edb242 --- /dev/null +++ b/api/test/unit/passports/passport-validator-utilities.spec.ts @@ -0,0 +1,47 @@ +import { checkUserLockout } from '../../../src/utilities/passport-validator-utilities'; + +describe('Testing checkUserLockout', () => { + it('should return without erroring and set failedLoginAttemptsCount to be 0 if lockout expired', () => { + const val = { + lastLoginAt: new Date('01/01/2024'), + failedLoginAttemptsCount: 5, + }; + const updatedFailedLoginCount = checkUserLockout( + val.lastLoginAt, + val.failedLoginAttemptsCount, + 5, + 10, + ); + expect(updatedFailedLoginCount).toEqual(0); + }); + + it('should return without erroring and leave failed login count unchanged if user is and was not locked out', () => { + const val = { + lastLoginAt: new Date('01/01/2024'), + failedLoginAttemptsCount: 2, + }; + const updatedFailedLoginCount = checkUserLockout( + val.lastLoginAt, + val.failedLoginAttemptsCount, + 5, + 10, + ); + expect(updatedFailedLoginCount).toEqual(2); + }); + + it('should error if user is still in lockout period', () => { + const val = { + lastLoginAt: new Date(), + failedLoginAttemptsCount: 5, + }; + expect( + async () => + await checkUserLockout( + val.lastLoginAt, + val.failedLoginAttemptsCount, + 5, + 10, + ), + ).rejects.toThrowError(`Failed login attempts exceeded.`); + }); +});