From e8ce3856a903946268119acdb040c61d51a73aac Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Sat, 25 Aug 2018 01:12:59 -0300 Subject: [PATCH 1/3] Support new minimum password length parameter --- package.json | 1 + src/__tests__/core/client/index.test.js | 50 +++++++++++++++++++ src/__tests__/field/password.test.js | 22 ++++++++ .../password_strength.test.jsx.snap | 3 ++ .../input/password/password_strength.test.jsx | 25 ++++++++++ src/core/client/index.js | 10 +++- src/field/password.js | 4 +- src/field/password/password_pane.jsx | 2 +- src/ui/input/password/password_strength.jsx | 6 +-- src/ui/input/password_input.jsx | 2 +- yarn.lock | 6 +++ 11 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 src/__tests__/core/client/index.test.js create mode 100644 src/__tests__/field/password.test.js create mode 100644 src/__tests__/ui/input/password/__snapshots__/password_strength.test.jsx.snap create mode 100644 src/__tests__/ui/input/password/password_strength.test.jsx diff --git a/package.json b/package.json index b714bbda4..2e2bd9b8c 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ }, "dependencies": { "auth0-js": "^9.7.3", + "auth0-password-policies": "auth0/auth0-password-policies#v1.0.1", "blueimp-md5": "2.3.1", "fbjs": "^0.3.1", "immutable": "^3.7.3", diff --git a/src/__tests__/core/client/index.test.js b/src/__tests__/core/client/index.test.js new file mode 100644 index 000000000..3d1fe246c --- /dev/null +++ b/src/__tests__/core/client/index.test.js @@ -0,0 +1,50 @@ +import Immutable from 'immutable'; +import { initClient } from '../../../core/client'; + +describe('core/client/index', () => { + describe('initClient', () => { + it('loads password policy correctly without a password_complexity_options option', () => { + const client = { + strategies: [ + { + name: 'auth0', + connections: [ + { + name: 'Username-Password-Authentication', + passwordPolicy: 'low' + } + ] + } + ] + }; + const result = initClient(Immutable.fromJS({}), client).toJS(); + expect(result.client.connections.database[0].passwordPolicy).toMatchObject({ + length: { + minLength: 6 + } + }); + }); + it('loads password policy correctly with a password_complexity_options option', () => { + const client = { + strategies: [ + { + name: 'auth0', + connections: [ + { + name: 'Username-Password-Authentication', + passwordPolicy: 'low', + password_complexity_options: { min_length: 4 } + } + ] + } + ] + }; + const result = initClient(Immutable.fromJS({}), client).toJS(); + expect(result.client.connections.database[0].passwordPolicy).toMatchObject({ + length: { + minLength: 4 + } + }); + }); + }); +}); diff --git a/src/__tests__/field/password.test.js b/src/__tests__/field/password.test.js new file mode 100644 index 000000000..1e6712977 --- /dev/null +++ b/src/__tests__/field/password.test.js @@ -0,0 +1,22 @@ +import Immutable from 'immutable'; + +describe('field/password', () => { + let passwordField; + beforeEach(() => { + jest.resetModules(); + jest.mock('password-sheriff/lib/policy'); + passwordField = require('field/password'); + }); + describe('validatePassword()', () => { + it(`validates password correctly`, () => { + const model = { + toJS: jest.fn() + }; + passwordField.validatePassword('the-password', model); + const { mock } = require('password-sheriff/lib/policy').prototype.check; + expect(mock.calls.length).toBe(1); + expect(mock.calls[0][0]).toBe('the-password'); + expect(model.toJS).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/__tests__/ui/input/password/__snapshots__/password_strength.test.jsx.snap b/src/__tests__/ui/input/password/__snapshots__/password_strength.test.jsx.snap new file mode 100644 index 000000000..b91e15352 --- /dev/null +++ b/src/__tests__/ui/input/password/__snapshots__/password_strength.test.jsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PasswordStrength validatePassword() validates password correctly with invalid password 1`] = `"
"`; diff --git a/src/__tests__/ui/input/password/password_strength.test.jsx b/src/__tests__/ui/input/password/password_strength.test.jsx new file mode 100644 index 000000000..d17c35295 --- /dev/null +++ b/src/__tests__/ui/input/password/password_strength.test.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import PasswordStrength from '../../../../ui/input/password/password_strength'; + +describe('PasswordStrength', () => { + beforeEach(() => { + jest.resetModules(); + }); + describe('validatePassword()', () => { + it(`validates password correctly with invalid password`, () => { + const policy = { + toJS: () => ({ + length: { + minLength: 20 + } + }) + }; + const messages = { foo: 'the-message' }; + const wrapper = mount( + + ); + expect(wrapper.html()).toMatchSnapshot(); + }); + }); +}); diff --git a/src/core/client/index.js b/src/core/client/index.js index b09d01190..f3308a4c6 100644 --- a/src/core/client/index.js +++ b/src/core/client/index.js @@ -1,4 +1,6 @@ import Immutable, { List, Map } from 'immutable'; +import passwordPolicies from 'auth0-password-policies'; + import { dataFns } from '../../utils/data_utils'; // TODO: this module should depend from social stuff import { STRATEGIES as SOCIAL_STRATEGIES } from '../../connection/social/index'; @@ -112,7 +114,13 @@ function formatClientConnection(connectionType, strategyName, connection) { }; if (connectionType === 'database') { - result.passwordPolicy = connection.passwordPolicy || 'none'; + result.passwordPolicy = passwordPolicies[connection.passwordPolicy || 'none']; + if ( + connection.password_complexity_options && + connection.password_complexity_options.min_length + ) { + result.passwordPolicy.length.minLength = connection.password_complexity_options.min_length; + } result.allowSignup = typeof connection.showSignup === 'boolean' ? connection.showSignup : true; result.allowForgot = typeof connection.showForgot === 'boolean' ? connection.showForgot : true; result.requireUsername = diff --git a/src/field/password.js b/src/field/password.js index 2c8b66a43..310c54c93 100644 --- a/src/field/password.js +++ b/src/field/password.js @@ -1,8 +1,8 @@ -import createPolicy from 'password-sheriff'; +import PasswordPolicy from 'password-sheriff/lib/policy'; import { setField } from './index'; export function validatePassword(password, policy) { - return createPolicy(policy).check(password); + return new PasswordPolicy(policy.toJS()).check(password); } export function setPassword(m, password, policy) { diff --git a/src/field/password/password_pane.jsx b/src/field/password/password_pane.jsx index 27c676fc2..57fa80e98 100644 --- a/src/field/password/password_pane.jsx +++ b/src/field/password/password_pane.jsx @@ -50,7 +50,7 @@ PasswordPane.propTypes = { lock: PropTypes.object.isRequired, onChange: PropTypes.func, placeholder: PropTypes.string.isRequired, - policy: PropTypes.string, + policy: PropTypes.object, strengthMessages: PropTypes.object, hidden: PropTypes.bool }; diff --git a/src/ui/input/password/password_strength.jsx b/src/ui/input/password/password_strength.jsx index 13343a402..30d03a945 100644 --- a/src/ui/input/password/password_strength.jsx +++ b/src/ui/input/password/password_strength.jsx @@ -1,12 +1,12 @@ import PropTypes from 'prop-types'; import React from 'react'; -import createPolicy from 'password-sheriff'; +import PasswordPolicy from 'password-sheriff/lib/policy'; import util from 'util'; export default class PasswordStrength extends React.Component { render() { const { password, policy, messages } = this.props; - const analysis = createPolicy(policy).missing(password); + const analysis = new PasswordPolicy(policy.toJS()).missing(password); // TODO: add a component for fadeIn / fadeOut animations? const className = 'auth0-lock-password-strength animated ' + (!analysis.verified ? 'fadeIn' : 'fadeOut'); @@ -39,7 +39,7 @@ export default class PasswordStrength extends React.Component { PasswordStrength.propTypes = { messages: PropTypes.object.isRequired, password: PropTypes.string.isRequired, - policy: PropTypes.oneOf(['none', 'low', 'fair', 'good', 'excellent']).isRequired + policy: PropTypes.object.isRequired }; PasswordStrength.defaultProps = { diff --git a/src/ui/input/password_input.jsx b/src/ui/input/password_input.jsx index dca074820..99d70dfbd 100644 --- a/src/ui/input/password_input.jsx +++ b/src/ui/input/password_input.jsx @@ -14,7 +14,7 @@ export default class PasswordInput extends React.Component { isValid: PropTypes.bool.isRequired, onChange: PropTypes.func.isRequired, placeholder: PropTypes.string, - policy: PropTypes.string, + policy: PropTypes.object, strengthMessages: PropTypes.object, value: PropTypes.string.isRequired, showPassword: PropTypes.bool.isRequired, diff --git a/yarn.lock b/yarn.lock index 2229e52f6..fdf1e9362 100644 --- a/yarn.lock +++ b/yarn.lock @@ -404,6 +404,12 @@ auth0-js@^9.7.3: url-join "^1.1.0" winchan "^0.2.0" +auth0-password-policies@auth0/auth0-password-policies#v1.0.1: + version "1.0.1" + resolved "https://codeload.github.com/auth0/auth0-password-policies/tar.gz/0b3da9b369b122a8b294d496143abfd33311e6b2" + dependencies: + password-sheriff "^1.1.0" + autoprefixer-stylus@^0.9.4: version "0.9.4" resolved "https://registry.yarnpkg.com/autoprefixer-stylus/-/autoprefixer-stylus-0.9.4.tgz#760914695dce321c9980b490d81a4385c08d914d" From 21e778d55bca058d0ce5c405c01b6d9b5e5ab5b3 Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Mon, 27 Aug 2018 16:03:58 -0300 Subject: [PATCH 2/3] Fix no policy --- src/__tests__/field/password.test.js | 6 +++++- src/field/password.js | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/__tests__/field/password.test.js b/src/__tests__/field/password.test.js index 1e6712977..56d95dc14 100644 --- a/src/__tests__/field/password.test.js +++ b/src/__tests__/field/password.test.js @@ -8,7 +8,11 @@ describe('field/password', () => { passwordField = require('field/password'); }); describe('validatePassword()', () => { - it(`validates password correctly`, () => { + it(`returns true when there is no policy`, () => { + const value = passwordField.validatePassword('the-password'); + expect(value).toBe(true); + }); + it(`validates password correctly when there is a policy`, () => { const model = { toJS: jest.fn() }; diff --git a/src/field/password.js b/src/field/password.js index 310c54c93..eba939918 100644 --- a/src/field/password.js +++ b/src/field/password.js @@ -2,6 +2,9 @@ import PasswordPolicy from 'password-sheriff/lib/policy'; import { setField } from './index'; export function validatePassword(password, policy) { + if (!policy) { + return true; + } return new PasswordPolicy(policy.toJS()).check(password); } From 1491c482a7c59a5d3155199fe9ec58b7f94ee3ad Mon Sep 17 00:00:00 2001 From: Luis Deschamps Rudge Date: Tue, 28 Aug 2018 14:30:04 -0300 Subject: [PATCH 3/3] Fix tests --- .../client/__snapshots__/index.test.js.snap | 61 +++++++++++++++ src/__tests__/core/client/index.test.js | 74 +++++++++---------- 2 files changed, 95 insertions(+), 40 deletions(-) create mode 100644 src/__tests__/core/client/__snapshots__/index.test.js.snap diff --git a/src/__tests__/core/client/__snapshots__/index.test.js.snap b/src/__tests__/core/client/__snapshots__/index.test.js.snap new file mode 100644 index 000000000..ebdaf60a0 --- /dev/null +++ b/src/__tests__/core/client/__snapshots__/index.test.js.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`core/client/index initClient loads password policy 'excellent' correctly with a password_complexity_options option 1`] = ` +Object { + "minLength": 4, +} +`; + +exports[`core/client/index initClient loads password policy 'excellent' correctly without a password_complexity_options option 1`] = ` +Object { + "minLength": 10, +} +`; + +exports[`core/client/index initClient loads password policy 'fair' correctly with a password_complexity_options option 1`] = ` +Object { + "minLength": 4, +} +`; + +exports[`core/client/index initClient loads password policy 'fair' correctly without a password_complexity_options option 1`] = ` +Object { + "minLength": 8, +} +`; + +exports[`core/client/index initClient loads password policy 'good' correctly with a password_complexity_options option 1`] = ` +Object { + "minLength": 4, +} +`; + +exports[`core/client/index initClient loads password policy 'good' correctly without a password_complexity_options option 1`] = ` +Object { + "minLength": 8, +} +`; + +exports[`core/client/index initClient loads password policy 'low' correctly with a password_complexity_options option 1`] = ` +Object { + "minLength": 4, +} +`; + +exports[`core/client/index initClient loads password policy 'low' correctly without a password_complexity_options option 1`] = ` +Object { + "minLength": 6, +} +`; + +exports[`core/client/index initClient loads password policy 'none' correctly with a password_complexity_options option 1`] = ` +Object { + "minLength": 4, +} +`; + +exports[`core/client/index initClient loads password policy 'none' correctly without a password_complexity_options option 1`] = ` +Object { + "minLength": 1, +} +`; diff --git a/src/__tests__/core/client/index.test.js b/src/__tests__/core/client/index.test.js index 3d1fe246c..e0d38a4bc 100644 --- a/src/__tests__/core/client/index.test.js +++ b/src/__tests__/core/client/index.test.js @@ -3,47 +3,41 @@ import { initClient } from '../../../core/client'; describe('core/client/index', () => { describe('initClient', () => { - it('loads password policy correctly without a password_complexity_options option', () => { - const client = { - strategies: [ - { - name: 'auth0', - connections: [ - { - name: 'Username-Password-Authentication', - passwordPolicy: 'low' - } - ] - } - ] - }; - const result = initClient(Immutable.fromJS({}), client).toJS(); - expect(result.client.connections.database[0].passwordPolicy).toMatchObject({ - length: { - minLength: 6 - } + ['none', 'low', 'fair', 'good', 'excellent'].forEach(policy => { + it(`loads password policy '${policy}' correctly without a password_complexity_options option`, () => { + const client = { + strategies: [ + { + name: 'auth0', + connections: [ + { + name: 'Username-Password-Authentication', + passwordPolicy: policy + } + ] + } + ] + }; + const result = initClient(Immutable.fromJS({}), client).toJS(); + expect(result.client.connections.database[0].passwordPolicy.length).toMatchSnapshot(); }); - }); - it('loads password policy correctly with a password_complexity_options option', () => { - const client = { - strategies: [ - { - name: 'auth0', - connections: [ - { - name: 'Username-Password-Authentication', - passwordPolicy: 'low', - password_complexity_options: { min_length: 4 } - } - ] - } - ] - }; - const result = initClient(Immutable.fromJS({}), client).toJS(); - expect(result.client.connections.database[0].passwordPolicy).toMatchObject({ - length: { - minLength: 4 - } + it(`loads password policy '${policy}' correctly with a password_complexity_options option`, () => { + const client = { + strategies: [ + { + name: 'auth0', + connections: [ + { + name: 'Username-Password-Authentication', + passwordPolicy: policy, + password_complexity_options: { min_length: 4 } + } + ] + } + ] + }; + const result = initClient(Immutable.fromJS({}), client).toJS(); + expect(result.client.connections.database[0].passwordPolicy.length).toMatchSnapshot(); }); }); });