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/__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
new file mode 100644
index 000000000..e0d38a4bc
--- /dev/null
+++ b/src/__tests__/core/client/index.test.js
@@ -0,0 +1,44 @@
+import Immutable from 'immutable';
+import { initClient } from '../../../core/client';
+
+describe('core/client/index', () => {
+ describe('initClient', () => {
+ ['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 '${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();
+ });
+ });
+ });
+});
diff --git a/src/__tests__/field/password.test.js b/src/__tests__/field/password.test.js
new file mode 100644
index 000000000..56d95dc14
--- /dev/null
+++ b/src/__tests__/field/password.test.js
@@ -0,0 +1,26 @@
+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(`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()
+ };
+ 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`] = `"
- At least 20 characters in length
"`;
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..eba939918 100644
--- a/src/field/password.js
+++ b/src/field/password.js
@@ -1,8 +1,11 @@
-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);
+ if (!policy) {
+ return true;
+ }
+ 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 5f2ea0e9a..3647a0929 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"