mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +00:00
refactor(set-change-password): [Auth/PM-18206] Update InputPasswordComponent to handle multiple flows (#13745)
Updates the InputPasswordComponent so that it can eventually be used in multiple set/change password scenarios. Most importantly, this PR adds an InputPasswordFlow enum and @Input so that parent components can dictate which UI elements to show.
This commit is contained in:
@@ -2297,6 +2297,9 @@
|
|||||||
"privacyPolicy": {
|
"privacyPolicy": {
|
||||||
"message": "Privacy Policy"
|
"message": "Privacy Policy"
|
||||||
},
|
},
|
||||||
|
"yourNewPasswordCannotBeTheSameAsYourCurrentPassword": {
|
||||||
|
"message": "Your new password cannot be the same as your current password."
|
||||||
|
},
|
||||||
"hintEqualsPassword": {
|
"hintEqualsPassword": {
|
||||||
"message": "Your password hint cannot be the same as your password."
|
"message": "Your password hint cannot be the same as your password."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2072,6 +2072,9 @@
|
|||||||
"personalOwnershipSubmitError": {
|
"personalOwnershipSubmitError": {
|
||||||
"message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections."
|
"message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections."
|
||||||
},
|
},
|
||||||
|
"yourNewPasswordCannotBeTheSameAsYourCurrentPassword": {
|
||||||
|
"message": "Your new password cannot be the same as your current password."
|
||||||
|
},
|
||||||
"hintEqualsPassword": {
|
"hintEqualsPassword": {
|
||||||
"message": "Your password hint cannot be the same as your password."
|
"message": "Your password hint cannot be the same as your password."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -187,11 +187,11 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
|
masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
|
||||||
passwordInputResult = {
|
passwordInputResult = {
|
||||||
masterKey: masterKey,
|
masterKey: masterKey,
|
||||||
masterKeyHash: "masterKeyHash",
|
serverMasterKeyHash: "serverMasterKeyHash",
|
||||||
localMasterKeyHash: "localMasterKeyHash",
|
localMasterKeyHash: "localMasterKeyHash",
|
||||||
kdfConfig: DEFAULT_KDF_CONFIG,
|
kdfConfig: DEFAULT_KDF_CONFIG,
|
||||||
hint: "hint",
|
hint: "hint",
|
||||||
password: "password",
|
newPassword: "newPassword",
|
||||||
};
|
};
|
||||||
|
|
||||||
userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
|
userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
|
||||||
@@ -239,7 +239,7 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
email,
|
email,
|
||||||
emailVerificationToken: emailVerificationToken,
|
emailVerificationToken: emailVerificationToken,
|
||||||
masterPasswordHash: passwordInputResult.masterKeyHash,
|
masterPasswordHash: passwordInputResult.serverMasterKeyHash,
|
||||||
masterPasswordHint: passwordInputResult.hint,
|
masterPasswordHint: passwordInputResult.hint,
|
||||||
userSymmetricKey: userKeyEncString.encryptedString,
|
userSymmetricKey: userKeyEncString.encryptedString,
|
||||||
userAsymmetricKeys: {
|
userAsymmetricKeys: {
|
||||||
@@ -277,7 +277,7 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
email,
|
email,
|
||||||
emailVerificationToken: undefined,
|
emailVerificationToken: undefined,
|
||||||
masterPasswordHash: passwordInputResult.masterKeyHash,
|
masterPasswordHash: passwordInputResult.serverMasterKeyHash,
|
||||||
masterPasswordHint: passwordInputResult.hint,
|
masterPasswordHint: passwordInputResult.hint,
|
||||||
userSymmetricKey: userKeyEncString.encryptedString,
|
userSymmetricKey: userKeyEncString.encryptedString,
|
||||||
userAsymmetricKeys: {
|
userAsymmetricKeys: {
|
||||||
@@ -320,7 +320,7 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
email,
|
email,
|
||||||
emailVerificationToken: undefined,
|
emailVerificationToken: undefined,
|
||||||
masterPasswordHash: passwordInputResult.masterKeyHash,
|
masterPasswordHash: passwordInputResult.serverMasterKeyHash,
|
||||||
masterPasswordHint: passwordInputResult.hint,
|
masterPasswordHint: passwordInputResult.hint,
|
||||||
userSymmetricKey: userKeyEncString.encryptedString,
|
userSymmetricKey: userKeyEncString.encryptedString,
|
||||||
userAsymmetricKeys: {
|
userAsymmetricKeys: {
|
||||||
@@ -365,7 +365,7 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
email,
|
email,
|
||||||
emailVerificationToken: undefined,
|
emailVerificationToken: undefined,
|
||||||
masterPasswordHash: passwordInputResult.masterKeyHash,
|
masterPasswordHash: passwordInputResult.serverMasterKeyHash,
|
||||||
masterPasswordHint: passwordInputResult.hint,
|
masterPasswordHint: passwordInputResult.hint,
|
||||||
userSymmetricKey: userKeyEncString.encryptedString,
|
userSymmetricKey: userKeyEncString.encryptedString,
|
||||||
userAsymmetricKeys: {
|
userAsymmetricKeys: {
|
||||||
@@ -412,7 +412,7 @@ describe("WebRegistrationFinishService", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
email,
|
email,
|
||||||
emailVerificationToken: undefined,
|
emailVerificationToken: undefined,
|
||||||
masterPasswordHash: passwordInputResult.masterKeyHash,
|
masterPasswordHash: passwordInputResult.serverMasterKeyHash,
|
||||||
masterPasswordHint: passwordInputResult.hint,
|
masterPasswordHint: passwordInputResult.hint,
|
||||||
userSymmetricKey: userKeyEncString.encryptedString,
|
userSymmetricKey: userKeyEncString.encryptedString,
|
||||||
userAsymmetricKeys: {
|
userAsymmetricKeys: {
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
<div *ngIf="!useTrialStepper">
|
<div *ngIf="!useTrialStepper">
|
||||||
<auth-input-password
|
<auth-input-password
|
||||||
|
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
|
||||||
[email]="email"
|
[email]="email"
|
||||||
[masterPasswordPolicyOptions]="enforcedPolicyOptions"
|
[masterPasswordPolicyOptions]="enforcedPolicyOptions"
|
||||||
(onPasswordFormSubmit)="handlePasswordSubmit($event)"
|
(onPasswordFormSubmit)="handlePasswordSubmit($event)"
|
||||||
[buttonText]="'createAccount' | i18n"
|
[primaryButtonText]="{ key: 'createAccount' }"
|
||||||
></auth-input-password>
|
></auth-input-password>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="useTrialStepper">
|
<div *ngIf="useTrialStepper">
|
||||||
<app-vertical-stepper #stepper linear (selectionChange)="verticalStepChange($event)">
|
<app-vertical-stepper #stepper linear (selectionChange)="verticalStepChange($event)">
|
||||||
<app-vertical-step label="Create Account" [editable]="false" [subLabel]="email">
|
<app-vertical-step label="Create Account" [editable]="false" [subLabel]="email">
|
||||||
<auth-input-password
|
<auth-input-password
|
||||||
|
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
|
||||||
[email]="email"
|
[email]="email"
|
||||||
[masterPasswordPolicyOptions]="enforcedPolicyOptions"
|
[masterPasswordPolicyOptions]="enforcedPolicyOptions"
|
||||||
(onPasswordFormSubmit)="handlePasswordSubmit($event)"
|
(onPasswordFormSubmit)="handlePasswordSubmit($event)"
|
||||||
[buttonText]="'createAccount' | i18n"
|
[primaryButtonText]="{ key: 'createAccount' }"
|
||||||
></auth-input-password>
|
></auth-input-password>
|
||||||
</app-vertical-step>
|
</app-vertical-step>
|
||||||
<app-vertical-step label="Organization Information" [subLabel]="orgInfoSubLabel">
|
<app-vertical-step label="Organization Information" [subLabel]="orgInfoSubLabel">
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import { FormBuilder, Validators } from "@angular/forms";
|
|||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { firstValueFrom, Subject, switchMap, takeUntil } from "rxjs";
|
import { firstValueFrom, Subject, switchMap, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { PasswordInputResult, RegistrationFinishService } from "@bitwarden/auth/angular";
|
import {
|
||||||
|
InputPasswordFlow,
|
||||||
|
PasswordInputResult,
|
||||||
|
RegistrationFinishService,
|
||||||
|
} from "@bitwarden/auth/angular";
|
||||||
import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common";
|
import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common";
|
||||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
@@ -47,6 +51,8 @@ export type InitiationPath =
|
|||||||
export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
|
export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent;
|
@ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent;
|
||||||
|
|
||||||
|
InputPasswordFlow = InputPasswordFlow;
|
||||||
|
|
||||||
/** Password Manager or Secrets Manager */
|
/** Password Manager or Secrets Manager */
|
||||||
product: ProductType;
|
product: ProductType;
|
||||||
/** The tier of product being subscribed to */
|
/** The tier of product being subscribed to */
|
||||||
@@ -363,7 +369,7 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.logIn(passwordInputResult.password, captchaToken);
|
await this.logIn(passwordInputResult.newPassword, captchaToken);
|
||||||
|
|
||||||
this.submitting = false;
|
this.submitting = false;
|
||||||
|
|
||||||
|
|||||||
@@ -5707,6 +5707,9 @@
|
|||||||
"webAuthnSuccess": {
|
"webAuthnSuccess": {
|
||||||
"message": "WebAuthn verified successfully! You may close this tab."
|
"message": "WebAuthn verified successfully! You may close this tab."
|
||||||
},
|
},
|
||||||
|
"yourNewPasswordCannotBeTheSameAsYourCurrentPassword": {
|
||||||
|
"message": "Your new password cannot be the same as your current password."
|
||||||
|
},
|
||||||
"hintEqualsPassword": {
|
"hintEqualsPassword": {
|
||||||
"message": "Your password hint cannot be the same as your password."
|
"message": "Your password hint cannot be the same as your password."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,14 +4,36 @@
|
|||||||
[policy]="masterPasswordPolicyOptions"
|
[policy]="masterPasswordPolicyOptions"
|
||||||
></auth-password-callout>
|
></auth-password-callout>
|
||||||
|
|
||||||
|
<bit-form-field
|
||||||
|
*ngIf="
|
||||||
|
inputPasswordFlow === InputPasswordFlow.ChangePassword ||
|
||||||
|
inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<bit-label>{{ "currentMasterPass" | i18n }}</bit-label>
|
||||||
|
<input
|
||||||
|
id="input-password-form_current-password"
|
||||||
|
bitInput
|
||||||
|
type="password"
|
||||||
|
formControlName="currentPassword"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitIconButton
|
||||||
|
bitSuffix
|
||||||
|
bitPasswordInputToggle
|
||||||
|
[(toggled)]="showPassword"
|
||||||
|
></button>
|
||||||
|
</bit-form-field>
|
||||||
|
|
||||||
<div class="tw-mb-6">
|
<div class="tw-mb-6">
|
||||||
<bit-form-field>
|
<bit-form-field>
|
||||||
<bit-label>{{ "masterPassword" | i18n }}</bit-label>
|
<bit-label>{{ "newMasterPass" | i18n }}</bit-label>
|
||||||
<input
|
<input
|
||||||
id="input-password-form_password"
|
id="input-password-form_new-password"
|
||||||
bitInput
|
bitInput
|
||||||
type="password"
|
type="password"
|
||||||
formControlName="password"
|
formControlName="newPassword"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -30,7 +52,7 @@
|
|||||||
<tools-password-strength
|
<tools-password-strength
|
||||||
[showText]="true"
|
[showText]="true"
|
||||||
[email]="email"
|
[email]="email"
|
||||||
[password]="formGroup.controls.password.value"
|
[password]="formGroup.controls.newPassword.value"
|
||||||
(passwordStrengthScore)="getPasswordStrengthScore($event)"
|
(passwordStrengthScore)="getPasswordStrengthScore($event)"
|
||||||
></tools-password-strength>
|
></tools-password-strength>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,10 +60,10 @@
|
|||||||
<bit-form-field>
|
<bit-form-field>
|
||||||
<bit-label>{{ "confirmMasterPassword" | i18n }}</bit-label>
|
<bit-label>{{ "confirmMasterPassword" | i18n }}</bit-label>
|
||||||
<input
|
<input
|
||||||
id="input-password-form_confirmed-password"
|
id="input-password-form_confirm-new-password"
|
||||||
bitInput
|
bitInput
|
||||||
type="password"
|
type="password"
|
||||||
formControlName="confirmedPassword"
|
formControlName="confirmNewPassword"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -65,16 +87,40 @@
|
|||||||
<bit-label>{{ "checkForBreaches" | i18n }}</bit-label>
|
<bit-label>{{ "checkForBreaches" | i18n }}</bit-label>
|
||||||
</bit-form-control>
|
</bit-form-control>
|
||||||
|
|
||||||
<button
|
<bit-form-control
|
||||||
type="submit"
|
*ngIf="inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation"
|
||||||
bitButton
|
|
||||||
bitFormButton
|
|
||||||
buttonType="primary"
|
|
||||||
[block]="btnBlock"
|
|
||||||
[loading]="loading"
|
|
||||||
>
|
>
|
||||||
{{ buttonText || ("setMasterPassword" | i18n) }}
|
<input type="checkbox" bitCheckbox formControlName="rotateUserKey" />
|
||||||
</button>
|
<bit-label>
|
||||||
|
{{ "rotateAccountEncKey" | i18n }}
|
||||||
|
<a
|
||||||
|
href="https://bitwarden.com/help/account-encryption-key/#rotate-your-encryption-key"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
appA11yTitle="{{ 'impactOfRotatingYourEncryptionKey' | i18n }}"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</bit-label>
|
||||||
|
</bit-form-control>
|
||||||
|
|
||||||
|
<div class="tw-flex tw-gap-2" [ngClass]="inlineButtons ? 'tw-flex-row' : 'tw-flex-col'">
|
||||||
|
<button type="submit" bitButton bitFormButton buttonType="primary" [loading]="loading">
|
||||||
|
{{ primaryButtonTextStr || ("setMasterPassword" | i18n) }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
*ngIf="secondaryButtonText"
|
||||||
|
type="button"
|
||||||
|
bitButton
|
||||||
|
bitFormButton
|
||||||
|
buttonType="secondary"
|
||||||
|
[loading]="loading"
|
||||||
|
(click)="onSecondaryButtonClick.emit()"
|
||||||
|
>
|
||||||
|
{{ secondaryButtonTextStr }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formGroup"></bit-error-summary>
|
<bit-error-summary *ngIf="showErrorSummary" [formGroup]="formGroup"></bit-error-summary>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
// @ts-strict-ignore
|
import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from "@angular/forms";
|
||||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
|
||||||
import { ReactiveFormsModule, FormBuilder, Validators } from "@angular/forms";
|
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import {
|
import {
|
||||||
@@ -23,6 +21,7 @@ import {
|
|||||||
IconButtonModule,
|
IconButtonModule,
|
||||||
InputModule,
|
InputModule,
|
||||||
ToastService,
|
ToastService,
|
||||||
|
Translation,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management";
|
import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
@@ -36,6 +35,29 @@ import { PasswordCalloutComponent } from "../password-callout/password-callout.c
|
|||||||
|
|
||||||
import { PasswordInputResult } from "./password-input-result";
|
import { PasswordInputResult } from "./password-input-result";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines which form input elements will be displayed in the UI.
|
||||||
|
*/
|
||||||
|
export enum InputPasswordFlow {
|
||||||
|
/**
|
||||||
|
* - Input: New password
|
||||||
|
* - Input: Confirm new password
|
||||||
|
* - Input: Hint
|
||||||
|
* - Checkbox: Check for breaches
|
||||||
|
*/
|
||||||
|
SetInitialPassword,
|
||||||
|
/**
|
||||||
|
* Everything above, plus:
|
||||||
|
* - Input: Current password (as the first element in the UI)
|
||||||
|
*/
|
||||||
|
ChangePassword,
|
||||||
|
/**
|
||||||
|
* Everything above, plus:
|
||||||
|
* - Checkbox: Rotate account encryption key (as the last element in the UI)
|
||||||
|
*/
|
||||||
|
ChangePasswordWithOptionalUserKeyRotation,
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
selector: "auth-input-password",
|
selector: "auth-input-password",
|
||||||
@@ -54,44 +76,58 @@ import { PasswordInputResult } from "./password-input-result";
|
|||||||
JslibModule,
|
JslibModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class InputPasswordComponent {
|
export class InputPasswordComponent implements OnInit {
|
||||||
@Output() onPasswordFormSubmit = new EventEmitter<PasswordInputResult>();
|
@Output() onPasswordFormSubmit = new EventEmitter<PasswordInputResult>();
|
||||||
|
@Output() onSecondaryButtonClick = new EventEmitter<void>();
|
||||||
|
|
||||||
@Input({ required: true }) email: string;
|
@Input({ required: true }) inputPasswordFlow!: InputPasswordFlow;
|
||||||
@Input() buttonText: string;
|
@Input({ required: true }) email!: string;
|
||||||
|
|
||||||
|
@Input() loading = false;
|
||||||
@Input() masterPasswordPolicyOptions: MasterPasswordPolicyOptions | null = null;
|
@Input() masterPasswordPolicyOptions: MasterPasswordPolicyOptions | null = null;
|
||||||
@Input() loading: boolean = false;
|
|
||||||
@Input() btnBlock: boolean = true;
|
|
||||||
|
|
||||||
|
@Input() inlineButtons = false;
|
||||||
|
@Input() primaryButtonText?: Translation;
|
||||||
|
protected primaryButtonTextStr: string = "";
|
||||||
|
@Input() secondaryButtonText?: Translation;
|
||||||
|
protected secondaryButtonTextStr: string = "";
|
||||||
|
|
||||||
|
protected InputPasswordFlow = InputPasswordFlow;
|
||||||
private minHintLength = 0;
|
private minHintLength = 0;
|
||||||
protected maxHintLength = 50;
|
protected maxHintLength = 50;
|
||||||
protected minPasswordLength = Utils.minimumPasswordLength;
|
protected minPasswordLength = Utils.minimumPasswordLength;
|
||||||
protected minPasswordMsg = "";
|
protected minPasswordMsg = "";
|
||||||
protected passwordStrengthScore: PasswordStrengthScore;
|
protected passwordStrengthScore: PasswordStrengthScore = 0;
|
||||||
protected showErrorSummary = false;
|
protected showErrorSummary = false;
|
||||||
protected showPassword = false;
|
protected showPassword = false;
|
||||||
|
|
||||||
protected formGroup = this.formBuilder.group(
|
protected formGroup = this.formBuilder.nonNullable.group(
|
||||||
{
|
{
|
||||||
password: ["", [Validators.required, Validators.minLength(this.minPasswordLength)]],
|
newPassword: ["", [Validators.required, Validators.minLength(this.minPasswordLength)]],
|
||||||
confirmedPassword: ["", Validators.required],
|
confirmNewPassword: ["", Validators.required],
|
||||||
hint: [
|
hint: [
|
||||||
"", // must be string (not null) because we check length in validation
|
"", // must be string (not null) because we check length in validation
|
||||||
[Validators.minLength(this.minHintLength), Validators.maxLength(this.maxHintLength)],
|
[Validators.minLength(this.minHintLength), Validators.maxLength(this.maxHintLength)],
|
||||||
],
|
],
|
||||||
checkForBreaches: true,
|
checkForBreaches: [true],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validators: [
|
validators: [
|
||||||
|
InputsFieldMatch.compareInputs(
|
||||||
|
"doNotMatch",
|
||||||
|
"currentPassword",
|
||||||
|
"newPassword",
|
||||||
|
this.i18nService.t("yourNewPasswordCannotBeTheSameAsYourCurrentPassword"),
|
||||||
|
),
|
||||||
InputsFieldMatch.compareInputs(
|
InputsFieldMatch.compareInputs(
|
||||||
"match",
|
"match",
|
||||||
"password",
|
"newPassword",
|
||||||
"confirmedPassword",
|
"confirmNewPassword",
|
||||||
this.i18nService.t("masterPassDoesntMatch"),
|
this.i18nService.t("masterPassDoesntMatch"),
|
||||||
),
|
),
|
||||||
InputsFieldMatch.compareInputs(
|
InputsFieldMatch.compareInputs(
|
||||||
"doNotMatch",
|
"doNotMatch",
|
||||||
"password",
|
"newPassword",
|
||||||
"hint",
|
"hint",
|
||||||
this.i18nService.t("hintEqualsPassword"),
|
this.i18nService.t("hintEqualsPassword"),
|
||||||
),
|
),
|
||||||
@@ -109,6 +145,41 @@ export class InputPasswordComponent {
|
|||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (
|
||||||
|
this.inputPasswordFlow === InputPasswordFlow.ChangePassword ||
|
||||||
|
this.inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
|
||||||
|
) {
|
||||||
|
// https://github.com/angular/angular/issues/48794
|
||||||
|
(this.formGroup as FormGroup<any>).addControl(
|
||||||
|
"currentPassword",
|
||||||
|
this.formBuilder.control("", Validators.required),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) {
|
||||||
|
// https://github.com/angular/angular/issues/48794
|
||||||
|
(this.formGroup as FormGroup<any>).addControl(
|
||||||
|
"rotateUserKey",
|
||||||
|
this.formBuilder.control(false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.primaryButtonText) {
|
||||||
|
this.primaryButtonTextStr = this.i18nService.t(
|
||||||
|
this.primaryButtonText.key,
|
||||||
|
...(this.primaryButtonText?.placeholders ?? []),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.secondaryButtonText) {
|
||||||
|
this.secondaryButtonTextStr = this.i18nService.t(
|
||||||
|
this.secondaryButtonText.key,
|
||||||
|
...(this.secondaryButtonText?.placeholders ?? []),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get minPasswordLengthMsg() {
|
get minPasswordLengthMsg() {
|
||||||
if (
|
if (
|
||||||
this.masterPasswordPolicyOptions != null &&
|
this.masterPasswordPolicyOptions != null &&
|
||||||
@@ -132,10 +203,10 @@ export class InputPasswordComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const password = this.formGroup.controls.password.value;
|
const newPassword = this.formGroup.controls.newPassword.value;
|
||||||
|
|
||||||
const passwordEvaluatedSuccessfully = await this.evaluatePassword(
|
const passwordEvaluatedSuccessfully = await this.evaluateNewPassword(
|
||||||
password,
|
newPassword,
|
||||||
this.passwordStrengthScore,
|
this.passwordStrengthScore,
|
||||||
this.formGroup.controls.checkForBreaches.value,
|
this.formGroup.controls.checkForBreaches.value,
|
||||||
);
|
);
|
||||||
@@ -152,38 +223,55 @@ export class InputPasswordComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const masterKey = await this.keyService.makeMasterKey(
|
const masterKey = await this.keyService.makeMasterKey(
|
||||||
password,
|
newPassword,
|
||||||
this.email.trim().toLowerCase(),
|
this.email.trim().toLowerCase(),
|
||||||
kdfConfig,
|
kdfConfig,
|
||||||
);
|
);
|
||||||
|
|
||||||
const masterKeyHash = await this.keyService.hashMasterKey(password, masterKey);
|
const serverMasterKeyHash = await this.keyService.hashMasterKey(
|
||||||
|
newPassword,
|
||||||
|
masterKey,
|
||||||
|
HashPurpose.ServerAuthorization,
|
||||||
|
);
|
||||||
|
|
||||||
const localMasterKeyHash = await this.keyService.hashMasterKey(
|
const localMasterKeyHash = await this.keyService.hashMasterKey(
|
||||||
password,
|
newPassword,
|
||||||
masterKey,
|
masterKey,
|
||||||
HashPurpose.LocalAuthorization,
|
HashPurpose.LocalAuthorization,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.onPasswordFormSubmit.emit({
|
const passwordInputResult: PasswordInputResult = {
|
||||||
masterKey,
|
newPassword,
|
||||||
masterKeyHash,
|
|
||||||
localMasterKeyHash,
|
|
||||||
kdfConfig,
|
|
||||||
hint: this.formGroup.controls.hint.value,
|
hint: this.formGroup.controls.hint.value,
|
||||||
password,
|
kdfConfig,
|
||||||
});
|
masterKey,
|
||||||
|
serverMasterKeyHash,
|
||||||
|
localMasterKeyHash,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.inputPasswordFlow === InputPasswordFlow.ChangePassword ||
|
||||||
|
this.inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
|
||||||
|
) {
|
||||||
|
passwordInputResult.currentPassword = this.formGroup.get("currentPassword")?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) {
|
||||||
|
passwordInputResult.rotateUserKey = this.formGroup.get("rotateUserKey")?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onPasswordFormSubmit.emit(passwordInputResult);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns true if the password passes all checks, false otherwise
|
// Returns true if the password passes all checks, false otherwise
|
||||||
private async evaluatePassword(
|
private async evaluateNewPassword(
|
||||||
password: string,
|
newPassword: string,
|
||||||
passwordStrengthScore: PasswordStrengthScore,
|
passwordStrengthScore: PasswordStrengthScore,
|
||||||
checkForBreaches: boolean,
|
checkForBreaches: boolean,
|
||||||
) {
|
) {
|
||||||
// Check if the password is breached, weak, or both
|
// Check if the password is breached, weak, or both
|
||||||
const passwordIsBreached =
|
const passwordIsBreached =
|
||||||
checkForBreaches && (await this.auditService.passwordLeaked(password));
|
checkForBreaches && (await this.auditService.passwordLeaked(newPassword));
|
||||||
|
|
||||||
const passwordWeak = passwordStrengthScore != null && passwordStrengthScore < 3;
|
const passwordWeak = passwordStrengthScore != null && passwordStrengthScore < 3;
|
||||||
|
|
||||||
@@ -224,7 +312,7 @@ export class InputPasswordComponent {
|
|||||||
this.masterPasswordPolicyOptions != null &&
|
this.masterPasswordPolicyOptions != null &&
|
||||||
!this.policyService.evaluateMasterPassword(
|
!this.policyService.evaluateMasterPassword(
|
||||||
this.passwordStrengthScore,
|
this.passwordStrengthScore,
|
||||||
password,
|
newPassword,
|
||||||
this.masterPasswordPolicyOptions,
|
this.masterPasswordPolicyOptions,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import * as stories from "./input-password.stories.ts";
|
|||||||
|
|
||||||
# InputPassword Component
|
# InputPassword Component
|
||||||
|
|
||||||
The `InputPasswordComponent` allows a user to enter a master password and hint. On submission it
|
The `InputPasswordComponent` allows a user to enter master password related credentials. On
|
||||||
creates a master key, master key hash, and emits those values to the parent (along with the hint and
|
submission it creates a master key, master key hash, and emits those values to the parent (along
|
||||||
default kdfConfig).
|
with the other values found in `PasswordInputResult`).
|
||||||
|
|
||||||
The component is intended for re-use in different scenarios throughout the application. Therefore it
|
The component is intended for re-use in different scenarios throughout the application. Therefore it
|
||||||
is mostly presentational and simply emits values rather than acting on them itself. It is the job of
|
is mostly presentational and simply emits values rather than acting on them itself. It is the job of
|
||||||
@@ -18,26 +18,66 @@ the parent component to act on those values as needed.
|
|||||||
|
|
||||||
## `@Input()`'s
|
## `@Input()`'s
|
||||||
|
|
||||||
- `email` (**required**) - the parent component must provide an email so that the
|
**Required**
|
||||||
`InputPasswordComponent` can create a master key.
|
|
||||||
- `buttonText` (optional) - an `i18n` translated string that can be used as button text (default
|
- `inputPasswordFlow` - the parent component must provide the correct flow, which is used to
|
||||||
text is "Set master password").
|
determine which form input elements will be displayed in the UI.
|
||||||
- `masterPasswordPolicyOptions` (optional) - used to display and enforce master password policy
|
- `email` - the parent component must provide an email so that the `InputPasswordComponent` can
|
||||||
requirements.
|
create a master key.
|
||||||
|
|
||||||
|
**Optional**
|
||||||
|
|
||||||
|
- `loading` - a boolean used to indicate that the parent component is performing some
|
||||||
|
long-running/async operation and that the form should be disabled until the operation is complete.
|
||||||
|
The primary button will also show a spinner if `loading` true.
|
||||||
|
- `masterPasswordPolicyOptions` - used to display and enforce master password policy requirements.
|
||||||
|
- `inlineButtons` - takes a boolean that determines if the button(s) should be displayed inline (as
|
||||||
|
opposed to full-width)
|
||||||
|
- `primaryButtonText` - takes a `Translation` object that can be used as button text
|
||||||
|
- `secondaryButtonText` - takes a `Translation` object that can be used as button text
|
||||||
|
|
||||||
|
## `@Output()`'s
|
||||||
|
|
||||||
|
- `onPasswordFormSubmit` - on form submit, emits a `PasswordInputResult` object
|
||||||
|
- `onSecondaryButtonClick` - on click, emits a notice that the secondary button has been clicked.
|
||||||
|
The parent component can listen for this event and take some custom action as needed (go back,
|
||||||
|
cancel, logout, etc.)
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
## Form Input Fields
|
## Form Input Fields
|
||||||
|
|
||||||
The `InputPasswordComponent` allows a user to enter:
|
The `InputPasswordComponent` can handle up to 6 different form input fields, depending on the
|
||||||
|
`InputPasswordFlow` provided by the parent component.
|
||||||
|
|
||||||
1. Master password
|
**InputPasswordFlow.SetInitialPassword**
|
||||||
2. Master password confirmation
|
|
||||||
3. Hint (optional)
|
|
||||||
4. Chooses whether to check for password breaches (checkbox)
|
|
||||||
|
|
||||||
Validation ensures that the master password and confirmed master password are the same, and that the
|
- Input: New password
|
||||||
master password and hint values are not the same.
|
- Input: Confirm new password
|
||||||
|
- Input: Hint
|
||||||
|
- Checkbox: Check for breaches
|
||||||
|
|
||||||
|
**InputPasswordFlow.ChangePassword**
|
||||||
|
|
||||||
|
Includes everything above, plus:
|
||||||
|
|
||||||
|
- Input: Current password (as the first element in the UI)
|
||||||
|
|
||||||
|
**InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation**
|
||||||
|
|
||||||
|
Includes everything above, plus:
|
||||||
|
|
||||||
|
- Checkbox: Rotate account encryption key (as the last element in the UI)
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Validation ensures that:
|
||||||
|
|
||||||
|
- The current password and new password are NOT the same
|
||||||
|
- The new password and confirmed new password are the same
|
||||||
|
- The new password and password hint are NOT the same
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
@@ -57,19 +97,23 @@ When the form is submitted, the `InputPasswordComponent` does the following in o
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export interface PasswordInputResult {
|
export interface PasswordInputResult {
|
||||||
masterKey: MasterKey;
|
newPassword: string;
|
||||||
masterKeyHash: string;
|
|
||||||
kdfConfig: PBKDF2KdfConfig;
|
|
||||||
hint: string;
|
hint: string;
|
||||||
|
kdfConfig: PBKDF2KdfConfig;
|
||||||
|
masterKey: MasterKey;
|
||||||
|
serverMasterKeyHash: string;
|
||||||
|
localMasterKeyHash: string;
|
||||||
|
currentPassword?: string; // included if the flow is ChangePassword or ChangePasswordWithOptionalUserKeyRotation
|
||||||
|
rotateUserKey?: boolean; // included if the flow is ChangePasswordWithOptionalUserKeyRotation
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
# Default Example
|
# Example - InputPasswordFlow.SetInitialPassword
|
||||||
|
|
||||||
<Story of={stories.Default} />
|
<Story of={stories.SetInitialPassword} />
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
# With Policy Requrements
|
# Example - With Policy Requrements
|
||||||
|
|
||||||
<Story of={stories.WithPolicy} />
|
<Story of={stories.WithPolicies} />
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
|
||||||
// @ts-strict-ignore
|
|
||||||
import { importProvidersFrom } from "@angular/core";
|
import { importProvidersFrom } from "@angular/core";
|
||||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
import { action } from "@storybook/addon-actions";
|
import { action } from "@storybook/addon-actions";
|
||||||
@@ -18,7 +16,7 @@ import { KeyService } from "@bitwarden/key-management";
|
|||||||
// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports
|
// eslint-disable-next-line import/no-restricted-paths, no-restricted-imports
|
||||||
import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests";
|
import { PreloadedEnglishI18nModule } from "../../../../../apps/web/src/app/core/tests";
|
||||||
|
|
||||||
import { InputPasswordComponent } from "./input-password.component";
|
import { InputPasswordComponent, InputPasswordFlow } from "./input-password.component";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Auth/Input Password",
|
title: "Auth/Input Password",
|
||||||
@@ -62,7 +60,7 @@ export default {
|
|||||||
provide: PasswordStrengthServiceAbstraction,
|
provide: PasswordStrengthServiceAbstraction,
|
||||||
useValue: {
|
useValue: {
|
||||||
getPasswordStrength: (password) => {
|
getPasswordStrength: (password) => {
|
||||||
let score = 0;
|
let score: number | null = null;
|
||||||
if (password.length === 0) {
|
if (password.length === 0) {
|
||||||
score = null;
|
score = null;
|
||||||
} else if (password.length <= 4) {
|
} else if (password.length <= 4) {
|
||||||
@@ -88,6 +86,12 @@ export default {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
args: {
|
args: {
|
||||||
|
InputPasswordFlow: {
|
||||||
|
SetInitialPassword: InputPasswordFlow.SetInitialPassword,
|
||||||
|
ChangePassword: InputPasswordFlow.ChangePassword,
|
||||||
|
ChangePasswordWithOptionalUserKeyRotation:
|
||||||
|
InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation,
|
||||||
|
},
|
||||||
masterPasswordPolicyOptions: {
|
masterPasswordPolicyOptions: {
|
||||||
minComplexity: 4,
|
minComplexity: 4,
|
||||||
minLength: 14,
|
minLength: 14,
|
||||||
@@ -96,25 +100,77 @@ export default {
|
|||||||
requireNumbers: true,
|
requireNumbers: true,
|
||||||
requireSpecial: true,
|
requireSpecial: true,
|
||||||
} as MasterPasswordPolicyOptions,
|
} as MasterPasswordPolicyOptions,
|
||||||
|
argTypes: {
|
||||||
|
onSecondaryButtonClick: { action: "onSecondaryButtonClick" },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
type Story = StoryObj<InputPasswordComponent>;
|
type Story = StoryObj<InputPasswordComponent>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const SetInitialPassword: Story = {
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
template: `
|
template: `
|
||||||
<auth-input-password></auth-input-password>
|
<auth-input-password [inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"></auth-input-password>
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithPolicy: Story = {
|
export const ChangePassword: Story = {
|
||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
template: `
|
template: `
|
||||||
<auth-input-password [masterPasswordPolicyOptions]="masterPasswordPolicyOptions"></auth-input-password>
|
<auth-input-password [inputPasswordFlow]="InputPasswordFlow.ChangePassword"></auth-input-password>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChangePasswordWithOptionalUserKeyRotation: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<auth-input-password
|
||||||
|
[inputPasswordFlow]="InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation"
|
||||||
|
></auth-input-password>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithPolicies: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<auth-input-password
|
||||||
|
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
|
||||||
|
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
|
||||||
|
></auth-input-password>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SecondaryButton: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<auth-input-password
|
||||||
|
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
|
||||||
|
[secondaryButtonText]="{ key: 'cancel' }"
|
||||||
|
(onSecondaryButtonClick)="onSecondaryButtonClick()"
|
||||||
|
></auth-input-password>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SecondaryButtonWithPlaceHolderText: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<auth-input-password
|
||||||
|
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
|
||||||
|
[secondaryButtonText]="{ key: 'backTo', placeholders: ['homepage'] }"
|
||||||
|
(onSecondaryButtonClick)="onSecondaryButtonClick()"
|
||||||
|
></auth-input-password>
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -123,7 +179,24 @@ export const InlineButton: Story = {
|
|||||||
render: (args) => ({
|
render: (args) => ({
|
||||||
props: args,
|
props: args,
|
||||||
template: `
|
template: `
|
||||||
<auth-input-password [btnBlock]="false" [masterPasswordPolicyOptions]="masterPasswordPolicyOptions"></auth-input-password>
|
<auth-input-password
|
||||||
|
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
|
||||||
|
[inlineButtons]="true"
|
||||||
|
></auth-input-password>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InlineButtons: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<auth-input-password
|
||||||
|
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
|
||||||
|
[secondaryButtonText]="{ key: 'cancel' }"
|
||||||
|
[inlineButtons]="true"
|
||||||
|
(onSecondaryButtonClick)="onSecondaryButtonClick()"
|
||||||
|
></auth-input-password>
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import { MasterKey } from "@bitwarden/common/types/key";
|
|||||||
import { PBKDF2KdfConfig } from "@bitwarden/key-management";
|
import { PBKDF2KdfConfig } from "@bitwarden/key-management";
|
||||||
|
|
||||||
export interface PasswordInputResult {
|
export interface PasswordInputResult {
|
||||||
masterKey: MasterKey;
|
newPassword: string;
|
||||||
masterKeyHash: string;
|
|
||||||
localMasterKeyHash: string;
|
|
||||||
kdfConfig: PBKDF2KdfConfig;
|
|
||||||
hint: string;
|
hint: string;
|
||||||
password: string;
|
kdfConfig: PBKDF2KdfConfig;
|
||||||
|
masterKey: MasterKey;
|
||||||
|
serverMasterKeyHash: string;
|
||||||
|
localMasterKeyHash: string;
|
||||||
|
currentPassword?: string;
|
||||||
|
rotateUserKey?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,11 +60,11 @@ describe("DefaultRegistrationFinishService", () => {
|
|||||||
masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
|
masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
|
||||||
passwordInputResult = {
|
passwordInputResult = {
|
||||||
masterKey: masterKey,
|
masterKey: masterKey,
|
||||||
masterKeyHash: "masterKeyHash",
|
serverMasterKeyHash: "serverMasterKeyHash",
|
||||||
localMasterKeyHash: "localMasterKeyHash",
|
localMasterKeyHash: "localMasterKeyHash",
|
||||||
kdfConfig: DEFAULT_KDF_CONFIG,
|
kdfConfig: DEFAULT_KDF_CONFIG,
|
||||||
hint: "hint",
|
hint: "hint",
|
||||||
password: "password",
|
newPassword: "password",
|
||||||
};
|
};
|
||||||
|
|
||||||
userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
|
userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
|
||||||
@@ -101,7 +101,7 @@ describe("DefaultRegistrationFinishService", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
email,
|
email,
|
||||||
emailVerificationToken: emailVerificationToken,
|
emailVerificationToken: emailVerificationToken,
|
||||||
masterPasswordHash: passwordInputResult.masterKeyHash,
|
masterPasswordHash: passwordInputResult.serverMasterKeyHash,
|
||||||
masterPasswordHint: passwordInputResult.hint,
|
masterPasswordHint: passwordInputResult.hint,
|
||||||
userSymmetricKey: userKeyEncString.encryptedString,
|
userSymmetricKey: userKeyEncString.encryptedString,
|
||||||
userAsymmetricKeys: {
|
userAsymmetricKeys: {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi
|
|||||||
|
|
||||||
const registerFinishRequest = new RegisterFinishRequest(
|
const registerFinishRequest = new RegisterFinishRequest(
|
||||||
email,
|
email,
|
||||||
passwordInputResult.masterKeyHash,
|
passwordInputResult.serverMasterKeyHash,
|
||||||
passwordInputResult.hint,
|
passwordInputResult.hint,
|
||||||
encryptedUserKey,
|
encryptedUserKey,
|
||||||
userAsymmetricKeysRequest,
|
userAsymmetricKeysRequest,
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
<auth-input-password
|
<auth-input-password
|
||||||
*ngIf="!loading"
|
*ngIf="!loading"
|
||||||
[email]="email"
|
[email]="email"
|
||||||
|
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
|
||||||
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
|
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
|
||||||
[loading]="submitting"
|
[loading]="submitting"
|
||||||
|
[primaryButtonText]="{ key: 'createAccount' }"
|
||||||
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
|
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
|
||||||
[buttonText]="'createAccount' | i18n"
|
|
||||||
></auth-input-password>
|
></auth-input-password>
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ import {
|
|||||||
PasswordLoginCredentials,
|
PasswordLoginCredentials,
|
||||||
} from "../../../common";
|
} from "../../../common";
|
||||||
import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service";
|
import { AnonLayoutWrapperDataService } from "../../anon-layout/anon-layout-wrapper-data.service";
|
||||||
import { InputPasswordComponent } from "../../input-password/input-password.component";
|
import {
|
||||||
|
InputPasswordComponent,
|
||||||
|
InputPasswordFlow,
|
||||||
|
} from "../../input-password/input-password.component";
|
||||||
import { PasswordInputResult } from "../../input-password/password-input-result";
|
import { PasswordInputResult } from "../../input-password/password-input-result";
|
||||||
|
|
||||||
import { RegistrationFinishService } from "./registration-finish.service";
|
import { RegistrationFinishService } from "./registration-finish.service";
|
||||||
@@ -36,6 +39,8 @@ import { RegistrationFinishService } from "./registration-finish.service";
|
|||||||
export class RegistrationFinishComponent implements OnInit, OnDestroy {
|
export class RegistrationFinishComponent implements OnInit, OnDestroy {
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
InputPasswordFlow = InputPasswordFlow;
|
||||||
|
|
||||||
loading = true;
|
loading = true;
|
||||||
submitting = false;
|
submitting = false;
|
||||||
email: string;
|
email: string;
|
||||||
@@ -176,7 +181,7 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {
|
|||||||
try {
|
try {
|
||||||
const credentials = new PasswordLoginCredentials(
|
const credentials = new PasswordLoginCredentials(
|
||||||
this.email,
|
this.email,
|
||||||
passwordInputResult.password,
|
passwordInputResult.newPassword,
|
||||||
captchaBypassToken,
|
captchaBypassToken,
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -112,11 +112,11 @@ describe("DefaultSetPasswordJitService", () => {
|
|||||||
|
|
||||||
passwordInputResult = {
|
passwordInputResult = {
|
||||||
masterKey: masterKey,
|
masterKey: masterKey,
|
||||||
masterKeyHash: "masterKeyHash",
|
serverMasterKeyHash: "serverMasterKeyHash",
|
||||||
localMasterKeyHash: "localMasterKeyHash",
|
localMasterKeyHash: "localMasterKeyHash",
|
||||||
hint: "hint",
|
hint: "hint",
|
||||||
kdfConfig: DEFAULT_KDF_CONFIG,
|
kdfConfig: DEFAULT_KDF_CONFIG,
|
||||||
password: "password",
|
newPassword: "password",
|
||||||
};
|
};
|
||||||
|
|
||||||
credentials = {
|
credentials = {
|
||||||
@@ -131,7 +131,7 @@ describe("DefaultSetPasswordJitService", () => {
|
|||||||
userDecryptionOptionsService.userDecryptionOptions$ = userDecryptionOptionsSubject;
|
userDecryptionOptionsService.userDecryptionOptions$ = userDecryptionOptionsSubject;
|
||||||
|
|
||||||
setPasswordRequest = new SetPasswordRequest(
|
setPasswordRequest = new SetPasswordRequest(
|
||||||
passwordInputResult.masterKeyHash,
|
passwordInputResult.serverMasterKeyHash,
|
||||||
protectedUserKey[1].encryptedString,
|
protectedUserKey[1].encryptedString,
|
||||||
passwordInputResult.hint,
|
passwordInputResult.hint,
|
||||||
orgSsoIdentifier,
|
orgSsoIdentifier,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
|
|||||||
async setPassword(credentials: SetPasswordCredentials): Promise<void> {
|
async setPassword(credentials: SetPasswordCredentials): Promise<void> {
|
||||||
const {
|
const {
|
||||||
masterKey,
|
masterKey,
|
||||||
masterKeyHash,
|
serverMasterKeyHash,
|
||||||
localMasterKeyHash,
|
localMasterKeyHash,
|
||||||
hint,
|
hint,
|
||||||
kdfConfig,
|
kdfConfig,
|
||||||
@@ -70,7 +70,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
|
|||||||
const [keyPair, keysRequest] = await this.makeKeyPairAndRequest(protectedUserKey);
|
const [keyPair, keysRequest] = await this.makeKeyPairAndRequest(protectedUserKey);
|
||||||
|
|
||||||
const request = new SetPasswordRequest(
|
const request = new SetPasswordRequest(
|
||||||
masterKeyHash,
|
serverMasterKeyHash,
|
||||||
protectedUserKey[1].encryptedString,
|
protectedUserKey[1].encryptedString,
|
||||||
hint,
|
hint,
|
||||||
orgSsoIdentifier,
|
orgSsoIdentifier,
|
||||||
@@ -92,7 +92,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
|
|||||||
await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, userId);
|
await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, userId);
|
||||||
|
|
||||||
if (resetPasswordAutoEnroll) {
|
if (resetPasswordAutoEnroll) {
|
||||||
await this.handleResetPasswordAutoEnroll(masterKeyHash, orgId, userId);
|
await this.handleResetPasswordAutoEnroll(serverMasterKeyHash, orgId, userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
</app-callout>
|
</app-callout>
|
||||||
|
|
||||||
<auth-input-password
|
<auth-input-password
|
||||||
[buttonText]="'createAccount' | i18n"
|
[inputPasswordFlow]="InputPasswordFlow.SetInitialPassword"
|
||||||
|
[primaryButtonText]="{ key: 'createAccount' }"
|
||||||
[email]="email"
|
[email]="email"
|
||||||
[loading]="submitting"
|
[loading]="submitting"
|
||||||
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
|
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
|
|||||||
// FIXME: remove `src` and fix import
|
// FIXME: remove `src` and fix import
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { ToastService } from "../../../../components/src/toast";
|
import { ToastService } from "../../../../components/src/toast";
|
||||||
import { InputPasswordComponent } from "../input-password/input-password.component";
|
import {
|
||||||
|
InputPasswordComponent,
|
||||||
|
InputPasswordFlow,
|
||||||
|
} from "../input-password/input-password.component";
|
||||||
import { PasswordInputResult } from "../input-password/password-input-result";
|
import { PasswordInputResult } from "../input-password/password-input-result";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -33,6 +36,7 @@ import {
|
|||||||
imports: [CommonModule, InputPasswordComponent, JslibModule],
|
imports: [CommonModule, InputPasswordComponent, JslibModule],
|
||||||
})
|
})
|
||||||
export class SetPasswordJitComponent implements OnInit {
|
export class SetPasswordJitComponent implements OnInit {
|
||||||
|
protected InputPasswordFlow = InputPasswordFlow;
|
||||||
protected email: string;
|
protected email: string;
|
||||||
protected masterPasswordPolicyOptions: MasterPasswordPolicyOptions;
|
protected masterPasswordPolicyOptions: MasterPasswordPolicyOptions;
|
||||||
protected orgId: string;
|
protected orgId: string;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { PBKDF2KdfConfig } from "@bitwarden/key-management";
|
|||||||
|
|
||||||
export interface SetPasswordCredentials {
|
export interface SetPasswordCredentials {
|
||||||
masterKey: MasterKey;
|
masterKey: MasterKey;
|
||||||
masterKeyHash: string;
|
serverMasterKeyHash: string;
|
||||||
localMasterKeyHash: string;
|
localMasterKeyHash: string;
|
||||||
kdfConfig: PBKDF2KdfConfig;
|
kdfConfig: PBKDF2KdfConfig;
|
||||||
hint: string;
|
hint: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user