diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts
index 9935b7ac21..acb4111a65 100644
--- a/libs/vault/src/cipher-form/cipher-form.stories.ts
+++ b/libs/vault/src/cipher-form/cipher-form.stories.ts
@@ -9,6 +9,7 @@ import {
} from "@storybook/angular";
import { BehaviorSubject } from "rxjs";
+import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CipherType } from "@bitwarden/common/vault/enums";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
@@ -17,7 +18,10 @@ import { CollectionView } from "@bitwarden/common/vault/models/view/collection.v
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
import { AsyncActionsModule, ButtonModule, ToastService } from "@bitwarden/components";
-import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
+import {
+ PasswordGenerationServiceAbstraction,
+ UsernameGenerationServiceAbstraction,
+} from "@bitwarden/generator-legacy";
import { CipherFormConfig, PasswordRepromptService } from "@bitwarden/vault";
import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/src/app/core/tests";
@@ -134,12 +138,25 @@ export default {
generatePassword: () => Promise.resolve("random-password"),
},
},
+ {
+ provide: UsernameGenerationServiceAbstraction,
+ useValue: {
+ getOptions: () => Promise.resolve({}),
+ generateUsername: () => Promise.resolve("random-username"),
+ },
+ },
{
provide: TotpCaptureService,
useValue: {
captureTotpFromTab: () => Promise.resolve("some-value"),
},
},
+ {
+ provide: AuditService,
+ useValue: {
+ passwordLeaked: () => Promise.resolve(0),
+ },
+ },
],
}),
componentWrapperDecorator(
diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html
index 5766ad6a25..786ad726cb 100644
--- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html
+++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html
@@ -9,11 +9,32 @@
{{ "username" | i18n }}
+
{{ "password" | i18n }}
+
+
@@ -68,7 +98,7 @@
bitIconButton="bwi-camera"
bitSuffix
*ngIf="canCaptureTotp"
- [bitAction]="captureTotpFromTab"
+ [bitAction]="captureTotp"
[appA11yTitle]="'totpCapture' | i18n"
>
diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts
index 5aef61a5dd..d28db24a0d 100644
--- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts
+++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts
@@ -5,6 +5,7 @@ import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
import {
@@ -18,7 +19,10 @@ import {
ToastService,
TypographyModule,
} from "@bitwarden/components";
-import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
+import {
+ PasswordGenerationServiceAbstraction,
+ UsernameGenerationServiceAbstraction,
+} from "@bitwarden/generator-legacy";
import { TotpCaptureService } from "../../abstractions/totp-capture.service";
import { CipherFormContainer } from "../../cipher-form-container";
@@ -84,7 +88,9 @@ export class LoginDetailsSectionComponent implements OnInit {
private cipherFormContainer: CipherFormContainer,
private formBuilder: FormBuilder,
private i18nService: I18nService,
- private generatorService: PasswordGenerationServiceAbstraction,
+ private passwordGenerationService: PasswordGenerationServiceAbstraction,
+ private usernameGenerationService: UsernameGenerationServiceAbstraction,
+ private auditService: AuditService,
private toastService: ToastService,
@Optional() private totpCaptureService?: TotpCaptureService,
) {
@@ -142,7 +148,7 @@ export class LoginDetailsSectionComponent implements OnInit {
this.loginDetailsForm.controls.password.patchValue(await this.generateNewPassword());
}
- captureTotpFromTab = async () => {
+ captureTotp = async () => {
if (!this.canCaptureTotp) {
return;
}
@@ -173,8 +179,54 @@ export class LoginDetailsSectionComponent implements OnInit {
});
};
+ /**
+ * Generate a new password and update the form.
+ * TODO: Browser extension needs a means to cache the current form so values are not lost upon navigating to the generator.
+ */
+ generatePassword = async () => {
+ const newPassword = await this.generateNewPassword();
+ this.loginDetailsForm.controls.password.patchValue(newPassword);
+ };
+
+ /**
+ * Generate a new username and update the form.
+ * TODO: Browser extension needs a means to cache the current form so values are not lost upon navigating to the generator.
+ */
+ generateUsername = async () => {
+ const options = await this.usernameGenerationService.getOptions();
+ const newUsername = await this.usernameGenerationService.generateUsername(options);
+ this.loginDetailsForm.controls.username.patchValue(newUsername);
+ };
+
+ /**
+ * Checks if the password has been exposed in a data breach using the AuditService.
+ */
+ checkPassword = async () => {
+ const password = this.loginDetailsForm.controls.password.value;
+
+ if (password == null || password === "") {
+ return;
+ }
+
+ const matches = await this.auditService.passwordLeaked(password);
+
+ if (matches > 0) {
+ this.toastService.showToast({
+ variant: "warning",
+ title: null,
+ message: this.i18nService.t("passwordExposed", matches.toString()),
+ });
+ } else {
+ this.toastService.showToast({
+ variant: "success",
+ title: null,
+ message: this.i18nService.t("passwordSafe"),
+ });
+ }
+ };
+
private async generateNewPassword() {
- const [options] = await this.generatorService.getOptions();
- return await this.generatorService.generatePassword(options);
+ const [options] = await this.passwordGenerationService.getOptions();
+ return await this.passwordGenerationService.generatePassword(options);
}
}