Skip to content

Commit

Permalink
Add support for validating passwords against the password policy in a…
Browse files Browse the repository at this point in the history
…uth (#7514)

* Implement validatePassword endpoint for public API with PasswordPolicy and PasswordValidationStatus public types

* Update auth demo to include a section for password validation
  • Loading branch information
ch5zzy committed Aug 7, 2023
1 parent 0038e11 commit c9e2b0b
Show file tree
Hide file tree
Showing 30 changed files with 2,864 additions and 105 deletions.
6 changes: 6 additions & 0 deletions .changeset/thick-lions-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@firebase/auth': minor
'firebase': minor
---

Add a validatePassword method for validating passwords against the password policy configured for the project or a tenant. This method returns a status object that can be used to display the requirements of the password policy and whether each one was met.
30 changes: 30 additions & 0 deletions common/api-review/auth.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,33 @@ export interface ParsedToken {
'sub'?: string;
}

// @public
export interface PasswordPolicy {
readonly allowedNonAlphanumericCharacters: string;
readonly customStrengthOptions: {
readonly minPasswordLength?: number;
readonly maxPasswordLength?: number;
readonly containsLowercaseLetter?: boolean;
readonly containsUppercaseLetter?: boolean;
readonly containsNumericCharacter?: boolean;
readonly containsNonAlphanumericCharacter?: boolean;
};
readonly enforcementState: string;
readonly forceUpgradeOnSignin: boolean;
}

// @public
export interface PasswordValidationStatus {
readonly containsLowercaseLetter?: boolean;
readonly containsNonAlphanumericCharacter?: boolean;
readonly containsNumericCharacter?: boolean;
readonly containsUppercaseLetter?: boolean;
readonly isValid: boolean;
readonly meetsMaxPasswordLength?: boolean;
readonly meetsMinPasswordLength?: boolean;
readonly passwordPolicy: PasswordPolicy;
}

// @public
export interface Persistence {
readonly type: 'SESSION' | 'LOCAL' | 'NONE';
Expand Down Expand Up @@ -869,6 +896,9 @@ export interface UserMetadata {
// @public
export type UserProfile = Record<string, unknown>;

// @public
export function validatePassword(auth: Auth, password: string): Promise<PasswordValidationStatus>;

// @public
export function verifyBeforeUpdateEmail(user: User, newEmail: string, actionCodeSettings?: ActionCodeSettings | null): Promise<void>;

Expand Down
36 changes: 36 additions & 0 deletions docs-devsite/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Firebase Authentication
| [signOut(auth)](./auth.md#signout) | Signs out the current user. |
| [updateCurrentUser(auth, user)](./auth.md#updatecurrentuser) | Asynchronously sets the provided user as [Auth.currentUser](./auth.auth.md#authcurrentuser) on the [Auth](./auth.auth.md#auth_interface) instance. |
| [useDeviceLanguage(auth)](./auth.md#usedevicelanguage) | Sets the current language to the default device/browser preference. |
| [validatePassword(auth, password)](./auth.md#validatepassword) | Validates the password against the password policy configured for the project or tenant. |
| [verifyPasswordResetCode(auth, code)](./auth.md#verifypasswordresetcode) | Checks a password reset code sent to the user by email or other out-of-band mechanism. |
| <b>function(link...)</b> |
| [parseActionCodeURL(link)](./auth.md#parseactioncodeurl) | Parses the email action link string and returns an [ActionCodeURL](./auth.actioncodeurl.md#actioncodeurl_class) if the link is valid, otherwise returns null. |
Expand Down Expand Up @@ -124,6 +125,8 @@ Firebase Authentication
| [MultiFactorUser](./auth.multifactoruser.md#multifactoruser_interface) | An interface that defines the multi-factor related properties and operations pertaining to a [User](./auth.user.md#user_interface)<!-- -->. |
| [OAuthCredentialOptions](./auth.oauthcredentialoptions.md#oauthcredentialoptions_interface) | Defines the options for initializing an [OAuthCredential](./auth.oauthcredential.md#oauthcredential_class)<!-- -->. |
| [ParsedToken](./auth.parsedtoken.md#parsedtoken_interface) | Interface representing a parsed ID token. |
| [PasswordPolicy](./auth.passwordpolicy.md#passwordpolicy_interface) | A structure specifying password policy requirements. |
| [PasswordValidationStatus](./auth.passwordvalidationstatus.md#passwordvalidationstatus_interface) | A structure indicating which password policy requirements were met or violated and what the requirements are. |
| [Persistence](./auth.persistence.md#persistence_interface) | An interface covering the possible persistence mechanism types. |
| [PhoneMultiFactorAssertion](./auth.phonemultifactorassertion.md#phonemultifactorassertion_interface) | The class for asserting ownership of a phone second factor. Provided by [PhoneMultiFactorGenerator.assertion()](./auth.phonemultifactorgenerator.md#phonemultifactorgeneratorassertion)<!-- -->. |
| [PhoneMultiFactorEnrollInfoOptions](./auth.phonemultifactorenrollinfooptions.md#phonemultifactorenrollinfooptions_interface) | Options used for enrolling a second factor. |
Expand Down Expand Up @@ -1077,6 +1080,39 @@ export declare function useDeviceLanguage(auth: Auth): void;

void

## validatePassword()

Validates the password against the password policy configured for the project or tenant.

If no tenant ID is set on the `Auth` instance, then this method will use the password policy configured for the project. Otherwise, this method will use the policy configured for the tenant. If a password policy has not been configured, then the default policy configured for all projects will be used.

If an auth flow fails because a submitted password does not meet the password policy requirements and this method has previously been called, then this method will use the most recent policy available when called again.

<b>Signature:</b>

```typescript
export declare function validatePassword(auth: Auth, password: string): Promise<PasswordValidationStatus>;
```

### Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| auth | [Auth](./auth.auth.md#auth_interface) | The [Auth](./auth.auth.md#auth_interface) instance. |
| password | string | The password to validate. |

<b>Returns:</b>

Promise&lt;[PasswordValidationStatus](./auth.passwordvalidationstatus.md#passwordvalidationstatus_interface)<!-- -->&gt;

### Example


```javascript
validatePassword(auth, 'some-password');

```

## verifyPasswordResetCode()

Checks a password reset code sent to the user by email or other out-of-band mechanism.
Expand Down
75 changes: 75 additions & 0 deletions docs-devsite/auth.passwordpolicy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
Project: /docs/reference/js/_project.yaml
Book: /docs/reference/_book.yaml
page_type: reference

{% comment %}
DO NOT EDIT THIS FILE!
This is generated by the JS SDK team, and any local changes will be
overwritten. Changes should be made in the source code at
https://github.com/firebase/firebase-js-sdk
{% endcomment %}

# PasswordPolicy interface
A structure specifying password policy requirements.

<b>Signature:</b>

```typescript
export interface PasswordPolicy
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [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

List of characters that are considered non-alphanumeric during validation.

<b>Signature:</b>

```typescript
readonly allowedNonAlphanumericCharacters: string;
```

## PasswordPolicy.customStrengthOptions

Requirements enforced by this password policy.

<b>Signature:</b>

```typescript
readonly customStrengthOptions: {
readonly minPasswordLength?: number;
readonly maxPasswordLength?: number;
readonly containsLowercaseLetter?: boolean;
readonly containsUppercaseLetter?: boolean;
readonly containsNumericCharacter?: boolean;
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;
```
112 changes: 112 additions & 0 deletions docs-devsite/auth.passwordvalidationstatus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
Project: /docs/reference/js/_project.yaml
Book: /docs/reference/_book.yaml
page_type: reference

{% comment %}
DO NOT EDIT THIS FILE!
This is generated by the JS SDK team, and any local changes will be
overwritten. Changes should be made in the source code at
https://github.com/firebase/firebase-js-sdk
{% endcomment %}

# PasswordValidationStatus interface
A structure indicating which password policy requirements were met or violated and what the requirements are.

<b>Signature:</b>

```typescript
export interface PasswordValidationStatus
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [containsLowercaseLetter](./auth.passwordvalidationstatus.md#passwordvalidationstatuscontainslowercaseletter) | boolean | Whether the password contains a lowercase letter, or undefined if not required. |
| [containsNonAlphanumericCharacter](./auth.passwordvalidationstatus.md#passwordvalidationstatuscontainsnonalphanumericcharacter) | boolean | Whether the password contains a non-alphanumeric character, or undefined if not required. |
| [containsNumericCharacter](./auth.passwordvalidationstatus.md#passwordvalidationstatuscontainsnumericcharacter) | boolean | Whether the password contains a numeric character, or undefined if not required. |
| [containsUppercaseLetter](./auth.passwordvalidationstatus.md#passwordvalidationstatuscontainsuppercaseletter) | boolean | Whether the password contains an uppercase letter, or undefined if not required. |
| [isValid](./auth.passwordvalidationstatus.md#passwordvalidationstatusisvalid) | boolean | Whether the password meets all requirements. |
| [meetsMaxPasswordLength](./auth.passwordvalidationstatus.md#passwordvalidationstatusmeetsmaxpasswordlength) | boolean | Whether the password meets the maximum password length, or undefined if not required. |
| [meetsMinPasswordLength](./auth.passwordvalidationstatus.md#passwordvalidationstatusmeetsminpasswordlength) | boolean | Whether the password meets the minimum password length, or undefined if not required. |
| [passwordPolicy](./auth.passwordvalidationstatus.md#passwordvalidationstatuspasswordpolicy) | [PasswordPolicy](./auth.passwordpolicy.md#passwordpolicy_interface) | The policy used to validate the password. |

## PasswordValidationStatus.containsLowercaseLetter

Whether the password contains a lowercase letter, or undefined if not required.

<b>Signature:</b>

```typescript
readonly containsLowercaseLetter?: boolean;
```

## PasswordValidationStatus.containsNonAlphanumericCharacter

Whether the password contains a non-alphanumeric character, or undefined if not required.

<b>Signature:</b>

```typescript
readonly containsNonAlphanumericCharacter?: boolean;
```

## PasswordValidationStatus.containsNumericCharacter

Whether the password contains a numeric character, or undefined if not required.

<b>Signature:</b>

```typescript
readonly containsNumericCharacter?: boolean;
```

## PasswordValidationStatus.containsUppercaseLetter

Whether the password contains an uppercase letter, or undefined if not required.

<b>Signature:</b>

```typescript
readonly containsUppercaseLetter?: boolean;
```

## PasswordValidationStatus.isValid

Whether the password meets all requirements.

<b>Signature:</b>

```typescript
readonly isValid: boolean;
```

## PasswordValidationStatus.meetsMaxPasswordLength

Whether the password meets the maximum password length, or undefined if not required.

<b>Signature:</b>

```typescript
readonly meetsMaxPasswordLength?: boolean;
```

## PasswordValidationStatus.meetsMinPasswordLength

Whether the password meets the minimum password length, or undefined if not required.

<b>Signature:</b>

```typescript
readonly meetsMinPasswordLength?: boolean;
```

## PasswordValidationStatus.passwordPolicy

The policy used to validate the password.

<b>Signature:</b>

```typescript
readonly passwordPolicy: PasswordPolicy;
```
29 changes: 28 additions & 1 deletion packages/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ firebase emulators:exec --project foo-bar --only auth "yarn test:integration:loc

### Integration testing with the production backend

Currently, MFA TOTP tests only run against the production backend (since they are not supported on the emulator yet).
Currently, MFA TOTP and password policy tests only run against the production backend (since they are not supported on the emulator yet).
Running against the backend also makes it a more reliable end-to-end test.

The TOTP tests require the following email/password combination to exist in the project, so if you are running this test against your test project, please create this user:
Expand All @@ -71,6 +71,33 @@ curl -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Conten
}'
```

The password policy tests require a tenant configured with a password policy that requires all options to exist in the project.

If you are running this test against your test project, please create the tenant and configure the policy with the following curl command:

```
curl -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json" -H "X-Goog-User-Project: ${PROJECT_ID}" -X POST https://identitytoolkit.googleapis.com/v2/projects/${PROJECT_ID}/tenants -d '{
"displayName": "passpol-tenant",
"passwordPolicyConfig": {
"passwordPolicyEnforcementState": "ENFORCE",
"passwordPolicyVersions": [
{
"customStrengthOptions": {
"minPasswordLength": 8,
"maxPasswordLength": 24,
"containsLowercaseCharacter": true,
"containsUppercaseCharacter": true,
"containsNumericCharacter": true,
"containsNonAlphanumericCharacter": true
}
}
]
}
}'
```

Replace the tenant ID `passpol-tenant-d7hha` in [test/integration/flows/password_policy.test.ts](https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/test/integration/flows/password_policy.test.ts) with the ID for the newly created tenant. The tenant ID can be found at the end of the `name` property in the response and is in the format `passpol-tenant-xxxxx`.

### Selenium Webdriver tests

These tests assume that you have both Firefox and Chrome installed on your
Expand Down

0 comments on commit c9e2b0b

Please sign in to comment.