1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 22:33:35 +00:00

[PM-23628] Require userId for fetching provider keys (#16993)

* remove getProviderKey and expose providerKeys$

* update consumers
This commit is contained in:
Thomas Avery
2025-10-27 11:04:17 -05:00
committed by GitHub
parent b335987213
commit bd89c0ce6d
9 changed files with 223 additions and 64 deletions

View File

@@ -10,7 +10,7 @@ import {
import { WrappedSigningKey } from "@bitwarden/common/key-management/types";
import { KeySuffixOptions, HashPurpose } from "@bitwarden/common/platform/enums";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, ProviderId, UserId } from "@bitwarden/common/types/guid";
import {
UserKey,
MasterKey,
@@ -248,17 +248,19 @@ export abstract class KeyService {
/**
* Stores the provider keys for a given user.
* @param orgs The provider orgs for which to save the keys from.
* @param providers The provider orgs for which to save the keys from.
* @param userId The user id of the user for which to store the keys for.
*/
abstract setProviderKeys(orgs: ProfileProviderResponse[], userId: UserId): Promise<void>;
abstract setProviderKeys(providers: ProfileProviderResponse[], userId: UserId): Promise<void>;
/**
*
* @throws Error when providerId is null or no active user
* @param providerId The desired provider
* @returns The provider's symmetric key
* Gets an observable of provider keys for the given user.
* @param userId The user to get provider keys for.
* @return An observable stream of the users providers keys if they are unlocked, or null if the user is not unlocked.
* @throws If an invalid user id is passed in.
*/
abstract getProviderKey(providerId: string): Promise<ProviderKey | null>;
abstract providerKeys$(userId: UserId): Observable<Record<ProviderId, ProviderKey> | null>;
/**
* Creates a new organization key and encrypts it with the user's public key.
* This method can also return Provider keys for creating new Provider users.

View File

@@ -39,7 +39,7 @@ import {
FakeSingleUserState,
} from "@bitwarden/common/spec";
import { CsprngArray } from "@bitwarden/common/types/csprng";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, ProviderId, UserId } from "@bitwarden/common/types/guid";
import {
UserKey,
MasterKey,
@@ -1314,6 +1314,49 @@ describe("keyService", () => {
});
});
describe("providerKeys$", () => {
let mockUserPrivateKey: Uint8Array;
let mockProviderKeys: Record<ProviderId, ProviderKey>;
beforeEach(() => {
mockUserPrivateKey = makeStaticByteArray(64, 1);
mockProviderKeys = {
["provider1" as ProviderId]: makeSymmetricCryptoKey<ProviderKey>(64),
["provider2" as ProviderId]: makeSymmetricCryptoKey<ProviderKey>(64),
};
});
it("returns null when userPrivateKey is null", async () => {
jest.spyOn(keyService, "userPrivateKey$").mockReturnValue(of(null));
const result = await firstValueFrom(keyService.providerKeys$(mockUserId));
expect(result).toBeNull();
});
it("returns provider keys when userPrivateKey is available", async () => {
jest.spyOn(keyService, "userPrivateKey$").mockReturnValue(of(mockUserPrivateKey as any));
jest.spyOn(keyService as any, "providerKeysHelper$").mockReturnValue(of(mockProviderKeys));
const result = await firstValueFrom(keyService.providerKeys$(mockUserId));
expect(result).toEqual(mockProviderKeys);
expect((keyService as any).providerKeysHelper$).toHaveBeenCalledWith(
mockUserId,
mockUserPrivateKey,
);
});
it("returns null when providerKeysHelper$ returns null", async () => {
jest.spyOn(keyService, "userPrivateKey$").mockReturnValue(of(mockUserPrivateKey as any));
jest.spyOn(keyService as any, "providerKeysHelper$").mockReturnValue(of(null));
const result = await firstValueFrom(keyService.providerKeys$(mockUserId));
expect(result).toBeNull();
});
});
describe("makeKeyPair", () => {
test.each([null as unknown as SymmetricCryptoKey, undefined as unknown as SymmetricCryptoKey])(
"throws when the provided key is %s",

View File

@@ -426,20 +426,16 @@ export class DefaultKeyService implements KeyServiceAbstraction {
});
}
// TODO: Deprecate in favor of observable
async getProviderKey(providerId: ProviderId): Promise<ProviderKey | null> {
if (providerId == null) {
return null;
}
providerKeys$(userId: UserId): Observable<Record<ProviderId, ProviderKey> | null> {
return this.userPrivateKey$(userId).pipe(
switchMap((userPrivateKey) => {
if (userPrivateKey == null) {
return of(null);
}
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
if (activeUserId == null) {
throw new Error("No active user found.");
}
const providerKeys = await firstValueFrom(this.providerKeys$(activeUserId));
return providerKeys?.[providerId] ?? null;
return this.providerKeysHelper$(userId, userPrivateKey);
}),
);
}
private async clearProviderKeys(userId: UserId): Promise<void> {
@@ -829,18 +825,6 @@ export class DefaultKeyService implements KeyServiceAbstraction {
)) as UserPrivateKey;
}
providerKeys$(userId: UserId) {
return this.userPrivateKey$(userId).pipe(
switchMap((userPrivateKey) => {
if (userPrivateKey == null) {
return of(null);
}
return this.providerKeysHelper$(userId, userPrivateKey);
}),
);
}
/**
* A helper for decrypting provider keys that requires a user id and that users decrypted private key
* this is helpful for when you may have already grabbed the user private key and don't want to redo