diff --git a/services/web/app/src/Features/Authentication/AuthenticationManager.js b/services/web/app/src/Features/Authentication/AuthenticationManager.js index 1249ea7dcf..3dfdba055e 100644 --- a/services/web/app/src/Features/Authentication/AuthenticationManager.js +++ b/services/web/app/src/Features/Authentication/AuthenticationManager.js @@ -171,6 +171,8 @@ const AuthenticationManager = { }) } + Metrics.inc('try-validate-password') + let allowAnyChars, min, max if (Settings.passwordStrengthOptions) { allowAnyChars = Settings.passwordStrengthOptions.allowAnyChars === true @@ -380,18 +382,29 @@ const AuthenticationManager = { password = password.toLowerCase() email = email.toLowerCase() const stringsToCheck = [email].concat(email.split(/\W+/)) + let largestSimilarity = 0 + let err = null for (const emailPart of stringsToCheck) { if (!_exceedsMaximumLengthRatio(password, MAX_SIMILARITY, emailPart)) { const similarity = DiffHelper.stringSimilarity(password, emailPart) + const similarityOneDecimalPlace = Math.floor(similarity * 10) / 10 + largestSimilarity = Math.max( + largestSimilarity, + similarityOneDecimalPlace + ) if (similarity > MAX_SIMILARITY) { logger.warn( { email, emailPart, similarity, maxSimilarity: MAX_SIMILARITY }, 'Password too similar to email' ) - return new Error('password is too similar to email') + err = new Error('password is too similar to email') } } } + Metrics.inc('password-validation-similarity', 1, { + similarity: largestSimilarity, + }) + return err }, } diff --git a/services/web/test/unit/src/Authentication/AuthenticationManagerTests.js b/services/web/test/unit/src/Authentication/AuthenticationManagerTests.js index 494414783a..5072cae386 100644 --- a/services/web/test/unit/src/Authentication/AuthenticationManagerTests.js +++ b/services/web/test/unit/src/Authentication/AuthenticationManagerTests.js @@ -484,6 +484,17 @@ describe('AuthenticationManager', function () { describe('password length', function () { describe('with the default password length options', function () { + beforeEach(function () { + this.metrics.inc.reset() + }) + + it('should send a metric', function () { + this.AuthenticationManager.validatePassword('foo') + expect(this.metrics.inc.calledWith('try-validate-password')).to.equal( + true + ) + }) + it('should reject passwords that are too short', function () { const result1 = this.AuthenticationManager.validatePassword('') expect(result1).to.be.an.instanceOf( @@ -697,6 +708,21 @@ describe('AuthenticationManager', function () { expect(error).to.exist }) + it('should send a metric with a rounded similarity score when password is too similar to email', function () { + const password = 'su2oe1em3re' + const email = 'someuser@example.com' + const error = this.AuthenticationManager._validatePasswordNotTooSimilar( + password, + email + ) + expect( + this.metrics.inc.calledWith('password-validation-similarity', 1, { + similarity: 0.7, + }) + ).to.equal(true) + expect(error).to.exist + }) + it('should return nothing when the password different from email', function () { const password = '58WyLvr' const email = 'someuser@example.com'