mirror of
https://github.com/bitwarden/browser
synced 2026-02-06 11:43:51 +00:00
tmp
This commit is contained in:
@@ -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>
|
||||
@@ -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 {}
|
||||
@@ -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>
|
||||
@@ -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(["/"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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 {}
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
@@ -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"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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 {}
|
||||
@@ -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: "/",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from "./change-password.component";
|
||||
export * from "./change-password.service.abstraction";
|
||||
export * from "./default-change-password.service";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -157,8 +157,6 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
credentials.newPasswordHint,
|
||||
credentials.orgSsoIdentifier,
|
||||
keysRequest,
|
||||
credentials.kdfConfig.kdfType,
|
||||
credentials.kdfConfig.iterations,
|
||||
);
|
||||
|
||||
enrollmentRequest = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
@@ -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"]);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -4,5 +4,4 @@ import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class EmailTokenRequest extends SecretVerificationRequest {
|
||||
newEmail: string;
|
||||
masterPasswordHash: string;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user