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 @@
-
- {{ "minLength" | 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"