1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 11:43:51 +00:00
This commit is contained in:
Bernd Schoolmann
2025-07-23 12:54:54 +02:00
parent 29f87f2b95
commit f2edf37cf3
77 changed files with 541 additions and 3592 deletions

View File

@@ -1,160 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<button type="button" routerLink="/login">{{ "cancel" | i18n }}</button>
</div>
<h1 class="center">
<span class="title">{{ "setMasterPassword" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" [disabled]="form.loading">
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<main tabindex="-1">
<div class="full-loading-spinner" *ngIf="syncLoading">
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
</div>
<div *ngIf="!syncLoading">
<div class="box">
<p
class="tw-px-4"
*ngIf="
forceSetPasswordReason ==
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission;
else defaultCardDesc
"
>
{{ "orgPermissionsUpdatedMustSetPassword" | i18n }}
</p>
<ng-template #defaultCardDesc>
<p class="tw-px-4">{{ "orgRequiresYouToSetPassword" | i18n }}</p>
</ng-template>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong class="sub-label text-{{ color }}" *ngIf="text">
{{ text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
aria-describedby="masterPasswordHelp"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
[attr.aria-pressed]="showPassword"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<app-password-strength
[password]="masterPassword"
[email]="email"
(passwordStrengthResult)="getStrengthResult($event)"
(passwordScoreColor)="getPasswordScoreText($event)"
>
</app-password-strength>
</div>
</div>
<div id="masterPasswordHelp" class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
[attr.aria-pressed]="showPassword"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input
id="hint"
type="text"
name="Hint"
aria-describedby="hintHelp"
[(ngModel)]="hint"
/>
</div>
</div>
<div id="hintHelp" class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
</div>
</main>
</form>

View File

@@ -1,10 +0,0 @@
import { Component } from "@angular/core";
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
@Component({
selector: "app-set-password",
templateUrl: "set-password.component.html",
standalone: false,
})
export class SetPasswordComponent extends BaseSetPasswordComponent {}

View File

@@ -1,142 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<button type="button" (click)="logOut()">{{ "logOut" | i18n }}</button>
</div>
<h1 class="center">
<span class="title">{{ "updateMasterPassword" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" [disabled]="form.loading">
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<main tabindex="-1">
<app-callout type="warning" title="{{ 'updateMasterPassword' | i18n }}">
{{ masterPasswordWarningText }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<div class="box" *ngIf="requireCurrentPassword">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="currentMasterPassword">
{{ "currentMasterPass" | i18n }}
</label>
<input
id="currentMasterPassword"
type="password"
name="CurrentMasterPassword"
class="monospaced"
[(ngModel)]="verification.secret"
required
appInputVerbatim
/>
</div>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "newMasterPass" | i18n }}
<strong class="sub-label text-{{ color }}" *ngIf="text">
{{ text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
[attr.aria-pressed]="showPassword"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<app-password-strength
[password]="masterPassword"
[email]="email"
(passwordStrengthResult)="getStrengthResult($event)"
(passwordScoreColor)="getPasswordScoreText($event)"
>
</app-password-strength>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
[attr.aria-pressed]="showPassword"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" aria-describedby="hintHelp" [(ngModel)]="hint" />
</div>
</div>
<div id="hintHelp" class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
</main>
</form>

View File

@@ -1,30 +0,0 @@
import { Component } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "@bitwarden/angular/auth/components/update-temp-password.component";
import { postLogoutMessageListener$ } from "./utils/post-logout-message-listener";
@Component({
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
standalone: false,
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
onSuccessfulChangePassword: () => Promise<void> = this.doOnSuccessfulChangePassword.bind(this);
private async doOnSuccessfulChangePassword() {
// start listening for "switchAccountFinish" or "doneLoggingOut"
const messagePromise = firstValueFrom(postLogoutMessageListener$);
this.messagingService.send("logout");
// wait for messages
const command = await messagePromise;
// doneLoggingOut already has a message handler that will navigate us
if (command === "switchAccountFinish") {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/"]);
}
}
}

View File

@@ -1,169 +0,0 @@
<form id="set-password-page" #form>
<div class="content">
<img class="logo-image" alt="Bitwarden" />
<p class="lead">{{ "setMasterPassword" | i18n }}</p>
<div class="box text-center" *ngIf="syncLoading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<div *ngIf="!syncLoading">
<div class="box">
<p
*ngIf="
forceSetPasswordReason ==
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission;
else defaultCardDesc
"
>
{{ "orgPermissionsUpdatedMustSetPassword" | i18n }}
</p>
<ng-template #defaultCardDesc>
<p>{{ "orgRequiresYouToSetPassword" | i18n }}</p>
</ng-template>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
</div>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword"
>{{ "masterPass" | i18n }}
<strong class="sub-label text-{{ color }}" *ngIf="text">
{{ text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
aria-describedby="masterPasswordHelp"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<app-password-strength
[password]="masterPassword"
[email]="email"
(passwordStrengthResult)="getStrengthResult($event)"
(passwordScoreColor)="getPasswordScoreText($event)"
>
</app-password-strength>
</div>
</div>
<div id="masterPasswordHelp" class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input
id="hint"
type="text"
name="Hint"
aria-describedby="hintHelp"
[(ngModel)]="hint"
/>
</div>
</div>
<div id="hintHelp" class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<i
*ngIf="form.loading"
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "submit" | i18n }}</span>
</button>
<button type="button" class="btn block" (click)="logOut()">
<span>{{ "logOut" | i18n }}</span>
</button>
</div>
</form>
</div>
</div>
</form>

View File

@@ -1,111 +0,0 @@
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService, ToastService } from "@bitwarden/components";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
const BroadcasterSubscriptionId = "SetPasswordComponent";
@Component({
selector: "app-set-password",
templateUrl: "set-password.component.html",
standalone: false,
})
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnInit, OnDestroy {
constructor(
protected accountService: AccountService,
protected dialogService: DialogService,
protected encryptService: EncryptService,
protected i18nService: I18nService,
protected kdfConfigService: KdfConfigService,
protected keyService: KeyService,
protected masterPasswordApiService: MasterPasswordApiService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected messagingService: MessagingService,
protected organizationApiService: OrganizationApiServiceAbstraction,
protected organizationUserApiService: OrganizationUserApiService,
protected platformUtilsService: PlatformUtilsService,
protected policyApiService: PolicyApiServiceAbstraction,
protected policyService: PolicyService,
protected route: ActivatedRoute,
protected router: Router,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected syncService: SyncService,
protected toastService: ToastService,
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
) {
super(
accountService,
dialogService,
encryptService,
i18nService,
kdfConfigService,
keyService,
masterPasswordApiService,
masterPasswordService,
messagingService,
organizationApiService,
organizationUserApiService,
platformUtilsService,
policyApiService,
policyService,
route,
router,
ssoLoginService,
syncService,
toastService,
userDecryptionOptionsService,
);
}
async ngOnInit() {
await super.ngOnInit();
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
default:
}
});
});
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
}
onWindowHidden() {
this.showPassword = false;
}
protected async onSetPasswordSuccess(
masterKey: MasterKey,
userKey: [UserKey, EncString],
keyPair: [string, EncString],
): Promise<void> {
await super.onSetPasswordSuccess(masterKey, userKey, keyPair);
this.messagingService.send("redrawMenu");
}
}

View File

@@ -1,136 +0,0 @@
<form id="update-temp-password-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<div class="content">
<app-callout type="warning" title="{{ 'updateMasterPassword' | i18n }}">
{{ masterPasswordWarningText }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<div class="box" *ngIf="requireCurrentPassword">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="currentMasterPassword">
{{ "currentMasterPass" | i18n }}
</label>
<input
id="currentMasterPassword"
type="password"
name="currentMasterPassword"
class="monospaced"
[(ngModel)]="verification.secret"
required
appInputVerbatim
/>
</div>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "newMasterPass" | i18n }}
<strong class="sub-label text-{{ color }}" *ngIf="text">
{{ text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="masterPassword === ''"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<app-password-strength
[password]="masterPassword"
[email]="email"
(passwordStrengthResult)="getStrengthResult($event)"
(passwordScoreColor)="getPasswordScoreText($event)"
>
</app-password-strength>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" aria-describedby="hintHelp" [(ngModel)]="hint" />
</div>
</div>
<div id="hintHelp" class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div class="buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<b [hidden]="form.loading">{{ "submit" | i18n }}</b>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<button type="button" (click)="logOut()" class="btn block">{{ "logOut" | i18n }}</button>
</div>
</div>
</form>

View File

@@ -1,10 +0,0 @@
import { Component } from "@angular/core";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "@bitwarden/angular/auth/components/update-temp-password.component";
@Component({
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
standalone: false,
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {}

View File

@@ -15,17 +15,15 @@ import {
EncryptedString,
EncString,
} from "@bitwarden/common/key-management/crypto/models/enc-string";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { UserKey } from "@bitwarden/common/types/key";
import {
Argon2KdfConfig,
KdfConfig,
PBKDF2KdfConfig,
UserKeyRotationKeyRecoveryProvider,
KeyService,
KdfType,
} from "@bitwarden/key-management";
import { OrganizationUserResetPasswordEntry } from "./organization-user-reset-password-entry";
@@ -35,11 +33,10 @@ import { OrganizationUserResetPasswordEntry } from "./organization-user-reset-pa
})
export class OrganizationUserResetPasswordService
implements
UserKeyRotationKeyRecoveryProvider<
OrganizationUserResetPasswordWithIdRequest,
OrganizationUserResetPasswordEntry
>
{
UserKeyRotationKeyRecoveryProvider<
OrganizationUserResetPasswordWithIdRequest,
OrganizationUserResetPasswordEntry
> {
constructor(
private keyService: KeyService,
private encryptService: EncryptService,
@@ -47,7 +44,8 @@ export class OrganizationUserResetPasswordService
private organizationUserApiService: OrganizationUserApiService,
private organizationApiService: OrganizationApiServiceAbstraction,
private i18nService: I18nService,
) {}
private masterPasswordService: MasterPasswordServiceAbstraction,
) { }
/**
* Builds a recovery key for a user to recover their account.
@@ -109,48 +107,34 @@ export class OrganizationUserResetPasswordService
if (response == null) {
throw new Error(this.i18nService.t("resetPasswordDetailsError"));
}
// TODO: Salt should come from server, since it will be decoupled from email
const salt = email.toLowerCase().trim() as MasterPasswordSalt;
// Decrypt Organization's encrypted Private Key with org key
const orgSymKey = await this.keyService.getOrgKey(orgId);
if (orgSymKey == null) {
throw new Error("No org key found");
}
const decPrivateKey = await this.encryptService.unwrapDecapsulationKey(
new EncString(response.encryptedPrivateKey),
EncString.fromEncryptedString(response.encryptedPrivateKey),
orgSymKey,
);
// Decrypt User's Reset Password Key to get UserKey
const userKey = await this.encryptService.decapsulateKeyUnsigned(
new EncString(response.resetPasswordKey),
EncString.fromEncryptedString(response.resetPasswordKey),
decPrivateKey,
);
const existingUserKey = userKey as UserKey;
) as UserKey;
// determine Kdf Algorithm
const kdfConfig: KdfConfig =
response.kdf === KdfType.PBKDF2_SHA256
? new PBKDF2KdfConfig(response.kdfIterations)
: new Argon2KdfConfig(response.kdfIterations, response.kdfMemory, response.kdfParallelism);
// Create new master key and hash new password
const newMasterKey = await this.keyService.makeMasterKey(
newMasterPassword,
email.trim().toLowerCase(),
kdfConfig,
);
const newMasterKeyHash = await this.keyService.hashMasterKey(newMasterPassword, newMasterKey);
// Create new encrypted user key for the User
const newUserKey = await this.keyService.encryptUserKeyWithMasterKey(
newMasterKey,
existingUserKey,
);
const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(newMasterPassword, response.kdf, salt);
const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(newMasterPassword, response.kdf, salt, userKey);
// Create request
const request = new OrganizationUserResetPasswordRequest();
request.key = newUserKey[1].encryptedString;
request.newMasterPasswordHash = newMasterKeyHash;
const request = new OrganizationUserResetPasswordRequest(
authenticationData,
unlockData,
);
// Change user's password
await this.organizationUserApiService.putOrganizationUserResetPassword(

View File

@@ -184,7 +184,7 @@ describe("WebRegistrationFinishService", () => {
newMasterKey: masterKey,
newServerMasterKeyHash: "newServerMasterKeyHash",
newLocalMasterKeyHash: "newLocalMasterKeyHash",
kdfConfig: DEFAULT_KDF_CONFIG,
kdf: DEFAULT_KDF_CONFIG,
newPasswordHint: "newPasswordHint",
newPassword: "newPassword",
};
@@ -234,8 +234,8 @@ describe("WebRegistrationFinishService", () => {
publicKey: userKeyPair[0],
encryptedPrivateKey: userKeyPair[1].encryptedString,
},
kdf: passwordInputResult.kdfConfig.kdfType,
kdfIterations: passwordInputResult.kdfConfig.iterations,
kdf: passwordInputResult.kdf.kdfType,
kdfIterations: passwordInputResult.kdf.iterations,
kdfMemory: undefined,
kdfParallelism: undefined,
orgInviteToken: undefined,
@@ -270,8 +270,8 @@ describe("WebRegistrationFinishService", () => {
publicKey: userKeyPair[0],
encryptedPrivateKey: userKeyPair[1].encryptedString,
},
kdf: passwordInputResult.kdfConfig.kdfType,
kdfIterations: passwordInputResult.kdfConfig.iterations,
kdf: passwordInputResult.kdf.kdfType,
kdfIterations: passwordInputResult.kdf.iterations,
kdfMemory: undefined,
kdfParallelism: undefined,
orgInviteToken: orgInvite.token,
@@ -311,8 +311,8 @@ describe("WebRegistrationFinishService", () => {
publicKey: userKeyPair[0],
encryptedPrivateKey: userKeyPair[1].encryptedString,
},
kdf: passwordInputResult.kdfConfig.kdfType,
kdfIterations: passwordInputResult.kdfConfig.iterations,
kdf: passwordInputResult.kdf.kdfType,
kdfIterations: passwordInputResult.kdf.iterations,
kdfMemory: undefined,
kdfParallelism: undefined,
orgInviteToken: undefined,
@@ -354,8 +354,8 @@ describe("WebRegistrationFinishService", () => {
publicKey: userKeyPair[0],
encryptedPrivateKey: userKeyPair[1].encryptedString,
},
kdf: passwordInputResult.kdfConfig.kdfType,
kdfIterations: passwordInputResult.kdfConfig.iterations,
kdf: passwordInputResult.kdf.kdfType,
kdfIterations: passwordInputResult.kdf.iterations,
kdfMemory: undefined,
kdfParallelism: undefined,
orgInviteToken: undefined,
@@ -399,8 +399,8 @@ describe("WebRegistrationFinishService", () => {
publicKey: userKeyPair[0],
encryptedPrivateKey: userKeyPair[1].encryptedString,
},
kdf: passwordInputResult.kdfConfig.kdfType,
kdfIterations: passwordInputResult.kdfConfig.iterations,
kdf: passwordInputResult.kdf.kdfType,
kdfIterations: passwordInputResult.kdf.iterations,
kdfMemory: undefined,
kdfParallelism: undefined,
orgInviteToken: undefined,

View File

@@ -1,130 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5">
<div class="col-5">
<p class="lead text-center mb-4">{{ "setMasterPassword" | i18n }}</p>
<div class="card d-block">
<div class="card-body text-center" *ngIf="syncLoading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<div class="card-body" *ngIf="!syncLoading">
<p
*ngIf="
forceSetPasswordReason ==
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission;
else defaultCardDesc
"
>
{{ "orgPermissionsUpdatedMustSetPassword" | i18n }}
</p>
<ng-template #defaultCardDesc>
<p>{{ "orgRequiresYouToSetPassword" | i18n }}</p>
</ng-template>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<div class="form-group">
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</auth-password-callout>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<div class="d-flex">
<div class="w-100">
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordHash"
class="text-monospace form-control mb-1"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
<app-password-strength
[password]="masterPassword"
[email]="email"
[showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
>
</app-password-strength>
</div>
<div>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
<div class="progress-bar invisible"></div>
</div>
</div>
<small class="form-text text-muted">{{ "masterPassDesc" | i18n }}</small>
</div>
<div class="form-group">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<div class="d-flex">
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="text-monospace form-control"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
<button
type="button"
class="ml-1 btn btn-link"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="form-group">
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" class="form-control" type="text" name="Hint" [(ngModel)]="hint" />
<small class="form-text text-muted">{{ "masterPassHintDesc" | i18n }}</small>
</div>
<hr />
<div class="d-flex">
<button
type="submit"
class="btn btn-primary btn-block btn-submit"
[disabled]="form.loading"
>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "submit" | i18n }}</span>
</button>
<button
type="button"
class="btn btn-outline-secondary btn-block ml-2 mt-0"
(click)="logOut()"
>
{{ "logOut" | i18n }}
</button>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,30 +0,0 @@
import { Component, inject } from "@angular/core";
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { RouterService } from "../core";
@Component({
selector: "app-set-password",
templateUrl: "set-password.component.html",
standalone: false,
})
export class SetPasswordComponent extends BaseSetPasswordComponent {
routerService = inject(RouterService);
organizationInviteService = inject(OrganizationInviteService);
protected override async onSetPasswordSuccess(
masterKey: MasterKey,
userKey: [UserKey, EncString],
keyPair: [string, EncString],
): Promise<void> {
await super.onSetPasswordSuccess(masterKey, userKey, keyPair);
// SSO JIT accepts org invites when setting their MP, meaning
// we can clear the deep linked url for accepting it.
await this.routerService.getAndClearLoginRedirectUrl();
await this.organizationInviteService.clearOrganizationInvitation();
}
}

View File

@@ -8,6 +8,8 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p
import { EmailTokenRequest } from "@bitwarden/common/auth/models/request/email-token.request";
import { EmailRequest } from "@bitwarden/common/auth/models/request/email.request";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { UserId } from "@bitwarden/common/types/guid";
@@ -43,7 +45,8 @@ export class ChangeEmailComponent implements OnInit {
private formBuilder: FormBuilder,
private kdfConfigService: KdfConfigService,
private toastService: ToastService,
) {}
private masterPasswordService: MasterPasswordServiceAbstraction,
) { }
async ngOnInit() {
this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
@@ -79,15 +82,20 @@ export class ChangeEmailComponent implements OnInit {
throw new Error("Missing email or password");
}
const existingHash = await this.keyService.hashMasterKey(
masterPassword,
await this.keyService.getOrDeriveMasterKey(masterPassword, this.userId),
);
const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(this.userId));
if (kdfConfig == null) {
throw new Error("Missing kdf config");
}
const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(this.userId));
if (salt == null) {
throw new Error("Missing salt");
}
const existingAuthenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(masterPassword, kdfConfig, salt);
if (!this.tokenSent) {
const request = new EmailTokenRequest();
request.newEmail = newEmail;
request.masterPasswordHash = existingHash;
request.masterPasswordHash = existingAuthenticationData.masterPasswordAuthenticationHash;
await this.apiService.postEmailToken(request);
this.activateStep2();
} else {
@@ -95,31 +103,29 @@ export class ChangeEmailComponent implements OnInit {
if (token == null) {
throw new Error("Missing token");
}
const request = new EmailRequest();
request.token = token;
request.newEmail = newEmail;
request.masterPasswordHash = existingHash;
const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(this.userId));
if (kdfConfig == null) {
throw new Error("Missing kdf config");
}
const newMasterKey = await this.keyService.makeMasterKey(masterPassword, newEmail, kdfConfig);
request.newMasterPasswordHash = await this.keyService.hashMasterKey(
masterPassword,
newMasterKey,
);
const userKey = await firstValueFrom(this.keyService.userKey$(this.userId));
if (userKey == null) {
throw new Error("Can't find UserKey");
}
const newUserKey = await this.keyService.encryptUserKeyWithMasterKey(newMasterKey, userKey);
const encryptedUserKey = newUserKey[1]?.encryptedString;
if (encryptedUserKey == null) {
throw new Error("Missing Encrypted User Key");
}
request.key = encryptedUserKey;
const newSalt = newEmail.toLowerCase().trim() as MasterPasswordSalt;
const newAuthenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(
masterPassword,
kdfConfig,
newSalt,
);
const newUnlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(
masterPassword,
kdfConfig,
newSalt,
userKey
);
const request = new EmailRequest(newAuthenticationData, newUnlockData);
request.token = token;
request.newEmail = newEmail;
request.masterPasswordHash = existingAuthenticationData.masterPasswordAuthenticationHash;
await this.apiService.postEmail(request);
this.reset();

View File

@@ -1,129 +0,0 @@
<div class="tabbed-header">
<h1>{{ "changeMasterPassword" | i18n }}</h1>
</div>
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</auth-password-callout>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
class="tw-mb-14"
>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="currentMasterPassword">{{ "currentMasterPass" | i18n }}</label>
<input
id="currentMasterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[(ngModel)]="currentMasterPassword"
required
appInputVerbatim
/>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="newMasterPassword">{{ "newMasterPass" | i18n }}</label>
<input
id="newMasterPassword"
type="password"
name="NewMasterPasswordHash"
class="form-control mb-1"
[(ngModel)]="masterPassword"
required
appInputVerbatim
autocomplete="new-password"
/>
<bit-hint>
<span class="tw-font-semibold">{{ "important" | i18n }}</span>
{{ "masterPassImportant" | i18n }} {{ characterMinimumMessage }}
</bit-hint>
<app-password-strength
[password]="masterPassword"
[email]="email"
[showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
>
</app-password-strength>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="form-control"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="checkForBreaches"
name="checkForBreaches"
[(ngModel)]="checkForBreaches"
/>
<label class="form-check-label" for="checkForBreaches">
{{ "checkForBreaches" | i18n }}
</label>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="rotateUserKey"
name="RotateUserKey"
[(ngModel)]="rotateUserKey"
(change)="rotateUserKeyClicked()"
/>
<label class="form-check-label" for="rotateUserKey">
{{ "rotateAccountEncKey" | i18n }}
</label>
<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>
</div>
</div>
<div class="form-group">
<label for="masterPasswordHint">{{ "newMasterPassHint" | i18n }}</label>
<input
id="masterPasswordHint"
class="form-control"
maxlength="50"
type="text"
name="MasterPasswordHint"
[(ngModel)]="masterPasswordHint"
/>
</div>
<button type="submit" buttonType="primary" bitButton [loading]="loading">
{{ "changeMasterPassword" | i18n }}
</button>
</form>
<app-webauthn-login-settings></app-webauthn-login-settings>

View File

@@ -1,258 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom, map } from "rxjs";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService, ToastService } from "@bitwarden/components";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
import { UserKeyRotationService } from "../../key-management/key-rotation/user-key-rotation.service";
/**
* @deprecated use the auth `PasswordSettingsComponent` instead
*/
@Component({
selector: "app-change-password",
templateUrl: "change-password.component.html",
standalone: false,
})
export class ChangePasswordComponent
extends BaseChangePasswordComponent
implements OnInit, OnDestroy
{
loading = false;
rotateUserKey = false;
currentMasterPassword: string;
masterPasswordHint: string;
checkForBreaches = true;
characterMinimumMessage = "";
constructor(
private auditService: AuditService,
private cipherService: CipherService,
private keyRotationService: UserKeyRotationService,
private masterPasswordApiService: MasterPasswordApiService,
private router: Router,
private syncService: SyncService,
private userVerificationService: UserVerificationService,
protected accountService: AccountService,
protected dialogService: DialogService,
protected i18nService: I18nService,
protected kdfConfigService: KdfConfigService,
protected keyService: KeyService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected messagingService: MessagingService,
protected platformUtilsService: PlatformUtilsService,
protected policyService: PolicyService,
protected toastService: ToastService,
) {
super(
accountService,
dialogService,
i18nService,
kdfConfigService,
keyService,
masterPasswordService,
messagingService,
platformUtilsService,
policyService,
toastService,
);
}
async ngOnInit() {
if (!(await this.userVerificationService.hasMasterPassword())) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/settings/security/two-factor"]);
}
await super.ngOnInit();
this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength);
}
async rotateUserKeyClicked() {
if (this.rotateUserKey) {
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const ciphers = await this.cipherService.getAllDecrypted(activeUserId);
let hasOldAttachments = false;
if (ciphers != null) {
for (let i = 0; i < ciphers.length; i++) {
if (ciphers[i].organizationId == null && ciphers[i].hasOldAttachments) {
hasOldAttachments = true;
break;
}
}
}
if (hasOldAttachments) {
const learnMore = await this.dialogService.openSimpleDialog({
title: { key: "warning" },
content: { key: "oldAttachmentsNeedFixDesc" },
acceptButtonText: { key: "learnMore" },
cancelButtonText: { key: "close" },
type: "warning",
});
if (learnMore) {
this.platformUtilsService.launchUri(
"https://bitwarden.com/help/attachments/#add-storage-space",
);
}
this.rotateUserKey = false;
return;
}
const result = await this.dialogService.openSimpleDialog({
title: { key: "rotateEncKeyTitle" },
content:
this.i18nService.t("updateEncryptionKeyWarning") +
" " +
this.i18nService.t("updateEncryptionKeyAccountExportWarning") +
" " +
this.i18nService.t("rotateEncKeyConfirmation"),
type: "warning",
});
if (!result) {
this.rotateUserKey = false;
}
}
}
async submit() {
this.loading = true;
if (this.currentMasterPassword == null || this.currentMasterPassword === "") {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordRequired"),
});
this.loading = false;
return;
}
if (
this.masterPasswordHint != null &&
this.masterPasswordHint.toLowerCase() === this.masterPassword.toLowerCase()
) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("hintEqualsPassword"),
});
this.loading = false;
return;
}
this.leakedPassword = false;
if (this.checkForBreaches) {
this.leakedPassword = (await this.auditService.passwordLeaked(this.masterPassword)) > 0;
}
if (!(await this.strongPassword())) {
this.loading = false;
return;
}
try {
if (this.rotateUserKey) {
await this.syncService.fullSync(true);
const user = await firstValueFrom(this.accountService.activeAccount$);
await this.keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData(
this.currentMasterPassword,
this.masterPassword,
user,
this.masterPasswordHint,
);
} else {
await this.updatePassword(this.masterPassword);
}
} catch (e) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: e.message,
});
} finally {
this.loading = false;
}
}
// todo: move this to a service
// https://bitwarden.atlassian.net/browse/PM-17108
private async updatePassword(newMasterPassword: string) {
const currentMasterPassword = this.currentMasterPassword;
const { userId, email } = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => ({ userId: a?.id, email: a?.email }))),
);
const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(userId));
const currentMasterKey = await this.keyService.makeMasterKey(
currentMasterPassword,
email,
kdfConfig,
);
const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
currentMasterKey,
userId,
);
if (decryptedUserKey == null) {
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("invalidMasterPassword"),
});
return;
}
const newMasterKey = await this.keyService.makeMasterKey(newMasterPassword, email, kdfConfig);
const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(
newMasterKey,
decryptedUserKey,
);
const request = new PasswordRequest();
request.masterPasswordHash = await this.keyService.hashMasterKey(
this.currentMasterPassword,
currentMasterKey,
);
request.masterPasswordHint = this.masterPasswordHint;
request.newMasterPasswordHash = await this.keyService.hashMasterKey(
newMasterPassword,
newMasterKey,
);
request.key = newMasterKeyEncryptedUserKey[1].encryptedString;
try {
await this.masterPasswordApiService.postPassword(request);
this.toastService.showToast({
variant: "success",
message: this.i18nService.t("masterPasswordChanged"),
});
this.messagingService.send("logout");
} catch {
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
}
}
}

View File

@@ -40,10 +40,6 @@ import {
EmergencyAccessTakeoverDialogComponent,
EmergencyAccessTakeoverDialogResultType,
} from "./takeover/emergency-access-takeover-dialog.component";
import {
EmergencyAccessTakeoverComponent,
EmergencyAccessTakeoverResultType,
} from "./takeover/emergency-access-takeover.component";
@Component({
selector: "emergency-access",

View File

@@ -1,54 +0,0 @@
<form [formGroup]="takeoverForm" [bitSubmit]="submit">
<bit-dialog dialogSize="large">
<span bitDialogTitle>
{{ "takeover" | i18n }}
<small class="tw-text-muted" *ngIf="params.name">{{ params.name }}</small>
</span>
<div bitDialogContent>
<bit-callout type="warning">{{ "loggedOutWarning" | i18n }}</bit-callout>
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</auth-password-callout>
<div class="tw-w-full tw-flex tw-gap-4">
<div class="tw-relative tw-flex-1">
<bit-form-field disableMargin class="tw-mb-2">
<bit-label>{{ "newMasterPass" | i18n }}</bit-label>
<input
bitInput
type="password"
autocomplete="new-password"
formControlName="masterPassword"
/>
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
</bit-form-field>
<app-password-strength
[password]="takeoverForm.value.masterPassword"
[email]="email"
[showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
>
</app-password-strength>
</div>
<div class="tw-relative tw-flex-1">
<bit-form-field disableMargin class="tw-mb-2">
<bit-label>{{ "confirmNewMasterPass" | i18n }}</bit-label>
<input
bitInput
type="password"
autocomplete="new-password"
formControlName="masterPasswordRetype"
/>
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
</bit-form-field>
</div>
</div>
</div>
<ng-container bitDialogFooter>
<button type="submit" bitButton bitFormButton buttonType="primary">
{{ "save" | i18n }}
</button>
<button bitButton bitFormButton type="button" buttonType="secondary" bitDialogClose>
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>

View File

@@ -1,145 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnDestroy, OnInit, Inject, Input } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { switchMap, takeUntil } from "rxjs";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import {
DialogConfig,
DialogRef,
DIALOG_DATA,
DialogService,
ToastService,
} from "@bitwarden/components";
import { KdfType, KdfConfigService, KeyService } from "@bitwarden/key-management";
import { EmergencyAccessService } from "../../../emergency-access";
// FIXME: update to use a const object instead of a typescript enum
// eslint-disable-next-line @bitwarden/platform/no-enums
export enum EmergencyAccessTakeoverResultType {
Done = "done",
}
type EmergencyAccessTakeoverDialogData = {
/** display name of the account requesting emergency access takeover */
name: string;
/** email of the account requesting emergency access takeover */
email: string;
/** traces a unique emergency request */
emergencyAccessId: string;
};
@Component({
selector: "emergency-access-takeover",
templateUrl: "emergency-access-takeover.component.html",
standalone: false,
})
export class EmergencyAccessTakeoverComponent
extends ChangePasswordComponent
implements OnInit, OnDestroy
{
@Input() kdf: KdfType;
@Input() kdfIterations: number;
takeoverForm = this.formBuilder.group({
masterPassword: ["", [Validators.required]],
masterPasswordRetype: ["", [Validators.required]],
});
constructor(
@Inject(DIALOG_DATA) protected params: EmergencyAccessTakeoverDialogData,
private formBuilder: FormBuilder,
i18nService: I18nService,
keyService: KeyService,
messagingService: MessagingService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
private emergencyAccessService: EmergencyAccessService,
private logService: LogService,
dialogService: DialogService,
private dialogRef: DialogRef<EmergencyAccessTakeoverResultType>,
kdfConfigService: KdfConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
protected toastService: ToastService,
) {
super(
accountService,
dialogService,
i18nService,
kdfConfigService,
keyService,
masterPasswordService,
messagingService,
platformUtilsService,
policyService,
toastService,
);
}
async ngOnInit() {
const policies = await this.emergencyAccessService.getGrantorPolicies(
this.params.emergencyAccessId,
);
this.accountService.activeAccount$
.pipe(
getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)),
takeUntil(this.destroy$),
)
.subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions));
}
ngOnDestroy(): void {
super.ngOnDestroy();
}
submit = async () => {
if (this.takeoverForm.invalid) {
this.takeoverForm.markAllAsTouched();
return;
}
this.masterPassword = this.takeoverForm.get("masterPassword").value;
this.masterPasswordRetype = this.takeoverForm.get("masterPasswordRetype").value;
if (!(await this.strongPassword())) {
return;
}
try {
await this.emergencyAccessService.takeover(
this.params.emergencyAccessId,
this.masterPassword,
this.params.email,
);
} catch (e) {
this.logService.error(e);
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("unexpectedError"),
});
}
this.dialogRef.close(EmergencyAccessTakeoverResultType.Done);
};
/**
* Strongly typed helper to open a EmergencyAccessTakeoverComponent
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Configuration for the dialog
*/
static open = (
dialogService: DialogService,
config: DialogConfig<EmergencyAccessTakeoverDialogData>,
) => {
return dialogService.open<EmergencyAccessTakeoverResultType>(
EmergencyAccessTakeoverComponent,
config,
);
};
}

View File

@@ -4,14 +4,12 @@ import { Component, Inject } from "@angular/core";
import { FormGroup, FormControl, Validators } from "@angular/forms";
import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfRequest } from "@bitwarden/common/models/request/kdf.request";
import { ChangeKdfServiceAbstraction } from "@bitwarden/common/key-management/kdf/abstractions/change-kdf-service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DIALOG_DATA, ToastService } from "@bitwarden/components";
import { KdfConfig, KdfType, KeyService } from "@bitwarden/key-management";
import { KdfConfig, KdfType } from "@bitwarden/key-management";
@Component({
selector: "app-change-kdf-confirmation",
@@ -29,14 +27,12 @@ export class ChangeKdfConfirmationComponent {
loading = false;
constructor(
private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private keyService: KeyService,
private messagingService: MessagingService,
@Inject(DIALOG_DATA) params: { kdf: KdfType; kdfConfig: KdfConfig },
private accountService: AccountService,
private toastService: ToastService,
private changeKdfService: ChangeKdfServiceAbstraction,
) {
this.kdfConfig = params.kdfConfig;
this.masterPassword = null;
@@ -63,27 +59,7 @@ export class ChangeKdfConfirmationComponent {
// Ensure the KDF config is valid.
this.kdfConfig.validateKdfConfigForSetting();
const request = new KdfRequest();
request.kdf = this.kdfConfig.kdfType;
request.kdfIterations = this.kdfConfig.iterations;
if (this.kdfConfig.kdfType === KdfType.Argon2id) {
request.kdfMemory = this.kdfConfig.memory;
request.kdfParallelism = this.kdfConfig.parallelism;
}
const masterKey = await this.keyService.getOrDeriveMasterKey(masterPassword);
request.masterPasswordHash = await this.keyService.hashMasterKey(masterPassword, masterKey);
const email = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
);
const newMasterKey = await this.keyService.makeMasterKey(masterPassword, email, this.kdfConfig);
request.newMasterPasswordHash = await this.keyService.hashMasterKey(
masterPassword,
newMasterKey,
);
const newUserKey = await this.keyService.encryptUserKeyWithMasterKey(newMasterKey);
request.key = newUserKey[1].encryptedString;
await this.apiService.postAccountKdf(request);
const activeAccountId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
await this.changeKdfService.updateUserKdfParams(masterPassword, this.kdfConfig, activeAccountId);
}
}

View File

@@ -2,7 +2,6 @@ import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
import { InputPasswordFlow } from "@bitwarden/auth/angular";
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
import { CalloutModule } from "@bitwarden/components";
@@ -13,7 +12,7 @@ import { WebauthnLoginSettingsModule } from "../../webauthn-login-settings";
@Component({
selector: "app-password-settings",
templateUrl: "password-settings.component.html",
imports: [CalloutModule, ChangePasswordComponent, I18nPipe, WebauthnLoginSettingsModule],
imports: [CalloutModule, I18nPipe, WebauthnLoginSettingsModule],
})
export class PasswordSettingsComponent implements OnInit {
inputPasswordFlow = InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation;
@@ -22,7 +21,7 @@ export class PasswordSettingsComponent implements OnInit {
constructor(
private router: Router,
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
) {}
) { }
async ngOnInit() {
const userHasMasterPassword = await firstValueFrom(

View File

@@ -6,7 +6,6 @@ import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag
import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ChangePasswordComponent } from "../change-password.component";
import { TwoFactorSetupComponent } from "../two-factor/two-factor-setup.component";
import { DeviceManagementOldComponent } from "./device-management-old.component";
@@ -21,19 +20,6 @@ const routes: Routes = [
data: { titleId: "security" },
children: [
{ path: "", pathMatch: "full", redirectTo: "password" },
{
path: "change-password",
component: ChangePasswordComponent,
canActivate: [
canAccessFeature(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
false,
"/settings/security/password",
false,
),
],
data: { titleId: "masterPassword" },
},
{
path: "password",
component: PasswordSettingsComponent,
@@ -74,4 +60,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class SecurityRoutingModule {}
export class SecurityRoutingModule { }

View File

@@ -6,7 +6,6 @@ import { UserKeyRotationModule } from "../../key-management/key-rotation/user-ke
import { SharedModule } from "../../shared";
import { EmergencyAccessModule } from "../emergency-access";
import { ChangePasswordComponent } from "./change-password.component";
import { WebauthnLoginSettingsModule } from "./webauthn-login-settings";
@NgModule({
@@ -17,8 +16,8 @@ import { WebauthnLoginSettingsModule } from "./webauthn-login-settings";
PasswordCalloutComponent,
UserKeyRotationModule,
],
declarations: [ChangePasswordComponent],
declarations: [],
providers: [],
exports: [ChangePasswordComponent],
exports: [],
})
export class AuthSettingsModule {}
export class AuthSettingsModule { }

View File

@@ -7,6 +7,7 @@ import { VerificationType } from "@bitwarden/common/auth/enums/verification-type
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request";
import { AuthResponseBase } from "@bitwarden/common/auth/types/auth-response";
import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -37,7 +38,7 @@ export abstract class TwoFactorSetupMethodBaseComponent {
protected userVerificationService: UserVerificationService,
protected dialogService: DialogService,
protected toastService: ToastService,
) {}
) { }
protected auth(authResponse: AuthResponseBase) {
this.hashedSecret = authResponse.secret;
@@ -132,7 +133,7 @@ export abstract class TwoFactorSetupMethodBaseComponent {
}
return this.userVerificationService.buildRequest(
{
secret: this.hashedSecret,
secret: this.hashedSecret as MasterPasswordAuthenticationHash,
type: this.verificationType,
},
requestClass,

View File

@@ -1,90 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="row justify-content-md-center mt-5">
<div class="col-4">
<p class="lead text-center mb-4">{{ "updateMasterPassword" | i18n }}</p>
<div class="card d-block">
<div class="card-body">
<app-callout type="warning">{{ "masterPasswordInvalidWarning" | i18n }} </app-callout>
<auth-password-callout
[policy]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
></auth-password-callout>
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="currentMasterPassword">{{ "currentMasterPass" | i18n }}</label>
<input
id="currentMasterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[(ngModel)]="currentMasterPassword"
required
appInputVerbatim
/>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="form-group">
<label for="newMasterPassword">{{ "newMasterPass" | i18n }}</label>
<input
id="newMasterPassword"
type="password"
name="NewMasterPasswordHash"
class="form-control mb-1"
[(ngModel)]="masterPassword"
required
appInputVerbatim
autocomplete="new-password"
/>
<app-password-strength
[password]="masterPassword"
[email]="email"
[showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
></app-password-strength>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="form-control"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "changeMasterPassword" | i18n }}</span>
</button>
<button (click)="cancel()" type="button" class="btn btn-outline-secondary">
<span>{{ "cancel" | i18n }}</span>
</button>
</form>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,24 +0,0 @@
import { Component, inject } from "@angular/core";
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component";
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
import { RouterService } from "../core";
@Component({
selector: "app-update-password",
templateUrl: "update-password.component.html",
standalone: false,
})
export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
private routerService = inject(RouterService);
private organizationInviteService = inject(OrganizationInviteService);
override async cancel() {
// clearing the login redirect url so that the user
// does not join the organization if they cancel
await this.routerService.getAndClearLoginRedirectUrl();
await this.organizationInviteService.clearOrganizationInvitation();
await super.cancel();
}
}

View File

@@ -1,96 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<div class="tw-mt-12 tw-flex tw-justify-center">
<div class="tw-w-1/3">
<h1 bitTypography="h1" class="tw-mb-4 tw-text-center">{{ "updateMasterPassword" | i18n }}</h1>
<div
class="tw-block tw-rounded tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-8"
>
<app-callout type="warning">{{ masterPasswordWarningText }} </app-callout>
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</auth-password-callout>
<bit-form-field *ngIf="requireCurrentPassword">
<bit-label>{{ "currentMasterPass" | i18n }}</bit-label>
<input
bitInput
type="password"
appInputVerbatim
required
[(ngModel)]="verification.secret"
name="currentMasterPassword"
id="currentMasterPassword"
[appAutofocus]="requireCurrentPassword"
/>
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
</bit-form-field>
<div class="tw-mb-4">
<bit-form-field class="!tw-mb-1">
<bit-label>{{ "newMasterPass" | i18n }}</bit-label>
<input
bitInput
type="password"
appInputVerbatim
required
[(ngModel)]="masterPassword"
name="masterPassword"
id="masterPassword"
/>
<button
type="button"
bitIconButton
bitSuffix
bitPasswordInputToggle
[(toggled)]="showPassword"
></button>
</bit-form-field>
<app-password-strength
[password]="masterPassword"
[email]="email"
[showText]="true"
(passwordStrengthResult)="getStrengthResult($event)"
>
</app-password-strength>
</div>
<bit-form-field>
<bit-label>{{ "confirmNewMasterPass" | i18n }}</bit-label>
<input
bitInput
type="password"
appInputVerbatim
required
[(ngModel)]="masterPasswordRetype"
name="masterPasswordRetype"
id="masterPasswordRetype"
/>
<button
type="button"
bitIconButton
bitSuffix
bitPasswordInputToggle
[(toggled)]="showPassword"
></button>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "masterPassHint" | i18n }}</bit-label>
<input bitInput type="text" [(ngModel)]="hint" name="hint" id="hint" />
<bit-hint>{{ "masterPassHintDesc" | i18n }}</bit-hint>
</bit-form-field>
<hr />
<div class="tw-flex tw-space-x-2">
<button
type="submit"
bitButton
[block]="true"
buttonType="primary"
[loading]="form.loading"
[disabled]="form.loading"
>
{{ "submit" | i18n }}
</button>
<button type="button" bitButton [block]="true" buttonType="secondary" (click)="logOut()">
{{ "logOut" | i18n }}
</button>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,10 +0,0 @@
import { Component } from "@angular/core";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "@bitwarden/angular/auth/components/update-temp-password.component";
@Component({
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
standalone: false,
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {}

View File

@@ -10,7 +10,6 @@ import {
unauthGuardFn,
activeAuthGuard,
} from "@bitwarden/angular/auth/guards";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import {
@@ -19,7 +18,6 @@ import {
RegistrationStartComponent,
RegistrationStartSecondaryComponent,
RegistrationStartSecondaryComponentData,
SetPasswordJitComponent,
RegistrationLinkExpiredComponent,
LoginComponent,
LoginSecondaryContentComponent,
@@ -55,13 +53,10 @@ import { LoginViaWebAuthnComponent } from "./auth/login/login-via-webauthn/login
import { AcceptOrganizationComponent } from "./auth/organization-invite/accept-organization.component";
import { RecoverDeleteComponent } from "./auth/recover-delete.component";
import { RecoverTwoFactorComponent } from "./auth/recover-two-factor.component";
import { SetPasswordComponent } from "./auth/set-password.component";
import { AccountComponent } from "./auth/settings/account/account.component";
import { EmergencyAccessComponent } from "./auth/settings/emergency-access/emergency-access.component";
import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/view/emergency-access-view.component";
import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module";
import { UpdatePasswordComponent } from "./auth/update-password.component";
import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component";
import { SponsoredFamiliesComponent } from "./billing/settings/sponsored-families.component";
@@ -114,11 +109,6 @@ const routes: Routes = [
component: LoginViaWebAuthnComponent,
data: { titleId: "logInWithPasskey" } satisfies RouteDataProperties,
},
{
path: "set-password",
component: SetPasswordComponent,
data: { titleId: "setMasterPassword" } satisfies RouteDataProperties,
},
{ path: "verify-email", component: VerifyEmailTokenComponent },
{
path: "accept-organization",
@@ -142,34 +132,6 @@ const routes: Routes = [
canActivate: [unauthGuardFn()],
data: { titleId: "deleteOrganization" },
},
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [
canAccessFeature(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
false,
"change-password",
false,
),
authGuard,
],
data: { titleId: "updateTempPassword" } satisfies RouteDataProperties,
},
{
path: "update-password",
component: UpdatePasswordComponent,
canActivate: [
canAccessFeature(
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
false,
"change-password",
false,
),
authGuard,
],
data: { titleId: "updatePassword" } satisfies RouteDataProperties,
},
],
},
{
@@ -334,18 +296,6 @@ const routes: Routes = [
maxWidth: "lg",
} satisfies AnonLayoutWrapperData,
},
{
path: "set-password-jit",
component: SetPasswordJitComponent,
data: {
pageTitle: {
key: "joinOrganization",
},
pageSubtitle: {
key: "finishJoiningThisOrganizationBySettingAMasterPassword",
},
} satisfies AnonLayoutWrapperData,
},
{
path: "signup-link-expired",
canActivate: [unauthGuardFn()],
@@ -597,14 +547,6 @@ const routes: Routes = [
},
],
},
{
path: "change-password",
component: ChangePasswordComponent,
canActivate: [
canAccessFeature(FeatureFlag.PM16117_ChangeExistingPasswordRefactor),
authGuard,
],
},
{
path: "setup-extension",
data: {
@@ -756,13 +698,13 @@ const routes: Routes = [
],
exports: [RouterModule],
})
export class OssRoutingModule {}
export class OssRoutingModule { }
export function buildFlaggedRoute(flagName: keyof Flags, route: Route): Route {
return flagEnabled(flagName)
? route
: {
path: route.path,
redirectTo: "/",
};
path: route.path,
redirectTo: "/",
};
}

View File

@@ -14,16 +14,12 @@ import { VerifyRecoverDeleteOrgComponent } from "../admin-console/organizations/
import { AcceptFamilySponsorshipComponent } from "../admin-console/organizations/sponsorships/accept-family-sponsorship.component";
import { RecoverDeleteComponent } from "../auth/recover-delete.component";
import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component";
import { SetPasswordComponent } from "../auth/set-password.component";
import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component";
import { EmergencyAccessConfirmComponent } from "../auth/settings/emergency-access/confirm/emergency-access-confirm.component";
import { EmergencyAccessAddEditComponent } from "../auth/settings/emergency-access/emergency-access-add-edit.component";
import { EmergencyAccessComponent } from "../auth/settings/emergency-access/emergency-access.component";
import { EmergencyAccessTakeoverComponent } from "../auth/settings/emergency-access/takeover/emergency-access-takeover.component";
import { EmergencyAccessViewComponent } from "../auth/settings/emergency-access/view/emergency-access-view.component";
import { UserVerificationModule } from "../auth/shared/components/user-verification";
import { UpdatePasswordComponent } from "../auth/update-password.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component";
import { FreeBitwardenFamiliesComponent } from "../billing/members/free-bitwarden-families.component";
@@ -73,7 +69,6 @@ import { SharedModule } from "./shared.module";
EmergencyAccessAddEditComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
OrgEventsComponent,
OrgExposedPasswordsReportComponent,
@@ -85,12 +80,9 @@ import { SharedModule } from "./shared.module";
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RemovePasswordComponent,
SetPasswordComponent,
SponsoredFamiliesComponent,
FreeBitwardenFamiliesComponent,
SponsoringOrgRowComponent,
UpdatePasswordComponent,
UpdateTempPasswordComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
],
@@ -100,7 +92,6 @@ import { SharedModule } from "./shared.module";
EmergencyAccessAddEditComponent,
EmergencyAccessComponent,
EmergencyAccessConfirmComponent,
EmergencyAccessTakeoverComponent,
EmergencyAccessViewComponent,
OrganizationLayoutComponent,
OrgEventsComponent,
@@ -114,16 +105,13 @@ import { SharedModule } from "./shared.module";
RecoverDeleteComponent,
RecoverTwoFactorComponent,
RemovePasswordComponent,
SetPasswordComponent,
SponsoredFamiliesComponent,
FreeBitwardenFamiliesComponent,
SponsoringOrgRowComponent,
UpdateTempPasswordComponent,
UpdatePasswordComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
HeaderModule,
DangerZoneComponent,
],
})
export class LooseComponentsModule {}
export class LooseComponentsModule { }

View File

@@ -16,6 +16,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -65,7 +66,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
private keyService: KeyService,
private accountService: AccountService,
private linkSsoService: LinkSsoService,
) {}
) { }
async ngOnInit() {
const resetPasswordPolicies$ = this.accountService.activeAccount$.pipe(
@@ -215,7 +216,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
} else {
// Remove reset password
const request = new OrganizationUserResetPasswordEnrollmentRequest();
request.masterPasswordHash = "ignored";
request.masterPasswordHash = "ignored" as MasterPasswordAuthenticationHash;
request.resetPasswordKey = "";
this.actionPromise =
this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(

View File

@@ -20,7 +20,7 @@ export class OrganizationAuthRequestService {
private keyService: KeyService,
private encryptService: EncryptService,
private organizationUserApiService: OrganizationUserApiService,
) {}
) { }
async listPendingRequests(organizationId: string): Promise<PendingAuthRequestView[]> {
return await this.organizationAuthRequestApiService.listPendingRequests(organizationId);

View File

@@ -1,6 +1,14 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { MasterPasswordAuthenticationData, MasterPasswordAuthenticationHash, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types";
export class OrganizationUserResetPasswordRequest {
newMasterPasswordHash: string;
key: string;
/** @deprecated */
newMasterPasswordHash: MasterPasswordAuthenticationHash;
/** @deprecated */
key: EncryptedString;
constructor(masterPasswordAuthenticationData: MasterPasswordAuthenticationData, masterPasswordUnlockData: MasterPasswordUnlockData) {
this.newMasterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash;
this.key = masterPasswordUnlockData.masterKeyWrappedUserKey.encryptedString;
}
}

View File

@@ -1,11 +1,14 @@
// @ts-strict-ignore
import {
OrganizationUserStatusType,
OrganizationUserType,
} from "@bitwarden/common/admin-console/enums";
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
import { SelectionReadOnlyResponse } from "@bitwarden/common/admin-console/models/response/selection-read-only.response";
import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { BaseResponse } from "@bitwarden/common/models/response/base.response";
import { KdfType } from "@bitwarden/key-management";
import { Argon2KdfConfig, KdfConfig, KdfType, PBKDF2KdfConfig } from "@bitwarden/key-management";
export class OrganizationUserResponse extends BaseResponse {
id: string;
@@ -75,20 +78,31 @@ export class OrganizationUserDetailsResponse extends OrganizationUserResponse {
export class OrganizationUserResetPasswordDetailsResponse extends BaseResponse {
organizationUserId: string;
kdf: KdfType;
kdfIterations: number;
kdfMemory?: number;
kdfParallelism?: number;
resetPasswordKey: string;
encryptedPrivateKey: string;
kdf: KdfConfig;
// OrgPublicKeyEncapsulatedUnsignedSharedUserKey
resetPasswordKey: EncryptedString;
// UserKeyEncryptedPrivateKey
encryptedPrivateKey: EncryptedString;
constructor(response: any) {
super(response);
this.organizationUserId = this.getResponseProperty("OrganizationUserId");
this.kdf = this.getResponseProperty("Kdf");
this.kdfIterations = this.getResponseProperty("KdfIterations");
this.kdfMemory = this.getResponseProperty("KdfMemory");
this.kdfParallelism = this.getResponseProperty("KdfParallelism");
const kdf = this.getResponseProperty("Kdf");
const kdfIterations = this.getResponseProperty("KdfIterations");
const kdfMemory = this.getResponseProperty("KdfMemory");
const kdfParallelism = this.getResponseProperty("KdfParallelism");
if (kdf === KdfType.PBKDF2_SHA256) {
this.kdf = new PBKDF2KdfConfig(kdfIterations);
} else if (kdf === KdfType.Argon2id) {
this.kdf = new Argon2KdfConfig(kdfIterations, kdfMemory, kdfParallelism);
} else {
throw new Error(`Unsupported KDF type: ${kdf}`);
}
// "ResetPasswordKey" is just the
this.resetPasswordKey = this.getResponseProperty("ResetPasswordKey");
this.encryptedPrivateKey = this.getResponseProperty("EncryptedPrivateKey");
}

View File

@@ -1,232 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, OnDestroy, OnInit } from "@angular/core";
import { Subject, firstValueFrom, map, switchMap, takeUntil } from "rxjs";
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 { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserKey, MasterKey } from "@bitwarden/common/types/key";
import { DialogService, ToastService } from "@bitwarden/components";
import { KdfConfig, KdfConfigService, KeyService } from "@bitwarden/key-management";
import { PasswordColorText } from "../../tools/password-strength/password-strength.component";
@Directive()
export class ChangePasswordComponent implements OnInit, OnDestroy {
masterPassword: string;
masterPasswordRetype: string;
formPromise: Promise<any>;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
passwordStrengthResult: any;
color: string;
text: string;
leakedPassword: boolean;
minimumLength = Utils.minimumPasswordLength;
protected email: string;
protected kdfConfig: KdfConfig;
protected destroy$ = new Subject<void>();
constructor(
protected accountService: AccountService,
protected dialogService: DialogService,
protected i18nService: I18nService,
protected kdfConfigService: KdfConfigService,
protected keyService: KeyService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected messagingService: MessagingService,
protected platformUtilsService: PlatformUtilsService,
protected policyService: PolicyService,
protected toastService: ToastService,
) {}
async ngOnInit() {
this.email = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
);
this.accountService.activeAccount$
.pipe(
getUserId,
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)),
takeUntil(this.destroy$),
)
.subscribe(
(enforcedPasswordPolicyOptions) =>
(this.enforcedPolicyOptions ??= enforcedPasswordPolicyOptions),
);
if (this.enforcedPolicyOptions?.minLength) {
this.minimumLength = this.enforcedPolicyOptions.minLength;
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
async submit() {
if (!(await this.strongPassword())) {
return;
}
if (!(await this.setupSubmitActions())) {
return;
}
const [userId, email] = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])),
);
if (this.kdfConfig == null) {
this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
}
// Create new master key
const newMasterKey = await this.keyService.makeMasterKey(
this.masterPassword,
email.trim().toLowerCase(),
this.kdfConfig,
);
const newMasterKeyHash = await this.keyService.hashMasterKey(this.masterPassword, newMasterKey);
let newProtectedUserKey: [UserKey, EncString] = null;
const userKey = await this.keyService.getUserKey();
if (userKey == null) {
newProtectedUserKey = await this.keyService.makeUserKey(newMasterKey);
} else {
newProtectedUserKey = await this.keyService.encryptUserKeyWithMasterKey(newMasterKey);
}
await this.performSubmitActions(newMasterKeyHash, newMasterKey, newProtectedUserKey);
}
async setupSubmitActions(): Promise<boolean> {
// Override in sub-class
// Can be used for additional validation and/or other processes the should occur before changing passwords
return true;
}
async performSubmitActions(
newMasterKeyHash: string,
newMasterKey: MasterKey,
newUserKey: [UserKey, EncString],
) {
// Override in sub-class
}
async strongPassword(): Promise<boolean> {
if (this.masterPassword == null || this.masterPassword === "") {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordRequired"),
});
return false;
}
if (this.masterPassword.length < this.minimumLength) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordMinimumlength", this.minimumLength),
});
return false;
}
if (this.masterPassword !== this.masterPasswordRetype) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPassDoesntMatch"),
});
return false;
}
const strengthResult = this.passwordStrengthResult;
if (
this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
strengthResult.score,
this.masterPassword,
this.enforcedPolicyOptions,
)
) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordPolicyRequirementsNotMet"),
});
return false;
}
const weakPassword = strengthResult != null && strengthResult.score < 3;
if (weakPassword && this.leakedPassword) {
const result = await this.dialogService.openSimpleDialog({
title: { key: "weakAndExposedMasterPassword" },
content: { key: "weakAndBreachedMasterPasswordDesc" },
type: "warning",
});
if (!result) {
return false;
}
} else {
if (weakPassword) {
const result = await this.dialogService.openSimpleDialog({
title: { key: "weakMasterPassword" },
content: { key: "weakMasterPasswordDesc" },
type: "warning",
});
if (!result) {
return false;
}
}
if (this.leakedPassword) {
const result = await this.dialogService.openSimpleDialog({
title: { key: "exposedMasterPassword" },
content: { key: "exposedMasterPasswordDesc" },
type: "warning",
});
if (!result) {
return false;
}
}
}
return true;
}
async logOut() {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "logOut" },
content: { key: "logOutConfirmation" },
acceptButtonText: { key: "logOut" },
type: "warning",
});
if (confirmed) {
this.messagingService.send("logout");
}
}
getStrengthResult(result: any) {
this.passwordStrengthResult = result;
}
getPasswordScoreText(event: PasswordColorText) {
this.color = event.color;
this.text = event.text;
}
}

View File

@@ -1,300 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom, of } from "rxjs";
import { filter, first, switchMap, tap } from "rxjs/operators";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import {
OrganizationUserApiService,
OrganizationUserResetPasswordEnrollmentRequest,
} from "@bitwarden/admin-console/common";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-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 { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { OrganizationAutoEnrollStatusResponse } from "@bitwarden/common/admin-console/models/response/organization-auto-enroll-status.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { HashPurpose } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService, ToastService } from "@bitwarden/components";
import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
@Directive()
export class SetPasswordComponent extends BaseChangePasswordComponent implements OnInit {
syncLoading = true;
showPassword = false;
hint = "";
orgSsoIdentifier: string = null;
orgId: string;
resetPasswordAutoEnroll = false;
onSuccessfulChangePassword: () => Promise<void>;
successRoute = "vault";
activeUserId: UserId;
forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None;
ForceSetPasswordReason = ForceSetPasswordReason;
constructor(
protected accountService: AccountService,
protected dialogService: DialogService,
protected encryptService: EncryptService,
protected i18nService: I18nService,
protected kdfConfigService: KdfConfigService,
protected keyService: KeyService,
protected masterPasswordApiService: MasterPasswordApiService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected messagingService: MessagingService,
protected organizationApiService: OrganizationApiServiceAbstraction,
protected organizationUserApiService: OrganizationUserApiService,
protected platformUtilsService: PlatformUtilsService,
protected policyApiService: PolicyApiServiceAbstraction,
protected policyService: PolicyService,
protected route: ActivatedRoute,
protected router: Router,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected syncService: SyncService,
protected toastService: ToastService,
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
) {
super(
accountService,
dialogService,
i18nService,
kdfConfigService,
keyService,
masterPasswordService,
messagingService,
platformUtilsService,
policyService,
toastService,
);
}
async ngOnInit() {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
super.ngOnInit();
await this.syncService.fullSync(true);
this.syncLoading = false;
this.activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
this.forceSetPasswordReason = await firstValueFrom(
this.masterPasswordService.forceSetPasswordReason$(this.activeUserId),
);
this.route.queryParams
.pipe(
first(),
switchMap((qParams) => {
if (qParams.identifier != null) {
return of(qParams.identifier);
} else {
// Try to get orgSsoId from state as fallback
// Note: this is primarily for the TDE user w/out MP obtains admin MP reset permission scenario.
return this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(this.activeUserId);
}
}),
filter((orgSsoId) => orgSsoId != null),
tap((orgSsoId: string) => {
this.orgSsoIdentifier = orgSsoId;
}),
switchMap((orgSsoId: string) => this.organizationApiService.getAutoEnrollStatus(orgSsoId)),
tap((orgAutoEnrollStatusResponse: OrganizationAutoEnrollStatusResponse) => {
this.orgId = orgAutoEnrollStatusResponse.id;
this.resetPasswordAutoEnroll = orgAutoEnrollStatusResponse.resetPasswordEnabled;
}),
switchMap((orgAutoEnrollStatusResponse: OrganizationAutoEnrollStatusResponse) =>
// Must get org id from response to get master password policy options
this.policyApiService.getMasterPasswordPolicyOptsForOrgUser(
orgAutoEnrollStatusResponse.id,
),
),
tap((masterPasswordPolicyOptions: MasterPasswordPolicyOptions) => {
this.enforcedPolicyOptions = masterPasswordPolicyOptions;
}),
)
.subscribe({
error: () => {
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
},
});
}
async setupSubmitActions() {
this.kdfConfig = DEFAULT_KDF_CONFIG;
return true;
}
async performSubmitActions(
masterPasswordHash: string,
masterKey: MasterKey,
userKey: [UserKey, EncString],
) {
let keysRequest: KeysRequest | null = null;
let newKeyPair: [string, EncString] | null = null;
if (
this.forceSetPasswordReason !=
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission
) {
// Existing JIT provisioned user in a MP encryption org setting first password
// Users in this state will not already have a user asymmetric key pair so must create it for them
// We don't want to re-create the user key pair if the user already has one (TDE user case)
// in case we have a local private key, and are not sure whether it has been posted to the server, we post the local private key instead of generating a new one
const existingUserPrivateKey = (await firstValueFrom(
this.keyService.userPrivateKey$(this.activeUserId),
)) as Uint8Array;
const existingUserPublicKey = await firstValueFrom(
this.keyService.userPublicKey$(this.activeUserId),
);
if (existingUserPrivateKey != null && existingUserPublicKey != null) {
const existingUserPublicKeyB64 = Utils.fromBufferToB64(existingUserPublicKey);
newKeyPair = [
existingUserPublicKeyB64,
await this.encryptService.wrapDecapsulationKey(existingUserPrivateKey, userKey[0]),
];
} else {
newKeyPair = await this.keyService.makeKeyPair(userKey[0]);
}
keysRequest = new KeysRequest(newKeyPair[0], newKeyPair[1].encryptedString);
}
const request = new SetPasswordRequest(
masterPasswordHash,
userKey[1].encryptedString,
this.hint,
this.orgSsoIdentifier,
keysRequest,
this.kdfConfig.kdfType, //always PBKDF2 --> see this.setupSubmitActions
this.kdfConfig.iterations,
);
try {
if (this.resetPasswordAutoEnroll) {
this.formPromise = this.masterPasswordApiService
.setPassword(request)
.then(async () => {
await this.onSetPasswordSuccess(masterKey, userKey, newKeyPair);
return this.organizationApiService.getKeys(this.orgId);
})
.then(async (response) => {
if (response == null) {
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
}
const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user key with organization public key
const userKey = await this.keyService.getUserKey();
const encryptedUserKey = await this.encryptService.encapsulateKeyUnsigned(
userKey,
publicKey,
);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.masterPasswordHash = masterPasswordHash;
resetRequest.resetPasswordKey = encryptedUserKey.encryptedString;
return this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
this.orgId,
this.activeUserId,
resetRequest,
);
});
} else {
this.formPromise = this.masterPasswordApiService.setPassword(request).then(async () => {
await this.onSetPasswordSuccess(masterKey, userKey, newKeyPair);
});
}
await this.formPromise;
if (this.onSuccessfulChangePassword != null) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.onSuccessfulChangePassword();
} else {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate([this.successRoute]);
}
} catch {
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
}
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
}
protected async onSetPasswordSuccess(
masterKey: MasterKey,
userKey: [UserKey, EncString],
keyPair: [string, EncString] | null,
) {
// Clear force set password reason to allow navigation back to vault.
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.None,
this.activeUserId,
);
// User now has a password so update account decryption options in state
const userDecryptionOpts = await firstValueFrom(
this.userDecryptionOptionsService.userDecryptionOptions$,
);
userDecryptionOpts.hasMasterPassword = true;
await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts);
await this.kdfConfigService.setKdfConfig(this.activeUserId, this.kdfConfig);
await this.masterPasswordService.setMasterKey(masterKey, this.activeUserId);
await this.keyService.setUserKey(userKey[0], this.activeUserId);
// Set private key only for new JIT provisioned users in MP encryption orgs
// Existing TDE users will have private key set on sync or on login
if (
keyPair !== null &&
this.forceSetPasswordReason !=
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission
) {
await this.keyService.setPrivateKey(keyPair[1].encryptedString, this.activeUserId);
}
const localMasterKeyHash = await this.keyService.hashMasterKey(
this.masterPassword,
masterKey,
HashPurpose.LocalAuthorization,
);
await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, this.activeUserId);
}
}

View File

@@ -1,141 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
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 { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { Verification } from "@bitwarden/common/auth/types/verification";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { DialogService, ToastService } from "@bitwarden/components";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
@Directive()
export class UpdatePasswordComponent extends BaseChangePasswordComponent {
hint: string;
key: string;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
showPassword = false;
currentMasterPassword: string;
onSuccessfulChangePassword: () => Promise<void>;
constructor(
protected router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
keyService: KeyService,
messagingService: MessagingService,
private masterPasswordApiService: MasterPasswordApiService,
private userVerificationService: UserVerificationService,
private logService: LogService,
dialogService: DialogService,
kdfConfigService: KdfConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
toastService: ToastService,
) {
super(
accountService,
dialogService,
i18nService,
kdfConfigService,
keyService,
masterPasswordService,
messagingService,
platformUtilsService,
policyService,
toastService,
);
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
}
async cancel() {
await this.router.navigate(["/vault"]);
}
async setupSubmitActions(): Promise<boolean> {
if (this.currentMasterPassword == null || this.currentMasterPassword === "") {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: this.i18nService.t("masterPasswordRequired"),
});
return false;
}
const secret: Verification = {
type: VerificationType.MasterPassword,
secret: this.currentMasterPassword,
};
try {
await this.userVerificationService.verifyUser(secret);
} catch (e) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccurred"),
message: e.message,
});
return false;
}
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
return true;
}
async performSubmitActions(
newMasterKeyHash: string,
newMasterKey: MasterKey,
newUserKey: [UserKey, EncString],
) {
try {
// Create Request
const request = new PasswordRequest();
request.masterPasswordHash = await this.keyService.hashMasterKey(
this.currentMasterPassword,
await this.keyService.getOrDeriveMasterKey(this.currentMasterPassword),
);
request.newMasterPasswordHash = newMasterKeyHash;
request.key = newUserKey[1].encryptedString;
// Update user's password
await this.masterPasswordApiService.postPassword(request);
this.toastService.showToast({
variant: "success",
title: this.i18nService.t("masterPasswordChanged"),
message: this.i18nService.t("logBackIn"),
});
if (this.onSuccessfulChangePassword != null) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.onSuccessfulChangePassword();
} else {
this.messagingService.send("logout");
}
} catch (e) {
this.logService.error(e);
}
}
}

View File

@@ -1,232 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom, map } from "rxjs";
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 { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request";
import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request";
import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService, ToastService } from "@bitwarden/components";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
@Directive()
export class UpdateTempPasswordComponent extends BaseChangePasswordComponent implements OnInit {
hint: string;
key: string;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
showPassword = false;
reason: ForceSetPasswordReason = ForceSetPasswordReason.None;
verification: MasterPasswordVerification = {
type: VerificationType.MasterPassword,
secret: "",
};
onSuccessfulChangePassword: () => Promise<any>;
get requireCurrentPassword(): boolean {
return this.reason === ForceSetPasswordReason.WeakMasterPassword;
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
keyService: KeyService,
messagingService: MessagingService,
private masterPasswordApiService: MasterPasswordApiService,
private syncService: SyncService,
private logService: LogService,
private userVerificationService: UserVerificationService,
protected router: Router,
dialogService: DialogService,
kdfConfigService: KdfConfigService,
accountService: AccountService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
toastService: ToastService,
) {
super(
accountService,
dialogService,
i18nService,
kdfConfigService,
keyService,
masterPasswordService,
messagingService,
platformUtilsService,
policyService,
toastService,
);
}
async ngOnInit() {
await this.syncService.fullSync(true);
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
this.reason = await firstValueFrom(this.masterPasswordService.forceSetPasswordReason$(userId));
// If we somehow end up here without a reason, go back to the home page
if (this.reason == ForceSetPasswordReason.None) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/"]);
return;
}
await super.ngOnInit();
}
get masterPasswordWarningText(): string {
if (this.reason == ForceSetPasswordReason.WeakMasterPassword) {
return this.i18nService.t("updateWeakMasterPasswordWarning");
} else if (this.reason == ForceSetPasswordReason.TdeOffboarding) {
return this.i18nService.t("tdeDisabledMasterPasswordRequired");
} else {
return this.i18nService.t("updateMasterPasswordWarning");
}
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
}
async setupSubmitActions(): Promise<boolean> {
const [userId, email] = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])),
);
this.email = email;
this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId);
return true;
}
async submit() {
// Validation
if (!(await this.strongPassword())) {
return;
}
if (!(await this.setupSubmitActions())) {
return;
}
try {
// Create new key and hash new password
const newMasterKey = await this.keyService.makeMasterKey(
this.masterPassword,
this.email.trim().toLowerCase(),
this.kdfConfig,
);
const newPasswordHash = await this.keyService.hashMasterKey(
this.masterPassword,
newMasterKey,
);
// Grab user key
const userKey = await this.keyService.getUserKey();
// Encrypt user key with new master key
const newProtectedUserKey = await this.keyService.encryptUserKeyWithMasterKey(
newMasterKey,
userKey,
);
await this.performSubmitActions(newPasswordHash, newMasterKey, newProtectedUserKey);
} catch (e) {
this.logService.error(e);
}
}
async performSubmitActions(
masterPasswordHash: string,
masterKey: MasterKey,
userKey: [UserKey, EncString],
) {
try {
switch (this.reason) {
case ForceSetPasswordReason.AdminForcePasswordReset:
this.formPromise = this.updateTempPassword(masterPasswordHash, userKey);
break;
case ForceSetPasswordReason.WeakMasterPassword:
this.formPromise = this.updatePassword(masterPasswordHash, userKey);
break;
case ForceSetPasswordReason.TdeOffboarding:
this.formPromise = this.updateTdeOffboardingPassword(masterPasswordHash, userKey);
break;
}
await this.formPromise;
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("updatedMasterPassword"),
});
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.None,
userId,
);
if (this.onSuccessfulChangePassword != null) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.onSuccessfulChangePassword();
} else {
this.messagingService.send("logout");
}
} catch (e) {
this.logService.error(e);
}
}
private async updateTempPassword(masterPasswordHash: string, userKey: [UserKey, EncString]) {
const request = new UpdateTempPasswordRequest();
request.key = userKey[1].encryptedString;
request.newMasterPasswordHash = masterPasswordHash;
request.masterPasswordHint = this.hint;
return this.masterPasswordApiService.putUpdateTempPassword(request);
}
private async updatePassword(newMasterPasswordHash: string, userKey: [UserKey, EncString]) {
const request = await this.userVerificationService.buildRequest(
this.verification,
PasswordRequest,
);
request.masterPasswordHint = this.hint;
request.newMasterPasswordHash = newMasterPasswordHash;
request.key = userKey[1].encryptedString;
return this.masterPasswordApiService.postPassword(request);
}
private async updateTdeOffboardingPassword(
masterPasswordHash: string,
userKey: [UserKey, EncString],
) {
const request = new UpdateTdeOffboardingPasswordRequest();
request.key = userKey[1].encryptedString;
request.newMasterPasswordHash = masterPasswordHash;
request.masterPasswordHint = this.hint;
return this.masterPasswordApiService.putUpdateTdeOffboardingPassword(request);
}
}

View File

@@ -1,28 +0,0 @@
@if (initializing) {
<i
class="bwi bwi-spinner bwi-spin bwi-2x tw-text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
} @else {
<bit-callout
*ngIf="this.forceSetPasswordReason !== ForceSetPasswordReason.AdminForcePasswordReset"
type="warning"
>{{ "changePasswordWarning" | i18n }}</bit-callout
>
<auth-input-password
[flow]="inputPasswordFlow"
[email]="email"
[userId]="userId"
[loading]="submitting"
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
[inlineButtons]="true"
[primaryButtonText]="{ key: 'changeMasterPassword' }"
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
[secondaryButtonText]="secondaryButtonText()"
(onSecondaryButtonClick)="logOut()"
>
</auth-input-password>
}

View File

@@ -1,203 +0,0 @@
import { CommonModule } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core";
import { firstValueFrom } from "rxjs";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import {
InputPasswordComponent,
InputPasswordFlow,
PasswordInputResult,
} from "@bitwarden/auth/angular";
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 { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { SyncService } from "@bitwarden/common/platform/sync";
import { UserId } from "@bitwarden/common/types/guid";
import {
AnonLayoutWrapperDataService,
DialogService,
ToastService,
Icons,
CalloutComponent,
} from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";
import { ChangePasswordService } from "./change-password.service.abstraction";
/**
* Change Password Component
*
* NOTE: The change password component uses the input-password component which will show the
* current password input form in some flows, although it could be left off. This is intentional
* and by design to maintain a strong security posture as some flows could have the user
* end up at a change password without having one before.
*/
@Component({
selector: "auth-change-password",
templateUrl: "change-password.component.html",
imports: [InputPasswordComponent, I18nPipe, CalloutComponent, CommonModule],
})
export class ChangePasswordComponent implements OnInit {
@Input() inputPasswordFlow: InputPasswordFlow = InputPasswordFlow.ChangePassword;
activeAccount: Account | null = null;
email?: string;
userId?: UserId;
masterPasswordPolicyOptions?: MasterPasswordPolicyOptions;
initializing = true;
submitting = false;
formPromise?: Promise<any>;
forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None;
protected readonly ForceSetPasswordReason = ForceSetPasswordReason;
constructor(
private accountService: AccountService,
private changePasswordService: ChangePasswordService,
private i18nService: I18nService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private anonLayoutWrapperDataService: AnonLayoutWrapperDataService,
private organizationInviteService: OrganizationInviteService,
private messagingService: MessagingService,
private policyService: PolicyService,
private toastService: ToastService,
private syncService: SyncService,
private dialogService: DialogService,
private logService: LogService,
) {}
async ngOnInit() {
this.activeAccount = await firstValueFrom(this.accountService.activeAccount$);
if (!this.activeAccount) {
throw new Error("No active active account found while trying to change passwords.");
}
this.userId = this.activeAccount.id;
this.email = this.activeAccount.email;
if (!this.userId) {
throw new Error("userId not found");
}
this.masterPasswordPolicyOptions = await firstValueFrom(
this.policyService.masterPasswordPolicyOptions$(this.userId),
);
this.forceSetPasswordReason = await firstValueFrom(
this.masterPasswordService.forceSetPasswordReason$(this.userId),
);
if (this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset) {
this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({
pageIcon: Icons.LockIcon,
pageTitle: { key: "updateMasterPassword" },
pageSubtitle: { key: "accountRecoveryUpdateMasterPasswordSubtitle" },
});
} else if (this.forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword) {
this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({
pageIcon: Icons.LockIcon,
pageTitle: { key: "updateMasterPassword" },
pageSubtitle: { key: "updateMasterPasswordSubtitle" },
maxWidth: "lg",
});
}
this.initializing = false;
}
async logOut() {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "logOut" },
content: { key: "logOutConfirmation" },
acceptButtonText: { key: "logOut" },
type: "warning",
});
if (confirmed) {
await this.organizationInviteService.clearOrganizationInvitation();
if (this.changePasswordService.clearDeeplinkState) {
await this.changePasswordService.clearDeeplinkState();
}
// TODO: PM-23515 eventually use the logout service instead of messaging service once it is available without circular dependencies
this.messagingService.send("logout");
}
}
async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
this.submitting = true;
try {
if (passwordInputResult.rotateUserKey) {
if (this.activeAccount == null) {
throw new Error("activeAccount not found");
}
if (
passwordInputResult.currentPassword == null ||
passwordInputResult.newPasswordHint == null
) {
throw new Error("currentPassword or newPasswordHint not found");
}
await this.syncService.fullSync(true);
await this.changePasswordService.rotateUserKeyMasterPasswordAndEncryptedData(
passwordInputResult.currentPassword,
passwordInputResult.newPassword,
this.activeAccount,
passwordInputResult.newPasswordHint,
);
} else {
if (!this.userId) {
throw new Error("userId not found");
}
if (this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset) {
await this.changePasswordService.changePasswordForAccountRecovery(
passwordInputResult,
this.userId,
);
} else {
await this.changePasswordService.changePassword(passwordInputResult, this.userId);
}
this.toastService.showToast({
variant: "success",
message: this.i18nService.t("masterPasswordChanged"),
});
// TODO: PM-23515 eventually use the logout service instead of messaging service once it is available without circular dependencies
this.messagingService.send("logout");
}
} catch (error) {
this.logService.error(error);
this.toastService.showToast({
variant: "error",
title: "",
message: this.i18nService.t("errorOccurred"),
});
} finally {
this.submitting = false;
}
}
/**
* Shows the logout button in the case of admin force reset password or weak password upon login.
*/
protected secondaryButtonText(): { key: string } | undefined {
return this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset ||
this.forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword
? { key: "logOut" }
: undefined;
}
}

View File

@@ -41,7 +41,7 @@ describe("DefaultChangePasswordService", () => {
newServerMasterKeyHash: "newServerMasterKeyHash",
newLocalMasterKeyHash: "newLocalMasterKeyHash",
kdfConfig: new PBKDF2KdfConfig(),
kdf: new PBKDF2KdfConfig(),
};
const decryptedUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;

View File

@@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { PasswordInputResult } from "@bitwarden/auth/angular";
@@ -5,10 +7,8 @@ import { Account } from "@bitwarden/common/auth/abstractions/account.service";
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { UserId } from "@bitwarden/common/types/guid";
import { UserKey } from "@bitwarden/common/types/key";
import { KeyService } from "@bitwarden/key-management";
import { ChangePasswordService } from "./change-password.service.abstraction";
@@ -18,7 +18,7 @@ export class DefaultChangePasswordService implements ChangePasswordService {
protected keyService: KeyService,
protected masterPasswordApiService: MasterPasswordApiService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
) {}
) { }
async rotateUserKeyMasterPasswordAndEncryptedData(
currentPassword: string,
@@ -29,60 +29,20 @@ export class DefaultChangePasswordService implements ChangePasswordService {
throw new Error("rotateUserKeyMasterPasswordAndEncryptedData() is only implemented in Web");
}
private async preparePasswordChange(
passwordInputResult: PasswordInputResult,
userId: UserId | null,
request: PasswordRequest | UpdateTempPasswordRequest,
): Promise<[UserKey, EncString]> {
if (!userId) {
throw new Error("userId not found");
}
if (
!passwordInputResult.currentMasterKey ||
!passwordInputResult.currentServerMasterKeyHash ||
!passwordInputResult.newMasterKey ||
!passwordInputResult.newServerMasterKeyHash ||
passwordInputResult.newPasswordHint == null
) {
throw new Error("invalid PasswordInputResult credentials, could not change password");
}
const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
passwordInputResult.currentMasterKey,
userId,
);
if (decryptedUserKey == null) {
throw new Error("Could not decrypt user key");
}
const newKeyValue = await this.keyService.encryptUserKeyWithMasterKey(
passwordInputResult.newMasterKey,
decryptedUserKey,
);
if (request instanceof PasswordRequest) {
request.masterPasswordHash = passwordInputResult.currentServerMasterKeyHash;
request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash;
request.masterPasswordHint = passwordInputResult.newPasswordHint;
} else if (request instanceof UpdateTempPasswordRequest) {
request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash;
request.masterPasswordHint = passwordInputResult.newPasswordHint;
}
return newKeyValue;
}
async changePassword(passwordInputResult: PasswordInputResult, userId: UserId | null) {
const request = new PasswordRequest();
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
throw new Error("Can't find UserKey");
}
const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(userId));
const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(passwordInputResult.newPassword, passwordInputResult.kdf, salt);
const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(passwordInputResult.newPassword, passwordInputResult.kdf, passwordInputResult.salt, userKey);
const newMasterKeyEncryptedUserKey = await this.preparePasswordChange(
passwordInputResult,
userId,
request,
);
const request = new PasswordRequest(authenticationData, unlockData);
request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string;
const oldAuthenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(passwordInputResult.currentPassword, passwordInputResult.kdf, salt);
request.masterPasswordHash = oldAuthenticationData.masterPasswordAuthenticationHash;
request.masterPasswordHint = passwordInputResult.newPasswordHint;
try {
await this.masterPasswordApiService.postPassword(request);
@@ -92,15 +52,16 @@ export class DefaultChangePasswordService implements ChangePasswordService {
}
async changePasswordForAccountRecovery(passwordInputResult: PasswordInputResult, userId: UserId) {
const request = new UpdateTempPasswordRequest();
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
throw new Error("Can't find UserKey");
}
const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(userId));
const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(passwordInputResult.newPassword, passwordInputResult.kdf, salt);
const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(passwordInputResult.newPassword, passwordInputResult.kdf, passwordInputResult.salt, userKey);
const newMasterKeyEncryptedUserKey = await this.preparePasswordChange(
passwordInputResult,
userId,
request,
);
request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string;
const request = new UpdateTempPasswordRequest(authenticationData, unlockData);
request.masterPasswordHint = passwordInputResult.newPasswordHint;
try {
// TODO: PM-23047 will look to consolidate this into the change password endpoint.

View File

@@ -1,3 +1,2 @@
export * from "./change-password.component";
export * from "./change-password.service.abstraction";
export * from "./default-change-password.service";

View File

@@ -18,8 +18,10 @@ import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/mode
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { MasterPasswordAuthenticationData } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { HashPurpose } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
@@ -44,7 +46,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
protected organizationApiService: OrganizationApiServiceAbstraction,
protected organizationUserApiService: OrganizationUserApiService,
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
) {}
) { }
async setInitialPassword(
credentials: SetInitialPasswordCredentials,
@@ -52,11 +54,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
userId: UserId,
): Promise<void> {
const {
newMasterKey,
newServerMasterKeyHash,
newLocalMasterKeyHash,
newPasswordHint,
kdfConfig,
orgSsoIdentifier,
orgId,
resetPasswordAutoEnroll,
@@ -74,13 +72,18 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
throw new Error("userType not found. Could not set password.");
}
const masterKeyEncryptedUserKey = await this.makeMasterKeyEncryptedUserKey(
newMasterKey,
userId,
const userKey = await this.keyService.makeUserKeyV1Raw();
const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(
credentials.initialPassword,
credentials.kdf,
credentials.salt,
);
const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(
credentials.initialPassword,
credentials.kdf,
credentials.salt,
userKey,
);
if (masterKeyEncryptedUserKey == null || !masterKeyEncryptedUserKey[1].encryptedString) {
throw new Error("masterKeyEncryptedUserKey not found. Could not set password.");
}
let keyPair: [string, EncString] | null = null;
let keysRequest: KeysRequest | null = null;
@@ -113,12 +116,12 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
existingUserPublicKeyB64,
await this.encryptService.wrapDecapsulationKey(
existingUserPrivateKey,
masterKeyEncryptedUserKey[0],
userKey,
),
];
} else {
// New key pair
keyPair = await this.keyService.makeKeyPair(masterKeyEncryptedUserKey[0]);
keyPair = await this.keyService.makeKeyPair(userKey);
}
if (keyPair == null) {
@@ -132,13 +135,11 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
}
const request = new SetPasswordRequest(
newServerMasterKeyHash,
masterKeyEncryptedUserKey[1].encryptedString,
authenticationData,
unlockData,
newPasswordHint,
orgSsoIdentifier,
keysRequest,
kdfConfig.kdfType,
kdfConfig.iterations,
);
await this.masterPasswordApiService.setPassword(request);
@@ -147,10 +148,18 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
// User now has a password so update account decryption options in state
// TODO: REMOVE when masterKey is no longer used
const masterKey = await this.keyService.makeMasterKey(credentials.initialPassword, credentials.salt, credentials.kdf);
const localMasterKeyHash = await this.keyService.hashMasterKey(
credentials.initialPassword,
masterKey,
HashPurpose.LocalAuthorization
) as string;
await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, userId);
await this.updateAccountDecryptionProperties(
newMasterKey,
kdfConfig,
masterKeyEncryptedUserKey,
masterKey,
credentials.kdf,
userKey,
userId,
);
@@ -165,34 +174,17 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
await this.keyService.setPrivateKey(keyPair[1].encryptedString, userId);
}
await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId);
if (resetPasswordAutoEnroll) {
await this.handleResetPasswordAutoEnroll(newServerMasterKeyHash, orgId, userId);
await this.handleResetPasswordAutoEnroll(authenticationData, orgId, userId);
}
}
private async makeMasterKeyEncryptedUserKey(
masterKey: MasterKey,
userId: UserId,
): Promise<[UserKey, EncString]> {
let masterKeyEncryptedUserKey: [UserKey, EncString] | null = null;
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
masterKeyEncryptedUserKey = await this.keyService.makeUserKey(masterKey);
} else {
masterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(masterKey);
}
return masterKeyEncryptedUserKey;
}
private async updateAccountDecryptionProperties(
/** @deprecated */
masterKey: MasterKey,
kdfConfig: KdfConfig,
masterKeyEncryptedUserKey: [UserKey, EncString],
userKey: UserKey,
userId: UserId,
) {
const userDecryptionOpts = await firstValueFrom(
@@ -201,12 +193,13 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
userDecryptionOpts.hasMasterPassword = true;
await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts);
await this.kdfConfigService.setKdfConfig(userId, kdfConfig);
// TODO: Remove when masterKey is no longer used
await this.masterPasswordService.setMasterKey(masterKey, userId);
await this.keyService.setUserKey(masterKeyEncryptedUserKey[0], userId);
await this.keyService.setUserKey(userKey, userId);
}
private async handleResetPasswordAutoEnroll(
masterKeyHash: string,
authenticationData: MasterPasswordAuthenticationData,
orgId: string,
userId: UserId,
) {
@@ -238,7 +231,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
}
const enrollmentRequest = new OrganizationUserResetPasswordEnrollmentRequest();
enrollmentRequest.masterPasswordHash = masterKeyHash;
enrollmentRequest.masterPasswordHash = authenticationData.masterPasswordAuthenticationHash;
enrollmentRequest.resetPasswordKey = orgPublicKeyEncryptedUserKey.encryptedString;
await this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
@@ -252,7 +245,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
credentials: SetInitialPasswordTdeOffboardingCredentials,
userId: UserId,
) {
const { newMasterKey, newServerMasterKeyHash, newPasswordHint } = credentials;
const { initialPassword, kdf, salt, newPasswordHint } = credentials;
for (const [key, value] of Object.entries(credentials)) {
if (value == null) {
throw new Error(`${key} not found. Could not set password.`);
@@ -262,24 +255,24 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
if (userId == null) {
throw new Error("userId not found. Could not set password.");
}
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
throw new Error("userKey not found. Could not set password.");
}
const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(
newMasterKey,
const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(
initialPassword,
kdf,
salt,
);
const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(
initialPassword,
kdf,
salt,
userKey,
);
if (!newMasterKeyEncryptedUserKey[1].encryptedString) {
throw new Error("newMasterKeyEncryptedUserKey not found. Could not set password.");
}
const request = new UpdateTdeOffboardingPasswordRequest();
request.key = newMasterKeyEncryptedUserKey[1].encryptedString;
request.newMasterPasswordHash = newServerMasterKeyHash;
const request = new UpdateTdeOffboardingPasswordRequest(authenticationData, unlockData);
request.masterPasswordHint = newPasswordHint;
await this.masterPasswordApiService.putUpdateTdeOffboardingPassword(request);

View File

@@ -157,8 +157,6 @@ describe("DefaultSetInitialPasswordService", () => {
credentials.newPasswordHint,
credentials.orgSsoIdentifier,
keysRequest,
credentials.kdfConfig.kdfType,
credentials.kdfConfig.iterations,
);
enrollmentRequest = new OrganizationUserResetPasswordEnrollmentRequest();

View File

@@ -85,7 +85,7 @@ export class SetInitialPasswordComponent implements OnInit {
private syncService: SyncService,
private toastService: ToastService,
private validationService: ValidationService,
) {}
) { }
async ngOnInit() {
await this.syncService.fullSync(true);
@@ -209,10 +209,9 @@ export class SetInitialPasswordComponent implements OnInit {
private async setInitialPassword(passwordInputResult: PasswordInputResult) {
const ctx = "Could not set initial password.";
assertTruthy(passwordInputResult.newMasterKey, "newMasterKey", ctx);
assertTruthy(passwordInputResult.newServerMasterKeyHash, "newServerMasterKeyHash", ctx);
assertTruthy(passwordInputResult.newLocalMasterKeyHash, "newLocalMasterKeyHash", ctx);
assertTruthy(passwordInputResult.kdfConfig, "kdfConfig", ctx);
assertTruthy(passwordInputResult.newPassword, "newPassword", ctx);
assertTruthy(passwordInputResult.kdf, "kdf", ctx);
assertTruthy(passwordInputResult.salt, "salt", ctx);
assertTruthy(this.orgSsoIdentifier, "orgSsoIdentifier", ctx);
assertTruthy(this.orgId, "orgId", ctx);
assertTruthy(this.userType, "userType", ctx);
@@ -222,11 +221,10 @@ export class SetInitialPasswordComponent implements OnInit {
try {
const credentials: SetInitialPasswordCredentials = {
newMasterKey: passwordInputResult.newMasterKey,
newServerMasterKeyHash: passwordInputResult.newServerMasterKeyHash,
newLocalMasterKeyHash: passwordInputResult.newLocalMasterKeyHash,
initialPassword: passwordInputResult.newPassword,
kdf: passwordInputResult.kdf,
salt: passwordInputResult.salt,
newPasswordHint: passwordInputResult.newPasswordHint,
kdfConfig: passwordInputResult.kdfConfig,
orgSsoIdentifier: this.orgSsoIdentifier,
orgId: this.orgId,
resetPasswordAutoEnroll: this.resetPasswordAutoEnroll,
@@ -251,15 +249,17 @@ export class SetInitialPasswordComponent implements OnInit {
private async setInitialPasswordTdeOffboarding(passwordInputResult: PasswordInputResult) {
const ctx = "Could not set initial password.";
assertTruthy(passwordInputResult.newMasterKey, "newMasterKey", ctx);
assertTruthy(passwordInputResult.newServerMasterKeyHash, "newServerMasterKeyHash", ctx);
assertTruthy(passwordInputResult.newPassword, "newPassword", ctx);
assertTruthy(passwordInputResult.kdf, "kdf", ctx);
assertTruthy(passwordInputResult.salt, "salt", ctx);
assertTruthy(this.userId, "userId", ctx);
assertNonNullish(passwordInputResult.newPasswordHint, "newPasswordHint", ctx); // can have an empty string as a valid value, so check non-nullish
try {
const credentials: SetInitialPasswordTdeOffboardingCredentials = {
newMasterKey: passwordInputResult.newMasterKey,
newServerMasterKeyHash: passwordInputResult.newServerMasterKeyHash,
initialPassword: passwordInputResult.newPassword,
kdf: passwordInputResult.kdf,
salt: passwordInputResult.salt,
newPasswordHint: passwordInputResult.newPasswordHint,
};

View File

@@ -1,5 +1,5 @@
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey } from "@bitwarden/common/types/key";
import { KdfConfig } from "@bitwarden/key-management";
export const _SetInitialPasswordUserType = {
@@ -42,19 +42,21 @@ export const SetInitialPasswordUserType: Readonly<{
}> = Object.freeze(_SetInitialPasswordUserType);
export interface SetInitialPasswordCredentials {
newMasterKey: MasterKey;
newServerMasterKeyHash: string;
newLocalMasterKeyHash: string;
initialPassword: string,
kdf: KdfConfig,
salt: MasterPasswordSalt,
newPasswordHint: string;
kdfConfig: KdfConfig;
orgSsoIdentifier: string;
orgId: string;
resetPasswordAutoEnroll: boolean;
}
export interface SetInitialPasswordTdeOffboardingCredentials {
newMasterKey: MasterKey;
newServerMasterKeyHash: string;
initialPassword: string;
kdf: KdfConfig;
salt: MasterPasswordSalt;
newPasswordHint: string;
}

View File

@@ -423,11 +423,11 @@ const safeProviders: SafeProvider[] = [
provide: LOGOUT_CALLBACK,
useFactory:
(messagingService: MessagingServiceAbstraction) =>
async (logoutReason: LogoutReason, userId?: string) => {
return Promise.resolve(
messagingService.send("logout", { logoutReason: logoutReason, userId: userId }),
);
},
async (logoutReason: LogoutReason, userId?: string) => {
return Promise.resolve(
messagingService.send("logout", { logoutReason: logoutReason, userId: userId }),
);
},
deps: [MessagingServiceAbstraction],
}),
safeProvider({
@@ -1024,6 +1024,7 @@ const safeProviders: SafeProvider[] = [
EncryptService,
LogService,
CryptoFunctionServiceAbstraction,
AccountServiceAbstraction
],
}),
safeProvider({
@@ -1614,4 +1615,4 @@ const safeProviders: SafeProvider[] = [
// Do not register your dependency here! Add it to the typesafeProviders array using the helper function
providers: safeProviders,
})
export class JslibServicesModule {}
export class JslibServicesModule { }

View File

@@ -42,7 +42,6 @@ export * from "./registration/registration-finish/registration-finish.service";
export * from "./registration/registration-finish/default-registration-finish.service";
// set password (JIT user)
export * from "./set-password-jit/set-password-jit.component";
export * from "./set-password-jit/set-password-jit.service.abstraction";
export * from "./set-password-jit/default-set-password-jit.service";

View File

@@ -14,7 +14,6 @@ import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-manageme
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { HashPurpose } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -192,7 +191,7 @@ export class InputPasswordComponent implements OnInit {
private policyService: PolicyService,
private toastService: ToastService,
private validationService: ValidationService,
) {}
) { }
ngOnInit(): void {
this.addFormFieldsIfNecessary();
@@ -300,6 +299,7 @@ export class InputPasswordComponent implements OnInit {
if (this.kdfConfig == null) {
throw new Error("KdfConfig is required to create master key.");
}
const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(this.userId));
// 2. Verify current password is correct (if necessary)
if (
@@ -326,61 +326,14 @@ export class InputPasswordComponent implements OnInit {
}
// 4. Create cryptographic keys and build a PasswordInputResult object
const newMasterKey = await this.keyService.makeMasterKey(
newPassword,
this.email,
this.kdfConfig,
);
const newServerMasterKeyHash = await this.keyService.hashMasterKey(
newPassword,
newMasterKey,
HashPurpose.ServerAuthorization,
);
const newLocalMasterKeyHash = await this.keyService.hashMasterKey(
newPassword,
newMasterKey,
HashPurpose.LocalAuthorization,
);
const passwordInputResult: PasswordInputResult = {
currentPassword,
newPassword,
newMasterKey,
newServerMasterKeyHash,
newLocalMasterKeyHash,
newPasswordHint,
kdfConfig: this.kdfConfig,
kdf: this.kdfConfig,
salt,
};
if (
this.flow === InputPasswordFlow.ChangePassword ||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
) {
const currentMasterKey = await this.keyService.makeMasterKey(
currentPassword,
this.email,
this.kdfConfig,
);
const currentServerMasterKeyHash = await this.keyService.hashMasterKey(
currentPassword,
currentMasterKey,
HashPurpose.ServerAuthorization,
);
const currentLocalMasterKeyHash = await this.keyService.hashMasterKey(
currentPassword,
currentMasterKey,
HashPurpose.LocalAuthorization,
);
passwordInputResult.currentPassword = currentPassword;
passwordInputResult.currentMasterKey = currentMasterKey;
passwordInputResult.currentServerMasterKeyHash = currentServerMasterKeyHash;
passwordInputResult.currentLocalMasterKeyHash = currentLocalMasterKeyHash;
}
if (this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) {
passwordInputResult.rotateUserKey = this.formGroup.controls.rotateUserKey?.value;
}

View File

@@ -1,18 +1,11 @@
import { MasterKey } from "@bitwarden/common/types/key";
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { KdfConfig } from "@bitwarden/key-management";
export interface PasswordInputResult {
currentPassword?: string;
currentMasterKey?: MasterKey;
currentServerMasterKeyHash?: string;
currentLocalMasterKeyHash?: string;
newPassword: string;
kdf?: KdfConfig;
salt?: MasterPasswordSalt;
newPasswordHint?: string;
newMasterKey?: MasterKey;
newServerMasterKeyHash?: string;
newLocalMasterKeyHash?: string;
kdfConfig?: KdfConfig;
rotateUserKey?: boolean;
}

View File

@@ -61,7 +61,7 @@ describe("DefaultRegistrationFinishService", () => {
newMasterKey: masterKey,
newServerMasterKeyHash: "newServerMasterKeyHash",
newLocalMasterKeyHash: "newLocalMasterKeyHash",
kdfConfig: DEFAULT_KDF_CONFIG,
kdf: DEFAULT_KDF_CONFIG,
newPasswordHint: "newPasswordHint",
newPassword: "newPassword",
};
@@ -100,8 +100,8 @@ describe("DefaultRegistrationFinishService", () => {
publicKey: userKeyPair[0],
encryptedPrivateKey: userKeyPair[1].encryptedString,
},
kdf: passwordInputResult.kdfConfig.kdfType,
kdfIterations: passwordInputResult.kdfConfig.iterations,
kdf: passwordInputResult.kdf.kdfType,
kdfIterations: passwordInputResult.kdf.iterations,
kdfMemory: undefined,
kdfParallelism: undefined,
// Web only fields should be undefined

View File

@@ -4,9 +4,10 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service";
import { RegisterFinishRequest } from "@bitwarden/common/auth/models/request/registration/register-finish.request";
import {
EncryptedString,
EncString,
} from "@bitwarden/common/key-management/crypto/models/enc-string";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { MasterPasswordAuthenticationData, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { KeyService } from "@bitwarden/key-management";
@@ -18,7 +19,8 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi
constructor(
protected keyService: KeyService,
protected accountApiService: AccountApiService,
) {}
protected masterPasswordService: MasterPasswordServiceAbstraction,
) { }
getOrgNameFromOrgInvite(): Promise<string | null> {
return null;
@@ -42,19 +44,25 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi
providerInviteToken?: string,
providerUserId?: string,
): Promise<void> {
const [newUserKey, newEncUserKey] = await this.keyService.makeUserKey(
passwordInputResult.newMasterKey,
);
if (!newUserKey || !newEncUserKey) {
throw new Error("User key could not be created");
}
const newUserKey = await this.keyService.makeUserKeyV1Raw();
const userAsymmetricKeys = await this.keyService.makeKeyPair(newUserKey);
const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(
passwordInputResult.newPassword,
passwordInputResult.kdf,
passwordInputResult.salt,
);
const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(
passwordInputResult.newPassword,
passwordInputResult.kdf,
passwordInputResult.salt,
newUserKey,
);
const registerRequest = await this.buildRegisterRequest(
email,
passwordInputResult,
newEncUserKey.encryptedString,
authenticationData,
unlockData,
userAsymmetricKeys,
emailVerificationToken,
orgSponsoredFreeFamilyPlanToken,
@@ -70,7 +78,8 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi
protected async buildRegisterRequest(
email: string,
passwordInputResult: PasswordInputResult,
encryptedUserKey: EncryptedString,
masterPasswordAuthenticationData: MasterPasswordAuthenticationData,
masterPasswordUnlockData: MasterPasswordUnlockData,
userAsymmetricKeys: [string, EncString],
emailVerificationToken?: string,
orgSponsoredFreeFamilyPlanToken?: string, // web only
@@ -86,12 +95,10 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi
const registerFinishRequest = new RegisterFinishRequest(
email,
passwordInputResult.newServerMasterKeyHash,
passwordInputResult.newPasswordHint,
encryptedUserKey,
masterPasswordAuthenticationData,
masterPasswordUnlockData,
userAsymmetricKeysRequest,
passwordInputResult.kdfConfig.kdfType,
passwordInputResult.kdfConfig.iterations,
);
if (emailVerificationToken) {

View File

@@ -113,7 +113,7 @@ describe("DefaultSetPasswordJitService", () => {
newServerMasterKeyHash: "newServerMasterKeyHash",
newLocalMasterKeyHash: "newLocalMasterKeyHash",
newPasswordHint: "newPasswordHint",
kdfConfig: DEFAULT_KDF_CONFIG,
kdf: DEFAULT_KDF_CONFIG,
newPassword: "newPassword",
};
@@ -122,7 +122,7 @@ describe("DefaultSetPasswordJitService", () => {
newServerMasterKeyHash: passwordInputResult.newServerMasterKeyHash,
newLocalMasterKeyHash: passwordInputResult.newLocalMasterKeyHash,
newPasswordHint: passwordInputResult.newPasswordHint,
kdfConfig: passwordInputResult.kdfConfig,
kdfConfig: passwordInputResult.kdf,
orgSsoIdentifier,
orgId,
resetPasswordAutoEnroll,
@@ -138,8 +138,8 @@ describe("DefaultSetPasswordJitService", () => {
passwordInputResult.newPasswordHint,
orgSsoIdentifier,
keysRequest,
passwordInputResult.kdfConfig.kdfType,
passwordInputResult.kdfConfig.iterations,
passwordInputResult.kdf.kdfType,
passwordInputResult.kdf.iterations,
);
});

View File

@@ -16,11 +16,13 @@ import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-pa
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { MasterPasswordAuthenticationData } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { HashPurpose } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { UserKey } from "@bitwarden/common/types/key";
import { KdfConfigService, KeyService, KdfConfig } from "@bitwarden/key-management";
import {
@@ -39,19 +41,18 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
protected organizationApiService: OrganizationApiServiceAbstraction,
protected organizationUserApiService: OrganizationUserApiService,
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
) {}
) { }
async setPassword(credentials: SetPasswordCredentials): Promise<void> {
const {
newMasterKey,
newServerMasterKeyHash,
newLocalMasterKeyHash,
newPassword,
newPasswordHint,
kdfConfig,
orgSsoIdentifier,
orgId,
resetPasswordAutoEnroll,
userId,
kdfConfig,
} = credentials;
for (const [key, value] of Object.entries(credentials)) {
@@ -60,23 +61,30 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
}
}
const protectedUserKey = await this.makeProtectedUserKey(newMasterKey, userId);
if (protectedUserKey == null) {
throw new Error("protectedUserKey not found. Could not set password.");
}
const userKey = await this.keyService.makeUserKeyV1Raw();
const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(userId));
const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(
newPassword,
kdfConfig,
salt,
);
const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(
newPassword,
kdfConfig,
salt,
userKey,
);
// Since this is an existing JIT provisioned user in a MP encryption org setting first password,
// they will not already have a user asymmetric key pair so we must create it for them.
const [keyPair, keysRequest] = await this.makeKeyPairAndRequest(protectedUserKey);
const [keyPair, keysRequest] = await this.makeKeyPairAndRequest(userKey);
const request = new SetPasswordRequest(
newServerMasterKeyHash,
protectedUserKey[1].encryptedString,
authenticationData,
unlockData,
newPasswordHint,
orgSsoIdentifier,
keysRequest,
kdfConfig.kdfType,
kdfConfig.iterations,
);
await this.masterPasswordApiService.setPassword(request);
@@ -84,39 +92,31 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
// Clear force set password reason to allow navigation back to vault.
await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
{
// TODO: Remove this block once master key and local master key hash are removed from state. This usage is deprecated.
const newMasterKey = await this.keyService.makeMasterKey(newPassword, salt, kdfConfig);
await this.masterPasswordService.setMasterKey(newMasterKey, userId);
const newLocalMasterKeyHash = await this.keyService.hashMasterKey(
newPassword,
newMasterKey,
HashPurpose.LocalAuthorization,
);
await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId);
}
// User now has a password so update account decryption options in state
await this.updateAccountDecryptionProperties(newMasterKey, kdfConfig, protectedUserKey, userId);
await this.updateAccountDecryptionProperties(kdfConfig, userKey, userId);
await this.keyService.setPrivateKey(keyPair[1].encryptedString, userId);
await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId);
if (resetPasswordAutoEnroll) {
await this.handleResetPasswordAutoEnroll(newServerMasterKeyHash, orgId, userId);
await this.handleResetPasswordAutoEnroll(authenticationData, orgId, userId);
}
}
private async makeProtectedUserKey(
masterKey: MasterKey,
userId: UserId,
): Promise<[UserKey, EncString]> {
let protectedUserKey: [UserKey, EncString] = null;
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
protectedUserKey = await this.keyService.makeUserKey(masterKey);
} else {
protectedUserKey = await this.keyService.encryptUserKeyWithMasterKey(masterKey);
}
return protectedUserKey;
}
private async makeKeyPairAndRequest(
protectedUserKey: [UserKey, EncString],
userKey: UserKey,
): Promise<[[string, EncString], KeysRequest]> {
const keyPair = await this.keyService.makeKeyPair(protectedUserKey[0]);
const keyPair = await this.keyService.makeKeyPair(userKey);
if (keyPair == null) {
throw new Error("keyPair not found. Could not set password.");
}
@@ -126,9 +126,8 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
}
private async updateAccountDecryptionProperties(
masterKey: MasterKey,
kdfConfig: KdfConfig,
protectedUserKey: [UserKey, EncString],
userKey: UserKey,
userId: UserId,
) {
const userDecryptionOpts = await firstValueFrom(
@@ -137,12 +136,11 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
userDecryptionOpts.hasMasterPassword = true;
await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts);
await this.kdfConfigService.setKdfConfig(userId, kdfConfig);
await this.masterPasswordService.setMasterKey(masterKey, userId);
await this.keyService.setUserKey(protectedUserKey[0], userId);
await this.keyService.setUserKey(userKey, userId);
}
private async handleResetPasswordAutoEnroll(
masterKeyHash: string,
masterPasswordAuthenticationData: MasterPasswordAuthenticationData,
orgId: string,
userId: UserId,
) {
@@ -164,7 +162,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
const encryptedUserKey = await this.encryptService.encapsulateKeyUnsigned(userKey, publicKey);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.masterPasswordHash = masterKeyHash;
resetRequest.masterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash;
resetRequest.resetPasswordKey = encryptedUserKey.encryptedString;
await this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(

View File

@@ -1,24 +0,0 @@
<ng-container *ngIf="syncLoading">
<i class="bwi bwi-spinner bwi-spin tw-mr-2" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</ng-container>
<ng-container *ngIf="!syncLoading">
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<auth-input-password
[flow]="inputPasswordFlow"
[email]="email"
[userId]="userId"
[loading]="submitting"
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions"
[primaryButtonText]="{ key: 'createAccount' }"
(onPasswordFormSubmit)="handlePasswordFormSubmit($event)"
></auth-input-password>
</ng-container>

View File

@@ -1,135 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { UserId } from "@bitwarden/common/types/guid";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { ToastService } from "../../../../components/src/toast";
import {
InputPasswordComponent,
InputPasswordFlow,
} from "../input-password/input-password.component";
import { PasswordInputResult } from "../input-password/password-input-result";
import {
SetPasswordCredentials,
SetPasswordJitService,
} from "./set-password-jit.service.abstraction";
@Component({
selector: "auth-set-password-jit",
templateUrl: "set-password-jit.component.html",
imports: [CommonModule, InputPasswordComponent, JslibModule],
})
export class SetPasswordJitComponent implements OnInit {
protected inputPasswordFlow = InputPasswordFlow.SetInitialPasswordAuthedUser;
protected email: string;
protected masterPasswordPolicyOptions: MasterPasswordPolicyOptions;
protected orgId: string;
protected orgSsoIdentifier: string;
protected resetPasswordAutoEnroll: boolean;
protected submitting = false;
protected syncLoading = true;
protected userId: UserId;
constructor(
private accountService: AccountService,
private activatedRoute: ActivatedRoute,
private i18nService: I18nService,
private organizationApiService: OrganizationApiServiceAbstraction,
private policyApiService: PolicyApiServiceAbstraction,
private router: Router,
private setPasswordJitService: SetPasswordJitService,
private syncService: SyncService,
private toastService: ToastService,
private validationService: ValidationService,
) {}
async ngOnInit() {
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
this.userId = activeAccount?.id;
this.email = activeAccount?.email;
await this.syncService.fullSync(true);
this.syncLoading = false;
await this.handleQueryParams();
}
private async handleQueryParams() {
const qParams = await firstValueFrom(this.activatedRoute.queryParams);
if (qParams.identifier != null) {
try {
this.orgSsoIdentifier = qParams.identifier;
const autoEnrollStatus = await this.organizationApiService.getAutoEnrollStatus(
this.orgSsoIdentifier,
);
this.orgId = autoEnrollStatus.id;
this.resetPasswordAutoEnroll = autoEnrollStatus.resetPasswordEnabled;
this.masterPasswordPolicyOptions =
await this.policyApiService.getMasterPasswordPolicyOptsForOrgUser(autoEnrollStatus.id);
} catch {
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("errorOccurred"),
});
}
}
}
protected async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
this.submitting = true;
const credentials: SetPasswordCredentials = {
newMasterKey: passwordInputResult.newMasterKey,
newServerMasterKeyHash: passwordInputResult.newServerMasterKeyHash,
newLocalMasterKeyHash: passwordInputResult.newLocalMasterKeyHash,
newPasswordHint: passwordInputResult.newPasswordHint,
kdfConfig: passwordInputResult.kdfConfig,
orgSsoIdentifier: this.orgSsoIdentifier,
orgId: this.orgId,
resetPasswordAutoEnroll: this.resetPasswordAutoEnroll,
userId: this.userId,
};
try {
await this.setPasswordJitService.setPassword(credentials);
} catch (e) {
this.validationService.showError(e);
this.submitting = false;
return;
}
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("accountSuccessfullyCreated"),
});
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("inviteAccepted"),
});
this.submitting = false;
await this.router.navigate(["vault"]);
}
}

View File

@@ -8,6 +8,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -66,7 +67,7 @@ export class TwoFactorAuthEmailComponent implements OnInit {
protected appIdService: AppIdService,
private toastService: ToastService,
private cacheService: TwoFactorAuthEmailComponentCacheService,
) {}
) { }
async ngOnInit(): Promise<void> {
// Check if email was already sent
@@ -125,7 +126,7 @@ export class TwoFactorAuthEmailComponent implements OnInit {
const request = new TwoFactorEmailRequest();
request.email = email;
request.masterPasswordHash = (await this.loginStrategyService.getMasterPasswordHash()) ?? "";
request.masterPasswordHash = ((await this.loginStrategyService.getMasterPasswordHash()) ?? "") as MasterPasswordAuthenticationHash;
request.ssoEmail2FaSessionToken =
(await this.loginStrategyService.getSsoEmail2FaSessionToken()) ?? "";
request.deviceIdentifier = await this.appIdService.getAppId();

View File

@@ -4,5 +4,4 @@ import { SecretVerificationRequest } from "./secret-verification.request";
export class EmailTokenRequest extends SecretVerificationRequest {
newEmail: string;
masterPasswordHash: string;
}

View File

@@ -1,9 +1,18 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { MasterPasswordAuthenticationData, MasterPasswordAuthenticationHash, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { EmailTokenRequest } from "./email-token.request";
export class EmailRequest extends EmailTokenRequest {
newMasterPasswordHash: string;
token: string;
key: string;
newMasterPasswordHash: MasterPasswordAuthenticationHash;
token: string
key: EncryptedString;
constructor(authenticationData: MasterPasswordAuthenticationData, unlockData: MasterPasswordUnlockData) {
super();
this.masterPasswordHash = authenticationData.masterPasswordAuthenticationHash;
this.newMasterPasswordHash = authenticationData.masterPasswordAuthenticationHash;
}
}

View File

@@ -1,9 +1,26 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { MasterPasswordAuthenticationData, MasterPasswordUnlockData } from "../../../key-management/master-password/types/master-password.types";
import { SecretVerificationRequest } from "./secret-verification.request";
export class PasswordRequest extends SecretVerificationRequest {
masterPasswordHint: string | null = null;
masterPasswordUnlockData: MasterPasswordUnlockData;
masterPasswordAuthenticationData: MasterPasswordAuthenticationData;
/** @deprecated */
newMasterPasswordHash: string;
masterPasswordHint: string;
/** @deprecated */
key: string;
constructor(masterPasswordAuthenticationData: MasterPasswordAuthenticationData, masterPasswordUnlockData: MasterPasswordUnlockData) {
super();
this.masterPasswordUnlockData = masterPasswordUnlockData;
this.masterPasswordAuthenticationData = masterPasswordAuthenticationData;
{ // TODO: This will be removed once the deprecated properties are removed
this.newMasterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash;
this.key = masterPasswordUnlockData.masterKeyWrappedUserKey.encryptedString;
}
}
}

View File

@@ -1,4 +1,6 @@
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
import { MasterPasswordAuthenticationData, MasterPasswordAuthenticationHash, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types";
// eslint-disable-next-line no-restricted-imports
import { KdfType } from "@bitwarden/key-management";
@@ -6,20 +8,20 @@ import { EncryptedString } from "../../../../key-management/crypto/models/enc-st
import { KeysRequest } from "../../../../models/request/keys.request";
export class RegisterFinishRequest {
masterPasswordHash: MasterPasswordAuthenticationHash;
userSymmetricKey: EncryptedString;
kdf: KdfType;
kdfIterations: number;
kdfMemory?: number;
kdfParallelism?: number;
constructor(
public email: string,
public masterPasswordHash: string,
public masterPasswordHint: string,
public userSymmetricKey: EncryptedString,
masterPasswordAuthenticationData: MasterPasswordAuthenticationData,
masterPasswordUnlockData: MasterPasswordUnlockData,
public userAsymmetricKeys: KeysRequest,
public kdf: KdfType,
public kdfIterations: number,
public kdfMemory?: number,
public kdfParallelism?: number,
public emailVerificationToken?: string,
public orgSponsoredFreeFamilyPlanToken?: string,
public acceptEmergencyAccessInviteToken?: string,
@@ -30,5 +32,23 @@ export class RegisterFinishRequest {
// Org Invite data (only applies on web)
public organizationUserId?: string,
public orgInviteToken?: string,
) {}
) {
this.masterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash;
this.userSymmetricKey = masterPasswordUnlockData.masterKeyWrappedUserKey.encryptedString;
const kdf = masterPasswordAuthenticationData.kdf;
if (kdf.kdfType === KdfType.PBKDF2_SHA256) {
this.kdf = KdfType.PBKDF2_SHA256;
this.kdfIterations = kdf.iterations;
this.kdfMemory = undefined;
this.kdfParallelism = undefined;
} else if (kdf.kdfType === KdfType.Argon2id) {
this.kdf = KdfType.Argon2id;
this.kdfIterations = kdf.iterations;
this.kdfMemory = kdf.memory;
this.kdfParallelism = kdf.parallelism;
} else {
throw new Error(`Unsupported KDF type: ${kdf}`);
}
}
}

View File

@@ -1,7 +1,10 @@
// FIXME: Update this file to be type safe and remove this and next line
import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types";
// @ts-strict-ignore
export class SecretVerificationRequest {
masterPasswordHash: string;
masterPasswordHash: MasterPasswordAuthenticationHash;
otp: string;
authRequestAccessCode: string;
}

View File

@@ -1,3 +1,4 @@
import { MasterKeyWrappedUserKey, MasterPasswordAuthenticationData, MasterPasswordAuthenticationHash, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { KdfType } from "@bitwarden/key-management";
@@ -5,35 +6,50 @@ import { KdfType } from "@bitwarden/key-management";
import { KeysRequest } from "../../../models/request/keys.request";
export class SetPasswordRequest {
masterPasswordHash: string;
key: string;
// TODO: This will be replaced by masterPasswordAuthenticationData in the future
masterPasswordHash: MasterPasswordAuthenticationHash;
// TODO: This will be replaced by masterPasswordAuthenticationData in the future
key: MasterKeyWrappedUserKey;
masterPasswordHint: string;
keys: KeysRequest | null;
kdf: KdfType;
kdfIterations: number;
kdfMemory?: number;
kdfParallelism?: number;
orgIdentifier: string;
keys: KeysRequest | null;
/** @deprecated */
kdf: KdfType;
/** @deprecated */
kdfIterations: number;
/** @deprecated */
kdfMemory?: number;
/** @deprecated */
kdfParallelism?: number;
constructor(
masterPasswordHash: string,
key: string,
masterPasswordAuthenticationData: MasterPasswordAuthenticationData,
masterPasswordUnlockData: MasterPasswordUnlockData,
masterPasswordHint: string,
orgIdentifier: string,
keys: KeysRequest | null,
kdf: KdfType,
kdfIterations: number,
kdfMemory?: number,
kdfParallelism?: number,
keys: KeysRequest | null
) {
this.masterPasswordHash = masterPasswordHash;
this.key = key;
this.masterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash;
this.key = masterPasswordUnlockData.masterKeyWrappedUserKey;
this.masterPasswordHint = masterPasswordHint;
this.kdf = kdf;
this.kdfIterations = kdfIterations;
this.kdfMemory = kdfMemory;
this.kdfParallelism = kdfParallelism;
this.orgIdentifier = orgIdentifier;
this.keys = keys;
// This will be removed when the deprecated properties are removed
const kdf = masterPasswordAuthenticationData.kdf;
if (kdf.kdfType === KdfType.PBKDF2_SHA256) {
this.kdf = KdfType.PBKDF2_SHA256;
this.kdfIterations = kdf.iterations;
} else if (kdf.kdfType === KdfType.Argon2id) {
this.kdf = KdfType.Argon2id;
this.kdfIterations = kdf.iterations;
this.kdfMemory = kdf.memory;
this.kdfParallelism = kdf.parallelism;
} else {
throw new Error(`Unsupported KDF type: ${kdf}`);
}
}
}

View File

@@ -6,7 +6,8 @@ import { firstValueFrom, map } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import {
BiometricsService,
BiometricsStatus,
@@ -55,7 +56,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
private pinService: PinServiceAbstraction,
private kdfConfigService: KdfConfigService,
private biometricsService: BiometricsService,
) {}
) { }
async getAvailableVerificationOptions(
verificationType: keyof UserVerificationOptions,
@@ -113,20 +114,15 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
if (verification.type === VerificationType.OTP) {
request.otp = verification.secret;
} else {
const [userId, email] = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])),
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
let masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
if (!masterKey && !alreadyHashed) {
masterKey = await this.keyService.makeMasterKey(
verification.secret,
email,
await this.kdfConfigService.getKdfConfig(userId),
);
}
const kdf = await this.kdfConfigService.getKdfConfig(userId);
const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(userId));
request.masterPasswordHash = alreadyHashed
? verification.secret
: await this.keyService.hashMasterKey(verification.secret, masterKey);
: (await this.masterPasswordService.makeMasterPasswordAuthenticationData(verification.secret, kdf, salt)).masterPasswordAuthenticationHash;
}
return request;

View File

@@ -1,5 +1,6 @@
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { KdfConfig } from "@bitwarden/key-management";
import { MasterKey } from "../../types/key";
@@ -7,7 +8,7 @@ import { VerificationType } from "../enums/verification-type";
import { MasterPasswordPolicyResponse } from "../models/response/master-password-policy.response";
export type OtpVerification = { type: VerificationType.OTP; secret: string };
export type MasterPasswordVerification = { type: VerificationType.MasterPassword; secret: string };
export type MasterPasswordVerification = { type: VerificationType.MasterPassword; secret: MasterPasswordAuthenticationHash };
export type PinVerification = { type: VerificationType.PIN; secret: string };
export type BiometricsVerification = { type: VerificationType.Biometrics };

View File

@@ -2,12 +2,15 @@
// @ts-strict-ignore
import { Jsonify, Opaque } from "type-fest";
import { EncString as SdkEncString } from "@bitwarden/sdk-internal";
import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../../platform/enums";
import { Encrypted } from "../../../platform/interfaces/encrypted";
import { Utils } from "../../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { EncryptService } from "../abstractions/encrypt.service";
export const DECRYPT_ERROR = "[error: cannot decrypt]";
export class EncString implements Encrypted {
@@ -55,6 +58,18 @@ export class EncString implements Encrypted {
return new EncString(obj);
}
static fromSdk(obj: SdkEncString): EncString {
return new EncString(obj);
}
toSdk(): SdkEncString {
return this.encryptedString as SdkEncString;
}
static fromEncryptedString(obj: EncryptedString): EncString {
return new EncString(obj);
}
private initFromData(encType: EncryptionType, data: string, iv: string, mac: string) {
if (iv != null) {
this.encryptedString = (encType + "." + iv + "|" + data) as EncryptedString;

View File

@@ -8,6 +8,7 @@ import { DeviceResponse } from "../../../auth/abstractions/devices/responses/dev
import { UserId } from "../../../types/guid";
import { DeviceKey, UserKey } from "../../../types/key";
import { EncString } from "../../crypto/models/enc-string";
import { MasterPasswordAuthenticationData } from "../../master-password/types/master-password.types";
export abstract class DeviceTrustServiceAbstraction {
/**
@@ -50,7 +51,7 @@ export abstract class DeviceTrustServiceAbstraction {
rotateDevicesTrust: (
userId: UserId,
newUserKey: UserKey,
masterPasswordHash: string,
masterPasswordAuthenticationData: MasterPasswordAuthenticationData,
) => Promise<void>;
/**
* Notifies the server that the device has a device key, but didn't receive any associated decryption keys.

View File

@@ -33,6 +33,7 @@ import { UserKey, DeviceKey } from "../../../types/key";
import { CryptoFunctionService } from "../../crypto/abstractions/crypto-function.service";
import { EncryptService } from "../../crypto/abstractions/encrypt.service";
import { EncString } from "../../crypto/models/enc-string";
import { MasterPasswordAuthenticationData } from "../../master-password/types/master-password.types";
import { DeviceTrustServiceAbstraction } from "../abstractions/device-trust.service.abstraction";
/** Uses disk storage so that the device key can persist after log out and tab removal. */
@@ -256,7 +257,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
async rotateDevicesTrust(
userId: UserId,
newUserKey: UserKey,
masterPasswordHash: string,
masterPasswordAuthenticationData: MasterPasswordAuthenticationData,
): Promise<void> {
this.logService.info("[Device trust rotation] Rotating device trust...");
if (!userId) {
@@ -279,7 +280,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
const deviceIdentifier = await this.appIdService.getAppId();
const secretVerificationRequest = new SecretVerificationRequest();
secretVerificationRequest.masterPasswordHash = masterPasswordHash;
secretVerificationRequest.masterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash;
// Get the keys that are used in rotating a devices keys from the server
const currentDeviceKeys = await this.devicesApiService.getDeviceKeys(deviceIdentifier);
@@ -310,7 +311,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
// then it can be added to trustRequest.otherDevices.
const trustRequest = new UpdateDevicesTrustRequest();
trustRequest.masterPasswordHash = masterPasswordHash;
trustRequest.masterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash;
trustRequest.currentDevice = currentDeviceUpdateRequest;
trustRequest.otherDevices = [];

View File

@@ -0,0 +1,8 @@
// eslint-disable-next-line no-restricted-imports
import { KdfConfig } from "@bitwarden/key-management";
import { UserId } from "../../../types/guid";
export abstract class ChangeKdfServiceAbstraction {
abstract updateUserKdfParams(masterPassword: string, kdf: KdfConfig, userId: UserId): Promise<void>;
}

View File

@@ -0,0 +1,23 @@
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { KdfRequest } from "@bitwarden/common/models/request/kdf.request";
import { UserId } from "@bitwarden/common/types/guid";
// eslint-disable-next-line no-restricted-imports
import { KdfConfig, KeyService } from "@bitwarden/key-management";
import { MasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction";
import { ChangeKdfServiceAbstraction } from "../abstractions/change-kdf-service";
export class ChangeKdfService implements ChangeKdfServiceAbstraction {
constructor(private apiService: ApiService, private masterPasswordService: MasterPasswordServiceAbstraction, private keyService: KeyService) { }
async updateUserKdfParams(masterPassword: string, kdf: KdfConfig, userId: UserId): Promise<void> {
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(userId));
const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(masterPassword, kdf, salt);
const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(masterPassword, kdf, salt, userKey);
const request = new KdfRequest(authenticationData, unlockData);
return this.apiService.send("POST", "/accounts/kdf", request, true, false);
}
}

View File

@@ -20,6 +20,10 @@ export abstract class MasterPasswordServiceAbstraction {
* @throws If the user ID is missing.
*/
abstract forceSetPasswordReason$: (userId: UserId) => Observable<ForceSetPasswordReason>;
/**
*
*/
abstract saltForAccount$: (userId: UserId) => Observable<MasterPasswordSalt>;
/**
* An observable that emits the master key for the user.
* @deprecated Interacting with the master-key directly is deprecated. Please use {@link makeMasterPasswordUnlockData}, {@link makeMasterPasswordAuthenticationData} or {@link unwrapUserKeyFromMasterPasswordUnlockData} instead.

View File

@@ -33,6 +33,10 @@ export class FakeMasterPasswordService implements InternalMasterPasswordServiceA
this.masterKeyHashSubject.next(initialMasterKeyHash);
}
saltForAccount$(userId: UserId): Observable<MasterPasswordSalt> {
return this.mock.saltForAccount$(userId);
}
masterKey$(userId: UserId): Observable<MasterKey> {
return this.masterKeySubject.asObservable();
}

View File

@@ -2,6 +2,7 @@
// @ts-strict-ignore
import { firstValueFrom, map, Observable } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
// eslint-disable-next-line no-restricted-imports
@@ -74,7 +75,8 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr
private encryptService: EncryptService,
private logService: LogService,
private cryptoFunctionService: CryptoFunctionService,
) {}
private accountService: AccountService,
) { }
masterKey$(userId: UserId): Observable<MasterKey> {
if (userId == null) {
@@ -83,6 +85,13 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr
return this.stateProvider.getUser(userId, MASTER_KEY).state$;
}
saltForAccount$(userId: UserId): Observable<MasterPasswordSalt> {
if (userId == null) {
throw new Error("User ID is required.");
}
return this.accountService.activeAccount$.pipe(map((a) => a?.email), map((email) => this.emailToSalt(email)));
}
masterKeyHash$(userId: UserId): Observable<string> {
if (userId == null) {
throw new Error("User ID is required.");

View File

@@ -1,14 +1,33 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
import { MasterPasswordAuthenticationData, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types";
// eslint-disable-next-line no-restricted-imports
import { KdfType } from "@bitwarden/key-management";
import { PasswordRequest } from "../../auth/models/request/password.request";
export class KdfRequest extends PasswordRequest {
/** @deprecated */
kdf: KdfType;
/** @deprecated */
kdfIterations: number;
/** @deprecated */
kdfMemory?: number;
kdfParallelism?: number;
/** @deprecated */
kdfParallelism?: number;;
constructor(authenticationData: MasterPasswordAuthenticationData, unlockData: MasterPasswordUnlockData) {
super(authenticationData, unlockData);
const kdf = authenticationData.kdf;
if (kdf.kdfType === KdfType.PBKDF2_SHA256) {
this.kdf = KdfType.PBKDF2_SHA256;
this.kdfIterations = kdf.iterations;
} else if (kdf.kdfType === KdfType.Argon2id) {
this.kdf = KdfType.Argon2id;
this.kdfIterations = kdf.iterations;
this.kdfMemory = kdf.memory;
this.kdfParallelism = kdf.parallelism;
} else {
throw new Error(`Unsupported KDF type: ${kdf}`);
}
}
}

View File

@@ -148,6 +148,12 @@ export abstract class KeyService {
*/
abstract hasUserKey(userId: UserId): Promise<boolean>;
/**
* Makes a v1 user key, and does not wrap it.
* @deprecated Note: This is a place-holder until account registration is moved to a higher level
* / migrated to the SDK, and should not be used for new features.
*/
abstract makeUserKeyV1Raw(): Promise<UserKey>;
/**
* Generates a new user key
* @throws Error when master key is null and there is no active user
@@ -170,6 +176,7 @@ export abstract class KeyService {
abstract getOrDeriveMasterKey(password: string, userId?: string): Promise<MasterKey>;
/**
* Generates a master key from the provided password
* @deprecated
* @param password The user's master password
* @param email The user's email
* @param KdfConfig The user's key derivation function configuration
@@ -191,6 +198,7 @@ export abstract class KeyService {
* Creates a master password hash from the user's master password. Can
* be used for local authentication or for server authentication depending
* on the hashPurpose provided.
* @deprecated
* @throws Error when password is null or key is null and no active user or active user have no master key
* @param password The user's master password
* @param key The user's master key or active's user master key.

View File

@@ -209,6 +209,11 @@ export class DefaultKeyService implements KeyServiceAbstraction {
return (await firstValueFrom(this.stateProvider.getUserState$(USER_KEY, userId))) != null;
}
async makeUserKeyV1Raw(): Promise<UserKey> {
const newUserKey = await this.keyGenerationService.createKey(512);
return newUserKey as UserKey;
}
/**
* @deprecated Please use `makeMasterPasswordUnlockData` in {@link MasterPasswordService} instead.
*/

View File

@@ -145,4 +145,19 @@ export class Argon2KdfConfig {
}
}
export function kdfConfigFromValues(
kdfType: KdfType,
iterations: number,
memory?: number,
parallelism?: number,
): KdfConfig {
if (kdfType === KdfType.PBKDF2_SHA256) {
return new PBKDF2KdfConfig(iterations);
} else if (kdfType === KdfType.Argon2id) {
return new Argon2KdfConfig(iterations, memory, parallelism);
} else {
throw new Error(`Unsupported KDF type: ${kdfType}`);
}
}
export const DEFAULT_KDF_CONFIG = new PBKDF2KdfConfig(PBKDF2KdfConfig.ITERATIONS.defaultValue);