|
|
@@ -0,0 +1,211 @@
|
|
|
+const { expect } = require('chai')
|
|
|
+const User = require('./helpers/User').promises
|
|
|
+const MockHaveIBeenPwnedApiClass = require('./mocks/MockHaveIBeenPwnedApi')
|
|
|
+const { db } = require('../../../app/src/infrastructure/mongodb')
|
|
|
+const { getMetric } = require('./helpers/metrics').promises
|
|
|
+const sleep = require('util').promisify(setTimeout)
|
|
|
+
|
|
|
+let MockHaveIBeenPwnedApi
|
|
|
+before(function () {
|
|
|
+ MockHaveIBeenPwnedApi = MockHaveIBeenPwnedApiClass.instance()
|
|
|
+})
|
|
|
+
|
|
|
+async function letPasswordCheckRunInBackground() {
|
|
|
+ await sleep(100)
|
|
|
+}
|
|
|
+
|
|
|
+async function getMetricReUsed() {
|
|
|
+ return getMetric(
|
|
|
+ line => line.includes('password_re_use') && line.includes('re-used')
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+async function getMetricUnique() {
|
|
|
+ return getMetric(
|
|
|
+ line => line.includes('password_re_use') && line.includes('unique')
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+async function getMetricFailure() {
|
|
|
+ return getMetric(
|
|
|
+ line => line.includes('password_re_use') && line.includes('failure')
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+let user, previous
|
|
|
+async function resetPassword(password) {
|
|
|
+ await user.getCsrfToken()
|
|
|
+ await user.doRequest('POST', {
|
|
|
+ url: '/user/password/reset',
|
|
|
+ form: {
|
|
|
+ email: user.email,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ const token = (
|
|
|
+ await db.tokens.findOne({
|
|
|
+ 'data.user_id': user._id.toString(),
|
|
|
+ })
|
|
|
+ ).token
|
|
|
+
|
|
|
+ await user.doRequest('GET', {
|
|
|
+ url: `/user/password/set?passwordResetToken=${token}&email=${user.email}`,
|
|
|
+ })
|
|
|
+ await user.doRequest('POST', {
|
|
|
+ url: '/user/password/set',
|
|
|
+ form: {
|
|
|
+ passwordResetToken: token,
|
|
|
+ password,
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+describe('HaveIBeenPwnedApi', function () {
|
|
|
+ describe('login with weak password', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ user = new User()
|
|
|
+ user.password = 'aLeakedPassword42'
|
|
|
+
|
|
|
+ // echo -n aLeakedPassword42 | sha1sum
|
|
|
+ MockHaveIBeenPwnedApi.addPasswordByHash(
|
|
|
+ 'D1ABBDEEE70CBE8BBCE5D9D039C53C0CE91C0C16'
|
|
|
+ )
|
|
|
+ })
|
|
|
+ beforeEach('create the user', async function () {
|
|
|
+ await user.ensureUserExists()
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ beforeEach('fetch previous count', async function () {
|
|
|
+ previous = await getMetricReUsed()
|
|
|
+ })
|
|
|
+ beforeEach('login', async function () {
|
|
|
+ await user.loginNoUpdate()
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ it('should track the weak password', async function () {
|
|
|
+ const after = await getMetricReUsed()
|
|
|
+ expect(after).to.equal(previous + 1)
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('login with strong password', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ user = new User()
|
|
|
+ user.password = 'this-is-a-strong-password'
|
|
|
+ })
|
|
|
+ beforeEach('create the user', async function () {
|
|
|
+ await user.ensureUserExists()
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ beforeEach('fetch previous count', async function () {
|
|
|
+ previous = await getMetricUnique()
|
|
|
+ })
|
|
|
+ beforeEach('login', async function () {
|
|
|
+ await user.loginNoUpdate()
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ it('should track the strong password', async function () {
|
|
|
+ const after = await getMetricUnique()
|
|
|
+ expect(after).to.equal(previous + 1)
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('when the api is producing garbage', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ user = new User()
|
|
|
+ user.password = 'trigger-garbage-output'
|
|
|
+ })
|
|
|
+ beforeEach('create the user', async function () {
|
|
|
+ await user.ensureUserExists()
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ beforeEach('fetch previous count', async function () {
|
|
|
+ previous = await getMetricFailure()
|
|
|
+ })
|
|
|
+ beforeEach('login', async function () {
|
|
|
+ await user.loginNoUpdate()
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ it('should track the failure to collect a score', async function () {
|
|
|
+ const after = await getMetricFailure()
|
|
|
+ expect(after).to.equal(previous + 1)
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('login attempt with weak password', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ user = new User()
|
|
|
+ // echo -n aLeakedPassword42 | sha1sum
|
|
|
+ MockHaveIBeenPwnedApi.addPasswordByHash(
|
|
|
+ 'D1ABBDEEE70CBE8BBCE5D9D039C53C0CE91C0C16'
|
|
|
+ )
|
|
|
+ })
|
|
|
+ beforeEach('create the user', async function () {
|
|
|
+ await user.ensureUserExists()
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ beforeEach('fetch previous counts', async function () {
|
|
|
+ previous = {
|
|
|
+ reUsed: await getMetricReUsed(),
|
|
|
+ unique: await getMetricUnique(),
|
|
|
+ failure: await getMetricFailure(),
|
|
|
+ }
|
|
|
+ })
|
|
|
+ beforeEach('login', async function () {
|
|
|
+ await user.loginWithEmailPassword(user.email, 'aLeakedPassword42')
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ it('should not increment any counter', async function () {
|
|
|
+ expect(previous).to.deep.equal({
|
|
|
+ reUsed: await getMetricReUsed(),
|
|
|
+ unique: await getMetricUnique(),
|
|
|
+ failure: await getMetricFailure(),
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('password reset with a weak password', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ user = new User()
|
|
|
+ // echo -n aLeakedPassword42 | sha1sum
|
|
|
+ MockHaveIBeenPwnedApi.addPasswordByHash(
|
|
|
+ 'D1ABBDEEE70CBE8BBCE5D9D039C53C0CE91C0C16'
|
|
|
+ )
|
|
|
+ })
|
|
|
+ beforeEach('create the user', async function () {
|
|
|
+ await user.ensureUserExists()
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ beforeEach('fetch previous count', async function () {
|
|
|
+ previous = await getMetricReUsed()
|
|
|
+ })
|
|
|
+ beforeEach('set password', async function () {
|
|
|
+ await resetPassword('aLeakedPassword42')
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ it('should track the weak password', async function () {
|
|
|
+ const after = await getMetricReUsed()
|
|
|
+ expect(after).to.equal(previous + 1)
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('password reset with a strong password', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ user = new User()
|
|
|
+ })
|
|
|
+ beforeEach('create the user', async function () {
|
|
|
+ await user.ensureUserExists()
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ beforeEach('fetch previous count', async function () {
|
|
|
+ previous = await getMetricUnique()
|
|
|
+ })
|
|
|
+ beforeEach('set password', async function () {
|
|
|
+ await resetPassword('a-strong-new-password')
|
|
|
+ await letPasswordCheckRunInBackground()
|
|
|
+ })
|
|
|
+ it('should track the strong password', async function () {
|
|
|
+ const after = await getMetricUnique()
|
|
|
+ expect(after).to.equal(previous + 1)
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|