mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 17:23:37 +00:00
[PM-19532] - Add Fingerprint phrase to cli auth request (#14556)
* first pass at adding fingerprint phrase to auth requests * Moved call to getFingerprint into the service layer. Added a new method for getting auth requests. Updated tests. * Fixing the import * Renaming to WithFingerprint
This commit is contained in:
@@ -47,7 +47,10 @@ export class ListCommand {
|
||||
|
||||
try {
|
||||
const requests =
|
||||
await this.organizationAuthRequestService.listPendingRequests(organizationId);
|
||||
await this.organizationAuthRequestService.listPendingRequestsWithFingerprint(
|
||||
organizationId,
|
||||
);
|
||||
|
||||
const res = new ListResponse(requests.map((r) => new PendingAuthRequestResponse(r)));
|
||||
return Response.success(res);
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PendingAuthRequestView } from "@bitwarden/bit-common/admin-console/auth-requests/";
|
||||
import { PendingAuthRequestWithFingerprintView } from "@bitwarden/bit-common/admin-console/auth-requests/pending-auth-request-with-fingerprint.view";
|
||||
import { BaseResponse } from "@bitwarden/cli/models/response/base.response";
|
||||
|
||||
export class PendingAuthRequestResponse implements BaseResponse {
|
||||
@@ -12,8 +12,9 @@ export class PendingAuthRequestResponse implements BaseResponse {
|
||||
requestDeviceType: string;
|
||||
requestIpAddress: string;
|
||||
creationDate: Date;
|
||||
fingerprintPhrase: string;
|
||||
|
||||
constructor(authRequest: PendingAuthRequestView) {
|
||||
constructor(authRequest: PendingAuthRequestWithFingerprintView) {
|
||||
this.id = authRequest.id;
|
||||
this.userId = authRequest.userId;
|
||||
this.organizationUserId = authRequest.organizationUserId;
|
||||
@@ -22,5 +23,6 @@ export class PendingAuthRequestResponse implements BaseResponse {
|
||||
this.requestDeviceType = authRequest.requestDeviceType;
|
||||
this.requestIpAddress = authRequest.requestIpAddress;
|
||||
this.creationDate = authRequest.creationDate;
|
||||
this.fingerprintPhrase = authRequest.fingerprintPhrase;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,64 @@ describe("OrganizationAuthRequestService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("listPendingRequestsWithDetails", () => {
|
||||
it("should retrieve the fingerprint phrase for each request and return the new result", async () => {
|
||||
jest.spyOn(organizationAuthRequestApiService, "listPendingRequests");
|
||||
|
||||
const organizationId = "organizationId";
|
||||
|
||||
const pendingAuthRequest = new PendingAuthRequestView();
|
||||
pendingAuthRequest.id = "requestId1";
|
||||
pendingAuthRequest.userId = "userId1";
|
||||
pendingAuthRequest.organizationUserId = "userId1";
|
||||
pendingAuthRequest.email = "email1";
|
||||
pendingAuthRequest.publicKey = "publicKey1";
|
||||
pendingAuthRequest.requestDeviceIdentifier = "requestDeviceIdentifier1";
|
||||
pendingAuthRequest.requestDeviceType = "requestDeviceType1";
|
||||
pendingAuthRequest.requestIpAddress = "requestIpAddress1";
|
||||
pendingAuthRequest.creationDate = new Date();
|
||||
const mockPendingAuthRequests = [pendingAuthRequest];
|
||||
organizationAuthRequestApiService.listPendingRequests
|
||||
.calledWith(organizationId)
|
||||
.mockResolvedValue(mockPendingAuthRequests);
|
||||
|
||||
const fingerprintPhrase = ["fingerprint", "phrase"];
|
||||
keyService.getFingerprint
|
||||
.calledWith(pendingAuthRequest.email, expect.any(Uint8Array))
|
||||
.mockResolvedValue(fingerprintPhrase);
|
||||
|
||||
const result =
|
||||
await organizationAuthRequestService.listPendingRequestsWithFingerprint(organizationId);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toEqual([
|
||||
{ ...pendingAuthRequest, fingerprintPhrase: fingerprintPhrase.join("-") },
|
||||
]);
|
||||
expect(organizationAuthRequestApiService.listPendingRequests).toHaveBeenCalledWith(
|
||||
organizationId,
|
||||
);
|
||||
});
|
||||
|
||||
it("should return empty list if no results and not call keyService", async () => {
|
||||
jest.spyOn(organizationAuthRequestApiService, "listPendingRequests");
|
||||
|
||||
const organizationId = "organizationId";
|
||||
|
||||
organizationAuthRequestApiService.listPendingRequests
|
||||
.calledWith(organizationId)
|
||||
.mockResolvedValue([]);
|
||||
|
||||
const result =
|
||||
await organizationAuthRequestService.listPendingRequestsWithFingerprint(organizationId);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
expect(keyService.getFingerprint).not.toHaveBeenCalled();
|
||||
expect(organizationAuthRequestApiService.listPendingRequests).toHaveBeenCalledWith(
|
||||
organizationId,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("denyPendingRequests", () => {
|
||||
it("should deny the specified pending auth requests", async () => {
|
||||
jest.spyOn(organizationAuthRequestApiService, "denyPendingRequests");
|
||||
|
||||
@@ -11,6 +11,7 @@ import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service";
|
||||
import { OrganizationAuthRequestUpdateRequest } from "./organization-auth-request-update.request";
|
||||
import { PendingAuthRequestWithFingerprintView } from "./pending-auth-request-with-fingerprint.view";
|
||||
import { PendingAuthRequestView } from "./pending-auth-request.view";
|
||||
|
||||
export class OrganizationAuthRequestService {
|
||||
@@ -25,6 +26,16 @@ export class OrganizationAuthRequestService {
|
||||
return await this.organizationAuthRequestApiService.listPendingRequests(organizationId);
|
||||
}
|
||||
|
||||
async listPendingRequestsWithFingerprint(
|
||||
organizationId: string,
|
||||
): Promise<PendingAuthRequestWithFingerprintView[]> {
|
||||
return Promise.all(
|
||||
((await this.listPendingRequests(organizationId)) ?? []).map(
|
||||
async (r) => await PendingAuthRequestWithFingerprintView.fromView(r, this.keyService),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async denyPendingRequests(organizationId: string, ...requestIds: string[]): Promise<void> {
|
||||
await this.organizationAuthRequestApiService.denyPendingRequests(organizationId, ...requestIds);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { PendingAuthRequestView } from "./pending-auth-request.view";
|
||||
|
||||
export class PendingAuthRequestWithFingerprintView extends PendingAuthRequestView {
|
||||
fingerprintPhrase: string = "";
|
||||
|
||||
static async fromView(
|
||||
view: PendingAuthRequestView,
|
||||
keyService: KeyService,
|
||||
): Promise<PendingAuthRequestWithFingerprintView> {
|
||||
const requestWithDetailsView = Object.assign(
|
||||
new PendingAuthRequestWithFingerprintView(),
|
||||
view,
|
||||
) as PendingAuthRequestWithFingerprintView;
|
||||
|
||||
requestWithDetailsView.fingerprintPhrase = (
|
||||
await keyService.getFingerprint(
|
||||
requestWithDetailsView.email,
|
||||
Utils.fromB64ToArray(requestWithDetailsView.publicKey),
|
||||
)
|
||||
)?.join("-");
|
||||
|
||||
return requestWithDetailsView;
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@
|
||||
<tr bitRow alignContent="top" *ngFor="let r of rows$ | async">
|
||||
<td bitCell class="tw-flex-col">
|
||||
<div>{{ r.email }}</div>
|
||||
<code class="tw-text-sm">{{ r.publicKey | fingerprint: r.email | async }}</code>
|
||||
<code class="tw-text-sm">{{ r.fingerprintPhrase }}</code>
|
||||
</td>
|
||||
<td bitCell class="tw-flex-col">
|
||||
<div>{{ r.requestDeviceType }}</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
|
||||
import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
||||
import { OrganizationAuthRequestApiService } from "@bitwarden/bit-common/admin-console/auth-requests/organization-auth-request-api.service";
|
||||
import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests/organization-auth-request.service";
|
||||
import { PendingAuthRequestWithFingerprintView } from "@bitwarden/bit-common/admin-console/auth-requests/pending-auth-request-with-fingerprint.view";
|
||||
import { PendingAuthRequestView } from "@bitwarden/bit-common/admin-console/auth-requests/pending-auth-request.view";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
@@ -44,7 +45,7 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module";
|
||||
imports: [SharedModule, NoItemsModule, LooseComponentsModule],
|
||||
})
|
||||
export class DeviceApprovalsComponent implements OnInit, OnDestroy {
|
||||
tableDataSource = new TableDataSource<PendingAuthRequestView>();
|
||||
tableDataSource = new TableDataSource<PendingAuthRequestWithFingerprintView>();
|
||||
organizationId: string;
|
||||
loading = true;
|
||||
actionInProgress = false;
|
||||
@@ -73,7 +74,9 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy {
|
||||
this.refresh$.pipe(
|
||||
tap(() => (this.loading = true)),
|
||||
switchMap(() =>
|
||||
this.organizationAuthRequestService.listPendingRequests(this.organizationId),
|
||||
this.organizationAuthRequestService.listPendingRequestsWithFingerprint(
|
||||
this.organizationId,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user