From 657d2f2db02a1c931461a363fcf2916e0587b095 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 7 Oct 2024 14:01:54 +0200 Subject: [PATCH 01/39] feat: update sdk service abstraction with documentation and new `userClient$` function --- .../platform/abstractions/sdk/sdk.service.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts index 360f2e91a76..90a153ab579 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk.service.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -2,9 +2,27 @@ import { Observable } from "rxjs"; import { BitwardenClient } from "@bitwarden/sdk-internal"; +import { UserId } from "../../../types/guid"; + export abstract class SdkService { - client$: Observable; + /** + * Check if the SDK is supported in the current environment. + */ supported$: Observable; + /** + * Retrieve a client initialized without a user. + * This client can only be used for operations that don't require a user context. + */ + client$: Observable; + + /** + * Retrieve a client initialized for a specific user. + * This client can be used for operations that require a user context, such as retrieving ciphers + * and operations involving crypto. It can also be used for operations that don't require a user context. + * @param userId + */ + abstract userClient$(userId: UserId): Observable; + abstract failedToInitialize(): Promise; } From e3326702c25760e470578daf162ce12b7ccf0ea2 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 8 Oct 2024 10:10:50 +0200 Subject: [PATCH 02/39] feat: add uninitialized user client with cache --- .../src/services/jslib-services.module.ts | 2 + .../services/sdk/default-sdk.service.ts | 74 ++++++++++++++++--- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index cc7af0c0b05..8829b01cf9a 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1334,6 +1334,8 @@ const safeProviders: SafeProvider[] = [ SdkClientFactory, EnvironmentService, PlatformUtilsServiceAbstraction, + AccountServiceAbstraction, + TokenServiceAbstraction, ApiServiceAbstraction, ], }), diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index d4a9cfeb7ed..9b3c1a8a0a8 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -1,27 +1,43 @@ -import { concatMap, firstValueFrom, shareReplay } from "rxjs"; +import { + combineLatest, + concatMap, + firstValueFrom, + Observable, + shareReplay, + tap, + map, + filter, +} from "rxjs"; -import { LogLevel, DeviceType as SdkDeviceType } from "@bitwarden/sdk-internal"; +import { + BitwardenClient, + ClientSettings, + LogLevel, + DeviceType as SdkDeviceType, +} from "@bitwarden/sdk-internal"; import { ApiService } from "../../../abstractions/api.service"; +import { AccountService } from "../../../auth/abstractions/account.service"; +import { TokenService } from "../../../auth/abstractions/token.service"; import { DeviceType } from "../../../enums/device-type.enum"; -import { EnvironmentService } from "../../abstractions/environment.service"; +import { UserId } from "../../../types/guid"; +import { Environment, EnvironmentService } from "../../abstractions/environment.service"; import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; import { SdkService } from "../../abstractions/sdk/sdk.service"; export class DefaultSdkService implements SdkService { + private sdkClientCache = new Map>(); + client$ = this.environmentService.environment$.pipe( concatMap(async (env) => { - const settings = { - apiUrl: env.getApiUrl(), - identityUrl: env.getIdentityUrl(), - deviceType: this.toDevice(this.platformUtilsService.getDevice()), - userAgent: this.userAgent ?? navigator.userAgent, - }; - + const settings = this.toSettings(env); return await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); }), shareReplay({ refCount: true, bufferSize: 1 }), + tap((client) => { + (window as any).client = client; + }), ); supported$ = this.client$.pipe( @@ -34,10 +50,39 @@ export class DefaultSdkService implements SdkService { private sdkClientFactory: SdkClientFactory, private environmentService: EnvironmentService, private platformUtilsService: PlatformUtilsService, + private accountService: AccountService, + private tokenService: TokenService, private apiService: ApiService, // Yes we shouldn't import ApiService, but it's temporary private userAgent: string = null, ) {} + userClient$(userId: UserId): Observable { + // TODO: Figure out what happens when the user logs out + if (this.sdkClientCache.has(userId)) { + return this.sdkClientCache.get(userId); + } + + const account$ = this.accountService.accounts$.pipe(map((accounts) => accounts[userId])); + const token$ = this.tokenService.hasAccessToken$(userId).pipe( + filter((hasToken) => hasToken), + concatMap(() => this.tokenService.getAccessToken(userId)), + ); + + const client$ = combineLatest([this.environmentService.environment$, account$, token$]).pipe( + concatMap(async ([env, account, token]) => { + const settings = this.toSettings(env); + const client = await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); + + // TODO: Init client crypto + + return client; + }), + ); + + this.sdkClientCache.set(userId, client$); + return client$; + } + async failedToInitialize(): Promise { // Only log on cloud instances if ( @@ -52,6 +97,15 @@ export class DefaultSdkService implements SdkService { }); } + private toSettings(env: Environment): ClientSettings { + return { + apiUrl: env.getApiUrl(), + identityUrl: env.getIdentityUrl(), + deviceType: this.toDevice(this.platformUtilsService.getDevice()), + userAgent: this.userAgent ?? navigator.userAgent, + }; + } + private toDevice(device: DeviceType): SdkDeviceType { switch (device) { case DeviceType.Android: From f7f79ba61c67fe0141bf49308f3ae028cd8a5aa1 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 8 Oct 2024 14:24:54 +0200 Subject: [PATCH 03/39] feat: initialize user crypto --- apps/desktop/src/app/app.component.ts | 24 ++++++++- .../src/services/jslib-services.module.ts | 3 +- .../auth/abstractions/kdf-config.service.ts | 7 ++- .../src/auth/services/kdf-config.service.ts | 6 ++- .../platform/abstractions/crypto.service.ts | 13 ++++- .../src/platform/services/crypto.service.ts | 4 ++ .../services/sdk/default-sdk.service.ts | 50 +++++++++++++------ 7 files changed, 87 insertions(+), 20 deletions(-) diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index d3b39218b52..2b549aedf4c 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -10,7 +10,17 @@ import { } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; -import { catchError, filter, firstValueFrom, map, of, Subject, takeUntil, timeout } from "rxjs"; +import { + catchError, + filter, + firstValueFrom, + map, + of, + Subject, + switchMap, + takeUntil, + timeout, +} from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; @@ -172,6 +182,18 @@ export class AppComponent implements OnInit, OnDestroy { this.logService.debug("SDK is supported"); } }); + + this.accountService.activeAccount$ + .pipe( + switchMap((account) => this.sdkService.userClient$(account.id)), + takeUntilDestroyed(), + ) + .subscribe({ + error: (e: unknown) => this.logService.error(e), + next: (client) => { + this.logService.info("userClient$", client); + }, + }); } } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 8829b01cf9a..6af0fe2f660 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1335,7 +1335,8 @@ const safeProviders: SafeProvider[] = [ EnvironmentService, PlatformUtilsServiceAbstraction, AccountServiceAbstraction, - TokenServiceAbstraction, + KdfConfigServiceAbstraction, + CryptoServiceAbstraction, ApiServiceAbstraction, ], }), diff --git a/libs/common/src/auth/abstractions/kdf-config.service.ts b/libs/common/src/auth/abstractions/kdf-config.service.ts index 6b41979e1b9..f4ffe31baa4 100644 --- a/libs/common/src/auth/abstractions/kdf-config.service.ts +++ b/libs/common/src/auth/abstractions/kdf-config.service.ts @@ -1,7 +1,10 @@ +import { Observable } from "rxjs"; + import { UserId } from "../../types/guid"; import { KdfConfig } from "../models/domain/kdf-config"; export abstract class KdfConfigService { - setKdfConfig: (userId: UserId, KdfConfig: KdfConfig) => Promise; - getKdfConfig: () => Promise; + abstract setKdfConfig(userId: UserId, KdfConfig: KdfConfig): Promise; + abstract getKdfConfig(): Promise; + abstract getKdfConfig$(userId: UserId): Observable; } diff --git a/libs/common/src/auth/services/kdf-config.service.ts b/libs/common/src/auth/services/kdf-config.service.ts index cfd2a3e1de0..604a186d765 100644 --- a/libs/common/src/auth/services/kdf-config.service.ts +++ b/libs/common/src/auth/services/kdf-config.service.ts @@ -1,4 +1,4 @@ -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, Observable } from "rxjs"; import { KdfType } from "../../platform/enums/kdf-type.enum"; import { KDF_CONFIG_DISK, StateProvider, UserKeyDefinition } from "../../platform/state"; @@ -38,4 +38,8 @@ export class KdfConfigService implements KdfConfigServiceAbstraction { } return state; } + + getKdfConfig$(userId: UserId): Observable { + return this.stateProvider.getUser(userId, KDF_CONFIG).state$; + } } diff --git a/libs/common/src/platform/abstractions/crypto.service.ts b/libs/common/src/platform/abstractions/crypto.service.ts index 020cfb81754..8e638e626ae 100644 --- a/libs/common/src/platform/abstractions/crypto.service.ts +++ b/libs/common/src/platform/abstractions/crypto.service.ts @@ -15,7 +15,7 @@ import { UserPublicKey, } from "../../types/key"; import { KeySuffixOptions, HashPurpose } from "../enums"; -import { EncString } from "../models/domain/enc-string"; +import { EncryptedString, EncString } from "../models/domain/enc-string"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; export class UserPrivateKeyDecryptionFailedError extends Error { @@ -288,6 +288,17 @@ export abstract class CryptoService { */ abstract userPrivateKey$(userId: UserId): Observable; + /** + * Gets an observable stream of the given users encrypted private key, will emit null if the user + * doesn't have an encrypted private key at all. + * + * @param userId The user id of the user to get the data for. + * + * @deprecated Temporary function to allow the SDK to be initialized after the login process, it + * will be removed when auth has been migrated to the SDK. + */ + abstract userEncryptedPrivateKey$(userId: UserId): Observable; + /** * Gets an observable stream of the given users decrypted private key with legacy support, * will emit null if the user doesn't have a UserKey to decrypt the encrypted private key diff --git a/libs/common/src/platform/services/crypto.service.ts b/libs/common/src/platform/services/crypto.service.ts index 6b2afdb9806..fd758fb47c2 100644 --- a/libs/common/src/platform/services/crypto.service.ts +++ b/libs/common/src/platform/services/crypto.service.ts @@ -841,6 +841,10 @@ export class CryptoService implements CryptoServiceAbstraction { return this.userPrivateKeyHelper$(userId, false).pipe(map((keys) => keys?.userPrivateKey)); } + userEncryptedPrivateKey$(userId: UserId): Observable { + return this.stateProvider.getUser(userId, USER_ENCRYPTED_PRIVATE_KEY).state$; + } + userPrivateKeyWithLegacySupport$(userId: UserId): Observable { return this.userPrivateKeyHelper$(userId, true).pipe(map((keys) => keys?.userPrivateKey)); } diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 9b3c1a8a0a8..389d1e52c13 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -4,7 +4,6 @@ import { firstValueFrom, Observable, shareReplay, - tap, map, filter, } from "rxjs"; @@ -18,13 +17,15 @@ import { import { ApiService } from "../../../abstractions/api.service"; import { AccountService } from "../../../auth/abstractions/account.service"; -import { TokenService } from "../../../auth/abstractions/token.service"; +import { KdfConfigService } from "../../../auth/abstractions/kdf-config.service"; import { DeviceType } from "../../../enums/device-type.enum"; import { UserId } from "../../../types/guid"; +import { CryptoService } from "../../abstractions/crypto.service"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; import { SdkService } from "../../abstractions/sdk/sdk.service"; +import { KdfType } from "../../enums"; export class DefaultSdkService implements SdkService { private sdkClientCache = new Map>(); @@ -35,9 +36,6 @@ export class DefaultSdkService implements SdkService { return await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); }), shareReplay({ refCount: true, bufferSize: 1 }), - tap((client) => { - (window as any).client = client; - }), ); supported$ = this.client$.pipe( @@ -51,7 +49,8 @@ export class DefaultSdkService implements SdkService { private environmentService: EnvironmentService, private platformUtilsService: PlatformUtilsService, private accountService: AccountService, - private tokenService: TokenService, + private kdfConfigService: KdfConfigService, + private cryptoService: CryptoService, private apiService: ApiService, // Yes we shouldn't import ApiService, but it's temporary private userAgent: string = null, ) {} @@ -63,17 +62,40 @@ export class DefaultSdkService implements SdkService { } const account$ = this.accountService.accounts$.pipe(map((accounts) => accounts[userId])); - const token$ = this.tokenService.hasAccessToken$(userId).pipe( - filter((hasToken) => hasToken), - concatMap(() => this.tokenService.getAccessToken(userId)), - ); - - const client$ = combineLatest([this.environmentService.environment$, account$, token$]).pipe( - concatMap(async ([env, account, token]) => { + const kdfParams$ = this.kdfConfigService.getKdfConfig$(userId); + const privateKey$ = this.cryptoService + .userEncryptedPrivateKey$(userId) + .pipe(filter((key) => key != null)); + const userKey$ = this.cryptoService.userKey$(userId).pipe(filter((key) => key != null)); + + const client$ = combineLatest([ + this.environmentService.environment$, + account$, + kdfParams$, + privateKey$, + userKey$, + ]).pipe( + concatMap(async ([env, account, kdfParams, privateKey, userKey]) => { const settings = this.toSettings(env); const client = await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); - // TODO: Init client crypto + await client.crypto().initialize_user_crypto({ + email: account.email, + method: { decryptedKey: { decrypted_user_key: userKey.keyB64 } }, + kdfParams: + kdfParams.kdfType === KdfType.PBKDF2_SHA256 + ? { + pBKDF2: { iterations: kdfParams.iterations }, + } + : { + argon2id: { + iterations: kdfParams.iterations, + memory: kdfParams.memory, + parallelism: kdfParams.parallelism, + }, + }, + privateKey, + }); return client; }), From f4a589fec4fc238b978d46209a7974442ec8d620 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 8 Oct 2024 15:06:24 +0200 Subject: [PATCH 04/39] feat: initialize org keys --- .../src/platform/services/sdk/default-sdk.service.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 389d1e52c13..caa33a75a10 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -67,6 +67,7 @@ export class DefaultSdkService implements SdkService { .userEncryptedPrivateKey$(userId) .pipe(filter((key) => key != null)); const userKey$ = this.cryptoService.userKey$(userId).pipe(filter((key) => key != null)); + const orgKeys$ = this.cryptoService.orgKeys$(userId).pipe(filter((keys) => keys != null)); const client$ = combineLatest([ this.environmentService.environment$, @@ -74,8 +75,9 @@ export class DefaultSdkService implements SdkService { kdfParams$, privateKey$, userKey$, + orgKeys$, ]).pipe( - concatMap(async ([env, account, kdfParams, privateKey, userKey]) => { + concatMap(async ([env, account, kdfParams, privateKey, userKey, orgKeys]) => { const settings = this.toSettings(env); const client = await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); @@ -97,6 +99,10 @@ export class DefaultSdkService implements SdkService { privateKey, }); + await client.crypto().initialize_org_crypto({ + organizationKeys: new Map(Object.entries(orgKeys).map(([k, v]) => [k, v.keyB64])), + }); + return client; }), ); From d7e82fcc8f4352eb86e227f246a7ea18786c5001 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 9 Oct 2024 14:34:21 +0200 Subject: [PATCH 05/39] fix: org crypto not initializing properly --- .../src/platform/abstractions/crypto.service.ts | 13 +++++++++++++ libs/common/src/platform/services/crypto.service.ts | 6 ++++++ .../platform/services/sdk/default-sdk.service.ts | 11 +++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/libs/common/src/platform/abstractions/crypto.service.ts b/libs/common/src/platform/abstractions/crypto.service.ts index 8e638e626ae..0a554f6249b 100644 --- a/libs/common/src/platform/abstractions/crypto.service.ts +++ b/libs/common/src/platform/abstractions/crypto.service.ts @@ -1,5 +1,6 @@ import { Observable } from "rxjs"; +import { EncryptedOrganizationKeyData } from "../../admin-console/models/data/encrypted-organization-key.data"; import { ProfileOrganizationResponse } from "../../admin-console/models/response/profile-organization.response"; import { ProfileProviderOrganizationResponse } from "../../admin-console/models/response/profile-provider-organization.response"; import { ProfileProviderResponse } from "../../admin-console/models/response/profile-provider.response"; @@ -392,6 +393,18 @@ export abstract class CryptoService { */ abstract orgKeys$(userId: UserId): Observable | null>; + /** + * Gets an observable stream of the given users encrypted organisation keys. + * + * @param userId The user id of the user to get the data for. + * + * @deprecated Temporary function to allow the SDK to be initialized after the login process, it + * will be removed when auth has been migrated to the SDK. + */ + abstract encryptedOrgKeys$( + userId: UserId, + ): Observable>; + /** * Gets an observable stream of the users public key. If the user is does not have * a {@link UserKey} or {@link UserPrivateKey} that is decryptable, this will emit null. diff --git a/libs/common/src/platform/services/crypto.service.ts b/libs/common/src/platform/services/crypto.service.ts index fd758fb47c2..a6db9a2c1bf 100644 --- a/libs/common/src/platform/services/crypto.service.ts +++ b/libs/common/src/platform/services/crypto.service.ts @@ -933,6 +933,12 @@ export class CryptoService implements CryptoServiceAbstraction { return this.cipherDecryptionKeys$(userId, true).pipe(map((keys) => keys?.orgKeys)); } + encryptedOrgKeys$( + userId: UserId, + ): Observable> { + return this.stateProvider.getUser(userId, USER_ENCRYPTED_ORGANIZATION_KEYS).state$; + } + cipherDecryptionKeys$( userId: UserId, legacySupport: boolean = false, diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index caa33a75a10..42c22dfe313 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -67,7 +67,9 @@ export class DefaultSdkService implements SdkService { .userEncryptedPrivateKey$(userId) .pipe(filter((key) => key != null)); const userKey$ = this.cryptoService.userKey$(userId).pipe(filter((key) => key != null)); - const orgKeys$ = this.cryptoService.orgKeys$(userId).pipe(filter((keys) => keys != null)); + const orgKeys$ = this.cryptoService + .encryptedOrgKeys$(userId) + .pipe(filter((keys) => keys != null)); const client$ = combineLatest([ this.environmentService.environment$, @@ -100,11 +102,16 @@ export class DefaultSdkService implements SdkService { }); await client.crypto().initialize_org_crypto({ - organizationKeys: new Map(Object.entries(orgKeys).map(([k, v]) => [k, v.keyB64])), + organizationKeys: new Map( + Object.entries(orgKeys) + .filter(([_, v]) => v.type === "organization") + .map(([k, v]) => [k, v.key]), + ), }); return client; }), + shareReplay({ refCount: true, bufferSize: 1 }), ); this.sdkClientCache.set(userId, client$); From cc8302a063a01dc25592a1ccdad4cefa3700ecab Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 9 Oct 2024 15:50:06 +0200 Subject: [PATCH 06/39] feat: avoid creating clients unnecessarily --- .../platform/abstractions/sdk/sdk.service.ts | 2 +- .../services/sdk/default-sdk.service.ts | 26 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts index 90a153ab579..5e4e4cb4cbe 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk.service.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -14,7 +14,7 @@ export abstract class SdkService { * Retrieve a client initialized without a user. * This client can only be used for operations that don't require a user context. */ - client$: Observable; + client$: Observable; /** * Retrieve a client initialized for a specific user. diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 42c22dfe313..2913dc51a8c 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -5,7 +5,7 @@ import { Observable, shareReplay, map, - filter, + distinctUntilChanged, } from "rxjs"; import { @@ -26,6 +26,7 @@ import { PlatformUtilsService } from "../../abstractions/platform-utils.service" import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; import { SdkService } from "../../abstractions/sdk/sdk.service"; import { KdfType } from "../../enums"; +import { compareValues } from "../../misc/compare-values"; export class DefaultSdkService implements SdkService { private sdkClientCache = new Map>(); @@ -55,21 +56,24 @@ export class DefaultSdkService implements SdkService { private userAgent: string = null, ) {} - userClient$(userId: UserId): Observable { + userClient$(userId: UserId): Observable { // TODO: Figure out what happens when the user logs out if (this.sdkClientCache.has(userId)) { return this.sdkClientCache.get(userId); } - const account$ = this.accountService.accounts$.pipe(map((accounts) => accounts[userId])); - const kdfParams$ = this.kdfConfigService.getKdfConfig$(userId); + const account$ = this.accountService.accounts$.pipe( + map((accounts) => accounts[userId]), + distinctUntilChanged(), + ); + const kdfParams$ = this.kdfConfigService.getKdfConfig$(userId).pipe(distinctUntilChanged()); const privateKey$ = this.cryptoService .userEncryptedPrivateKey$(userId) - .pipe(filter((key) => key != null)); - const userKey$ = this.cryptoService.userKey$(userId).pipe(filter((key) => key != null)); - const orgKeys$ = this.cryptoService - .encryptedOrgKeys$(userId) - .pipe(filter((keys) => keys != null)); + .pipe(distinctUntilChanged()); + const userKey$ = this.cryptoService.userKey$(userId).pipe(distinctUntilChanged()); + const orgKeys$ = this.cryptoService.encryptedOrgKeys$(userId).pipe( + distinctUntilChanged(compareValues), // The upstream observable emits different objects with the same values + ); const client$ = combineLatest([ this.environmentService.environment$, @@ -80,6 +84,10 @@ export class DefaultSdkService implements SdkService { orgKeys$, ]).pipe( concatMap(async ([env, account, kdfParams, privateKey, userKey, orgKeys]) => { + if (privateKey == null || userKey == null || orgKeys == null) { + return undefined; + } + const settings = this.toSettings(env); const client = await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); From 421f3725b02b5717fa48f2a0fdccdad4ec43059b Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 9 Oct 2024 15:50:41 +0200 Subject: [PATCH 07/39] chore: remove dev print/subscription --- apps/desktop/src/app/app.component.ts | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 2b549aedf4c..d3b39218b52 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -10,17 +10,7 @@ import { } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; -import { - catchError, - filter, - firstValueFrom, - map, - of, - Subject, - switchMap, - takeUntil, - timeout, -} from "rxjs"; +import { catchError, filter, firstValueFrom, map, of, Subject, takeUntil, timeout } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; @@ -182,18 +172,6 @@ export class AppComponent implements OnInit, OnDestroy { this.logService.debug("SDK is supported"); } }); - - this.accountService.activeAccount$ - .pipe( - switchMap((account) => this.sdkService.userClient$(account.id)), - takeUntilDestroyed(), - ) - .subscribe({ - error: (e: unknown) => this.logService.error(e), - next: (client) => { - this.logService.info("userClient$", client); - }, - }); } } From f95cfe54228c767057c0478c8dfb76589eabc47e Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 9 Oct 2024 16:47:38 +0200 Subject: [PATCH 08/39] fix: clean up cache --- .../src/platform/services/sdk/default-sdk.service.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 2913dc51a8c..dad264010b5 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -6,6 +6,7 @@ import { shareReplay, map, distinctUntilChanged, + tap, } from "rxjs"; import { @@ -119,6 +120,13 @@ export class DefaultSdkService implements SdkService { return client; }), + tap({ + finalize: () => { + if (this.sdkClientCache.has(userId)) { + this.sdkClientCache.delete(userId); + } + }, + }), shareReplay({ refCount: true, bufferSize: 1 }), ); From 8e0ef471e865b9cb1da5c5acd6ed78615b352f15 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 14 Oct 2024 15:31:40 +0200 Subject: [PATCH 09/39] chore: update sdk version --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index f8971e8ac66..f7b0e3eac01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.3", + "@bitwarden/sdk-internal": "0.1.4", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1", @@ -4696,10 +4696,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.3.tgz", - "integrity": "sha512-zk9DyYMjylVLdljeLn3OLBcD939Hg/qMNJ2FxbyjiSKtcOcgglXgYmbcS01NRFFfM9REbn+j+2fWbQo6N+8SHw==", - "license": "SEE LICENSE IN LICENSE" + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.4.tgz", + "integrity": "sha512-gwf/P6V6H4o8SV9uhJCGp4rLE9txE8mxCHwoRxk2Oq5Qd32R0CkoA4/BMxx9ho5+I/ALdju7KuMlAmVPgjDO1Q==" }, "node_modules/@bitwarden/vault": { "resolved": "libs/vault", diff --git a/package.json b/package.json index 830288f45de..c88bb321696 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.3", + "@bitwarden/sdk-internal": "0.1.4", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1", From 7e7a10a4c6c5758cc9bb29e9b8ef7e2d5d0c1d58 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 14 Oct 2024 15:38:08 +0200 Subject: [PATCH 10/39] feat: implement clean-up logic (#11504) --- .../services/sdk/default-sdk.service.spec.ts | 131 ++++++++++++++++++ .../services/sdk/default-sdk.service.ts | 106 +++++++++----- 2 files changed, 201 insertions(+), 36 deletions(-) create mode 100644 libs/common/src/platform/services/sdk/default-sdk.service.spec.ts diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts new file mode 100644 index 00000000000..9bbd04cda43 --- /dev/null +++ b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts @@ -0,0 +1,131 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject, firstValueFrom, of } from "rxjs"; + +import { BitwardenClient } from "@bitwarden/sdk-internal"; + +import { ApiService } from "../../../abstractions/api.service"; +import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service"; +import { KdfConfigService } from "../../../auth/abstractions/kdf-config.service"; +import { PBKDF2KdfConfig } from "../../../auth/models/domain/kdf-config"; +import { UserId } from "../../../types/guid"; +import { UserKey } from "../../../types/key"; +import { CryptoService } from "../../abstractions/crypto.service"; +import { Environment, EnvironmentService } from "../../abstractions/environment.service"; +import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; +import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; +import { EncryptedString } from "../../models/domain/enc-string"; +import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; + +import { DefaultSdkService } from "./default-sdk.service"; + +describe("DefaultSdkService", () => { + describe("userClient$", () => { + let sdkClientFactory!: MockProxy; + let environmentService!: MockProxy; + let platformUtilsService!: MockProxy; + let accountService!: MockProxy; + let kdfConfigService!: MockProxy; + let cryptoService!: MockProxy; + let apiService!: MockProxy; + let service!: DefaultSdkService; + + let mockClient!: MockProxy; + + beforeEach(() => { + sdkClientFactory = mock(); + environmentService = mock(); + platformUtilsService = mock(); + accountService = mock(); + kdfConfigService = mock(); + cryptoService = mock(); + apiService = mock(); + + // Can't use `of(mock())` for some reason + environmentService.environment$ = new BehaviorSubject(mock()); + + service = new DefaultSdkService( + sdkClientFactory, + environmentService, + platformUtilsService, + accountService, + kdfConfigService, + cryptoService, + apiService, + ); + + mockClient = mock(); + mockClient.crypto.mockReturnValue(mock()); + sdkClientFactory.createSdkClient.mockResolvedValue(mockClient); + }); + + describe("given the user is logged in", () => { + const userId = "user-id" as UserId; + + beforeEach(() => { + accountService.accounts$ = of({ + [userId]: { email: "email", emailVerified: true, name: "name" } as AccountInfo, + }); + kdfConfigService.getKdfConfig$ + .calledWith(userId) + .mockReturnValue(of(new PBKDF2KdfConfig())); + cryptoService.userKey$ + .calledWith(userId) + .mockReturnValue(of(new SymmetricCryptoKey(new Uint8Array(64)) as UserKey)); + cryptoService.userEncryptedPrivateKey$ + .calledWith(userId) + .mockReturnValue(of("private-key" as EncryptedString)); + cryptoService.encryptedOrgKeys$.calledWith(userId).mockReturnValue(of({})); + }); + + it("creates an SDK client when called the first time", async () => { + const result = await firstValueFrom(service.userClient$(userId)); + + expect(result).toBe(mockClient); + expect(sdkClientFactory.createSdkClient).toHaveBeenCalled(); + }); + + it("does not create an SDK client when called the second time with same userId", async () => { + const subject_1 = new BehaviorSubject(undefined); + const subject_2 = new BehaviorSubject(undefined); + + // Use subjects to ensure the subscription is kept alive + service.userClient$(userId).subscribe(subject_1); + service.userClient$(userId).subscribe(subject_2); + + // Wait for the next tick to ensure all async operations are done + await new Promise(process.nextTick); + + expect(subject_1.value).toBe(mockClient); + expect(subject_2.value).toBe(mockClient); + expect(sdkClientFactory.createSdkClient).toHaveBeenCalledTimes(1); + }); + + it("destroys the SDK client when all subscriptions are closed", async () => { + const subject_1 = new BehaviorSubject(undefined); + const subject_2 = new BehaviorSubject(undefined); + const subscription_1 = service.userClient$(userId).subscribe(subject_1); + const subscription_2 = service.userClient$(userId).subscribe(subject_2); + await new Promise(process.nextTick); + + subscription_1.unsubscribe(); + subscription_2.unsubscribe(); + + expect(mockClient.free).toHaveBeenCalledTimes(1); + }); + + it("destroys the SDK client when the userKey is unset (i.e. lock or logout)", async () => { + const userKey$ = new BehaviorSubject(new SymmetricCryptoKey(new Uint8Array(64)) as UserKey); + cryptoService.userKey$.calledWith(userId).mockReturnValue(userKey$); + + const subject = new BehaviorSubject(undefined); + service.userClient$(userId).subscribe(subject); + await new Promise(process.nextTick); + + userKey$.next(undefined); + + expect(mockClient.free).toHaveBeenCalledTimes(1); + expect(subject.value).toBeUndefined(); + }); + }); + }); +}); diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index dad264010b5..9ea956a7000 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -7,6 +7,7 @@ import { map, distinctUntilChanged, tap, + switchMap, } from "rxjs"; import { @@ -17,10 +18,13 @@ import { } from "@bitwarden/sdk-internal"; import { ApiService } from "../../../abstractions/api.service"; -import { AccountService } from "../../../auth/abstractions/account.service"; +import { EncryptedOrganizationKeyData } from "../../../admin-console/models/data/encrypted-organization-key.data"; +import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service"; import { KdfConfigService } from "../../../auth/abstractions/kdf-config.service"; +import { KdfConfig } from "../../../auth/models/domain/kdf-config"; import { DeviceType } from "../../../enums/device-type.enum"; -import { UserId } from "../../../types/guid"; +import { OrganizationId, UserId } from "../../../types/guid"; +import { UserKey } from "../../../types/key"; import { CryptoService } from "../../abstractions/crypto.service"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; @@ -28,6 +32,7 @@ import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; import { SdkService } from "../../abstractions/sdk/sdk.service"; import { KdfType } from "../../enums"; import { compareValues } from "../../misc/compare-values"; +import { EncryptedString } from "../../models/domain/enc-string"; export class DefaultSdkService implements SdkService { private sdkClientCache = new Map>(); @@ -84,41 +89,36 @@ export class DefaultSdkService implements SdkService { userKey$, orgKeys$, ]).pipe( - concatMap(async ([env, account, kdfParams, privateKey, userKey, orgKeys]) => { - if (privateKey == null || userKey == null || orgKeys == null) { - return undefined; - } - - const settings = this.toSettings(env); - const client = await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); - - await client.crypto().initialize_user_crypto({ - email: account.email, - method: { decryptedKey: { decrypted_user_key: userKey.keyB64 } }, - kdfParams: - kdfParams.kdfType === KdfType.PBKDF2_SHA256 - ? { - pBKDF2: { iterations: kdfParams.iterations }, - } - : { - argon2id: { - iterations: kdfParams.iterations, - memory: kdfParams.memory, - parallelism: kdfParams.parallelism, - }, - }, - privateKey, + // switchMap is required to allow the clean-up logic to be executed when `combineLatest` emits a new value. + switchMap(([env, account, kdfParams, privateKey, userKey, orgKeys]) => { + // Create our own observable to be able to implement clean-up logic + return new Observable((subscriber) => { + let client: BitwardenClient; + + void (async (): Promise => { + if (privateKey == null || userKey == null || orgKeys == null) { + subscriber.next(undefined); + return; + } + + try { + const settings = this.toSettings(env); + client = await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); + + await this.initializeClient(client, account, kdfParams, privateKey, userKey, orgKeys); + + subscriber.next(client); + } catch (error) { + subscriber.error(error); + } + })(); + + return () => { + if (client != null) { + client.free(); + } + }; }); - - await client.crypto().initialize_org_crypto({ - organizationKeys: new Map( - Object.entries(orgKeys) - .filter(([_, v]) => v.type === "organization") - .map(([k, v]) => [k, v.key]), - ), - }); - - return client; }), tap({ finalize: () => { @@ -148,6 +148,40 @@ export class DefaultSdkService implements SdkService { }); } + private async initializeClient( + client: BitwardenClient, + account: AccountInfo, + kdfParams: KdfConfig, + privateKey: EncryptedString, + userKey: UserKey, + orgKeys: Record, + ) { + await client.crypto().initialize_user_crypto({ + email: account.email, + method: { decryptedKey: { decrypted_user_key: userKey.keyB64 } }, + kdfParams: + kdfParams.kdfType === KdfType.PBKDF2_SHA256 + ? { + pBKDF2: { iterations: kdfParams.iterations }, + } + : { + argon2id: { + iterations: kdfParams.iterations, + memory: kdfParams.memory, + parallelism: kdfParams.parallelism, + }, + }, + privateKey, + }); + await client.crypto().initialize_org_crypto({ + organizationKeys: new Map( + Object.entries(orgKeys) + .filter(([_, v]) => v.type === "organization") + .map(([k, v]) => [k, v.key]), + ), + }); + } + private toSettings(env: Environment): ClientSettings { return { apiUrl: env.getApiUrl(), From 1ed18e56ae2a1d02e906e7032c1c8a42064c8765 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 15 Oct 2024 08:12:59 +0200 Subject: [PATCH 11/39] chore: bump sdk version to fix build issues --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f7b0e3eac01..b559d852a60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.4", + "@bitwarden/sdk-internal": "0.1.5", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1", @@ -4696,9 +4696,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.4.tgz", - "integrity": "sha512-gwf/P6V6H4o8SV9uhJCGp4rLE9txE8mxCHwoRxk2Oq5Qd32R0CkoA4/BMxx9ho5+I/ALdju7KuMlAmVPgjDO1Q==" + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.5.tgz", + "integrity": "sha512-txl/94EXDbH2REiiaqway3UDbBiRT2+YSDHhQSxQxYgNhy10m0koAS/133Zej1ZlxY3fe2JxgnjnOgxue1+dvg==" }, "node_modules/@bitwarden/vault": { "resolved": "libs/vault", diff --git a/package.json b/package.json index c88bb321696..6321c17aaa1 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.4", + "@bitwarden/sdk-internal": "0.1.5", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1", From e8a1049a6dbdd5b59daf3a3fced5f1e6e167b42b Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 15 Oct 2024 08:36:48 +0200 Subject: [PATCH 12/39] chore: bump sdk version to fix build issues --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index b559d852a60..e1409f82f83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.5", + "@bitwarden/sdk-internal": "0.1.6", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1", @@ -4696,9 +4696,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.5.tgz", - "integrity": "sha512-txl/94EXDbH2REiiaqway3UDbBiRT2+YSDHhQSxQxYgNhy10m0koAS/133Zej1ZlxY3fe2JxgnjnOgxue1+dvg==" + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.6.tgz", + "integrity": "sha512-YUOOcXnK004mAwE+vfy7AgeLYCtTyafYaXEWED3PNRaSun/a5elrAD//h2yuF9u8Dn5jg1VDkssMPpuG9+2VxA==" }, "node_modules/@bitwarden/vault": { "resolved": "libs/vault", diff --git a/package.json b/package.json index 6321c17aaa1..92c3f860631 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.5", + "@bitwarden/sdk-internal": "0.1.6", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1", From b9a729ba1d5538e13bfed7ac0ca291585df95209 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 15 Oct 2024 08:44:26 +0200 Subject: [PATCH 13/39] fix: missing constructor parameters --- apps/browser/src/background/main.background.ts | 3 +++ apps/cli/src/service-container/service-container.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 318b856b324..f49a88d72e8 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -731,6 +731,9 @@ export default class MainBackground { sdkClientFactory, this.environmentService, this.platformUtilsService, + this.accountService, + this.kdfConfigService, + this.cryptoService, this.apiService, ); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 7643683221a..cb3bf25f9a5 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -535,6 +535,9 @@ export class ServiceContainer { sdkClientFactory, this.environmentService, this.platformUtilsService, + this.accountService, + this.kdfConfigService, + this.cryptoService, this.apiService, customUserAgent, ); From ac89518127beef10c2e1dd94bf8ff59c48c6600b Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 16 Oct 2024 10:30:38 +0200 Subject: [PATCH 14/39] refactor: simplify free() and delete() calls --- .../src/platform/services/sdk/default-sdk.service.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 9ea956a7000..46eb0f8ba38 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -113,19 +113,11 @@ export class DefaultSdkService implements SdkService { } })(); - return () => { - if (client != null) { - client.free(); - } - }; + return () => client?.free(); }); }), tap({ - finalize: () => { - if (this.sdkClientCache.has(userId)) { - this.sdkClientCache.delete(userId); - } - }, + finalize: () => this.sdkClientCache.delete(userId), }), shareReplay({ refCount: true, bufferSize: 1 }), ); From 3adc18ead9e96544ac33684dcfc964b499edcd48 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 16 Oct 2024 10:36:21 +0200 Subject: [PATCH 15/39] refactor: use a named function for client creation --- .../services/sdk/default-sdk.service.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 46eb0f8ba38..5096ef7c9fd 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -95,23 +95,25 @@ export class DefaultSdkService implements SdkService { return new Observable((subscriber) => { let client: BitwardenClient; - void (async (): Promise => { + async function createAndInitializeClient() { if (privateKey == null || userKey == null || orgKeys == null) { subscriber.next(undefined); return; } - try { - const settings = this.toSettings(env); - client = await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); + const settings = this.toSettings(env); + client = await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); - await this.initializeClient(client, account, kdfParams, privateKey, userKey, orgKeys); + await this.initializeClient(client, account, kdfParams, privateKey, userKey, orgKeys); - subscriber.next(client); - } catch (error) { - subscriber.error(error); - } - })(); + return client; + } + + createAndInitializeClient() + .then((c) => subscriber.next(c)) + .catch((e) => { + subscriber.error(e); + }); return () => client?.free(); }); From 6b7b016f5db782c87ac4ad842a16435ea55a031b Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 16 Oct 2024 10:38:08 +0200 Subject: [PATCH 16/39] fix: client never freeing after refactor --- libs/common/src/platform/services/sdk/default-sdk.service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 5096ef7c9fd..5fcc4bf1111 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -110,7 +110,10 @@ export class DefaultSdkService implements SdkService { } createAndInitializeClient() - .then((c) => subscriber.next(c)) + .then((c) => { + client = c; + subscriber.next(c); + }) .catch((e) => { subscriber.error(e); }); From 85d4809c31eb5105eb060784905d51c0ee814411 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 16 Oct 2024 16:21:21 +0200 Subject: [PATCH 17/39] fix: broken impl and race condition in tests --- .../src/platform/services/sdk/default-sdk.service.spec.ts | 3 ++- .../src/platform/services/sdk/default-sdk.service.ts | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts index 9bbd04cda43..dad99401f75 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts @@ -122,9 +122,10 @@ describe("DefaultSdkService", () => { await new Promise(process.nextTick); userKey$.next(undefined); + await new Promise(process.nextTick); expect(mockClient.free).toHaveBeenCalledTimes(1); - expect(subject.value).toBeUndefined(); + expect(subject.value).toBe(undefined); }); }); }); diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 5fcc4bf1111..1b7a9a939a4 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -95,10 +95,9 @@ export class DefaultSdkService implements SdkService { return new Observable((subscriber) => { let client: BitwardenClient; - async function createAndInitializeClient() { + const createAndInitializeClient = async () => { if (privateKey == null || userKey == null || orgKeys == null) { - subscriber.next(undefined); - return; + return undefined; } const settings = this.toSettings(env); @@ -107,7 +106,7 @@ export class DefaultSdkService implements SdkService { await this.initializeClient(client, account, kdfParams, privateKey, userKey, orgKeys); return client; - } + }; createAndInitializeClient() .then((c) => { From 276da3ec90895f3a9663998ea91e2d291baf61a5 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 17 Oct 2024 13:55:15 +0200 Subject: [PATCH 18/39] feat: add sdk override to desktop build --- .github/workflows/build-desktop.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 5022184bd05..df759a0dfee 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -26,7 +26,11 @@ on: - '!*.txt' - '.github/workflows/build-desktop.yml' workflow_dispatch: - inputs: {} + inputs: + sdk_branch: + description: "Custom SDK branch to use for the build" + required: false + type: string defaults: run: @@ -69,6 +73,26 @@ jobs: - name: Checkout repo uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk + path: ../sdk-internal + if_no_artifact_found: fail + + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Get Package Version id: retrieve-version run: | From 5e1647c3a9a0a7cfc68bc49f8a3181e23851e9f3 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 21 Oct 2024 09:27:33 +0200 Subject: [PATCH 19/39] feat: add SDK version to browser about dialog --- .../popup/settings/about-dialog/about-dialog.component.html | 1 + .../popup/settings/about-dialog/about-dialog.component.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html index bad39a53d31..eceeaf19cb7 100644 --- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html +++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html @@ -6,6 +6,7 @@

© Bitwarden Inc. 2015-{{ year }}

{{ "version" | i18n }}: {{ version$ | async }}

+

SDK: {{ sdkVersion$ | async }}

{{ "serverVersion" | i18n }}: {{ data.serverConfig?.version }} diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts index 0467debdfb5..8969d4a1f9a 100644 --- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts +++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts @@ -6,6 +6,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { ButtonModule, DialogModule } from "@bitwarden/components"; @Component({ @@ -22,10 +23,13 @@ export class AboutDialogComponent { this.environmentService.environment$.pipe(map((env) => env.isCloud())), ]).pipe(map(([serverConfig, isCloud]) => ({ serverConfig, isCloud }))); + protected sdkVersion$ = this.sdkService.client$.pipe(map((c) => c.version())); + constructor( private configService: ConfigService, private environmentService: EnvironmentService, private platformUtilsService: PlatformUtilsService, + private sdkService: SdkService, ) { this.version$ = defer(() => this.platformUtilsService.getApplicationVersion()); } From 4045cd8fdd8d8f06f855e3ab3c96ead223e6cafc Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 21 Oct 2024 15:08:48 +0200 Subject: [PATCH 20/39] feat: add sdk override to browser build --- .github/workflows/build-browser.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 096fff8db34..4db057d675d 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -27,7 +27,11 @@ on: workflow_call: inputs: {} workflow_dispatch: - inputs: {} + inputs: + sdk_branch: + description: "Custom SDK branch to use for the build" + required: false + type: string defaults: run: @@ -45,6 +49,26 @@ jobs: - name: Checkout repo uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk + path: ../sdk-internal + if_no_artifact_found: fail + + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Get Package Version id: gen_vars run: | From f5af7df3218601d8234329036f089e8603c264d8 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 21 Oct 2024 15:24:23 +0200 Subject: [PATCH 21/39] fix: `npm ci` overriding the override --- .github/workflows/build-browser.yml | 21 ++++++++----- .github/workflows/build-desktop.yml | 49 ++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 4db057d675d..88fc708a5f5 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -62,13 +62,6 @@ jobs: path: ../sdk-internal if_no_artifact_found: fail - - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} - working-directory: ./ - run: | - ls -l ../ - npm link ../sdk-internal - - name: Get Package Version id: gen_vars run: | @@ -180,6 +173,13 @@ jobs: run: npm ci working-directory: browser-source/ + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Build run: npm run dist working-directory: browser-source/apps/browser @@ -352,6 +352,13 @@ jobs: run: npm ci working-directory: ./ + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Build Safari extension run: npm run dist:safari working-directory: apps/browser diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index df759a0dfee..613f023ebc1 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -86,13 +86,6 @@ jobs: path: ../sdk-internal if_no_artifact_found: fail - - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} - working-directory: ./ - run: | - ls -l ../ - npm link ../sdk-internal - - name: Get Package Version id: retrieve-version run: | @@ -192,6 +185,13 @@ jobs: run: npm ci working-directory: ./ + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Cache Native Module uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache @@ -322,6 +322,13 @@ jobs: run: npm ci working-directory: ./ + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Cache Native Module uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache @@ -607,6 +614,13 @@ jobs: run: npm ci working-directory: ./ + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Cache Native Module uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache @@ -773,6 +787,13 @@ jobs: run: npm ci working-directory: ./ + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Cache Native Module uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache @@ -999,6 +1020,13 @@ jobs: run: npm ci working-directory: ./ + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Cache Native Module uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache @@ -1234,6 +1262,13 @@ jobs: run: npm ci working-directory: ./ + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Cache Native Module uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache From c0175b40ce6b1c9340bfab4565045301e73447ed Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 21 Oct 2024 15:29:34 +0200 Subject: [PATCH 22/39] fix: artifacts not properly downloaded --- .github/workflows/build-browser.yml | 47 +++++++++------ .github/workflows/build-desktop.yml | 91 ++++++++++++++++++++++++----- 2 files changed, 107 insertions(+), 31 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 88fc708a5f5..a83ee379042 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -29,7 +29,7 @@ on: workflow_dispatch: inputs: sdk_branch: - description: "Custom SDK branch to use for the build" + description: "Custom SDK branch" required: false type: string @@ -49,19 +49,6 @@ jobs: - name: Checkout repo uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - github_token: ${{secrets.GITHUB_TOKEN}} - workflow: build-wasm-internal.yml - workflow_conclusion: success - branch: ${{ inputs.sdk_branch }} - artifacts: sdk-internal - repo: bitwarden/sdk - path: ../sdk-internal - if_no_artifact_found: fail - - name: Get Package Version id: gen_vars run: | @@ -173,11 +160,23 @@ jobs: run: npm ci working-directory: browser-source/ + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' || inputs.sdk_commit != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk + path: sdk-internal + if_no_artifact_found: fail + - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} - working-directory: ./ + if: ${{ inputs.sdk_branch != '' || inputs.sdk_commit != '' }} + working-directory: browser-source/ run: | - ls -l ../ npm link ../sdk-internal - name: Build @@ -352,11 +351,23 @@ jobs: run: npm ci working-directory: ./ + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk + path: ../sdk-internal + if_no_artifact_found: fail + - name: Override SDK if: ${{ inputs.sdk_branch != '' }} working-directory: ./ run: | - ls -l ../ npm link ../sdk-internal - name: Build Safari extension diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 613f023ebc1..e36492aa172 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -73,19 +73,6 @@ jobs: - name: Checkout repo uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - github_token: ${{secrets.GITHUB_TOKEN}} - workflow: build-wasm-internal.yml - workflow_conclusion: success - branch: ${{ inputs.sdk_branch }} - artifacts: sdk-internal - repo: bitwarden/sdk - path: ../sdk-internal - if_no_artifact_found: fail - - name: Get Package Version id: retrieve-version run: | @@ -185,6 +172,19 @@ jobs: run: npm ci working-directory: ./ + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk + path: ../sdk-internal + if_no_artifact_found: fail + - name: Override SDK if: ${{ inputs.sdk_branch != '' }} working-directory: ./ @@ -322,6 +322,19 @@ jobs: run: npm ci working-directory: ./ + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk + path: ../sdk-internal + if_no_artifact_found: fail + - name: Override SDK if: ${{ inputs.sdk_branch != '' }} working-directory: ./ @@ -614,6 +627,19 @@ jobs: run: npm ci working-directory: ./ + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk + path: ../sdk-internal + if_no_artifact_found: fail + - name: Override SDK if: ${{ inputs.sdk_branch != '' }} working-directory: ./ @@ -787,6 +813,19 @@ jobs: run: npm ci working-directory: ./ + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk + path: ../sdk-internal + if_no_artifact_found: fail + - name: Override SDK if: ${{ inputs.sdk_branch != '' }} working-directory: ./ @@ -1020,6 +1059,19 @@ jobs: run: npm ci working-directory: ./ + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk + path: ../sdk-internal + if_no_artifact_found: fail + - name: Override SDK if: ${{ inputs.sdk_branch != '' }} working-directory: ./ @@ -1262,6 +1314,19 @@ jobs: run: npm ci working-directory: ./ + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk + path: ../sdk-internal + if_no_artifact_found: fail + - name: Override SDK if: ${{ inputs.sdk_branch != '' }} working-directory: ./ From f3895a89c50152da6ec5420e988433d1e9abb313 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 22 Oct 2024 13:09:10 +0200 Subject: [PATCH 23/39] fix: switch to new repository --- .github/workflows/build-browser.yml | 4 ++-- .github/workflows/build-desktop.yml | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index a83ee379042..3a05f615656 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -169,7 +169,7 @@ jobs: workflow_conclusion: success branch: ${{ inputs.sdk_branch }} artifacts: sdk-internal - repo: bitwarden/sdk + repo: bitwarden/sdk-internal path: sdk-internal if_no_artifact_found: fail @@ -360,7 +360,7 @@ jobs: workflow_conclusion: success branch: ${{ inputs.sdk_branch }} artifacts: sdk-internal - repo: bitwarden/sdk + repo: bitwarden/sdk-internal path: ../sdk-internal if_no_artifact_found: fail diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index e36492aa172..e0144260a70 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -181,7 +181,7 @@ jobs: workflow_conclusion: success branch: ${{ inputs.sdk_branch }} artifacts: sdk-internal - repo: bitwarden/sdk + repo: bitwarden/sdk-internal path: ../sdk-internal if_no_artifact_found: fail @@ -331,7 +331,7 @@ jobs: workflow_conclusion: success branch: ${{ inputs.sdk_branch }} artifacts: sdk-internal - repo: bitwarden/sdk + repo: bitwarden/sdk-internal path: ../sdk-internal if_no_artifact_found: fail @@ -636,7 +636,7 @@ jobs: workflow_conclusion: success branch: ${{ inputs.sdk_branch }} artifacts: sdk-internal - repo: bitwarden/sdk + repo: bitwarden/sdk-internal path: ../sdk-internal if_no_artifact_found: fail @@ -822,7 +822,7 @@ jobs: workflow_conclusion: success branch: ${{ inputs.sdk_branch }} artifacts: sdk-internal - repo: bitwarden/sdk + repo: bitwarden/sdk-internal path: ../sdk-internal if_no_artifact_found: fail @@ -1068,7 +1068,7 @@ jobs: workflow_conclusion: success branch: ${{ inputs.sdk_branch }} artifacts: sdk-internal - repo: bitwarden/sdk + repo: bitwarden/sdk-internal path: ../sdk-internal if_no_artifact_found: fail @@ -1323,7 +1323,7 @@ jobs: workflow_conclusion: success branch: ${{ inputs.sdk_branch }} artifacts: sdk-internal - repo: bitwarden/sdk + repo: bitwarden/sdk-internal path: ../sdk-internal if_no_artifact_found: fail From dfa70f9a10f12644084896b180853a27f9d2956c Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 23 Oct 2024 08:37:44 +0200 Subject: [PATCH 24/39] feat: add debug version function to web --- apps/web/src/app/core/init.service.ts | 5 ++++ apps/web/src/app/platform/version.service.ts | 29 ++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 apps/web/src/app/platform/version.service.ts diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 55dc1544ffd..3cece036131 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -17,6 +17,8 @@ import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/us import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; +import { VersionService } from "../platform/version.service"; + @Injectable() export class InitService { constructor( @@ -32,6 +34,7 @@ export class InitService { private encryptService: EncryptService, private userAutoUnlockKeyService: UserAutoUnlockKeyService, private accountService: AccountService, + private versionService: VersionService, @Inject(DOCUMENT) private document: Document, ) {} @@ -56,6 +59,8 @@ export class InitService { this.themingService.applyThemeChangesTo(this.document); const containerService = new ContainerService(this.cryptoService, this.encryptService); containerService.attachToGlobal(this.win); + + this.versionService.init(); }; } } diff --git a/apps/web/src/app/platform/version.service.ts b/apps/web/src/app/platform/version.service.ts new file mode 100644 index 00000000000..d8158853184 --- /dev/null +++ b/apps/web/src/app/platform/version.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; + +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; + +type Version = { + client: string; + sdk: string; +}; + +@Injectable({ + providedIn: "root", +}) +export class VersionService { + constructor( + private platformUtilsService: PlatformUtilsService, + private sdkService: SdkService, + ) {} + + init() { + (window as any).__version = async (): Promise => { + return { + client: await this.platformUtilsService.getApplicationVersion(), + sdk: await firstValueFrom(this.sdkService.client$.pipe(map((client) => client.version()))), + }; + }; + } +} From 3eb529821fa4fd5d00de8982d467a21b70b0d3fc Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 23 Oct 2024 09:24:53 +0200 Subject: [PATCH 25/39] feat: add sdk-version to CLI --- apps/cli/src/program.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 7582c760959..a90507cf828 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -1,5 +1,6 @@ import * as chalk from "chalk"; import { program, Command, OptionValues } from "commander"; +import { firstValueFrom, map } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -36,6 +37,13 @@ export class Program extends BaseProgram { "-v, --version", ); + program.option("--sdk-version", "Print the SDK version").action(async () => { + const version = await firstValueFrom( + this.serviceContainer.sdkService.client$.pipe(map((client) => client.version())), + ); + writeLn(version, true); + }); + program.on("option:pretty", () => { process.env.BW_PRETTY = "true"; }); From 020e4c6ff903d4800474a4d723beca714688ad77 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 23 Oct 2024 13:13:06 +0200 Subject: [PATCH 26/39] feat: add version to desktop --- apps/desktop/src/app/services/init.service.ts | 4 ++++ apps/desktop/src/main.ts | 5 +++++ apps/desktop/src/main/menu/menu.about.ts | 7 +++++++ apps/desktop/src/main/menu/menu.main.ts | 3 +++ apps/desktop/src/main/menu/menubar.ts | 4 +++- apps/desktop/src/platform/main/version.main.ts | 17 +++++++++++++++++ apps/desktop/src/platform/preload.ts | 7 +++++++ .../src/platform/services/version.service.ts | 18 ++++++++++++++++++ apps/web/src/app/core/init.service.ts | 5 +++-- 9 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 apps/desktop/src/platform/main/version.main.ts create mode 100644 apps/desktop/src/platform/services/version.service.ts diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 8793587300f..937f7099640 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -21,6 +21,7 @@ import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/va import { UserId } from "@bitwarden/common/types/guid"; import { I18nRendererService } from "../../platform/services/i18n.renderer.service"; +import { VersionService } from "../../platform/services/version.service"; import { NativeMessagingService } from "../../services/native-messaging.service"; @Injectable() @@ -41,6 +42,7 @@ export class InitService { private encryptService: EncryptService, private userAutoUnlockKeyService: UserAutoUnlockKeyService, private accountService: AccountService, + private versionService: VersionService, @Inject(DOCUMENT) private document: Document, ) {} @@ -73,6 +75,8 @@ export class InitService { htmlEl.classList.add("os_" + this.platformUtilsService.getDeviceString()); this.themingService.applyThemeChangesTo(this.document); + this.versionService.init(); + const containerService = new ContainerService(this.cryptoService, this.encryptService); containerService.attachToGlobal(this.win); }; diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index a1b03509c70..cfbd56d5318 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -38,6 +38,7 @@ import { WindowMain } from "./main/window.main"; import { ClipboardMain } from "./platform/main/clipboard.main"; import { DesktopCredentialStorageListener } from "./platform/main/desktop-credential-storage-listener"; import { MainCryptoFunctionService } from "./platform/main/main-crypto-function.service"; +import { VersionMain } from "./platform/main/version.main"; import { DesktopSettingsService } from "./platform/services/desktop-settings.service"; import { ElectronLogMainService } from "./platform/services/electron-log.main.service"; import { ElectronStorageService } from "./platform/services/electron-storage.service"; @@ -71,6 +72,7 @@ export class Main { nativeMessagingMain: NativeMessagingMain; clipboardMain: ClipboardMain; desktopAutofillSettingsService: DesktopAutofillSettingsService; + versionMain: VersionMain; constructor() { // Set paths for portable builds @@ -197,6 +199,8 @@ export class Main { }); }); + this.versionMain = new VersionMain(this.windowMain); + this.powerMonitorMain = new PowerMonitorMain(this.messagingService, this.logService); this.menuMain = new MenuMain( this.i18nService, @@ -205,6 +209,7 @@ export class Main { this.windowMain, this.updaterMain, this.desktopSettingsService, + this.versionMain, ); this.biometricsService = new BiometricsService( diff --git a/apps/desktop/src/main/menu/menu.about.ts b/apps/desktop/src/main/menu/menu.about.ts index 50e125b3d6c..bd82d73fcb4 100644 --- a/apps/desktop/src/main/menu/menu.about.ts +++ b/apps/desktop/src/main/menu/menu.about.ts @@ -2,6 +2,7 @@ import { BrowserWindow, clipboard, dialog, MenuItemConstructorOptions } from "el import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { VersionMain } from "../../platform/main/version.main"; import { isMacAppStore, isSnapStore, isWindowsStore } from "../../utils"; import { UpdaterMain } from "../updater.main"; @@ -22,17 +23,20 @@ export class AboutMenu implements IMenubarMenu { private readonly _updater: UpdaterMain; private readonly _window: BrowserWindow; private readonly _version: string; + private readonly _versionMain: VersionMain; constructor( i18nService: I18nService, version: string, window: BrowserWindow, updater: UpdaterMain, + versionMain: VersionMain, ) { this._i18nService = i18nService; this._updater = updater; this._version = version; this._window = window; + this._versionMain = versionMain; } private get separator(): MenuItemConstructorOptions { @@ -53,8 +57,11 @@ export class AboutMenu implements IMenubarMenu { id: "aboutBitwarden", label: this.localize("aboutBitwarden"), click: async () => { + const sdkVersion = await this._versionMain.sdkVersion(); const aboutInformation = this.localize("version", this._version) + + "\nSDK " + + sdkVersion + "\nShell " + process.versions.electron + "\nRenderer " + diff --git a/apps/desktop/src/main/menu/menu.main.ts b/apps/desktop/src/main/menu/menu.main.ts index 9a63d389b59..eafadf3bfb5 100644 --- a/apps/desktop/src/main/menu/menu.main.ts +++ b/apps/desktop/src/main/menu/menu.main.ts @@ -5,6 +5,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { VersionMain } from "../../platform/main/version.main"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; import { UpdaterMain } from "../updater.main"; import { WindowMain } from "../window.main"; @@ -22,6 +23,7 @@ export class MenuMain { private windowMain: WindowMain, private updaterMain: UpdaterMain, private desktopSettingsService: DesktopSettingsService, + private versionMain: VersionMain, ) {} async init() { @@ -44,6 +46,7 @@ export class MenuMain { await this.getWebVaultUrl(), app.getVersion(), await firstValueFrom(this.desktopSettingsService.hardwareAcceleration$), + this.versionMain, updateRequest, ).menu, ); diff --git a/apps/desktop/src/main/menu/menubar.ts b/apps/desktop/src/main/menu/menubar.ts index b71774c5afe..f69174b9a3b 100644 --- a/apps/desktop/src/main/menu/menubar.ts +++ b/apps/desktop/src/main/menu/menubar.ts @@ -3,6 +3,7 @@ import { Menu, MenuItemConstructorOptions } from "electron"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { VersionMain } from "../../platform/main/version.main"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; import { isMac } from "../../utils"; import { UpdaterMain } from "../updater.main"; @@ -54,6 +55,7 @@ export class Menubar { webVaultUrl: string, appVersion: string, hardwareAccelerationEnabled: boolean, + versionMain: VersionMain, updateRequest?: MenuUpdateRequest, ) { let isLocked = true; @@ -96,7 +98,7 @@ export class Menubar { desktopSettingsService, webVaultUrl, hardwareAccelerationEnabled, - new AboutMenu(i18nService, appVersion, windowMain.win, updaterMain), + new AboutMenu(i18nService, appVersion, windowMain.win, updaterMain, versionMain), ), ]; diff --git a/apps/desktop/src/platform/main/version.main.ts b/apps/desktop/src/platform/main/version.main.ts new file mode 100644 index 00000000000..1c39b641d27 --- /dev/null +++ b/apps/desktop/src/platform/main/version.main.ts @@ -0,0 +1,17 @@ +import { ipcMain } from "electron"; + +import { WindowMain } from "../../main/window.main"; + +export class VersionMain { + constructor(private windowMain: WindowMain) {} + + sdkVersion() { + const timeout = new Promise((resolve) => setTimeout(() => resolve("Timeout error"), 1000)); + const version = new Promise((resolve) => { + ipcMain.once("sdkVersion", (_, version) => resolve(version)); + this.windowMain.win.webContents.send("sdkVersion"); + }); + + return Promise.race([timeout, version]); + } +} diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index bf02ca697f0..74f6b2e588c 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -97,6 +97,13 @@ const localhostCallbackService = { export default { versions: { app: (): Promise => ipcRenderer.invoke("appVersion"), + registerSdkVersionProvider: (provide: (resolve: (version: string) => void) => void) => { + const resolve = (version: string) => ipcRenderer.send("sdkVersion", version); + + ipcRenderer.on("sdkVersion", () => { + provide(resolve); + }); + }, }, deviceType: deviceType(), isDev: isDev(), diff --git a/apps/desktop/src/platform/services/version.service.ts b/apps/desktop/src/platform/services/version.service.ts new file mode 100644 index 00000000000..9027d969823 --- /dev/null +++ b/apps/desktop/src/platform/services/version.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; + +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; + +@Injectable({ + providedIn: "root", +}) +export class VersionService { + constructor(private sdkService: SdkService) {} + + init() { + ipc.platform.versions.registerSdkVersionProvider(async (resolve) => { + const version = await firstValueFrom(this.sdkService.client$.pipe(map((c) => c.version()))); + resolve(version); + }); + } +} diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 3cece036131..614f3a1c2eb 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -57,10 +57,11 @@ export class InitService { const htmlEl = this.win.document.documentElement; htmlEl.classList.add("locale_" + this.i18nService.translationLocale); this.themingService.applyThemeChangesTo(this.document); - const containerService = new ContainerService(this.cryptoService, this.encryptService); - containerService.attachToGlobal(this.win); this.versionService.init(); + + const containerService = new ContainerService(this.cryptoService, this.encryptService); + containerService.attachToGlobal(this.win); }; } } From 79d61c9061139630c34ac4b6406b0411032d725f Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 23 Oct 2024 13:17:06 +0200 Subject: [PATCH 27/39] feat: add override to cli --- .github/workflows/build-cli.yml | 48 +++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index b5b212423ab..f1cf1f48714 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -28,7 +28,11 @@ on: - '.github/workflows/build-cli.yml' - 'bitwarden_license/bit-cli/**' workflow_dispatch: - inputs: {} + inputs: + sdk_branch: + description: "Custom SDK branch" + required: false + type: string defaults: run: @@ -103,6 +107,26 @@ jobs: run: npm ci working-directory: ./ + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk-internal + path: ../sdk-internal + if_no_artifact_found: fail + + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Build & Package Unix run: npm run dist:${{ matrix.license_type.build_prefix }}:${{ env.SHORT_RUNNER_OS }} --quiet @@ -236,6 +260,26 @@ jobs: run: npm ci working-directory: ./ + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk-internal + path: ../sdk-internal + if_no_artifact_found: fail + + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Build & Package Windows run: npm run dist:${{ matrix.license_type.build_prefix }}:win --quiet @@ -289,7 +333,7 @@ jobs: name: bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg path: apps/cli/dist/chocolatey/bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - + - name: Zip NPM Build Artifact run: Get-ChildItem -Path .\build | Compress-Archive -DestinationPath .\bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip From 091b95fa46d8e399042ecab397908e0927cb38c3 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 23 Oct 2024 13:17:55 +0200 Subject: [PATCH 28/39] feat: add override to web --- .github/workflows/build-web.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 4551a7baac8..0675ea01d9b 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -32,6 +32,10 @@ on: custom_tag_extension: description: "Custom image tag extension" required: false + sdk_branch: + description: "Custom SDK branch" + required: false + type: string env: _AZ_REGISTRY: bitwardenprod.azurecr.io @@ -113,6 +117,26 @@ jobs: - name: Install dependencies run: npm ci + - name: Download SDK Artifacts + if: ${{ inputs.sdk_branch != '' }} + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-wasm-internal.yml + workflow_conclusion: success + branch: ${{ inputs.sdk_branch }} + artifacts: sdk-internal + repo: bitwarden/sdk-internal + path: ../sdk-internal + if_no_artifact_found: fail + + - name: Override SDK + if: ${{ inputs.sdk_branch != '' }} + working-directory: ./ + run: | + ls -l ../ + npm link ../sdk-internal + - name: Add Git metadata to build version working-directory: apps/web if: matrix.git_metadata From cf565bb271d7ad66213f726461d971adee9ccfa0 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 23 Oct 2024 14:30:46 +0200 Subject: [PATCH 29/39] fix: cli version acting as default command --- apps/cli/src/program.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index a90507cf828..99c1c28335f 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -24,6 +24,11 @@ const writeLn = CliUtils.writeLn; export class Program extends BaseProgram { async register() { + const clientVersion = await this.serviceContainer.platformUtilsService.getApplicationVersion(); + const sdkVersion = await firstValueFrom( + this.serviceContainer.sdkService.client$.pipe(map((client) => client.version())), + ); + program .option("--pretty", "Format output. JSON is tabbed with two spaces.") .option("--raw", "Return raw output instead of a descriptive message.") @@ -32,17 +37,7 @@ export class Program extends BaseProgram { .option("--quiet", "Don't return anything to stdout.") .option("--nointeraction", "Do not prompt for interactive user input.") .option("--session ", "Pass session key instead of reading from env.") - .version( - await this.serviceContainer.platformUtilsService.getApplicationVersion(), - "-v, --version", - ); - - program.option("--sdk-version", "Print the SDK version").action(async () => { - const version = await firstValueFrom( - this.serviceContainer.sdkService.client$.pipe(map((client) => client.version())), - ); - writeLn(version, true); - }); + .version(`${clientVersion} - SDK: ${sdkVersion}`, "-v, --version"); program.on("option:pretty", () => { process.env.BW_PRETTY = "true"; From f5f2cb87ad69995ddcf0b929e822b1c49783b0c7 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 23 Oct 2024 15:01:58 +0200 Subject: [PATCH 30/39] fix: consistent workflow input name --- .github/workflows/build-desktop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index e0144260a70..a0dcd561786 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -28,7 +28,7 @@ on: workflow_dispatch: inputs: sdk_branch: - description: "Custom SDK branch to use for the build" + description: "Custom SDK branch" required: false type: string From 52866443198d0c4ae91d12a70eaf569d2552321b Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 24 Oct 2024 09:37:55 +0200 Subject: [PATCH 31/39] feat: add error handling --- .../settings/about-dialog/about-dialog.component.ts | 7 +++++-- apps/cli/src/program.ts | 7 +++++-- apps/desktop/src/platform/services/version.service.ts | 9 +++++++-- apps/web/src/app/platform/version.service.ts | 9 +++++++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts index 8969d4a1f9a..9887d33e0ed 100644 --- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts +++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts @@ -1,6 +1,6 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { Observable, combineLatest, defer, map } from "rxjs"; +import { Observable, catchError, combineLatest, defer, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -23,7 +23,10 @@ export class AboutDialogComponent { this.environmentService.environment$.pipe(map((env) => env.isCloud())), ]).pipe(map(([serverConfig, isCloud]) => ({ serverConfig, isCloud }))); - protected sdkVersion$ = this.sdkService.client$.pipe(map((c) => c.version())); + protected sdkVersion$ = this.sdkService.client$.pipe( + map((c) => c.version()), + catchError(() => "Unsupported"), + ); constructor( private configService: ConfigService, diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 99c1c28335f..393a46661cd 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -1,6 +1,6 @@ import * as chalk from "chalk"; import { program, Command, OptionValues } from "commander"; -import { firstValueFrom, map } from "rxjs"; +import { catchError, firstValueFrom, map } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -26,7 +26,10 @@ export class Program extends BaseProgram { async register() { const clientVersion = await this.serviceContainer.platformUtilsService.getApplicationVersion(); const sdkVersion = await firstValueFrom( - this.serviceContainer.sdkService.client$.pipe(map((client) => client.version())), + this.serviceContainer.sdkService.client$.pipe( + map((c) => c.version()), + catchError(() => "Unsupported"), + ), ); program diff --git a/apps/desktop/src/platform/services/version.service.ts b/apps/desktop/src/platform/services/version.service.ts index 9027d969823..07fa707bee6 100644 --- a/apps/desktop/src/platform/services/version.service.ts +++ b/apps/desktop/src/platform/services/version.service.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { catchError, firstValueFrom, map } from "rxjs"; import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; @@ -11,7 +11,12 @@ export class VersionService { init() { ipc.platform.versions.registerSdkVersionProvider(async (resolve) => { - const version = await firstValueFrom(this.sdkService.client$.pipe(map((c) => c.version()))); + const version = await firstValueFrom( + this.sdkService.client$.pipe( + map((c) => c.version()), + catchError(() => "Unsupported"), + ), + ); resolve(version); }); } diff --git a/apps/web/src/app/platform/version.service.ts b/apps/web/src/app/platform/version.service.ts index d8158853184..4d3df8044a7 100644 --- a/apps/web/src/app/platform/version.service.ts +++ b/apps/web/src/app/platform/version.service.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { catchError, firstValueFrom, map } from "rxjs"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; @@ -22,7 +22,12 @@ export class VersionService { (window as any).__version = async (): Promise => { return { client: await this.platformUtilsService.getApplicationVersion(), - sdk: await firstValueFrom(this.sdkService.client$.pipe(map((client) => client.version()))), + sdk: await firstValueFrom( + this.sdkService.client$.pipe( + map((client) => client.version()), + catchError(() => "Unsupported"), + ), + ), }; }; } From b6da518c11a93355cdc07d4d6a0dc87623ee0243 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 25 Oct 2024 09:15:29 +0200 Subject: [PATCH 32/39] feat: upgrade sdk-internal --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 92c3f860631..d986d06d91d 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.6", + "@bitwarden/sdk-internal": "0.1.7", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1", From 9c8d8417e76c9d3190cc4c62a3251cc976c0de6e Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 28 Oct 2024 13:48:17 +0100 Subject: [PATCH 33/39] fix: forgot to update package lock --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 344cf7835e1..2fd574a4010 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.6", + "@bitwarden/sdk-internal": "0.1.7", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "13.1.0", @@ -4695,9 +4695,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.6.tgz", - "integrity": "sha512-YUOOcXnK004mAwE+vfy7AgeLYCtTyafYaXEWED3PNRaSun/a5elrAD//h2yuF9u8Dn5jg1VDkssMPpuG9+2VxA==" + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.7.tgz", + "integrity": "sha512-bpcY4rMipUtNSuhMQBAIdPR/Cz1Fx501yG5Vfsp7CuG28g3eQhkIRQsc07s/HAoKlT20senWbMGdHMM0q1IAGw==" }, "node_modules/@bitwarden/vault": { "resolved": "libs/vault", From 1f3ed1fcae89f0eafcb2c1811d9982e9144d6aee Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 29 Oct 2024 10:34:14 +0100 Subject: [PATCH 34/39] fix: broken CI build move sdk version to a regular command --- apps/cli/src/program.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 78dc5d9b4a4..0e48d41bde1 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -25,12 +25,6 @@ const writeLn = CliUtils.writeLn; export class Program extends BaseProgram { async register() { const clientVersion = await this.serviceContainer.platformUtilsService.getApplicationVersion(); - const sdkVersion = await firstValueFrom( - this.serviceContainer.sdkService.client$.pipe( - map((c) => c.version()), - catchError(() => "Unsupported"), - ), - ); program .option("--pretty", "Format output. JSON is tabbed with two spaces.") @@ -40,7 +34,7 @@ export class Program extends BaseProgram { .option("--quiet", "Don't return anything to stdout.") .option("--nointeraction", "Do not prompt for interactive user input.") .option("--session ", "Pass session key instead of reading from env.") - .version(`${clientVersion} - SDK: ${sdkVersion}`, "-v, --version"); + .version(clientVersion, "-v, --version"); program.on("option:pretty", () => { process.env.BW_PRETTY = "true"; @@ -108,6 +102,19 @@ export class Program extends BaseProgram { writeLn("", true); }); + program + .command("sdk-version") + .description("Print the SDK version.") + .action(async () => { + const sdkVersion = await firstValueFrom( + this.serviceContainer.sdkService.client$.pipe( + map((c) => c.version()), + catchError(() => "Unsupported"), + ), + ); + writeLn(sdkVersion, true); + }); + program .command("login [email] [password]") .description("Log into a user account.") From 32e038c008b224499b7dc4a975ce208f1f03c3e7 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 29 Oct 2024 10:35:35 +0100 Subject: [PATCH 35/39] chore: revert version changes --- apps/cli/src/program.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 0e48d41bde1..8bd3124ba94 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -24,8 +24,6 @@ const writeLn = CliUtils.writeLn; export class Program extends BaseProgram { async register() { - const clientVersion = await this.serviceContainer.platformUtilsService.getApplicationVersion(); - program .option("--pretty", "Format output. JSON is tabbed with two spaces.") .option("--raw", "Return raw output instead of a descriptive message.") @@ -34,7 +32,10 @@ export class Program extends BaseProgram { .option("--quiet", "Don't return anything to stdout.") .option("--nointeraction", "Do not prompt for interactive user input.") .option("--session ", "Pass session key instead of reading from env.") - .version(clientVersion, "-v, --version"); + .version( + await this.serviceContainer.platformUtilsService.getApplicationVersion(), + "-v, --version", + ); program.on("option:pretty", () => { process.env.BW_PRETTY = "true"; From f75f71fc725276c689786dd265d881bd32d9d72e Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 29 Oct 2024 15:56:16 +0100 Subject: [PATCH 36/39] refactor: move error handling code --- .../settings/about-dialog/about-dialog.component.ts | 7 ++----- apps/cli/src/program.ts | 9 ++------- apps/desktop/src/platform/services/version.service.ts | 9 ++------- libs/common/src/platform/abstractions/sdk/sdk.service.ts | 5 +++++ .../src/platform/services/sdk/default-sdk.service.ts | 6 ++++++ 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts index 9887d33e0ed..f730fef24b3 100644 --- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts +++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts @@ -1,6 +1,6 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { Observable, catchError, combineLatest, defer, map } from "rxjs"; +import { Observable, combineLatest, defer, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -23,10 +23,7 @@ export class AboutDialogComponent { this.environmentService.environment$.pipe(map((env) => env.isCloud())), ]).pipe(map(([serverConfig, isCloud]) => ({ serverConfig, isCloud }))); - protected sdkVersion$ = this.sdkService.client$.pipe( - map((c) => c.version()), - catchError(() => "Unsupported"), - ); + protected sdkVersion$ = this.sdkService.version$; constructor( private configService: ConfigService, diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 8bd3124ba94..ca2adb85755 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -1,6 +1,6 @@ import * as chalk from "chalk"; import { program, Command, OptionValues } from "commander"; -import { catchError, firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -107,12 +107,7 @@ export class Program extends BaseProgram { .command("sdk-version") .description("Print the SDK version.") .action(async () => { - const sdkVersion = await firstValueFrom( - this.serviceContainer.sdkService.client$.pipe( - map((c) => c.version()), - catchError(() => "Unsupported"), - ), - ); + const sdkVersion = await firstValueFrom(this.serviceContainer.sdkService.version$); writeLn(sdkVersion, true); }); diff --git a/apps/desktop/src/platform/services/version.service.ts b/apps/desktop/src/platform/services/version.service.ts index 07fa707bee6..2628f83d593 100644 --- a/apps/desktop/src/platform/services/version.service.ts +++ b/apps/desktop/src/platform/services/version.service.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { catchError, firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; @@ -11,12 +11,7 @@ export class VersionService { init() { ipc.platform.versions.registerSdkVersionProvider(async (resolve) => { - const version = await firstValueFrom( - this.sdkService.client$.pipe( - map((c) => c.version()), - catchError(() => "Unsupported"), - ), - ); + const version = await firstValueFrom(this.sdkService.version$); resolve(version); }); } diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts index 5e4e4cb4cbe..6a5e28a6f31 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk.service.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -10,6 +10,11 @@ export abstract class SdkService { */ supported$: Observable; + /** + * Retrieve the version of the SDK. + */ + version$: Observable; + /** * Retrieve a client initialized without a user. * This client can only be used for operations that don't require a user context. diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index a1617315448..1f1e560ea31 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -8,6 +8,7 @@ import { distinctUntilChanged, tap, switchMap, + catchError, } from "rxjs"; import { KeyService } from "@bitwarden/key-management"; @@ -51,6 +52,11 @@ export class DefaultSdkService implements SdkService { }), ); + version$ = this.client$.pipe( + map((client) => client.version()), + catchError(() => "Unsupported"), + ); + constructor( private sdkClientFactory: SdkClientFactory, private environmentService: EnvironmentService, From f0319f9d462a092a1abcb4b521495d5554342a1e Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 4 Nov 2024 14:17:12 +0100 Subject: [PATCH 37/39] chore: bump SDK to 0.2.0.main-1 --- package-lock.json | 26 ++++++++++++-------------- package.json | 6 +++--- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index a642036fef0..44a77bac879 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.7", + "@bitwarden/sdk-internal": "0.2.0-main.1", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "13.1.0", @@ -101,7 +101,7 @@ "@types/firefox-webext-browser": "120.0.4", "@types/inquirer": "8.2.10", "@types/jest": "29.5.12", - "@types/jquery": "3.5.30", + "@types/jquery": "3.5.32", "@types/jsdom": "21.1.7", "@types/koa": "2.15.0", "@types/koa__multer": "2.0.7", @@ -152,7 +152,7 @@ "gulp-zip": "6.0.0", "html-loader": "5.0.0", "html-webpack-injector": "1.1.4", - "html-webpack-plugin": "5.6.0", + "html-webpack-plugin": "5.6.3", "husky": "9.1.4", "jest-extended": "^4.0.2", "jest-junit": "16.0.0", @@ -4696,9 +4696,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.7.tgz", - "integrity": "sha512-bpcY4rMipUtNSuhMQBAIdPR/Cz1Fx501yG5Vfsp7CuG28g3eQhkIRQsc07s/HAoKlT20senWbMGdHMM0q1IAGw==" + "version": "0.2.0-main.1", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.1.tgz", + "integrity": "sha512-DMAaW7p4OeAFp5S57Vy9r3Nkyzg16/IjBffERYZSVHBPxZMfogkU4jj++FWMnYdwqs/VBoRDAKEZAWMduspUjw==" }, "node_modules/@bitwarden/vault": { "resolved": "libs/vault", @@ -9467,11 +9467,10 @@ "license": "MIT" }, "node_modules/@types/jquery": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.30.tgz", - "integrity": "sha512-nbWKkkyb919DOUxjmRVk8vwtDb0/k8FKncmUKFi+NY+QXqWltooxTrswvz4LspQwxvLdvzBN1TImr6cw3aQx2A==", + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.32.tgz", + "integrity": "sha512-b9Xbf4CkMqS02YH8zACqN1xzdxc3cO735Qe5AbSUFmyOiaWAbcpqh9Wna+Uk0vgACvoQHpWDg2rGdHkYPLmCiQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/sizzle": "*" } @@ -21464,11 +21463,10 @@ "license": "ISC" }, "node_modules/html-webpack-plugin": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", - "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", + "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", "dev": true, - "license": "MIT", "dependencies": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", diff --git a/package.json b/package.json index 8627333ddea..6c43bb2e1b4 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@types/firefox-webext-browser": "120.0.4", "@types/inquirer": "8.2.10", "@types/jest": "29.5.12", - "@types/jquery": "3.5.30", + "@types/jquery": "3.5.32", "@types/jsdom": "21.1.7", "@types/koa": "2.15.0", "@types/koa__multer": "2.0.7", @@ -113,7 +113,7 @@ "gulp-zip": "6.0.0", "html-loader": "5.0.0", "html-webpack-injector": "1.1.4", - "html-webpack-plugin": "5.6.0", + "html-webpack-plugin": "5.6.3", "husky": "9.1.4", "jest-extended": "^4.0.2", "jest-junit": "16.0.0", @@ -158,7 +158,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@bitwarden/sdk-internal": "0.1.7", + "@bitwarden/sdk-internal": "0.2.0-main.1", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "13.1.0", From 8a9ef070ad81167f5bf84cff87d47a68e35fbc2b Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 4 Nov 2024 14:26:25 +0100 Subject: [PATCH 38/39] fix: clean up references to inputs.sdk_commit --- .github/workflows/build-browser.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 98ead2fab36..11f0d3feef2 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -160,7 +160,7 @@ jobs: working-directory: browser-source/ - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' || inputs.sdk_commit != '' }} + if: ${{ inputs.sdk_branch != '' }} uses: bitwarden/gh-actions/download-artifacts@main with: github_token: ${{secrets.GITHUB_TOKEN}} @@ -173,7 +173,7 @@ jobs: if_no_artifact_found: fail - name: Override SDK - if: ${{ inputs.sdk_branch != '' || inputs.sdk_commit != '' }} + if: ${{ inputs.sdk_branch != '' }} working-directory: browser-source/ run: | npm link ../sdk-internal From da703f3866913f7cc822ead2027b91dfd69f9412 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 4 Nov 2024 14:27:42 +0100 Subject: [PATCH 39/39] refactor: rename `init` to `applyVersionToWindow` --- apps/web/src/app/core/init.service.ts | 2 +- apps/web/src/app/platform/version.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index d96cbf09283..b3e6d691f75 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -57,7 +57,7 @@ export class InitService { const htmlEl = this.win.document.documentElement; htmlEl.classList.add("locale_" + this.i18nService.translationLocale); this.themingService.applyThemeChangesTo(this.document); - this.versionService.init(); + this.versionService.applyVersionToWindow(); const containerService = new ContainerService(this.keyService, this.encryptService); containerService.attachToGlobal(this.win); diff --git a/apps/web/src/app/platform/version.service.ts b/apps/web/src/app/platform/version.service.ts index 4d3df8044a7..d82dc5590d8 100644 --- a/apps/web/src/app/platform/version.service.ts +++ b/apps/web/src/app/platform/version.service.ts @@ -18,7 +18,7 @@ export class VersionService { private sdkService: SdkService, ) {} - init() { + applyVersionToWindow() { (window as any).__version = async (): Promise => { return { client: await this.platformUtilsService.getApplicationVersion(),