auth0-password-policies 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,100 @@
1
+ name: Test and Publish
2
+
3
+
4
+ on:
5
+ push:
6
+ branches: [ master ]
7
+ tags: [ 'v*' ]
8
+ pull_request:
9
+ branches: [ master ]
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+
15
+ strategy:
16
+ matrix:
17
+ node-version: [16.x, 18.x, 20.x]
18
+
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # Aug 2025
22
+
23
+ - name: Setup Node.js ${{ matrix.node-version }}
24
+ uses: actions/setup-node@5e2628c959b9ade56971c0afcebbe5332d44b398 # Aug 2025
25
+ with:
26
+ node-version: ${{ matrix.node-version }}
27
+ cache: 'yarn'
28
+
29
+ - name: Install dependencies
30
+ run: yarn install
31
+
32
+ - name: Run tests
33
+ run: yarn test
34
+
35
+ publish:
36
+ runs-on: ubuntu-latest
37
+ needs: test
38
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
39
+ permissions:
40
+ contents: read
41
+ id-token: write
42
+ steps:
43
+ - name: Checkout code
44
+ uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # Aug 2025
45
+
46
+ - name: Verify tag is on master branch
47
+ shell: bash
48
+ run: |
49
+
50
+ git fetch origin master
51
+
52
+ # Check if the current commit (tag) is reachable from master
53
+ if ! git merge-base --is-ancestor HEAD origin/master; then
54
+ echo "Error: Tag ${{ github.ref_name }} is not on the master branch"
55
+ echo "Tags can only be published from commits that exist on master"
56
+ exit 1
57
+ fi
58
+ echo "✅ Tag ${{ github.ref_name }} is on master branch"
59
+
60
+ - name: Validate tag format
61
+ shell: bash
62
+ run: |
63
+ TAG=${GITHUB_REF#refs/tags/}
64
+ echo "Publishing tag: $TAG"
65
+ if [[ ! $TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-beta(\.[0-9]+)?)?$ ]]; then
66
+ echo "Invalid tag format. Expected: v1.2.3 or v1.2.3-beta or v1.2.3-beta.1"
67
+ exit 1
68
+ fi
69
+
70
+ - name: Setup Node
71
+ uses: actions/setup-node@5e2628c959b9ade56971c0afcebbe5332d44b398 # Aug 2025
72
+ with:
73
+ node-version: 20
74
+ cache: 'yarn'
75
+ registry-url: 'https://registry.npmjs.org'
76
+
77
+ - name: Install dependencies
78
+ shell: bash
79
+ run: yarn install --frozen-lockfile
80
+
81
+ - name: Determine npm tag
82
+ id: npm-tag
83
+ shell: bash
84
+ run: |
85
+ TAG="${GITHUB_REF#refs/tags/}"
86
+ if [[ "$TAG" == *"-beta"* ]]; then
87
+ echo "tag=beta" >> $GITHUB_OUTPUT
88
+ echo "Publishing as beta release"
89
+ else
90
+ echo "tag=latest" >> $GITHUB_OUTPUT
91
+ echo "Publishing as latest release"
92
+ fi
93
+
94
+ - name: Publish release to NPM
95
+ shell: bash
96
+ run: |
97
+ echo "About to run: npm publish --provenance --tag ${{ steps.npm-tag.outputs.tag }}"
98
+ npm publish --provenance --tag ${{ steps.npm-tag.outputs.tag }}
99
+ env:
100
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CODEOWNERS ADDED
@@ -0,0 +1,2 @@
1
+ # Code owners are automatically requested for review when someone opens a pull request that modifies code that they own.
2
+ * @auth0/project-iam-user-management-engineer-codeowner
@@ -0,0 +1,39 @@
1
+
2
+ # Contributing to Auth0 projects
3
+
4
+ A big welcome and thank you for considering contributing to the Auth0 projects. It’s people like you that make it a reality for users in our community.
5
+
6
+ Reading and following these guidelines will help us make the contribution process easy and effective for everyone involved. It also communicates that you agree to respect the time of the developers managing and developing these open source projects. In return, we will reciprocate that respect by addressing your issue.
7
+
8
+ ### Quicklinks
9
+
10
+ * [Code of Conduct](#code-of-conduct)
11
+ * [Getting Started](#getting-started)
12
+ * [Making Changes](#making-changes)
13
+ * [Getting in Touch](#getting-in-touch)
14
+ * [Got a question or problem?](#got-a-question-or-problem?)
15
+ * [Vulnerability Reporting](#vulnerability-reporting)
16
+
17
+ ## Code of Conduct
18
+
19
+ By participating and contributing to this project, you are expected to uphold our [Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md).
20
+
21
+ ## Getting Started
22
+
23
+ ### Making Changes
24
+
25
+ To contribute to a repository, the first step is to open a support ticket
26
+
27
+ [Opening a ticket] (https://auth0.com/docs/troubleshoot/customer-support/open-and-manage-support-tickets).
28
+
29
+ ## Getting in touch
30
+
31
+ ### Have a question or problem?
32
+
33
+ Please do not open issues for general support or usage questions. Instead, join us over in the Auth0 community at [community.auth0.com](https://community.auth0.com) and post your question there in the correct category with a descriptive tag.
34
+
35
+ ### Vulnerability Reporting
36
+
37
+ Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues.
38
+
39
+ # More Information about this service is located in README.md at the root of this repository
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Auth0, Inc. <support@auth0.com> (http://auth0.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -23,3 +23,65 @@ Password policies presets used by Auth0. Extracted from [password-sheriff](https
23
23
  * minimum characters: 10
24
24
  * contains at least one character in three different groups out of: lowerCase, upperCase, numbers, specialCharacters
25
25
  * may not contain any character repeated more than twice
26
+
27
+ ## Helpers
28
+
29
+ ### createRulesFromOptions
30
+ Converts an Auth0 `connection.options.password_options.complexity` object into a `password-sheriff` compatible rules object, and applies default values.
31
+
32
+ Usage:
33
+ ```js
34
+ const { PasswordPolicy } = require('password-sheriff');
35
+ const { createRulesFromOptions } = require('auth0-password-policies');
36
+
37
+ const passwordOptions = {
38
+ character_types: ["uppercase","lowercase","number","special"],
39
+ "require_3of4_character_types": true,
40
+ identical_characters: "disallow"
41
+ };
42
+
43
+ const rules = createRulesFromOptions(passwordOptions);
44
+ const customPolicy = new PasswordPolicy(rules);
45
+ console.log(customPolicy.toString());
46
+ /**
47
+ * Output is:
48
+ * * At least 15 characters in length
49
+ * * At least 3 of the following 4 types of characters:
50
+ * * lower case letters (a-z)
51
+ * * upper case letters (A-Z)
52
+ * * numbers (i.e. 0-9)
53
+ * * special characters (e.g. !@#$%^&*)
54
+ * * No more than 2 identical characters in a row (e.g., "aaa" not allowed)
55
+ */
56
+ ```
57
+
58
+ ## Publishing
59
+
60
+ This package is automatically published to npm when:
61
+
62
+ 1. **Tests pass**: All tests must pass across Node.js versions 16, 18, and 20
63
+ 2. **Version tag is pushed**: Create and push a git tag following semantic versioning
64
+
65
+ ### Publishing Steps
66
+
67
+ 1. Update the version in `package.json`:
68
+ ```bash
69
+ npm version patch # For bug fixes (1.0.0 -> 1.0.1)
70
+ npm version minor # For new features (1.0.0 -> 1.1.0)
71
+ npm version major # For breaking changes (1.0.0 -> 2.0.0)
72
+ npm version prerelease --preid=beta # For beta releases (1.0.0 -> 1.0.1-beta.0)
73
+ ```
74
+
75
+ 2. Push the tag to trigger publishing:
76
+ ```bash
77
+ git push origin master --tags
78
+ ```
79
+
80
+ The workflow will:
81
+ - Run tests across multiple Node.js versions
82
+ - Only publish if all tests pass (using `needs: test`)
83
+ - Publish with the appropriate npm tag (`latest` for stable, `beta` for pre-releases)
84
+ - Use provenance for enhanced security
85
+
86
+ ### Tag Format
87
+ Tags must follow the format: `v1.2.3` or `v1.2.3-beta` or `v1.2.3-beta.1`
package/index.js CHANGED
@@ -1,38 +1,147 @@
1
- var charsets = require('password-sheriff').charsets;
1
+ const charsets = require("password-sheriff").charsets;
2
2
 
3
- var upperCase = charsets.upperCase;
4
- var lowerCase = charsets.lowerCase;
5
- var numbers = charsets.numbers;
6
- var specialCharacters = charsets.specialCharacters;
3
+ const upperCase = charsets.upperCase;
4
+ const lowerCase = charsets.lowerCase;
5
+ const numbers = charsets.numbers;
6
+ const specialCharacters = charsets.specialCharacters;
7
7
 
8
- var policies = {
8
+ const policies = {
9
9
  none: {
10
- length: { minLength: 1 }
10
+ length: { minLength: 1 },
11
11
  },
12
12
  low: {
13
- length: { minLength: 6 }
13
+ length: { minLength: 6 },
14
14
  },
15
15
  fair: {
16
16
  length: { minLength: 8 },
17
17
  contains: {
18
- expressions: [lowerCase, upperCase, numbers]
19
- }
18
+ expressions: [lowerCase, upperCase, numbers],
19
+ },
20
20
  },
21
21
  good: {
22
22
  length: { minLength: 8 },
23
23
  containsAtLeast: {
24
24
  atLeast: 3,
25
- expressions: [lowerCase, upperCase, numbers, specialCharacters]
26
- }
25
+ expressions: [lowerCase, upperCase, numbers, specialCharacters],
26
+ },
27
27
  },
28
28
  excellent: {
29
29
  length: { minLength: 10 },
30
30
  containsAtLeast: {
31
31
  atLeast: 3,
32
- expressions: [lowerCase, upperCase, numbers, specialCharacters]
32
+ expressions: [lowerCase, upperCase, numbers, specialCharacters],
33
33
  },
34
- identicalChars: { max: 2 }
35
- }
34
+ identicalChars: { max: 2 },
35
+ },
36
+ };
37
+
38
+ const CHARACTER_TYPES = {
39
+ LOWERCASE: "lowercase",
40
+ UPPERCASE: "uppercase",
41
+ NUMBER: "number",
42
+ SPECIAL: "special",
36
43
  };
37
44
 
45
+ /**
46
+ * @typedef {Object} PasswordOptions
47
+ * @property {number} [min_length=15] - Minimum password length (1-72)
48
+ * @property {Array<'uppercase'|'lowercase'|'number'|'special'>} [character_types=[]] - Required character types
49
+ * @property {boolean} [require_3of4_character_types=false] - Whether to require 3 out of 4 character types (requires all 4 types to be specified)
50
+ * @property {'allow'|'disallow'} [identical_characters='disallow'] - Whether to allow >2 identical consecutive characters
51
+ */
52
+
53
+ /**
54
+ * Default values for password options
55
+ * @constant
56
+ * @type {PasswordOptions}
57
+ */
58
+ const DEFAULT_PASSWORD_OPTIONS = {
59
+ min_length: 15,
60
+ character_types: [],
61
+ require_3of4_character_types: false,
62
+ identical_characters: "disallow",
63
+ };
64
+
65
+ /**
66
+ * Creates a PasswordPolicy rules configuration from an Auth0
67
+ * `connection.options.password_options.complexity` object.
68
+ *
69
+ * @param {PasswordOptions} options - Auth0 password_options.complexity object
70
+ * @returns {Object} password-sheriff rules configuration object that can be passed to PasswordPolicy constructor
71
+ */
72
+ function createRulesFromOptions(options = {}) {
73
+ const rules = {};
74
+
75
+ // Apply defaults
76
+ const {
77
+ min_length: minLength,
78
+ character_types: requiredTypes,
79
+ require_3of4_character_types: require3of4,
80
+ identical_characters: identicalChars,
81
+ } = { ...DEFAULT_PASSWORD_OPTIONS, ...options };
82
+
83
+ // Validate min_length is within acceptable range
84
+ if (minLength < 1 || minLength > 72) {
85
+ throw new Error("min_length must be between 1 and 72");
86
+ }
87
+
88
+ // Handle min_length
89
+ rules.length = { minLength: minLength };
90
+
91
+ // Validate require_3of4_character_types prerequisite
92
+ if (require3of4) {
93
+ const hasAllFourTypes = Object.values(CHARACTER_TYPES).every(function (
94
+ type
95
+ ) {
96
+ return requiredTypes.includes(type);
97
+ });
98
+
99
+ if (!hasAllFourTypes) {
100
+ throw new Error(
101
+ `require_3of4_character_types can only be used when all four character types (${Object.values(
102
+ CHARACTER_TYPES
103
+ ).join(", ")}) are selected`
104
+ );
105
+ }
106
+ }
107
+
108
+ if (requiredTypes.length > 0 || require3of4) {
109
+ const expressions = [];
110
+
111
+ if (require3of4) {
112
+ // Use containsAtLeast with 3 out of 4
113
+ rules.containsAtLeast = {
114
+ atLeast: 3,
115
+ expressions: [lowerCase, upperCase, numbers, specialCharacters],
116
+ };
117
+ } else {
118
+ // Map character types to expressions
119
+ if (requiredTypes.includes(CHARACTER_TYPES.LOWERCASE)) {
120
+ expressions.push(lowerCase);
121
+ }
122
+ if (requiredTypes.includes(CHARACTER_TYPES.UPPERCASE)) {
123
+ expressions.push(upperCase);
124
+ }
125
+ if (requiredTypes.includes(CHARACTER_TYPES.NUMBER)) {
126
+ expressions.push(numbers);
127
+ }
128
+ if (requiredTypes.includes(CHARACTER_TYPES.SPECIAL)) {
129
+ expressions.push(specialCharacters);
130
+ }
131
+
132
+ rules.contains = {
133
+ expressions: expressions,
134
+ };
135
+ }
136
+ }
137
+
138
+ if (identicalChars === "disallow") {
139
+ rules.identicalChars = { max: 2 };
140
+ }
141
+
142
+ return rules;
143
+ }
144
+
38
145
  module.exports = policies;
146
+
147
+ module.exports.createRulesFromOptions = createRulesFromOptions;
@@ -0,0 +1,5 @@
1
+ {
2
+ "testEnvironment": "node",
3
+ "testMatch": ["<rootDir>/test/**/*.js"],
4
+ "verbose": true
5
+ }
package/opslevel.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ version: 1
3
+ repository:
4
+ owner: iam_user_management
5
+ tier:
6
+ tags:
package/package.json CHANGED
@@ -1,10 +1,16 @@
1
1
  {
2
2
  "name": "auth0-password-policies",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "main": "index.js",
5
5
  "repository": "git@github.com:auth0/auth0-password-policies.git",
6
6
  "author": "Shaun Starsprung <shaun.starsprung@auth0.com>",
7
- "license": "MIT",
7
+ "license": "UNLICENSED",
8
+ "scripts": {
9
+ "test": "jest"
10
+ },
11
+ "devDependencies": {
12
+ "jest": "^29.0.0"
13
+ },
8
14
  "dependencies": {
9
15
  "password-sheriff": "^1.1.0"
10
16
  }
package/test/test.js ADDED
@@ -0,0 +1,173 @@
1
+ const policies = require("..");
2
+ const { createRulesFromOptions } = policies;
3
+
4
+ describe("password policies", function () {
5
+ describe("main export", function () {
6
+ it("should export all props", function () {
7
+ expect(Object.keys(policies)).toEqual(
8
+ expect.arrayContaining([
9
+ "none",
10
+ "low",
11
+ "fair",
12
+ "good",
13
+ "excellent",
14
+ "createRulesFromOptions",
15
+ ])
16
+ );
17
+ });
18
+ });
19
+
20
+ describe("createRulesFromOptions helper", function () {
21
+ describe("min_length", function () {
22
+ it("should test min_length from 1 to 72", function () {
23
+ const auth0Config1 = {
24
+ min_length: 1,
25
+ identical_characters: "allow",
26
+ };
27
+ const rules = createRulesFromOptions(auth0Config1);
28
+ expect(rules).toEqual({
29
+ length: {
30
+ minLength: 1,
31
+ },
32
+ });
33
+
34
+ const auth0Config72 = {
35
+ min_length: 72,
36
+ identical_characters: "allow",
37
+ };
38
+ const rules72 = createRulesFromOptions(auth0Config72);
39
+ expect(rules72).toEqual({
40
+ length: {
41
+ minLength: 72,
42
+ },
43
+ });
44
+ });
45
+
46
+ it("should throw an error when min_length > 72", function () {
47
+ expect(function () {
48
+ const auth0Config = {
49
+ min_length: 73,
50
+ };
51
+ createRulesFromOptions(auth0Config);
52
+ }).toThrow("min_length must be between 1 and 72");
53
+ });
54
+ });
55
+
56
+ describe("character_types", function () {
57
+ it("should enforce required character types", function () {
58
+ const auth0Config = {
59
+ character_types: ["lowercase", "uppercase", "number", "special"],
60
+ identical_characters: "allow",
61
+ min_length: 4,
62
+ };
63
+ const rules = createRulesFromOptions(auth0Config);
64
+ expect(rules).toHaveProperty("length");
65
+ expect(rules.length).toEqual({ minLength: 4 });
66
+ expect(rules).toHaveProperty("contains");
67
+ expect(rules.contains).toHaveProperty("expressions");
68
+ expect(rules.contains.expressions).toHaveLength(4);
69
+ // Verify each expression has test and explain functions
70
+ rules.contains.expressions.forEach(function (expr) {
71
+ expect(expr).toHaveProperty("test");
72
+ expect(expr).toHaveProperty("explain");
73
+ expect(typeof expr.test).toBe("function");
74
+ expect(typeof expr.explain).toBe("function");
75
+ });
76
+ });
77
+ });
78
+
79
+ describe("require_3of4_character_types", function () {
80
+ it("should enforce 3 out of 4 character types when all 4 types are specified", function () {
81
+ const auth0Config = {
82
+ character_types: ["lowercase", "uppercase", "number", "special"],
83
+ require_3of4_character_types: true,
84
+ identical_characters: "allow",
85
+ min_length: 3,
86
+ };
87
+ const rules = createRulesFromOptions(auth0Config);
88
+ expect(rules).toHaveProperty("length");
89
+ expect(rules.length).toEqual({ minLength: 3 });
90
+ expect(rules).toHaveProperty("containsAtLeast");
91
+ expect(rules.containsAtLeast).toHaveProperty("atLeast", 3);
92
+ expect(rules.containsAtLeast).toHaveProperty("expressions");
93
+ expect(rules.containsAtLeast.expressions).toHaveLength(4);
94
+ // Verify each expression has test and explain functions
95
+ rules.containsAtLeast.expressions.forEach(function (expr) {
96
+ expect(expr).toHaveProperty("test");
97
+ expect(expr).toHaveProperty("explain");
98
+ expect(typeof expr.test).toBe("function");
99
+ expect(typeof expr.explain).toBe("function");
100
+ });
101
+ });
102
+
103
+ it("should throw an error when require_3of4_character_types is used without all 4 character types", function () {
104
+ expect(function () {
105
+ const auth0Config = {
106
+ character_types: ["lowercase", "uppercase"],
107
+ require_3of4_character_types: true,
108
+ };
109
+ createRulesFromOptions(auth0Config);
110
+ }).toThrow(
111
+ "require_3of4_character_types can only be used when all four character types (lowercase, uppercase, number, special) are selected"
112
+ );
113
+ });
114
+ });
115
+
116
+ describe("identical_characters", function () {
117
+ it("should disallow more than 2 identical characters when specified", function () {
118
+ const auth0Config = {
119
+ identical_characters: "disallow",
120
+ };
121
+ const rules = createRulesFromOptions(auth0Config);
122
+ expect(rules).toEqual({
123
+ length: {
124
+ minLength: 15,
125
+ },
126
+ identicalChars: {
127
+ max: 2,
128
+ },
129
+ });
130
+ });
131
+
132
+ it("should allow more than 2 identical characters when specified", function () {
133
+ const auth0Config = {
134
+ identical_characters: "allow",
135
+ };
136
+ const rules = createRulesFromOptions(auth0Config);
137
+ expect(rules).toEqual({
138
+ length: {
139
+ minLength: 15,
140
+ },
141
+ });
142
+ });
143
+ });
144
+
145
+ describe("default values", function () {
146
+ it("should apply default values when not specified", function () {
147
+ const auth0Config = {};
148
+ const rules = createRulesFromOptions(auth0Config);
149
+ expect(rules).toEqual({
150
+ length: {
151
+ minLength: 15,
152
+ },
153
+ identicalChars: {
154
+ max: 2,
155
+ },
156
+ });
157
+ });
158
+
159
+ it("should allow overriding default values", function () {
160
+ const auth0Config = {
161
+ min_length: 5,
162
+ identical_characters: "allow",
163
+ };
164
+ const rules = createRulesFromOptions(auth0Config);
165
+ expect(rules).toEqual({
166
+ length: {
167
+ minLength: 5,
168
+ },
169
+ });
170
+ });
171
+ });
172
+ });
173
+ });
package/yarn.lock DELETED
@@ -1,7 +0,0 @@
1
- # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
- # yarn lockfile v1
3
-
4
-
5
- password-sheriff@^1.1.0:
6
- version "1.1.0"
7
- resolved "https://registry.yarnpkg.com/password-sheriff/-/password-sheriff-1.1.0.tgz#fdb3c3d845a0a3c92de422b2ad9346ce08a71413"