1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-05 01:53:55 +00:00

[PM-5499] auth request service migrations (#8597)

* move auth request storage to service

* create migrations for auth requests

* fix tests

* fix browser

* fix login strategy

* update migration

* use correct test descriptions in migration
This commit is contained in:
Jake Fink
2024-04-15 12:34:30 -04:00
committed by GitHub
parent d0bcc75721
commit 576431d29e
26 changed files with 503 additions and 120 deletions

View File

@@ -9,6 +9,7 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { StateProvider } from "@bitwarden/common/platform/state";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
@@ -18,6 +19,7 @@ import { AuthRequestService } from "./auth-request.service";
describe("AuthRequestService", () => {
let sut: AuthRequestService;
const stateProvider = mock<StateProvider>();
let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
const appIdService = mock<AppIdService>();
@@ -38,6 +40,7 @@ describe("AuthRequestService", () => {
masterPasswordService,
cryptoService,
apiService,
stateProvider,
);
mockPrivateKey = new Uint8Array(64);
@@ -59,6 +62,31 @@ describe("AuthRequestService", () => {
});
});
describe("AcceptAuthRequests", () => {
it("returns an error when userId isn't provided", async () => {
await expect(sut.getAcceptAuthRequests(undefined)).rejects.toThrow("User ID is required");
await expect(sut.setAcceptAuthRequests(true, undefined)).rejects.toThrow(
"User ID is required",
);
});
});
describe("AdminAuthRequest", () => {
it("returns an error when userId isn't provided", async () => {
await expect(sut.getAdminAuthRequest(undefined)).rejects.toThrow("User ID is required");
await expect(sut.setAdminAuthRequest(undefined, undefined)).rejects.toThrow(
"User ID is required",
);
await expect(sut.clearAdminAuthRequest(undefined)).rejects.toThrow("User ID is required");
});
it("does not allow clearing from setAdminAuthRequest", async () => {
await expect(sut.setAdminAuthRequest(null, "USER_ID" as UserId)).rejects.toThrow(
"Auth request is required",
);
});
});
describe("approveOrDenyAuthRequest", () => {
beforeEach(() => {
cryptoService.rsaEncrypt.mockResolvedValue({

View File

@@ -1,8 +1,10 @@
import { firstValueFrom, Observable, Subject } from "rxjs";
import { Observable, Subject, firstValueFrom } from "rxjs";
import { Jsonify } from "type-fest";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable";
import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response";
@@ -10,10 +12,43 @@ import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.ser
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
AUTH_REQUEST_DISK_LOCAL,
StateProvider,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { AuthRequestServiceAbstraction } from "../../abstractions/auth-request.service.abstraction";
/**
* Disk-local to maintain consistency between tabs (even though
* approvals are currently only available on desktop). We don't
* want to clear this on logout as it's a user preference.
*/
export const ACCEPT_AUTH_REQUESTS_KEY = new UserKeyDefinition<boolean>(
AUTH_REQUEST_DISK_LOCAL,
"acceptAuthRequests",
{
deserializer: (value) => value ?? false,
clearOn: [],
},
);
/**
* Disk-local to maintain consistency between tabs. We don't want to
* clear this on logout since admin auth requests are long-lived.
*/
export const ADMIN_AUTH_REQUEST_KEY = new UserKeyDefinition<Jsonify<AdminAuthRequestStorable>>(
AUTH_REQUEST_DISK_LOCAL,
"adminAuthRequest",
{
deserializer: (value) => value,
clearOn: [],
},
);
export class AuthRequestService implements AuthRequestServiceAbstraction {
private authRequestPushNotificationSubject = new Subject<string>();
authRequestPushNotification$: Observable<string>;
@@ -24,10 +59,61 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private cryptoService: CryptoService,
private apiService: ApiService,
private stateProvider: StateProvider,
) {
this.authRequestPushNotification$ = this.authRequestPushNotificationSubject.asObservable();
}
async getAcceptAuthRequests(userId: UserId): Promise<boolean> {
if (userId == null) {
throw new Error("User ID is required");
}
const value = await firstValueFrom(
this.stateProvider.getUser(userId, ACCEPT_AUTH_REQUESTS_KEY).state$,
);
return value;
}
async setAcceptAuthRequests(accept: boolean, userId: UserId): Promise<void> {
if (userId == null) {
throw new Error("User ID is required");
}
await this.stateProvider.setUserState(ACCEPT_AUTH_REQUESTS_KEY, accept, userId);
}
async getAdminAuthRequest(userId: UserId): Promise<AdminAuthRequestStorable | null> {
if (userId == null) {
throw new Error("User ID is required");
}
const authRequestSerialized = await firstValueFrom(
this.stateProvider.getUser(userId, ADMIN_AUTH_REQUEST_KEY).state$,
);
const adminAuthRequestStorable = AdminAuthRequestStorable.fromJSON(authRequestSerialized);
return adminAuthRequestStorable;
}
async setAdminAuthRequest(authRequest: AdminAuthRequestStorable, userId: UserId): Promise<void> {
if (userId == null) {
throw new Error("User ID is required");
}
if (authRequest == null) {
throw new Error("Auth request is required");
}
await this.stateProvider.setUserState(ADMIN_AUTH_REQUEST_KEY, authRequest.toJSON(), userId);
}
async clearAdminAuthRequest(userId: UserId): Promise<void> {
if (userId == null) {
throw new Error("User ID is required");
}
await this.stateProvider.setUserState(ADMIN_AUTH_REQUEST_KEY, null, userId);
}
async approveOrDenyAuthRequest(
approve: boolean,
authRequest: AuthRequestResponse,