mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-12 00:41:29 +01:00
[PM-8524] Add support for generating usernames/passwords and auditing passwords
This commit is contained in:
parent
2d7cbeb96c
commit
39ac6061ca
@ -9,6 +9,7 @@ import {
|
|||||||
} from "@storybook/angular";
|
} from "@storybook/angular";
|
||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
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 { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||||
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||||
import { AsyncActionsModule, ButtonModule, ToastService } from "@bitwarden/components";
|
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 { CipherFormConfig, PasswordRepromptService } from "@bitwarden/vault";
|
||||||
import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/src/app/core/tests";
|
import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/src/app/core/tests";
|
||||||
|
|
||||||
@ -134,12 +138,25 @@ export default {
|
|||||||
generatePassword: () => Promise.resolve("random-password"),
|
generatePassword: () => Promise.resolve("random-password"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: UsernameGenerationServiceAbstraction,
|
||||||
|
useValue: {
|
||||||
|
getOptions: () => Promise.resolve({}),
|
||||||
|
generateUsername: () => Promise.resolve("random-username"),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: TotpCaptureService,
|
provide: TotpCaptureService,
|
||||||
useValue: {
|
useValue: {
|
||||||
captureTotpFromTab: () => Promise.resolve("some-value"),
|
captureTotpFromTab: () => Promise.resolve("some-value"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: AuditService,
|
||||||
|
useValue: {
|
||||||
|
passwordLeaked: () => Promise.resolve(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
componentWrapperDecorator(
|
componentWrapperDecorator(
|
||||||
|
@ -9,11 +9,32 @@
|
|||||||
<bit-form-field>
|
<bit-form-field>
|
||||||
<bit-label>{{ "username" | i18n }}</bit-label>
|
<bit-label>{{ "username" | i18n }}</bit-label>
|
||||||
<input bitInput formControlName="username" />
|
<input bitInput formControlName="username" />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitIconButton="bwi-generate"
|
||||||
|
bitSuffix
|
||||||
|
*ngIf="loginDetailsForm.controls.username.enabled"
|
||||||
|
data-testid="generate-username-button"
|
||||||
|
[appA11yTitle]="'generateUsername' | i18n"
|
||||||
|
[bitAction]="generateUsername"
|
||||||
|
></button>
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
|
|
||||||
<bit-form-field>
|
<bit-form-field>
|
||||||
<bit-label>{{ "password" | i18n }}</bit-label>
|
<bit-label>{{ "password" | i18n }}</bit-label>
|
||||||
<input bitInput formControlName="password" type="password" #passwordInput />
|
<input bitInput formControlName="password" type="password" #passwordInput />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitIconButton="bwi-check-circle"
|
||||||
|
bitSuffix
|
||||||
|
*ngIf="
|
||||||
|
loginDetailsForm.controls.password.enabled &&
|
||||||
|
loginDetailsForm.controls.password.value.length > 0
|
||||||
|
"
|
||||||
|
data-testid="check-password-button"
|
||||||
|
[appA11yTitle]="'checkPassword' | i18n"
|
||||||
|
[bitAction]="checkPassword"
|
||||||
|
></button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
bitIconButton
|
bitIconButton
|
||||||
@ -23,6 +44,15 @@
|
|||||||
bitPasswordInputToggle
|
bitPasswordInputToggle
|
||||||
[passwordInput]="passwordInput"
|
[passwordInput]="passwordInput"
|
||||||
></button>
|
></button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitIconButton="bwi-generate"
|
||||||
|
bitSuffix
|
||||||
|
*ngIf="loginDetailsForm.controls.password.enabled"
|
||||||
|
data-testid="generate-password-button"
|
||||||
|
[appA11yTitle]="'generatePassword' | i18n"
|
||||||
|
[bitAction]="generatePassword"
|
||||||
|
></button>
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
|
|
||||||
<bit-form-field *ngIf="hasPasskey">
|
<bit-form-field *ngIf="hasPasskey">
|
||||||
@ -68,7 +98,7 @@
|
|||||||
bitIconButton="bwi-camera"
|
bitIconButton="bwi-camera"
|
||||||
bitSuffix
|
bitSuffix
|
||||||
*ngIf="canCaptureTotp"
|
*ngIf="canCaptureTotp"
|
||||||
[bitAction]="captureTotpFromTab"
|
[bitAction]="captureTotp"
|
||||||
[appA11yTitle]="'totpCapture' | i18n"
|
[appA11yTitle]="'totpCapture' | i18n"
|
||||||
></button>
|
></button>
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
|
@ -5,6 +5,7 @@ import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
|
|||||||
import { map } from "rxjs";
|
import { map } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||||
import {
|
import {
|
||||||
@ -18,7 +19,10 @@ import {
|
|||||||
ToastService,
|
ToastService,
|
||||||
TypographyModule,
|
TypographyModule,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
import {
|
||||||
|
PasswordGenerationServiceAbstraction,
|
||||||
|
UsernameGenerationServiceAbstraction,
|
||||||
|
} from "@bitwarden/generator-legacy";
|
||||||
|
|
||||||
import { TotpCaptureService } from "../../abstractions/totp-capture.service";
|
import { TotpCaptureService } from "../../abstractions/totp-capture.service";
|
||||||
import { CipherFormContainer } from "../../cipher-form-container";
|
import { CipherFormContainer } from "../../cipher-form-container";
|
||||||
@ -84,7 +88,9 @@ export class LoginDetailsSectionComponent implements OnInit {
|
|||||||
private cipherFormContainer: CipherFormContainer,
|
private cipherFormContainer: CipherFormContainer,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private generatorService: PasswordGenerationServiceAbstraction,
|
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
|
private usernameGenerationService: UsernameGenerationServiceAbstraction,
|
||||||
|
private auditService: AuditService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
@Optional() private totpCaptureService?: TotpCaptureService,
|
@Optional() private totpCaptureService?: TotpCaptureService,
|
||||||
) {
|
) {
|
||||||
@ -142,7 +148,7 @@ export class LoginDetailsSectionComponent implements OnInit {
|
|||||||
this.loginDetailsForm.controls.password.patchValue(await this.generateNewPassword());
|
this.loginDetailsForm.controls.password.patchValue(await this.generateNewPassword());
|
||||||
}
|
}
|
||||||
|
|
||||||
captureTotpFromTab = async () => {
|
captureTotp = async () => {
|
||||||
if (!this.canCaptureTotp) {
|
if (!this.canCaptureTotp) {
|
||||||
return;
|
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() {
|
private async generateNewPassword() {
|
||||||
const [options] = await this.generatorService.getOptions();
|
const [options] = await this.passwordGenerationService.getOptions();
|
||||||
return await this.generatorService.generatePassword(options);
|
return await this.passwordGenerationService.generatePassword(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user