-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6466 from NMDSdevopsServiceAdm/feature-branch/for…
…gotten-username Feature branch/forgotten username
- Loading branch information
Showing
60 changed files
with
3,082 additions
and
118 deletions.
There are no files selected for viewing
17 changes: 17 additions & 0 deletions
17
backend/migrations/20241216140000-addInvalidFindUsernameAttemptsColumnToLoginTable.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
'use strict'; | ||
|
||
const table = { | ||
tableName: 'Login', | ||
schema: 'cqc', | ||
}; | ||
|
||
/** @type {import('sequelize-cli').Migration} */ | ||
module.exports = { | ||
async up(queryInterface, Sequelize) { | ||
return queryInterface.addColumn(table, 'InvalidFindUsernameAttempts', { type: Sequelize.DataTypes.INTEGER }); | ||
}, | ||
|
||
async down(queryInterface) { | ||
return queryInterface.removeColumn(table, 'InvalidFindUsernameAttempts'); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const UserAccountStatus = { | ||
Locked: 'Locked', | ||
Pending: 'PENDING', | ||
}; | ||
|
||
const MaxLoginAttempts = 10; | ||
const MaxFindUsernameAttempts = 5; | ||
|
||
module.exports = { UserAccountStatus, MaxLoginAttempts, MaxFindUsernameAttempts }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
const { isEmpty } = require('lodash'); | ||
const { sanitisePostcode } = require('../../utils/postcodeSanitizer'); | ||
const limitFindUserAccountUtils = require('../../utils/limitFindUserAccountUtils'); | ||
const HttpError = require('../../utils/errors/httpError'); | ||
const models = require('../../models/index'); | ||
const { MaxFindUsernameAttempts } = require('../../data/constants'); | ||
|
||
class FindUserAccountException extends HttpError {} | ||
|
||
const findUserAccount = async (req, res) => { | ||
try { | ||
await validateRequest(req); | ||
|
||
const { name, workplaceIdOrPostcode, email } = req.body; | ||
let userFound = null; | ||
|
||
const postcode = sanitisePostcode(workplaceIdOrPostcode); | ||
if (postcode) { | ||
userFound = await models.user.findByRelevantInfo({ name, postcode, email }); | ||
} | ||
|
||
userFound = | ||
userFound ?? (await models.user.findByRelevantInfo({ name, workplaceId: workplaceIdOrPostcode, email })); | ||
|
||
if (!userFound) { | ||
const failedAttemptsCount = await limitFindUserAccountUtils.recordFailedAttempt(req.ip); | ||
const remainingAttempts = MaxFindUsernameAttempts - failedAttemptsCount; | ||
|
||
return sendNotFoundResponse(res, remainingAttempts); | ||
} | ||
|
||
if (userFound.multipleAccountsFound) { | ||
return sendMultipleAccountsFoundResponse(res); | ||
} | ||
|
||
if (userFound.accountLocked) { | ||
throw new FindUserAccountException('User account is locked', 423); | ||
} | ||
|
||
return sendSuccessResponse(res, userFound); | ||
} catch (err) { | ||
return sendErrorResponse(res, err); | ||
} | ||
}; | ||
|
||
const validateRequest = async (req) => { | ||
if (requestBodyIsInvalid(req)) { | ||
throw new FindUserAccountException('Invalid request', 400); | ||
} | ||
|
||
if (await ipAddressReachedMaxAttempt(req)) { | ||
throw new FindUserAccountException('Reached maximum retry', 429); | ||
} | ||
}; | ||
|
||
const requestBodyIsInvalid = (req) => { | ||
if (!req.body) { | ||
return true; | ||
} | ||
const { name, workplaceIdOrPostcode, email } = req.body; | ||
|
||
return [name, workplaceIdOrPostcode, email].some((field) => isEmpty(field)); | ||
}; | ||
|
||
const ipAddressReachedMaxAttempt = async (req) => { | ||
const attemptsSoFar = (await limitFindUserAccountUtils.getNumberOfFailedAttempts(req.ip)) ?? 0; | ||
return attemptsSoFar >= MaxFindUsernameAttempts; | ||
}; | ||
|
||
const sendSuccessResponse = (res, userFound) => { | ||
const { uid, SecurityQuestionValue } = userFound; | ||
return res.status(200).json({ | ||
status: 'AccountFound', | ||
accountUid: uid, | ||
securityQuestion: SecurityQuestionValue, | ||
}); | ||
}; | ||
|
||
const sendNotFoundResponse = (res, remainingAttempts = 0) => { | ||
return res.status(200).json({ status: 'AccountNotFound', remainingAttempts }); | ||
}; | ||
|
||
const sendMultipleAccountsFoundResponse = (res) => { | ||
return res.status(200).json({ status: 'MultipleAccountsFound' }); | ||
}; | ||
|
||
const sendErrorResponse = (res, err) => { | ||
console.error('registration POST findUserAccount - failed', err); | ||
|
||
if (err instanceof FindUserAccountException) { | ||
return res.status(err.statusCode).json({ message: err.message }); | ||
} | ||
|
||
return res.status(500).send('Internal server error'); | ||
}; | ||
|
||
module.exports = { findUserAccount, FindUserAccountException }; |
Oops, something went wrong.