[PM-27233] Support v2 encryption for JIT Password signups (#18222)

* Support v2 encryption for JIT Password signups

* TDE set master password split

* update sdk-internal dependency

* moved encryption v2 to InitializeJitPasswordUserService

* remove account cryptographic state legacy states from #18164

* legacy state comments

* sdk update

* unit test coverage

* consolidate do SetInitialPasswordService

* replace legacy master key with setLegacyMasterKeyFromUnlockData

* typo

* web and desktop overrides with unit tests

* early return

* compact validation

* simplify super prototype
This commit is contained in:
Maciej Zieniuk
2026-01-22 13:01:49 +01:00
committed by GitHub
parent d0e3923eb6
commit 1ccacb03a6
14 changed files with 693 additions and 110 deletions

View File

@@ -89,6 +89,7 @@ import {
PlatformUtilsService,
PlatformUtilsService as PlatformUtilsServiceAbstraction,
} from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
@@ -432,6 +433,7 @@ const safeProviders: SafeProvider[] = [
InternalUserDecryptionOptionsServiceAbstraction,
MessagingServiceAbstraction,
AccountCryptographicStateService,
RegisterSdkService,
],
}),
safeProvider({

View File

@@ -1,8 +1,10 @@
import { MockProxy, mock } from "jest-mock-extended";
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, of } from "rxjs";
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
import { DefaultSetInitialPasswordService } from "@bitwarden/angular/auth/password-management/set-initial-password/default-set-initial-password.service.implementation";
import {
InitializeJitPasswordCredentials,
SetInitialPasswordCredentials,
SetInitialPasswordService,
SetInitialPasswordUserType,
@@ -19,12 +21,14 @@ import { AccountCryptographicStateService } from "@bitwarden/common/key-manageme
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management";
@@ -45,6 +49,7 @@ describe("DesktopSetInitialPasswordService", () => {
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
let messagingService: MockProxy<MessagingService>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
let registerSdkService: MockProxy<RegisterSdkService>;
beforeEach(() => {
apiService = mock<ApiService>();
@@ -59,6 +64,7 @@ describe("DesktopSetInitialPasswordService", () => {
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
messagingService = mock<MessagingService>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
registerSdkService = mock<RegisterSdkService>();
sut = new DesktopSetInitialPasswordService(
apiService,
@@ -73,6 +79,7 @@ describe("DesktopSetInitialPasswordService", () => {
userDecryptionOptionsService,
messagingService,
accountCryptographicStateService,
registerSdkService,
);
});
@@ -179,4 +186,36 @@ describe("DesktopSetInitialPasswordService", () => {
});
});
});
describe("initializePasswordJitPasswordUserV2Encryption(...)", () => {
it("should send a 'redrawMenu' message", async () => {
// Arrange
const credentials: InitializeJitPasswordCredentials = {
newPasswordHint: "newPasswordHint",
orgSsoIdentifier: "orgSsoIdentifier",
orgId: "orgId" as OrganizationId,
resetPasswordAutoEnroll: false,
newPassword: "newPassword123!",
salt: "user@example.com" as MasterPasswordSalt,
};
const userId = "userId" as UserId;
const superSpy = jest
.spyOn(
DefaultSetInitialPasswordService.prototype,
"initializePasswordJitPasswordUserV2Encryption",
)
.mockResolvedValue(undefined);
// Act
await sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
// Assert
expect(superSpy).toHaveBeenCalledWith(credentials, userId);
expect(messagingService.send).toHaveBeenCalledTimes(1);
expect(messagingService.send).toHaveBeenCalledWith("redrawMenu");
superSpy.mockRestore();
});
});
});

View File

@@ -1,6 +1,7 @@
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
import { DefaultSetInitialPasswordService } from "@bitwarden/angular/auth/password-management/set-initial-password/default-set-initial-password.service.implementation";
import {
InitializeJitPasswordCredentials,
SetInitialPasswordCredentials,
SetInitialPasswordService,
SetInitialPasswordUserType,
@@ -14,6 +15,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
import { UserId } from "@bitwarden/common/types/guid";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
@@ -34,6 +36,7 @@ export class DesktopSetInitialPasswordService
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
private messagingService: MessagingService,
protected accountCryptographicStateService: AccountCryptographicStateService,
protected registerSdkService: RegisterSdkService,
) {
super(
apiService,
@@ -47,6 +50,7 @@ export class DesktopSetInitialPasswordService
organizationUserApiService,
userDecryptionOptionsService,
accountCryptographicStateService,
registerSdkService,
);
}
@@ -59,4 +63,13 @@ export class DesktopSetInitialPasswordService
this.messagingService.send("redrawMenu");
}
override async initializePasswordJitPasswordUserV2Encryption(
credentials: InitializeJitPasswordCredentials,
userId: UserId,
): Promise<void> {
await super.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
this.messagingService.send("redrawMenu");
}
}

View File

@@ -3,6 +3,7 @@ import { BehaviorSubject, of } from "rxjs";
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
import {
InitializeJitPasswordCredentials,
SetInitialPasswordCredentials,
SetInitialPasswordService,
SetInitialPasswordUserType,
@@ -20,11 +21,13 @@ import { AccountCryptographicStateService } from "@bitwarden/common/key-manageme
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management";
import { RouterService } from "@bitwarden/web-vault/app/core";
@@ -47,6 +50,7 @@ describe("WebSetInitialPasswordService", () => {
let organizationInviteService: MockProxy<OrganizationInviteService>;
let routerService: MockProxy<RouterService>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
let registerSdkService: MockProxy<RegisterSdkService>;
beforeEach(() => {
apiService = mock<ApiService>();
@@ -62,6 +66,7 @@ describe("WebSetInitialPasswordService", () => {
organizationInviteService = mock<OrganizationInviteService>();
routerService = mock<RouterService>();
accountCryptographicStateService = mock<AccountCryptographicStateService>();
registerSdkService = mock<RegisterSdkService>();
sut = new WebSetInitialPasswordService(
apiService,
@@ -77,6 +82,7 @@ describe("WebSetInitialPasswordService", () => {
organizationInviteService,
routerService,
accountCryptographicStateService,
registerSdkService,
);
});
@@ -208,4 +214,36 @@ describe("WebSetInitialPasswordService", () => {
});
});
});
describe("initializePasswordJitPasswordUserV2Encryption(...)", () => {
it("should call routerService.getAndClearLoginRedirectUrl() and organizationInviteService.clearOrganizationInvitation()", async () => {
// Arrange
const credentials: InitializeJitPasswordCredentials = {
newPasswordHint: "newPasswordHint",
orgSsoIdentifier: "orgSsoIdentifier",
orgId: "orgId" as OrganizationId,
resetPasswordAutoEnroll: false,
newPassword: "newPassword123!",
salt: "user@example.com" as MasterPasswordSalt,
};
const userId = "userId" as UserId;
const superSpy = jest
.spyOn(
Object.getPrototypeOf(Object.getPrototypeOf(sut)),
"initializePasswordJitPasswordUserV2Encryption",
)
.mockResolvedValue(undefined);
// Act
await sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
// Assert
expect(superSpy).toHaveBeenCalledWith(credentials, userId);
expect(routerService.getAndClearLoginRedirectUrl).toHaveBeenCalledTimes(1);
expect(organizationInviteService.clearOrganizationInvitation).toHaveBeenCalledTimes(1);
superSpy.mockRestore();
});
});
});

View File

@@ -1,6 +1,7 @@
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
import { DefaultSetInitialPasswordService } from "@bitwarden/angular/auth/password-management/set-initial-password/default-set-initial-password.service.implementation";
import {
InitializeJitPasswordCredentials,
SetInitialPasswordCredentials,
SetInitialPasswordService,
SetInitialPasswordUserType,
@@ -14,6 +15,7 @@ import { AccountCryptographicStateService } from "@bitwarden/common/key-manageme
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
import { UserId } from "@bitwarden/common/types/guid";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
import { RouterService } from "@bitwarden/web-vault/app/core";
@@ -36,6 +38,7 @@ export class WebSetInitialPasswordService
private organizationInviteService: OrganizationInviteService,
private routerService: RouterService,
protected accountCryptographicStateService: AccountCryptographicStateService,
protected registerSdkService: RegisterSdkService,
) {
super(
apiService,
@@ -49,6 +52,7 @@ export class WebSetInitialPasswordService
organizationUserApiService,
userDecryptionOptionsService,
accountCryptographicStateService,
registerSdkService,
);
}
@@ -83,4 +87,15 @@ export class WebSetInitialPasswordService
await this.routerService.getAndClearLoginRedirectUrl();
await this.organizationInviteService.clearOrganizationInvitation();
}
override async initializePasswordJitPasswordUserV2Encryption(
credentials: InitializeJitPasswordCredentials,
userId: UserId,
): Promise<void> {
await super.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
// TODO: Investigate refactoring the following logic in https://bitwarden.atlassian.net/browse/PM-22615
await this.routerService.getAndClearLoginRedirectUrl();
await this.organizationInviteService.clearOrganizationInvitation();
}
}

View File

@@ -6,11 +6,11 @@ import { Router } from "@angular/router";
import {
CollectionAdminService,
DefaultCollectionAdminService,
OrganizationUserApiService,
CollectionService,
OrganizationUserService,
DefaultCollectionAdminService,
DefaultOrganizationUserService,
OrganizationUserApiService,
OrganizationUserService,
} from "@bitwarden/admin-console/common";
import { DefaultDeviceManagementComponentService } from "@bitwarden/angular/auth/device-management/default-device-management-component.service";
import { DeviceManagementComponentServiceAbstraction } from "@bitwarden/angular/auth/device-management/device-management-component.service.abstraction";
@@ -27,17 +27,17 @@ import {
OBSERVABLE_DISK_LOCAL_STORAGE,
OBSERVABLE_DISK_STORAGE,
OBSERVABLE_MEMORY_STORAGE,
SafeInjectionToken,
SECURE_STORAGE,
SYSTEM_LANGUAGE,
SafeInjectionToken,
WINDOW,
} from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import {
RegistrationFinishService as RegistrationFinishServiceAbstraction,
LoginComponentService,
SsoComponentService,
LoginDecryptionOptionsService,
RegistrationFinishService as RegistrationFinishServiceAbstraction,
SsoComponentService,
TwoFactorAuthDuoComponentService,
} from "@bitwarden/auth/angular";
import {
@@ -90,6 +90,7 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
@@ -120,9 +121,9 @@ import { DialogService, ToastService } from "@bitwarden/components";
import { GeneratorServicesModule } from "@bitwarden/generator-components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import {
BiometricsService,
KdfConfigService,
KeyService as KeyServiceAbstraction,
BiometricsService,
} from "@bitwarden/key-management";
import {
LockComponentService,
@@ -135,17 +136,17 @@ import { WebVaultPremiumUpgradePromptService } from "@bitwarden/web-vault/app/va
import { flagEnabled } from "../../utils/flags";
import {
POLICY_EDIT_REGISTER,
ossPolicyEditRegister,
POLICY_EDIT_REGISTER,
} from "../admin-console/organizations/policies";
import {
LinkSsoService,
WebChangePasswordService,
WebRegistrationFinishService,
WebLoginComponentService,
WebLoginDecryptionOptionsService,
WebTwoFactorAuthDuoComponentService,
LinkSsoService,
WebRegistrationFinishService,
WebSetInitialPasswordService,
WebTwoFactorAuthDuoComponentService,
} from "../auth";
import { WebSsoComponentService } from "../auth/core/services/login/web-sso-component.service";
import { WebPremiumInterestStateService } from "../billing/services/premium-interest/web-premium-interest-state.service";
@@ -320,6 +321,7 @@ const safeProviders: SafeProvider[] = [
OrganizationInviteService,
RouterService,
AccountCryptographicStateService,
RegisterSdkService,
],
}),
safeProvider({

View File

@@ -1,4 +1,4 @@
import { firstValueFrom } from "rxjs";
import { concatMap, firstValueFrom } from "rxjs";
// 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
@@ -19,19 +19,32 @@ import { AccountCryptographicStateService } from "@bitwarden/common/key-manageme
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import {
MasterPasswordSalt,
MasterPasswordUnlockData,
} from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
import { asUuid } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { KdfConfigService, KeyService, KdfConfig } from "@bitwarden/key-management";
import {
fromSdkKdfConfig,
KdfConfig,
KdfConfigService,
KeyService,
} from "@bitwarden/key-management";
import { OrganizationId as SdkOrganizationId, UserId as SdkUserId } from "@bitwarden/sdk-internal";
import {
SetInitialPasswordService,
InitializeJitPasswordCredentials,
SetInitialPasswordCredentials,
SetInitialPasswordUserType,
SetInitialPasswordService,
SetInitialPasswordTdeOffboardingCredentials,
SetInitialPasswordUserType,
} from "./set-initial-password.service.abstraction";
export class DefaultSetInitialPasswordService implements SetInitialPasswordService {
@@ -47,6 +60,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
protected organizationUserApiService: OrganizationUserApiService,
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
protected accountCryptographicStateService: AccountCryptographicStateService,
protected registerSdkService: RegisterSdkService,
) {}
async setInitialPassword(
@@ -199,6 +213,126 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
}
}
async setInitialPasswordTdeOffboarding(
credentials: SetInitialPasswordTdeOffboardingCredentials,
userId: UserId,
) {
const { newMasterKey, newServerMasterKeyHash, newPasswordHint } = credentials;
for (const [key, value] of Object.entries(credentials)) {
if (value == null) {
throw new Error(`${key} not found. Could not set password.`);
}
}
if (userId == null) {
throw new Error("userId not found. Could not set password.");
}
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
throw new Error("userKey not found. Could not set password.");
}
const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(
newMasterKey,
userKey,
);
if (!newMasterKeyEncryptedUserKey[1].encryptedString) {
throw new Error("newMasterKeyEncryptedUserKey not found. Could not set password.");
}
const request = new UpdateTdeOffboardingPasswordRequest();
request.key = newMasterKeyEncryptedUserKey[1].encryptedString;
request.newMasterPasswordHash = newServerMasterKeyHash;
request.masterPasswordHint = newPasswordHint;
await this.masterPasswordApiService.putUpdateTdeOffboardingPassword(request);
// Clear force set password reason to allow navigation back to vault.
await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
}
async initializePasswordJitPasswordUserV2Encryption(
credentials: InitializeJitPasswordCredentials,
userId: UserId,
): Promise<void> {
if (userId == null) {
throw new Error("User ID is required.");
}
for (const [key, value] of Object.entries(credentials)) {
if (value == null) {
throw new Error(`${key} is required.`);
}
}
const { newPasswordHint, orgSsoIdentifier, orgId, resetPasswordAutoEnroll, newPassword, salt } =
credentials;
const organizationKeys = await this.organizationApiService.getKeys(orgId);
if (organizationKeys == null) {
throw new Error("Organization keys response is null.");
}
const registerResult = await firstValueFrom(
this.registerSdkService.registerClient$(userId).pipe(
concatMap(async (sdk) => {
if (!sdk) {
throw new Error("SDK not available");
}
using ref = sdk.take();
return await ref.value
.auth()
.registration()
.post_keys_for_jit_password_registration({
org_id: asUuid<SdkOrganizationId>(orgId),
org_public_key: organizationKeys.publicKey,
master_password: newPassword,
master_password_hint: newPasswordHint,
salt: salt,
organization_sso_identifier: orgSsoIdentifier,
user_id: asUuid<SdkUserId>(userId),
reset_password_enroll: resetPasswordAutoEnroll,
});
}),
),
);
if (!("V2" in registerResult.account_cryptographic_state)) {
throw new Error("Unexpected V2 account cryptographic state");
}
// Note: When SDK state management matures, these should be moved into post_keys_for_tde_registration
// Set account cryptography state
await this.accountCryptographicStateService.setAccountCryptographicState(
registerResult.account_cryptographic_state,
userId,
);
// Clear force set password reason to allow navigation back to vault.
await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
const masterPasswordUnlockData = MasterPasswordUnlockData.fromSdk(
registerResult.master_password_unlock,
);
await this.masterPasswordService.setMasterPasswordUnlockData(masterPasswordUnlockData, userId);
await this.keyService.setUserKey(
SymmetricCryptoKey.fromString(registerResult.user_key) as UserKey,
userId,
);
await this.updateLegacyState(
newPassword,
fromSdkKdfConfig(registerResult.master_password_unlock.kdf),
new EncString(registerResult.master_password_unlock.masterKeyWrappedUserKey),
userId,
masterPasswordUnlockData,
);
}
private async makeMasterKeyEncryptedUserKey(
masterKey: MasterKey,
userId: UserId,
@@ -244,6 +378,37 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
await this.keyService.setUserKey(masterKeyEncryptedUserKey[0], userId);
}
// Deprecated legacy support - to be removed in future
private async updateLegacyState(
newPassword: string,
kdfConfig: KdfConfig,
masterKeyWrappedUserKey: EncString,
userId: UserId,
masterPasswordUnlockData: MasterPasswordUnlockData,
) {
// TODO Remove HasMasterPassword from UserDecryptionOptions https://bitwarden.atlassian.net/browse/PM-23475
const userDecryptionOpts = await firstValueFrom(
this.userDecryptionOptionsService.userDecryptionOptionsById$(userId),
);
userDecryptionOpts.hasMasterPassword = true;
await this.userDecryptionOptionsService.setUserDecryptionOptionsById(
userId,
userDecryptionOpts,
);
// TODO Remove KDF state https://bitwarden.atlassian.net/browse/PM-30661
await this.kdfConfigService.setKdfConfig(userId, kdfConfig);
// TODO Remove master key memory state https://bitwarden.atlassian.net/browse/PM-23477
await this.masterPasswordService.setMasterKeyEncryptedUserKey(masterKeyWrappedUserKey, userId);
// TODO Removed with https://bitwarden.atlassian.net/browse/PM-30676
await this.masterPasswordService.setLegacyMasterKeyFromUnlockData(
newPassword,
masterPasswordUnlockData,
userId,
);
}
/**
* As part of [PM-28494], adding this setting path to accommodate the changes that are
* emerging with pm-23246-unlock-with-master-password-unlock-data.
@@ -310,44 +475,4 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
enrollmentRequest,
);
}
async setInitialPasswordTdeOffboarding(
credentials: SetInitialPasswordTdeOffboardingCredentials,
userId: UserId,
) {
const { newMasterKey, newServerMasterKeyHash, newPasswordHint } = credentials;
for (const [key, value] of Object.entries(credentials)) {
if (value == null) {
throw new Error(`${key} not found. Could not set password.`);
}
}
if (userId == null) {
throw new Error("userId not found. Could not set password.");
}
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
throw new Error("userKey not found. Could not set password.");
}
const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(
newMasterKey,
userKey,
);
if (!newMasterKeyEncryptedUserKey[1].encryptedString) {
throw new Error("newMasterKeyEncryptedUserKey not found. Could not set password.");
}
const request = new UpdateTdeOffboardingPasswordRequest();
request.key = newMasterKeyEncryptedUserKey[1].encryptedString;
request.newMasterPasswordHash = newServerMasterKeyHash;
request.masterPasswordHint = newPasswordHint;
await this.masterPasswordApiService.putUpdateTdeOffboardingPassword(request);
// Clear force set password reason to allow navigation back to vault.
await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
}
}

View File

@@ -1,5 +1,8 @@
import { MockProxy, mock } from "jest-mock-extended";
import { BehaviorSubject, of } from "rxjs";
// Polyfill for Symbol.dispose required by the service's use of `using` keyword
import "core-js/proposals/explicit-resource-management";
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, Observable, of } from "rxjs";
// 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
@@ -27,17 +30,35 @@ import {
EncString,
} from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import {
MasterPasswordSalt,
MasterPasswordUnlockData,
} from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
import { Rc } from "@bitwarden/common/platform/misc/reference-counting/rc";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { makeEncString, makeSymmetricCryptoKey } from "@bitwarden/common/spec";
import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey, UserPrivateKey, UserPublicKey } from "@bitwarden/common/types/key";
import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management";
import {
DEFAULT_KDF_CONFIG,
fromSdkKdfConfig,
KdfConfigService,
KeyService,
} from "@bitwarden/key-management";
import {
AuthClient,
BitwardenClient,
WrappedAccountCryptographicState,
} from "@bitwarden/sdk-internal";
import { DefaultSetInitialPasswordService } from "./default-set-initial-password.service.implementation";
import {
InitializeJitPasswordCredentials,
SetInitialPasswordCredentials,
SetInitialPasswordService,
SetInitialPasswordTdeOffboardingCredentials,
@@ -58,6 +79,7 @@ describe("DefaultSetInitialPasswordService", () => {
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
const registerSdkService = mock<RegisterSdkService>();
let userId: UserId;
let userKey: UserKey;
@@ -94,6 +116,7 @@ describe("DefaultSetInitialPasswordService", () => {
organizationUserApiService,
userDecryptionOptionsService,
accountCryptographicStateService,
registerSdkService,
);
});
@@ -834,4 +857,246 @@ describe("DefaultSetInitialPasswordService", () => {
});
});
});
describe("initializePasswordJitPasswordUserV2Encryption()", () => {
let mockSdkRef: {
value: MockProxy<BitwardenClient>;
[Symbol.dispose]: jest.Mock;
};
let mockSdk: {
take: jest.Mock;
};
let mockRegistration: jest.Mock;
const userId = "d4e2e3a1-1b5e-4c3b-8d7a-9f8e7d6c5b4a" as UserId;
const orgId = "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d" as OrganizationId;
const credentials: InitializeJitPasswordCredentials = {
newPasswordHint: "test-hint",
orgSsoIdentifier: "org-sso-id",
orgId: orgId,
resetPasswordAutoEnroll: false,
newPassword: "Test@Password123!",
salt: "user@example.com" as unknown as MasterPasswordSalt,
};
const orgKeys: OrganizationKeysResponse = {
publicKey: "org-public-key-base64",
privateKey: "org-private-key-encrypted",
} as OrganizationKeysResponse;
const sdkRegistrationResult = {
account_cryptographic_state: {
V2: {
private_key: makeEncString().encryptedString!,
signed_public_key: "test-signed-public-key",
signing_key: makeEncString().encryptedString!,
security_state: "test-security-state",
},
},
master_password_unlock: {
kdf: {
pBKDF2: {
iterations: 600000,
},
},
masterKeyWrappedUserKey: makeEncString().encryptedString!,
salt: "user@example.com" as unknown as MasterPasswordSalt,
},
user_key: makeSymmetricCryptoKey(64).keyB64,
};
beforeEach(() => {
jest.clearAllMocks();
mockSdkRef = {
value: mock<BitwardenClient>(),
[Symbol.dispose]: jest.fn(),
};
mockSdkRef.value.auth.mockReturnValue({
registration: jest.fn().mockReturnValue({
post_keys_for_jit_password_registration: jest.fn(),
}),
} as unknown as AuthClient);
mockSdk = {
take: jest.fn().mockReturnValue(mockSdkRef),
};
registerSdkService.registerClient$.mockReturnValue(
of(mockSdk) as unknown as Observable<Rc<BitwardenClient>>,
);
organizationApiService.getKeys.mockResolvedValue(orgKeys);
mockRegistration = mockSdkRef.value.auth().registration()
.post_keys_for_jit_password_registration as unknown as jest.Mock;
mockRegistration.mockResolvedValue(sdkRegistrationResult);
const mockUserDecryptionOpts = new UserDecryptionOptions({ hasMasterPassword: false });
userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue(
of(mockUserDecryptionOpts),
);
});
it("should successfully initialize JIT password user", async () => {
await sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
expect(organizationApiService.getKeys).toHaveBeenCalledWith(credentials.orgId);
expect(registerSdkService.registerClient$).toHaveBeenCalledWith(userId);
expect(mockRegistration).toHaveBeenCalledWith(
expect.objectContaining({
org_id: credentials.orgId,
org_public_key: orgKeys.publicKey,
master_password: credentials.newPassword,
master_password_hint: credentials.newPasswordHint,
salt: credentials.salt,
organization_sso_identifier: credentials.orgSsoIdentifier,
user_id: userId,
reset_password_enroll: credentials.resetPasswordAutoEnroll,
}),
);
expect(accountCryptographicStateService.setAccountCryptographicState).toHaveBeenCalledWith(
sdkRegistrationResult.account_cryptographic_state,
userId,
);
expect(masterPasswordService.setForceSetPasswordReason).toHaveBeenCalledWith(
ForceSetPasswordReason.None,
userId,
);
expect(masterPasswordService.setMasterPasswordUnlockData).toHaveBeenCalledWith(
MasterPasswordUnlockData.fromSdk(sdkRegistrationResult.master_password_unlock),
userId,
);
expect(keyService.setUserKey).toHaveBeenCalledWith(
SymmetricCryptoKey.fromString(sdkRegistrationResult.user_key) as UserKey,
userId,
);
// Verify legacy state updates below
expect(userDecryptionOptionsService.userDecryptionOptionsById$).toHaveBeenCalledWith(userId);
expect(userDecryptionOptionsService.setUserDecryptionOptionsById).toHaveBeenCalledWith(
userId,
expect.objectContaining({ hasMasterPassword: true }),
);
expect(kdfConfigService.setKdfConfig).toHaveBeenCalledWith(
userId,
fromSdkKdfConfig(sdkRegistrationResult.master_password_unlock.kdf),
);
expect(masterPasswordService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(
new EncString(sdkRegistrationResult.master_password_unlock.masterKeyWrappedUserKey),
userId,
);
expect(masterPasswordService.setLegacyMasterKeyFromUnlockData).toHaveBeenCalledWith(
credentials.newPassword,
MasterPasswordUnlockData.fromSdk(sdkRegistrationResult.master_password_unlock),
userId,
);
});
describe("input validation", () => {
it.each([
"newPasswordHint",
"orgSsoIdentifier",
"orgId",
"resetPasswordAutoEnroll",
"newPassword",
"salt",
])("should throw error when %s is null", async (field) => {
const invalidCredentials = {
...credentials,
[field]: null,
} as unknown as InitializeJitPasswordCredentials;
const promise = sut.initializePasswordJitPasswordUserV2Encryption(
invalidCredentials,
userId,
);
await expect(promise).rejects.toThrow(`${field} is required.`);
expect(organizationApiService.getKeys).not.toHaveBeenCalled();
expect(registerSdkService.registerClient$).not.toHaveBeenCalled();
});
it("should throw error when userId is null", async () => {
const nullUserId = null as unknown as UserId;
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, nullUserId);
await expect(promise).rejects.toThrow("User ID is required.");
expect(organizationApiService.getKeys).not.toHaveBeenCalled();
});
});
describe("organization API error handling", () => {
it("should throw when organizationApiService.getKeys returns null", async () => {
organizationApiService.getKeys.mockResolvedValue(
null as unknown as OrganizationKeysResponse,
);
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
await expect(promise).rejects.toThrow("Organization keys response is null.");
expect(organizationApiService.getKeys).toHaveBeenCalledWith(credentials.orgId);
expect(registerSdkService.registerClient$).not.toHaveBeenCalled();
});
it("should throw when organizationApiService.getKeys rejects", async () => {
const apiError = new Error("API network error");
organizationApiService.getKeys.mockRejectedValue(apiError);
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
await expect(promise).rejects.toThrow("API network error");
expect(registerSdkService.registerClient$).not.toHaveBeenCalled();
});
});
describe("SDK error handling", () => {
it("should throw when SDK is not available", async () => {
organizationApiService.getKeys.mockResolvedValue(orgKeys);
registerSdkService.registerClient$.mockReturnValue(
of(null) as unknown as Observable<Rc<BitwardenClient>>,
);
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
await expect(promise).rejects.toThrow("SDK not available");
});
it("should throw when SDK registration fails", async () => {
const sdkError = new Error("SDK crypto operation failed");
organizationApiService.getKeys.mockResolvedValue(orgKeys);
mockRegistration.mockRejectedValue(sdkError);
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
await expect(promise).rejects.toThrow("SDK crypto operation failed");
});
});
it("should throw when account_cryptographic_state is not V2", async () => {
const invalidResult = {
...sdkRegistrationResult,
account_cryptographic_state: { V1: {} } as unknown as WrappedAccountCryptographicState,
};
mockRegistration.mockResolvedValue(invalidResult);
const promise = sut.initializePasswordJitPasswordUserV2Encryption(credentials, userId);
await expect(promise).rejects.toThrow("Unexpected V2 account cryptographic state");
});
});
});

View File

@@ -21,14 +21,16 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { assertTruthy, assertNonNullish } from "@bitwarden/common/auth/utils";
import { assertNonNullish, assertTruthy } from "@bitwarden/common/auth/utils";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { SyncService } from "@bitwarden/common/platform/sync";
import { UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import {
AnonLayoutWrapperDataService,
ButtonModule,
@@ -39,6 +41,7 @@ import {
import { I18nPipe } from "@bitwarden/ui-common";
import {
InitializeJitPasswordCredentials,
SetInitialPasswordCredentials,
SetInitialPasswordService,
SetInitialPasswordTdeOffboardingCredentials,
@@ -86,6 +89,7 @@ export class SetInitialPasswordComponent implements OnInit {
private syncService: SyncService,
private toastService: ToastService,
private validationService: ValidationService,
private configService: ConfigService,
) {}
async ngOnInit() {
@@ -101,6 +105,51 @@ export class SetInitialPasswordComponent implements OnInit {
this.initializing = false;
}
protected async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
this.submitting = true;
switch (this.userType) {
case SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER: {
const accountEncryptionV2 = await this.configService.getFeatureFlag(
FeatureFlag.EnableAccountEncryptionV2JitPasswordRegistration,
);
if (accountEncryptionV2) {
await this.setInitialPasswordJitMPUserV2Encryption(passwordInputResult);
return;
}
await this.setInitialPassword(passwordInputResult);
break;
}
case SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP:
await this.setInitialPassword(passwordInputResult);
break;
case SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER:
await this.setInitialPasswordTdeOffboarding(passwordInputResult);
break;
default:
this.logService.error(
`Unexpected user type: ${this.userType}. Could not set initial password.`,
);
this.validationService.showError("Unexpected user type. Could not set initial password.");
}
}
protected async logout() {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "logOut" },
content: { key: "logOutConfirmation" },
acceptButtonText: { key: "logOut" },
type: "warning",
});
if (confirmed) {
this.messagingService.send("logout");
}
}
private async establishUserType() {
if (!this.userId) {
throw new Error("userId not found. Could not determine user type.");
@@ -189,22 +238,39 @@ export class SetInitialPasswordComponent implements OnInit {
}
}
protected async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
this.submitting = true;
private async setInitialPasswordJitMPUserV2Encryption(passwordInputResult: PasswordInputResult) {
const ctx = "Could not set initial password for SSO JIT master password encryption user.";
assertTruthy(passwordInputResult.newPassword, "newPassword", ctx);
assertTruthy(passwordInputResult.salt, "salt", ctx);
assertTruthy(this.orgSsoIdentifier, "orgSsoIdentifier", ctx);
assertTruthy(this.orgId, "orgId", ctx);
assertTruthy(this.userId, "userId", ctx);
assertNonNullish(passwordInputResult.newPasswordHint, "newPasswordHint", ctx); // can have an empty string as a valid value, so check non-nullish
assertNonNullish(this.resetPasswordAutoEnroll, "resetPasswordAutoEnroll", ctx); // can have `false` as a valid value, so check non-nullish
switch (this.userType) {
case SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER:
case SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP:
await this.setInitialPassword(passwordInputResult);
break;
case SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER:
await this.setInitialPasswordTdeOffboarding(passwordInputResult);
break;
default:
this.logService.error(
`Unexpected user type: ${this.userType}. Could not set initial password.`,
);
this.validationService.showError("Unexpected user type. Could not set initial password.");
try {
const credentials: InitializeJitPasswordCredentials = {
newPasswordHint: passwordInputResult.newPasswordHint,
orgSsoIdentifier: this.orgSsoIdentifier,
orgId: this.orgId as OrganizationId,
resetPasswordAutoEnroll: this.resetPasswordAutoEnroll,
newPassword: passwordInputResult.newPassword,
salt: passwordInputResult.salt,
};
await this.setInitialPasswordService.initializePasswordJitPasswordUserV2Encryption(
credentials,
this.userId,
);
this.showSuccessToastByUserType();
this.submitting = false;
await this.router.navigate(["vault"]);
} catch (e) {
this.logService.error("Error setting initial password", e);
this.validationService.showError(e);
this.submitting = false;
}
}
@@ -307,17 +373,4 @@ export class SetInitialPasswordComponent implements OnInit {
});
}
}
protected async logout() {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "logOut" },
content: { key: "logOutConfirmation" },
acceptButtonText: { key: "logOut" },
type: "warning",
});
if (confirmed) {
this.messagingService.send("logout");
}
}
}

View File

@@ -1,5 +1,5 @@
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { MasterKey } from "@bitwarden/common/types/key";
import { KdfConfig } from "@bitwarden/key-management";
@@ -61,6 +61,24 @@ export interface SetInitialPasswordTdeOffboardingCredentials {
newPasswordHint: string;
}
/**
* Credentials required to initialize a just-in-time (JIT) provisioned user with a master password.
*/
export interface InitializeJitPasswordCredentials {
/** Hint for the new master password */
newPasswordHint: string;
/** SSO identifier for the organization */
orgSsoIdentifier: string;
/** Organization ID */
orgId: OrganizationId;
/** Whether to auto-enroll the user in account recovery (reset password) */
resetPasswordAutoEnroll: boolean;
/** The new master password */
newPassword: string;
/** Master password salt (typically the user's email) */
salt: MasterPasswordSalt;
}
/**
* Handles setting an initial password for an existing authed user.
*
@@ -95,4 +113,14 @@ export abstract class SetInitialPasswordService {
credentials: SetInitialPasswordTdeOffboardingCredentials,
userId: UserId,
) => Promise<void>;
/**
* Initializes a JIT-provisioned user's cryptographic state and enrolls them in master password unlock.
* @param credentials The credentials needed to initialize the JIT password user
* @param userId The account userId
*/
abstract initializePasswordJitPasswordUserV2Encryption(
credentials: InitializeJitPasswordCredentials,
userId: UserId,
): Promise<void>;
}

View File

@@ -108,7 +108,7 @@ import { UserVerificationService as UserVerificationServiceAbstraction } from "@
import { WebAuthnLoginApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-api.service.abstraction";
import { WebAuthnLoginPrfKeyServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-prf-key.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { SendTokenService, DefaultSendTokenService } from "@bitwarden/common/auth/send-access";
import { DefaultSendTokenService, SendTokenService } from "@bitwarden/common/auth/send-access";
import { AccountApiServiceImplementation } from "@bitwarden/common/auth/services/account-api.service";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AnonymousHubService } from "@bitwarden/common/auth/services/anonymous-hub.service";
@@ -131,10 +131,10 @@ import { WebAuthnLoginApiService } from "@bitwarden/common/auth/services/webauth
import { WebAuthnLoginPrfKeyService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-prf-key.service";
import { WebAuthnLoginService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login.service";
import {
TwoFactorApiService,
DefaultTwoFactorApiService,
TwoFactorService,
DefaultTwoFactorService,
TwoFactorApiService,
TwoFactorService,
} from "@bitwarden/common/auth/two-factor";
import {
AutofillSettingsService,
@@ -208,8 +208,8 @@ import { PinService } from "@bitwarden/common/key-management/pin/pin.service.imp
import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service";
import { DefaultSecurityStateService } from "@bitwarden/common/key-management/security-state/services/security-state.service";
import {
SendPasswordService,
DefaultSendPasswordService,
SendPasswordService,
} from "@bitwarden/common/key-management/sends";
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
import {
@@ -387,12 +387,12 @@ import { SafeInjectionToken } from "@bitwarden/ui-common";
// eslint-disable-next-line no-restricted-imports
import { PasswordRepromptService } from "@bitwarden/vault";
import {
DefaultVaultExportApiService,
IndividualVaultExportService,
IndividualVaultExportServiceAbstraction,
DefaultVaultExportApiService,
VaultExportApiService,
OrganizationVaultExportService,
OrganizationVaultExportServiceAbstraction,
VaultExportApiService,
VaultExportService,
VaultExportServiceAbstraction,
} from "@bitwarden/vault-export-core";
@@ -1583,6 +1583,7 @@ const safeProviders: SafeProvider[] = [
OrganizationUserApiService,
InternalUserDecryptionOptionsServiceAbstraction,
AccountCryptographicStateService,
RegisterSdkService,
],
}),
safeProvider({

View File

@@ -47,6 +47,7 @@ export enum FeatureFlag {
ConsolidatedSessionTimeoutComponent = "pm-26056-consolidated-session-timeout-component",
PM27279_V2RegistrationTdeJit = "pm-27279-v2-registration-tde-jit",
EnableAccountEncryptionV2KeyConnectorRegistration = "enable-account-encryption-v2-key-connector-registration",
EnableAccountEncryptionV2JitPasswordRegistration = "enable-account-encryption-v2-jit-password-registration",
/* Tools */
UseSdkPasswordGenerators = "pm-19976-use-sdk-password-generators",
@@ -156,6 +157,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.ConsolidatedSessionTimeoutComponent]: FALSE,
[FeatureFlag.PM27279_V2RegistrationTdeJit]: FALSE,
[FeatureFlag.EnableAccountEncryptionV2KeyConnectorRegistration]: FALSE,
[FeatureFlag.EnableAccountEncryptionV2JitPasswordRegistration]: FALSE,
/* Platform */
[FeatureFlag.IpcChannelFramework]: FALSE,

16
package-lock.json generated
View File

@@ -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.450",
"@bitwarden/sdk-internal": "0.2.0-main.450",
"@bitwarden/commercial-sdk-internal": "0.2.0-main.470",
"@bitwarden/sdk-internal": "0.2.0-main.470",
"@electron/fuses": "1.8.0",
"@emotion/css": "11.13.5",
"@koa/multer": "4.0.0",
@@ -4982,9 +4982,9 @@
"link": true
},
"node_modules/@bitwarden/commercial-sdk-internal": {
"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==",
"version": "0.2.0-main.470",
"resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.470.tgz",
"integrity": "sha512-QYhxv5eX6ouFJv94gMtBW7MjuK6t2KAN9FLz+/w1wnq8dScnA9Iky25phNPw+iHMgWwhq/dzZq45asKUFF//oA==",
"license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT",
"dependencies": {
"type-fest": "^4.41.0"
@@ -5087,9 +5087,9 @@
"link": true
},
"node_modules/@bitwarden/sdk-internal": {
"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==",
"version": "0.2.0-main.470",
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.470.tgz",
"integrity": "sha512-XKvcUtoU6NnxeEzl3WK7bATiCh2RNxRmuX6JYNgcQHUtHUH+x3ckToR6II1qM3nha0VH0u1ijy3+07UdNQM+JQ==",
"license": "GPL-3.0",
"dependencies": {
"type-fest": "^4.41.0"

View File

@@ -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.450",
"@bitwarden/commercial-sdk-internal": "0.2.0-main.450",
"@bitwarden/sdk-internal": "0.2.0-main.470",
"@bitwarden/commercial-sdk-internal": "0.2.0-main.470",
"@electron/fuses": "1.8.0",
"@emotion/css": "11.13.5",
"@koa/multer": "4.0.0",