1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-13 06:54:07 +00:00

Update the sdk service to use accountCryptographicState

This commit is contained in:
Thomas Avery
2026-01-08 16:13:51 -06:00
parent 64dc507630
commit e5968310f5
5 changed files with 61 additions and 93 deletions

View File

@@ -838,7 +838,7 @@ export default class MainBackground {
this.accountService,
this.kdfConfigService,
this.keyService,
this.securityStateService,
this.accountCryptographicStateService,
this.apiService,
this.stateProvider,
this.configService,

View File

@@ -653,7 +653,7 @@ export class ServiceContainer {
this.accountService,
this.kdfConfigService,
this.keyService,
this.securityStateService,
this.accountCryptographicStateService,
this.apiService,
this.stateProvider,
this.configService,

View File

@@ -1651,7 +1651,7 @@ const safeProviders: SafeProvider[] = [
AccountServiceAbstraction,
KdfConfigService,
KeyService,
SecurityStateService,
AccountCryptographicStateService,
ApiServiceAbstraction,
StateProvider,
ConfigService,

View File

@@ -1,7 +1,6 @@
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom, of } from "rxjs";
import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { KdfConfigService, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management";
@@ -15,6 +14,7 @@ import {
mockAccountInfoWith,
} from "../../../../spec";
import { ApiService } from "../../../abstractions/api.service";
import { AccountCryptographicStateService } from "../../../key-management/account-cryptography/account-cryptographic-state.service";
import { EncryptedString } from "../../../key-management/crypto/models/enc-string";
import { UserId } from "../../../types/guid";
import { UserKey } from "../../../types/key";
@@ -44,7 +44,7 @@ describe("DefaultSdkService", () => {
let platformUtilsService!: MockProxy<PlatformUtilsService>;
let kdfConfigService!: MockProxy<KdfConfigService>;
let keyService!: MockProxy<KeyService>;
let securityStateService!: MockProxy<SecurityStateService>;
let accountCryptographicStateService!: MockProxy<AccountCryptographicStateService>;
let configService!: MockProxy<ConfigService>;
let service!: DefaultSdkService;
let accountService!: FakeAccountService;
@@ -59,7 +59,7 @@ describe("DefaultSdkService", () => {
platformUtilsService = mock<PlatformUtilsService>();
kdfConfigService = mock<KdfConfigService>();
keyService = mock<KeyService>();
securityStateService = mock<SecurityStateService>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
apiService = mock<ApiService>();
const mockUserId = Utils.newGuid() as UserId;
accountService = mockAccountServiceWith(mockUserId);
@@ -78,7 +78,7 @@ describe("DefaultSdkService", () => {
accountService,
kdfConfigService,
keyService,
securityStateService,
accountCryptographicStateService,
apiService,
fakeStateProvider,
configService,
@@ -103,13 +103,16 @@ describe("DefaultSdkService", () => {
keyService.userKey$
.calledWith(userId)
.mockReturnValue(of(new SymmetricCryptoKey(new Uint8Array(64)) as UserKey));
keyService.userEncryptedPrivateKey$
.calledWith(userId)
.mockReturnValue(of("private-key" as EncryptedString));
keyService.encryptedOrgKeys$.calledWith(userId).mockReturnValue(of({}));
keyService.userSigningKey$.calledWith(userId).mockReturnValue(of(null));
keyService.userSignedPublicKey$.calledWith(userId).mockReturnValue(of(null));
securityStateService.accountSecurityState$.calledWith(userId).mockReturnValue(of(null));
accountCryptographicStateService.accountCryptographicState$
.calledWith(userId)
.mockReturnValue(
of({
V1: {
private_key: "private-key" as EncryptedString,
},
}),
);
});
describe("given no client override has been set for the user", () => {

View File

@@ -30,8 +30,8 @@ import {
import { ApiService } from "../../../abstractions/api.service";
import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service";
import { AccountCryptographicStateService } from "../../../key-management/account-cryptography/account-cryptographic-state.service";
import { EncString } from "../../../key-management/crypto/models/enc-string";
import { SecurityStateService } from "../../../key-management/security-state/abstractions/security-state.service";
import { OrganizationId, UserId } from "../../../types/guid";
import { Environment, EnvironmentService } from "../../abstractions/environment.service";
import { PlatformUtilsService } from "../../abstractions/platform-utils.service";
@@ -103,7 +103,7 @@ export class DefaultSdkService implements SdkService {
private accountService: AccountService,
private kdfConfigService: KdfConfigService,
private keyService: KeyService,
private securityStateService: SecurityStateService,
private accountCryptographyStateService: AccountCryptographicStateService,
private apiService: ApiService,
private stateProvider: StateProvider,
private configService: ConfigService,
@@ -163,105 +163,70 @@ export class DefaultSdkService implements SdkService {
distinctUntilChanged(),
);
const kdfParams$ = this.kdfConfigService.getKdfConfig$(userId).pipe(distinctUntilChanged());
const privateKey$ = this.keyService
.userEncryptedPrivateKey$(userId)
const accountCryptographicState$ = this.accountCryptographyStateService
.accountCryptographicState$(userId)
.pipe(distinctUntilChanged());
const signingKey$ = this.keyService.userSigningKey$(userId).pipe(distinctUntilChanged());
const userKey$ = this.keyService.userKey$(userId).pipe(distinctUntilChanged());
const orgKeys$ = this.keyService.encryptedOrgKeys$(userId).pipe(
distinctUntilChanged(compareValues), // The upstream observable emits different objects with the same values
);
const securityState$ = this.securityStateService
.accountSecurityState$(userId)
.pipe(distinctUntilChanged(compareValues));
const signedPublicKey$ = this.keyService
.userSignedPublicKey$(userId)
.pipe(distinctUntilChanged(compareValues));
const client$ = combineLatest([
this.environmentService.getEnvironment$(userId),
account$,
kdfParams$,
privateKey$,
accountCryptographicState$,
userKey$,
signingKey$,
orgKeys$,
securityState$,
signedPublicKey$,
SdkLoadService.Ready, // Makes sure we wait (once) for the SDK to be loaded
]).pipe(
// switchMap is required to allow the clean-up logic to be executed when `combineLatest` emits a new value.
switchMap(
([
env,
account,
kdfParams,
privateKey,
userKey,
signingKey,
orgKeys,
securityState,
signedPublicKey,
]) => {
// Create our own observable to be able to implement clean-up logic
return new Observable<Rc<PasswordManagerClient>>((subscriber) => {
const createAndInitializeClient = async () => {
if (env == null || kdfParams == null || privateKey == null || userKey == null) {
return undefined;
}
switchMap(([env, account, kdfParams, accountCryptographicState, userKey, orgKeys]) => {
// Create our own observable to be able to implement clean-up logic
return new Observable<Rc<PasswordManagerClient>>((subscriber) => {
const createAndInitializeClient = async () => {
if (
env == null ||
kdfParams == null ||
accountCryptographicState == null ||
userKey == null
) {
return undefined;
}
const settings = this.toSettings(env);
const client = await this.sdkClientFactory.createSdkClient(
new JsTokenProvider(this.apiService, userId),
settings,
);
const settings = this.toSettings(env);
const client = await this.sdkClientFactory.createSdkClient(
new JsTokenProvider(this.apiService, userId),
settings,
);
let accountCryptographicState: WrappedAccountCryptographicState;
if (signingKey != null && securityState != null && signedPublicKey != null) {
accountCryptographicState = {
V2: {
private_key: privateKey,
signing_key: signingKey,
security_state: securityState,
signed_public_key: signedPublicKey,
},
};
} else {
accountCryptographicState = {
V1: {
private_key: privateKey,
},
};
}
await this.initializeClient(
userId,
client,
account,
kdfParams,
userKey,
accountCryptographicState,
orgKeys,
);
await this.initializeClient(
userId,
client,
account,
kdfParams,
userKey,
accountCryptographicState,
orgKeys,
);
return client;
};
return client;
};
let client: Rc<PasswordManagerClient> | undefined;
createAndInitializeClient()
.then((c) => {
client = c === undefined ? undefined : new Rc(c);
let client: Rc<PasswordManagerClient> | undefined;
createAndInitializeClient()
.then((c) => {
client = c === undefined ? undefined : new Rc(c);
subscriber.next(client);
})
.catch((e) => {
subscriber.error(e);
});
subscriber.next(client);
})
.catch((e) => {
subscriber.error(e);
});
return () => client?.markForDisposal();
});
},
),
return () => client?.markForDisposal();
});
}),
tap({ finalize: () => this.sdkClientCache.delete(userId) }),
shareReplay({ refCount: true, bufferSize: 1 }),
);