1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-16541] Key rotation & enrollment trust for emergency access & organizations (#12655)

* Implement key rotation v2

* Pass through masterpassword hint

* Properly split old and new code

* Mark legacy rotation as deprecated

* Throw when data is null

* Cleanup

* Add tests

* Fix build

* Update libs/key-management/src/key.service.spec.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update apps/web/src/app/auth/settings/change-password.component.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Add documentation

* Centralize loading logic

* Implement trust dialogs

* Fix build and clean up

* Add tests for accept organization component

* Fix enrollment

* Update apps/web/src/app/admin-console/organizations/manage/organization-trust.component.html

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Cleanup according to feedback

* Change div to ng-container

* Init uninited strings

* Fix type errors on dialog config

* Fix typing

* Fix build

* Fix build

* Update libs/key-management-ui/src/key-rotation/key-rotation-trust-info.component.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Fix linting

* Undo legacy component import change

* Simplify dialog text

---------

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
This commit is contained in:
Bernd Schoolmann
2025-04-07 13:41:19 +02:00
committed by GitHub
parent c6821608d0
commit 1c44640ea5
26 changed files with 1048 additions and 103 deletions

View File

@@ -4,3 +4,6 @@
export { LockComponent } from "./lock/components/lock.component";
export { LockComponentService, UnlockOptions } from "./lock/services/lock-component.service";
export { KeyRotationTrustInfoComponent } from "./key-rotation/key-rotation-trust-info.component";
export { AccountRecoveryTrustComponent } from "./trust/account-recovery-trust.component";
export { EmergencyAccessTrustComponent } from "./trust/emergency-access-trust.component";

View File

@@ -0,0 +1,26 @@
<bit-dialog dialogSize="large">
<span bitDialogTitle>
<strong> {{ "userkeyRotationDisclaimerTitle" | i18n }} </strong>
</span>
<span bitDialogContent>
{{ "userkeyRotationDisclaimerDescription" | i18n }}
<ul class="tw-mt-2 tw-mb-0 tw-pl-4">
<li *ngIf="params.orgName != null">
{{ "userkeyRotationDisclaimerAccountRecoveryOrgsText" | i18n: params.orgName }}
</li>
<li *ngIf="params.numberOfEmergencyAccessUsers > 0">
{{
"userkeyRotationDisclaimerEmergencyAccessText" | i18n: params.numberOfEmergencyAccessUsers
}}
</li>
</ul>
</span>
<ng-container bitDialogFooter>
<a bitButton target="_blank" rel="noreferrer" buttonType="primary" (click)="submit()">
{{ "continue" | i18n }}
</a>
<button bitButton type="button" buttonType="secondary" bitDialogClose>
{{ "close" | i18n }}
</button>
</ng-container>
</bit-dialog>

View File

@@ -0,0 +1,58 @@
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, Inject } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import {
AsyncActionsModule,
ButtonModule,
DialogModule,
DialogService,
} from "@bitwarden/components";
type KeyRotationTrustDialogData = {
orgName?: string;
numberOfEmergencyAccessUsers: number;
};
@Component({
selector: "key-rotation-trust-info",
templateUrl: "key-rotation-trust-info.component.html",
standalone: true,
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
ReactiveFormsModule,
AsyncActionsModule,
FormsModule,
],
})
export class KeyRotationTrustInfoComponent {
constructor(
@Inject(DIALOG_DATA) protected params: KeyRotationTrustDialogData,
private logService: LogService,
private dialogRef: DialogRef<boolean>,
) {}
async submit() {
try {
this.dialogRef.close(true);
} catch (e) {
this.logService.error(e);
}
}
/**
* Strongly typed helper to open a KeyRotationTrustComponent
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param data The data to pass to the dialog
*/
static open(dialogService: DialogService, data: KeyRotationTrustDialogData) {
return dialogService.open<boolean, KeyRotationTrustDialogData>(KeyRotationTrustInfoComponent, {
data,
});
}
}

View File

@@ -0,0 +1,21 @@
<bit-dialog
dialogSize="large"
[loading]="loading"
[title]="'trustOrganization' | i18n"
[subtitle]="params.name"
>
<ng-container bitDialogContent>
<bit-callout type="warning">{{ "orgTrustWarning" | i18n }}</bit-callout>
<p bitTypography="body1">
{{ "fingerprintPhrase" | i18n }} <code>{{ fingerprint }}</code>
</p>
</ng-container>
<ng-container bitDialogFooter>
<button buttonType="primary" bitButton bitFormButton type="button" (click)="submit()">
<span>{{ "trust" | i18n }}</span>
</button>
<button bitButton bitFormButton buttonType="secondary" type="button" bitDialogClose>
{{ "doNotTrust" | i18n }}
</button>
</ng-container>
</bit-dialog>

View File

@@ -0,0 +1,94 @@
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, OnInit, Inject } from "@angular/core";
import { FormBuilder, FormsModule, ReactiveFormsModule } from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import {
AsyncActionsModule,
ButtonModule,
CalloutModule,
DialogModule,
DialogService,
FormFieldModule,
LinkModule,
TypographyModule,
} from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
type AccountRecoveryTrustDialogData = {
/** display name of the user */
name: string;
/** org id */
orgId: string;
/** org public key */
publicKey: Uint8Array;
};
@Component({
selector: "account-recovery-trust",
templateUrl: "account-recovery-trust.component.html",
standalone: true,
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
FormsModule,
CalloutModule,
],
})
export class AccountRecoveryTrustComponent implements OnInit {
loading = true;
fingerprint: string = "";
confirmForm = this.formBuilder.group({});
constructor(
@Inject(DIALOG_DATA) protected params: AccountRecoveryTrustDialogData,
private formBuilder: FormBuilder,
private keyService: KeyService,
private logService: LogService,
private dialogRef: DialogRef<boolean>,
) {}
async ngOnInit() {
try {
const fingerprint = await this.keyService.getFingerprint(
this.params.orgId,
this.params.publicKey,
);
if (fingerprint != null) {
this.fingerprint = fingerprint.join("-");
}
} catch (e) {
this.logService.error(e);
}
this.loading = false;
}
async submit() {
if (this.loading) {
return;
}
this.dialogRef.close(true);
}
/**
* Strongly typed helper to open a AccountRecoveryTrustComponent
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param data The data to pass to the dialog
*/
static open(dialogService: DialogService, data: AccountRecoveryTrustDialogData) {
return dialogService.open<boolean, AccountRecoveryTrustDialogData>(
AccountRecoveryTrustComponent,
{
data,
},
);
}
}

View File

@@ -0,0 +1,32 @@
<bit-dialog
dialogSize="large"
[loading]="loading"
[title]="'trustUser' | i18n"
[subtitle]="params.name"
>
<ng-container bitDialogContent>
<bit-callout type="warning">{{ "emergencyAccessTrustWarning" | i18n }}</bit-callout>
<p bitTypography="body1">
{{ "fingerprintEnsureIntegrityVerify" | i18n }}
<a
bitLink
href="https://bitwarden.com/help/fingerprint-phrase/"
target="_blank"
rel="noopener"
>
{{ "learnMore" | i18n }}</a
>
</p>
<p bitTypography="body1">
<code>{{ fingerprint }}</code>
</p>
</ng-container>
<ng-container bitDialogFooter>
<button buttonType="primary" bitButton bitFormButton type="button" (click)="submit()">
<span>{{ "trust" | i18n }}</span>
</button>
<button bitButton bitFormButton buttonType="secondary" type="button" bitDialogClose>
{{ "doNotTrust" | i18n }}
</button>
</ng-container>
</bit-dialog>

View File

@@ -0,0 +1,94 @@
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, OnInit, Inject } from "@angular/core";
import { FormBuilder, FormsModule, ReactiveFormsModule } from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import {
AsyncActionsModule,
ButtonModule,
CalloutModule,
DialogModule,
DialogService,
FormFieldModule,
LinkModule,
TypographyModule,
} from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
type EmergencyAccessTrustDialogData = {
/** display name of the user */
name: string;
/** userid of the user */
userId: string;
/** user public key */
publicKey: Uint8Array;
};
@Component({
selector: "emergency-access-trust",
templateUrl: "emergency-access-trust.component.html",
standalone: true,
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
FormsModule,
CalloutModule,
],
})
export class EmergencyAccessTrustComponent implements OnInit {
loading = true;
fingerprint: string = "";
confirmForm = this.formBuilder.group({});
constructor(
@Inject(DIALOG_DATA) protected params: EmergencyAccessTrustDialogData,
private formBuilder: FormBuilder,
private keyService: KeyService,
private logService: LogService,
private dialogRef: DialogRef<boolean, EmergencyAccessTrustComponent>,
) {}
async ngOnInit() {
try {
const fingerprint = await this.keyService.getFingerprint(
this.params.userId,
this.params.publicKey,
);
if (fingerprint != null) {
this.fingerprint = fingerprint.join("-");
}
} catch (e) {
this.logService.error(e);
}
this.loading = false;
}
async submit() {
if (this.loading) {
return;
}
this.dialogRef.close(true);
}
/**
* Strongly typed helper to open a EmergencyAccessTrustComponent
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param data The data to pass to the dialog
*/
static open(dialogService: DialogService, data: EmergencyAccessTrustDialogData) {
return dialogService.open<boolean, EmergencyAccessTrustDialogData>(
EmergencyAccessTrustComponent,
{
data,
},
);
}
}