1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-07 20:24:01 +00:00
This commit is contained in:
Brandon
2025-09-05 16:41:49 -04:00
parent 1a0d3e3ada
commit 6a8361fafa
5 changed files with 144 additions and 9 deletions

View File

@@ -1,6 +1,15 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from "@angular/core";
import {
Component,
DestroyRef,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ReactiveFormsModule, FormBuilder, Validators, FormControl } from "@angular/forms";
import { firstValueFrom } from "rxjs";
import { filter, firstValueFrom, Subject } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
@@ -10,6 +19,7 @@ import {
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -32,6 +42,13 @@ import {
ToastService,
Translation,
} from "@bitwarden/components";
import { GeneratorServicesModule } from "@bitwarden/generator-components";
import {
CredentialGeneratorService,
GenerateRequest,
Profile,
Type,
} from "@bitwarden/generator-core";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import {
DEFAULT_KDF_CONFIG,
@@ -107,6 +124,7 @@ interface InputPasswordForm {
ButtonModule,
CheckboxModule,
FormFieldModule,
GeneratorServicesModule,
IconButtonModule,
InputModule,
JslibModule,
@@ -149,6 +167,9 @@ export class InputPasswordComponent implements OnInit {
protected showErrorSummary = false;
protected showPassword = false;
private generate$: Subject<GenerateRequest> = new Subject();
private account$ = this.accountService.activeAccount$.pipe(filter((acc) => acc != null));
protected formGroup = this.formBuilder.nonNullable.group<InputPasswordForm>(
{
newPassword: this.formBuilder.nonNullable.control("", [
@@ -194,11 +215,22 @@ export class InputPasswordComponent implements OnInit {
private policyService: PolicyService,
private toastService: ToastService,
private validationService: ValidationService,
private credentialGeneratorService: CredentialGeneratorService,
private accountService: AccountService,
private destroy$: DestroyRef,
) {}
ngOnInit(): void {
this.addFormFieldsIfNecessary();
this.setButtonText();
this.credentialGeneratorService
.generate$({ on$: this.generate$, account$: this.account$ })
.pipe(takeUntilDestroyed(this.destroy$))
.subscribe((generated) => {
this.formGroup.patchValue({
newPassword: generated.credential,
});
});
}
private addFormFieldsIfNecessary() {
@@ -634,10 +666,13 @@ export class InputPasswordComponent implements OnInit {
}
protected async generatePassword() {
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
this.formGroup.patchValue({
newPassword: await this.passwordGenerationService.generatePassword(options),
});
const request: GenerateRequest = {
type: Type.password,
profile: Profile.masterPassword,
source: "master-password",
};
this.generate$.next(request);
if (!this.passwordStrengthComponent) {
throw new Error("PasswordStrengthComponent is not initialized");

View File

@@ -31,8 +31,7 @@ export const Profile = Object.freeze({
* @remarks these are the options displayed on the generator tab
*/
account: "account",
// FIXME: consider adding a profile for bitwarden's master password
masterPassword: "masterPassword",
});
/** Credential generation algorithms grouped by purpose. */

View File

@@ -5,6 +5,7 @@ import { deepFreeze } from "@bitwarden/common/tools/util";
import { PasswordRandomizer, SdkPasswordRandomizer } from "../../engine";
import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies";
import { masterPasswordReducer } from "../../policies/master-password-policy-reducer";
import { GeneratorDependencyProvider } from "../../providers";
import { CredentialGenerator, PasswordGeneratorSettings } from "../../types";
import { Algorithm, Profile, Type } from "../data";
@@ -111,6 +112,80 @@ const password: GeneratorMetadata<PasswordGeneratorSettings> = deepFreeze({
},
},
},
[Profile.masterPassword]: {
type: "core",
storage: {
key: "passwordGeneratorSettings",
target: "object",
format: "plain",
classifier: new PublicClassifier<PasswordGeneratorSettings>([
"length",
"ambiguous",
"uppercase",
"minUppercase",
"lowercase",
"minLowercase",
"number",
"minNumber",
"special",
"minSpecial",
]),
state: GENERATOR_DISK,
initial: {
length: 14,
ambiguous: true,
uppercase: true,
minUppercase: 1,
lowercase: true,
minLowercase: 1,
number: true,
minNumber: 1,
special: false,
minSpecial: 0,
},
options: {
deserializer(value) {
return value;
},
clearOn: ["logout"],
},
},
constraints: {
type: PolicyType.MasterPassword,
default: {
length: {
min: 5,
max: 128,
recommendation: 14,
},
minNumber: {
min: 0,
max: 9,
},
minSpecial: {
min: 0,
max: 9,
},
},
create(policies, context) {
const initial = {
minLength: 0,
useUppercase: false,
useLowercase: true,
useNumbers: false,
numberCount: 0,
useSpecial: false,
specialCount: 0,
};
const policy = policies.reduce(masterPasswordReducer, initial);
const constraints = new DynamicPasswordPolicyConstraints(
policy,
context.defaultConstraints,
);
return constraints;
},
},
},
},
});

View File

@@ -0,0 +1,19 @@
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { PasswordGeneratorPolicy } from "../types";
export function masterPasswordReducer(acc: PasswordGeneratorPolicy, policy: Policy) {
if (policy.type !== PolicyType.MasterPassword || !policy.enabled) {
return acc;
}
return {
minLength: Math.max(acc.minLength, policy.data.minLength ?? acc.minLength),
useUppercase: policy.data.requireUpper || acc.useUppercase,
useLowercase: policy.data.requireLower || acc.useLowercase,
useNumbers: policy.data.requireNumbers || acc.useNumbers,
numberCount: acc.numberCount,
useSpecial: policy.data.requireSpecial || acc.useSpecial,
specialCount: acc.specialCount,
};
}

View File

@@ -86,8 +86,15 @@ export class GeneratorProfileProvider {
"initializing constraints$",
);
// The problem is here, this just gets applicable policies to the current user, which the owner/admin may be exempt from if generating a pw for another user via account recovery.
//const policies$ = profile.constraints.type
// ? this.policyService.policiesByType$(profile.constraints.type, account.id)
// : of([]);
const policies$ = profile.constraints.type
? this.policyService.policiesByType$(profile.constraints.type, account.id)
? this.policyService
.policies$(account.id)
.pipe(map((policies) => policies.filter((p) => p.type === profile.constraints.type)))
: of([]);
const context: ProfileContext<Settings> = {