mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 14:53:33 +00:00
[PM-3797 Part 1] Add Emergency Access Service (#6612)
* lazy load and move accept emergency component
* create emergency access services
- move api calls to specific api service and refactor
* remove any from emergency api service
* move emergency access logic to service
* create emergency access view
* move view ciphers logic to service
* move models to web folder
* move takeover logic to service
* remove emergency api service dependency from other files
* write tests for emergency access service
* import shared module into component
* fix imports
* Revert "fix imports"
This reverts commit d21cb02bd8.
* create emergency access module for service
* move emergency access out of core folder
- add more organization to components under settings
* change EA views to domain models
* move EA enums to folder
* resolve PR feedback
This commit is contained in:
@@ -1,16 +1,18 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { EmergencyAccessAcceptRequest } from "@bitwarden/common/auth/models/request/emergency-access-accept.request";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
|
||||||
import { BaseAcceptComponent } from "../common/base.accept.component";
|
import { BaseAcceptComponent } from "../../../common/base.accept.component";
|
||||||
|
import { SharedModule } from "../../../shared";
|
||||||
|
import { EmergencyAccessModule } from "../emergency-access.module";
|
||||||
|
import { EmergencyAccessService } from "../services/emergency-access.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-accept-emergency",
|
standalone: true,
|
||||||
|
imports: [SharedModule, EmergencyAccessModule],
|
||||||
templateUrl: "accept-emergency.component.html",
|
templateUrl: "accept-emergency.component.html",
|
||||||
})
|
})
|
||||||
export class AcceptEmergencyComponent extends BaseAcceptComponent {
|
export class AcceptEmergencyComponent extends BaseAcceptComponent {
|
||||||
@@ -25,16 +27,14 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent {
|
|||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
route: ActivatedRoute,
|
route: ActivatedRoute,
|
||||||
private apiService: ApiService,
|
stateService: StateService,
|
||||||
stateService: StateService
|
private emergencyAccessService: EmergencyAccessService
|
||||||
) {
|
) {
|
||||||
super(router, platformUtilsService, i18nService, route, stateService);
|
super(router, platformUtilsService, i18nService, route, stateService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async authedHandler(qParams: Params): Promise<void> {
|
async authedHandler(qParams: Params): Promise<void> {
|
||||||
const request = new EmergencyAccessAcceptRequest();
|
this.actionPromise = this.emergencyAccessService.accept(qParams.id, qParams.token);
|
||||||
request.token = qParams.token;
|
|
||||||
this.actionPromise = this.apiService.postEmergencyAccessAccept(qParams.id, request);
|
|
||||||
await this.actionPromise;
|
await this.actionPromise;
|
||||||
await this.stateService.setEmergencyAccessInvitation(null);
|
await this.stateService.setEmergencyAccessInvitation(null);
|
||||||
this.platformUtilService.showToast(
|
this.platformUtilService.showToast(
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
|
import { EmergencyAccessApiService } from "./services/emergency-access-api.service";
|
||||||
|
import { EmergencyAccessService } from "./services/emergency-access.service";
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [],
|
||||||
|
imports: [],
|
||||||
|
providers: [EmergencyAccessApiService, EmergencyAccessService],
|
||||||
|
})
|
||||||
|
export class EmergencyAccessModule {}
|
||||||
2
apps/web/src/app/auth/emergency-access/index.ts
Normal file
2
apps/web/src/app/auth/emergency-access/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./emergency-access.module";
|
||||||
|
export * from "./services";
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { KdfType } from "@bitwarden/common/enums";
|
||||||
|
import { CipherResponse } from "@bitwarden/common/vault/models/response/cipher.response";
|
||||||
|
|
||||||
|
import { EmergencyAccessStatusType } from "../enums/emergency-access-status-type";
|
||||||
|
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
||||||
|
|
||||||
|
export class GranteeEmergencyAccess {
|
||||||
|
id: string;
|
||||||
|
granteeId: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
type: EmergencyAccessType;
|
||||||
|
status: EmergencyAccessStatusType;
|
||||||
|
waitTimeDays: number;
|
||||||
|
creationDate: string;
|
||||||
|
avatarColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GrantorEmergencyAccess {
|
||||||
|
id: string;
|
||||||
|
grantorId: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
type: EmergencyAccessType;
|
||||||
|
status: EmergencyAccessStatusType;
|
||||||
|
waitTimeDays: number;
|
||||||
|
creationDate: string;
|
||||||
|
avatarColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TakeoverTypeEmergencyAccess {
|
||||||
|
keyEncrypted: string;
|
||||||
|
kdf: KdfType;
|
||||||
|
kdfIterations: number;
|
||||||
|
kdfMemory?: number;
|
||||||
|
kdfParallelism?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ViewTypeEmergencyAccess {
|
||||||
|
keyEncrypted: string;
|
||||||
|
ciphers: CipherResponse[] = [];
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EmergencyAccessType } from "../../enums/emergency-access-type";
|
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
||||||
|
|
||||||
export class EmergencyAccessInviteRequest {
|
export class EmergencyAccessInviteRequest {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EmergencyAccessType } from "../../enums/emergency-access-type";
|
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
||||||
|
|
||||||
export class EmergencyAccessUpdateRequest {
|
export class EmergencyAccessUpdateRequest {
|
||||||
type: EmergencyAccessType;
|
type: EmergencyAccessType;
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { KdfType } from "../../../enums";
|
import { KdfType } from "@bitwarden/common/enums";
|
||||||
import { BaseResponse } from "../../../models/response/base.response";
|
import { BaseResponse } from "@bitwarden/common/models/response/base.response";
|
||||||
import { CipherResponse } from "../../../vault/models/response/cipher.response";
|
import { CipherResponse } from "@bitwarden/common/vault/models/response/cipher.response";
|
||||||
import { EmergencyAccessStatusType } from "../../enums/emergency-access-status-type";
|
|
||||||
import { EmergencyAccessType } from "../../enums/emergency-access-type";
|
import { EmergencyAccessStatusType } from "../enums/emergency-access-status-type";
|
||||||
|
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
||||||
|
|
||||||
export class EmergencyAccessGranteeDetailsResponse extends BaseResponse {
|
export class EmergencyAccessGranteeDetailsResponse extends BaseResponse {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
|
||||||
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
|
|
||||||
|
import { EmergencyAccessAcceptRequest } from "../request/emergency-access-accept.request";
|
||||||
|
import { EmergencyAccessConfirmRequest } from "../request/emergency-access-confirm.request";
|
||||||
|
import { EmergencyAccessInviteRequest } from "../request/emergency-access-invite.request";
|
||||||
|
import { EmergencyAccessPasswordRequest } from "../request/emergency-access-password.request";
|
||||||
|
import { EmergencyAccessUpdateRequest } from "../request/emergency-access-update.request";
|
||||||
|
import {
|
||||||
|
EmergencyAccessGranteeDetailsResponse,
|
||||||
|
EmergencyAccessGrantorDetailsResponse,
|
||||||
|
EmergencyAccessTakeoverResponse,
|
||||||
|
EmergencyAccessViewResponse,
|
||||||
|
} from "../response/emergency-access.response";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EmergencyAccessApiService {
|
||||||
|
constructor(private apiService: ApiService) {}
|
||||||
|
|
||||||
|
async getEmergencyAccessTrusted(): Promise<ListResponse<EmergencyAccessGranteeDetailsResponse>> {
|
||||||
|
const r = await this.apiService.send("GET", "/emergency-access/trusted", null, true, true);
|
||||||
|
return new ListResponse(r, EmergencyAccessGranteeDetailsResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEmergencyAccessGranted(): Promise<ListResponse<EmergencyAccessGrantorDetailsResponse>> {
|
||||||
|
const r = await this.apiService.send("GET", "/emergency-access/granted", null, true, true);
|
||||||
|
return new ListResponse(r, EmergencyAccessGrantorDetailsResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEmergencyAccess(id: string): Promise<EmergencyAccessGranteeDetailsResponse> {
|
||||||
|
const r = await this.apiService.send("GET", "/emergency-access/" + id, null, true, true);
|
||||||
|
return new EmergencyAccessGranteeDetailsResponse(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEmergencyGrantorPolicies(id: string): Promise<ListResponse<PolicyResponse>> {
|
||||||
|
const r = await this.apiService.send(
|
||||||
|
"GET",
|
||||||
|
"/emergency-access/" + id + "/policies",
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
return new ListResponse(r, PolicyResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
putEmergencyAccess(id: string, request: EmergencyAccessUpdateRequest): Promise<void> {
|
||||||
|
return this.apiService.send("PUT", "/emergency-access/" + id, request, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteEmergencyAccess(id: string): Promise<void> {
|
||||||
|
return this.apiService.send("DELETE", "/emergency-access/" + id, null, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
postEmergencyAccessInvite(request: EmergencyAccessInviteRequest): Promise<void> {
|
||||||
|
return this.apiService.send("POST", "/emergency-access/invite", request, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
postEmergencyAccessReinvite(id: string): Promise<void> {
|
||||||
|
return this.apiService.send("POST", "/emergency-access/" + id + "/reinvite", null, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
postEmergencyAccessAccept(id: string, request: EmergencyAccessAcceptRequest): Promise<void> {
|
||||||
|
return this.apiService.send(
|
||||||
|
"POST",
|
||||||
|
"/emergency-access/" + id + "/accept",
|
||||||
|
request,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
postEmergencyAccessConfirm(id: string, request: EmergencyAccessConfirmRequest): Promise<void> {
|
||||||
|
return this.apiService.send(
|
||||||
|
"POST",
|
||||||
|
"/emergency-access/" + id + "/confirm",
|
||||||
|
request,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
postEmergencyAccessInitiate(id: string): Promise<void> {
|
||||||
|
return this.apiService.send("POST", "/emergency-access/" + id + "/initiate", null, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
postEmergencyAccessApprove(id: string): Promise<void> {
|
||||||
|
return this.apiService.send("POST", "/emergency-access/" + id + "/approve", null, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
postEmergencyAccessReject(id: string): Promise<void> {
|
||||||
|
return this.apiService.send("POST", "/emergency-access/" + id + "/reject", null, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async postEmergencyAccessTakeover(id: string): Promise<EmergencyAccessTakeoverResponse> {
|
||||||
|
const r = await this.apiService.send(
|
||||||
|
"POST",
|
||||||
|
"/emergency-access/" + id + "/takeover",
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
return new EmergencyAccessTakeoverResponse(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
async postEmergencyAccessPassword(
|
||||||
|
id: string,
|
||||||
|
request: EmergencyAccessPasswordRequest
|
||||||
|
): Promise<void> {
|
||||||
|
await this.apiService.send(
|
||||||
|
"POST",
|
||||||
|
"/emergency-access/" + id + "/password",
|
||||||
|
request,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async postEmergencyAccessView(id: string): Promise<EmergencyAccessViewResponse> {
|
||||||
|
const r = await this.apiService.send(
|
||||||
|
"POST",
|
||||||
|
"/emergency-access/" + id + "/view",
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
return new EmergencyAccessViewResponse(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
import { MockProxy } from "jest-mock-extended";
|
||||||
|
import mock from "jest-mock-extended/lib/Mock";
|
||||||
|
|
||||||
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { EncryptionType, KdfType } from "@bitwarden/common/enums";
|
||||||
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
|
import { UserKeyResponse } from "@bitwarden/common/models/response/user-key.response";
|
||||||
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
|
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||||
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
|
import {
|
||||||
|
UserKey,
|
||||||
|
SymmetricCryptoKey,
|
||||||
|
MasterKey,
|
||||||
|
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
|
||||||
|
import { EmergencyAccessStatusType } from "../enums/emergency-access-status-type";
|
||||||
|
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
||||||
|
import { EmergencyAccessPasswordRequest } from "../request/emergency-access-password.request";
|
||||||
|
import { EmergencyAccessUpdateRequest } from "../request/emergency-access-update.request";
|
||||||
|
import {
|
||||||
|
EmergencyAccessGranteeDetailsResponse,
|
||||||
|
EmergencyAccessTakeoverResponse,
|
||||||
|
} from "../response/emergency-access.response";
|
||||||
|
|
||||||
|
import { EmergencyAccessApiService } from "./emergency-access-api.service";
|
||||||
|
import { EmergencyAccessService } from "./emergency-access.service";
|
||||||
|
|
||||||
|
describe("EmergencyAccessService", () => {
|
||||||
|
let emergencyAccessApiService: MockProxy<EmergencyAccessApiService>;
|
||||||
|
let apiService: MockProxy<ApiService>;
|
||||||
|
let cryptoService: MockProxy<CryptoService>;
|
||||||
|
let encryptService: MockProxy<EncryptService>;
|
||||||
|
let cipherService: MockProxy<CipherService>;
|
||||||
|
let logService: MockProxy<LogService>;
|
||||||
|
let emergencyAccessService: EmergencyAccessService;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
emergencyAccessApiService = mock<EmergencyAccessApiService>();
|
||||||
|
apiService = mock<ApiService>();
|
||||||
|
cryptoService = mock<CryptoService>();
|
||||||
|
encryptService = mock<EncryptService>();
|
||||||
|
cipherService = mock<CipherService>();
|
||||||
|
logService = mock<LogService>();
|
||||||
|
|
||||||
|
emergencyAccessService = new EmergencyAccessService(
|
||||||
|
emergencyAccessApiService,
|
||||||
|
apiService,
|
||||||
|
cryptoService,
|
||||||
|
encryptService,
|
||||||
|
cipherService,
|
||||||
|
logService
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("3 step setup process", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Step 1: invite", () => {
|
||||||
|
it("should post an emergency access invitation", async () => {
|
||||||
|
// Arrange
|
||||||
|
const email = "test@example.com";
|
||||||
|
const type = EmergencyAccessType.View;
|
||||||
|
const waitTimeDays = 5;
|
||||||
|
|
||||||
|
emergencyAccessApiService.postEmergencyAccessInvite.mockResolvedValueOnce();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await emergencyAccessService.invite(email, type, waitTimeDays);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(emergencyAccessApiService.postEmergencyAccessInvite).toHaveBeenCalledWith({
|
||||||
|
email: email.trim(),
|
||||||
|
type: type,
|
||||||
|
waitTimeDays: waitTimeDays,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Step 2: accept", () => {
|
||||||
|
it("should post an emergency access accept request", async () => {
|
||||||
|
// Arrange
|
||||||
|
const id = "some-id";
|
||||||
|
const token = "some-token";
|
||||||
|
|
||||||
|
emergencyAccessApiService.postEmergencyAccessAccept.mockResolvedValueOnce();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await emergencyAccessService.accept(id, token);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(emergencyAccessApiService.postEmergencyAccessAccept).toHaveBeenCalledWith(id, {
|
||||||
|
token: token,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Step 3: confirm", () => {
|
||||||
|
it("should post an emergency access confirmation", async () => {
|
||||||
|
// Arrange
|
||||||
|
const id = "some-id";
|
||||||
|
const granteeId = "grantee-id";
|
||||||
|
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
|
||||||
|
|
||||||
|
const mockPublicKeyB64 = "some-public-key-in-base64";
|
||||||
|
|
||||||
|
// const publicKey = Utils.fromB64ToArray(publicKeyB64);
|
||||||
|
|
||||||
|
const mockUserPublicKeyResponse = new UserKeyResponse({
|
||||||
|
UserId: granteeId,
|
||||||
|
PublicKey: mockPublicKeyB64,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockUserPublicKeyEncryptedUserKey = new EncString(
|
||||||
|
EncryptionType.AesCbc256_HmacSha256_B64,
|
||||||
|
"mockUserPublicKeyEncryptedUserKey"
|
||||||
|
);
|
||||||
|
|
||||||
|
cryptoService.getUserKey.mockResolvedValueOnce(mockUserKey);
|
||||||
|
apiService.getUserPublicKey.mockResolvedValueOnce(mockUserPublicKeyResponse);
|
||||||
|
|
||||||
|
cryptoService.rsaEncrypt.mockResolvedValueOnce(mockUserPublicKeyEncryptedUserKey);
|
||||||
|
|
||||||
|
emergencyAccessApiService.postEmergencyAccessConfirm.mockResolvedValueOnce();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await emergencyAccessService.confirm(id, granteeId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(emergencyAccessApiService.postEmergencyAccessConfirm).toHaveBeenCalledWith(id, {
|
||||||
|
key: mockUserPublicKeyEncryptedUserKey.encryptedString,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("takeover", () => {
|
||||||
|
const mockId = "emergencyAccessId";
|
||||||
|
const mockEmail = "emergencyAccessEmail";
|
||||||
|
const mockName = "emergencyAccessName";
|
||||||
|
|
||||||
|
it("posts a new password when decryption succeeds", async () => {
|
||||||
|
// Arrange
|
||||||
|
emergencyAccessApiService.postEmergencyAccessTakeover.mockResolvedValueOnce({
|
||||||
|
keyEncrypted: "EncryptedKey",
|
||||||
|
kdf: KdfType.PBKDF2_SHA256,
|
||||||
|
kdfIterations: 500,
|
||||||
|
} as EmergencyAccessTakeoverResponse);
|
||||||
|
|
||||||
|
const mockDecryptedGrantorUserKey = new Uint8Array(64);
|
||||||
|
cryptoService.rsaDecrypt.mockResolvedValueOnce(mockDecryptedGrantorUserKey);
|
||||||
|
|
||||||
|
const mockMasterKey = new SymmetricCryptoKey(new Uint8Array(64) as CsprngArray) as MasterKey;
|
||||||
|
|
||||||
|
cryptoService.makeMasterKey.mockResolvedValueOnce(mockMasterKey);
|
||||||
|
|
||||||
|
const mockMasterKeyHash = "mockMasterKeyHash";
|
||||||
|
cryptoService.hashMasterKey.mockResolvedValueOnce(mockMasterKeyHash);
|
||||||
|
|
||||||
|
// must mock [UserKey, EncString] return from cryptoService.encryptUserKeyWithMasterKey
|
||||||
|
// where UserKey is the decrypted grantor user key
|
||||||
|
const mockMasterKeyEncryptedUserKey = new EncString(
|
||||||
|
EncryptionType.AesCbc256_HmacSha256_B64,
|
||||||
|
"mockMasterKeyEncryptedUserKey"
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockUserKey = new SymmetricCryptoKey(mockDecryptedGrantorUserKey) as UserKey;
|
||||||
|
|
||||||
|
cryptoService.encryptUserKeyWithMasterKey.mockResolvedValueOnce([
|
||||||
|
mockUserKey,
|
||||||
|
mockMasterKeyEncryptedUserKey,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const expectedEmergencyAccessPasswordRequest = new EmergencyAccessPasswordRequest();
|
||||||
|
expectedEmergencyAccessPasswordRequest.newMasterPasswordHash = mockMasterKeyHash;
|
||||||
|
expectedEmergencyAccessPasswordRequest.key = mockMasterKeyEncryptedUserKey.encryptedString;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await emergencyAccessService.takeover(mockId, mockEmail, mockName);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(emergencyAccessApiService.postEmergencyAccessPassword).toHaveBeenCalledWith(
|
||||||
|
mockId,
|
||||||
|
expectedEmergencyAccessPasswordRequest
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not post a new password if decryption fails", async () => {
|
||||||
|
cryptoService.rsaDecrypt.mockResolvedValueOnce(null);
|
||||||
|
emergencyAccessApiService.postEmergencyAccessTakeover.mockResolvedValueOnce({
|
||||||
|
keyEncrypted: "EncryptedKey",
|
||||||
|
kdf: KdfType.PBKDF2_SHA256,
|
||||||
|
kdfIterations: 500,
|
||||||
|
} as EmergencyAccessTakeoverResponse);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
emergencyAccessService.takeover(mockId, mockEmail, mockName)
|
||||||
|
).rejects.toThrowError("Failed to decrypt grantor key");
|
||||||
|
|
||||||
|
expect(emergencyAccessApiService.postEmergencyAccessPassword).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("rotate", () => {
|
||||||
|
let mockUserKey: UserKey;
|
||||||
|
const allowedStatuses = [
|
||||||
|
EmergencyAccessStatusType.Confirmed,
|
||||||
|
EmergencyAccessStatusType.RecoveryInitiated,
|
||||||
|
EmergencyAccessStatusType.RecoveryApproved,
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockEmergencyAccess = {
|
||||||
|
data: [
|
||||||
|
createMockEmergencyAccess("0", "EA 0", EmergencyAccessStatusType.Invited),
|
||||||
|
createMockEmergencyAccess("1", "EA 1", EmergencyAccessStatusType.Accepted),
|
||||||
|
createMockEmergencyAccess("2", "EA 2", EmergencyAccessStatusType.Confirmed),
|
||||||
|
createMockEmergencyAccess("3", "EA 3", EmergencyAccessStatusType.RecoveryInitiated),
|
||||||
|
createMockEmergencyAccess("4", "EA 4", EmergencyAccessStatusType.RecoveryApproved),
|
||||||
|
],
|
||||||
|
} as ListResponse<EmergencyAccessGranteeDetailsResponse>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||||
|
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||||
|
|
||||||
|
emergencyAccessApiService.getEmergencyAccessTrusted.mockResolvedValue(mockEmergencyAccess);
|
||||||
|
apiService.getUserPublicKey.mockResolvedValue({
|
||||||
|
userId: "mockUserId",
|
||||||
|
publicKey: "mockPublicKey",
|
||||||
|
} as UserKeyResponse);
|
||||||
|
|
||||||
|
cryptoService.rsaEncrypt.mockImplementation((plainValue, publicKey) => {
|
||||||
|
return Promise.resolve(
|
||||||
|
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "Encrypted: " + plainValue)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Only updates emergency accesses with allowed statuses", async () => {
|
||||||
|
await emergencyAccessService.rotate(mockUserKey);
|
||||||
|
|
||||||
|
let expectedCallCount = 0;
|
||||||
|
|
||||||
|
mockEmergencyAccess.data.forEach((emergencyAccess) => {
|
||||||
|
if (allowedStatuses.includes(emergencyAccess.status)) {
|
||||||
|
expect(emergencyAccessApiService.putEmergencyAccess).toHaveBeenCalledWith(
|
||||||
|
emergencyAccess.id,
|
||||||
|
expect.any(EmergencyAccessUpdateRequest)
|
||||||
|
);
|
||||||
|
expectedCallCount++;
|
||||||
|
} else {
|
||||||
|
expect(emergencyAccessApiService.putEmergencyAccess).not.toHaveBeenCalledWith(
|
||||||
|
emergencyAccess.id,
|
||||||
|
expect.any(EmergencyAccessUpdateRequest)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(emergencyAccessApiService.putEmergencyAccess).toHaveBeenCalledTimes(expectedCallCount);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createMockEmergencyAccess(
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
status: EmergencyAccessStatusType
|
||||||
|
): EmergencyAccessGranteeDetailsResponse {
|
||||||
|
const emergencyAccess = new EmergencyAccessGranteeDetailsResponse({});
|
||||||
|
emergencyAccess.id = id;
|
||||||
|
emergencyAccess.name = name;
|
||||||
|
emergencyAccess.type = 0;
|
||||||
|
emergencyAccess.status = status;
|
||||||
|
return emergencyAccess;
|
||||||
|
}
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
|
||||||
|
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||||
|
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
|
||||||
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
|
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||||
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
|
import {
|
||||||
|
SymmetricCryptoKey,
|
||||||
|
UserKey,
|
||||||
|
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
|
import { EmergencyAccessStatusType } from "../enums/emergency-access-status-type";
|
||||||
|
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
||||||
|
import { GranteeEmergencyAccess, GrantorEmergencyAccess } from "../models/emergency-access";
|
||||||
|
import { EmergencyAccessAcceptRequest } from "../request/emergency-access-accept.request";
|
||||||
|
import { EmergencyAccessConfirmRequest } from "../request/emergency-access-confirm.request";
|
||||||
|
import { EmergencyAccessInviteRequest } from "../request/emergency-access-invite.request";
|
||||||
|
import { EmergencyAccessPasswordRequest } from "../request/emergency-access-password.request";
|
||||||
|
import { EmergencyAccessUpdateRequest } from "../request/emergency-access-update.request";
|
||||||
|
|
||||||
|
import { EmergencyAccessApiService } from "./emergency-access-api.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EmergencyAccessService {
|
||||||
|
constructor(
|
||||||
|
private emergencyAccessApiService: EmergencyAccessApiService,
|
||||||
|
private apiService: ApiService,
|
||||||
|
private cryptoService: CryptoService,
|
||||||
|
private encryptService: EncryptService,
|
||||||
|
private cipherService: CipherService,
|
||||||
|
private logService: LogService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an emergency access by id.
|
||||||
|
* @param id emergency access id
|
||||||
|
*/
|
||||||
|
getEmergencyAccess(id: string): Promise<GranteeEmergencyAccess> {
|
||||||
|
return this.emergencyAccessApiService.getEmergencyAccess(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all emergency access that the user has been granted.
|
||||||
|
*/
|
||||||
|
async getEmergencyAccessTrusted(): Promise<GranteeEmergencyAccess[]> {
|
||||||
|
return (await this.emergencyAccessApiService.getEmergencyAccessTrusted()).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all emergency access that the user has granted.
|
||||||
|
*/
|
||||||
|
async getEmergencyAccessGranted(): Promise<GrantorEmergencyAccess[]> {
|
||||||
|
return (await this.emergencyAccessApiService.getEmergencyAccessGranted()).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns policies that apply to the grantor.
|
||||||
|
* Intended for grantee.
|
||||||
|
* @param id emergency access id
|
||||||
|
*/
|
||||||
|
async getGrantorPolicies(id: string): Promise<Policy[]> {
|
||||||
|
const response = await this.emergencyAccessApiService.getEmergencyGrantorPolicies(id);
|
||||||
|
let policies: Policy[];
|
||||||
|
if (response.data != null && response.data.length > 0) {
|
||||||
|
policies = response.data.map((policyResponse) => new Policy(new PolicyData(policyResponse)));
|
||||||
|
}
|
||||||
|
return policies;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invites the email address to be an emergency contact.
|
||||||
|
* Step 1 of the 3 step setup flow.
|
||||||
|
* Intended for grantor.
|
||||||
|
* @param email email address of trusted emergency contact
|
||||||
|
* @param type type of emergency access
|
||||||
|
* @param waitTimeDays number of days to wait before granting access
|
||||||
|
*/
|
||||||
|
async invite(email: string, type: EmergencyAccessType, waitTimeDays: number): Promise<void> {
|
||||||
|
const request = new EmergencyAccessInviteRequest();
|
||||||
|
request.email = email.trim();
|
||||||
|
request.type = type;
|
||||||
|
request.waitTimeDays = waitTimeDays;
|
||||||
|
|
||||||
|
await this.emergencyAccessApiService.postEmergencyAccessInvite(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends another email for an existing emergency access invitation.
|
||||||
|
* Intended for grantor.
|
||||||
|
* @param id emergency access id
|
||||||
|
*/
|
||||||
|
reinvite(id: string): Promise<void> {
|
||||||
|
return this.emergencyAccessApiService.postEmergencyAccessReinvite(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits an existing emergency access.
|
||||||
|
* Intended for grantor.
|
||||||
|
* @param id emergency access id
|
||||||
|
* @param type type of emergency access
|
||||||
|
* @param waitTimeDays number of days to wait before granting access
|
||||||
|
*/
|
||||||
|
async update(id: string, type: EmergencyAccessType, waitTimeDays: number) {
|
||||||
|
const request = new EmergencyAccessUpdateRequest();
|
||||||
|
request.type = type;
|
||||||
|
request.waitTimeDays = waitTimeDays;
|
||||||
|
|
||||||
|
await this.emergencyAccessApiService.putEmergencyAccess(id, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts an emergency access invitation.
|
||||||
|
* Step 2 of the 3 step setup flow.
|
||||||
|
* Intended for grantee.
|
||||||
|
* @param id emergency access id
|
||||||
|
* @param token secret token provided in email
|
||||||
|
*/
|
||||||
|
async accept(id: string, token: string): Promise<void> {
|
||||||
|
const request = new EmergencyAccessAcceptRequest();
|
||||||
|
request.token = token;
|
||||||
|
|
||||||
|
await this.emergencyAccessApiService.postEmergencyAccessAccept(id, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts user key with grantee's public key and sends to bitwarden.
|
||||||
|
* Step 3 of the 3 step setup flow.
|
||||||
|
* Intended for grantor.
|
||||||
|
* @param id emergency access id
|
||||||
|
* @param token secret token provided in email
|
||||||
|
*/
|
||||||
|
async confirm(id: string, granteeId: string) {
|
||||||
|
const userKey = await this.cryptoService.getUserKey();
|
||||||
|
if (!userKey) {
|
||||||
|
throw new Error("No user key found");
|
||||||
|
}
|
||||||
|
const publicKeyResponse = await this.apiService.getUserPublicKey(granteeId);
|
||||||
|
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.logService.debug(
|
||||||
|
"User's fingerprint: " +
|
||||||
|
(await this.cryptoService.getFingerprint(granteeId, publicKey)).join("-")
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// Ignore errors since it's just a debug message
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = new EmergencyAccessConfirmRequest();
|
||||||
|
request.key = await this.encryptKey(userKey, publicKey);
|
||||||
|
await this.emergencyAccessApiService.postEmergencyAccessConfirm(id, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an existing emergency access.
|
||||||
|
* Intended for either grantor or grantee.
|
||||||
|
* @param id emergency access id
|
||||||
|
*/
|
||||||
|
delete(id: string): Promise<void> {
|
||||||
|
return this.emergencyAccessApiService.deleteEmergencyAccess(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests access to grantor's vault.
|
||||||
|
* Intended for grantee.
|
||||||
|
* @param id emergency access id
|
||||||
|
*/
|
||||||
|
requestAccess(id: string): Promise<void> {
|
||||||
|
return this.emergencyAccessApiService.postEmergencyAccessInitiate(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Approves access to grantor's vault.
|
||||||
|
* Intended for grantor.
|
||||||
|
* @param id emergency access id
|
||||||
|
*/
|
||||||
|
approve(id: string): Promise<void> {
|
||||||
|
return this.emergencyAccessApiService.postEmergencyAccessApprove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rejects access to grantor's vault.
|
||||||
|
* Intended for grantor.
|
||||||
|
* @param id emergency access id
|
||||||
|
*/
|
||||||
|
reject(id: string): Promise<void> {
|
||||||
|
return this.emergencyAccessApiService.postEmergencyAccessReject(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the grantor ciphers for an emergency access in view mode.
|
||||||
|
* Intended for grantee.
|
||||||
|
* @param id emergency access id
|
||||||
|
*/
|
||||||
|
async getViewOnlyCiphers(id: string): Promise<CipherView[]> {
|
||||||
|
const response = await this.emergencyAccessApiService.postEmergencyAccessView(id);
|
||||||
|
|
||||||
|
const grantorKeyBuffer = await this.cryptoService.rsaDecrypt(response.keyEncrypted);
|
||||||
|
const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey;
|
||||||
|
|
||||||
|
const ciphers = await this.encryptService.decryptItems(
|
||||||
|
response.ciphers.map((c) => new Cipher(c)),
|
||||||
|
grantorUserKey
|
||||||
|
);
|
||||||
|
return ciphers.sort(this.cipherService.getLocaleSortingFunction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the password for an emergency access.
|
||||||
|
* Intended for grantee.
|
||||||
|
* @param id emergency access id
|
||||||
|
* @param masterPassword new master password
|
||||||
|
* @param email email address of grantee (must be consistent or login will fail)
|
||||||
|
*/
|
||||||
|
async takeover(id: string, masterPassword: string, email: string) {
|
||||||
|
const takeoverResponse = await this.emergencyAccessApiService.postEmergencyAccessTakeover(id);
|
||||||
|
|
||||||
|
const grantorKeyBuffer = await this.cryptoService.rsaDecrypt(takeoverResponse.keyEncrypted);
|
||||||
|
if (grantorKeyBuffer == null) {
|
||||||
|
throw new Error("Failed to decrypt grantor key");
|
||||||
|
}
|
||||||
|
|
||||||
|
const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey;
|
||||||
|
|
||||||
|
const masterKey = await this.cryptoService.makeMasterKey(
|
||||||
|
masterPassword,
|
||||||
|
email,
|
||||||
|
takeoverResponse.kdf,
|
||||||
|
new KdfConfig(
|
||||||
|
takeoverResponse.kdfIterations,
|
||||||
|
takeoverResponse.kdfMemory,
|
||||||
|
takeoverResponse.kdfParallelism
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const masterKeyHash = await this.cryptoService.hashMasterKey(masterPassword, masterKey);
|
||||||
|
|
||||||
|
const encKey = await this.cryptoService.encryptUserKeyWithMasterKey(masterKey, grantorUserKey);
|
||||||
|
|
||||||
|
const request = new EmergencyAccessPasswordRequest();
|
||||||
|
request.newMasterPasswordHash = masterKeyHash;
|
||||||
|
request.key = encKey[1].encryptedString;
|
||||||
|
|
||||||
|
this.emergencyAccessApiService.postEmergencyAccessPassword(id, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the user key for all existing emergency access.
|
||||||
|
* Intended for grantor.
|
||||||
|
* @param newUserKey the new user key
|
||||||
|
*/
|
||||||
|
async rotate(newUserKey: UserKey): Promise<void> {
|
||||||
|
const existingEmergencyAccess =
|
||||||
|
await this.emergencyAccessApiService.getEmergencyAccessTrusted();
|
||||||
|
// Any Invited or Accepted requests won't have the key yet, so we don't need to update them
|
||||||
|
const allowedStatuses = new Set([
|
||||||
|
EmergencyAccessStatusType.Confirmed,
|
||||||
|
EmergencyAccessStatusType.RecoveryInitiated,
|
||||||
|
EmergencyAccessStatusType.RecoveryApproved,
|
||||||
|
]);
|
||||||
|
const filteredAccesses = existingEmergencyAccess.data.filter((d) =>
|
||||||
|
allowedStatuses.has(d.status)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const details of filteredAccesses) {
|
||||||
|
// Get public key of grantee
|
||||||
|
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
|
||||||
|
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||||
|
|
||||||
|
// Encrypt new user key with public key
|
||||||
|
const encryptedKey = await this.encryptKey(newUserKey, publicKey);
|
||||||
|
|
||||||
|
const updateRequest = new EmergencyAccessUpdateRequest();
|
||||||
|
updateRequest.type = details.type;
|
||||||
|
updateRequest.waitTimeDays = details.waitTimeDays;
|
||||||
|
updateRequest.keyEncrypted = encryptedKey;
|
||||||
|
|
||||||
|
await this.emergencyAccessApiService.putEmergencyAccess(details.id, updateRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async encryptKey(userKey: UserKey, publicKey: Uint8Array): Promise<EncryptedString> {
|
||||||
|
return (await this.cryptoService.rsaEncrypt(userKey.key, publicKey)).encryptedString;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
apps/web/src/app/auth/emergency-access/services/index.ts
Normal file
1
apps/web/src/app/auth/emergency-access/services/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./emergency-access.service";
|
||||||
@@ -8,6 +8,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
import { SharedModule } from "../../shared";
|
import { SharedModule } from "../../shared";
|
||||||
|
import { EmergencyAccessModule } from "../emergency-access";
|
||||||
|
|
||||||
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
|
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.
|
|||||||
// This component is used to migrate from the old encryption scheme to the new one.
|
// This component is used to migrate from the old encryption scheme to the new one.
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule],
|
imports: [SharedModule, EmergencyAccessModule],
|
||||||
providers: [MigrateFromLegacyEncryptionService],
|
providers: [MigrateFromLegacyEncryptionService],
|
||||||
templateUrl: "migrate-legacy-encryption.component.html",
|
templateUrl: "migrate-legacy-encryption.component.html",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,12 +8,7 @@ import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/commo
|
|||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
|
import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
|
||||||
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
|
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
|
||||||
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
|
|
||||||
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
|
|
||||||
import { EmergencyAccessGranteeDetailsResponse } from "@bitwarden/common/auth/models/response/emergency-access.response";
|
|
||||||
import { EncryptionType, KdfType } from "@bitwarden/common/enums";
|
import { EncryptionType, KdfType } from "@bitwarden/common/enums";
|
||||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
|
||||||
import { UserKeyResponse } from "@bitwarden/common/models/response/user-key.response";
|
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
@@ -34,6 +29,8 @@ import { Folder } from "@bitwarden/common/vault/models/domain/folder";
|
|||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||||
|
|
||||||
|
import { EmergencyAccessService } from "../emergency-access";
|
||||||
|
|
||||||
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
|
import { MigrateFromLegacyEncryptionService } from "./migrate-legacy-encryption.service";
|
||||||
|
|
||||||
describe("migrateFromLegacyEncryptionService", () => {
|
describe("migrateFromLegacyEncryptionService", () => {
|
||||||
@@ -42,6 +39,7 @@ describe("migrateFromLegacyEncryptionService", () => {
|
|||||||
const organizationService = mock<OrganizationService>();
|
const organizationService = mock<OrganizationService>();
|
||||||
const organizationApiService = mock<OrganizationApiService>();
|
const organizationApiService = mock<OrganizationApiService>();
|
||||||
const organizationUserService = mock<OrganizationUserService>();
|
const organizationUserService = mock<OrganizationUserService>();
|
||||||
|
const emergencyAccessService = mock<EmergencyAccessService>();
|
||||||
const apiService = mock<ApiService>();
|
const apiService = mock<ApiService>();
|
||||||
const encryptService = mock<EncryptService>();
|
const encryptService = mock<EncryptService>();
|
||||||
const cryptoService = mock<CryptoService>();
|
const cryptoService = mock<CryptoService>();
|
||||||
@@ -60,6 +58,7 @@ describe("migrateFromLegacyEncryptionService", () => {
|
|||||||
organizationService,
|
organizationService,
|
||||||
organizationApiService,
|
organizationApiService,
|
||||||
organizationUserService,
|
organizationUserService,
|
||||||
|
emergencyAccessService,
|
||||||
apiService,
|
apiService,
|
||||||
cryptoService,
|
cryptoService,
|
||||||
encryptService,
|
encryptService,
|
||||||
@@ -109,6 +108,9 @@ describe("migrateFromLegacyEncryptionService", () => {
|
|||||||
const mockSends = [createMockSend("1", "Send 1"), createMockSend("2", "Send 2")];
|
const mockSends = [createMockSend("1", "Send 1"), createMockSend("2", "Send 2")];
|
||||||
|
|
||||||
cryptoService.getPrivateKey.mockResolvedValue(new Uint8Array(64) as CsprngArray);
|
cryptoService.getPrivateKey.mockResolvedValue(new Uint8Array(64) as CsprngArray);
|
||||||
|
cryptoService.rsaEncrypt.mockResolvedValue(
|
||||||
|
new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "Encrypted")
|
||||||
|
);
|
||||||
|
|
||||||
folderViews = new BehaviorSubject<FolderView[]>(mockFolders);
|
folderViews = new BehaviorSubject<FolderView[]>(mockFolders);
|
||||||
folderService.folderViews$ = folderViews;
|
folderService.folderViews$ = folderViews;
|
||||||
@@ -201,40 +203,12 @@ describe("migrateFromLegacyEncryptionService", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||||
|
|
||||||
const mockEmergencyAccess = {
|
|
||||||
data: [
|
|
||||||
createMockEmergencyAccess("0", "EA 0", EmergencyAccessStatusType.Invited),
|
|
||||||
createMockEmergencyAccess("1", "EA 1", EmergencyAccessStatusType.Accepted),
|
|
||||||
createMockEmergencyAccess("2", "EA 2", EmergencyAccessStatusType.Confirmed),
|
|
||||||
createMockEmergencyAccess("3", "EA 3", EmergencyAccessStatusType.RecoveryInitiated),
|
|
||||||
createMockEmergencyAccess("4", "EA 4", EmergencyAccessStatusType.RecoveryApproved),
|
|
||||||
],
|
|
||||||
} as ListResponse<EmergencyAccessGranteeDetailsResponse>;
|
|
||||||
apiService.getEmergencyAccessTrusted.mockResolvedValue(mockEmergencyAccess);
|
|
||||||
apiService.getUserPublicKey.mockResolvedValue({
|
|
||||||
userId: "mockUserId",
|
|
||||||
publicKey: "mockPublicKey",
|
|
||||||
} as UserKeyResponse);
|
|
||||||
|
|
||||||
cryptoService.rsaEncrypt.mockImplementation((plainValue, publicKey) => {
|
|
||||||
return Promise.resolve(
|
|
||||||
new EncString(EncryptionType.Rsa2048_OaepSha1_B64, "Encrypted: " + plainValue)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Only updates emergency accesses with allowed statuses", async () => {
|
it("Uses emergency access service to rotate", async () => {
|
||||||
await migrateFromLegacyEncryptionService.updateEmergencyAccesses(mockUserKey);
|
await migrateFromLegacyEncryptionService.updateEmergencyAccesses(mockUserKey);
|
||||||
|
|
||||||
expect(apiService.putEmergencyAccess).not.toHaveBeenCalledWith(
|
expect(emergencyAccessService.rotate).toHaveBeenCalled();
|
||||||
"0",
|
|
||||||
expect.any(EmergencyAccessUpdateRequest)
|
|
||||||
);
|
|
||||||
expect(apiService.putEmergencyAccess).not.toHaveBeenCalledWith(
|
|
||||||
"1",
|
|
||||||
expect.any(EmergencyAccessUpdateRequest)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -322,19 +296,6 @@ function createMockSend(id: string, name: string): Send {
|
|||||||
return send;
|
return send;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMockEmergencyAccess(
|
|
||||||
id: string,
|
|
||||||
name: string,
|
|
||||||
status: EmergencyAccessStatusType
|
|
||||||
): EmergencyAccessGranteeDetailsResponse {
|
|
||||||
const emergencyAccess = new EmergencyAccessGranteeDetailsResponse({});
|
|
||||||
emergencyAccess.id = id;
|
|
||||||
emergencyAccess.name = name;
|
|
||||||
emergencyAccess.type = 0;
|
|
||||||
emergencyAccess.status = status;
|
|
||||||
return emergencyAccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createOrganization(id: string, name: string, resetPasswordEnrolled: boolean) {
|
function createOrganization(id: string, name: string, resetPasswordEnrolled: boolean) {
|
||||||
const org = new Organization();
|
const org = new Organization();
|
||||||
org.id = id;
|
org.id = id;
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
|
|||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||||
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||||
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
|
|
||||||
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
|
|
||||||
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
|
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||||
@@ -23,6 +21,8 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
|
|||||||
import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
|
import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
|
||||||
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
|
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
|
||||||
|
|
||||||
|
import { EmergencyAccessService } from "../emergency-access";
|
||||||
|
|
||||||
// TODO: PM-3797 - This service should be expanded and used for user key rotations in change-password.component.ts
|
// TODO: PM-3797 - This service should be expanded and used for user key rotations in change-password.component.ts
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MigrateFromLegacyEncryptionService {
|
export class MigrateFromLegacyEncryptionService {
|
||||||
@@ -30,6 +30,7 @@ export class MigrateFromLegacyEncryptionService {
|
|||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
private organizationUserService: OrganizationUserService,
|
private organizationUserService: OrganizationUserService,
|
||||||
|
private emergencyAccessService: EmergencyAccessService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private encryptService: EncryptService,
|
private encryptService: EncryptService,
|
||||||
@@ -102,31 +103,8 @@ export class MigrateFromLegacyEncryptionService {
|
|||||||
* on the server.
|
* on the server.
|
||||||
* @param newUserKey The new user key
|
* @param newUserKey The new user key
|
||||||
*/
|
*/
|
||||||
async updateEmergencyAccesses(newUserKey: UserKey) {
|
updateEmergencyAccesses(newUserKey: UserKey) {
|
||||||
const emergencyAccess = await this.apiService.getEmergencyAccessTrusted();
|
return this.emergencyAccessService.rotate(newUserKey);
|
||||||
// Any Invited or Accepted requests won't have the key yet, so we don't need to update them
|
|
||||||
const allowedStatuses = new Set([
|
|
||||||
EmergencyAccessStatusType.Confirmed,
|
|
||||||
EmergencyAccessStatusType.RecoveryInitiated,
|
|
||||||
EmergencyAccessStatusType.RecoveryApproved,
|
|
||||||
]);
|
|
||||||
const filteredAccesses = emergencyAccess.data.filter((d) => allowedStatuses.has(d.status));
|
|
||||||
|
|
||||||
for (const details of filteredAccesses) {
|
|
||||||
// Get public key of grantee
|
|
||||||
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
|
|
||||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
|
||||||
|
|
||||||
// Encrypt new user key with public key
|
|
||||||
const encryptedKey = await this.cryptoService.rsaEncrypt(newUserKey.key, publicKey);
|
|
||||||
|
|
||||||
const updateRequest = new EmergencyAccessUpdateRequest();
|
|
||||||
updateRequest.type = details.type;
|
|
||||||
updateRequest.waitTimeDays = details.waitTimeDays;
|
|
||||||
updateRequest.keyEncrypted = encryptedKey.encryptedString;
|
|
||||||
|
|
||||||
await this.apiService.putEmergencyAccess(details.id, updateRequest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Updates all admin recovery keys on the server with the new user key
|
/** Updates all admin recovery keys on the server with the new user key
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/commo
|
|||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
|
|
||||||
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
|
|
||||||
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
|
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
|
||||||
@@ -25,11 +23,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
import {
|
import { MasterKey, UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
MasterKey,
|
|
||||||
SymmetricCryptoKey,
|
|
||||||
UserKey,
|
|
||||||
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request";
|
import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request";
|
||||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||||
@@ -40,6 +34,8 @@ import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/ciph
|
|||||||
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
|
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { EmergencyAccessService } from "../emergency-access";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-change-password",
|
selector: "app-change-password",
|
||||||
templateUrl: "change-password.component.html",
|
templateUrl: "change-password.component.html",
|
||||||
@@ -65,6 +61,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
|||||||
private folderService: FolderService,
|
private folderService: FolderService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
|
private emergencyAccessService: EmergencyAccessService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private sendService: SendService,
|
private sendService: SendService,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
@@ -267,36 +264,11 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
|
|||||||
|
|
||||||
await this.apiService.postAccountKey(request);
|
await this.apiService.postAccountKey(request);
|
||||||
|
|
||||||
await this.updateEmergencyAccesses(newUserKey);
|
await this.emergencyAccessService.rotate(newUserKey);
|
||||||
|
|
||||||
await this.updateAllResetPasswordKeys(newUserKey, masterPasswordHash);
|
await this.updateAllResetPasswordKeys(newUserKey, masterPasswordHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateEmergencyAccesses(encKey: SymmetricCryptoKey) {
|
|
||||||
const emergencyAccess = await this.apiService.getEmergencyAccessTrusted();
|
|
||||||
const allowedStatuses = [
|
|
||||||
EmergencyAccessStatusType.Confirmed,
|
|
||||||
EmergencyAccessStatusType.RecoveryInitiated,
|
|
||||||
EmergencyAccessStatusType.RecoveryApproved,
|
|
||||||
];
|
|
||||||
|
|
||||||
const filteredAccesses = emergencyAccess.data.filter((d) => allowedStatuses.includes(d.status));
|
|
||||||
|
|
||||||
for (const details of filteredAccesses) {
|
|
||||||
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
|
|
||||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
|
||||||
|
|
||||||
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey);
|
|
||||||
|
|
||||||
const updateRequest = new EmergencyAccessUpdateRequest();
|
|
||||||
updateRequest.type = details.type;
|
|
||||||
updateRequest.waitTimeDays = details.waitTimeDays;
|
|
||||||
updateRequest.keyEncrypted = encryptedKey.encryptedString;
|
|
||||||
|
|
||||||
await this.apiService.putEmergencyAccess(details.id, updateRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async updateAllResetPasswordKeys(userKey: UserKey, masterPasswordHash: string) {
|
private async updateAllResetPasswordKeys(userKey: UserKey, masterPasswordHash: string) {
|
||||||
const orgs = await this.organizationService.getAll();
|
const orgs = await this.organizationService.getAll();
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { DialogService } from "@bitwarden/components";
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "emergency-access-attachments",
|
selector: "emergency-access-attachments",
|
||||||
templateUrl: "../../../vault/individual-vault/attachments.component.html",
|
templateUrl: "../../../../vault/individual-vault/attachments.component.html",
|
||||||
})
|
})
|
||||||
export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponent {
|
export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponent {
|
||||||
viewOnly = true;
|
viewOnly = true;
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { EmergencyAccessType } from "@bitwarden/common/auth/enums/emergency-access-type";
|
|
||||||
import { EmergencyAccessInviteRequest } from "@bitwarden/common/auth/models/request/emergency-access-invite.request";
|
|
||||||
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
|
import { EmergencyAccessService } from "../../emergency-access";
|
||||||
|
import { EmergencyAccessType } from "../../emergency-access/enums/emergency-access-type";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "emergency-access-add-edit",
|
selector: "emergency-access-add-edit",
|
||||||
templateUrl: "emergency-access-add-edit.component.html",
|
templateUrl: "emergency-access-add-edit.component.html",
|
||||||
@@ -32,7 +31,7 @@ export class EmergencyAccessAddEditComponent implements OnInit {
|
|||||||
waitTime: number;
|
waitTime: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private emergencyAccessService: EmergencyAccessService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private logService: LogService
|
private logService: LogService
|
||||||
@@ -54,7 +53,9 @@ export class EmergencyAccessAddEditComponent implements OnInit {
|
|||||||
this.editMode = true;
|
this.editMode = true;
|
||||||
this.title = this.i18nService.t("editEmergencyContact");
|
this.title = this.i18nService.t("editEmergencyContact");
|
||||||
try {
|
try {
|
||||||
const emergencyAccess = await this.apiService.getEmergencyAccess(this.emergencyAccessId);
|
const emergencyAccess = await this.emergencyAccessService.getEmergencyAccess(
|
||||||
|
this.emergencyAccessId
|
||||||
|
);
|
||||||
this.type = emergencyAccess.type;
|
this.type = emergencyAccess.type;
|
||||||
this.waitTime = emergencyAccess.waitTimeDays;
|
this.waitTime = emergencyAccess.waitTimeDays;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -71,18 +72,9 @@ export class EmergencyAccessAddEditComponent implements OnInit {
|
|||||||
async submit() {
|
async submit() {
|
||||||
try {
|
try {
|
||||||
if (this.editMode) {
|
if (this.editMode) {
|
||||||
const request = new EmergencyAccessUpdateRequest();
|
await this.emergencyAccessService.update(this.emergencyAccessId, this.type, this.waitTime);
|
||||||
request.type = this.type;
|
|
||||||
request.waitTimeDays = this.waitTime;
|
|
||||||
|
|
||||||
this.formPromise = this.apiService.putEmergencyAccess(this.emergencyAccessId, request);
|
|
||||||
} else {
|
} else {
|
||||||
const request = new EmergencyAccessInviteRequest();
|
await this.emergencyAccessService.invite(this.email, this.type, this.waitTime);
|
||||||
request.email = this.email.trim();
|
|
||||||
request.type = this.type;
|
|
||||||
request.waitTimeDays = this.waitTime;
|
|
||||||
|
|
||||||
this.formPromise = this.apiService.postEmergencyAccessInvite(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.formPromise;
|
await this.formPromise;
|
||||||
|
|||||||
@@ -2,27 +2,25 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
|||||||
|
|
||||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
|
|
||||||
import { EmergencyAccessType } from "@bitwarden/common/auth/enums/emergency-access-type";
|
|
||||||
import { EmergencyAccessConfirmRequest } from "@bitwarden/common/auth/models/request/emergency-access-confirm.request";
|
|
||||||
import {
|
|
||||||
EmergencyAccessGranteeDetailsResponse,
|
|
||||||
EmergencyAccessGrantorDetailsResponse,
|
|
||||||
} from "@bitwarden/common/auth/models/response/emergency-access.response";
|
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { EmergencyAccessService } from "../../emergency-access";
|
||||||
|
import { EmergencyAccessStatusType } from "../../emergency-access/enums/emergency-access-status-type";
|
||||||
|
import { EmergencyAccessType } from "../../emergency-access/enums/emergency-access-type";
|
||||||
|
import {
|
||||||
|
GranteeEmergencyAccess,
|
||||||
|
GrantorEmergencyAccess,
|
||||||
|
} from "../../emergency-access/models/emergency-access";
|
||||||
|
|
||||||
|
import { EmergencyAccessConfirmComponent } from "./confirm/emergency-access-confirm.component";
|
||||||
import { EmergencyAccessAddEditComponent } from "./emergency-access-add-edit.component";
|
import { EmergencyAccessAddEditComponent } from "./emergency-access-add-edit.component";
|
||||||
import { EmergencyAccessConfirmComponent } from "./emergency-access-confirm.component";
|
import { EmergencyAccessTakeoverComponent } from "./takeover/emergency-access-takeover.component";
|
||||||
import { EmergencyAccessTakeoverComponent } from "./emergency-access-takeover.component";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "emergency-access",
|
selector: "emergency-access",
|
||||||
@@ -38,19 +36,18 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
|
|
||||||
loaded = false;
|
loaded = false;
|
||||||
canAccessPremium: boolean;
|
canAccessPremium: boolean;
|
||||||
trustedContacts: EmergencyAccessGranteeDetailsResponse[];
|
trustedContacts: GranteeEmergencyAccess[];
|
||||||
grantedContacts: EmergencyAccessGrantorDetailsResponse[];
|
grantedContacts: GrantorEmergencyAccess[];
|
||||||
emergencyAccessType = EmergencyAccessType;
|
emergencyAccessType = EmergencyAccessType;
|
||||||
emergencyAccessStatusType = EmergencyAccessStatusType;
|
emergencyAccessStatusType = EmergencyAccessStatusType;
|
||||||
actionPromise: Promise<any>;
|
actionPromise: Promise<any>;
|
||||||
isOrganizationOwner: boolean;
|
isOrganizationOwner: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private emergencyAccessService: EmergencyAccessService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private modalService: ModalService,
|
private modalService: ModalService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private cryptoService: CryptoService,
|
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
private userNamePipe: UserNamePipe,
|
private userNamePipe: UserNamePipe,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
@@ -67,8 +64,8 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.trustedContacts = (await this.apiService.getEmergencyAccessTrusted()).data;
|
this.trustedContacts = await this.emergencyAccessService.getEmergencyAccessTrusted();
|
||||||
this.grantedContacts = (await this.apiService.getEmergencyAccessGranted()).data;
|
this.grantedContacts = await this.emergencyAccessService.getEmergencyAccessGranted();
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +76,7 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async edit(details: EmergencyAccessGranteeDetailsResponse) {
|
async edit(details: GranteeEmergencyAccess) {
|
||||||
const [modal] = await this.modalService.openViewRef(
|
const [modal] = await this.modalService.openViewRef(
|
||||||
EmergencyAccessAddEditComponent,
|
EmergencyAccessAddEditComponent,
|
||||||
this.addEditModalRef,
|
this.addEditModalRef,
|
||||||
@@ -105,11 +102,11 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
this.edit(null);
|
this.edit(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reinvite(contact: EmergencyAccessGranteeDetailsResponse) {
|
async reinvite(contact: GranteeEmergencyAccess) {
|
||||||
if (this.actionPromise != null) {
|
if (this.actionPromise != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.actionPromise = this.apiService.postEmergencyAccessReinvite(contact.id);
|
this.actionPromise = this.emergencyAccessService.reinvite(contact.id);
|
||||||
await this.actionPromise;
|
await this.actionPromise;
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"success",
|
"success",
|
||||||
@@ -119,7 +116,7 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
this.actionPromise = null;
|
this.actionPromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirm(contact: EmergencyAccessGranteeDetailsResponse) {
|
async confirm(contact: GranteeEmergencyAccess) {
|
||||||
function updateUser() {
|
function updateUser() {
|
||||||
contact.status = EmergencyAccessStatusType.Confirmed;
|
contact.status = EmergencyAccessStatusType.Confirmed;
|
||||||
}
|
}
|
||||||
@@ -141,7 +138,7 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
comp.onConfirmed.subscribe(async () => {
|
comp.onConfirmed.subscribe(async () => {
|
||||||
modal.close();
|
modal.close();
|
||||||
|
|
||||||
comp.formPromise = this.doConfirmation(contact);
|
comp.formPromise = this.emergencyAccessService.confirm(contact.id, contact.granteeId);
|
||||||
await comp.formPromise;
|
await comp.formPromise;
|
||||||
|
|
||||||
updateUser();
|
updateUser();
|
||||||
@@ -156,7 +153,7 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.actionPromise = this.doConfirmation(contact);
|
this.actionPromise = this.emergencyAccessService.confirm(contact.id, contact.granteeId);
|
||||||
await this.actionPromise;
|
await this.actionPromise;
|
||||||
updateUser();
|
updateUser();
|
||||||
|
|
||||||
@@ -168,9 +165,7 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
this.actionPromise = null;
|
this.actionPromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(
|
async remove(details: GranteeEmergencyAccess | GrantorEmergencyAccess) {
|
||||||
details: EmergencyAccessGranteeDetailsResponse | EmergencyAccessGrantorDetailsResponse
|
|
||||||
) {
|
|
||||||
const confirmed = await this.dialogService.openSimpleDialog({
|
const confirmed = await this.dialogService.openSimpleDialog({
|
||||||
title: this.userNamePipe.transform(details),
|
title: this.userNamePipe.transform(details),
|
||||||
content: { key: "removeUserConfirmation" },
|
content: { key: "removeUserConfirmation" },
|
||||||
@@ -182,14 +177,14 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.apiService.deleteEmergencyAccess(details.id);
|
await this.emergencyAccessService.delete(details.id);
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"success",
|
"success",
|
||||||
null,
|
null,
|
||||||
this.i18nService.t("removedUserId", this.userNamePipe.transform(details))
|
this.i18nService.t("removedUserId", this.userNamePipe.transform(details))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (details instanceof EmergencyAccessGranteeDetailsResponse) {
|
if (details instanceof GranteeEmergencyAccess) {
|
||||||
this.removeGrantee(details);
|
this.removeGrantee(details);
|
||||||
} else {
|
} else {
|
||||||
this.removeGrantor(details);
|
this.removeGrantor(details);
|
||||||
@@ -199,7 +194,7 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestAccess(details: EmergencyAccessGrantorDetailsResponse) {
|
async requestAccess(details: GrantorEmergencyAccess) {
|
||||||
const confirmed = await this.dialogService.openSimpleDialog({
|
const confirmed = await this.dialogService.openSimpleDialog({
|
||||||
title: this.userNamePipe.transform(details),
|
title: this.userNamePipe.transform(details),
|
||||||
content: {
|
content: {
|
||||||
@@ -214,7 +209,7 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.apiService.postEmergencyAccessInitiate(details.id);
|
await this.emergencyAccessService.requestAccess(details.id);
|
||||||
|
|
||||||
details.status = EmergencyAccessStatusType.RecoveryInitiated;
|
details.status = EmergencyAccessStatusType.RecoveryInitiated;
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
@@ -224,7 +219,7 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async approve(details: EmergencyAccessGranteeDetailsResponse) {
|
async approve(details: GranteeEmergencyAccess) {
|
||||||
const type = this.i18nService.t(
|
const type = this.i18nService.t(
|
||||||
details.type === EmergencyAccessType.View ? "view" : "takeover"
|
details.type === EmergencyAccessType.View ? "view" : "takeover"
|
||||||
);
|
);
|
||||||
@@ -243,7 +238,7 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.apiService.postEmergencyAccessApprove(details.id);
|
await this.emergencyAccessService.approve(details.id);
|
||||||
details.status = EmergencyAccessStatusType.RecoveryApproved;
|
details.status = EmergencyAccessStatusType.RecoveryApproved;
|
||||||
|
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
@@ -253,8 +248,8 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reject(details: EmergencyAccessGranteeDetailsResponse) {
|
async reject(details: GranteeEmergencyAccess) {
|
||||||
await this.apiService.postEmergencyAccessReject(details.id);
|
await this.emergencyAccessService.reject(details.id);
|
||||||
details.status = EmergencyAccessStatusType.Confirmed;
|
details.status = EmergencyAccessStatusType.Confirmed;
|
||||||
|
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
@@ -264,7 +259,7 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeover(details: EmergencyAccessGrantorDetailsResponse) {
|
async takeover(details: GrantorEmergencyAccess) {
|
||||||
const [modal] = await this.modalService.openViewRef(
|
const [modal] = await this.modalService.openViewRef(
|
||||||
EmergencyAccessTakeoverComponent,
|
EmergencyAccessTakeoverComponent,
|
||||||
this.takeoverModalRef,
|
this.takeoverModalRef,
|
||||||
@@ -286,41 +281,17 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeGrantee(details: EmergencyAccessGranteeDetailsResponse) {
|
private removeGrantee(details: GranteeEmergencyAccess) {
|
||||||
const index = this.trustedContacts.indexOf(details);
|
const index = this.trustedContacts.indexOf(details);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.trustedContacts.splice(index, 1);
|
this.trustedContacts.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeGrantor(details: EmergencyAccessGrantorDetailsResponse) {
|
private removeGrantor(details: GrantorEmergencyAccess) {
|
||||||
const index = this.grantedContacts.indexOf(details);
|
const index = this.grantedContacts.indexOf(details);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.grantedContacts.splice(index, 1);
|
this.grantedContacts.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt the user key with the grantees public key, and send it to bitwarden for escrow.
|
|
||||||
private async doConfirmation(details: EmergencyAccessGranteeDetailsResponse) {
|
|
||||||
const userKey = await this.cryptoService.getUserKey();
|
|
||||||
if (!userKey) {
|
|
||||||
throw new Error("No user key found");
|
|
||||||
}
|
|
||||||
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
|
|
||||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.logService.debug(
|
|
||||||
"User's fingerprint: " +
|
|
||||||
(await this.cryptoService.getFingerprint(details.granteeId, publicKey)).join("-")
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
// Ignore errors since it's just a debug message
|
|
||||||
}
|
|
||||||
|
|
||||||
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey);
|
|
||||||
const request = new EmergencyAccessConfirmRequest();
|
|
||||||
request.key = encryptedKey.encryptedString;
|
|
||||||
await this.apiService.postEmergencyAccessConfirm(details.id, request);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,7 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angu
|
|||||||
import { takeUntil } from "rxjs";
|
import { takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
|
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
|
|
||||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
|
||||||
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
|
|
||||||
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
|
|
||||||
import { EmergencyAccessPasswordRequest } from "@bitwarden/common/auth/models/request/emergency-access-password.request";
|
|
||||||
import { KdfType } from "@bitwarden/common/enums";
|
import { KdfType } from "@bitwarden/common/enums";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@@ -16,13 +10,11 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
|||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import {
|
|
||||||
SymmetricCryptoKey,
|
|
||||||
UserKey,
|
|
||||||
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { EmergencyAccessService } from "../../../emergency-access";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "emergency-access-takeover",
|
selector: "emergency-access-takeover",
|
||||||
templateUrl: "emergency-access-takeover.component.html",
|
templateUrl: "emergency-access-takeover.component.html",
|
||||||
@@ -49,7 +41,7 @@ export class EmergencyAccessTakeoverComponent
|
|||||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
policyService: PolicyService,
|
policyService: PolicyService,
|
||||||
private apiService: ApiService,
|
private emergencyAccessService: EmergencyAccessService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
dialogService: DialogService
|
dialogService: DialogService
|
||||||
) {
|
) {
|
||||||
@@ -66,17 +58,11 @@ export class EmergencyAccessTakeoverComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const response = await this.apiService.getEmergencyGrantorPolicies(this.emergencyAccessId);
|
const policies = await this.emergencyAccessService.getGrantorPolicies(this.emergencyAccessId);
|
||||||
if (response.data != null && response.data.length > 0) {
|
this.policyService
|
||||||
const policies = response.data.map(
|
.masterPasswordPolicyOptions$(policies)
|
||||||
(policyResponse: PolicyResponse) => new Policy(new PolicyData(policyResponse))
|
.pipe(takeUntil(this.destroy$))
|
||||||
);
|
.subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions));
|
||||||
|
|
||||||
this.policyService
|
|
||||||
.masterPasswordPolicyOptions$(policies)
|
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||||
@@ -89,46 +75,20 @@ export class EmergencyAccessTakeoverComponent
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const takeoverResponse = await this.apiService.postEmergencyAccessTakeover(
|
try {
|
||||||
this.emergencyAccessId
|
await this.emergencyAccessService.takeover(
|
||||||
);
|
this.emergencyAccessId,
|
||||||
|
this.masterPassword,
|
||||||
const oldKeyBuffer = await this.cryptoService.rsaDecrypt(takeoverResponse.keyEncrypted);
|
this.email
|
||||||
const oldUserKey = new SymmetricCryptoKey(oldKeyBuffer) as UserKey;
|
);
|
||||||
|
this.onDone.emit();
|
||||||
if (oldUserKey == null) {
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"error",
|
"error",
|
||||||
this.i18nService.t("errorOccurred"),
|
this.i18nService.t("errorOccurred"),
|
||||||
this.i18nService.t("unexpectedError")
|
this.i18nService.t("unexpectedError")
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const masterKey = await this.cryptoService.makeMasterKey(
|
|
||||||
this.masterPassword,
|
|
||||||
this.email,
|
|
||||||
takeoverResponse.kdf,
|
|
||||||
new KdfConfig(
|
|
||||||
takeoverResponse.kdfIterations,
|
|
||||||
takeoverResponse.kdfMemory,
|
|
||||||
takeoverResponse.kdfParallelism
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const masterKeyHash = await this.cryptoService.hashMasterKey(this.masterPassword, masterKey);
|
|
||||||
|
|
||||||
const encKey = await this.cryptoService.encryptUserKeyWithMasterKey(masterKey, oldUserKey);
|
|
||||||
|
|
||||||
const request = new EmergencyAccessPasswordRequest();
|
|
||||||
request.newMasterPasswordHash = masterKeyHash;
|
|
||||||
request.key = encKey[1].encryptedString;
|
|
||||||
|
|
||||||
this.apiService.postEmergencyAccessPassword(this.emergencyAccessId, request);
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.onDone.emit();
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,20 +2,12 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
|||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { EmergencyAccessViewResponse } from "@bitwarden/common/auth/models/response/emergency-access.response";
|
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
|
||||||
import {
|
|
||||||
SymmetricCryptoKey,
|
|
||||||
UserKey,
|
|
||||||
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
|
||||||
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
|
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
|
||||||
import { EmergencyAccessAttachmentsComponent } from "./emergency-access-attachments.component";
|
import { EmergencyAccessService } from "../../../emergency-access";
|
||||||
import { EmergencyAddEditComponent } from "./emergency-add-edit.component";
|
import { EmergencyAccessAttachmentsComponent } from "../attachments/emergency-access-attachments.component";
|
||||||
|
|
||||||
|
import { EmergencyAddEditCipherComponent } from "./emergency-add-edit-cipher.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "emergency-access-view",
|
selector: "emergency-access-view",
|
||||||
@@ -33,12 +25,10 @@ export class EmergencyAccessViewComponent implements OnInit {
|
|||||||
loaded = false;
|
loaded = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cipherService: CipherService,
|
|
||||||
private cryptoService: CryptoService,
|
|
||||||
private modalService: ModalService,
|
private modalService: ModalService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private apiService: ApiService
|
private emergencyAccessService: EmergencyAccessService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -57,7 +47,7 @@ export class EmergencyAccessViewComponent implements OnInit {
|
|||||||
async selectCipher(cipher: CipherView) {
|
async selectCipher(cipher: CipherView) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const [_, childComponent] = await this.modalService.openViewRef(
|
const [_, childComponent] = await this.modalService.openViewRef(
|
||||||
EmergencyAddEditComponent,
|
EmergencyAddEditCipherComponent,
|
||||||
this.cipherAddEditModalRef,
|
this.cipherAddEditModalRef,
|
||||||
(comp) => {
|
(comp) => {
|
||||||
comp.cipherId = cipher == null ? null : cipher.id;
|
comp.cipherId = cipher == null ? null : cipher.id;
|
||||||
@@ -69,8 +59,7 @@ export class EmergencyAccessViewComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
const response = await this.apiService.postEmergencyAccessView(this.id);
|
this.ciphers = await this.emergencyAccessService.getViewOnlyCiphers(this.id);
|
||||||
this.ciphers = await this.getAllCiphers(response);
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,24 +73,4 @@ export class EmergencyAccessViewComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getAllCiphers(response: EmergencyAccessViewResponse): Promise<CipherView[]> {
|
|
||||||
const ciphers = response.ciphers;
|
|
||||||
|
|
||||||
const decCiphers: CipherView[] = [];
|
|
||||||
const oldKeyBuffer = await this.cryptoService.rsaDecrypt(response.keyEncrypted);
|
|
||||||
const oldUserKey = new SymmetricCryptoKey(oldKeyBuffer) as UserKey;
|
|
||||||
|
|
||||||
const promises: any[] = [];
|
|
||||||
ciphers.forEach((cipherResponse) => {
|
|
||||||
const cipherData = new CipherData(cipherResponse);
|
|
||||||
const cipher = new Cipher(cipherData);
|
|
||||||
promises.push(cipher.decrypt(oldUserKey).then((c) => decCiphers.push(c)));
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
decCiphers.sort(this.cipherService.getLocaleSortingFunction());
|
|
||||||
|
|
||||||
return decCiphers;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -20,13 +20,13 @@ import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
|||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
import { AddEditComponent as BaseAddEditComponent } from "../../../vault/individual-vault/add-edit.component";
|
import { AddEditComponent as BaseAddEditComponent } from "../../../../vault/individual-vault/add-edit.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-org-vault-add-edit",
|
selector: "app-org-vault-add-edit",
|
||||||
templateUrl: "../../../vault/individual-vault/add-edit.component.html",
|
templateUrl: "../../../../vault/individual-vault/add-edit.component.html",
|
||||||
})
|
})
|
||||||
export class EmergencyAddEditComponent extends BaseAddEditComponent {
|
export class EmergencyAddEditCipherComponent extends BaseAddEditComponent {
|
||||||
originalCipher: Cipher = null;
|
originalCipher: Cipher = null;
|
||||||
viewOnly = true;
|
viewOnly = true;
|
||||||
protected override componentName = "app-org-vault-add-edit";
|
protected override componentName = "app-org-vault-add-edit";
|
||||||
@@ -3,12 +3,18 @@ import { NgModule } from "@angular/core";
|
|||||||
import { PasswordCalloutComponent } from "@bitwarden/auth";
|
import { PasswordCalloutComponent } from "@bitwarden/auth";
|
||||||
|
|
||||||
import { SharedModule } from "../../shared";
|
import { SharedModule } from "../../shared";
|
||||||
|
import { EmergencyAccessModule } from "../emergency-access";
|
||||||
|
|
||||||
import { ChangePasswordComponent } from "./change-password.component";
|
import { ChangePasswordComponent } from "./change-password.component";
|
||||||
import { WebauthnLoginSettingsModule } from "./webauthn-login-settings";
|
import { WebauthnLoginSettingsModule } from "./webauthn-login-settings";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [SharedModule, WebauthnLoginSettingsModule, PasswordCalloutComponent],
|
imports: [
|
||||||
|
SharedModule,
|
||||||
|
WebauthnLoginSettingsModule,
|
||||||
|
EmergencyAccessModule,
|
||||||
|
PasswordCalloutComponent,
|
||||||
|
],
|
||||||
declarations: [ChangePasswordComponent],
|
declarations: [ChangePasswordComponent],
|
||||||
providers: [],
|
providers: [],
|
||||||
exports: [ChangePasswordComponent],
|
exports: [ChangePasswordComponent],
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { AcceptFamilySponsorshipComponent } from "./admin-console/organizations/
|
|||||||
import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component";
|
import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component";
|
||||||
import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component";
|
import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component";
|
||||||
import { SponsoredFamiliesComponent } from "./admin-console/settings/sponsored-families.component";
|
import { SponsoredFamiliesComponent } from "./admin-console/settings/sponsored-families.component";
|
||||||
import { AcceptEmergencyComponent } from "./auth/accept-emergency.component";
|
|
||||||
import { AcceptOrganizationComponent } from "./auth/accept-organization.component";
|
import { AcceptOrganizationComponent } from "./auth/accept-organization.component";
|
||||||
import { HintComponent } from "./auth/hint.component";
|
import { HintComponent } from "./auth/hint.component";
|
||||||
import { LockComponent } from "./auth/lock.component";
|
import { LockComponent } from "./auth/lock.component";
|
||||||
@@ -28,8 +27,8 @@ import { RecoverDeleteComponent } from "./auth/recover-delete.component";
|
|||||||
import { RecoverTwoFactorComponent } from "./auth/recover-two-factor.component";
|
import { RecoverTwoFactorComponent } from "./auth/recover-two-factor.component";
|
||||||
import { RemovePasswordComponent } from "./auth/remove-password.component";
|
import { RemovePasswordComponent } from "./auth/remove-password.component";
|
||||||
import { SetPasswordComponent } from "./auth/set-password.component";
|
import { SetPasswordComponent } from "./auth/set-password.component";
|
||||||
import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/emergency-access-view.component";
|
|
||||||
import { EmergencyAccessComponent } from "./auth/settings/emergency-access/emergency-access.component";
|
import { EmergencyAccessComponent } from "./auth/settings/emergency-access/emergency-access.component";
|
||||||
|
import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/view/emergency-access-view.component";
|
||||||
import { SsoComponent } from "./auth/sso.component";
|
import { SsoComponent } from "./auth/sso.component";
|
||||||
import { TrialInitiationComponent } from "./auth/trial-initiation/trial-initiation.component";
|
import { TrialInitiationComponent } from "./auth/trial-initiation/trial-initiation.component";
|
||||||
import { TwoFactorComponent } from "./auth/two-factor.component";
|
import { TwoFactorComponent } from "./auth/two-factor.component";
|
||||||
@@ -124,8 +123,11 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "accept-emergency",
|
path: "accept-emergency",
|
||||||
component: AcceptEmergencyComponent,
|
|
||||||
data: { titleId: "acceptEmergency", doNotSaveUrl: false },
|
data: { titleId: "acceptEmergency", doNotSaveUrl: false },
|
||||||
|
loadComponent: () =>
|
||||||
|
import("./auth/emergency-access/accept/accept-emergency.component").then(
|
||||||
|
(mod) => mod.AcceptEmergencyComponent
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "accept-families-for-enterprise",
|
path: "accept-families-for-enterprise",
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from
|
|||||||
import { ProvidersComponent } from "../admin-console/providers/providers.component";
|
import { ProvidersComponent } from "../admin-console/providers/providers.component";
|
||||||
import { SponsoredFamiliesComponent } from "../admin-console/settings/sponsored-families.component";
|
import { SponsoredFamiliesComponent } from "../admin-console/settings/sponsored-families.component";
|
||||||
import { SponsoringOrgRowComponent } from "../admin-console/settings/sponsoring-org-row.component";
|
import { SponsoringOrgRowComponent } from "../admin-console/settings/sponsoring-org-row.component";
|
||||||
import { AcceptEmergencyComponent } from "../auth/accept-emergency.component";
|
|
||||||
import { AcceptOrganizationComponent } from "../auth/accept-organization.component";
|
import { AcceptOrganizationComponent } from "../auth/accept-organization.component";
|
||||||
import { HintComponent } from "../auth/hint.component";
|
import { HintComponent } from "../auth/hint.component";
|
||||||
import { LockComponent } from "../auth/lock.component";
|
import { LockComponent } from "../auth/lock.component";
|
||||||
@@ -26,13 +25,13 @@ import { RegisterFormModule } from "../auth/register-form/register-form.module";
|
|||||||
import { RemovePasswordComponent } from "../auth/remove-password.component";
|
import { RemovePasswordComponent } from "../auth/remove-password.component";
|
||||||
import { SetPasswordComponent } from "../auth/set-password.component";
|
import { SetPasswordComponent } from "../auth/set-password.component";
|
||||||
import { DeauthorizeSessionsComponent } from "../auth/settings/deauthorize-sessions.component";
|
import { DeauthorizeSessionsComponent } from "../auth/settings/deauthorize-sessions.component";
|
||||||
|
import { EmergencyAccessAttachmentsComponent } from "../auth/settings/emergency-access/attachments/emergency-access-attachments.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 { EmergencyAccessAddEditComponent } from "../auth/settings/emergency-access/emergency-access-add-edit.component";
|
||||||
import { EmergencyAccessAttachmentsComponent } from "../auth/settings/emergency-access/emergency-access-attachments.component";
|
|
||||||
import { EmergencyAccessConfirmComponent } from "../auth/settings/emergency-access/emergency-access-confirm.component";
|
|
||||||
import { EmergencyAccessTakeoverComponent } from "../auth/settings/emergency-access/emergency-access-takeover.component";
|
|
||||||
import { EmergencyAccessViewComponent } from "../auth/settings/emergency-access/emergency-access-view.component";
|
|
||||||
import { EmergencyAccessComponent } from "../auth/settings/emergency-access/emergency-access.component";
|
import { EmergencyAccessComponent } from "../auth/settings/emergency-access/emergency-access.component";
|
||||||
import { EmergencyAddEditComponent } from "../auth/settings/emergency-access/emergency-add-edit.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 { EmergencyAddEditCipherComponent } from "../auth/settings/emergency-access/view/emergency-add-edit-cipher.component";
|
||||||
import { TwoFactorAuthenticatorComponent } from "../auth/settings/two-factor-authenticator.component";
|
import { TwoFactorAuthenticatorComponent } from "../auth/settings/two-factor-authenticator.component";
|
||||||
import { TwoFactorDuoComponent } from "../auth/settings/two-factor-duo.component";
|
import { TwoFactorDuoComponent } from "../auth/settings/two-factor-duo.component";
|
||||||
import { TwoFactorEmailComponent } from "../auth/settings/two-factor-email.component";
|
import { TwoFactorEmailComponent } from "../auth/settings/two-factor-email.component";
|
||||||
@@ -107,7 +106,6 @@ import { SharedModule } from "./shared.module";
|
|||||||
PasswordCalloutComponent,
|
PasswordCalloutComponent,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AcceptEmergencyComponent,
|
|
||||||
AcceptFamilySponsorshipComponent,
|
AcceptFamilySponsorshipComponent,
|
||||||
AcceptOrganizationComponent,
|
AcceptOrganizationComponent,
|
||||||
AccessComponent,
|
AccessComponent,
|
||||||
@@ -128,7 +126,7 @@ import { SharedModule } from "./shared.module";
|
|||||||
EmergencyAccessConfirmComponent,
|
EmergencyAccessConfirmComponent,
|
||||||
EmergencyAccessTakeoverComponent,
|
EmergencyAccessTakeoverComponent,
|
||||||
EmergencyAccessViewComponent,
|
EmergencyAccessViewComponent,
|
||||||
EmergencyAddEditComponent,
|
EmergencyAddEditCipherComponent,
|
||||||
FolderAddEditComponent,
|
FolderAddEditComponent,
|
||||||
FooterComponent,
|
FooterComponent,
|
||||||
FrontendLayoutComponent,
|
FrontendLayoutComponent,
|
||||||
@@ -192,7 +190,6 @@ import { SharedModule } from "./shared.module";
|
|||||||
exports: [
|
exports: [
|
||||||
UserVerificationModule,
|
UserVerificationModule,
|
||||||
PremiumBadgeComponent,
|
PremiumBadgeComponent,
|
||||||
AcceptEmergencyComponent,
|
|
||||||
AcceptOrganizationComponent,
|
AcceptOrganizationComponent,
|
||||||
AccessComponent,
|
AccessComponent,
|
||||||
AccountComponent,
|
AccountComponent,
|
||||||
@@ -213,7 +210,7 @@ import { SharedModule } from "./shared.module";
|
|||||||
EmergencyAccessConfirmComponent,
|
EmergencyAccessConfirmComponent,
|
||||||
EmergencyAccessTakeoverComponent,
|
EmergencyAccessTakeoverComponent,
|
||||||
EmergencyAccessViewComponent,
|
EmergencyAccessViewComponent,
|
||||||
EmergencyAddEditComponent,
|
EmergencyAddEditCipherComponent,
|
||||||
FolderAddEditComponent,
|
FolderAddEditComponent,
|
||||||
FooterComponent,
|
FooterComponent,
|
||||||
FrontendLayoutComponent,
|
FrontendLayoutComponent,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
} from "../admin-console/models/response/organization-connection.response";
|
} from "../admin-console/models/response/organization-connection.response";
|
||||||
import { OrganizationExportResponse } from "../admin-console/models/response/organization-export.response";
|
import { OrganizationExportResponse } from "../admin-console/models/response/organization-export.response";
|
||||||
import { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response";
|
import { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response";
|
||||||
import { PolicyResponse } from "../admin-console/models/response/policy.response";
|
|
||||||
import {
|
import {
|
||||||
ProviderOrganizationOrganizationDetailsResponse,
|
ProviderOrganizationOrganizationDetailsResponse,
|
||||||
ProviderOrganizationResponse,
|
ProviderOrganizationResponse,
|
||||||
@@ -36,11 +35,6 @@ import { CreateAuthRequest } from "../auth/models/request/create-auth.request";
|
|||||||
import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request";
|
import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request";
|
||||||
import { EmailTokenRequest } from "../auth/models/request/email-token.request";
|
import { EmailTokenRequest } from "../auth/models/request/email-token.request";
|
||||||
import { EmailRequest } from "../auth/models/request/email.request";
|
import { EmailRequest } from "../auth/models/request/email.request";
|
||||||
import { EmergencyAccessAcceptRequest } from "../auth/models/request/emergency-access-accept.request";
|
|
||||||
import { EmergencyAccessConfirmRequest } from "../auth/models/request/emergency-access-confirm.request";
|
|
||||||
import { EmergencyAccessInviteRequest } from "../auth/models/request/emergency-access-invite.request";
|
|
||||||
import { EmergencyAccessPasswordRequest } from "../auth/models/request/emergency-access-password.request";
|
|
||||||
import { EmergencyAccessUpdateRequest } from "../auth/models/request/emergency-access-update.request";
|
|
||||||
import { PasswordTokenRequest } from "../auth/models/request/identity-token/password-token.request";
|
import { PasswordTokenRequest } from "../auth/models/request/identity-token/password-token.request";
|
||||||
import { SsoTokenRequest } from "../auth/models/request/identity-token/sso-token.request";
|
import { SsoTokenRequest } from "../auth/models/request/identity-token/sso-token.request";
|
||||||
import { UserApiTokenRequest } from "../auth/models/request/identity-token/user-api-token.request";
|
import { UserApiTokenRequest } from "../auth/models/request/identity-token/user-api-token.request";
|
||||||
@@ -65,12 +59,6 @@ import { UpdateTwoFactorYubioOtpRequest } from "../auth/models/request/update-tw
|
|||||||
import { ApiKeyResponse } from "../auth/models/response/api-key.response";
|
import { ApiKeyResponse } from "../auth/models/response/api-key.response";
|
||||||
import { AuthRequestResponse } from "../auth/models/response/auth-request.response";
|
import { AuthRequestResponse } from "../auth/models/response/auth-request.response";
|
||||||
import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response";
|
import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response";
|
||||||
import {
|
|
||||||
EmergencyAccessGranteeDetailsResponse,
|
|
||||||
EmergencyAccessGrantorDetailsResponse,
|
|
||||||
EmergencyAccessTakeoverResponse,
|
|
||||||
EmergencyAccessViewResponse,
|
|
||||||
} from "../auth/models/response/emergency-access.response";
|
|
||||||
import { IdentityCaptchaResponse } from "../auth/models/response/identity-captcha.response";
|
import { IdentityCaptchaResponse } from "../auth/models/response/identity-captcha.response";
|
||||||
import { IdentityTokenResponse } from "../auth/models/response/identity-token.response";
|
import { IdentityTokenResponse } from "../auth/models/response/identity-token.response";
|
||||||
import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response";
|
import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response";
|
||||||
@@ -365,25 +353,6 @@ export abstract class ApiService {
|
|||||||
request: DeviceVerificationRequest
|
request: DeviceVerificationRequest
|
||||||
) => Promise<DeviceVerificationResponse>;
|
) => Promise<DeviceVerificationResponse>;
|
||||||
|
|
||||||
getEmergencyAccessTrusted: () => Promise<ListResponse<EmergencyAccessGranteeDetailsResponse>>;
|
|
||||||
getEmergencyAccessGranted: () => Promise<ListResponse<EmergencyAccessGrantorDetailsResponse>>;
|
|
||||||
getEmergencyAccess: (id: string) => Promise<EmergencyAccessGranteeDetailsResponse>;
|
|
||||||
getEmergencyGrantorPolicies: (id: string) => Promise<ListResponse<PolicyResponse>>;
|
|
||||||
putEmergencyAccess: (id: string, request: EmergencyAccessUpdateRequest) => Promise<any>;
|
|
||||||
deleteEmergencyAccess: (id: string) => Promise<any>;
|
|
||||||
postEmergencyAccessInvite: (request: EmergencyAccessInviteRequest) => Promise<any>;
|
|
||||||
postEmergencyAccessReinvite: (id: string) => Promise<any>;
|
|
||||||
postEmergencyAccessAccept: (id: string, request: EmergencyAccessAcceptRequest) => Promise<any>;
|
|
||||||
postEmergencyAccessConfirm: (id: string, request: EmergencyAccessConfirmRequest) => Promise<any>;
|
|
||||||
postEmergencyAccessInitiate: (id: string) => Promise<any>;
|
|
||||||
postEmergencyAccessApprove: (id: string) => Promise<any>;
|
|
||||||
postEmergencyAccessReject: (id: string) => Promise<any>;
|
|
||||||
postEmergencyAccessTakeover: (id: string) => Promise<EmergencyAccessTakeoverResponse>;
|
|
||||||
postEmergencyAccessPassword: (
|
|
||||||
id: string,
|
|
||||||
request: EmergencyAccessPasswordRequest
|
|
||||||
) => Promise<any>;
|
|
||||||
postEmergencyAccessView: (id: string) => Promise<EmergencyAccessViewResponse>;
|
|
||||||
getCloudCommunicationsEnabled: () => Promise<boolean>;
|
getCloudCommunicationsEnabled: () => Promise<boolean>;
|
||||||
abstract getOrganizationConnection<TConfig extends OrganizationConnectionConfigApis>(
|
abstract getOrganizationConnection<TConfig extends OrganizationConnectionConfigApis>(
|
||||||
id: string,
|
id: string,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
} from "../admin-console/models/response/organization-connection.response";
|
} from "../admin-console/models/response/organization-connection.response";
|
||||||
import { OrganizationExportResponse } from "../admin-console/models/response/organization-export.response";
|
import { OrganizationExportResponse } from "../admin-console/models/response/organization-export.response";
|
||||||
import { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response";
|
import { OrganizationSponsorshipSyncStatusResponse } from "../admin-console/models/response/organization-sponsorship-sync-status.response";
|
||||||
import { PolicyResponse } from "../admin-console/models/response/policy.response";
|
|
||||||
import {
|
import {
|
||||||
ProviderOrganizationOrganizationDetailsResponse,
|
ProviderOrganizationOrganizationDetailsResponse,
|
||||||
ProviderOrganizationResponse,
|
ProviderOrganizationResponse,
|
||||||
@@ -38,11 +37,6 @@ import { CreateAuthRequest } from "../auth/models/request/create-auth.request";
|
|||||||
import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request";
|
import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request";
|
||||||
import { EmailTokenRequest } from "../auth/models/request/email-token.request";
|
import { EmailTokenRequest } from "../auth/models/request/email-token.request";
|
||||||
import { EmailRequest } from "../auth/models/request/email.request";
|
import { EmailRequest } from "../auth/models/request/email.request";
|
||||||
import { EmergencyAccessAcceptRequest } from "../auth/models/request/emergency-access-accept.request";
|
|
||||||
import { EmergencyAccessConfirmRequest } from "../auth/models/request/emergency-access-confirm.request";
|
|
||||||
import { EmergencyAccessInviteRequest } from "../auth/models/request/emergency-access-invite.request";
|
|
||||||
import { EmergencyAccessPasswordRequest } from "../auth/models/request/emergency-access-password.request";
|
|
||||||
import { EmergencyAccessUpdateRequest } from "../auth/models/request/emergency-access-update.request";
|
|
||||||
import { DeviceRequest } from "../auth/models/request/identity-token/device.request";
|
import { DeviceRequest } from "../auth/models/request/identity-token/device.request";
|
||||||
import { PasswordTokenRequest } from "../auth/models/request/identity-token/password-token.request";
|
import { PasswordTokenRequest } from "../auth/models/request/identity-token/password-token.request";
|
||||||
import { SsoTokenRequest } from "../auth/models/request/identity-token/sso-token.request";
|
import { SsoTokenRequest } from "../auth/models/request/identity-token/sso-token.request";
|
||||||
@@ -69,12 +63,6 @@ import { UpdateTwoFactorYubioOtpRequest } from "../auth/models/request/update-tw
|
|||||||
import { ApiKeyResponse } from "../auth/models/response/api-key.response";
|
import { ApiKeyResponse } from "../auth/models/response/api-key.response";
|
||||||
import { AuthRequestResponse } from "../auth/models/response/auth-request.response";
|
import { AuthRequestResponse } from "../auth/models/response/auth-request.response";
|
||||||
import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response";
|
import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response";
|
||||||
import {
|
|
||||||
EmergencyAccessGranteeDetailsResponse,
|
|
||||||
EmergencyAccessGrantorDetailsResponse,
|
|
||||||
EmergencyAccessTakeoverResponse,
|
|
||||||
EmergencyAccessViewResponse,
|
|
||||||
} from "../auth/models/response/emergency-access.response";
|
|
||||||
import { IdentityCaptchaResponse } from "../auth/models/response/identity-captcha.response";
|
import { IdentityCaptchaResponse } from "../auth/models/response/identity-captcha.response";
|
||||||
import { IdentityTokenResponse } from "../auth/models/response/identity-token.response";
|
import { IdentityTokenResponse } from "../auth/models/response/identity-token.response";
|
||||||
import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response";
|
import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response";
|
||||||
@@ -1123,81 +1111,6 @@ export class ApiService implements ApiServiceAbstraction {
|
|||||||
return new DeviceVerificationResponse(r);
|
return new DeviceVerificationResponse(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emergency Access APIs
|
|
||||||
|
|
||||||
async getEmergencyAccessTrusted(): Promise<ListResponse<EmergencyAccessGranteeDetailsResponse>> {
|
|
||||||
const r = await this.send("GET", "/emergency-access/trusted", null, true, true);
|
|
||||||
return new ListResponse(r, EmergencyAccessGranteeDetailsResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEmergencyAccessGranted(): Promise<ListResponse<EmergencyAccessGrantorDetailsResponse>> {
|
|
||||||
const r = await this.send("GET", "/emergency-access/granted", null, true, true);
|
|
||||||
return new ListResponse(r, EmergencyAccessGrantorDetailsResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEmergencyAccess(id: string): Promise<EmergencyAccessGranteeDetailsResponse> {
|
|
||||||
const r = await this.send("GET", "/emergency-access/" + id, null, true, true);
|
|
||||||
return new EmergencyAccessGranteeDetailsResponse(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEmergencyGrantorPolicies(id: string): Promise<ListResponse<PolicyResponse>> {
|
|
||||||
const r = await this.send("GET", "/emergency-access/" + id + "/policies", null, true, true);
|
|
||||||
return new ListResponse(r, PolicyResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
putEmergencyAccess(id: string, request: EmergencyAccessUpdateRequest): Promise<any> {
|
|
||||||
return this.send("PUT", "/emergency-access/" + id, request, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteEmergencyAccess(id: string): Promise<any> {
|
|
||||||
return this.send("DELETE", "/emergency-access/" + id, null, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
postEmergencyAccessInvite(request: EmergencyAccessInviteRequest): Promise<any> {
|
|
||||||
return this.send("POST", "/emergency-access/invite", request, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
postEmergencyAccessReinvite(id: string): Promise<any> {
|
|
||||||
return this.send("POST", "/emergency-access/" + id + "/reinvite", null, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
postEmergencyAccessAccept(id: string, request: EmergencyAccessAcceptRequest): Promise<any> {
|
|
||||||
return this.send("POST", "/emergency-access/" + id + "/accept", request, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
postEmergencyAccessConfirm(id: string, request: EmergencyAccessConfirmRequest): Promise<any> {
|
|
||||||
return this.send("POST", "/emergency-access/" + id + "/confirm", request, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
postEmergencyAccessInitiate(id: string): Promise<any> {
|
|
||||||
return this.send("POST", "/emergency-access/" + id + "/initiate", null, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
postEmergencyAccessApprove(id: string): Promise<any> {
|
|
||||||
return this.send("POST", "/emergency-access/" + id + "/approve", null, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
postEmergencyAccessReject(id: string): Promise<any> {
|
|
||||||
return this.send("POST", "/emergency-access/" + id + "/reject", null, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async postEmergencyAccessTakeover(id: string): Promise<EmergencyAccessTakeoverResponse> {
|
|
||||||
const r = await this.send("POST", "/emergency-access/" + id + "/takeover", null, true, true);
|
|
||||||
return new EmergencyAccessTakeoverResponse(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
async postEmergencyAccessPassword(
|
|
||||||
id: string,
|
|
||||||
request: EmergencyAccessPasswordRequest
|
|
||||||
): Promise<any> {
|
|
||||||
await this.send("POST", "/emergency-access/" + id + "/password", request, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
async postEmergencyAccessView(id: string): Promise<EmergencyAccessViewResponse> {
|
|
||||||
const r = await this.send("POST", "/emergency-access/" + id + "/view", null, true, true);
|
|
||||||
return new EmergencyAccessViewResponse(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Organization APIs
|
// Organization APIs
|
||||||
|
|
||||||
async getCloudCommunicationsEnabled(): Promise<boolean> {
|
async getCloudCommunicationsEnabled(): Promise<boolean> {
|
||||||
|
|||||||
Reference in New Issue
Block a user