mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
fix(EmergencyAccess): [Auth/PM-23860] - Restore contact removal functionality (#15666)
* PM-23860 - EmergencyAccessService - convert types from response model to actual constructed type to avoid structural typing issue at runtime * PM-23860 - EmergencyAccessService tests - add tests to both methods to prevent this from being possible again.
This commit is contained in:
@@ -5,6 +5,10 @@ import { KdfType } from "@bitwarden/key-management";
|
|||||||
|
|
||||||
import { EmergencyAccessStatusType } from "../enums/emergency-access-status-type";
|
import { EmergencyAccessStatusType } from "../enums/emergency-access-status-type";
|
||||||
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
||||||
|
import {
|
||||||
|
EmergencyAccessGranteeDetailsResponse,
|
||||||
|
EmergencyAccessGrantorDetailsResponse,
|
||||||
|
} from "../response/emergency-access.response";
|
||||||
|
|
||||||
export class GranteeEmergencyAccess {
|
export class GranteeEmergencyAccess {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -16,6 +20,24 @@ export class GranteeEmergencyAccess {
|
|||||||
waitTimeDays: number;
|
waitTimeDays: number;
|
||||||
creationDate: string;
|
creationDate: string;
|
||||||
avatarColor: string;
|
avatarColor: string;
|
||||||
|
|
||||||
|
constructor(partial: Partial<GranteeEmergencyAccess> = {}) {
|
||||||
|
Object.assign(this, partial);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromResponse(response: EmergencyAccessGranteeDetailsResponse) {
|
||||||
|
return new GranteeEmergencyAccess({
|
||||||
|
id: response.id,
|
||||||
|
granteeId: response.granteeId,
|
||||||
|
name: response.name,
|
||||||
|
email: response.email,
|
||||||
|
type: response.type,
|
||||||
|
status: response.status,
|
||||||
|
waitTimeDays: response.waitTimeDays,
|
||||||
|
creationDate: response.creationDate,
|
||||||
|
avatarColor: response.avatarColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GrantorEmergencyAccess {
|
export class GrantorEmergencyAccess {
|
||||||
@@ -28,6 +50,24 @@ export class GrantorEmergencyAccess {
|
|||||||
waitTimeDays: number;
|
waitTimeDays: number;
|
||||||
creationDate: string;
|
creationDate: string;
|
||||||
avatarColor: string;
|
avatarColor: string;
|
||||||
|
|
||||||
|
constructor(partial: Partial<GrantorEmergencyAccess> = {}) {
|
||||||
|
Object.assign(this, partial);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromResponse(response: EmergencyAccessGrantorDetailsResponse) {
|
||||||
|
return new GrantorEmergencyAccess({
|
||||||
|
id: response.id,
|
||||||
|
grantorId: response.grantorId,
|
||||||
|
name: response.name,
|
||||||
|
email: response.email,
|
||||||
|
type: response.type,
|
||||||
|
status: response.status,
|
||||||
|
waitTimeDays: response.waitTimeDays,
|
||||||
|
creationDate: response.creationDate,
|
||||||
|
avatarColor: response.avatarColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TakeoverTypeEmergencyAccess {
|
export class TakeoverTypeEmergencyAccess {
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ import { KdfType, KeyService } from "@bitwarden/key-management";
|
|||||||
|
|
||||||
import { EmergencyAccessStatusType } from "../enums/emergency-access-status-type";
|
import { EmergencyAccessStatusType } from "../enums/emergency-access-status-type";
|
||||||
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
import { EmergencyAccessType } from "../enums/emergency-access-type";
|
||||||
|
import { GranteeEmergencyAccess, GrantorEmergencyAccess } from "../models/emergency-access";
|
||||||
import { EmergencyAccessPasswordRequest } from "../request/emergency-access-password.request";
|
import { EmergencyAccessPasswordRequest } from "../request/emergency-access-password.request";
|
||||||
import {
|
import {
|
||||||
EmergencyAccessGranteeDetailsResponse,
|
EmergencyAccessGranteeDetailsResponse,
|
||||||
|
EmergencyAccessGrantorDetailsResponse,
|
||||||
EmergencyAccessTakeoverResponse,
|
EmergencyAccessTakeoverResponse,
|
||||||
} from "../response/emergency-access.response";
|
} from "../response/emergency-access.response";
|
||||||
|
|
||||||
@@ -242,11 +244,19 @@ describe("EmergencyAccessService", () => {
|
|||||||
|
|
||||||
const mockEmergencyAccess = {
|
const mockEmergencyAccess = {
|
||||||
data: [
|
data: [
|
||||||
createMockEmergencyAccess("0", "EA 0", EmergencyAccessStatusType.Invited),
|
createMockEmergencyAccessGranteeDetails("0", "EA 0", EmergencyAccessStatusType.Invited),
|
||||||
createMockEmergencyAccess("1", "EA 1", EmergencyAccessStatusType.Accepted),
|
createMockEmergencyAccessGranteeDetails("1", "EA 1", EmergencyAccessStatusType.Accepted),
|
||||||
createMockEmergencyAccess("2", "EA 2", EmergencyAccessStatusType.Confirmed),
|
createMockEmergencyAccessGranteeDetails("2", "EA 2", EmergencyAccessStatusType.Confirmed),
|
||||||
createMockEmergencyAccess("3", "EA 3", EmergencyAccessStatusType.RecoveryInitiated),
|
createMockEmergencyAccessGranteeDetails(
|
||||||
createMockEmergencyAccess("4", "EA 4", EmergencyAccessStatusType.RecoveryApproved),
|
"3",
|
||||||
|
"EA 3",
|
||||||
|
EmergencyAccessStatusType.RecoveryInitiated,
|
||||||
|
),
|
||||||
|
createMockEmergencyAccessGranteeDetails(
|
||||||
|
"4",
|
||||||
|
"EA 4",
|
||||||
|
EmergencyAccessStatusType.RecoveryApproved,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
} as ListResponse<EmergencyAccessGranteeDetailsResponse>;
|
} as ListResponse<EmergencyAccessGranteeDetailsResponse>;
|
||||||
|
|
||||||
@@ -295,9 +305,113 @@ describe("EmergencyAccessService", () => {
|
|||||||
).rejects.toThrow("New user key is required for rotation.");
|
).rejects.toThrow("New user key is required for rotation.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getEmergencyAccessTrusted", () => {
|
||||||
|
it("should return an empty array if no emergency access is granted", async () => {
|
||||||
|
emergencyAccessApiService.getEmergencyAccessTrusted.mockResolvedValue({
|
||||||
|
data: [],
|
||||||
|
} as ListResponse<EmergencyAccessGranteeDetailsResponse>);
|
||||||
|
|
||||||
|
const result = await emergencyAccessService.getEmergencyAccessTrusted();
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return an empty array if the API returns an empty response", async () => {
|
||||||
|
emergencyAccessApiService.getEmergencyAccessTrusted.mockResolvedValue(
|
||||||
|
null as unknown as ListResponse<EmergencyAccessGranteeDetailsResponse>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await emergencyAccessService.getEmergencyAccessTrusted();
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a list of trusted emergency access contacts", async () => {
|
||||||
|
const mockEmergencyAccess = [
|
||||||
|
createMockEmergencyAccessGranteeDetails("1", "EA 1", EmergencyAccessStatusType.Invited),
|
||||||
|
createMockEmergencyAccessGranteeDetails("2", "EA 2", EmergencyAccessStatusType.Invited),
|
||||||
|
createMockEmergencyAccessGranteeDetails("3", "EA 3", EmergencyAccessStatusType.Accepted),
|
||||||
|
createMockEmergencyAccessGranteeDetails("4", "EA 4", EmergencyAccessStatusType.Confirmed),
|
||||||
|
createMockEmergencyAccessGranteeDetails(
|
||||||
|
"5",
|
||||||
|
"EA 5",
|
||||||
|
EmergencyAccessStatusType.RecoveryInitiated,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
emergencyAccessApiService.getEmergencyAccessTrusted.mockResolvedValue({
|
||||||
|
data: mockEmergencyAccess,
|
||||||
|
} as ListResponse<EmergencyAccessGranteeDetailsResponse>);
|
||||||
|
|
||||||
|
const result = await emergencyAccessService.getEmergencyAccessTrusted();
|
||||||
|
|
||||||
|
expect(result).toHaveLength(mockEmergencyAccess.length);
|
||||||
|
|
||||||
|
result.forEach((access, index) => {
|
||||||
|
expect(access).toBeInstanceOf(GranteeEmergencyAccess);
|
||||||
|
|
||||||
|
expect(access.id).toBe(mockEmergencyAccess[index].id);
|
||||||
|
expect(access.name).toBe(mockEmergencyAccess[index].name);
|
||||||
|
expect(access.status).toBe(mockEmergencyAccess[index].status);
|
||||||
|
expect(access.type).toBe(mockEmergencyAccess[index].type);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getEmergencyAccessGranted", () => {
|
||||||
|
it("should return an empty array if no emergency access is granted", async () => {
|
||||||
|
emergencyAccessApiService.getEmergencyAccessGranted.mockResolvedValue({
|
||||||
|
data: [],
|
||||||
|
} as ListResponse<EmergencyAccessGrantorDetailsResponse>);
|
||||||
|
|
||||||
|
const result = await emergencyAccessService.getEmergencyAccessGranted();
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return an empty array if the API returns an empty response", async () => {
|
||||||
|
emergencyAccessApiService.getEmergencyAccessGranted.mockResolvedValue(
|
||||||
|
null as unknown as ListResponse<EmergencyAccessGrantorDetailsResponse>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await emergencyAccessService.getEmergencyAccessGranted();
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a list of granted emergency access contacts", async () => {
|
||||||
|
const mockEmergencyAccess = [
|
||||||
|
createMockEmergencyAccessGrantorDetails("1", "EA 1", EmergencyAccessStatusType.Invited),
|
||||||
|
createMockEmergencyAccessGrantorDetails("2", "EA 2", EmergencyAccessStatusType.Invited),
|
||||||
|
createMockEmergencyAccessGrantorDetails("3", "EA 3", EmergencyAccessStatusType.Accepted),
|
||||||
|
createMockEmergencyAccessGrantorDetails("4", "EA 4", EmergencyAccessStatusType.Confirmed),
|
||||||
|
createMockEmergencyAccessGrantorDetails(
|
||||||
|
"5",
|
||||||
|
"EA 5",
|
||||||
|
EmergencyAccessStatusType.RecoveryInitiated,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
emergencyAccessApiService.getEmergencyAccessGranted.mockResolvedValue({
|
||||||
|
data: mockEmergencyAccess,
|
||||||
|
} as ListResponse<EmergencyAccessGrantorDetailsResponse>);
|
||||||
|
|
||||||
|
const result = await emergencyAccessService.getEmergencyAccessGranted();
|
||||||
|
|
||||||
|
expect(result).toHaveLength(mockEmergencyAccess.length);
|
||||||
|
|
||||||
|
result.forEach((access, index) => {
|
||||||
|
expect(access).toBeInstanceOf(GrantorEmergencyAccess);
|
||||||
|
|
||||||
|
expect(access.id).toBe(mockEmergencyAccess[index].id);
|
||||||
|
expect(access.name).toBe(mockEmergencyAccess[index].name);
|
||||||
|
expect(access.status).toBe(mockEmergencyAccess[index].status);
|
||||||
|
expect(access.type).toBe(mockEmergencyAccess[index].type);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createMockEmergencyAccess(
|
function createMockEmergencyAccessGranteeDetails(
|
||||||
id: string,
|
id: string,
|
||||||
name: string,
|
name: string,
|
||||||
status: EmergencyAccessStatusType,
|
status: EmergencyAccessStatusType,
|
||||||
@@ -309,3 +423,16 @@ function createMockEmergencyAccess(
|
|||||||
emergencyAccess.status = status;
|
emergencyAccess.status = status;
|
||||||
return emergencyAccess;
|
return emergencyAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createMockEmergencyAccessGrantorDetails(
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
status: EmergencyAccessStatusType,
|
||||||
|
): EmergencyAccessGrantorDetailsResponse {
|
||||||
|
const emergencyAccess = new EmergencyAccessGrantorDetailsResponse({});
|
||||||
|
emergencyAccess.id = id;
|
||||||
|
emergencyAccess.name = name;
|
||||||
|
emergencyAccess.type = 0;
|
||||||
|
emergencyAccess.status = status;
|
||||||
|
return emergencyAccess;
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,14 +77,22 @@ export class EmergencyAccessService
|
|||||||
* Gets all emergency access that the user has been granted.
|
* Gets all emergency access that the user has been granted.
|
||||||
*/
|
*/
|
||||||
async getEmergencyAccessTrusted(): Promise<GranteeEmergencyAccess[]> {
|
async getEmergencyAccessTrusted(): Promise<GranteeEmergencyAccess[]> {
|
||||||
return (await this.emergencyAccessApiService.getEmergencyAccessTrusted()).data;
|
const listResponse = await this.emergencyAccessApiService.getEmergencyAccessTrusted();
|
||||||
|
if (!listResponse || listResponse.data.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return listResponse.data.map((response) => GranteeEmergencyAccess.fromResponse(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all emergency access that the user has granted.
|
* Gets all emergency access that the user has granted.
|
||||||
*/
|
*/
|
||||||
async getEmergencyAccessGranted(): Promise<GrantorEmergencyAccess[]> {
|
async getEmergencyAccessGranted(): Promise<GrantorEmergencyAccess[]> {
|
||||||
return (await this.emergencyAccessApiService.getEmergencyAccessGranted()).data;
|
const listResponse = await this.emergencyAccessApiService.getEmergencyAccessGranted();
|
||||||
|
if (!listResponse || listResponse.data.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return listResponse.data.map((response) => GrantorEmergencyAccess.fromResponse(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user