Skip to content

Add enforcementState and forceUpgradeOnSignin to PasswordPolicy type #7476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions common/api-review/auth.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,8 @@ export interface PasswordPolicy {
readonly containsNumericCharacter?: boolean;
readonly containsNonAlphanumericCharacter?: boolean;
};
readonly enforcementState: string;
readonly forceUpgradeOnSignin: boolean;
}

// @public
Expand Down
22 changes: 22 additions & 0 deletions docs-devsite/auth.passwordpolicy.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export interface PasswordPolicy
| --- | --- | --- |
| [allowedNonAlphanumericCharacters](./auth.passwordpolicy.md#passwordpolicyallowednonalphanumericcharacters) | string | List of characters that are considered non-alphanumeric during validation. |
| [customStrengthOptions](./auth.passwordpolicy.md#passwordpolicycustomstrengthoptions) | { readonly minPasswordLength?: number; readonly maxPasswordLength?: number; readonly containsLowercaseLetter?: boolean; readonly containsUppercaseLetter?: boolean; readonly containsNumericCharacter?: boolean; readonly containsNonAlphanumericCharacter?: boolean; } | Requirements enforced by this password policy. |
| [enforcementState](./auth.passwordpolicy.md#passwordpolicyenforcementstate) | string | The enforcement state of the policy. Can be 'OFF' or 'ENFORCE'. |
| [forceUpgradeOnSignin](./auth.passwordpolicy.md#passwordpolicyforceupgradeonsignin) | boolean | Whether existing passwords must meet the policy. |

## PasswordPolicy.allowedNonAlphanumericCharacters

Expand Down Expand Up @@ -51,3 +53,23 @@ readonly customStrengthOptions: {
readonly containsNonAlphanumericCharacter?: boolean;
};
```

## PasswordPolicy.enforcementState

The enforcement state of the policy. Can be 'OFF' or 'ENFORCE'.

<b>Signature:</b>

```typescript
readonly enforcementState: string;
```

## PasswordPolicy.forceUpgradeOnSignin

Whether existing passwords must meet the policy.

<b>Signature:</b>

```typescript
readonly forceUpgradeOnSignin: boolean;
```
2 changes: 2 additions & 0 deletions packages/auth/src/api/password_policy/get_password_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export interface GetPasswordPolicyResponse {
containsNonAlphanumericCharacter?: boolean;
};
allowedNonAlphanumericCharacters: string[];
enforcementState: string;
forceUpgradeOnSignin?: boolean;
schemaVersion: number;
}

Expand Down
16 changes: 14 additions & 2 deletions packages/auth/src/core/auth/auth_impl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,8 @@ describe('core/auth/auth_impl', () => {
const TEST_ALLOWED_NON_ALPHANUMERIC_STRING =
TEST_ALLOWED_NON_ALPHANUMERIC_CHARS.join('');
const TEST_MIN_PASSWORD_LENGTH = 6;
const TEST_ENFORCEMENT_STATE_ENFORCE = 'ENFORCE';
const TEST_FORCE_UPGRADE_ON_SIGN_IN = false;
const TEST_SCHEMA_VERSION = 1;
const TEST_UNSUPPORTED_SCHEMA_VERSION = 0;
const TEST_TENANT_ID = 'tenant-id';
Expand All @@ -805,6 +807,7 @@ describe('core/auth/auth_impl', () => {
minPasswordLength: TEST_MIN_PASSWORD_LENGTH
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS,
enforcementState: TEST_ENFORCEMENT_STATE_ENFORCE,
schemaVersion: TEST_SCHEMA_VERSION
};
const PASSWORD_POLICY_RESPONSE_REQUIRE_NUMERIC = {
Expand All @@ -813,6 +816,7 @@ describe('core/auth/auth_impl', () => {
containsNumericCharacter: true
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS,
enforcementState: TEST_ENFORCEMENT_STATE_ENFORCE,
schemaVersion: TEST_SCHEMA_VERSION
};
const PASSWORD_POLICY_RESPONSE_UNSUPPORTED_SCHEMA_VERSION = {
Expand All @@ -821,13 +825,17 @@ describe('core/auth/auth_impl', () => {
unsupportedPasswordPolicyProperty: 10
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS,
enforcementState: TEST_ENFORCEMENT_STATE_ENFORCE,
forceUpgradeOnSignin: TEST_FORCE_UPGRADE_ON_SIGN_IN,
schemaVersion: TEST_UNSUPPORTED_SCHEMA_VERSION
};
const CACHED_PASSWORD_POLICY = {
customStrengthOptions: {
minPasswordLength: TEST_MIN_PASSWORD_LENGTH
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_STRING,
enforcementState: TEST_ENFORCEMENT_STATE_ENFORCE,
forceUpgradeOnSignin: TEST_FORCE_UPGRADE_ON_SIGN_IN,
schemaVersion: TEST_SCHEMA_VERSION
};
const CACHED_PASSWORD_POLICY_REQUIRE_NUMERIC = {
Expand All @@ -836,13 +844,17 @@ describe('core/auth/auth_impl', () => {
containsNumericCharacter: true
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_STRING,
enforcementState: TEST_ENFORCEMENT_STATE_ENFORCE,
forceUpgradeOnSignin: TEST_FORCE_UPGRADE_ON_SIGN_IN,
schemaVersion: TEST_SCHEMA_VERSION
};
const PASSWORD_POLICY_UNSUPPORTED_SCHEMA_VERSION = {
const CACHED_PASSWORD_POLICY_UNSUPPORTED_SCHEMA_VERSION = {
customStrengthOptions: {
maxPasswordLength: TEST_MIN_PASSWORD_LENGTH
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_STRING,
enforcementState: TEST_ENFORCEMENT_STATE_ENFORCE,
forceUpgradeOnSignin: TEST_FORCE_UPGRADE_ON_SIGN_IN,
schemaVersion: TEST_UNSUPPORTED_SCHEMA_VERSION
};

Expand Down Expand Up @@ -915,7 +927,7 @@ describe('core/auth/auth_impl', () => {
await expect(auth._updatePasswordPolicy()).to.be.fulfilled;

expect(auth._getPasswordPolicyInternal()).to.eql(
PASSWORD_POLICY_UNSUPPORTED_SCHEMA_VERSION
CACHED_PASSWORD_POLICY_UNSUPPORTED_SCHEMA_VERSION
);
});

Expand Down
61 changes: 59 additions & 2 deletions packages/auth/src/core/auth/password_policy_impl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ describe('core/auth/password_policy_impl', () => {
const TEST_ALLOWED_NON_ALPHANUMERIC_CHARS = ['!', '(', ')', '@'];
const TEST_ALLOWED_NON_ALPHANUMERIC_STRING =
TEST_ALLOWED_NON_ALPHANUMERIC_CHARS.join('');
const TEST_ENFORCEMENT_STATE_ENFORCE = 'ENFORCE';
const TEST_ENFORCEMENT_STATE_OFF = 'OFF';
const TEST_REQUIRE_ALL_FORCE_UPGRADE_ON_SIGN_IN = true;
const TEST_REQUIRE_LENGTH_FORCE_UPGRADE_ON_SIGN_IN = false;
const TEST_SCHEMA_VERSION = 1;
const PASSWORD_POLICY_RESPONSE_REQUIRE_ALL: GetPasswordPolicyResponse = {
customStrengthOptions: {
Expand All @@ -50,6 +54,8 @@ describe('core/auth/password_policy_impl', () => {
containsNonAlphanumericCharacter: TEST_CONTAINS_NON_ALPHANUMERIC
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS,
enforcementState: TEST_ENFORCEMENT_STATE_ENFORCE,
forceUpgradeOnSignin: TEST_REQUIRE_ALL_FORCE_UPGRADE_ON_SIGN_IN,
schemaVersion: TEST_SCHEMA_VERSION
};
const PASSWORD_POLICY_RESPONSE_REQUIRE_LENGTH: GetPasswordPolicyResponse = {
Expand All @@ -58,6 +64,8 @@ describe('core/auth/password_policy_impl', () => {
maxPasswordLength: TEST_MAX_PASSWORD_LENGTH
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS,
enforcementState: TEST_ENFORCEMENT_STATE_OFF,
forceUpgradeOnSignin: TEST_REQUIRE_LENGTH_FORCE_UPGRADE_ON_SIGN_IN,
schemaVersion: TEST_SCHEMA_VERSION
};
const PASSWORD_POLICY_RESPONSE_REQUIRE_NUMERIC: GetPasswordPolicyResponse = {
Expand All @@ -67,8 +75,19 @@ describe('core/auth/password_policy_impl', () => {
containsNumericCharacter: TEST_CONTAINS_NUMERIC
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS,
enforcementState: TEST_ENFORCEMENT_STATE_ENFORCE,
schemaVersion: TEST_SCHEMA_VERSION
};
const PASSWORD_POLICY_RESPONSE_UNSPECIFIED_ENFORCEMENT_STATE: GetPasswordPolicyResponse =
{
customStrengthOptions: {
minPasswordLength: TEST_MIN_PASSWORD_LENGTH,
maxPasswordLength: TEST_MAX_PASSWORD_LENGTH
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS,
enforcementState: 'ENFORCEMENT_STATE_UNSPECIFIED',
schemaVersion: TEST_SCHEMA_VERSION
};
const PASSWORD_POLICY_REQUIRE_ALL: PasswordPolicy = {
customStrengthOptions: {
minPasswordLength: TEST_MIN_PASSWORD_LENGTH,
Expand All @@ -78,14 +97,18 @@ describe('core/auth/password_policy_impl', () => {
containsNumericCharacter: TEST_CONTAINS_NUMERIC,
containsNonAlphanumericCharacter: TEST_CONTAINS_UPPERCASE
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_STRING
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_STRING,
enforcementState: TEST_ENFORCEMENT_STATE_ENFORCE,
forceUpgradeOnSignin: TEST_REQUIRE_ALL_FORCE_UPGRADE_ON_SIGN_IN
};
const PASSWORD_POLICY_REQUIRE_LENGTH: PasswordPolicy = {
customStrengthOptions: {
minPasswordLength: TEST_MIN_PASSWORD_LENGTH,
maxPasswordLength: TEST_MAX_PASSWORD_LENGTH
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_STRING
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_STRING,
enforcementState: TEST_ENFORCEMENT_STATE_OFF,
forceUpgradeOnSignin: TEST_REQUIRE_LENGTH_FORCE_UPGRADE_ON_SIGN_IN
};
const TEST_EMPTY_PASSWORD = '';

Expand All @@ -100,6 +123,9 @@ describe('core/auth/password_policy_impl', () => {
expect(policy.allowedNonAlphanumericCharacters).to.eql(
PASSWORD_POLICY_REQUIRE_ALL.allowedNonAlphanumericCharacters
);
expect(policy.enforcementState).to.eql(
PASSWORD_POLICY_REQUIRE_ALL.enforcementState
);
expect(policy.schemaVersion).to.eql(
PASSWORD_POLICY_RESPONSE_REQUIRE_ALL.schemaVersion
);
Expand All @@ -115,6 +141,9 @@ describe('core/auth/password_policy_impl', () => {
expect(policy.allowedNonAlphanumericCharacters).to.eql(
PASSWORD_POLICY_REQUIRE_LENGTH.allowedNonAlphanumericCharacters
);
expect(policy.enforcementState).to.eql(
PASSWORD_POLICY_REQUIRE_LENGTH.enforcementState
);
expect(policy.schemaVersion).to.eql(
PASSWORD_POLICY_RESPONSE_REQUIRE_LENGTH.schemaVersion
);
Expand All @@ -129,6 +158,20 @@ describe('core/auth/password_policy_impl', () => {
.be.undefined;
});

it("assigns 'OFF' as the enforcement state when it is unspecified", () => {
const policy: PasswordPolicyInternal = new PasswordPolicyImpl(
PASSWORD_POLICY_RESPONSE_UNSPECIFIED_ENFORCEMENT_STATE
);
expect(policy.enforcementState).to.eql(TEST_ENFORCEMENT_STATE_OFF);
});

it('assigns false to forceUpgradeOnSignin when it is undefined in the response', () => {
const policy: PasswordPolicyInternal = new PasswordPolicyImpl(
PASSWORD_POLICY_RESPONSE_REQUIRE_NUMERIC
);
expect(policy.forceUpgradeOnSignin).to.be.false;
});

context('#validatePassword', () => {
const PASSWORD_POLICY_IMPL_REQUIRE_ALL = new PasswordPolicyImpl(
PASSWORD_POLICY_RESPONSE_REQUIRE_ALL
Expand Down Expand Up @@ -339,6 +382,20 @@ describe('core/auth/password_policy_impl', () => {
expect(status.containsUppercaseLetter).to.be.undefined;
expect(status.containsNonAlphanumericCharacter).to.be.undefined;
});

it("should consider a password invalid if it does not meet all requirements even if the enforcement state is 'OFF'", async () => {
const policy = PASSWORD_POLICY_IMPL_REQUIRE_NUMERIC;
const expectedValidationStatus: PasswordValidationStatus = {
isValid: false,
meetsMinPasswordLength: false,
meetsMaxPasswordLength: true,
containsNumericCharacter: true,
passwordPolicy: policy
};

const status = policy.validatePassword('p4ss');
expect(status).to.eql(expectedValidationStatus);
});
});
});
});
8 changes: 8 additions & 0 deletions packages/auth/src/core/auth/password_policy_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { PasswordValidationStatus } from '../../model/public_types';
export class PasswordPolicyImpl implements PasswordPolicyInternal {
readonly customStrengthOptions: PasswordPolicyCustomStrengthOptions;
readonly allowedNonAlphanumericCharacters: string;
readonly enforcementState: string;
readonly forceUpgradeOnSignin: boolean;
readonly schemaVersion: number;

constructor(response: GetPasswordPolicyResponse) {
Expand Down Expand Up @@ -62,8 +64,14 @@ export class PasswordPolicyImpl implements PasswordPolicyInternal {
responseOptions.containsNonAlphanumericCharacter;
}

this.enforcementState = response.enforcementState;
if (this.enforcementState === 'ENFORCEMENT_STATE_UNSPECIFIED') {
this.enforcementState = 'OFF';
}

this.allowedNonAlphanumericCharacters =
response.allowedNonAlphanumericCharacters.join('');
this.forceUpgradeOnSignin = response.forceUpgradeOnSignin ?? false;
this.schemaVersion = response.schemaVersion;
}

Expand Down
8 changes: 8 additions & 0 deletions packages/auth/src/core/strategies/email_and_password.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,8 @@ describe('password policy cache is updated in auth flows upon error', () => {
const TEST_ALLOWED_NON_ALPHANUMERIC_CHARS = ['!', '(', ')'];
const TEST_ALLOWED_NON_ALPHANUMERIC_STRING =
TEST_ALLOWED_NON_ALPHANUMERIC_CHARS.join('');
const TEST_ENFORCEMENT_STATE = 'ENFORCE';
const TEST_FORCE_UPGRADE_ON_SIGN_IN = false;
const TEST_SCHEMA_VERSION = 1;
const TEST_TENANT_ID = 'tenant-id';
const TEST_TENANT_ID_REQUIRE_NUMERIC = 'other-tenant-id';
Expand All @@ -812,6 +814,7 @@ describe('password policy cache is updated in auth flows upon error', () => {
minPasswordLength: TEST_MIN_PASSWORD_LENGTH
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS,
enforcementState: TEST_ENFORCEMENT_STATE,
schemaVersion: TEST_SCHEMA_VERSION
};
const PASSWORD_POLICY_RESPONSE_REQUIRE_NUMERIC = {
Expand All @@ -820,13 +823,16 @@ describe('password policy cache is updated in auth flows upon error', () => {
containsNumericCharacter: true
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_CHARS,
enforcementState: TEST_ENFORCEMENT_STATE,
schemaVersion: TEST_SCHEMA_VERSION
};
const CACHED_PASSWORD_POLICY = {
customStrengthOptions: {
minPasswordLength: TEST_MIN_PASSWORD_LENGTH
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_STRING,
enforcementState: TEST_ENFORCEMENT_STATE,
forceUpgradeOnSignin: TEST_FORCE_UPGRADE_ON_SIGN_IN,
schemaVersion: TEST_SCHEMA_VERSION
};
const CACHED_PASSWORD_POLICY_REQUIRE_NUMERIC = {
Expand All @@ -835,6 +841,8 @@ describe('password policy cache is updated in auth flows upon error', () => {
containsNumericCharacter: true
},
allowedNonAlphanumericCharacters: TEST_ALLOWED_NON_ALPHANUMERIC_STRING,
enforcementState: TEST_ENFORCEMENT_STATE,
forceUpgradeOnSignin: TEST_FORCE_UPGRADE_ON_SIGN_IN,
schemaVersion: TEST_SCHEMA_VERSION
};
let policyEndpointMock: mockFetch.Route;
Expand Down
8 changes: 8 additions & 0 deletions packages/auth/src/model/public_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,14 @@ export interface PasswordPolicy {
* List of characters that are considered non-alphanumeric during validation.
*/
readonly allowedNonAlphanumericCharacters: string;
/**
* The enforcement state of the policy. Can be 'OFF' or 'ENFORCE'.
*/
readonly enforcementState: string;
/**
* Whether existing passwords must meet the policy.
*/
readonly forceUpgradeOnSignin: boolean;
}

/**
Expand Down