diff --git a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.html b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.html index 63d6671afe..0e8b86de0f 100644 --- a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.html @@ -6,59 +6,70 @@
- {{ "defaultType" | i18n }} - - + {{ "overridePasswordTypePolicy" | i18n }} + +
-

{{ "password" | i18n }}

-
- - {{ "minLength" | i18n }} - - + +
+

{{ "password" | i18n }}

+
+ + {{ "minLength" | i18n }} + + +
+
+ + {{ "minNumbers" | i18n }} + + + + {{ "minSpecial" | i18n }} + + +
+ + + A-Z + + + + a-z + + + + 0-9 + + + + !@#$%^&* +
-
- - {{ "minNumbers" | i18n }} - - - - {{ "minSpecial" | i18n }} - - + + +
+

{{ "passphrase" | i18n }}

+
+ + {{ "minimumNumberOfWords" | i18n }} + + +
+ + + {{ "capitalize" | i18n }} + + + + {{ "includeNumber" | i18n }} +
- - - A-Z - - - - a-z - - - - 0-9 - - - - !@#$%^&* - -

{{ "passphrase" | i18n }}

-
- - {{ "minimumNumberOfWords" | i18n }} - - -
- - - {{ "capitalize" | i18n }} - - - - {{ "includeNumber" | i18n }} -
diff --git a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts index 93124c42fa..e1568da048 100644 --- a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts @@ -1,8 +1,11 @@ import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { UntypedFormBuilder, Validators } from "@angular/forms"; +import { BehaviorSubject, map } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DefaultPassphraseBoundaries, DefaultPasswordBoundaries } from "@bitwarden/generator-core"; import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; @@ -19,20 +22,59 @@ export class PasswordGeneratorPolicy extends BasePolicy { }) export class PasswordGeneratorPolicyComponent extends BasePolicyComponent { data = this.formBuilder.group({ - defaultType: [null], - minLength: [null, [Validators.min(5), Validators.max(128)]], + overridePasswordType: [null], + minLength: [ + null, + [ + Validators.min(DefaultPasswordBoundaries.length.min), + Validators.max(DefaultPasswordBoundaries.length.max), + ], + ], useUpper: [null], useLower: [null], useNumbers: [null], useSpecial: [null], - minNumbers: [null, [Validators.min(0), Validators.max(9)]], - minSpecial: [null, [Validators.min(0), Validators.max(9)]], - minNumberWords: [null, [Validators.min(3), Validators.max(20)]], + minNumbers: [ + null, + [ + Validators.min(DefaultPasswordBoundaries.minDigits.min), + Validators.max(DefaultPasswordBoundaries.minDigits.max), + ], + ], + minSpecial: [ + null, + [ + Validators.min(DefaultPasswordBoundaries.minSpecialCharacters.min), + Validators.max(DefaultPasswordBoundaries.minSpecialCharacters.max), + ], + ], + minNumberWords: [ + null, + [ + Validators.min(DefaultPassphraseBoundaries.numWords.min), + Validators.max(DefaultPassphraseBoundaries.numWords.max), + ], + ], capitalize: [null], includeNumber: [null], }); - defaultTypes: { name: string; value: string }[]; + overridePasswordTypeOptions: { name: string; value: string }[]; + + // These subjects cache visibility of the sub-options for passwords + // and passphrases; without them policy controls don't show up at all. + private showPasswordPolicies = new BehaviorSubject(true); + private showPassphrasePolicies = new BehaviorSubject(true); + + /** Emits `true` when the password policy options should be displayed */ + get showPasswordPolicies$() { + return this.showPasswordPolicies.asObservable(); + } + + /** Emits `true` when the passphrase policy options should be displayed */ + get showPassphrasePolicies$() { + return this.showPassphrasePolicies.asObservable(); + } constructor( private formBuilder: UntypedFormBuilder, @@ -40,10 +82,27 @@ export class PasswordGeneratorPolicyComponent extends BasePolicyComponent { ) { super(); - this.defaultTypes = [ + this.overridePasswordTypeOptions = [ { name: i18nService.t("userPreference"), value: null }, - { name: i18nService.t("password"), value: "password" }, + { name: i18nService.t("password"), value: PASSWORD_POLICY_VALUE }, { name: i18nService.t("passphrase"), value: "passphrase" }, ]; + + this.data.valueChanges + .pipe(isEnabled(PASSWORD_POLICY_VALUE), takeUntilDestroyed()) + .subscribe(this.showPasswordPolicies); + this.data.valueChanges + .pipe(isEnabled(PASSPHRASE_POLICY_VALUE), takeUntilDestroyed()) + .subscribe(this.showPassphrasePolicies); } } + +const PASSWORD_POLICY_VALUE = "password"; +const PASSPHRASE_POLICY_VALUE = "passphrase"; + +function isEnabled(enabledValue: string) { + return map((d: { overridePasswordType: string }) => { + const type = d?.overridePasswordType ?? enabledValue; + return type === enabledValue; + }); +} diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 2f4350718a..96a50b5405 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -4145,8 +4145,9 @@ "minimumNumberOfWords": { "message": "Minimum number of words" }, - "defaultType": { - "message": "Default type" + "overridePasswordTypePolicy": { + "message": "Password Type", + "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "User preference"