mirror of
https://github.com/bitwarden/browser
synced 2026-02-18 10:23:52 +00:00
[PM-27236] account registration v2 for key connector (#17951)
* account registration v2 for key connector * explicit naming * test coverage * missing AccountCryptographicStateService and DI dependencies * redundant SdkLoadService.Ready * update sdk version
This commit is contained in:
committed by
jaasen-livefront
parent
a3e5b94ba9
commit
b0000476b1
@@ -126,6 +126,7 @@ import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/co
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
|
||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
@@ -164,6 +165,7 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r
|
||||
import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory";
|
||||
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
|
||||
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
|
||||
import { DefaultRegisterSdkService } from "@bitwarden/common/platform/services/sdk/register-sdk.service";
|
||||
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
||||
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
||||
import { PrimarySecondaryStorageService } from "@bitwarden/common/platform/storage/primary-secondary-storage.service";
|
||||
@@ -463,6 +465,7 @@ export default class MainBackground {
|
||||
themeStateService: DefaultThemeStateService;
|
||||
autoSubmitLoginBackground: AutoSubmitLoginBackground;
|
||||
sdkService: SdkService;
|
||||
registerSdkService: RegisterSdkService;
|
||||
sdkLoadService: SdkLoadService;
|
||||
cipherAuthorizationService: CipherAuthorizationService;
|
||||
endUserNotificationService: EndUserNotificationService;
|
||||
@@ -797,18 +800,6 @@ export default class MainBackground {
|
||||
this.apiService,
|
||||
this.accountService,
|
||||
);
|
||||
this.keyConnectorService = new KeyConnectorService(
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
this.keyService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
this.logService,
|
||||
this.organizationService,
|
||||
this.keyGenerationService,
|
||||
logoutCallback,
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
this.authService = new AuthService(
|
||||
this.accountService,
|
||||
@@ -846,6 +837,37 @@ export default class MainBackground {
|
||||
this.configService,
|
||||
);
|
||||
|
||||
this.registerSdkService = new DefaultRegisterSdkService(
|
||||
sdkClientFactory,
|
||||
this.environmentService,
|
||||
this.platformUtilsService,
|
||||
this.accountService,
|
||||
this.apiService,
|
||||
this.stateProvider,
|
||||
this.configService,
|
||||
);
|
||||
|
||||
this.accountCryptographicStateService = new DefaultAccountCryptographicStateService(
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
this.keyConnectorService = new KeyConnectorService(
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
this.keyService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
this.logService,
|
||||
this.organizationService,
|
||||
this.keyGenerationService,
|
||||
logoutCallback,
|
||||
this.stateProvider,
|
||||
this.configService,
|
||||
this.registerSdkService,
|
||||
this.securityStateService,
|
||||
this.accountCryptographicStateService,
|
||||
);
|
||||
|
||||
this.pinService = new PinService(
|
||||
this.encryptService,
|
||||
this.logService,
|
||||
@@ -1013,9 +1035,7 @@ export default class MainBackground {
|
||||
this.avatarService = new AvatarService(this.apiService, this.stateProvider);
|
||||
|
||||
this.providerService = new ProviderService(this.stateProvider);
|
||||
this.accountCryptographicStateService = new DefaultAccountCryptographicStateService(
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
this.syncService = new DefaultSyncService(
|
||||
this.masterPasswordService,
|
||||
this.accountService,
|
||||
|
||||
@@ -104,6 +104,7 @@ import {
|
||||
EnvironmentService,
|
||||
RegionConfig,
|
||||
} from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
|
||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { LogLevelType } from "@bitwarden/common/platform/enums";
|
||||
@@ -124,6 +125,7 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r
|
||||
import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory";
|
||||
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
|
||||
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
|
||||
import { DefaultRegisterSdkService } from "@bitwarden/common/platform/services/sdk/register-sdk.service";
|
||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
@@ -323,6 +325,7 @@ export class ServiceContainer {
|
||||
kdfConfigService: KdfConfigService;
|
||||
taskSchedulerService: TaskSchedulerService;
|
||||
sdkService: SdkService;
|
||||
registerSdkService: RegisterSdkService;
|
||||
sdkLoadService: SdkLoadService;
|
||||
cipherAuthorizationService: CipherAuthorizationService;
|
||||
ssoUrlService: SsoUrlService;
|
||||
@@ -632,26 +635,10 @@ export class ServiceContainer {
|
||||
this.accountService,
|
||||
);
|
||||
|
||||
this.keyConnectorService = new KeyConnectorService(
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
this.keyService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
this.logService,
|
||||
this.organizationService,
|
||||
this.keyGenerationService,
|
||||
logoutCallback,
|
||||
this.accountCryptographicStateService = new DefaultAccountCryptographicStateService(
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
this.twoFactorService = new DefaultTwoFactorService(
|
||||
this.i18nService,
|
||||
this.platformUtilsService,
|
||||
this.globalStateProvider,
|
||||
this.twoFactorApiService,
|
||||
);
|
||||
|
||||
const sdkClientFactory = flagEnabled("sdk")
|
||||
? new DefaultSdkClientFactory()
|
||||
: new NoopSdkClientFactory();
|
||||
@@ -670,6 +657,41 @@ export class ServiceContainer {
|
||||
customUserAgent,
|
||||
);
|
||||
|
||||
this.registerSdkService = new DefaultRegisterSdkService(
|
||||
sdkClientFactory,
|
||||
this.environmentService,
|
||||
this.platformUtilsService,
|
||||
this.accountService,
|
||||
this.apiService,
|
||||
this.stateProvider,
|
||||
this.configService,
|
||||
customUserAgent,
|
||||
);
|
||||
|
||||
this.keyConnectorService = new KeyConnectorService(
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
this.keyService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
this.logService,
|
||||
this.organizationService,
|
||||
this.keyGenerationService,
|
||||
logoutCallback,
|
||||
this.stateProvider,
|
||||
this.configService,
|
||||
this.registerSdkService,
|
||||
this.securityStateService,
|
||||
this.accountCryptographicStateService,
|
||||
);
|
||||
|
||||
this.twoFactorService = new DefaultTwoFactorService(
|
||||
this.i18nService,
|
||||
this.platformUtilsService,
|
||||
this.globalStateProvider,
|
||||
this.twoFactorApiService,
|
||||
);
|
||||
|
||||
this.passwordStrengthService = new PasswordStrengthService();
|
||||
|
||||
this.passwordGenerationService = legacyPasswordGenerationServiceFactory(
|
||||
@@ -719,10 +741,6 @@ export class ServiceContainer {
|
||||
this.accountService,
|
||||
);
|
||||
|
||||
this.accountCryptographicStateService = new DefaultAccountCryptographicStateService(
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
this.loginStrategyService = new LoginStrategyService(
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
|
||||
@@ -1146,6 +1146,10 @@ const safeProviders: SafeProvider[] = [
|
||||
KeyGenerationService,
|
||||
LOGOUT_CALLBACK,
|
||||
StateProvider,
|
||||
ConfigService,
|
||||
RegisterSdkService,
|
||||
SecurityStateService,
|
||||
AccountCryptographicStateService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
@@ -45,6 +45,7 @@ export enum FeatureFlag {
|
||||
DataRecoveryTool = "pm-28813-data-recovery-tool",
|
||||
ConsolidatedSessionTimeoutComponent = "pm-26056-consolidated-session-timeout-component",
|
||||
PM27279_V2RegistrationTdeJit = "pm-27279-v2-registration-tde-jit",
|
||||
EnableAccountEncryptionV2KeyConnectorRegistration = "enable-account-encryption-v2-key-connector-registration",
|
||||
|
||||
/* Tools */
|
||||
DesktopSendUIRefresh = "desktop-send-ui-refresh",
|
||||
@@ -152,6 +153,7 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.DataRecoveryTool]: FALSE,
|
||||
[FeatureFlag.ConsolidatedSessionTimeoutComponent]: FALSE,
|
||||
[FeatureFlag.PM27279_V2RegistrationTdeJit]: FALSE,
|
||||
[FeatureFlag.EnableAccountEncryptionV2KeyConnectorRegistration]: FALSE,
|
||||
|
||||
/* Platform */
|
||||
[FeatureFlag.IpcChannelFramework]: FALSE,
|
||||
|
||||
@@ -5,5 +5,6 @@ import { KdfConfig } from "@bitwarden/key-management";
|
||||
export interface NewSsoUserKeyConnectorConversion {
|
||||
kdfConfig: KdfConfig;
|
||||
keyConnectorUrl: string;
|
||||
// SSO organization identifier, not UUID
|
||||
organizationId: string;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import { SetKeyConnectorKeyRequest } from "@bitwarden/common/key-management/key-
|
||||
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Argon2KdfConfig, PBKDF2KdfConfig, KeyService, KdfType } from "@bitwarden/key-management";
|
||||
import { Argon2KdfConfig, KdfType, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management";
|
||||
import { BitwardenClient } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec";
|
||||
import { ApiService } from "../../../abstractions/api.service";
|
||||
@@ -16,21 +17,26 @@ import { Organization } from "../../../admin-console/models/domain/organization"
|
||||
import { ProfileOrganizationResponse } from "../../../admin-console/models/response/profile-organization.response";
|
||||
import { KeyConnectorUserKeyResponse } from "../../../auth/models/response/key-connector-user-key.response";
|
||||
import { TokenService } from "../../../auth/services/token.service";
|
||||
import { ConfigService } from "../../../platform/abstractions/config/config.service";
|
||||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { RegisterSdkService } from "../../../platform/abstractions/sdk/register-sdk.service";
|
||||
import { Rc } from "../../../platform/misc/reference-counting/rc";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { OrganizationId, UserId } from "../../../types/guid";
|
||||
import { MasterKey, UserKey } from "../../../types/key";
|
||||
import { AccountCryptographicStateService } from "../../account-cryptography/account-cryptographic-state.service";
|
||||
import { KeyGenerationService } from "../../crypto";
|
||||
import { EncString } from "../../crypto/models/enc-string";
|
||||
import { FakeMasterPasswordService } from "../../master-password/services/fake-master-password.service";
|
||||
import { SecurityStateService } from "../../security-state/abstractions/security-state.service";
|
||||
import { KeyConnectorUserKeyRequest } from "../models/key-connector-user-key.request";
|
||||
import { NewSsoUserKeyConnectorConversion } from "../models/new-sso-user-key-connector-conversion";
|
||||
|
||||
import {
|
||||
USES_KEY_CONNECTOR,
|
||||
NEW_SSO_USER_KEY_CONNECTOR_CONVERSION,
|
||||
KeyConnectorService,
|
||||
NEW_SSO_USER_KEY_CONNECTOR_CONVERSION,
|
||||
USES_KEY_CONNECTOR,
|
||||
} from "./key-connector.service";
|
||||
|
||||
describe("KeyConnectorService", () => {
|
||||
@@ -43,6 +49,10 @@ describe("KeyConnectorService", () => {
|
||||
const organizationService = mock<OrganizationService>();
|
||||
const keyGenerationService = mock<KeyGenerationService>();
|
||||
const logoutCallback = jest.fn();
|
||||
const configService = mock<ConfigService>();
|
||||
const registerSdkService = mock<RegisterSdkService>();
|
||||
const securityStateService = mock<SecurityStateService>();
|
||||
const accountCryptographicStateService = mock<AccountCryptographicStateService>();
|
||||
|
||||
let stateProvider: FakeStateProvider;
|
||||
|
||||
@@ -50,6 +60,7 @@ describe("KeyConnectorService", () => {
|
||||
let masterPasswordService: FakeMasterPasswordService;
|
||||
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
const mockSsoOrgIdentifier = "test-sso-org-id";
|
||||
const mockOrgId = Utils.newGuid() as OrganizationId;
|
||||
|
||||
const mockMasterKeyResponse: KeyConnectorUserKeyResponse = new KeyConnectorUserKeyResponse({
|
||||
@@ -61,7 +72,7 @@ describe("KeyConnectorService", () => {
|
||||
const conversion: NewSsoUserKeyConnectorConversion = {
|
||||
kdfConfig: new PBKDF2KdfConfig(600_000),
|
||||
keyConnectorUrl,
|
||||
organizationId: mockOrgId,
|
||||
organizationId: mockSsoOrgIdentifier,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -82,6 +93,10 @@ describe("KeyConnectorService", () => {
|
||||
keyGenerationService,
|
||||
logoutCallback,
|
||||
stateProvider,
|
||||
configService,
|
||||
registerSdkService,
|
||||
securityStateService,
|
||||
accountCryptographicStateService,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -419,44 +434,52 @@ describe("KeyConnectorService", () => {
|
||||
});
|
||||
|
||||
describe("convertNewSsoUserToKeyConnector", () => {
|
||||
const passwordKey = new SymmetricCryptoKey(new Uint8Array(64));
|
||||
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
|
||||
const mockEmail = "test@example.com";
|
||||
const mockMasterKey = getMockMasterKey();
|
||||
const mockKeyPair = ["mockPubKey", new EncString("mockEncryptedPrivKey")] as [
|
||||
string,
|
||||
EncString,
|
||||
];
|
||||
let mockMakeUserKeyResult: [UserKey, EncString];
|
||||
describe("V2", () => {
|
||||
const mockKeyConnectorKey = Utils.fromBufferToB64(new Uint8Array(64));
|
||||
const mockUserKeyString = Utils.fromBufferToB64(new Uint8Array(64));
|
||||
const mockPrivateKey = "mockPrivateKey789";
|
||||
const mockKeyConnectorKeyWrappedUserKey = "2.mockWrappedUserKey";
|
||||
const mockSigningKey = "mockSigningKey";
|
||||
const mockSignedPublicKey = "mockSignedPublicKey";
|
||||
const mockSecurityState = "mockSecurityState";
|
||||
|
||||
beforeEach(() => {
|
||||
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
|
||||
const encString = new EncString("mockEncryptedString");
|
||||
mockMakeUserKeyResult = [mockUserKey, encString] as [UserKey, EncString];
|
||||
let mockSdkRef: any;
|
||||
let mockSdk: any;
|
||||
|
||||
keyGenerationService.createKey.mockResolvedValue(passwordKey);
|
||||
keyService.makeMasterKey.mockResolvedValue(mockMasterKey);
|
||||
keyService.makeUserKey.mockResolvedValue(mockMakeUserKeyResult);
|
||||
keyService.makeKeyPair.mockResolvedValue(mockKeyPair);
|
||||
tokenService.getEmail.mockResolvedValue(mockEmail);
|
||||
});
|
||||
beforeEach(() => {
|
||||
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||
|
||||
it.each([
|
||||
[KdfType.PBKDF2_SHA256, 700_000, undefined, undefined],
|
||||
[KdfType.Argon2id, 11, 65, 5],
|
||||
])(
|
||||
"sets up a new SSO user with key connector",
|
||||
async (kdfType, kdfIterations, kdfMemory, kdfParallelism) => {
|
||||
const expectedKdfConfig =
|
||||
kdfType == KdfType.PBKDF2_SHA256
|
||||
? new PBKDF2KdfConfig(kdfIterations)
|
||||
: new Argon2KdfConfig(kdfIterations, kdfMemory, kdfParallelism);
|
||||
|
||||
const conversion: NewSsoUserKeyConnectorConversion = {
|
||||
kdfConfig: expectedKdfConfig,
|
||||
keyConnectorUrl: keyConnectorUrl,
|
||||
organizationId: mockOrgId,
|
||||
mockSdkRef = {
|
||||
value: {
|
||||
auth: jest.fn().mockReturnValue({
|
||||
registration: jest.fn().mockReturnValue({
|
||||
post_keys_for_key_connector_registration: jest.fn().mockResolvedValue({
|
||||
key_connector_key: mockKeyConnectorKey,
|
||||
user_key: mockUserKeyString,
|
||||
key_connector_key_wrapped_user_key: mockKeyConnectorKeyWrappedUserKey,
|
||||
account_cryptographic_state: {
|
||||
V2: {
|
||||
private_key: mockPrivateKey,
|
||||
signing_key: mockSigningKey,
|
||||
signed_public_key: mockSignedPublicKey,
|
||||
security_state: mockSecurityState,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
[Symbol.dispose]: jest.fn(),
|
||||
};
|
||||
|
||||
mockSdk = {
|
||||
take: jest.fn().mockReturnValue(mockSdkRef),
|
||||
};
|
||||
|
||||
registerSdkService.registerClient$.mockReturnValue(of(mockSdk));
|
||||
});
|
||||
|
||||
it("should set up a new SSO user with key connector using V2", async () => {
|
||||
const conversionState = stateProvider.singleUser.getFake(
|
||||
mockUserId,
|
||||
NEW_SSO_USER_KEY_CONNECTOR_CONVERSION,
|
||||
@@ -465,11 +488,253 @@ describe("KeyConnectorService", () => {
|
||||
|
||||
await keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId);
|
||||
|
||||
expect(registerSdkService.registerClient$).toHaveBeenCalledWith(mockUserId);
|
||||
expect(mockSdk.take).toHaveBeenCalled();
|
||||
expect(mockSdkRef.value.auth).toHaveBeenCalled();
|
||||
|
||||
const mockRegistration = mockSdkRef.value
|
||||
.auth()
|
||||
.registration().post_keys_for_key_connector_registration;
|
||||
expect(mockRegistration).toHaveBeenCalledWith(
|
||||
keyConnectorUrl,
|
||||
mockSsoOrgIdentifier,
|
||||
mockUserId,
|
||||
);
|
||||
|
||||
expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(
|
||||
expect.any(SymmetricCryptoKey),
|
||||
mockUserId,
|
||||
);
|
||||
expect(keyService.setUserKey).toHaveBeenCalledWith(
|
||||
expect.any(SymmetricCryptoKey),
|
||||
mockUserId,
|
||||
);
|
||||
expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(
|
||||
expect.any(EncString),
|
||||
mockUserId,
|
||||
);
|
||||
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith(
|
||||
{
|
||||
V2: {
|
||||
private_key: mockPrivateKey,
|
||||
signing_key: mockSigningKey,
|
||||
signed_public_key: mockSignedPublicKey,
|
||||
security_state: mockSecurityState,
|
||||
},
|
||||
},
|
||||
mockUserId,
|
||||
);
|
||||
expect(keyService.setPrivateKey).toHaveBeenCalledWith(mockPrivateKey, mockUserId);
|
||||
expect(keyService.setUserSigningKey).toHaveBeenCalledWith(mockSigningKey, mockUserId);
|
||||
expect(securityStateService.setAccountSecurityState).toHaveBeenCalledWith(
|
||||
mockSecurityState,
|
||||
mockUserId,
|
||||
);
|
||||
expect(keyService.setSignedPublicKey).toHaveBeenCalledWith(mockSignedPublicKey, mockUserId);
|
||||
|
||||
expect(await firstValueFrom(conversionState.state$)).toBeNull();
|
||||
});
|
||||
|
||||
it("should throw error when SDK is not available", async () => {
|
||||
registerSdkService.registerClient$.mockReturnValue(
|
||||
of(null as unknown as Rc<BitwardenClient>),
|
||||
);
|
||||
|
||||
const conversionState = stateProvider.singleUser.getFake(
|
||||
mockUserId,
|
||||
NEW_SSO_USER_KEY_CONNECTOR_CONVERSION,
|
||||
);
|
||||
conversionState.nextState(conversion);
|
||||
|
||||
await expect(
|
||||
keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId),
|
||||
).rejects.toThrow("SDK not available");
|
||||
|
||||
expect(await firstValueFrom(conversionState.state$)).toEqual(conversion);
|
||||
expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled();
|
||||
expect(keyService.setUserKey).not.toHaveBeenCalled();
|
||||
expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).not.toHaveBeenCalled();
|
||||
expect(
|
||||
accountCryptographicStateService.setAccountCryptographicState,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(keyService.setPrivateKey).not.toHaveBeenCalled();
|
||||
expect(keyService.setUserSigningKey).not.toHaveBeenCalled();
|
||||
expect(securityStateService.setAccountSecurityState).not.toHaveBeenCalled();
|
||||
expect(keyService.setSignedPublicKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should throw error when account cryptographic state is not V2", async () => {
|
||||
mockSdkRef.value
|
||||
.auth()
|
||||
.registration()
|
||||
.post_keys_for_key_connector_registration.mockResolvedValue({
|
||||
key_connector_key: mockKeyConnectorKey,
|
||||
user_key: mockUserKeyString,
|
||||
key_connector_key_wrapped_user_key: mockKeyConnectorKeyWrappedUserKey,
|
||||
account_cryptographic_state: {
|
||||
V1: {
|
||||
private_key: mockPrivateKey,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const conversionState = stateProvider.singleUser.getFake(
|
||||
mockUserId,
|
||||
NEW_SSO_USER_KEY_CONNECTOR_CONVERSION,
|
||||
);
|
||||
conversionState.nextState(conversion);
|
||||
|
||||
await expect(
|
||||
keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId),
|
||||
).rejects.toThrow("Unexpected account cryptographic state version");
|
||||
|
||||
expect(await firstValueFrom(conversionState.state$)).toEqual(conversion);
|
||||
expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled();
|
||||
expect(keyService.setUserKey).not.toHaveBeenCalled();
|
||||
expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).not.toHaveBeenCalled();
|
||||
expect(
|
||||
accountCryptographicStateService.setAccountCryptographicState,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(keyService.setPrivateKey).not.toHaveBeenCalled();
|
||||
expect(keyService.setUserSigningKey).not.toHaveBeenCalled();
|
||||
expect(securityStateService.setAccountSecurityState).not.toHaveBeenCalled();
|
||||
expect(keyService.setSignedPublicKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should throw error when post_keys_for_key_connector_registration fails", async () => {
|
||||
const sdkError = new Error("Key Connector registration failed");
|
||||
mockSdkRef.value
|
||||
.auth()
|
||||
.registration()
|
||||
.post_keys_for_key_connector_registration.mockRejectedValue(sdkError);
|
||||
|
||||
const conversionState = stateProvider.singleUser.getFake(
|
||||
mockUserId,
|
||||
NEW_SSO_USER_KEY_CONNECTOR_CONVERSION,
|
||||
);
|
||||
conversionState.nextState(conversion);
|
||||
|
||||
await expect(
|
||||
keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId),
|
||||
).rejects.toThrow("Key Connector registration failed");
|
||||
|
||||
expect(await firstValueFrom(conversionState.state$)).toEqual(conversion);
|
||||
expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled();
|
||||
expect(keyService.setUserKey).not.toHaveBeenCalled();
|
||||
expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).not.toHaveBeenCalled();
|
||||
expect(
|
||||
accountCryptographicStateService.setAccountCryptographicState,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(keyService.setPrivateKey).not.toHaveBeenCalled();
|
||||
expect(keyService.setUserSigningKey).not.toHaveBeenCalled();
|
||||
expect(securityStateService.setAccountSecurityState).not.toHaveBeenCalled();
|
||||
expect(keyService.setSignedPublicKey).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("V1", () => {
|
||||
const passwordKey = new SymmetricCryptoKey(new Uint8Array(64));
|
||||
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
|
||||
const mockEmail = "test@example.com";
|
||||
const mockMasterKey = getMockMasterKey();
|
||||
const mockKeyPair = ["mockPubKey", new EncString("mockEncryptedPrivKey")] as [
|
||||
string,
|
||||
EncString,
|
||||
];
|
||||
let mockMakeUserKeyResult: [UserKey, EncString];
|
||||
|
||||
beforeEach(() => {
|
||||
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
|
||||
const encString = new EncString("mockEncryptedString");
|
||||
mockMakeUserKeyResult = [mockUserKey, encString] as [UserKey, EncString];
|
||||
|
||||
keyGenerationService.createKey.mockResolvedValue(passwordKey);
|
||||
keyService.makeMasterKey.mockResolvedValue(mockMasterKey);
|
||||
keyService.makeUserKey.mockResolvedValue(mockMakeUserKeyResult);
|
||||
keyService.makeKeyPair.mockResolvedValue(mockKeyPair);
|
||||
tokenService.getEmail.mockResolvedValue(mockEmail);
|
||||
configService.getFeatureFlag$.mockReturnValue(of(false));
|
||||
});
|
||||
|
||||
it.each([
|
||||
[KdfType.PBKDF2_SHA256, 700_000, undefined, undefined],
|
||||
[KdfType.Argon2id, 11, 65, 5],
|
||||
])(
|
||||
"sets up a new SSO user with key connector",
|
||||
async (kdfType, kdfIterations, kdfMemory, kdfParallelism) => {
|
||||
const expectedKdfConfig =
|
||||
kdfType == KdfType.PBKDF2_SHA256
|
||||
? new PBKDF2KdfConfig(kdfIterations)
|
||||
: new Argon2KdfConfig(kdfIterations, kdfMemory, kdfParallelism);
|
||||
|
||||
const conversion: NewSsoUserKeyConnectorConversion = {
|
||||
kdfConfig: expectedKdfConfig,
|
||||
keyConnectorUrl: keyConnectorUrl,
|
||||
organizationId: mockSsoOrgIdentifier,
|
||||
};
|
||||
const conversionState = stateProvider.singleUser.getFake(
|
||||
mockUserId,
|
||||
NEW_SSO_USER_KEY_CONNECTOR_CONVERSION,
|
||||
);
|
||||
conversionState.nextState(conversion);
|
||||
|
||||
await keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId);
|
||||
|
||||
expect(keyGenerationService.createKey).toHaveBeenCalledWith(512);
|
||||
expect(keyService.makeMasterKey).toHaveBeenCalledWith(
|
||||
passwordKey.keyB64,
|
||||
mockEmail,
|
||||
expectedKdfConfig,
|
||||
);
|
||||
expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(
|
||||
mockMasterKey,
|
||||
mockUserId,
|
||||
);
|
||||
expect(keyService.makeUserKey).toHaveBeenCalledWith(mockMasterKey);
|
||||
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, mockUserId);
|
||||
expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(
|
||||
mockMakeUserKeyResult[1],
|
||||
mockUserId,
|
||||
);
|
||||
expect(keyService.makeKeyPair).toHaveBeenCalledWith(mockMakeUserKeyResult[0]);
|
||||
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
|
||||
keyConnectorUrl,
|
||||
new KeyConnectorUserKeyRequest(
|
||||
Utils.fromBufferToB64(mockMasterKey.inner().encryptionKey),
|
||||
),
|
||||
);
|
||||
expect(apiService.postSetKeyConnectorKey).toHaveBeenCalledWith(
|
||||
new SetKeyConnectorKeyRequest(
|
||||
mockMakeUserKeyResult[1].encryptedString!,
|
||||
expectedKdfConfig,
|
||||
mockSsoOrgIdentifier,
|
||||
new KeysRequest(mockKeyPair[0], mockKeyPair[1].encryptedString!),
|
||||
),
|
||||
);
|
||||
|
||||
// Verify that conversion data is cleared from conversionState
|
||||
expect(await firstValueFrom(conversionState.state$)).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it("handles api error", async () => {
|
||||
apiService.postUserKeyToKeyConnector.mockRejectedValue(new Error("API error"));
|
||||
|
||||
const conversionState = stateProvider.singleUser.getFake(
|
||||
mockUserId,
|
||||
NEW_SSO_USER_KEY_CONNECTOR_CONVERSION,
|
||||
);
|
||||
conversionState.nextState(conversion);
|
||||
|
||||
await expect(
|
||||
keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId),
|
||||
).rejects.toThrow(new Error("Key Connector error"));
|
||||
|
||||
expect(keyGenerationService.createKey).toHaveBeenCalledWith(512);
|
||||
expect(keyService.makeMasterKey).toHaveBeenCalledWith(
|
||||
passwordKey.keyB64,
|
||||
mockEmail,
|
||||
expectedKdfConfig,
|
||||
new PBKDF2KdfConfig(600_000),
|
||||
);
|
||||
expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(
|
||||
mockMasterKey,
|
||||
@@ -488,76 +753,29 @@ describe("KeyConnectorService", () => {
|
||||
Utils.fromBufferToB64(mockMasterKey.inner().encryptionKey),
|
||||
),
|
||||
);
|
||||
expect(apiService.postSetKeyConnectorKey).toHaveBeenCalledWith(
|
||||
new SetKeyConnectorKeyRequest(
|
||||
mockMakeUserKeyResult[1].encryptedString!,
|
||||
expectedKdfConfig,
|
||||
mockOrgId,
|
||||
new KeysRequest(mockKeyPair[0], mockKeyPair[1].encryptedString!),
|
||||
),
|
||||
expect(apiService.postSetKeyConnectorKey).not.toHaveBeenCalled();
|
||||
expect(await firstValueFrom(conversionState.state$)).toEqual(conversion);
|
||||
|
||||
expect(logoutCallback).toHaveBeenCalledWith("keyConnectorError");
|
||||
});
|
||||
|
||||
it("should throw error when conversion data is null", async () => {
|
||||
const conversionState = stateProvider.singleUser.getFake(
|
||||
mockUserId,
|
||||
NEW_SSO_USER_KEY_CONNECTOR_CONVERSION,
|
||||
);
|
||||
conversionState.nextState(null);
|
||||
|
||||
// Verify that conversion data is cleared from conversionState
|
||||
expect(await firstValueFrom(conversionState.state$)).toBeNull();
|
||||
},
|
||||
);
|
||||
await expect(
|
||||
keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId),
|
||||
).rejects.toThrow(new Error("Key Connector conversion not found"));
|
||||
|
||||
it("handles api error", async () => {
|
||||
apiService.postUserKeyToKeyConnector.mockRejectedValue(new Error("API error"));
|
||||
|
||||
const conversionState = stateProvider.singleUser.getFake(
|
||||
mockUserId,
|
||||
NEW_SSO_USER_KEY_CONNECTOR_CONVERSION,
|
||||
);
|
||||
conversionState.nextState(conversion);
|
||||
|
||||
await expect(keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId)).rejects.toThrow(
|
||||
new Error("Key Connector error"),
|
||||
);
|
||||
|
||||
expect(keyGenerationService.createKey).toHaveBeenCalledWith(512);
|
||||
expect(keyService.makeMasterKey).toHaveBeenCalledWith(
|
||||
passwordKey.keyB64,
|
||||
mockEmail,
|
||||
new PBKDF2KdfConfig(600_000),
|
||||
);
|
||||
expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(
|
||||
mockMasterKey,
|
||||
mockUserId,
|
||||
);
|
||||
expect(keyService.makeUserKey).toHaveBeenCalledWith(mockMasterKey);
|
||||
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, mockUserId);
|
||||
expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(
|
||||
mockMakeUserKeyResult[1],
|
||||
mockUserId,
|
||||
);
|
||||
expect(keyService.makeKeyPair).toHaveBeenCalledWith(mockMakeUserKeyResult[0]);
|
||||
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
|
||||
keyConnectorUrl,
|
||||
new KeyConnectorUserKeyRequest(Utils.fromBufferToB64(mockMasterKey.inner().encryptionKey)),
|
||||
);
|
||||
expect(apiService.postSetKeyConnectorKey).not.toHaveBeenCalled();
|
||||
expect(await firstValueFrom(conversionState.state$)).toEqual(conversion);
|
||||
|
||||
expect(logoutCallback).toHaveBeenCalledWith("keyConnectorError");
|
||||
});
|
||||
|
||||
it("should throw error when conversion data is null", async () => {
|
||||
const conversionState = stateProvider.singleUser.getFake(
|
||||
mockUserId,
|
||||
NEW_SSO_USER_KEY_CONNECTOR_CONVERSION,
|
||||
);
|
||||
conversionState.nextState(null);
|
||||
|
||||
await expect(keyConnectorService.convertNewSsoUserToKeyConnector(mockUserId)).rejects.toThrow(
|
||||
new Error("Key Connector conversion not found"),
|
||||
);
|
||||
|
||||
// Verify that no key generation or API calls were made
|
||||
expect(keyGenerationService.createKey).not.toHaveBeenCalled();
|
||||
expect(keyService.makeMasterKey).not.toHaveBeenCalled();
|
||||
expect(apiService.postUserKeyToKeyConnector).not.toHaveBeenCalled();
|
||||
expect(apiService.postSetKeyConnectorKey).not.toHaveBeenCalled();
|
||||
// Verify that no key generation or API calls were made
|
||||
expect(keyGenerationService.createKey).not.toHaveBeenCalled();
|
||||
expect(keyService.makeMasterKey).not.toHaveBeenCalled();
|
||||
expect(apiService.postUserKeyToKeyConnector).not.toHaveBeenCalled();
|
||||
expect(apiService.postSetKeyConnectorKey).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -9,22 +9,36 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { NewSsoUserKeyConnectorConversion } from "@bitwarden/common/key-management/key-connector/models/new-sso-user-key-connector-conversion";
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Argon2KdfConfig, KdfType, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management";
|
||||
import {
|
||||
Argon2KdfConfig,
|
||||
KdfConfig,
|
||||
KdfType,
|
||||
KeyService,
|
||||
PBKDF2KdfConfig,
|
||||
} from "@bitwarden/key-management";
|
||||
import { LogService } from "@bitwarden/logging";
|
||||
|
||||
import { ApiService } from "../../../abstractions/api.service";
|
||||
import { OrganizationService } from "../../../admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationUserType } from "../../../admin-console/enums";
|
||||
import { Organization } from "../../../admin-console/models/domain/organization";
|
||||
import { TokenService } from "../../../auth/abstractions/token.service";
|
||||
import { FeatureFlag } from "../../../enums/feature-flag.enum";
|
||||
import { KeysRequest } from "../../../models/request/keys.request";
|
||||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { ConfigService } from "../../../platform/abstractions/config/config.service";
|
||||
import { RegisterSdkService } from "../../../platform/abstractions/sdk/register-sdk.service";
|
||||
import { asUuid } from "../../../platform/abstractions/sdk/sdk.service";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { KEY_CONNECTOR_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { MasterKey } from "../../../types/key";
|
||||
import { MasterKey, UserKey } from "../../../types/key";
|
||||
import { AccountCryptographicStateService } from "../../account-cryptography/account-cryptographic-state.service";
|
||||
import { KeyGenerationService } from "../../crypto";
|
||||
import { EncString } from "../../crypto/models/enc-string";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction";
|
||||
import { SecurityStateService } from "../../security-state/abstractions/security-state.service";
|
||||
import { SignedPublicKey, SignedSecurityState, WrappedSigningKey } from "../../types";
|
||||
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/key-connector.service";
|
||||
import { KeyConnectorDomainConfirmation } from "../models/key-connector-domain-confirmation";
|
||||
import { KeyConnectorUserKeyRequest } from "../models/key-connector-user-key.request";
|
||||
@@ -75,6 +89,10 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
private keyGenerationService: KeyGenerationService,
|
||||
private logoutCallback: (logoutReason: LogoutReason, userId?: string) => Promise<void>,
|
||||
private stateProvider: StateProvider,
|
||||
private configService: ConfigService,
|
||||
private registerSdkService: RegisterSdkService,
|
||||
private securityStateService: SecurityStateService,
|
||||
private accountCryptographicStateService: AccountCryptographicStateService,
|
||||
) {
|
||||
this.convertAccountRequired$ = accountService.activeAccount$.pipe(
|
||||
filter((account) => account != null),
|
||||
@@ -152,8 +170,106 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
throw new Error("Key Connector conversion not found");
|
||||
}
|
||||
|
||||
const { kdfConfig, keyConnectorUrl, organizationId } = conversion;
|
||||
const { kdfConfig, keyConnectorUrl, organizationId: ssoOrganizationIdentifier } = conversion;
|
||||
|
||||
if (
|
||||
await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(
|
||||
FeatureFlag.EnableAccountEncryptionV2KeyConnectorRegistration,
|
||||
),
|
||||
)
|
||||
) {
|
||||
await this.convertNewSsoUserToKeyConnectorV2(
|
||||
userId,
|
||||
keyConnectorUrl,
|
||||
ssoOrganizationIdentifier,
|
||||
);
|
||||
} else {
|
||||
await this.convertNewSsoUserToKeyConnectorV1(
|
||||
userId,
|
||||
kdfConfig,
|
||||
keyConnectorUrl,
|
||||
ssoOrganizationIdentifier,
|
||||
);
|
||||
}
|
||||
|
||||
await this.stateProvider
|
||||
.getUser(userId, NEW_SSO_USER_KEY_CONNECTOR_CONVERSION)
|
||||
.update(() => null);
|
||||
}
|
||||
|
||||
async convertNewSsoUserToKeyConnectorV2(
|
||||
userId: UserId,
|
||||
keyConnectorUrl: string,
|
||||
ssoOrganizationIdentifier: string,
|
||||
) {
|
||||
const result = await firstValueFrom(
|
||||
this.registerSdkService.registerClient$(userId).pipe(
|
||||
map((sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
|
||||
using ref = sdk.take();
|
||||
|
||||
return ref.value
|
||||
.auth()
|
||||
.registration()
|
||||
.post_keys_for_key_connector_registration(
|
||||
keyConnectorUrl,
|
||||
ssoOrganizationIdentifier,
|
||||
asUuid(userId),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
if (!("V2" in result.account_cryptographic_state)) {
|
||||
const version = Object.keys(result.account_cryptographic_state);
|
||||
throw new Error(`Unexpected account cryptographic state version ${version}`);
|
||||
}
|
||||
|
||||
await this.masterPasswordService.setMasterKey(
|
||||
SymmetricCryptoKey.fromString(result.key_connector_key) as MasterKey,
|
||||
userId,
|
||||
);
|
||||
await this.keyService.setUserKey(
|
||||
SymmetricCryptoKey.fromString(result.user_key) as UserKey,
|
||||
userId,
|
||||
);
|
||||
await this.masterPasswordService.setMasterKeyEncryptedUserKey(
|
||||
new EncString(result.key_connector_key_wrapped_user_key),
|
||||
userId,
|
||||
);
|
||||
|
||||
await this.accountCryptographicStateService.setAccountCryptographicState(
|
||||
result.account_cryptographic_state,
|
||||
userId,
|
||||
);
|
||||
// Legacy states
|
||||
await this.keyService.setPrivateKey(result.account_cryptographic_state.V2.private_key, userId);
|
||||
await this.keyService.setUserSigningKey(
|
||||
result.account_cryptographic_state.V2.signing_key as WrappedSigningKey,
|
||||
userId,
|
||||
);
|
||||
await this.securityStateService.setAccountSecurityState(
|
||||
result.account_cryptographic_state.V2.security_state as SignedSecurityState,
|
||||
userId,
|
||||
);
|
||||
if (result.account_cryptographic_state.V2.signed_public_key != null) {
|
||||
await this.keyService.setSignedPublicKey(
|
||||
result.account_cryptographic_state.V2.signed_public_key as SignedPublicKey,
|
||||
userId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async convertNewSsoUserToKeyConnectorV1(
|
||||
userId: UserId,
|
||||
kdfConfig: KdfConfig,
|
||||
keyConnectorUrl: string,
|
||||
ssoOrganizationIdentifier: string,
|
||||
) {
|
||||
const password = await this.keyGenerationService.createKey(512);
|
||||
|
||||
const masterKey = await this.keyService.makeMasterKey(
|
||||
@@ -182,14 +298,10 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
const setPasswordRequest = new SetKeyConnectorKeyRequest(
|
||||
userKey[1].encryptedString,
|
||||
kdfConfig,
|
||||
organizationId,
|
||||
ssoOrganizationIdentifier,
|
||||
keys,
|
||||
);
|
||||
await this.apiService.postSetKeyConnectorKey(setPasswordRequest);
|
||||
|
||||
await this.stateProvider
|
||||
.getUser(userId, NEW_SSO_USER_KEY_CONNECTOR_CONVERSION)
|
||||
.update(() => null);
|
||||
}
|
||||
|
||||
async setNewSsoUserKeyConnectorConversionData(
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -23,8 +23,8 @@
|
||||
"@angular/platform-browser": "20.3.15",
|
||||
"@angular/platform-browser-dynamic": "20.3.15",
|
||||
"@angular/router": "20.3.15",
|
||||
"@bitwarden/commercial-sdk-internal": "0.2.0-main.439",
|
||||
"@bitwarden/sdk-internal": "0.2.0-main.439",
|
||||
"@bitwarden/commercial-sdk-internal": "0.2.0-main.450",
|
||||
"@bitwarden/sdk-internal": "0.2.0-main.450",
|
||||
"@electron/fuses": "1.8.0",
|
||||
"@emotion/css": "11.13.5",
|
||||
"@koa/multer": "4.0.0",
|
||||
@@ -4973,9 +4973,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bitwarden/commercial-sdk-internal": {
|
||||
"version": "0.2.0-main.439",
|
||||
"resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.439.tgz",
|
||||
"integrity": "sha512-Wujtym00U7XMEsf9zJ3/0Ggw9WmMcIpE9hMtcLryloX182118vnzkEQbEldqtywpMHiDsD9VmP6RiZ725nnUIQ==",
|
||||
"version": "0.2.0-main.450",
|
||||
"resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.450.tgz",
|
||||
"integrity": "sha512-WCihR6ykpIfaqJBHl4Wou4xDB8mp+5UPi94eEKYUdkx/9/19YyX33SX9H56zEriOuOMCD8l2fymhzAFjAAB++g==",
|
||||
"license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT",
|
||||
"dependencies": {
|
||||
"type-fest": "^4.41.0"
|
||||
@@ -5078,9 +5078,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bitwarden/sdk-internal": {
|
||||
"version": "0.2.0-main.439",
|
||||
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.439.tgz",
|
||||
"integrity": "sha512-uvIS8erGmzgWCZom7Kt78C4n4tbjfZuTCn7+y2+E8BTtLBqIZNtl4kC0tNh8c4GUWsmoIYlbQyz+HymWQ7J+QA==",
|
||||
"version": "0.2.0-main.450",
|
||||
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.450.tgz",
|
||||
"integrity": "sha512-XRhrBN0uoo66ONx7dYo9glhe9N451+VhwtC/oh3wo3j3qYxbPwf9yE98szlQ52u3iUExLisiYJY7sQNzhZrbZw==",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"type-fest": "^4.41.0"
|
||||
|
||||
@@ -162,8 +162,8 @@
|
||||
"@angular/platform-browser": "20.3.15",
|
||||
"@angular/platform-browser-dynamic": "20.3.15",
|
||||
"@angular/router": "20.3.15",
|
||||
"@bitwarden/sdk-internal": "0.2.0-main.439",
|
||||
"@bitwarden/commercial-sdk-internal": "0.2.0-main.439",
|
||||
"@bitwarden/sdk-internal": "0.2.0-main.450",
|
||||
"@bitwarden/commercial-sdk-internal": "0.2.0-main.450",
|
||||
"@electron/fuses": "1.8.0",
|
||||
"@emotion/css": "11.13.5",
|
||||
"@koa/multer": "4.0.0",
|
||||
|
||||
Reference in New Issue
Block a user