Merge branch 'main' into autofill/pm-7710-avoid-re-indexing-ciphers-on-current-tab-component-and-re-setting-null-storage-values-for-popup-component

This commit is contained in:
Cesar Gonzalez 2024-04-25 16:01:25 -05:00 committed by GitHub
commit 41a0e6aa27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
112 changed files with 1418 additions and 797 deletions

View File

@ -0,0 +1,28 @@
import { KdfConfigService as AbstractKdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KdfConfigService } from "@bitwarden/common/auth/services/kdf-config.service";
import {
FactoryOptions,
CachedServices,
factory,
} from "../../../platform/background/service-factories/factory-options";
import {
StateProviderInitOptions,
stateProviderFactory,
} from "../../../platform/background/service-factories/state-provider.factory";
type KdfConfigServiceFactoryOptions = FactoryOptions;
export type KdfConfigServiceInitOptions = KdfConfigServiceFactoryOptions & StateProviderInitOptions;
export function kdfConfigServiceFactory(
cache: { kdfConfigService?: AbstractKdfConfigService } & CachedServices,
opts: KdfConfigServiceInitOptions,
): Promise<AbstractKdfConfigService> {
return factory(
cache,
"kdfConfigService",
opts,
async () => new KdfConfigService(await stateProviderFactory(cache, opts)),
);
}

View File

@ -68,6 +68,7 @@ import {
deviceTrustServiceFactory, deviceTrustServiceFactory,
DeviceTrustServiceInitOptions, DeviceTrustServiceInitOptions,
} from "./device-trust-service.factory"; } from "./device-trust-service.factory";
import { kdfConfigServiceFactory, KdfConfigServiceInitOptions } from "./kdf-config-service.factory";
import { import {
keyConnectorServiceFactory, keyConnectorServiceFactory,
KeyConnectorServiceInitOptions, KeyConnectorServiceInitOptions,
@ -106,7 +107,8 @@ export type LoginStrategyServiceInitOptions = LoginStrategyServiceFactoryOptions
AuthRequestServiceInitOptions & AuthRequestServiceInitOptions &
UserDecryptionOptionsServiceInitOptions & UserDecryptionOptionsServiceInitOptions &
GlobalStateProviderInitOptions & GlobalStateProviderInitOptions &
BillingAccountProfileStateServiceInitOptions; BillingAccountProfileStateServiceInitOptions &
KdfConfigServiceInitOptions;
export function loginStrategyServiceFactory( export function loginStrategyServiceFactory(
cache: { loginStrategyService?: LoginStrategyServiceAbstraction } & CachedServices, cache: { loginStrategyService?: LoginStrategyServiceAbstraction } & CachedServices,
@ -140,6 +142,7 @@ export function loginStrategyServiceFactory(
await internalUserDecryptionOptionServiceFactory(cache, opts), await internalUserDecryptionOptionServiceFactory(cache, opts),
await globalStateProviderFactory(cache, opts), await globalStateProviderFactory(cache, opts),
await billingAccountProfileStateServiceFactory(cache, opts), await billingAccountProfileStateServiceFactory(cache, opts),
await kdfConfigServiceFactory(cache, opts),
), ),
); );
} }

View File

@ -22,13 +22,16 @@ import {
stateServiceFactory, stateServiceFactory,
} from "../../../platform/background/service-factories/state-service.factory"; } from "../../../platform/background/service-factories/state-service.factory";
import { KdfConfigServiceInitOptions, kdfConfigServiceFactory } from "./kdf-config-service.factory";
type PinCryptoServiceFactoryOptions = FactoryOptions; type PinCryptoServiceFactoryOptions = FactoryOptions;
export type PinCryptoServiceInitOptions = PinCryptoServiceFactoryOptions & export type PinCryptoServiceInitOptions = PinCryptoServiceFactoryOptions &
StateServiceInitOptions & StateServiceInitOptions &
CryptoServiceInitOptions & CryptoServiceInitOptions &
VaultTimeoutSettingsServiceInitOptions & VaultTimeoutSettingsServiceInitOptions &
LogServiceInitOptions; LogServiceInitOptions &
KdfConfigServiceInitOptions;
export function pinCryptoServiceFactory( export function pinCryptoServiceFactory(
cache: { pinCryptoService?: PinCryptoServiceAbstraction } & CachedServices, cache: { pinCryptoService?: PinCryptoServiceAbstraction } & CachedServices,
@ -44,6 +47,7 @@ export function pinCryptoServiceFactory(
await cryptoServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts),
await vaultTimeoutSettingsServiceFactory(cache, opts), await vaultTimeoutSettingsServiceFactory(cache, opts),
await logServiceFactory(cache, opts), await logServiceFactory(cache, opts),
await kdfConfigServiceFactory(cache, opts),
), ),
); );
} }

View File

@ -1,11 +1,13 @@
import { TwoFactorService as AbstractTwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService as AbstractTwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
import { import {
FactoryOptions, FactoryOptions,
CachedServices, CachedServices,
factory, factory,
} from "../../../platform/background/service-factories/factory-options"; } from "../../../platform/background/service-factories/factory-options";
import { globalStateProviderFactory } from "../../../platform/background/service-factories/global-state-provider.factory";
import { import {
I18nServiceInitOptions, I18nServiceInitOptions,
i18nServiceFactory, i18nServiceFactory,
@ -19,7 +21,8 @@ type TwoFactorServiceFactoryOptions = FactoryOptions;
export type TwoFactorServiceInitOptions = TwoFactorServiceFactoryOptions & export type TwoFactorServiceInitOptions = TwoFactorServiceFactoryOptions &
I18nServiceInitOptions & I18nServiceInitOptions &
PlatformUtilsServiceInitOptions; PlatformUtilsServiceInitOptions &
GlobalStateProvider;
export async function twoFactorServiceFactory( export async function twoFactorServiceFactory(
cache: { twoFactorService?: AbstractTwoFactorService } & CachedServices, cache: { twoFactorService?: AbstractTwoFactorService } & CachedServices,
@ -33,6 +36,7 @@ export async function twoFactorServiceFactory(
new TwoFactorService( new TwoFactorService(
await i18nServiceFactory(cache, opts), await i18nServiceFactory(cache, opts),
await platformUtilsServiceFactory(cache, opts), await platformUtilsServiceFactory(cache, opts),
await globalStateProviderFactory(cache, opts),
), ),
); );
service.init(); service.init();

View File

@ -32,6 +32,7 @@ import {
} from "../../../platform/background/service-factories/state-service.factory"; } from "../../../platform/background/service-factories/state-service.factory";
import { accountServiceFactory, AccountServiceInitOptions } from "./account-service.factory"; import { accountServiceFactory, AccountServiceInitOptions } from "./account-service.factory";
import { KdfConfigServiceInitOptions, kdfConfigServiceFactory } from "./kdf-config-service.factory";
import { import {
internalMasterPasswordServiceFactory, internalMasterPasswordServiceFactory,
MasterPasswordServiceInitOptions, MasterPasswordServiceInitOptions,
@ -59,7 +60,8 @@ export type UserVerificationServiceInitOptions = UserVerificationServiceFactoryO
PinCryptoServiceInitOptions & PinCryptoServiceInitOptions &
LogServiceInitOptions & LogServiceInitOptions &
VaultTimeoutSettingsServiceInitOptions & VaultTimeoutSettingsServiceInitOptions &
PlatformUtilsServiceInitOptions; PlatformUtilsServiceInitOptions &
KdfConfigServiceInitOptions;
export function userVerificationServiceFactory( export function userVerificationServiceFactory(
cache: { userVerificationService?: AbstractUserVerificationService } & CachedServices, cache: { userVerificationService?: AbstractUserVerificationService } & CachedServices,
@ -82,6 +84,7 @@ export function userVerificationServiceFactory(
await logServiceFactory(cache, opts), await logServiceFactory(cache, opts),
await vaultTimeoutSettingsServiceFactory(cache, opts), await vaultTimeoutSettingsServiceFactory(cache, opts),
await platformUtilsServiceFactory(cache, opts), await platformUtilsServiceFactory(cache, opts),
await kdfConfigServiceFactory(cache, opts),
), ),
); );
} }

View File

@ -12,6 +12,7 @@ import { InternalPolicyService } from "@bitwarden/common/admin-console/abstracti
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
@ -66,6 +67,7 @@ export class LockComponent extends BaseLockComponent {
private routerService: BrowserRouterService, private routerService: BrowserRouterService,
biometricStateService: BiometricStateService, biometricStateService: BiometricStateService,
accountService: AccountService, accountService: AccountService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
masterPasswordService, masterPasswordService,
@ -90,6 +92,7 @@ export class LockComponent extends BaseLockComponent {
pinCryptoService, pinCryptoService,
biometricStateService, biometricStateService,
accountService, accountService,
kdfConfigService,
); );
this.successRoute = "/tabs/current"; this.successRoute = "/tabs/current";
this.isInitialLockScreen = (window as any).previousPopupUrl == null; this.isInitialLockScreen = (window as any).previousPopupUrl == null;

View File

@ -2,7 +2,10 @@ import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component"; import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import {
TwoFactorProviderDetails,
TwoFactorService,
} from "@bitwarden/common/auth/abstractions/two-factor.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -27,9 +30,9 @@ export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
this.navigateTo2FA(); this.navigateTo2FA();
} }
choose(p: any) { override async choose(p: TwoFactorProviderDetails) {
super.choose(p); await super.choose(p);
this.twoFactorService.setSelectedProvider(p.type); await this.twoFactorService.setSelectedProvider(p.type);
this.navigateTo2FA(); this.navigateTo2FA();
} }

View File

@ -3,8 +3,6 @@ import { Subject, firstValueFrom, merge } from "rxjs";
import { import {
PinCryptoServiceAbstraction, PinCryptoServiceAbstraction,
PinCryptoService, PinCryptoService,
LoginStrategyServiceAbstraction,
LoginStrategyService,
InternalUserDecryptionOptionsServiceAbstraction, InternalUserDecryptionOptionsServiceAbstraction,
UserDecryptionOptionsService, UserDecryptionOptionsService,
AuthRequestServiceAbstraction, AuthRequestServiceAbstraction,
@ -33,11 +31,11 @@ import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/aut
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { KdfConfigService as kdfConfigServiceAbstraction } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction"; import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
@ -48,11 +46,11 @@ import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation";
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { KdfConfigService } from "@bitwarden/common/auth/services/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service"; import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service";
import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service"; import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service";
import { TokenService } from "@bitwarden/common/auth/services/token.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service"; import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service";
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service"; import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
import { import {
@ -275,7 +273,6 @@ export default class MainBackground {
containerService: ContainerService; containerService: ContainerService;
auditService: AuditServiceAbstraction; auditService: AuditServiceAbstraction;
authService: AuthServiceAbstraction; authService: AuthServiceAbstraction;
loginStrategyService: LoginStrategyServiceAbstraction;
loginEmailService: LoginEmailServiceAbstraction; loginEmailService: LoginEmailServiceAbstraction;
importApiService: ImportApiServiceAbstraction; importApiService: ImportApiServiceAbstraction;
importService: ImportServiceAbstraction; importService: ImportServiceAbstraction;
@ -299,7 +296,6 @@ export default class MainBackground {
providerService: ProviderServiceAbstraction; providerService: ProviderServiceAbstraction;
keyConnectorService: KeyConnectorServiceAbstraction; keyConnectorService: KeyConnectorServiceAbstraction;
userVerificationService: UserVerificationServiceAbstraction; userVerificationService: UserVerificationServiceAbstraction;
twoFactorService: TwoFactorServiceAbstraction;
vaultFilterService: VaultFilterService; vaultFilterService: VaultFilterService;
usernameGenerationService: UsernameGenerationServiceAbstraction; usernameGenerationService: UsernameGenerationServiceAbstraction;
encryptService: EncryptService; encryptService: EncryptService;
@ -339,6 +335,7 @@ export default class MainBackground {
intraprocessMessagingSubject: Subject<Message<object>>; intraprocessMessagingSubject: Subject<Message<object>>;
userKeyInitService: UserKeyInitService; userKeyInitService: UserKeyInitService;
scriptInjectorService: BrowserScriptInjectorService; scriptInjectorService: BrowserScriptInjectorService;
kdfConfigService: kdfConfigServiceAbstraction;
onUpdatedRan: boolean; onUpdatedRan: boolean;
onReplacedRan: boolean; onReplacedRan: boolean;
@ -542,6 +539,9 @@ export default class MainBackground {
this.masterPasswordService = new MasterPasswordService(this.stateProvider); this.masterPasswordService = new MasterPasswordService(this.stateProvider);
this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider); this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider);
this.kdfConfigService = new KdfConfigService(this.stateProvider);
this.cryptoService = new BrowserCryptoService( this.cryptoService = new BrowserCryptoService(
this.masterPasswordService, this.masterPasswordService,
this.keyGenerationService, this.keyGenerationService,
@ -553,6 +553,7 @@ export default class MainBackground {
this.accountService, this.accountService,
this.stateProvider, this.stateProvider,
this.biometricStateService, this.biometricStateService,
this.kdfConfigService,
); );
this.appIdService = new AppIdService(this.globalStateProvider); this.appIdService = new AppIdService(this.globalStateProvider);
@ -607,8 +608,6 @@ export default class MainBackground {
this.stateService, this.stateService,
); );
this.twoFactorService = new TwoFactorService(this.i18nService, this.platformUtilsService);
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService); this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
@ -652,31 +651,6 @@ export default class MainBackground {
this.loginEmailService = new LoginEmailService(this.stateProvider); this.loginEmailService = new LoginEmailService(this.stateProvider);
this.loginStrategyService = new LoginStrategyService(
this.accountService,
this.masterPasswordService,
this.cryptoService,
this.apiService,
this.tokenService,
this.appIdService,
this.platformUtilsService,
this.messagingService,
this.logService,
this.keyConnectorService,
this.environmentService,
this.stateService,
this.twoFactorService,
this.i18nService,
this.encryptService,
this.passwordStrengthService,
this.policyService,
this.deviceTrustService,
this.authRequestService,
this.userDecryptionOptionsService,
this.globalStateProvider,
this.billingAccountProfileStateService,
);
this.ssoLoginService = new SsoLoginService(this.stateProvider); this.ssoLoginService = new SsoLoginService(this.stateProvider);
this.userVerificationApiService = new UserVerificationApiService(this.apiService); this.userVerificationApiService = new UserVerificationApiService(this.apiService);
@ -725,6 +699,7 @@ export default class MainBackground {
this.cryptoService, this.cryptoService,
this.vaultTimeoutSettingsService, this.vaultTimeoutSettingsService,
this.logService, this.logService,
this.kdfConfigService,
); );
this.userVerificationService = new UserVerificationService( this.userVerificationService = new UserVerificationService(
@ -739,6 +714,7 @@ export default class MainBackground {
this.logService, this.logService,
this.vaultTimeoutSettingsService, this.vaultTimeoutSettingsService,
this.platformUtilsService, this.platformUtilsService,
this.kdfConfigService,
); );
this.vaultFilterService = new VaultFilterService( this.vaultFilterService = new VaultFilterService(
@ -861,7 +837,7 @@ export default class MainBackground {
this.cipherService, this.cipherService,
this.cryptoService, this.cryptoService,
this.cryptoFunctionService, this.cryptoFunctionService,
this.stateService, this.kdfConfigService,
); );
this.organizationVaultExportService = new OrganizationVaultExportService( this.organizationVaultExportService = new OrganizationVaultExportService(
@ -869,8 +845,8 @@ export default class MainBackground {
this.apiService, this.apiService,
this.cryptoService, this.cryptoService,
this.cryptoFunctionService, this.cryptoFunctionService,
this.stateService,
this.collectionService, this.collectionService,
this.kdfConfigService,
); );
this.exportService = new VaultExportService( this.exportService = new VaultExportService(
@ -1104,8 +1080,7 @@ export default class MainBackground {
this.userKeyInitService.listenForActiveUserChangesToSetUserKey(); this.userKeyInitService.listenForActiveUserChangesToSetUserKey();
await (this.i18nService as I18nService).init(); await (this.i18nService as I18nService).init();
await (this.eventUploadService as EventUploadService).init(true); (this.eventUploadService as EventUploadService).init(true);
this.twoFactorService.init();
if (this.popupOnlyContext) { if (this.popupOnlyContext) {
return; return;

View File

@ -4,6 +4,10 @@ import {
AccountServiceInitOptions, AccountServiceInitOptions,
accountServiceFactory, accountServiceFactory,
} from "../../../auth/background/service-factories/account-service.factory"; } from "../../../auth/background/service-factories/account-service.factory";
import {
KdfConfigServiceInitOptions,
kdfConfigServiceFactory,
} from "../../../auth/background/service-factories/kdf-config-service.factory";
import { import {
internalMasterPasswordServiceFactory, internalMasterPasswordServiceFactory,
MasterPasswordServiceInitOptions, MasterPasswordServiceInitOptions,
@ -18,7 +22,10 @@ import {
} from "../../background/service-factories/log-service.factory"; } from "../../background/service-factories/log-service.factory";
import { BrowserCryptoService } from "../../services/browser-crypto.service"; import { BrowserCryptoService } from "../../services/browser-crypto.service";
import { biometricStateServiceFactory } from "./biometric-state-service.factory"; import {
BiometricStateServiceInitOptions,
biometricStateServiceFactory,
} from "./biometric-state-service.factory";
import { import {
cryptoFunctionServiceFactory, cryptoFunctionServiceFactory,
CryptoFunctionServiceInitOptions, CryptoFunctionServiceInitOptions,
@ -46,7 +53,9 @@ export type CryptoServiceInitOptions = CryptoServiceFactoryOptions &
LogServiceInitOptions & LogServiceInitOptions &
StateServiceInitOptions & StateServiceInitOptions &
AccountServiceInitOptions & AccountServiceInitOptions &
StateProviderInitOptions; StateProviderInitOptions &
BiometricStateServiceInitOptions &
KdfConfigServiceInitOptions;
export function cryptoServiceFactory( export function cryptoServiceFactory(
cache: { cryptoService?: AbstractCryptoService } & CachedServices, cache: { cryptoService?: AbstractCryptoService } & CachedServices,
@ -68,6 +77,7 @@ export function cryptoServiceFactory(
await accountServiceFactory(cache, opts), await accountServiceFactory(cache, opts),
await stateProviderFactory(cache, opts), await stateProviderFactory(cache, opts),
await biometricStateServiceFactory(cache, opts), await biometricStateServiceFactory(cache, opts),
await kdfConfigServiceFactory(cache, opts),
), ),
); );
} }

View File

@ -78,6 +78,11 @@ export default abstract class AbstractChromeStorageService
async save(key: string, obj: any): Promise<void> { async save(key: string, obj: any): Promise<void> {
obj = objToStore(obj); obj = objToStore(obj);
if (obj == null) {
// Safari does not support set of null values
return this.remove(key);
}
const keyedObj = { [key]: obj }; const keyedObj = { [key]: obj };
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
this.chromeStorageApi.set(keyedObj, () => { this.chromeStorageApi.set(keyedObj, () => {

View File

@ -62,6 +62,17 @@ describe("ChromeStorageApiService", () => {
expect.any(Function), expect.any(Function),
); );
}); });
it("removes the key when the value is null", async () => {
const removeMock = chrome.storage.local.remove as jest.Mock;
removeMock.mockImplementation((key, callback) => {
delete store[key];
callback();
});
const key = "key";
await service.save(key, null);
expect(removeMock).toHaveBeenCalledWith(key, expect.any(Function));
});
}); });
describe("get", () => { describe("get", () => {

View File

@ -1,6 +1,7 @@
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@ -28,6 +29,7 @@ export class BrowserCryptoService extends CryptoService {
accountService: AccountService, accountService: AccountService,
stateProvider: StateProvider, stateProvider: StateProvider,
private biometricStateService: BiometricStateService, private biometricStateService: BiometricStateService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
masterPasswordService, masterPasswordService,
@ -39,6 +41,7 @@ export class BrowserCryptoService extends CryptoService {
stateService, stateService,
accountService, accountService,
stateProvider, stateProvider,
kdfConfigService,
); );
} }
override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise<boolean> { override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise<boolean> {

View File

@ -2,6 +2,7 @@ import { DOCUMENT } from "@angular/common";
import { Inject, Injectable } from "@angular/core"; import { Inject, Injectable } from "@angular/core";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -15,6 +16,7 @@ export class InitService {
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService, private i18nService: I18nService,
private stateService: StateServiceAbstraction, private stateService: StateServiceAbstraction,
private twoFactorService: TwoFactorService,
private logService: LogServiceAbstraction, private logService: LogServiceAbstraction,
private themingService: AbstractThemingService, private themingService: AbstractThemingService,
@Inject(DOCUMENT) private document: Document, @Inject(DOCUMENT) private document: Document,
@ -24,6 +26,7 @@ export class InitService {
return async () => { return async () => {
await this.stateService.init({ runMigrations: false }); // Browser background is responsible for migrations await this.stateService.init({ runMigrations: false }); // Browser background is responsible for migrations
await this.i18nService.init(); await this.i18nService.init();
this.twoFactorService.init();
if (!BrowserPopupUtils.inPopup(window)) { if (!BrowserPopupUtils.inPopup(window)) {
window.document.body.classList.add("body-full"); window.document.body.classList.add("body-full");

View File

@ -15,10 +15,7 @@ import {
INTRAPROCESS_MESSAGING_SUBJECT, INTRAPROCESS_MESSAGING_SUBJECT,
} from "@bitwarden/angular/services/injection-tokens"; } from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import { import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
AuthRequestServiceAbstraction,
LoginStrategyServiceAbstraction,
} from "@bitwarden/auth/common";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service"; import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
@ -33,7 +30,6 @@ import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/d
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { import {
@ -168,21 +164,11 @@ const safeProviders: SafeProvider[] = [
useClass: UnauthGuardService, useClass: UnauthGuardService,
deps: [AuthServiceAbstraction, Router], deps: [AuthServiceAbstraction, Router],
}), }),
safeProvider({
provide: TwoFactorService,
useFactory: getBgService<TwoFactorService>("twoFactorService"),
deps: [],
}),
safeProvider({ safeProvider({
provide: AuthServiceAbstraction, provide: AuthServiceAbstraction,
useFactory: getBgService<AuthService>("authService"), useFactory: getBgService<AuthService>("authService"),
deps: [], deps: [],
}), }),
safeProvider({
provide: LoginStrategyServiceAbstraction,
useFactory: getBgService<LoginStrategyServiceAbstraction>("loginStrategyService"),
deps: [],
}),
safeProvider({ safeProvider({
provide: SsoLoginServiceAbstraction, provide: SsoLoginServiceAbstraction,
useFactory: getBgService<SsoLoginServiceAbstraction>("ssoLoginService"), useFactory: getBgService<SsoLoginServiceAbstraction>("ssoLoginService"),

View File

@ -16,6 +16,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@ -68,6 +69,7 @@ export class LoginCommand {
protected policyApiService: PolicyApiServiceAbstraction, protected policyApiService: PolicyApiServiceAbstraction,
protected orgService: OrganizationService, protected orgService: OrganizationService,
protected logoutCallback: () => Promise<void>, protected logoutCallback: () => Promise<void>,
protected kdfConfigService: KdfConfigService,
) {} ) {}
async run(email: string, password: string, options: OptionValues) { async run(email: string, password: string, options: OptionValues) {
@ -229,7 +231,7 @@ export class LoginCommand {
} }
} }
if (response.requiresTwoFactor) { if (response.requiresTwoFactor) {
const twoFactorProviders = this.twoFactorService.getSupportedProviders(null); const twoFactorProviders = await this.twoFactorService.getSupportedProviders(null);
if (twoFactorProviders.length === 0) { if (twoFactorProviders.length === 0) {
return Response.badRequest("No providers available for this client."); return Response.badRequest("No providers available for this client.");
} }
@ -270,7 +272,7 @@ export class LoginCommand {
if ( if (
twoFactorToken == null && twoFactorToken == null &&
response.twoFactorProviders.size > 1 && Object.keys(response.twoFactorProviders).length > 1 &&
selectedProvider.type === TwoFactorProviderType.Email selectedProvider.type === TwoFactorProviderType.Email
) { ) {
const emailReq = new TwoFactorEmailRequest(); const emailReq = new TwoFactorEmailRequest();
@ -563,14 +565,12 @@ export class LoginCommand {
message: "Master Password Hint (optional):", message: "Master Password Hint (optional):",
}); });
const masterPasswordHint = hint.input; const masterPasswordHint = hint.input;
const kdf = await this.stateService.getKdfType(); const kdfConfig = await this.kdfConfigService.getKdfConfig();
const kdfConfig = await this.stateService.getKdfConfig();
// Create new key and hash new password // Create new key and hash new password
const newMasterKey = await this.cryptoService.makeMasterKey( const newMasterKey = await this.cryptoService.makeMasterKey(
masterPassword, masterPassword,
this.email.trim().toLowerCase(), this.email.trim().toLowerCase(),
kdf,
kdfConfig, kdfConfig,
); );
const newPasswordHash = await this.cryptoService.hashMasterKey(masterPassword, newMasterKey); const newPasswordHash = await this.cryptoService.hashMasterKey(masterPassword, newMasterKey);

View File

@ -3,6 +3,7 @@ import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
@ -34,6 +35,7 @@ export class UnlockCommand {
private syncService: SyncService, private syncService: SyncService,
private organizationApiService: OrganizationApiServiceAbstraction, private organizationApiService: OrganizationApiServiceAbstraction,
private logout: () => Promise<void>, private logout: () => Promise<void>,
private kdfConfigService: KdfConfigService,
) {} ) {}
async run(password: string, cmdOptions: Record<string, any>) { async run(password: string, cmdOptions: Record<string, any>) {
@ -48,9 +50,8 @@ export class UnlockCommand {
await this.setNewSessionKey(); await this.setNewSessionKey();
const email = await this.stateService.getEmail(); const email = await this.stateService.getEmail();
const kdf = await this.stateService.getKdfType(); const kdfConfig = await this.kdfConfigService.getKdfConfig();
const kdfConfig = await this.stateService.getKdfConfig(); const masterKey = await this.cryptoService.makeMasterKey(password, email, kdfConfig);
const masterKey = await this.cryptoService.makeMasterKey(password, email, kdf, kdfConfig);
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const storedMasterKeyHash = await firstValueFrom( const storedMasterKeyHash = await firstValueFrom(
this.masterPasswordService.masterKeyHash$(userId), this.masterPasswordService.masterKeyHash$(userId),

View File

@ -30,12 +30,14 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service"; import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { KdfConfigService as KdfConfigServiceAbstraction } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { KdfConfigService } from "@bitwarden/common/auth/services/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service"; import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service";
import { TokenService } from "@bitwarden/common/auth/services/token.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service";
@ -235,6 +237,7 @@ export class Main {
billingAccountProfileStateService: BillingAccountProfileStateService; billingAccountProfileStateService: BillingAccountProfileStateService;
providerApiService: ProviderApiServiceAbstraction; providerApiService: ProviderApiServiceAbstraction;
userKeyInitService: UserKeyInitService; userKeyInitService: UserKeyInitService;
kdfConfigService: KdfConfigServiceAbstraction;
constructor() { constructor() {
let p = null; let p = null;
@ -357,6 +360,8 @@ export class Main {
this.masterPasswordService = new MasterPasswordService(this.stateProvider); this.masterPasswordService = new MasterPasswordService(this.stateProvider);
this.kdfConfigService = new KdfConfigService(this.stateProvider);
this.cryptoService = new CryptoService( this.cryptoService = new CryptoService(
this.masterPasswordService, this.masterPasswordService,
this.keyGenerationService, this.keyGenerationService,
@ -367,6 +372,7 @@ export class Main {
this.stateService, this.stateService,
this.accountService, this.accountService,
this.stateProvider, this.stateProvider,
this.kdfConfigService,
); );
this.appIdService = new AppIdService(this.globalStateProvider); this.appIdService = new AppIdService(this.globalStateProvider);
@ -449,7 +455,11 @@ export class Main {
this.stateProvider, this.stateProvider,
); );
this.twoFactorService = new TwoFactorService(this.i18nService, this.platformUtilsService); this.twoFactorService = new TwoFactorService(
this.i18nService,
this.platformUtilsService,
this.globalStateProvider,
);
this.passwordStrengthService = new PasswordStrengthService(); this.passwordStrengthService = new PasswordStrengthService();
@ -512,6 +522,7 @@ export class Main {
this.userDecryptionOptionsService, this.userDecryptionOptionsService,
this.globalStateProvider, this.globalStateProvider,
this.billingAccountProfileStateService, this.billingAccountProfileStateService,
this.kdfConfigService,
); );
this.authService = new AuthService( this.authService = new AuthService(
@ -574,6 +585,7 @@ export class Main {
this.cryptoService, this.cryptoService,
this.vaultTimeoutSettingsService, this.vaultTimeoutSettingsService,
this.logService, this.logService,
this.kdfConfigService,
); );
this.userVerificationService = new UserVerificationService( this.userVerificationService = new UserVerificationService(
@ -588,6 +600,7 @@ export class Main {
this.logService, this.logService,
this.vaultTimeoutSettingsService, this.vaultTimeoutSettingsService,
this.platformUtilsService, this.platformUtilsService,
this.kdfConfigService,
); );
this.vaultTimeoutService = new VaultTimeoutService( this.vaultTimeoutService = new VaultTimeoutService(
@ -654,7 +667,7 @@ export class Main {
this.cipherService, this.cipherService,
this.cryptoService, this.cryptoService,
this.cryptoFunctionService, this.cryptoFunctionService,
this.stateService, this.kdfConfigService,
); );
this.organizationExportService = new OrganizationVaultExportService( this.organizationExportService = new OrganizationVaultExportService(
@ -662,8 +675,8 @@ export class Main {
this.apiService, this.apiService,
this.cryptoService, this.cryptoService,
this.cryptoFunctionService, this.cryptoFunctionService,
this.stateService,
this.collectionService, this.collectionService,
this.kdfConfigService,
); );
this.exportService = new VaultExportService( this.exportService = new VaultExportService(

View File

@ -134,6 +134,7 @@ export class ServeCommand {
this.main.syncService, this.main.syncService,
this.main.organizationApiService, this.main.organizationApiService,
async () => await this.main.logout(), async () => await this.main.logout(),
this.main.kdfConfigService,
); );
this.sendCreateCommand = new SendCreateCommand( this.sendCreateCommand = new SendCreateCommand(

View File

@ -156,6 +156,7 @@ export class Program {
this.main.policyApiService, this.main.policyApiService,
this.main.organizationService, this.main.organizationService,
async () => await this.main.logout(), async () => await this.main.logout(),
this.main.kdfConfigService,
); );
const response = await command.run(email, password, options); const response = await command.run(email, password, options);
this.processResponse(response, true); this.processResponse(response, true);
@ -265,6 +266,7 @@ export class Program {
this.main.syncService, this.main.syncService,
this.main.organizationApiService, this.main.organizationApiService,
async () => await this.main.logout(), async () => await this.main.logout(),
this.main.kdfConfigService,
); );
const response = await command.run(password, cmd); const response = await command.run(password, cmd);
this.processResponse(response); this.processResponse(response);
@ -627,6 +629,7 @@ export class Program {
this.main.syncService, this.main.syncService,
this.main.organizationApiService, this.main.organizationApiService,
this.main.logout, this.main.logout,
this.main.kdfConfigService,
); );
const response = await command.run(null, null); const response = await command.run(null, null);
if (!response.success) { if (!response.success) {

View File

@ -21,6 +21,7 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaul
import { PolicyService as PolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService as PolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
import { KdfConfigService as KdfConfigServiceAbstraction } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
@ -258,6 +259,7 @@ const safeProviders: SafeProvider[] = [
AccountServiceAbstraction, AccountServiceAbstraction,
StateProvider, StateProvider,
BiometricStateService, BiometricStateService,
KdfConfigServiceAbstraction,
], ],
}), }),
safeProvider({ safeProvider({

View File

@ -14,6 +14,7 @@ import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abs
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
@ -164,6 +165,10 @@ describe("LockComponent", () => {
provide: AccountService, provide: AccountService,
useValue: accountService, useValue: accountService,
}, },
{
provide: KdfConfigService,
useValue: mock<KdfConfigService>(),
},
], ],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
}).compileComponents(); }).compileComponents();

View File

@ -11,6 +11,7 @@ import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abs
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { DeviceType } from "@bitwarden/common/enums"; import { DeviceType } from "@bitwarden/common/enums";
@ -63,6 +64,7 @@ export class LockComponent extends BaseLockComponent {
pinCryptoService: PinCryptoServiceAbstraction, pinCryptoService: PinCryptoServiceAbstraction,
biometricStateService: BiometricStateService, biometricStateService: BiometricStateService,
accountService: AccountService, accountService: AccountService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
masterPasswordService, masterPasswordService,
@ -87,6 +89,7 @@ export class LockComponent extends BaseLockComponent {
pinCryptoService, pinCryptoService,
biometricStateService, biometricStateService,
accountService, accountService,
kdfConfigService,
); );
} }

View File

@ -9,6 +9,7 @@ import { OrganizationUserService } from "@bitwarden/common/admin-console/abstrac
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
@ -52,6 +53,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction,
dialogService: DialogService, dialogService: DialogService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
accountService, accountService,
@ -73,6 +75,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
userDecryptionOptionsService, userDecryptionOptionsService,
ssoLoginService, ssoLoginService,
dialogService, dialogService,
kdfConfigService,
); );
} }

View File

@ -1,6 +1,7 @@
import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider"; import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider";
import { mock } from "jest-mock-extended"; import { mock } from "jest-mock-extended";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@ -35,6 +36,7 @@ describe("electronCryptoService", () => {
let accountService: FakeAccountService; let accountService: FakeAccountService;
let stateProvider: FakeStateProvider; let stateProvider: FakeStateProvider;
const biometricStateService = mock<BiometricStateService>(); const biometricStateService = mock<BiometricStateService>();
const kdfConfigService = mock<KdfConfigService>();
const mockUserId = "mock user id" as UserId; const mockUserId = "mock user id" as UserId;
@ -54,6 +56,7 @@ describe("electronCryptoService", () => {
accountService, accountService,
stateProvider, stateProvider,
biometricStateService, biometricStateService,
kdfConfigService,
); );
}); });

View File

@ -1,6 +1,7 @@
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@ -31,6 +32,7 @@ export class ElectronCryptoService extends CryptoService {
accountService: AccountService, accountService: AccountService,
stateProvider: StateProvider, stateProvider: StateProvider,
private biometricStateService: BiometricStateService, private biometricStateService: BiometricStateService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
masterPasswordService, masterPasswordService,
@ -42,6 +44,7 @@ export class ElectronCryptoService extends CryptoService {
stateService, stateService,
accountService, accountService,
stateProvider, stateProvider,
kdfConfigService,
); );
} }

View File

@ -7,10 +7,15 @@ import {
OrganizationUserResetPasswordRequest, OrganizationUserResetPasswordRequest,
OrganizationUserResetPasswordWithIdRequest, OrganizationUserResetPasswordWithIdRequest,
} from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import {
Argon2KdfConfig,
KdfConfig,
PBKDF2KdfConfig,
} from "@bitwarden/common/auth/models/domain/kdf-config";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { KdfType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
@ -90,12 +95,17 @@ export class OrganizationUserResetPasswordService {
const decValue = await this.cryptoService.rsaDecrypt(response.resetPasswordKey, decPrivateKey); const decValue = await this.cryptoService.rsaDecrypt(response.resetPasswordKey, decPrivateKey);
const existingUserKey = new SymmetricCryptoKey(decValue) as UserKey; const existingUserKey = new SymmetricCryptoKey(decValue) as UserKey;
// determine Kdf Algorithm
const kdfConfig: KdfConfig =
response.kdf === KdfType.PBKDF2_SHA256
? new PBKDF2KdfConfig(response.kdfIterations)
: new Argon2KdfConfig(response.kdfIterations, response.kdfMemory, response.kdfParallelism);
// Create new master key and hash new password // Create new master key and hash new password
const newMasterKey = await this.cryptoService.makeMasterKey( const newMasterKey = await this.cryptoService.makeMasterKey(
newMasterPassword, newMasterPassword,
email.trim().toLowerCase(), email.trim().toLowerCase(),
response.kdf, kdfConfig,
new KdfConfig(response.kdfIterations, response.kdfMemory, response.kdfParallelism),
); );
const newMasterKeyHash = await this.cryptoService.hashMasterKey( const newMasterKeyHash = await this.cryptoService.hashMasterKey(
newMasterPassword, newMasterPassword,

View File

@ -3,10 +3,15 @@ import { Injectable } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data"; import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import {
Argon2KdfConfig,
KdfConfig,
PBKDF2KdfConfig,
} from "@bitwarden/common/auth/models/domain/kdf-config";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { KdfType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
@ -231,16 +236,22 @@ export class EmergencyAccessService {
const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey; const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey;
const masterKey = await this.cryptoService.makeMasterKey( let config: KdfConfig;
masterPassword,
email, switch (takeoverResponse.kdf) {
takeoverResponse.kdf, case KdfType.PBKDF2_SHA256:
new KdfConfig( config = new PBKDF2KdfConfig(takeoverResponse.kdfIterations);
takeoverResponse.kdfIterations, break;
takeoverResponse.kdfMemory, case KdfType.Argon2id:
takeoverResponse.kdfParallelism, config = new Argon2KdfConfig(
), takeoverResponse.kdfIterations,
); takeoverResponse.kdfMemory,
takeoverResponse.kdfParallelism,
);
break;
}
const masterKey = await this.cryptoService.makeMasterKey(masterPassword, email, config);
const masterKeyHash = await this.cryptoService.hashMasterKey(masterPassword, masterKey); const masterKeyHash = await this.cryptoService.hashMasterKey(masterPassword, masterKey);
const encKey = await this.cryptoService.encryptUserKeyWithMasterKey(masterKey, grantorUserKey); const encKey = await this.cryptoService.encryptUserKeyWithMasterKey(masterKey, grantorUserKey);

View File

@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -47,6 +48,7 @@ describe("KeyRotationService", () => {
let mockEncryptService: MockProxy<EncryptService>; let mockEncryptService: MockProxy<EncryptService>;
let mockStateService: MockProxy<StateService>; let mockStateService: MockProxy<StateService>;
let mockConfigService: MockProxy<ConfigService>; let mockConfigService: MockProxy<ConfigService>;
let mockKdfConfigService: MockProxy<KdfConfigService>;
const mockUserId = Utils.newGuid() as UserId; const mockUserId = Utils.newGuid() as UserId;
const mockAccountService: FakeAccountService = mockAccountServiceWith(mockUserId); const mockAccountService: FakeAccountService = mockAccountServiceWith(mockUserId);
@ -65,6 +67,7 @@ describe("KeyRotationService", () => {
mockEncryptService = mock<EncryptService>(); mockEncryptService = mock<EncryptService>();
mockStateService = mock<StateService>(); mockStateService = mock<StateService>();
mockConfigService = mock<ConfigService>(); mockConfigService = mock<ConfigService>();
mockKdfConfigService = mock<KdfConfigService>();
keyRotationService = new UserKeyRotationService( keyRotationService = new UserKeyRotationService(
mockMasterPasswordService, mockMasterPasswordService,
@ -80,6 +83,7 @@ describe("KeyRotationService", () => {
mockStateService, mockStateService,
mockAccountService, mockAccountService,
mockConfigService, mockConfigService,
mockKdfConfigService,
); );
}); });

View File

@ -3,6 +3,7 @@ import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@ -39,6 +40,7 @@ export class UserKeyRotationService {
private stateService: StateService, private stateService: StateService,
private accountService: AccountService, private accountService: AccountService,
private configService: ConfigService, private configService: ConfigService,
private kdfConfigService: KdfConfigService,
) {} ) {}
/** /**
@ -54,8 +56,7 @@ export class UserKeyRotationService {
const masterKey = await this.cryptoService.makeMasterKey( const masterKey = await this.cryptoService.makeMasterKey(
masterPassword, masterPassword,
await this.stateService.getEmail(), await this.stateService.getEmail(),
await this.stateService.getKdfType(), await this.kdfConfigService.getKdfConfig(),
await this.stateService.getKdfConfig(),
); );
if (!masterKey) { if (!masterKey) {

View File

@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms"; import { FormBuilder, Validators } from "@angular/forms";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { EmailTokenRequest } from "@bitwarden/common/auth/models/request/email-token.request"; import { EmailTokenRequest } from "@bitwarden/common/auth/models/request/email-token.request";
import { EmailRequest } from "@bitwarden/common/auth/models/request/email.request"; import { EmailRequest } from "@bitwarden/common/auth/models/request/email.request";
@ -37,6 +38,7 @@ export class ChangeEmailComponent implements OnInit {
private logService: LogService, private logService: LogService,
private stateService: StateService, private stateService: StateService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private kdfConfigService: KdfConfigService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -83,12 +85,10 @@ export class ChangeEmailComponent implements OnInit {
step1Value.masterPassword, step1Value.masterPassword,
await this.cryptoService.getOrDeriveMasterKey(step1Value.masterPassword), await this.cryptoService.getOrDeriveMasterKey(step1Value.masterPassword),
); );
const kdf = await this.stateService.getKdfType(); const kdfConfig = await this.kdfConfigService.getKdfConfig();
const kdfConfig = await this.stateService.getKdfConfig();
const newMasterKey = await this.cryptoService.makeMasterKey( const newMasterKey = await this.cryptoService.makeMasterKey(
step1Value.masterPassword, step1Value.masterPassword,
newEmail, newEmail,
kdf,
kdfConfig, kdfConfig,
); );
request.newMasterPasswordHash = await this.cryptoService.hashMasterKey( request.newMasterPasswordHash = await this.cryptoService.hashMasterKey(

View File

@ -5,6 +5,7 @@ import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitward
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -48,6 +49,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
dialogService: DialogService, dialogService: DialogService,
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
private keyRotationService: UserKeyRotationService, private keyRotationService: UserKeyRotationService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
i18nService, i18nService,
@ -58,6 +60,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
policyService, policyService,
stateService, stateService,
dialogService, dialogService,
kdfConfigService,
); );
} }

View File

@ -5,6 +5,7 @@ import { takeUntil } from "rxjs";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component"; import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -58,6 +59,7 @@ export class EmergencyAccessTakeoverComponent
private logService: LogService, private logService: LogService,
dialogService: DialogService, dialogService: DialogService,
private dialogRef: DialogRef<EmergencyAccessTakeoverResultType>, private dialogRef: DialogRef<EmergencyAccessTakeoverResultType>,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
i18nService, i18nService,
@ -68,6 +70,7 @@ export class EmergencyAccessTakeoverComponent
policyService, policyService,
stateService, stateService,
dialogService, dialogService,
kdfConfigService,
); );
} }

View File

@ -3,6 +3,7 @@ import { Component, Inject } from "@angular/core";
import { FormGroup, FormControl, Validators } from "@angular/forms"; import { FormGroup, FormControl, Validators } from "@angular/forms";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import { KdfRequest } from "@bitwarden/common/models/request/kdf.request"; import { KdfRequest } from "@bitwarden/common/models/request/kdf.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -18,7 +19,6 @@ import { KdfType } from "@bitwarden/common/platform/enums";
templateUrl: "change-kdf-confirmation.component.html", templateUrl: "change-kdf-confirmation.component.html",
}) })
export class ChangeKdfConfirmationComponent { export class ChangeKdfConfirmationComponent {
kdf: KdfType;
kdfConfig: KdfConfig; kdfConfig: KdfConfig;
form = new FormGroup({ form = new FormGroup({
@ -37,9 +37,9 @@ export class ChangeKdfConfirmationComponent {
private messagingService: MessagingService, private messagingService: MessagingService,
private stateService: StateService, private stateService: StateService,
private logService: LogService, private logService: LogService,
@Inject(DIALOG_DATA) params: { kdf: KdfType; kdfConfig: KdfConfig }, private kdfConfigService: KdfConfigService,
@Inject(DIALOG_DATA) params: { kdfConfig: KdfConfig },
) { ) {
this.kdf = params.kdf;
this.kdfConfig = params.kdfConfig; this.kdfConfig = params.kdfConfig;
this.masterPassword = null; this.masterPassword = null;
} }
@ -65,22 +65,24 @@ export class ChangeKdfConfirmationComponent {
private async makeKeyAndSaveAsync() { private async makeKeyAndSaveAsync() {
const masterPassword = this.form.value.masterPassword; const masterPassword = this.form.value.masterPassword;
// Ensure the KDF config is valid.
this.kdfConfig.validateKdfConfig();
const request = new KdfRequest(); const request = new KdfRequest();
request.kdf = this.kdf; request.kdf = this.kdfConfig.kdfType;
request.kdfIterations = this.kdfConfig.iterations; request.kdfIterations = this.kdfConfig.iterations;
request.kdfMemory = this.kdfConfig.memory; if (this.kdfConfig.kdfType === KdfType.Argon2id) {
request.kdfParallelism = this.kdfConfig.parallelism; request.kdfMemory = this.kdfConfig.memory;
request.kdfParallelism = this.kdfConfig.parallelism;
}
const masterKey = await this.cryptoService.getOrDeriveMasterKey(masterPassword); const masterKey = await this.cryptoService.getOrDeriveMasterKey(masterPassword);
request.masterPasswordHash = await this.cryptoService.hashMasterKey(masterPassword, masterKey); request.masterPasswordHash = await this.cryptoService.hashMasterKey(masterPassword, masterKey);
const email = await this.stateService.getEmail(); const email = await this.stateService.getEmail();
// Ensure the KDF config is valid.
this.cryptoService.validateKdfConfig(this.kdf, this.kdfConfig);
const newMasterKey = await this.cryptoService.makeMasterKey( const newMasterKey = await this.cryptoService.makeMasterKey(
masterPassword, masterPassword,
email, email,
this.kdf,
this.kdfConfig, this.kdfConfig,
); );
request.newMasterPasswordHash = await this.cryptoService.hashMasterKey( request.newMasterPasswordHash = await this.cryptoService.hashMasterKey(

View File

@ -19,14 +19,14 @@
<select <select
id="kdf" id="kdf"
name="Kdf" name="Kdf"
[(ngModel)]="kdf" [(ngModel)]="kdfConfig.kdfType"
(ngModelChange)="onChangeKdf($event)" (ngModelChange)="onChangeKdf($event)"
class="form-control mb-3" class="form-control mb-3"
required required
> >
<option *ngFor="let o of kdfOptions" [ngValue]="o.value">{{ o.name }}</option> <option *ngFor="let o of kdfOptions" [ngValue]="o.value">{{ o.name }}</option>
</select> </select>
<ng-container *ngIf="kdf == kdfType.Argon2id"> <ng-container *ngIf="isArgon2(kdfConfig)">
<label for="kdfMemory">{{ "kdfMemory" | i18n }}</label> <label for="kdfMemory">{{ "kdfMemory" | i18n }}</label>
<input <input
id="kdfMemory" id="kdfMemory"
@ -43,7 +43,7 @@
</div> </div>
<div class="col-6"> <div class="col-6">
<div class="form-group mb-0"> <div class="form-group mb-0">
<ng-container *ngIf="kdf == kdfType.PBKDF2_SHA256"> <ng-container *ngIf="isPBKDF2(kdfConfig)">
<label for="kdfIterations">{{ "kdfIterations" | i18n }}</label> <label for="kdfIterations">{{ "kdfIterations" | i18n }}</label>
<a <a
class="ml-auto" class="ml-auto"
@ -65,7 +65,7 @@
required required
/> />
</ng-container> </ng-container>
<ng-container *ngIf="kdf == kdfType.Argon2id"> <ng-container *ngIf="isArgon2(kdfConfig)">
<label for="kdfIterations">{{ "kdfIterations" | i18n }}</label> <label for="kdfIterations">{{ "kdfIterations" | i18n }}</label>
<input <input
id="iterations" id="iterations"
@ -92,7 +92,7 @@
</div> </div>
</div> </div>
<div class="col-12"> <div class="col-12">
<ng-container *ngIf="kdf == kdfType.PBKDF2_SHA256"> <ng-container *ngIf="isPBKDF2(kdfConfig)">
<p class="small form-text text-muted"> <p class="small form-text text-muted">
{{ "kdfIterationsDesc" | i18n: (PBKDF2_ITERATIONS.defaultValue | number) }} {{ "kdfIterationsDesc" | i18n: (PBKDF2_ITERATIONS.defaultValue | number) }}
</p> </p>
@ -100,7 +100,7 @@
{{ "kdfIterationsWarning" | i18n: (100000 | number) }} {{ "kdfIterationsWarning" | i18n: (100000 | number) }}
</bit-callout> </bit-callout>
</ng-container> </ng-container>
<ng-container *ngIf="kdf == kdfType.Argon2id"> <ng-container *ngIf="isArgon2(kdfConfig)">
<p class="small form-text text-muted">{{ "argon2Desc" | i18n }}</p> <p class="small form-text text-muted">{{ "argon2Desc" | i18n }}</p>
<bit-callout type="warning"> {{ "argon2Warning" | i18n }}</bit-callout> <bit-callout type="warning"> {{ "argon2Warning" | i18n }}</bit-callout>
</ng-container> </ng-container>

View File

@ -1,7 +1,11 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import {
Argon2KdfConfig,
KdfConfig,
PBKDF2KdfConfig,
} from "@bitwarden/common/auth/models/domain/kdf-config";
import { import {
DEFAULT_KDF_CONFIG, DEFAULT_KDF_CONFIG,
PBKDF2_ITERATIONS, PBKDF2_ITERATIONS,
@ -19,7 +23,6 @@ import { ChangeKdfConfirmationComponent } from "./change-kdf-confirmation.compon
templateUrl: "change-kdf.component.html", templateUrl: "change-kdf.component.html",
}) })
export class ChangeKdfComponent implements OnInit { export class ChangeKdfComponent implements OnInit {
kdf = KdfType.PBKDF2_SHA256;
kdfConfig: KdfConfig = DEFAULT_KDF_CONFIG; kdfConfig: KdfConfig = DEFAULT_KDF_CONFIG;
kdfType = KdfType; kdfType = KdfType;
kdfOptions: any[] = []; kdfOptions: any[] = [];
@ -31,8 +34,8 @@ export class ChangeKdfComponent implements OnInit {
protected ARGON2_PARALLELISM = ARGON2_PARALLELISM; protected ARGON2_PARALLELISM = ARGON2_PARALLELISM;
constructor( constructor(
private stateService: StateService,
private dialogService: DialogService, private dialogService: DialogService,
private kdfConfigService: KdfConfigService,
) { ) {
this.kdfOptions = [ this.kdfOptions = [
{ name: "PBKDF2 SHA-256", value: KdfType.PBKDF2_SHA256 }, { name: "PBKDF2 SHA-256", value: KdfType.PBKDF2_SHA256 },
@ -41,19 +44,22 @@ export class ChangeKdfComponent implements OnInit {
} }
async ngOnInit() { async ngOnInit() {
this.kdf = await this.stateService.getKdfType(); this.kdfConfig = await this.kdfConfigService.getKdfConfig();
this.kdfConfig = await this.stateService.getKdfConfig(); }
isPBKDF2(t: KdfConfig): t is PBKDF2KdfConfig {
return t instanceof PBKDF2KdfConfig;
}
isArgon2(t: KdfConfig): t is Argon2KdfConfig {
return t instanceof Argon2KdfConfig;
} }
async onChangeKdf(newValue: KdfType) { async onChangeKdf(newValue: KdfType) {
if (newValue === KdfType.PBKDF2_SHA256) { if (newValue === KdfType.PBKDF2_SHA256) {
this.kdfConfig = new KdfConfig(PBKDF2_ITERATIONS.defaultValue); this.kdfConfig = new PBKDF2KdfConfig();
} else if (newValue === KdfType.Argon2id) { } else if (newValue === KdfType.Argon2id) {
this.kdfConfig = new KdfConfig( this.kdfConfig = new Argon2KdfConfig();
ARGON2_ITERATIONS.defaultValue,
ARGON2_MEMORY.defaultValue,
ARGON2_PARALLELISM.defaultValue,
);
} else { } else {
throw new Error("Unknown KDF type."); throw new Error("Unknown KDF type.");
} }
@ -62,7 +68,6 @@ export class ChangeKdfComponent implements OnInit {
async openConfirmationModal() { async openConfirmationModal() {
this.dialogService.open(ChangeKdfConfirmationComponent, { this.dialogService.open(ChangeKdfConfirmationComponent, {
data: { data: {
kdf: this.kdf,
kdfConfig: this.kdfConfig, kdfConfig: this.kdfConfig,
}, },
}); });

View File

@ -4,6 +4,7 @@ import { Router } from "@angular/router";
import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component"; import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -32,6 +33,7 @@ export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
stateService: StateService, stateService: StateService,
userVerificationService: UserVerificationService, userVerificationService: UserVerificationService,
dialogService: DialogService, dialogService: DialogService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
router, router,
@ -46,6 +48,7 @@ export class UpdatePasswordComponent extends BaseUpdatePasswordComponent {
userVerificationService, userVerificationService,
logService, logService,
dialogService, dialogService,
kdfConfigService,
); );
} }
} }

View File

@ -35,6 +35,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
@ -184,6 +185,7 @@ export class VaultComponent implements OnInit, OnDestroy {
private apiService: ApiService, private apiService: ApiService,
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
private billingAccountProfileStateService: BillingAccountProfileStateService, private billingAccountProfileStateService: BillingAccountProfileStateService,
protected kdfConfigService: KdfConfigService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -972,10 +974,10 @@ export class VaultComponent implements OnInit, OnDestroy {
} }
async isLowKdfIteration() { async isLowKdfIteration() {
const kdfType = await this.stateService.getKdfType(); const kdfConfig = await this.kdfConfigService.getKdfConfig();
const kdfOptions = await this.stateService.getKdfConfig();
return ( return (
kdfType === KdfType.PBKDF2_SHA256 && kdfOptions.iterations < PBKDF2_ITERATIONS.defaultValue kdfConfig.kdfType === KdfType.PBKDF2_SHA256 &&
kdfConfig.iterations < PBKDF2_ITERATIONS.defaultValue
); );
} }

View File

@ -0,0 +1,130 @@
import { SelectionModel } from "@angular/cdk/collections";
import { Directive, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { BehaviorSubject, from, Subject, switchMap } from "rxjs";
import { first, takeUntil } from "rxjs/operators";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { DialogService, TableDataSource, ToastService } from "@bitwarden/components";
import { WebProviderService } from "../services/web-provider.service";
@Directive()
export abstract class BaseClientsComponent implements OnInit, OnDestroy {
protected destroy$ = new Subject<void>();
private searchText$ = new BehaviorSubject<string>("");
get searchText() {
return this.searchText$.value;
}
set searchText(value: string) {
this.searchText$.next(value);
this.selection.clear();
this.dataSource.filter = value;
}
private searching = false;
protected scrolled = false;
protected pageSize = 100;
private pagedClientsCount = 0;
protected selection = new SelectionModel<string>(true, []);
protected clients: ProviderOrganizationOrganizationDetailsResponse[];
protected pagedClients: ProviderOrganizationOrganizationDetailsResponse[];
protected dataSource = new TableDataSource<ProviderOrganizationOrganizationDetailsResponse>();
abstract providerId: string;
protected constructor(
protected activatedRoute: ActivatedRoute,
protected dialogService: DialogService,
private i18nService: I18nService,
private searchService: SearchService,
private toastService: ToastService,
private validationService: ValidationService,
private webProviderService: WebProviderService,
) {}
abstract load(): Promise<void>;
ngOnInit() {
this.activatedRoute.queryParams
.pipe(first(), takeUntil(this.destroy$))
.subscribe((queryParams) => {
this.searchText = queryParams.search;
});
this.searchText$
.pipe(
switchMap((searchText) => from(this.searchService.isSearchable(searchText))),
takeUntil(this.destroy$),
)
.subscribe((isSearchable) => {
this.searching = isSearchable;
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
isPaging() {
if (this.searching && this.scrolled) {
this.resetPaging();
}
return !this.searching && this.clients && this.clients.length > this.pageSize;
}
resetPaging() {
this.pagedClients = [];
this.loadMore();
}
loadMore() {
if (!this.clients || this.clients.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedClients.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedClientsCount > this.pageSize) {
pagedSize = this.pagedClientsCount;
}
if (this.clients.length > pagedLength) {
this.pagedClients = this.pagedClients.concat(
this.clients.slice(pagedLength, pagedLength + pagedSize),
);
}
this.pagedClientsCount = this.pagedClients.length;
this.scrolled = this.pagedClients.length > this.pageSize;
}
async remove(organization: ProviderOrganizationOrganizationDetailsResponse) {
const confirmed = await this.dialogService.openSimpleDialog({
title: organization.organizationName,
content: { key: "detachOrganizationConfirmation" },
type: "warning",
});
if (!confirmed) {
return;
}
try {
await this.webProviderService.detachOrganization(this.providerId, organization.id);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("detachedOrganization", organization.organizationName),
});
await this.load();
} catch (e) {
this.validationService.showError(e);
}
}
}

View File

@ -1,29 +1,26 @@
import { Component, OnInit } from "@angular/core"; import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { BehaviorSubject, Subject, firstValueFrom, from } from "rxjs"; import { combineLatest, firstValueFrom, from } from "rxjs";
import { first, switchMap, takeUntil } from "rxjs/operators"; import { concatMap, switchMap, takeUntil } from "rxjs/operators";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
import { PlanType } from "@bitwarden/common/billing/enums"; import { PlanType } from "@bitwarden/common/billing/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { DialogService } from "@bitwarden/components"; import { DialogService, ToastService } from "@bitwarden/components";
import { WebProviderService } from "../services/web-provider.service"; import { WebProviderService } from "../services/web-provider.service";
import { AddOrganizationComponent } from "./add-organization.component"; import { AddOrganizationComponent } from "./add-organization.component";
import { BaseClientsComponent } from "./base-clients.component";
const DisallowedPlanTypes = [ const DisallowedPlanTypes = [
PlanType.Free, PlanType.Free,
@ -36,90 +33,76 @@ const DisallowedPlanTypes = [
@Component({ @Component({
templateUrl: "clients.component.html", templateUrl: "clients.component.html",
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil export class ClientsComponent extends BaseClientsComponent {
export class ClientsComponent implements OnInit {
providerId: string; providerId: string;
addableOrganizations: Organization[]; addableOrganizations: Organization[];
loading = true; loading = true;
manageOrganizations = false; manageOrganizations = false;
showAddExisting = false; showAddExisting = false;
clients: ProviderOrganizationOrganizationDetailsResponse[]; protected consolidatedBillingEnabled$ = this.configService.getFeatureFlag$(
pagedClients: ProviderOrganizationOrganizationDetailsResponse[];
protected didScroll = false;
protected pageSize = 100;
protected actionPromise: Promise<unknown>;
private pagedClientsCount = 0;
protected enableConsolidatedBilling$ = this.configService.getFeatureFlag$(
FeatureFlag.EnableConsolidatedBilling, FeatureFlag.EnableConsolidatedBilling,
false, false,
); );
private destroy$ = new Subject<void>();
private _searchText$ = new BehaviorSubject<string>("");
private isSearching: boolean = false;
get searchText() {
return this._searchText$.value;
}
set searchText(value: string) {
this._searchText$.next(value);
}
constructor( constructor(
private route: ActivatedRoute,
private router: Router, private router: Router,
private providerService: ProviderService, private providerService: ProviderService,
private apiService: ApiService, private apiService: ApiService,
private searchService: SearchService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private validationService: ValidationService,
private webProviderService: WebProviderService,
private logService: LogService,
private modalService: ModalService,
private organizationService: OrganizationService, private organizationService: OrganizationService,
private organizationApiService: OrganizationApiServiceAbstraction, private organizationApiService: OrganizationApiServiceAbstraction,
private dialogService: DialogService,
private configService: ConfigService, private configService: ConfigService,
) {} activatedRoute: ActivatedRoute,
dialogService: DialogService,
i18nService: I18nService,
searchService: SearchService,
toastService: ToastService,
validationService: ValidationService,
webProviderService: WebProviderService,
) {
super(
activatedRoute,
dialogService,
i18nService,
searchService,
toastService,
validationService,
webProviderService,
);
}
async ngOnInit() { ngOnInit() {
const enableConsolidatedBilling = await firstValueFrom(this.enableConsolidatedBilling$); this.activatedRoute.parent.params
.pipe(
if (enableConsolidatedBilling) { switchMap((params) => {
await this.router.navigate(["../manage-client-organizations"], { relativeTo: this.route }); this.providerId = params.providerId;
} else { return combineLatest([
this.route.parent.params this.providerService.get(this.providerId),
.pipe( this.consolidatedBillingEnabled$,
switchMap((params) => { ]).pipe(
this.providerId = params.providerId; concatMap(([provider, consolidatedBillingEnabled]) => {
return from(this.load()); if (
}), consolidatedBillingEnabled &&
takeUntil(this.destroy$), provider.providerStatus === ProviderStatusType.Billable
) ) {
.subscribe(); return from(
this.router.navigate(["../manage-client-organizations"], {
this.route.queryParams.pipe(first(), takeUntil(this.destroy$)).subscribe((qParams) => { relativeTo: this.activatedRoute,
this.searchText = qParams.search; }),
}); );
} else {
this._searchText$ return from(this.load());
.pipe( }
switchMap((searchText) => from(this.searchService.isSearchable(searchText))), }),
takeUntil(this.destroy$), );
) }),
.subscribe((isSearchable) => { takeUntil(this.destroy$),
this.isSearching = isSearchable; )
}); .subscribe();
}
} }
ngOnDestroy() { ngOnDestroy() {
this.destroy$.next(); super.ngOnDestroy();
this.destroy$.complete();
} }
async load() { async load() {
@ -141,37 +124,6 @@ export class ClientsComponent implements OnInit {
this.loading = false; this.loading = false;
} }
isPaging() {
const searching = this.isSearching;
if (searching && this.didScroll) {
this.resetPaging();
}
return !searching && this.clients && this.clients.length > this.pageSize;
}
resetPaging() {
this.pagedClients = [];
this.loadMore();
}
loadMore() {
if (!this.clients || this.clients.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedClients.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedClientsCount > this.pageSize) {
pagedSize = this.pagedClientsCount;
}
if (this.clients.length > pagedLength) {
this.pagedClients = this.pagedClients.concat(
this.clients.slice(pagedLength, pagedLength + pagedSize),
);
}
this.pagedClientsCount = this.pagedClients.length;
this.didScroll = this.pagedClients.length > this.pageSize;
}
async addExistingOrganization() { async addExistingOrganization() {
const dialogRef = AddOrganizationComponent.open(this.dialogService, { const dialogRef = AddOrganizationComponent.open(this.dialogService, {
providerId: this.providerId, providerId: this.providerId,
@ -182,33 +134,4 @@ export class ClientsComponent implements OnInit {
await this.load(); await this.load();
} }
} }
async remove(organization: ProviderOrganizationOrganizationDetailsResponse) {
const confirmed = await this.dialogService.openSimpleDialog({
title: organization.organizationName,
content: { key: "detachOrganizationConfirmation" },
type: "warning",
});
if (!confirmed) {
return false;
}
this.actionPromise = this.webProviderService.detachOrganization(
this.providerId,
organization.id,
);
try {
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("detachedOrganization", organization.organizationName),
);
await this.load();
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
} }

View File

@ -7,7 +7,12 @@
<bit-nav-item <bit-nav-item
icon="bwi-bank" icon="bwi-bank"
[text]="'clients' | i18n" [text]="'clients' | i18n"
[route]="(enableConsolidatedBilling$ | async) ? 'manage-client-organizations' : 'clients'" [route]="
(enableConsolidatedBilling$ | async) &&
provider.providerStatus === ProviderStatusType.Billable
? 'manage-client-organizations'
: 'clients'
"
></bit-nav-item> ></bit-nav-item>
<bit-nav-group icon="bwi-sliders" [text]="'manage' | i18n" route="manage" *ngIf="showManageTab"> <bit-nav-group icon="bwi-sliders" [text]="'manage' | i18n" route="manage" *ngIf="showManageTab">
<bit-nav-item <bit-nav-item

View File

@ -4,6 +4,7 @@ import { ActivatedRoute, RouterModule } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { ProviderStatusType } from "@bitwarden/common/admin-console/enums";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@ -83,4 +84,6 @@ export class ProvidersLayoutComponent {
return "manage/events"; return "manage/events";
} }
} }
protected readonly ProviderStatusType = ProviderStatusType;
} }

View File

@ -11,14 +11,7 @@
<bit-label> <bit-label>
{{ "assignedSeats" | i18n }} {{ "assignedSeats" | i18n }}
</bit-label> </bit-label>
<input <input id="assignedSeats" type="number" bitInput required [(ngModel)]="assignedSeats" />
id="assignedSeats"
type="number"
appAutoFocus
bitInput
required
[(ngModel)]="assignedSeats"
/>
</bit-form-field> </bit-form-field>
<ng-container *ngIf="remainingOpenSeats > 0"> <ng-container *ngIf="remainingOpenSeats > 0">
<p> <p>

View File

@ -1,21 +1,23 @@
import { SelectionModel } from "@angular/cdk/collections"; import { Component } from "@angular/core";
import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router";
import { ActivatedRoute } from "@angular/router"; import { combineLatest, firstValueFrom, from, lastValueFrom } from "rxjs";
import { BehaviorSubject, firstValueFrom, from, lastValueFrom, Subject } from "rxjs"; import { concatMap, switchMap, takeUntil } from "rxjs/operators";
import { first, switchMap, takeUntil } from "rxjs/operators";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { DialogService, TableDataSource } from "@bitwarden/components"; import { DialogService, ToastService } from "@bitwarden/components";
import { BaseClientsComponent } from "../../../admin-console/providers/clients/base-clients.component";
import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service";
import { import {
@ -27,127 +29,91 @@ import { ManageClientOrganizationSubscriptionComponent } from "./manage-client-o
@Component({ @Component({
templateUrl: "manage-client-organizations.component.html", templateUrl: "manage-client-organizations.component.html",
}) })
export class ManageClientOrganizationsComponent extends BaseClientsComponent {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class ManageClientOrganizationsComponent implements OnInit, OnDestroy {
providerId: string; providerId: string;
provider: Provider;
loading = true; loading = true;
manageOrganizations = false; manageOrganizations = false;
private destroy$ = new Subject<void>(); private consolidatedBillingEnabled$ = this.configService.getFeatureFlag$(
private _searchText$ = new BehaviorSubject<string>(""); FeatureFlag.EnableConsolidatedBilling,
private isSearching: boolean = false; false,
);
get searchText() {
return this._searchText$.value;
}
set searchText(search: string) {
this._searchText$.value;
this.selection.clear();
this.dataSource.filter = search;
}
clients: ProviderOrganizationOrganizationDetailsResponse[];
pagedClients: ProviderOrganizationOrganizationDetailsResponse[];
protected didScroll = false;
protected pageSize = 100;
protected actionPromise: Promise<unknown>;
private pagedClientsCount = 0;
selection = new SelectionModel<string>(true, []);
protected dataSource = new TableDataSource<ProviderOrganizationOrganizationDetailsResponse>();
protected plans: PlanResponse[]; protected plans: PlanResponse[];
constructor( constructor(
private route: ActivatedRoute,
private providerService: ProviderService,
private apiService: ApiService, private apiService: ApiService,
private searchService: SearchService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private validationService: ValidationService,
private webProviderService: WebProviderService,
private dialogService: DialogService,
private billingApiService: BillingApiService, private billingApiService: BillingApiService,
) {} private configService: ConfigService,
private providerService: ProviderService,
async ngOnInit() { private router: Router,
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe activatedRoute: ActivatedRoute,
this.route.parent.params.subscribe(async (params) => { dialogService: DialogService,
this.providerId = params.providerId; i18nService: I18nService,
searchService: SearchService,
await this.load(); toastService: ToastService,
validationService: ValidationService,
/* eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe, rxjs/no-nested-subscribe */ webProviderService: WebProviderService,
this.route.queryParams.pipe(first()).subscribe(async (qParams) => { ) {
this.searchText = qParams.search; super(
}); activatedRoute,
}); dialogService,
i18nService,
this._searchText$ searchService,
.pipe( toastService,
switchMap((searchText) => from(this.searchService.isSearchable(searchText))), validationService,
takeUntil(this.destroy$), webProviderService,
) );
.subscribe((isSearchable) => {
this.isSearching = isSearchable;
});
} }
ngOnDestroy(): void { ngOnInit() {
this.destroy$.next(); this.activatedRoute.parent.params
this.destroy$.complete(); .pipe(
switchMap((params) => {
this.providerId = params.providerId;
return combineLatest([
this.providerService.get(this.providerId),
this.consolidatedBillingEnabled$,
]).pipe(
concatMap(([provider, consolidatedBillingEnabled]) => {
if (
!consolidatedBillingEnabled ||
provider.providerStatus !== ProviderStatusType.Billable
) {
return from(
this.router.navigate(["../clients"], {
relativeTo: this.activatedRoute,
}),
);
} else {
this.provider = provider;
this.manageOrganizations = this.provider.type === ProviderUserType.ProviderAdmin;
return from(this.load());
}
}),
);
}),
takeUntil(this.destroy$),
)
.subscribe();
}
ngOnDestroy() {
super.ngOnDestroy();
} }
async load() { async load() {
const clientsResponse = await this.apiService.getProviderClients(this.providerId); this.clients = (await this.apiService.getProviderClients(this.providerId)).data;
this.clients =
clientsResponse.data != null && clientsResponse.data.length > 0 ? clientsResponse.data : [];
this.dataSource.data = this.clients;
this.manageOrganizations =
(await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin;
const plansResponse = await this.billingApiService.getPlans(); this.dataSource.data = this.clients;
this.plans = plansResponse.data;
this.plans = (await this.billingApiService.getPlans()).data;
this.loading = false; this.loading = false;
} }
isPaging() {
const searching = this.isSearching;
if (searching && this.didScroll) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.resetPaging();
}
return !searching && this.clients && this.clients.length > this.pageSize;
}
async resetPaging() {
this.pagedClients = [];
this.loadMore();
}
loadMore() {
if (!this.clients || this.clients.length <= this.pageSize) {
return;
}
const pagedLength = this.pagedClients.length;
let pagedSize = this.pageSize;
if (pagedLength === 0 && this.pagedClientsCount > this.pageSize) {
pagedSize = this.pagedClientsCount;
}
if (this.clients.length > pagedLength) {
this.pagedClients = this.pagedClients.concat(
this.clients.slice(pagedLength, pagedLength + pagedSize),
);
}
this.pagedClientsCount = this.pagedClients.length;
this.didScroll = this.pagedClients.length > this.pageSize;
}
async manageSubscription(organization: ProviderOrganizationOrganizationDetailsResponse) { async manageSubscription(organization: ProviderOrganizationOrganizationDetailsResponse) {
if (organization == null) { if (organization == null) {
return; return;
@ -161,35 +127,6 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy {
await this.load(); await this.load();
} }
async remove(organization: ProviderOrganizationOrganizationDetailsResponse) {
const confirmed = await this.dialogService.openSimpleDialog({
title: organization.organizationName,
content: { key: "detachOrganizationConfirmation" },
type: "warning",
});
if (!confirmed) {
return false;
}
this.actionPromise = this.webProviderService.detachOrganization(
this.providerId,
organization.id,
);
try {
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("detachedOrganization", organization.organizationName),
);
await this.load();
} catch (e) {
this.validationService.showError(e);
}
this.actionPromise = null;
}
createClientOrganization = async () => { createClientOrganization = async () => {
const reference = openCreateClientOrganizationDialog(this.dialogService, { const reference = openCreateClientOrganizationDialog(this.dialogService, {
data: { data: {

View File

@ -3,13 +3,13 @@ import { Subject, takeUntil } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { KdfType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
@ -31,7 +31,6 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
minimumLength = Utils.minimumPasswordLength; minimumLength = Utils.minimumPasswordLength;
protected email: string; protected email: string;
protected kdf: KdfType;
protected kdfConfig: KdfConfig; protected kdfConfig: KdfConfig;
protected destroy$ = new Subject<void>(); protected destroy$ = new Subject<void>();
@ -45,6 +44,7 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
protected policyService: PolicyService, protected policyService: PolicyService,
protected stateService: StateService, protected stateService: StateService,
protected dialogService: DialogService, protected dialogService: DialogService,
protected kdfConfigService: KdfConfigService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -73,18 +73,14 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
} }
const email = await this.stateService.getEmail(); const email = await this.stateService.getEmail();
if (this.kdf == null) {
this.kdf = await this.stateService.getKdfType();
}
if (this.kdfConfig == null) { if (this.kdfConfig == null) {
this.kdfConfig = await this.stateService.getKdfConfig(); this.kdfConfig = await this.kdfConfigService.getKdfConfig();
} }
// Create new master key // Create new master key
const newMasterKey = await this.cryptoService.makeMasterKey( const newMasterKey = await this.cryptoService.makeMasterKey(
this.masterPassword, this.masterPassword,
email.trim().toLowerCase(), email.trim().toLowerCase(),
this.kdf,
this.kdfConfig, this.kdfConfig,
); );
const newMasterKeyHash = await this.cryptoService.hashMasterKey( const newMasterKeyHash = await this.cryptoService.hashMasterKey(

View File

@ -12,6 +12,7 @@ import { InternalPolicyService } from "@bitwarden/common/admin-console/abstracti
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
@ -79,6 +80,7 @@ export class LockComponent implements OnInit, OnDestroy {
protected pinCryptoService: PinCryptoServiceAbstraction, protected pinCryptoService: PinCryptoServiceAbstraction,
protected biometricStateService: BiometricStateService, protected biometricStateService: BiometricStateService,
protected accountService: AccountService, protected accountService: AccountService,
protected kdfConfigService: KdfConfigService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -208,14 +210,12 @@ export class LockComponent implements OnInit, OnDestroy {
} }
private async doUnlockWithMasterPassword() { private async doUnlockWithMasterPassword() {
const kdfConfig = await this.kdfConfigService.getKdfConfig();
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig();
const masterKey = await this.cryptoService.makeMasterKey( const masterKey = await this.cryptoService.makeMasterKey(
this.masterPassword, this.masterPassword,
this.email, this.email,
kdf,
kdfConfig, kdfConfig,
); );
const storedMasterKeyHash = await firstValueFrom( const storedMasterKeyHash = await firstValueFrom(

View File

@ -15,7 +15,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DEFAULT_KDF_CONFIG, DEFAULT_KDF_TYPE } from "@bitwarden/common/platform/enums"; import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
@ -273,9 +273,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
name: string, name: string,
): Promise<RegisterRequest> { ): Promise<RegisterRequest> {
const hint = this.formGroup.value.hint; const hint = this.formGroup.value.hint;
const kdf = DEFAULT_KDF_TYPE;
const kdfConfig = DEFAULT_KDF_CONFIG; const kdfConfig = DEFAULT_KDF_CONFIG;
const key = await this.cryptoService.makeMasterKey(masterPassword, email, kdf, kdfConfig); const key = await this.cryptoService.makeMasterKey(masterPassword, email, kdfConfig);
const newUserKey = await this.cryptoService.makeUserKey(key); const newUserKey = await this.cryptoService.makeUserKey(key);
const masterKeyHash = await this.cryptoService.hashMasterKey(masterPassword, key); const masterKeyHash = await this.cryptoService.hashMasterKey(masterPassword, key);
const keys = await this.cryptoService.makeKeyPair(newUserKey[0]); const keys = await this.cryptoService.makeKeyPair(newUserKey[0]);
@ -287,10 +286,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
newUserKey[1].encryptedString, newUserKey[1].encryptedString,
this.referenceData, this.referenceData,
this.captchaToken, this.captchaToken,
kdf, kdfConfig.kdfType,
kdfConfig.iterations, kdfConfig.iterations,
kdfConfig.memory,
kdfConfig.parallelism,
); );
request.keys = new KeysRequest(keys[0], keys[1].encryptedString); request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
const orgInvite = await this.stateService.getOrganizationInvitation(); const orgInvite = await this.stateService.getOrganizationInvitation();

View File

@ -13,6 +13,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { OrganizationAutoEnrollStatusResponse } from "@bitwarden/common/admin-console/models/response/organization-auto-enroll-status.response"; import { OrganizationAutoEnrollStatusResponse } from "@bitwarden/common/admin-console/models/response/organization-auto-enroll-status.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
@ -23,11 +24,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { import { HashPurpose, DEFAULT_KDF_CONFIG } from "@bitwarden/common/platform/enums";
HashPurpose,
DEFAULT_KDF_TYPE,
DEFAULT_KDF_CONFIG,
} from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
@ -73,6 +70,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
private userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, private userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
private ssoLoginService: SsoLoginServiceAbstraction, private ssoLoginService: SsoLoginServiceAbstraction,
dialogService: DialogService, dialogService: DialogService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
i18nService, i18nService,
@ -83,6 +81,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
policyService, policyService,
stateService, stateService,
dialogService, dialogService,
kdfConfigService,
); );
} }
@ -139,7 +138,6 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
} }
async setupSubmitActions() { async setupSubmitActions() {
this.kdf = DEFAULT_KDF_TYPE;
this.kdfConfig = DEFAULT_KDF_CONFIG; this.kdfConfig = DEFAULT_KDF_CONFIG;
return true; return true;
} }
@ -169,10 +167,8 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
this.hint, this.hint,
this.orgSsoIdentifier, this.orgSsoIdentifier,
keysRequest, keysRequest,
this.kdf, this.kdfConfig.kdfType, //always PBKDF2 --> see this.setupSubmitActions
this.kdfConfig.iterations, this.kdfConfig.iterations,
this.kdfConfig.memory,
this.kdfConfig.parallelism,
); );
try { try {
if (this.resetPasswordAutoEnroll) { if (this.resetPasswordAutoEnroll) {
@ -246,9 +242,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
); );
userDecryptionOpts.hasMasterPassword = true; userDecryptionOpts.hasMasterPassword = true;
await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts); await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts);
await this.kdfConfigService.setKdfConfig(this.userId, this.kdfConfig);
await this.stateService.setKdfType(this.kdf);
await this.stateService.setKdfConfig(this.kdfConfig);
await this.masterPasswordService.setMasterKey(masterKey, this.userId); await this.masterPasswordService.setMasterKey(masterKey, this.userId);
await this.cryptoService.setUserKey(userKey[0]); await this.cryptoService.setUserKey(userKey[0]);

View File

@ -2,6 +2,7 @@ import { DialogRef } from "@angular/cdk/dialog";
import { Directive, OnInit } from "@angular/core"; import { Directive, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms"; import { FormBuilder, Validators } from "@angular/forms";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
@ -22,6 +23,7 @@ export class SetPinComponent implements OnInit {
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
private stateService: StateService, private stateService: StateService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private kdfConfigService: KdfConfigService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -43,8 +45,7 @@ export class SetPinComponent implements OnInit {
const pinKey = await this.cryptoService.makePinKey( const pinKey = await this.cryptoService.makePinKey(
pin, pin,
await this.stateService.getEmail(), await this.stateService.getEmail(),
await this.stateService.getKdfType(), await this.kdfConfigService.getKdfConfig(),
await this.stateService.getKdfConfig(),
); );
const userKey = await this.cryptoService.getUserKey(); const userKey = await this.cryptoService.getUserKey();
const pinProtectedKey = await this.cryptoService.encrypt(userKey.key, pinKey); const pinProtectedKey = await this.cryptoService.encrypt(userKey.key, pinKey);

View File

@ -253,7 +253,7 @@ describe("SsoComponent", () => {
describe("2FA scenarios", () => { describe("2FA scenarios", () => {
beforeEach(() => { beforeEach(() => {
const authResult = new AuthResult(); const authResult = new AuthResult();
authResult.twoFactorProviders = new Map([[TwoFactorProviderType.Authenticator, {}]]); authResult.twoFactorProviders = { [TwoFactorProviderType.Authenticator]: {} };
// use standard user with MP because this test is not concerned with password reset. // use standard user with MP because this test is not concerned with password reset.
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword); selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);

View File

@ -2,7 +2,10 @@ import { Directive, EventEmitter, OnInit, Output } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import {
TwoFactorProviderDetails,
TwoFactorService,
} from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -24,11 +27,11 @@ export class TwoFactorOptionsComponent implements OnInit {
protected environmentService: EnvironmentService, protected environmentService: EnvironmentService,
) {} ) {}
ngOnInit() { async ngOnInit() {
this.providers = this.twoFactorService.getSupportedProviders(this.win); this.providers = await this.twoFactorService.getSupportedProviders(this.win);
} }
choose(p: any) { async choose(p: TwoFactorProviderDetails) {
this.onProviderSelected.emit(p.type); this.onProviderSelected.emit(p.type);
} }

View File

@ -102,7 +102,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
} }
async ngOnInit() { async ngOnInit() {
if (!(await this.authing()) || this.twoFactorService.getProviders() == null) { if (!(await this.authing()) || (await this.twoFactorService.getProviders()) == null) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate([this.loginRoute]); this.router.navigate([this.loginRoute]);
@ -145,7 +145,9 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
); );
} }
this.selectedProviderType = this.twoFactorService.getDefaultProvider(this.webAuthnSupported); this.selectedProviderType = await this.twoFactorService.getDefaultProvider(
this.webAuthnSupported,
);
await this.init(); await this.init();
} }
@ -162,12 +164,14 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
this.cleanupWebAuthn(); this.cleanupWebAuthn();
this.title = (TwoFactorProviders as any)[this.selectedProviderType].name; this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
const providerData = this.twoFactorService.getProviders().get(this.selectedProviderType); const providerData = await this.twoFactorService.getProviders().then((providers) => {
return providers.get(this.selectedProviderType);
});
switch (this.selectedProviderType) { switch (this.selectedProviderType) {
case TwoFactorProviderType.WebAuthn: case TwoFactorProviderType.WebAuthn:
if (!this.webAuthnNewTab) { if (!this.webAuthnNewTab) {
setTimeout(() => { setTimeout(async () => {
this.authWebAuthn(); await this.authWebAuthn();
}, 500); }, 500);
} }
break; break;
@ -212,7 +216,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
break; break;
case TwoFactorProviderType.Email: case TwoFactorProviderType.Email:
this.twoFactorEmail = providerData.Email; this.twoFactorEmail = providerData.Email;
if (this.twoFactorService.getProviders().size > 1) { if ((await this.twoFactorService.getProviders()).size > 1) {
await this.sendEmail(false); await this.sendEmail(false);
} }
break; break;
@ -474,8 +478,10 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
this.emailPromise = null; this.emailPromise = null;
} }
authWebAuthn() { async authWebAuthn() {
const providerData = this.twoFactorService.getProviders().get(this.selectedProviderType); const providerData = await this.twoFactorService.getProviders().then((providers) => {
return providers.get(this.selectedProviderType);
});
if (!this.webAuthnSupported || this.webAuthn == null) { if (!this.webAuthnSupported || this.webAuthn == null) {
return; return;

View File

@ -4,6 +4,7 @@ import { Router } from "@angular/router";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
@ -44,6 +45,7 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
private logService: LogService, private logService: LogService,
dialogService: DialogService, dialogService: DialogService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
i18nService, i18nService,
@ -54,6 +56,7 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
policyService, policyService,
stateService, stateService,
dialogService, dialogService,
kdfConfigService,
); );
} }
@ -90,8 +93,7 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
return false; return false;
} }
this.kdf = await this.stateService.getKdfType(); this.kdfConfig = await this.kdfConfigService.getKdfConfig();
this.kdfConfig = await this.stateService.getKdfConfig();
return true; return true;
} }

View File

@ -6,6 +6,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
@ -59,6 +60,7 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
protected router: Router, protected router: Router,
dialogService: DialogService, dialogService: DialogService,
kdfConfigService: KdfConfigService,
private accountService: AccountService, private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction, private masterPasswordService: InternalMasterPasswordServiceAbstraction,
) { ) {
@ -71,6 +73,7 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
policyService, policyService,
stateService, stateService,
dialogService, dialogService,
kdfConfigService,
); );
} }
@ -104,8 +107,7 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
async setupSubmitActions(): Promise<boolean> { async setupSubmitActions(): Promise<boolean> {
this.email = await this.stateService.getEmail(); this.email = await this.stateService.getEmail();
this.kdf = await this.stateService.getKdfType(); this.kdfConfig = await this.kdfConfigService.getKdfConfig();
this.kdfConfig = await this.stateService.getKdfConfig();
return true; return true;
} }
@ -124,7 +126,6 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
const newMasterKey = await this.cryptoService.makeMasterKey( const newMasterKey = await this.cryptoService.makeMasterKey(
this.masterPassword, this.masterPassword,
this.email.trim().toLowerCase(), this.email.trim().toLowerCase(),
this.kdf,
this.kdfConfig, this.kdfConfig,
); );
const newPasswordHash = await this.cryptoService.hashMasterKey( const newPasswordHash = await this.cryptoService.hashMasterKey(

View File

@ -63,6 +63,7 @@ import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/aut
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { KdfConfigService as KdfConfigServiceAbstraction } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { import {
InternalMasterPasswordServiceAbstraction, InternalMasterPasswordServiceAbstraction,
@ -85,6 +86,7 @@ import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation"; import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation";
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { KdfConfigService } from "@bitwarden/common/auth/services/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service"; import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service";
import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation"; import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation";
@ -390,6 +392,7 @@ const safeProviders: SafeProvider[] = [
InternalUserDecryptionOptionsServiceAbstraction, InternalUserDecryptionOptionsServiceAbstraction,
GlobalStateProvider, GlobalStateProvider,
BillingAccountProfileStateService, BillingAccountProfileStateService,
KdfConfigServiceAbstraction,
], ],
}), }),
safeProvider({ safeProvider({
@ -543,6 +546,7 @@ const safeProviders: SafeProvider[] = [
StateServiceAbstraction, StateServiceAbstraction,
AccountServiceAbstraction, AccountServiceAbstraction,
StateProvider, StateProvider,
KdfConfigServiceAbstraction,
], ],
}), }),
safeProvider({ safeProvider({
@ -713,7 +717,7 @@ const safeProviders: SafeProvider[] = [
CipherServiceAbstraction, CipherServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
StateServiceAbstraction, KdfConfigServiceAbstraction,
], ],
}), }),
safeProvider({ safeProvider({
@ -724,8 +728,8 @@ const safeProviders: SafeProvider[] = [
ApiServiceAbstraction, ApiServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
StateServiceAbstraction,
CollectionServiceAbstraction, CollectionServiceAbstraction,
KdfConfigServiceAbstraction,
], ],
}), }),
safeProvider({ safeProvider({
@ -834,6 +838,7 @@ const safeProviders: SafeProvider[] = [
LogService, LogService,
VaultTimeoutSettingsServiceAbstraction, VaultTimeoutSettingsServiceAbstraction,
PlatformUtilsServiceAbstraction, PlatformUtilsServiceAbstraction,
KdfConfigServiceAbstraction,
], ],
}), }),
safeProvider({ safeProvider({
@ -869,7 +874,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({ safeProvider({
provide: TwoFactorServiceAbstraction, provide: TwoFactorServiceAbstraction,
useClass: TwoFactorService, useClass: TwoFactorService,
deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction], deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction, GlobalStateProvider],
}), }),
safeProvider({ safeProvider({
provide: FormValidationErrorsServiceAbstraction, provide: FormValidationErrorsServiceAbstraction,
@ -985,6 +990,7 @@ const safeProviders: SafeProvider[] = [
CryptoServiceAbstraction, CryptoServiceAbstraction,
VaultTimeoutSettingsServiceAbstraction, VaultTimeoutSettingsServiceAbstraction,
LogService, LogService,
KdfConfigServiceAbstraction,
], ],
}), }),
safeProvider({ safeProvider({
@ -1150,6 +1156,11 @@ const safeProviders: SafeProvider[] = [
useClass: ProviderApiService, useClass: ProviderApiService,
deps: [ApiServiceAbstraction], deps: [ApiServiceAbstraction],
}), }),
safeProvider({
provide: KdfConfigServiceAbstraction,
useClass: KdfConfigService,
deps: [StateProvider],
}),
]; ];
function encryptServiceFactory( function encryptServiceFactory(

View File

@ -184,8 +184,6 @@ export class AddEditComponent implements OnInit, OnDestroy {
FeatureFlag.FlexibleCollectionsV1, FeatureFlag.FlexibleCollectionsV1,
false, false,
); );
this.writeableCollections = await this.loadCollections();
this.canUseReprompt = await this.passwordRepromptService.enabled();
this.policyService this.policyService
.policyAppliesToActiveUser$(PolicyType.PersonalOwnership) .policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
@ -197,6 +195,9 @@ export class AddEditComponent implements OnInit, OnDestroy {
takeUntil(this.destroy$), takeUntil(this.destroy$),
) )
.subscribe(); .subscribe();
this.writeableCollections = await this.loadCollections();
this.canUseReprompt = await this.passwordRepromptService.enabled();
} }
ngOnDestroy() { ngOnDestroy() {

View File

@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
@ -44,6 +45,7 @@ describe("AuthRequestLoginStrategy", () => {
let userDecryptionOptions: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>; let userDecryptionOptions: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
let deviceTrustService: MockProxy<DeviceTrustServiceAbstraction>; let deviceTrustService: MockProxy<DeviceTrustServiceAbstraction>;
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>; let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
let kdfConfigService: MockProxy<KdfConfigService>;
const mockUserId = Utils.newGuid() as UserId; const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService; let accountService: FakeAccountService;
@ -77,13 +79,16 @@ describe("AuthRequestLoginStrategy", () => {
userDecryptionOptions = mock<InternalUserDecryptionOptionsServiceAbstraction>(); userDecryptionOptions = mock<InternalUserDecryptionOptionsServiceAbstraction>();
deviceTrustService = mock<DeviceTrustServiceAbstraction>(); deviceTrustService = mock<DeviceTrustServiceAbstraction>();
billingAccountProfileStateService = mock<BillingAccountProfileStateService>(); billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
kdfConfigService = mock<KdfConfigService>();
accountService = mockAccountServiceWith(mockUserId); accountService = mockAccountServiceWith(mockUserId);
masterPasswordService = new FakeMasterPasswordService(); masterPasswordService = new FakeMasterPasswordService();
tokenService.getTwoFactorToken.mockResolvedValue(null); tokenService.getTwoFactorToken.mockResolvedValue(null);
appIdService.getAppId.mockResolvedValue(deviceId); appIdService.getAppId.mockResolvedValue(deviceId);
tokenService.decodeAccessToken.mockResolvedValue({}); tokenService.decodeAccessToken.mockResolvedValue({
sub: mockUserId,
});
authRequestLoginStrategy = new AuthRequestLoginStrategy( authRequestLoginStrategy = new AuthRequestLoginStrategy(
cache, cache,
@ -101,6 +106,7 @@ describe("AuthRequestLoginStrategy", () => {
userDecryptionOptions, userDecryptionOptions,
deviceTrustService, deviceTrustService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
tokenResponse = identityTokenResponseFactory(); tokenResponse = identityTokenResponseFactory();

View File

@ -3,6 +3,7 @@ import { Jsonify } from "type-fest";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
@ -63,6 +64,7 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
private deviceTrustService: DeviceTrustServiceAbstraction, private deviceTrustService: DeviceTrustServiceAbstraction,
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
accountService, accountService,
@ -78,6 +80,7 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
twoFactorService, twoFactorService,
userDecryptionOptionsService, userDecryptionOptionsService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
this.cache = new BehaviorSubject(data); this.cache = new BehaviorSubject(data);

View File

@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@ -24,11 +25,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { import { Account, AccountProfile } from "@bitwarden/common/platform/models/domain/account";
Account,
AccountProfile,
AccountKeys,
} from "@bitwarden/common/platform/models/domain/account";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
@ -117,6 +114,7 @@ describe("LoginStrategy", () => {
let policyService: MockProxy<PolicyService>; let policyService: MockProxy<PolicyService>;
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>; let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>; let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
let kdfConfigService: MockProxy<KdfConfigService>;
let passwordLoginStrategy: PasswordLoginStrategy; let passwordLoginStrategy: PasswordLoginStrategy;
let credentials: PasswordLoginCredentials; let credentials: PasswordLoginCredentials;
@ -136,6 +134,7 @@ describe("LoginStrategy", () => {
stateService = mock<StateService>(); stateService = mock<StateService>();
twoFactorService = mock<TwoFactorService>(); twoFactorService = mock<TwoFactorService>();
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>(); userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
kdfConfigService = mock<KdfConfigService>();
policyService = mock<PolicyService>(); policyService = mock<PolicyService>();
passwordStrengthService = mock<PasswordStrengthService>(); passwordStrengthService = mock<PasswordStrengthService>();
billingAccountProfileStateService = mock<BillingAccountProfileStateService>(); billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
@ -162,6 +161,7 @@ describe("LoginStrategy", () => {
policyService, policyService,
loginStrategyService, loginStrategyService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
credentials = new PasswordLoginCredentials(email, masterPassword); credentials = new PasswordLoginCredentials(email, masterPassword);
}); });
@ -208,11 +208,8 @@ describe("LoginStrategy", () => {
userId: userId, userId: userId,
name: name, name: name,
email: email, email: email,
kdfIterations: kdfIterations,
kdfType: kdf,
}, },
}, },
keys: new AccountKeys(),
}), }),
); );
expect(userDecryptionOptionsService.setUserDecryptionOptions).toHaveBeenCalledWith( expect(userDecryptionOptionsService.setUserDecryptionOptions).toHaveBeenCalledWith(
@ -221,6 +218,21 @@ describe("LoginStrategy", () => {
expect(messagingService.send).toHaveBeenCalledWith("loggedIn"); expect(messagingService.send).toHaveBeenCalledWith("loggedIn");
}); });
it("throws if active account isn't found after being initialized", async () => {
const idTokenResponse = identityTokenResponseFactory();
apiService.postIdentityToken.mockResolvedValue(idTokenResponse);
const mockVaultTimeoutAction = VaultTimeoutAction.Lock;
const mockVaultTimeout = 1000;
stateService.getVaultTimeoutAction.mockResolvedValue(mockVaultTimeoutAction);
stateService.getVaultTimeout.mockResolvedValue(mockVaultTimeout);
accountService.activeAccountSubject.next(null);
await expect(async () => await passwordLoginStrategy.logIn(credentials)).rejects.toThrow();
});
it("builds AuthResult", async () => { it("builds AuthResult", async () => {
const tokenResponse = identityTokenResponseFactory(); const tokenResponse = identityTokenResponseFactory();
tokenResponse.forcePasswordReset = true; tokenResponse.forcePasswordReset = true;
@ -304,8 +316,10 @@ describe("LoginStrategy", () => {
expect(tokenService.clearTwoFactorToken).toHaveBeenCalled(); expect(tokenService.clearTwoFactorToken).toHaveBeenCalled();
const expected = new AuthResult(); const expected = new AuthResult();
expected.twoFactorProviders = new Map<TwoFactorProviderType, { [key: string]: string }>(); expected.twoFactorProviders = { 0: null } as Record<
expected.twoFactorProviders.set(0, null); TwoFactorProviderType,
Record<string, string>
>;
expect(result).toEqual(expected); expect(result).toEqual(expected);
}); });
@ -334,8 +348,9 @@ describe("LoginStrategy", () => {
expect(messagingService.send).not.toHaveBeenCalled(); expect(messagingService.send).not.toHaveBeenCalled();
const expected = new AuthResult(); const expected = new AuthResult();
expected.twoFactorProviders = new Map<TwoFactorProviderType, { [key: string]: string }>(); expected.twoFactorProviders = {
expected.twoFactorProviders.set(1, { Email: "k***@bitwarden.com" }); [TwoFactorProviderType.Email]: { Email: "k***@bitwarden.com" },
};
expected.email = userEmail; expected.email = userEmail;
expected.ssoEmail2FaSessionToken = ssoEmail2FaSessionToken; expected.ssoEmail2FaSessionToken = ssoEmail2FaSessionToken;
@ -404,6 +419,7 @@ describe("LoginStrategy", () => {
policyService, policyService,
loginStrategyService, loginStrategyService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory()); apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());

View File

@ -1,13 +1,15 @@
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject, filter, firstValueFrom, timeout } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { Argon2KdfConfig, PBKDF2KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import { DeviceRequest } from "@bitwarden/common/auth/models/request/identity-token/device.request"; import { DeviceRequest } from "@bitwarden/common/auth/models/request/identity-token/device.request";
import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request"; import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request";
import { SsoTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/sso-token.request"; import { SsoTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/sso-token.request";
@ -27,6 +29,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { KdfType } from "@bitwarden/common/platform/enums";
import { Account, AccountProfile } from "@bitwarden/common/platform/models/domain/account"; import { Account, AccountProfile } from "@bitwarden/common/platform/models/domain/account";
import { UserId } from "@bitwarden/common/types/guid"; import { UserId } from "@bitwarden/common/types/guid";
@ -72,6 +75,7 @@ export abstract class LoginStrategy {
protected twoFactorService: TwoFactorService, protected twoFactorService: TwoFactorService,
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
protected billingAccountProfileStateService: BillingAccountProfileStateService, protected billingAccountProfileStateService: BillingAccountProfileStateService,
protected KdfConfigService: KdfConfigService,
) {} ) {}
abstract exportCache(): CacheData; abstract exportCache(): CacheData;
@ -97,7 +101,7 @@ export abstract class LoginStrategy {
} }
protected async startLogIn(): Promise<[AuthResult, IdentityResponse]> { protected async startLogIn(): Promise<[AuthResult, IdentityResponse]> {
this.twoFactorService.clearSelectedProvider(); await this.twoFactorService.clearSelectedProvider();
const tokenRequest = this.cache.value.tokenRequest; const tokenRequest = this.cache.value.tokenRequest;
const response = await this.apiService.postIdentityToken(tokenRequest); const response = await this.apiService.postIdentityToken(tokenRequest);
@ -155,12 +159,12 @@ export abstract class LoginStrategy {
* It also sets the access token and refresh token in the token service. * It also sets the access token and refresh token in the token service.
* *
* @param {IdentityTokenResponse} tokenResponse - The response from the server containing the identity token. * @param {IdentityTokenResponse} tokenResponse - The response from the server containing the identity token.
* @returns {Promise<void>} - A promise that resolves when the account information has been successfully saved. * @returns {Promise<UserId>} - A promise that resolves the the UserId when the account information has been successfully saved.
*/ */
protected async saveAccountInformation(tokenResponse: IdentityTokenResponse): Promise<UserId> { protected async saveAccountInformation(tokenResponse: IdentityTokenResponse): Promise<UserId> {
const accountInformation = await this.tokenService.decodeAccessToken(tokenResponse.accessToken); const accountInformation = await this.tokenService.decodeAccessToken(tokenResponse.accessToken);
const userId = accountInformation.sub; const userId = accountInformation.sub as UserId;
const vaultTimeoutAction = await this.stateService.getVaultTimeoutAction({ userId }); const vaultTimeoutAction = await this.stateService.getVaultTimeoutAction({ userId });
const vaultTimeout = await this.stateService.getVaultTimeout({ userId }); const vaultTimeout = await this.stateService.getVaultTimeout({ userId });
@ -182,21 +186,30 @@ export abstract class LoginStrategy {
userId, userId,
name: accountInformation.name, name: accountInformation.name,
email: accountInformation.email, email: accountInformation.email,
kdfIterations: tokenResponse.kdfIterations,
kdfMemory: tokenResponse.kdfMemory,
kdfParallelism: tokenResponse.kdfParallelism,
kdfType: tokenResponse.kdf,
}, },
}, },
}), }),
); );
await this.verifyAccountAdded(userId);
await this.userDecryptionOptionsService.setUserDecryptionOptions( await this.userDecryptionOptionsService.setUserDecryptionOptions(
UserDecryptionOptions.fromResponse(tokenResponse), UserDecryptionOptions.fromResponse(tokenResponse),
); );
await this.KdfConfigService.setKdfConfig(
userId as UserId,
tokenResponse.kdf === KdfType.PBKDF2_SHA256
? new PBKDF2KdfConfig(tokenResponse.kdfIterations)
: new Argon2KdfConfig(
tokenResponse.kdfIterations,
tokenResponse.kdfMemory,
tokenResponse.kdfParallelism,
),
);
await this.billingAccountProfileStateService.setHasPremium(accountInformation.premium, false); await this.billingAccountProfileStateService.setHasPremium(accountInformation.premium, false);
return userId as UserId; return userId;
} }
protected async processTokenResponse(response: IdentityTokenResponse): Promise<AuthResult> { protected async processTokenResponse(response: IdentityTokenResponse): Promise<AuthResult> {
@ -273,7 +286,7 @@ export abstract class LoginStrategy {
const result = new AuthResult(); const result = new AuthResult();
result.twoFactorProviders = response.twoFactorProviders2; result.twoFactorProviders = response.twoFactorProviders2;
this.twoFactorService.setProviders(response); await this.twoFactorService.setProviders(response);
this.cache.next({ ...this.cache.value, captchaBypassToken: response.captchaToken ?? null }); this.cache.next({ ...this.cache.value, captchaBypassToken: response.captchaToken ?? null });
result.ssoEmail2FaSessionToken = response.ssoEmail2faSessionToken; result.ssoEmail2FaSessionToken = response.ssoEmail2faSessionToken;
result.email = response.email; result.email = response.email;
@ -295,4 +308,24 @@ export abstract class LoginStrategy {
result.captchaSiteKey = response.siteKey; result.captchaSiteKey = response.siteKey;
return result; return result;
} }
/**
* Verifies that the active account is set after initialization.
* Note: In browser there is a slight delay between when active account emits in background,
* and when it emits in foreground. We're giving the foreground 1 second to catch up.
* If nothing is emitted, we throw an error.
*/
private async verifyAccountAdded(expectedUserId: UserId) {
await firstValueFrom(
this.accountService.activeAccount$.pipe(
filter((account) => account?.id === expectedUserId),
timeout({
first: 1000,
with: () => {
throw new Error("Expected user never made active user after initialization.");
},
}),
),
);
}
} }

View File

@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@ -71,6 +72,7 @@ describe("PasswordLoginStrategy", () => {
let policyService: MockProxy<PolicyService>; let policyService: MockProxy<PolicyService>;
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>; let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>; let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
let kdfConfigService: MockProxy<KdfConfigService>;
let passwordLoginStrategy: PasswordLoginStrategy; let passwordLoginStrategy: PasswordLoginStrategy;
let credentials: PasswordLoginCredentials; let credentials: PasswordLoginCredentials;
@ -94,9 +96,12 @@ describe("PasswordLoginStrategy", () => {
policyService = mock<PolicyService>(); policyService = mock<PolicyService>();
passwordStrengthService = mock<PasswordStrengthService>(); passwordStrengthService = mock<PasswordStrengthService>();
billingAccountProfileStateService = mock<BillingAccountProfileStateService>(); billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
kdfConfigService = mock<KdfConfigService>();
appIdService.getAppId.mockResolvedValue(deviceId); appIdService.getAppId.mockResolvedValue(deviceId);
tokenService.decodeAccessToken.mockResolvedValue({}); tokenService.decodeAccessToken.mockResolvedValue({
sub: userId,
});
loginStrategyService.makePreloginKey.mockResolvedValue(masterKey); loginStrategyService.makePreloginKey.mockResolvedValue(masterKey);
@ -127,6 +132,7 @@ describe("PasswordLoginStrategy", () => {
policyService, policyService,
loginStrategyService, loginStrategyService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
credentials = new PasswordLoginCredentials(email, masterPassword); credentials = new PasswordLoginCredentials(email, masterPassword);
tokenResponse = identityTokenResponseFactory(masterPasswordPolicy); tokenResponse = identityTokenResponseFactory(masterPasswordPolicy);

View File

@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
@ -89,6 +90,7 @@ export class PasswordLoginStrategy extends LoginStrategy {
private policyService: PolicyService, private policyService: PolicyService,
private loginStrategyService: LoginStrategyServiceAbstraction, private loginStrategyService: LoginStrategyServiceAbstraction,
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
accountService, accountService,
@ -104,6 +106,7 @@ export class PasswordLoginStrategy extends LoginStrategy {
twoFactorService, twoFactorService,
userDecryptionOptionsService, userDecryptionOptionsService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
this.cache = new BehaviorSubject(data); this.cache = new BehaviorSubject(data);

View File

@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
@ -54,6 +55,7 @@ describe("SsoLoginStrategy", () => {
let authRequestService: MockProxy<AuthRequestServiceAbstraction>; let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
let i18nService: MockProxy<I18nService>; let i18nService: MockProxy<I18nService>;
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>; let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
let kdfConfigService: MockProxy<KdfConfigService>;
let ssoLoginStrategy: SsoLoginStrategy; let ssoLoginStrategy: SsoLoginStrategy;
let credentials: SsoLoginCredentials; let credentials: SsoLoginCredentials;
@ -86,10 +88,13 @@ describe("SsoLoginStrategy", () => {
authRequestService = mock<AuthRequestServiceAbstraction>(); authRequestService = mock<AuthRequestServiceAbstraction>();
i18nService = mock<I18nService>(); i18nService = mock<I18nService>();
billingAccountProfileStateService = mock<BillingAccountProfileStateService>(); billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
kdfConfigService = mock<KdfConfigService>();
tokenService.getTwoFactorToken.mockResolvedValue(null); tokenService.getTwoFactorToken.mockResolvedValue(null);
appIdService.getAppId.mockResolvedValue(deviceId); appIdService.getAppId.mockResolvedValue(deviceId);
tokenService.decodeAccessToken.mockResolvedValue({}); tokenService.decodeAccessToken.mockResolvedValue({
sub: userId,
});
ssoLoginStrategy = new SsoLoginStrategy( ssoLoginStrategy = new SsoLoginStrategy(
null, null,
@ -110,6 +115,7 @@ describe("SsoLoginStrategy", () => {
authRequestService, authRequestService,
i18nService, i18nService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
credentials = new SsoLoginCredentials(ssoCode, ssoCodeVerifier, ssoRedirectUrl, ssoOrgId); credentials = new SsoLoginCredentials(ssoCode, ssoCodeVerifier, ssoRedirectUrl, ssoOrgId);
}); });

View File

@ -3,6 +3,7 @@ import { Jsonify } from "type-fest";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
@ -98,6 +99,7 @@ export class SsoLoginStrategy extends LoginStrategy {
private authRequestService: AuthRequestServiceAbstraction, private authRequestService: AuthRequestServiceAbstraction,
private i18nService: I18nService, private i18nService: I18nService,
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
accountService, accountService,
@ -113,6 +115,7 @@ export class SsoLoginStrategy extends LoginStrategy {
twoFactorService, twoFactorService,
userDecryptionOptionsService, userDecryptionOptionsService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
this.cache = new BehaviorSubject(data); this.cache = new BehaviorSubject(data);

View File

@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
@ -49,6 +50,7 @@ describe("UserApiLoginStrategy", () => {
let keyConnectorService: MockProxy<KeyConnectorService>; let keyConnectorService: MockProxy<KeyConnectorService>;
let environmentService: MockProxy<EnvironmentService>; let environmentService: MockProxy<EnvironmentService>;
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>; let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
let kdfConfigService: MockProxy<KdfConfigService>;
let apiLogInStrategy: UserApiLoginStrategy; let apiLogInStrategy: UserApiLoginStrategy;
let credentials: UserApiLoginCredentials; let credentials: UserApiLoginCredentials;
@ -76,10 +78,13 @@ describe("UserApiLoginStrategy", () => {
keyConnectorService = mock<KeyConnectorService>(); keyConnectorService = mock<KeyConnectorService>();
environmentService = mock<EnvironmentService>(); environmentService = mock<EnvironmentService>();
billingAccountProfileStateService = mock<BillingAccountProfileStateService>(); billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
kdfConfigService = mock<KdfConfigService>();
appIdService.getAppId.mockResolvedValue(deviceId); appIdService.getAppId.mockResolvedValue(deviceId);
tokenService.getTwoFactorToken.mockResolvedValue(null); tokenService.getTwoFactorToken.mockResolvedValue(null);
tokenService.decodeAccessToken.mockResolvedValue({}); tokenService.decodeAccessToken.mockResolvedValue({
sub: userId,
});
apiLogInStrategy = new UserApiLoginStrategy( apiLogInStrategy = new UserApiLoginStrategy(
cache, cache,
@ -98,6 +103,7 @@ describe("UserApiLoginStrategy", () => {
environmentService, environmentService,
keyConnectorService, keyConnectorService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
credentials = new UserApiLoginCredentials(apiClientId, apiClientSecret); credentials = new UserApiLoginCredentials(apiClientId, apiClientSecret);

View File

@ -3,6 +3,7 @@ import { Jsonify } from "type-fest";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
@ -57,6 +58,7 @@ export class UserApiLoginStrategy extends LoginStrategy {
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private keyConnectorService: KeyConnectorService, private keyConnectorService: KeyConnectorService,
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
protected kdfConfigService: KdfConfigService,
) { ) {
super( super(
accountService, accountService,
@ -72,6 +74,7 @@ export class UserApiLoginStrategy extends LoginStrategy {
twoFactorService, twoFactorService,
userDecryptionOptionsService, userDecryptionOptionsService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
this.cache = new BehaviorSubject(data); this.cache = new BehaviorSubject(data);
} }

View File

@ -1,6 +1,7 @@
import { mock, MockProxy } from "jest-mock-extended"; import { mock, MockProxy } from "jest-mock-extended";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
@ -17,7 +18,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { FakeAccountService } from "@bitwarden/common/spec"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { PrfKey, UserKey } from "@bitwarden/common/types/key"; import { PrfKey, UserKey } from "@bitwarden/common/types/key";
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
@ -42,11 +44,13 @@ describe("WebAuthnLoginStrategy", () => {
let twoFactorService!: MockProxy<TwoFactorService>; let twoFactorService!: MockProxy<TwoFactorService>;
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>; let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>; let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
let kdfConfigService: MockProxy<KdfConfigService>;
let webAuthnLoginStrategy!: WebAuthnLoginStrategy; let webAuthnLoginStrategy!: WebAuthnLoginStrategy;
const token = "mockToken"; const token = "mockToken";
const deviceId = Utils.newGuid(); const deviceId = Utils.newGuid();
const userId = Utils.newGuid() as UserId;
let webAuthnCredentials!: WebAuthnLoginCredentials; let webAuthnCredentials!: WebAuthnLoginCredentials;
@ -67,7 +71,7 @@ describe("WebAuthnLoginStrategy", () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
accountService = new FakeAccountService(null); accountService = mockAccountServiceWith(userId);
masterPasswordService = new FakeMasterPasswordService(); masterPasswordService = new FakeMasterPasswordService();
cryptoService = mock<CryptoService>(); cryptoService = mock<CryptoService>();
@ -81,10 +85,13 @@ describe("WebAuthnLoginStrategy", () => {
twoFactorService = mock<TwoFactorService>(); twoFactorService = mock<TwoFactorService>();
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>(); userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
billingAccountProfileStateService = mock<BillingAccountProfileStateService>(); billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
kdfConfigService = mock<KdfConfigService>();
tokenService.getTwoFactorToken.mockResolvedValue(null); tokenService.getTwoFactorToken.mockResolvedValue(null);
appIdService.getAppId.mockResolvedValue(deviceId); appIdService.getAppId.mockResolvedValue(deviceId);
tokenService.decodeAccessToken.mockResolvedValue({}); tokenService.decodeAccessToken.mockResolvedValue({
sub: userId,
});
webAuthnLoginStrategy = new WebAuthnLoginStrategy( webAuthnLoginStrategy = new WebAuthnLoginStrategy(
cache, cache,
@ -101,6 +108,7 @@ describe("WebAuthnLoginStrategy", () => {
twoFactorService, twoFactorService,
userDecryptionOptionsService, userDecryptionOptionsService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
// Create credentials // Create credentials

View File

@ -3,6 +3,7 @@ import { Jsonify } from "type-fest";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
@ -57,6 +58,7 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
twoFactorService: TwoFactorService, twoFactorService: TwoFactorService,
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
kdfConfigService: KdfConfigService,
) { ) {
super( super(
accountService, accountService,
@ -72,6 +74,7 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
twoFactorService, twoFactorService,
userDecryptionOptionsService, userDecryptionOptionsService,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
this.cache = new BehaviorSubject(data); this.cache = new BehaviorSubject(data);

View File

@ -3,6 +3,7 @@ import { MockProxy, mock } from "jest-mock-extended";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
@ -66,6 +67,7 @@ describe("LoginStrategyService", () => {
let authRequestService: MockProxy<AuthRequestServiceAbstraction>; let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>; let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>; let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
let kdfConfigService: MockProxy<KdfConfigService>;
let stateProvider: FakeGlobalStateProvider; let stateProvider: FakeGlobalStateProvider;
let loginStrategyCacheExpirationState: FakeGlobalState<Date | null>; let loginStrategyCacheExpirationState: FakeGlobalState<Date | null>;
@ -95,6 +97,7 @@ describe("LoginStrategyService", () => {
userDecryptionOptionsService = mock<UserDecryptionOptionsService>(); userDecryptionOptionsService = mock<UserDecryptionOptionsService>();
billingAccountProfileStateService = mock<BillingAccountProfileStateService>(); billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
stateProvider = new FakeGlobalStateProvider(); stateProvider = new FakeGlobalStateProvider();
kdfConfigService = mock<KdfConfigService>();
sut = new LoginStrategyService( sut = new LoginStrategyService(
accountService, accountService,
@ -119,6 +122,7 @@ describe("LoginStrategyService", () => {
userDecryptionOptionsService, userDecryptionOptionsService,
stateProvider, stateProvider,
billingAccountProfileStateService, billingAccountProfileStateService,
kdfConfigService,
); );
loginStrategyCacheExpirationState = stateProvider.getFake(CACHE_EXPIRATION_KEY); loginStrategyCacheExpirationState = stateProvider.getFake(CACHE_EXPIRATION_KEY);

View File

@ -10,13 +10,18 @@ import {
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import {
Argon2KdfConfig,
KdfConfig,
PBKDF2KdfConfig,
} from "@bitwarden/common/auth/models/domain/kdf-config";
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request"; import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
@ -32,7 +37,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { KdfType } from "@bitwarden/common/platform/enums"; import { KdfType } from "@bitwarden/common/platform/enums/kdf-type.enum";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state"; import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction";
@ -105,6 +110,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
protected stateProvider: GlobalStateProvider, protected stateProvider: GlobalStateProvider,
protected billingAccountProfileStateService: BillingAccountProfileStateService, protected billingAccountProfileStateService: BillingAccountProfileStateService,
protected kdfConfigService: KdfConfigService,
) { ) {
this.currentAuthnTypeState = this.stateProvider.get(CURRENT_LOGIN_STRATEGY_KEY); this.currentAuthnTypeState = this.stateProvider.get(CURRENT_LOGIN_STRATEGY_KEY);
this.loginStrategyCacheState = this.stateProvider.get(CACHE_KEY); this.loginStrategyCacheState = this.stateProvider.get(CACHE_KEY);
@ -233,24 +239,25 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
async makePreloginKey(masterPassword: string, email: string): Promise<MasterKey> { async makePreloginKey(masterPassword: string, email: string): Promise<MasterKey> {
email = email.trim().toLowerCase(); email = email.trim().toLowerCase();
let kdf: KdfType = null;
let kdfConfig: KdfConfig = null; let kdfConfig: KdfConfig = null;
try { try {
const preloginResponse = await this.apiService.postPrelogin(new PreloginRequest(email)); const preloginResponse = await this.apiService.postPrelogin(new PreloginRequest(email));
if (preloginResponse != null) { if (preloginResponse != null) {
kdf = preloginResponse.kdf; kdfConfig =
kdfConfig = new KdfConfig( preloginResponse.kdf === KdfType.PBKDF2_SHA256
preloginResponse.kdfIterations, ? new PBKDF2KdfConfig(preloginResponse.kdfIterations)
preloginResponse.kdfMemory, : new Argon2KdfConfig(
preloginResponse.kdfParallelism, preloginResponse.kdfIterations,
); preloginResponse.kdfMemory,
preloginResponse.kdfParallelism,
);
} }
} catch (e) { } catch (e) {
if (e == null || e.statusCode !== 404) { if (e == null || e.statusCode !== 404) {
throw e; throw e;
} }
} }
return await this.cryptoService.makeMasterKey(masterPassword, email, kdf, kdfConfig); return await this.cryptoService.makeMasterKey(masterPassword, email, kdfConfig);
} }
// TODO: move to auth request service // TODO: move to auth request service
@ -354,6 +361,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
this.policyService, this.policyService,
this, this,
this.billingAccountProfileStateService, this.billingAccountProfileStateService,
this.kdfConfigService,
); );
case AuthenticationType.Sso: case AuthenticationType.Sso:
return new SsoLoginStrategy( return new SsoLoginStrategy(
@ -375,6 +383,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
this.authRequestService, this.authRequestService,
this.i18nService, this.i18nService,
this.billingAccountProfileStateService, this.billingAccountProfileStateService,
this.kdfConfigService,
); );
case AuthenticationType.UserApiKey: case AuthenticationType.UserApiKey:
return new UserApiLoginStrategy( return new UserApiLoginStrategy(
@ -394,6 +403,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
this.environmentService, this.environmentService,
this.keyConnectorService, this.keyConnectorService,
this.billingAccountProfileStateService, this.billingAccountProfileStateService,
this.kdfConfigService,
); );
case AuthenticationType.AuthRequest: case AuthenticationType.AuthRequest:
return new AuthRequestLoginStrategy( return new AuthRequestLoginStrategy(
@ -412,6 +422,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
this.userDecryptionOptionsService, this.userDecryptionOptionsService,
this.deviceTrustService, this.deviceTrustService,
this.billingAccountProfileStateService, this.billingAccountProfileStateService,
this.kdfConfigService,
); );
case AuthenticationType.WebAuthn: case AuthenticationType.WebAuthn:
return new WebAuthnLoginStrategy( return new WebAuthnLoginStrategy(
@ -429,6 +440,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
this.twoFactorService, this.twoFactorService,
this.userDecryptionOptionsService, this.userDecryptionOptionsService,
this.billingAccountProfileStateService, this.billingAccountProfileStateService,
this.kdfConfigService,
); );
} }
}), }),

View File

@ -1,9 +1,9 @@
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { KdfType } from "@bitwarden/common/platform/enums";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { PinLockType } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; import { PinLockType } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
import { UserKey } from "@bitwarden/common/types/key"; import { UserKey } from "@bitwarden/common/types/key";
@ -16,6 +16,7 @@ export class PinCryptoService implements PinCryptoServiceAbstraction {
private cryptoService: CryptoService, private cryptoService: CryptoService,
private vaultTimeoutSettingsService: VaultTimeoutSettingsService, private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
private logService: LogService, private logService: LogService,
private kdfConfigService: KdfConfigService,
) {} ) {}
async decryptUserKeyWithPin(pin: string): Promise<UserKey | null> { async decryptUserKeyWithPin(pin: string): Promise<UserKey | null> {
try { try {
@ -24,8 +25,7 @@ export class PinCryptoService implements PinCryptoServiceAbstraction {
const { pinKeyEncryptedUserKey, oldPinKeyEncryptedMasterKey } = const { pinKeyEncryptedUserKey, oldPinKeyEncryptedMasterKey } =
await this.getPinKeyEncryptedKeys(pinLockType); await this.getPinKeyEncryptedKeys(pinLockType);
const kdf: KdfType = await this.stateService.getKdfType(); const kdfConfig: KdfConfig = await this.kdfConfigService.getKdfConfig();
const kdfConfig: KdfConfig = await this.stateService.getKdfConfig();
let userKey: UserKey; let userKey: UserKey;
const email = await this.stateService.getEmail(); const email = await this.stateService.getEmail();
if (oldPinKeyEncryptedMasterKey) { if (oldPinKeyEncryptedMasterKey) {
@ -33,7 +33,6 @@ export class PinCryptoService implements PinCryptoServiceAbstraction {
pinLockType === "TRANSIENT", pinLockType === "TRANSIENT",
pin, pin,
email, email,
kdf,
kdfConfig, kdfConfig,
oldPinKeyEncryptedMasterKey, oldPinKeyEncryptedMasterKey,
); );
@ -41,7 +40,6 @@ export class PinCryptoService implements PinCryptoServiceAbstraction {
userKey = await this.cryptoService.decryptUserKeyWithPin( userKey = await this.cryptoService.decryptUserKeyWithPin(
pin, pin,
email, email,
kdf,
kdfConfig, kdfConfig,
pinKeyEncryptedUserKey, pinKeyEncryptedUserKey,
); );

View File

@ -1,9 +1,10 @@
import { mock } from "jest-mock-extended"; import { mock } from "jest-mock-extended";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/platform/enums";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { import {
@ -13,6 +14,7 @@ import {
import { UserKey } from "@bitwarden/common/types/key"; import { UserKey } from "@bitwarden/common/types/key";
import { PinCryptoService } from "./pin-crypto.service.implementation"; import { PinCryptoService } from "./pin-crypto.service.implementation";
describe("PinCryptoService", () => { describe("PinCryptoService", () => {
let pinCryptoService: PinCryptoService; let pinCryptoService: PinCryptoService;
@ -20,6 +22,7 @@ describe("PinCryptoService", () => {
const cryptoService = mock<CryptoService>(); const cryptoService = mock<CryptoService>();
const vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>(); const vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
const logService = mock<LogService>(); const logService = mock<LogService>();
const kdfConfigService = mock<KdfConfigService>();
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@ -29,6 +32,7 @@ describe("PinCryptoService", () => {
cryptoService, cryptoService,
vaultTimeoutSettingsService, vaultTimeoutSettingsService,
logService, logService,
kdfConfigService,
); );
}); });
@ -39,7 +43,6 @@ describe("PinCryptoService", () => {
describe("decryptUserKeyWithPin(...)", () => { describe("decryptUserKeyWithPin(...)", () => {
const mockPin = "1234"; const mockPin = "1234";
const mockProtectedPin = "protectedPin"; const mockProtectedPin = "protectedPin";
const DEFAULT_PBKDF2_ITERATIONS = 600000;
const mockUserEmail = "user@example.com"; const mockUserEmail = "user@example.com";
const mockUserKey = new SymmetricCryptoKey(randomBytes(32)) as UserKey; const mockUserKey = new SymmetricCryptoKey(randomBytes(32)) as UserKey;
@ -49,7 +52,7 @@ describe("PinCryptoService", () => {
) { ) {
vaultTimeoutSettingsService.isPinLockSet.mockResolvedValue(pinLockType); vaultTimeoutSettingsService.isPinLockSet.mockResolvedValue(pinLockType);
stateService.getKdfConfig.mockResolvedValue(new KdfConfig(DEFAULT_PBKDF2_ITERATIONS)); kdfConfigService.getKdfConfig.mockResolvedValue(DEFAULT_KDF_CONFIG);
stateService.getEmail.mockResolvedValue(mockUserEmail); stateService.getEmail.mockResolvedValue(mockUserEmail);
if (migrationStatus === "PRE") { if (migrationStatus === "PRE") {

View File

@ -7,3 +7,4 @@ export * from "./provider-type.enum";
export * from "./provider-user-status-type.enum"; export * from "./provider-user-status-type.enum";
export * from "./provider-user-type.enum"; export * from "./provider-user-type.enum";
export * from "./scim-provider-type.enum"; export * from "./scim-provider-type.enum";
export * from "./provider-status-type.enum";

View File

@ -0,0 +1,5 @@
export enum ProviderStatusType {
Pending = 0,
Created = 1,
Billable = 2,
}

View File

@ -1,4 +1,4 @@
import { ProviderUserStatusType, ProviderUserType } from "../../enums"; import { ProviderStatusType, ProviderUserStatusType, ProviderUserType } from "../../enums";
import { ProfileProviderResponse } from "../response/profile-provider.response"; import { ProfileProviderResponse } from "../response/profile-provider.response";
export class ProviderData { export class ProviderData {
@ -9,6 +9,7 @@ export class ProviderData {
enabled: boolean; enabled: boolean;
userId: string; userId: string;
useEvents: boolean; useEvents: boolean;
providerStatus: ProviderStatusType;
constructor(response: ProfileProviderResponse) { constructor(response: ProfileProviderResponse) {
this.id = response.id; this.id = response.id;
@ -18,5 +19,6 @@ export class ProviderData {
this.enabled = response.enabled; this.enabled = response.enabled;
this.userId = response.userId; this.userId = response.userId;
this.useEvents = response.useEvents; this.useEvents = response.useEvents;
this.providerStatus = response.providerStatus;
} }
} }

View File

@ -1,4 +1,4 @@
import { ProviderUserStatusType, ProviderUserType } from "../../enums"; import { ProviderStatusType, ProviderUserStatusType, ProviderUserType } from "../../enums";
import { ProviderData } from "../data/provider.data"; import { ProviderData } from "../data/provider.data";
export class Provider { export class Provider {
@ -9,6 +9,7 @@ export class Provider {
enabled: boolean; enabled: boolean;
userId: string; userId: string;
useEvents: boolean; useEvents: boolean;
providerStatus: ProviderStatusType;
constructor(obj?: ProviderData) { constructor(obj?: ProviderData) {
if (obj == null) { if (obj == null) {
@ -22,6 +23,7 @@ export class Provider {
this.enabled = obj.enabled; this.enabled = obj.enabled;
this.userId = obj.userId; this.userId = obj.userId;
this.useEvents = obj.useEvents; this.useEvents = obj.useEvents;
this.providerStatus = obj.providerStatus;
} }
get canAccess() { get canAccess() {

View File

@ -1,5 +1,5 @@
import { BaseResponse } from "../../../models/response/base.response"; import { BaseResponse } from "../../../models/response/base.response";
import { ProviderUserStatusType, ProviderUserType } from "../../enums"; import { ProviderStatusType, ProviderUserStatusType, ProviderUserType } from "../../enums";
import { PermissionsApi } from "../api/permissions.api"; import { PermissionsApi } from "../api/permissions.api";
export class ProfileProviderResponse extends BaseResponse { export class ProfileProviderResponse extends BaseResponse {
@ -12,6 +12,7 @@ export class ProfileProviderResponse extends BaseResponse {
permissions: PermissionsApi; permissions: PermissionsApi;
userId: string; userId: string;
useEvents: boolean; useEvents: boolean;
providerStatus: ProviderStatusType;
constructor(response: any) { constructor(response: any) {
super(response); super(response);
@ -24,5 +25,6 @@ export class ProfileProviderResponse extends BaseResponse {
this.permissions = new PermissionsApi(this.getResponseProperty("permissions")); this.permissions = new PermissionsApi(this.getResponseProperty("permissions"));
this.userId = this.getResponseProperty("UserId"); this.userId = this.getResponseProperty("UserId");
this.useEvents = this.getResponseProperty("UseEvents"); this.useEvents = this.getResponseProperty("UseEvents");
this.providerStatus = this.getResponseProperty("ProviderStatus");
} }
} }

View File

@ -2,7 +2,7 @@ import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from ".
import { FakeActiveUserState, FakeSingleUserState } from "../../../spec/fake-state"; import { FakeActiveUserState, FakeSingleUserState } from "../../../spec/fake-state";
import { Utils } from "../../platform/misc/utils"; import { Utils } from "../../platform/misc/utils";
import { UserId } from "../../types/guid"; import { UserId } from "../../types/guid";
import { ProviderUserStatusType, ProviderUserType } from "../enums"; import { ProviderStatusType, ProviderUserStatusType, ProviderUserType } from "../enums";
import { ProviderData } from "../models/data/provider.data"; import { ProviderData } from "../models/data/provider.data";
import { Provider } from "../models/domain/provider"; import { Provider } from "../models/domain/provider";
@ -64,6 +64,7 @@ describe("PROVIDERS key definition", () => {
enabled: true, enabled: true,
userId: "string", userId: "string",
useEvents: true, useEvents: true,
providerStatus: ProviderStatusType.Pending,
}, },
}; };
const result = sut.deserializer(JSON.parse(JSON.stringify(expectedResult))); const result = sut.deserializer(JSON.parse(JSON.stringify(expectedResult)));

View File

@ -0,0 +1,7 @@
import { UserId } from "../../types/guid";
import { KdfConfig } from "../models/domain/kdf-config";
export abstract class KdfConfigService {
setKdfConfig: (userId: UserId, KdfConfig: KdfConfig) => Promise<void>;
getKdfConfig: () => Promise<KdfConfig>;
}

View File

@ -12,12 +12,12 @@ export interface TwoFactorProviderDetails {
export abstract class TwoFactorService { export abstract class TwoFactorService {
init: () => void; init: () => void;
getSupportedProviders: (win: Window) => TwoFactorProviderDetails[]; getSupportedProviders: (win: Window) => Promise<TwoFactorProviderDetails[]>;
getDefaultProvider: (webAuthnSupported: boolean) => TwoFactorProviderType; getDefaultProvider: (webAuthnSupported: boolean) => Promise<TwoFactorProviderType>;
setSelectedProvider: (type: TwoFactorProviderType) => void; setSelectedProvider: (type: TwoFactorProviderType) => Promise<void>;
clearSelectedProvider: () => void; clearSelectedProvider: () => Promise<void>;
setProviders: (response: IdentityTwoFactorResponse) => void; setProviders: (response: IdentityTwoFactorResponse) => Promise<void>;
clearProviders: () => void; clearProviders: () => Promise<void>;
getProviders: () => Map<TwoFactorProviderType, { [key: string]: string }>; getProviders: () => Promise<Map<TwoFactorProviderType, { [key: string]: string }>>;
} }

View File

@ -14,7 +14,7 @@ export class AuthResult {
resetMasterPassword = false; resetMasterPassword = false;
forcePasswordReset: ForceSetPasswordReason = ForceSetPasswordReason.None; forcePasswordReset: ForceSetPasswordReason = ForceSetPasswordReason.None;
twoFactorProviders: Map<TwoFactorProviderType, { [key: string]: string }> = null; twoFactorProviders: Partial<Record<TwoFactorProviderType, Record<string, string>>> = null;
ssoEmail2FaSessionToken?: string; ssoEmail2FaSessionToken?: string;
email: string; email: string;
requiresEncryptionKeyMigration: boolean; requiresEncryptionKeyMigration: boolean;

View File

@ -1,11 +1,86 @@
export class KdfConfig { import { Jsonify } from "type-fest";
iterations: number;
memory?: number;
parallelism?: number;
constructor(iterations: number, memory?: number, parallelism?: number) { import {
this.iterations = iterations; ARGON2_ITERATIONS,
this.memory = memory; ARGON2_MEMORY,
this.parallelism = parallelism; ARGON2_PARALLELISM,
KdfType,
PBKDF2_ITERATIONS,
} from "../../../platform/enums/kdf-type.enum";
/**
* Represents a type safe KDF configuration.
*/
export type KdfConfig = PBKDF2KdfConfig | Argon2KdfConfig;
/**
* Password-Based Key Derivation Function 2 (PBKDF2) KDF configuration.
*/
export class PBKDF2KdfConfig {
kdfType: KdfType.PBKDF2_SHA256 = KdfType.PBKDF2_SHA256;
iterations: number;
constructor(iterations?: number) {
this.iterations = iterations ?? PBKDF2_ITERATIONS.defaultValue;
}
/**
* Validates the PBKDF2 KDF configuration.
* A Valid PBKDF2 KDF configuration has KDF iterations between the 600_000 and 2_000_000.
*/
validateKdfConfig(): void {
if (!PBKDF2_ITERATIONS.inRange(this.iterations)) {
throw new Error(
`PBKDF2 iterations must be between ${PBKDF2_ITERATIONS.min} and ${PBKDF2_ITERATIONS.max}`,
);
}
}
static fromJSON(json: Jsonify<PBKDF2KdfConfig>): PBKDF2KdfConfig {
return new PBKDF2KdfConfig(json.iterations);
}
}
/**
* Argon2 KDF configuration.
*/
export class Argon2KdfConfig {
kdfType: KdfType.Argon2id = KdfType.Argon2id;
iterations: number;
memory: number;
parallelism: number;
constructor(iterations?: number, memory?: number, parallelism?: number) {
this.iterations = iterations ?? ARGON2_ITERATIONS.defaultValue;
this.memory = memory ?? ARGON2_MEMORY.defaultValue;
this.parallelism = parallelism ?? ARGON2_PARALLELISM.defaultValue;
}
/**
* Validates the Argon2 KDF configuration.
* A Valid Argon2 KDF configuration has iterations between 2 and 10, memory between 16mb and 1024mb, and parallelism between 1 and 16.
*/
validateKdfConfig(): void {
if (!ARGON2_ITERATIONS.inRange(this.iterations)) {
throw new Error(
`Argon2 iterations must be between ${ARGON2_ITERATIONS.min} and ${ARGON2_ITERATIONS.max}`,
);
}
if (!ARGON2_MEMORY.inRange(this.memory)) {
throw new Error(
`Argon2 memory must be between ${ARGON2_MEMORY.min}mb and ${ARGON2_MEMORY.max}mb`,
);
}
if (!ARGON2_PARALLELISM.inRange(this.parallelism)) {
throw new Error(
`Argon2 parallelism must be between ${ARGON2_PARALLELISM.min} and ${ARGON2_PARALLELISM.max}.`,
);
}
}
static fromJSON(json: Jsonify<Argon2KdfConfig>): Argon2KdfConfig {
return new Argon2KdfConfig(json.iterations, json.memory, json.parallelism);
} }
} }

View File

@ -11,18 +11,14 @@ export class SetKeyConnectorKeyRequest {
kdfParallelism?: number; kdfParallelism?: number;
orgIdentifier: string; orgIdentifier: string;
constructor( constructor(key: string, kdfConfig: KdfConfig, orgIdentifier: string, keys: KeysRequest) {
key: string,
kdf: KdfType,
kdfConfig: KdfConfig,
orgIdentifier: string,
keys: KeysRequest,
) {
this.key = key; this.key = key;
this.kdf = kdf; this.kdf = kdfConfig.kdfType;
this.kdfIterations = kdfConfig.iterations; this.kdfIterations = kdfConfig.iterations;
this.kdfMemory = kdfConfig.memory; if (kdfConfig.kdfType === KdfType.Argon2id) {
this.kdfParallelism = kdfConfig.parallelism; this.kdfMemory = kdfConfig.memory;
this.kdfParallelism = kdfConfig.parallelism;
}
this.orgIdentifier = orgIdentifier; this.orgIdentifier = orgIdentifier;
this.keys = keys; this.keys = keys;
} }

View File

@ -4,8 +4,10 @@ import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
import { MasterPasswordPolicyResponse } from "./master-password-policy.response"; import { MasterPasswordPolicyResponse } from "./master-password-policy.response";
export class IdentityTwoFactorResponse extends BaseResponse { export class IdentityTwoFactorResponse extends BaseResponse {
// contains available two-factor providers
twoFactorProviders: TwoFactorProviderType[]; twoFactorProviders: TwoFactorProviderType[];
twoFactorProviders2 = new Map<TwoFactorProviderType, { [key: string]: string }>(); // a map of two-factor providers to necessary data for completion
twoFactorProviders2: Record<TwoFactorProviderType, Record<string, string>>;
captchaToken: string; captchaToken: string;
ssoEmail2faSessionToken: string; ssoEmail2faSessionToken: string;
email?: string; email?: string;
@ -15,15 +17,7 @@ export class IdentityTwoFactorResponse extends BaseResponse {
super(response); super(response);
this.captchaToken = this.getResponseProperty("CaptchaBypassToken"); this.captchaToken = this.getResponseProperty("CaptchaBypassToken");
this.twoFactorProviders = this.getResponseProperty("TwoFactorProviders"); this.twoFactorProviders = this.getResponseProperty("TwoFactorProviders");
const twoFactorProviders2 = this.getResponseProperty("TwoFactorProviders2"); this.twoFactorProviders2 = this.getResponseProperty("TwoFactorProviders2");
if (twoFactorProviders2 != null) {
for (const prop in twoFactorProviders2) {
// eslint-disable-next-line
if (twoFactorProviders2.hasOwnProperty(prop)) {
this.twoFactorProviders2.set(parseInt(prop, null), twoFactorProviders2[prop]);
}
}
}
this.masterPasswordPolicy = new MasterPasswordPolicyResponse( this.masterPasswordPolicy = new MasterPasswordPolicyResponse(
this.getResponseProperty("MasterPasswordPolicy"), this.getResponseProperty("MasterPasswordPolicy"),
); );

View File

@ -0,0 +1,104 @@
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec";
import {
ARGON2_ITERATIONS,
ARGON2_MEMORY,
ARGON2_PARALLELISM,
PBKDF2_ITERATIONS,
} from "../../platform/enums/kdf-type.enum";
import { Utils } from "../../platform/misc/utils";
import { UserId } from "../../types/guid";
import { Argon2KdfConfig, PBKDF2KdfConfig } from "../models/domain/kdf-config";
import { KdfConfigService } from "./kdf-config.service";
describe("KdfConfigService", () => {
let sutKdfConfigService: KdfConfigService;
let fakeStateProvider: FakeStateProvider;
let fakeAccountService: FakeAccountService;
const mockUserId = Utils.newGuid() as UserId;
beforeEach(() => {
jest.clearAllMocks();
fakeAccountService = mockAccountServiceWith(mockUserId);
fakeStateProvider = new FakeStateProvider(fakeAccountService);
sutKdfConfigService = new KdfConfigService(fakeStateProvider);
});
it("setKdfConfig(): should set the KDF config", async () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(600_000);
await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig);
await expect(sutKdfConfigService.getKdfConfig()).resolves.toEqual(kdfConfig);
});
it("setKdfConfig(): should get the KDF config", async () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 4);
await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig);
await expect(sutKdfConfigService.getKdfConfig()).resolves.toEqual(kdfConfig);
});
it("setKdfConfig(): should throw error KDF cannot be null", async () => {
const kdfConfig: Argon2KdfConfig = null;
try {
await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig);
} catch (e) {
expect(e).toEqual(new Error("kdfConfig cannot be null"));
}
});
it("setKdfConfig(): should throw error userId cannot be null", async () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 4);
try {
await sutKdfConfigService.setKdfConfig(null, kdfConfig);
} catch (e) {
expect(e).toEqual(new Error("userId cannot be null"));
}
});
it("getKdfConfig(): should throw error KdfConfig for active user account state is null", async () => {
try {
await sutKdfConfigService.getKdfConfig();
} catch (e) {
expect(e).toEqual(new Error("KdfConfig for active user account state is null"));
}
});
it("validateKdfConfig(): should validate the PBKDF2 KDF config", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(600_000);
expect(() => kdfConfig.validateKdfConfig()).not.toThrow();
});
it("validateKdfConfig(): should validate the Argon2id KDF config", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 4);
expect(() => kdfConfig.validateKdfConfig()).not.toThrow();
});
it("validateKdfConfig(): should throw an error for invalid PBKDF2 iterations", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(100);
expect(() => kdfConfig.validateKdfConfig()).toThrow(
`PBKDF2 iterations must be between ${PBKDF2_ITERATIONS.min} and ${PBKDF2_ITERATIONS.max}`,
);
});
it("validateKdfConfig(): should throw an error for invalid Argon2 iterations", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(11, 64, 4);
expect(() => kdfConfig.validateKdfConfig()).toThrow(
`Argon2 iterations must be between ${ARGON2_ITERATIONS.min} and ${ARGON2_ITERATIONS.max}`,
);
});
it("validateKdfConfig(): should throw an error for invalid Argon2 memory", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 1025, 4);
expect(() => kdfConfig.validateKdfConfig()).toThrow(
`Argon2 memory must be between ${ARGON2_MEMORY.min}mb and ${ARGON2_MEMORY.max}mb`,
);
});
it("validateKdfConfig(): should throw an error for invalid Argon2 parallelism", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 17);
expect(() => kdfConfig.validateKdfConfig()).toThrow(
`Argon2 parallelism must be between ${ARGON2_PARALLELISM.min} and ${ARGON2_PARALLELISM.max}`,
);
});
});

View File

@ -0,0 +1,41 @@
import { firstValueFrom } from "rxjs";
import { KdfType } from "../../platform/enums/kdf-type.enum";
import { KDF_CONFIG_DISK, StateProvider, UserKeyDefinition } from "../../platform/state";
import { UserId } from "../../types/guid";
import { KdfConfigService as KdfConfigServiceAbstraction } from "../abstractions/kdf-config.service";
import { Argon2KdfConfig, KdfConfig, PBKDF2KdfConfig } from "../models/domain/kdf-config";
export const KDF_CONFIG = new UserKeyDefinition<KdfConfig>(KDF_CONFIG_DISK, "kdfConfig", {
deserializer: (kdfConfig: KdfConfig) => {
if (kdfConfig == null) {
return null;
}
return kdfConfig.kdfType === KdfType.PBKDF2_SHA256
? PBKDF2KdfConfig.fromJSON(kdfConfig)
: Argon2KdfConfig.fromJSON(kdfConfig);
},
clearOn: ["logout"],
});
export class KdfConfigService implements KdfConfigServiceAbstraction {
constructor(private stateProvider: StateProvider) {}
async setKdfConfig(userId: UserId, kdfConfig: KdfConfig) {
if (!userId) {
throw new Error("userId cannot be null");
}
if (kdfConfig === null) {
throw new Error("kdfConfig cannot be null");
}
await this.stateProvider.setUserState(KDF_CONFIG, kdfConfig, userId);
}
async getKdfConfig(): Promise<KdfConfig> {
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
const state = await firstValueFrom(this.stateProvider.getUser(userId, KDF_CONFIG).state$);
if (state === null) {
throw new Error("KdfConfig for active user account state is null");
}
return state;
}
}

View File

@ -7,6 +7,7 @@ import { KeysRequest } from "../../models/request/keys.request";
import { CryptoService } from "../../platform/abstractions/crypto.service"; import { CryptoService } from "../../platform/abstractions/crypto.service";
import { KeyGenerationService } from "../../platform/abstractions/key-generation.service"; import { KeyGenerationService } from "../../platform/abstractions/key-generation.service";
import { LogService } from "../../platform/abstractions/log.service"; import { LogService } from "../../platform/abstractions/log.service";
import { KdfType } from "../../platform/enums/kdf-type.enum";
import { Utils } from "../../platform/misc/utils"; import { Utils } from "../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { import {
@ -20,7 +21,7 @@ import { AccountService } from "../abstractions/account.service";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/key-connector.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction";
import { TokenService } from "../abstractions/token.service"; import { TokenService } from "../abstractions/token.service";
import { KdfConfig } from "../models/domain/kdf-config"; import { Argon2KdfConfig, KdfConfig, PBKDF2KdfConfig } from "../models/domain/kdf-config";
import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user-key.request"; import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user-key.request";
import { SetKeyConnectorKeyRequest } from "../models/request/set-key-connector-key.request"; import { SetKeyConnectorKeyRequest } from "../models/request/set-key-connector-key.request";
import { IdentityTokenResponse } from "../models/response/identity-token.response"; import { IdentityTokenResponse } from "../models/response/identity-token.response";
@ -133,12 +134,14 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
userDecryptionOptions, userDecryptionOptions,
} = tokenResponse; } = tokenResponse;
const password = await this.keyGenerationService.createKey(512); const password = await this.keyGenerationService.createKey(512);
const kdfConfig = new KdfConfig(kdfIterations, kdfMemory, kdfParallelism); const kdfConfig: KdfConfig =
kdf === KdfType.PBKDF2_SHA256
? new PBKDF2KdfConfig(kdfIterations)
: new Argon2KdfConfig(kdfIterations, kdfMemory, kdfParallelism);
const masterKey = await this.cryptoService.makeMasterKey( const masterKey = await this.cryptoService.makeMasterKey(
password.keyB64, password.keyB64,
await this.tokenService.getEmail(), await this.tokenService.getEmail(),
kdf,
kdfConfig, kdfConfig,
); );
const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64);
@ -162,7 +165,6 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
const keys = new KeysRequest(pubKey, privKey.encryptedString); const keys = new KeysRequest(pubKey, privKey.encryptedString);
const setPasswordRequest = new SetKeyConnectorKeyRequest( const setPasswordRequest = new SetKeyConnectorKeyRequest(
userKey[1].encryptedString, userKey[1].encryptedString,
kdf,
kdfConfig, kdfConfig,
orgId, orgId,
keys, keys,

View File

@ -1,5 +1,9 @@
import { firstValueFrom, map } from "rxjs";
import { I18nService } from "../../platform/abstractions/i18n.service"; import { I18nService } from "../../platform/abstractions/i18n.service";
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { Utils } from "../../platform/misc/utils";
import { GlobalStateProvider, KeyDefinition, TWO_FACTOR_MEMORY } from "../../platform/state";
import { import {
TwoFactorProviderDetails, TwoFactorProviderDetails,
TwoFactorService as TwoFactorServiceAbstraction, TwoFactorService as TwoFactorServiceAbstraction,
@ -59,13 +63,36 @@ export const TwoFactorProviders: Partial<Record<TwoFactorProviderType, TwoFactor
}, },
}; };
// Memory storage as only required during authentication process
export const PROVIDERS = KeyDefinition.record<Record<string, string>, TwoFactorProviderType>(
TWO_FACTOR_MEMORY,
"providers",
{
deserializer: (obj) => obj,
},
);
// Memory storage as only required during authentication process
export const SELECTED_PROVIDER = new KeyDefinition<TwoFactorProviderType>(
TWO_FACTOR_MEMORY,
"selected",
{
deserializer: (obj) => obj,
},
);
export class TwoFactorService implements TwoFactorServiceAbstraction { export class TwoFactorService implements TwoFactorServiceAbstraction {
private twoFactorProvidersData: Map<TwoFactorProviderType, { [key: string]: string }>; private providersState = this.globalStateProvider.get(PROVIDERS);
private selectedTwoFactorProviderType: TwoFactorProviderType = null; private selectedState = this.globalStateProvider.get(SELECTED_PROVIDER);
readonly providers$ = this.providersState.state$.pipe(
map((providers) => Utils.recordToMap(providers)),
);
readonly selected$ = this.selectedState.state$;
constructor( constructor(
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private globalStateProvider: GlobalStateProvider,
) {} ) {}
init() { init() {
@ -93,63 +120,60 @@ export class TwoFactorService implements TwoFactorServiceAbstraction {
this.i18nService.t("yubiKeyDesc"); this.i18nService.t("yubiKeyDesc");
} }
getSupportedProviders(win: Window): TwoFactorProviderDetails[] { async getSupportedProviders(win: Window): Promise<TwoFactorProviderDetails[]> {
const data = await firstValueFrom(this.providers$);
const providers: any[] = []; const providers: any[] = [];
if (this.twoFactorProvidersData == null) { if (data == null) {
return providers; return providers;
} }
if ( if (
this.twoFactorProvidersData.has(TwoFactorProviderType.OrganizationDuo) && data.has(TwoFactorProviderType.OrganizationDuo) &&
this.platformUtilsService.supportsDuo() this.platformUtilsService.supportsDuo()
) { ) {
providers.push(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]); providers.push(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]);
} }
if (this.twoFactorProvidersData.has(TwoFactorProviderType.Authenticator)) { if (data.has(TwoFactorProviderType.Authenticator)) {
providers.push(TwoFactorProviders[TwoFactorProviderType.Authenticator]); providers.push(TwoFactorProviders[TwoFactorProviderType.Authenticator]);
} }
if (this.twoFactorProvidersData.has(TwoFactorProviderType.Yubikey)) { if (data.has(TwoFactorProviderType.Yubikey)) {
providers.push(TwoFactorProviders[TwoFactorProviderType.Yubikey]); providers.push(TwoFactorProviders[TwoFactorProviderType.Yubikey]);
} }
if ( if (data.has(TwoFactorProviderType.Duo) && this.platformUtilsService.supportsDuo()) {
this.twoFactorProvidersData.has(TwoFactorProviderType.Duo) &&
this.platformUtilsService.supportsDuo()
) {
providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]); providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]);
} }
if ( if (
this.twoFactorProvidersData.has(TwoFactorProviderType.WebAuthn) && data.has(TwoFactorProviderType.WebAuthn) &&
this.platformUtilsService.supportsWebAuthn(win) this.platformUtilsService.supportsWebAuthn(win)
) { ) {
providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]); providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]);
} }
if (this.twoFactorProvidersData.has(TwoFactorProviderType.Email)) { if (data.has(TwoFactorProviderType.Email)) {
providers.push(TwoFactorProviders[TwoFactorProviderType.Email]); providers.push(TwoFactorProviders[TwoFactorProviderType.Email]);
} }
return providers; return providers;
} }
getDefaultProvider(webAuthnSupported: boolean): TwoFactorProviderType { async getDefaultProvider(webAuthnSupported: boolean): Promise<TwoFactorProviderType> {
if (this.twoFactorProvidersData == null) { const data = await firstValueFrom(this.providers$);
const selected = await firstValueFrom(this.selected$);
if (data == null) {
return null; return null;
} }
if ( if (selected != null && data.has(selected)) {
this.selectedTwoFactorProviderType != null && return selected;
this.twoFactorProvidersData.has(this.selectedTwoFactorProviderType)
) {
return this.selectedTwoFactorProviderType;
} }
let providerType: TwoFactorProviderType = null; let providerType: TwoFactorProviderType = null;
let providerPriority = -1; let providerPriority = -1;
this.twoFactorProvidersData.forEach((_value, type) => { data.forEach((_value, type) => {
const provider = (TwoFactorProviders as any)[type]; const provider = (TwoFactorProviders as any)[type];
if (provider != null && provider.priority > providerPriority) { if (provider != null && provider.priority > providerPriority) {
if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) {
@ -164,23 +188,23 @@ export class TwoFactorService implements TwoFactorServiceAbstraction {
return providerType; return providerType;
} }
setSelectedProvider(type: TwoFactorProviderType) { async setSelectedProvider(type: TwoFactorProviderType): Promise<void> {
this.selectedTwoFactorProviderType = type; await this.selectedState.update(() => type);
} }
clearSelectedProvider() { async clearSelectedProvider(): Promise<void> {
this.selectedTwoFactorProviderType = null; await this.selectedState.update(() => null);
} }
setProviders(response: IdentityTwoFactorResponse) { async setProviders(response: IdentityTwoFactorResponse): Promise<void> {
this.twoFactorProvidersData = response.twoFactorProviders2; await this.providersState.update(() => response.twoFactorProviders2);
} }
clearProviders() { async clearProviders(): Promise<void> {
this.twoFactorProvidersData = null; await this.providersState.update(() => null);
} }
getProviders() { getProviders(): Promise<Map<TwoFactorProviderType, { [key: string]: string }>> {
return this.twoFactorProvidersData; return firstValueFrom(this.providers$);
} }
} }

View File

@ -13,6 +13,7 @@ import { KeySuffixOptions } from "../../../platform/enums/key-suffix-options.enu
import { UserId } from "../../../types/guid"; import { UserId } from "../../../types/guid";
import { UserKey } from "../../../types/key"; import { UserKey } from "../../../types/key";
import { AccountService } from "../../abstractions/account.service"; import { AccountService } from "../../abstractions/account.service";
import { KdfConfigService } from "../../abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction";
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction"; import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/user-verification/user-verification.service.abstraction";
@ -47,6 +48,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
private logService: LogService, private logService: LogService,
private vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction, private vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private kdfConfigService: KdfConfigService,
) {} ) {}
async getAvailableVerificationOptions( async getAvailableVerificationOptions(
@ -118,8 +120,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
masterKey = await this.cryptoService.makeMasterKey( masterKey = await this.cryptoService.makeMasterKey(
verification.secret, verification.secret,
await this.stateService.getEmail(), await this.stateService.getEmail(),
await this.stateService.getKdfType(), await this.kdfConfigService.getKdfConfig(),
await this.stateService.getKdfConfig(),
); );
} }
request.masterPasswordHash = alreadyHashed request.masterPasswordHash = alreadyHashed
@ -176,8 +177,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
masterKey = await this.cryptoService.makeMasterKey( masterKey = await this.cryptoService.makeMasterKey(
verification.secret, verification.secret,
await this.stateService.getEmail(), await this.stateService.getEmail(),
await this.stateService.getKdfType(), await this.kdfConfigService.getKdfConfig(),
await this.stateService.getKdfConfig(),
); );
} }
const passwordValid = await this.cryptoService.compareAndUpdateKeyHash( const passwordValid = await this.cryptoService.compareAndUpdateKeyHash(

View File

@ -6,7 +6,7 @@ import { ProfileProviderResponse } from "../../admin-console/models/response/pro
import { KdfConfig } from "../../auth/models/domain/kdf-config"; import { KdfConfig } from "../../auth/models/domain/kdf-config";
import { OrganizationId, ProviderId, UserId } from "../../types/guid"; import { OrganizationId, ProviderId, UserId } from "../../types/guid";
import { UserKey, MasterKey, OrgKey, ProviderKey, PinKey, CipherKey } from "../../types/key"; import { UserKey, MasterKey, OrgKey, ProviderKey, PinKey, CipherKey } from "../../types/key";
import { KeySuffixOptions, KdfType, HashPurpose } from "../enums"; import { KeySuffixOptions, HashPurpose } from "../enums";
import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
import { EncString } from "../models/domain/enc-string"; import { EncString } from "../models/domain/enc-string";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
@ -114,16 +114,10 @@ export abstract class CryptoService {
* Generates a master key from the provided password * Generates a master key from the provided password
* @param password The user's master password * @param password The user's master password
* @param email The user's email * @param email The user's email
* @param kdf The user's selected key derivation function to use
* @param KdfConfig The user's key derivation function configuration * @param KdfConfig The user's key derivation function configuration
* @returns A master key derived from the provided password * @returns A master key derived from the provided password
*/ */
abstract makeMasterKey( abstract makeMasterKey(password: string, email: string, KdfConfig: KdfConfig): Promise<MasterKey>;
password: string,
email: string,
kdf: KdfType,
KdfConfig: KdfConfig,
): Promise<MasterKey>;
/** /**
* Encrypts the existing (or provided) user key with the * Encrypts the existing (or provided) user key with the
* provided master key * provided master key
@ -258,16 +252,10 @@ export abstract class CryptoService {
/** /**
* @param pin The user's pin * @param pin The user's pin
* @param salt The user's salt * @param salt The user's salt
* @param kdf The user's kdf
* @param kdfConfig The user's kdf config * @param kdfConfig The user's kdf config
* @returns A key derived from the user's pin * @returns A key derived from the user's pin
*/ */
abstract makePinKey( abstract makePinKey(pin: string, salt: string, kdfConfig: KdfConfig): Promise<PinKey>;
pin: string,
salt: string,
kdf: KdfType,
kdfConfig: KdfConfig,
): Promise<PinKey>;
/** /**
* Clears the user's pin keys from storage * Clears the user's pin keys from storage
* Note: This will remove the stored pin and as a result, * Note: This will remove the stored pin and as a result,
@ -279,7 +267,6 @@ export abstract class CryptoService {
* Decrypts the user key with their pin * Decrypts the user key with their pin
* @param pin The user's PIN * @param pin The user's PIN
* @param salt The user's salt * @param salt The user's salt
* @param kdf The user's KDF
* @param kdfConfig The user's KDF config * @param kdfConfig The user's KDF config
* @param pinProtectedUserKey The user's PIN protected symmetric key, if not provided * @param pinProtectedUserKey The user's PIN protected symmetric key, if not provided
* it will be retrieved from storage * it will be retrieved from storage
@ -288,7 +275,6 @@ export abstract class CryptoService {
abstract decryptUserKeyWithPin( abstract decryptUserKeyWithPin(
pin: string, pin: string,
salt: string, salt: string,
kdf: KdfType,
kdfConfig: KdfConfig, kdfConfig: KdfConfig,
protectedKeyCs?: EncString, protectedKeyCs?: EncString,
): Promise<UserKey>; ): Promise<UserKey>;
@ -298,7 +284,6 @@ export abstract class CryptoService {
* @param masterPasswordOnRestart True if Master Password on Restart is enabled * @param masterPasswordOnRestart True if Master Password on Restart is enabled
* @param pin User's PIN * @param pin User's PIN
* @param email User's email * @param email User's email
* @param kdf User's KdfType
* @param kdfConfig User's KdfConfig * @param kdfConfig User's KdfConfig
* @param oldPinKey The old Pin key from state (retrieved from different * @param oldPinKey The old Pin key from state (retrieved from different
* places depending on if Master Password on Restart was enabled) * places depending on if Master Password on Restart was enabled)
@ -308,7 +293,6 @@ export abstract class CryptoService {
masterPasswordOnRestart: boolean, masterPasswordOnRestart: boolean,
pin: string, pin: string,
email: string, email: string,
kdf: KdfType,
kdfConfig: KdfConfig, kdfConfig: KdfConfig,
oldPinKey: EncString, oldPinKey: EncString,
): Promise<UserKey>; ): Promise<UserKey>;
@ -358,21 +342,12 @@ export abstract class CryptoService {
privateKey: EncString; privateKey: EncString;
}>; }>;
/**
* Validate that the KDF config follows the requirements for the given KDF type.
*
* @remarks
* Should always be called before updating a users KDF config.
*/
abstract validateKdfConfig(kdf: KdfType, kdfConfig: KdfConfig): void;
/** /**
* @deprecated Left for migration purposes. Use decryptUserKeyWithPin instead. * @deprecated Left for migration purposes. Use decryptUserKeyWithPin instead.
*/ */
abstract decryptMasterKeyWithPin( abstract decryptMasterKeyWithPin(
pin: string, pin: string,
salt: string, salt: string,
kdf: KdfType,
kdfConfig: KdfConfig, kdfConfig: KdfConfig,
protectedKeyCs?: EncString, protectedKeyCs?: EncString,
): Promise<MasterKey>; ): Promise<MasterKey>;

View File

@ -1,6 +1,5 @@
import { KdfConfig } from "../../auth/models/domain/kdf-config"; import { KdfConfig } from "../../auth/models/domain/kdf-config";
import { CsprngArray } from "../../types/csprng"; import { CsprngArray } from "../../types/csprng";
import { KdfType } from "../enums";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
export abstract class KeyGenerationService { export abstract class KeyGenerationService {
@ -46,14 +45,12 @@ export abstract class KeyGenerationService {
* Derives a 32 byte key from a password using a key derivation function. * Derives a 32 byte key from a password using a key derivation function.
* @param password Password to derive the key from. * @param password Password to derive the key from.
* @param salt Salt for the key derivation function. * @param salt Salt for the key derivation function.
* @param kdf Key derivation function to use.
* @param kdfConfig Configuration for the key derivation function. * @param kdfConfig Configuration for the key derivation function.
* @returns 32 byte derived key. * @returns 32 byte derived key.
*/ */
abstract deriveKeyFromPassword( abstract deriveKeyFromPassword(
password: string | Uint8Array, password: string | Uint8Array,
salt: string | Uint8Array, salt: string | Uint8Array,
kdf: KdfType,
kdfConfig: KdfConfig, kdfConfig: KdfConfig,
): Promise<SymmetricCryptoKey>; ): Promise<SymmetricCryptoKey>;
} }

View File

@ -1,12 +1,10 @@
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { KdfConfig } from "../../auth/models/domain/kdf-config";
import { BiometricKey } from "../../auth/types/biometric-key"; import { BiometricKey } from "../../auth/types/biometric-key";
import { GeneratorOptions } from "../../tools/generator/generator-options"; import { GeneratorOptions } from "../../tools/generator/generator-options";
import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password"; import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password";
import { UsernameGeneratorOptions } from "../../tools/generator/username"; import { UsernameGeneratorOptions } from "../../tools/generator/username";
import { UserId } from "../../types/guid"; import { UserId } from "../../types/guid";
import { KdfType } from "../enums";
import { Account } from "../models/domain/account"; import { Account } from "../models/domain/account";
import { EncString } from "../models/domain/enc-string"; import { EncString } from "../models/domain/enc-string";
import { StorageOptions } from "../models/domain/storage-options"; import { StorageOptions } from "../models/domain/storage-options";
@ -149,10 +147,6 @@ export abstract class StateService<T extends Account = Account> {
*/ */
setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>; setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>;
getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>; getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>;
getKdfConfig: (options?: StorageOptions) => Promise<KdfConfig>;
setKdfConfig: (kdfConfig: KdfConfig, options?: StorageOptions) => Promise<void>;
getKdfType: (options?: StorageOptions) => Promise<KdfType>;
setKdfType: (value: KdfType, options?: StorageOptions) => Promise<void>;
getLastActive: (options?: StorageOptions) => Promise<number>; getLastActive: (options?: StorageOptions) => Promise<number>;
setLastActive: (value: number, options?: StorageOptions) => Promise<void>; setLastActive: (value: number, options?: StorageOptions) => Promise<void>;
getLastSync: (options?: StorageOptions) => Promise<string>; getLastSync: (options?: StorageOptions) => Promise<string>;

View File

@ -1,4 +1,4 @@
import { KdfConfig } from "../../auth/models/domain/kdf-config"; import { PBKDF2KdfConfig } from "../../auth/models/domain/kdf-config";
import { RangeWithDefault } from "../misc/range-with-default"; import { RangeWithDefault } from "../misc/range-with-default";
export enum KdfType { export enum KdfType {
@ -12,4 +12,4 @@ export const ARGON2_ITERATIONS = new RangeWithDefault(2, 10, 3);
export const DEFAULT_KDF_TYPE = KdfType.PBKDF2_SHA256; export const DEFAULT_KDF_TYPE = KdfType.PBKDF2_SHA256;
export const PBKDF2_ITERATIONS = new RangeWithDefault(600_000, 2_000_000, 600_000); export const PBKDF2_ITERATIONS = new RangeWithDefault(600_000, 2_000_000, 600_000);
export const DEFAULT_KDF_CONFIG = new KdfConfig(PBKDF2_ITERATIONS.defaultValue); export const DEFAULT_KDF_CONFIG = new PBKDF2KdfConfig(PBKDF2_ITERATIONS.defaultValue);

View File

@ -4,6 +4,7 @@ import { firstValueFrom, of, tap } from "rxjs";
import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service";
import { FakeActiveUserState, FakeSingleUserState } from "../../../spec/fake-state"; import { FakeActiveUserState, FakeSingleUserState } from "../../../spec/fake-state";
import { FakeStateProvider } from "../../../spec/fake-state-provider"; import { FakeStateProvider } from "../../../spec/fake-state-provider";
import { KdfConfigService } from "../../auth/abstractions/kdf-config.service";
import { FakeMasterPasswordService } from "../../auth/services/master-password/fake-master-password.service"; import { FakeMasterPasswordService } from "../../auth/services/master-password/fake-master-password.service";
import { CsprngArray } from "../../types/csprng"; import { CsprngArray } from "../../types/csprng";
import { UserId } from "../../types/guid"; import { UserId } from "../../types/guid";
@ -37,6 +38,7 @@ describe("cryptoService", () => {
const platformUtilService = mock<PlatformUtilsService>(); const platformUtilService = mock<PlatformUtilsService>();
const logService = mock<LogService>(); const logService = mock<LogService>();
const stateService = mock<StateService>(); const stateService = mock<StateService>();
const kdfConfigService = mock<KdfConfigService>();
let stateProvider: FakeStateProvider; let stateProvider: FakeStateProvider;
const mockUserId = Utils.newGuid() as UserId; const mockUserId = Utils.newGuid() as UserId;
@ -58,6 +60,7 @@ describe("cryptoService", () => {
stateService, stateService,
accountService, accountService,
stateProvider, stateProvider,
kdfConfigService,
); );
}); });

View File

@ -6,6 +6,7 @@ import { ProfileOrganizationResponse } from "../../admin-console/models/response
import { ProfileProviderOrganizationResponse } from "../../admin-console/models/response/profile-provider-organization.response"; import { ProfileProviderOrganizationResponse } from "../../admin-console/models/response/profile-provider-organization.response";
import { ProfileProviderResponse } from "../../admin-console/models/response/profile-provider.response"; import { ProfileProviderResponse } from "../../admin-console/models/response/profile-provider.response";
import { AccountService } from "../../auth/abstractions/account.service"; import { AccountService } from "../../auth/abstractions/account.service";
import { KdfConfigService } from "../../auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction"; import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction";
import { KdfConfig } from "../../auth/models/domain/kdf-config"; import { KdfConfig } from "../../auth/models/domain/kdf-config";
import { Utils } from "../../platform/misc/utils"; import { Utils } from "../../platform/misc/utils";
@ -28,16 +29,7 @@ import { KeyGenerationService } from "../abstractions/key-generation.service";
import { LogService } from "../abstractions/log.service"; import { LogService } from "../abstractions/log.service";
import { PlatformUtilsService } from "../abstractions/platform-utils.service"; import { PlatformUtilsService } from "../abstractions/platform-utils.service";
import { StateService } from "../abstractions/state.service"; import { StateService } from "../abstractions/state.service";
import { import { KeySuffixOptions, HashPurpose, EncryptionType } from "../enums";
KeySuffixOptions,
HashPurpose,
KdfType,
ARGON2_ITERATIONS,
ARGON2_MEMORY,
ARGON2_PARALLELISM,
EncryptionType,
PBKDF2_ITERATIONS,
} from "../enums";
import { sequentialize } from "../misc/sequentialize"; import { sequentialize } from "../misc/sequentialize";
import { EFFLongWordList } from "../misc/wordlist"; import { EFFLongWordList } from "../misc/wordlist";
import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
@ -91,6 +83,7 @@ export class CryptoService implements CryptoServiceAbstraction {
protected stateService: StateService, protected stateService: StateService,
protected accountService: AccountService, protected accountService: AccountService,
protected stateProvider: StateProvider, protected stateProvider: StateProvider,
protected kdfConfigService: KdfConfigService,
) { ) {
// User Key // User Key
this.activeUserKeyState = stateProvider.getActive(USER_KEY); this.activeUserKeyState = stateProvider.getActive(USER_KEY);
@ -283,8 +276,7 @@ export class CryptoService implements CryptoServiceAbstraction {
return (masterKey ||= await this.makeMasterKey( return (masterKey ||= await this.makeMasterKey(
password, password,
await this.stateService.getEmail({ userId: userId }), await this.stateService.getEmail({ userId: userId }),
await this.stateService.getKdfType({ userId: userId }), await this.kdfConfigService.getKdfConfig(),
await this.stateService.getKdfConfig({ userId: userId }),
)); ));
} }
@ -295,16 +287,10 @@ export class CryptoService implements CryptoServiceAbstraction {
* Does not validate the kdf config to ensure it satisfies the minimum requirements for the given kdf type. * Does not validate the kdf config to ensure it satisfies the minimum requirements for the given kdf type.
* TODO: Move to MasterPasswordService * TODO: Move to MasterPasswordService
*/ */
async makeMasterKey( async makeMasterKey(password: string, email: string, KdfConfig: KdfConfig): Promise<MasterKey> {
password: string,
email: string,
kdf: KdfType,
KdfConfig: KdfConfig,
): Promise<MasterKey> {
return (await this.keyGenerationService.deriveKeyFromPassword( return (await this.keyGenerationService.deriveKeyFromPassword(
password, password,
email, email,
kdf,
KdfConfig, KdfConfig,
)) as MasterKey; )) as MasterKey;
} }
@ -560,8 +546,8 @@ export class CryptoService implements CryptoServiceAbstraction {
await this.stateProvider.setUserState(USER_ENCRYPTED_PRIVATE_KEY, null, userId); await this.stateProvider.setUserState(USER_ENCRYPTED_PRIVATE_KEY, null, userId);
} }
async makePinKey(pin: string, salt: string, kdf: KdfType, kdfConfig: KdfConfig): Promise<PinKey> { async makePinKey(pin: string, salt: string, kdfConfig: KdfConfig): Promise<PinKey> {
const pinKey = await this.keyGenerationService.deriveKeyFromPassword(pin, salt, kdf, kdfConfig); const pinKey = await this.keyGenerationService.deriveKeyFromPassword(pin, salt, kdfConfig);
return (await this.stretchKey(pinKey)) as PinKey; return (await this.stretchKey(pinKey)) as PinKey;
} }
@ -575,7 +561,6 @@ export class CryptoService implements CryptoServiceAbstraction {
async decryptUserKeyWithPin( async decryptUserKeyWithPin(
pin: string, pin: string,
salt: string, salt: string,
kdf: KdfType,
kdfConfig: KdfConfig, kdfConfig: KdfConfig,
pinProtectedUserKey?: EncString, pinProtectedUserKey?: EncString,
): Promise<UserKey> { ): Promise<UserKey> {
@ -584,7 +569,7 @@ export class CryptoService implements CryptoServiceAbstraction {
if (!pinProtectedUserKey) { if (!pinProtectedUserKey) {
throw new Error("No PIN protected key found."); throw new Error("No PIN protected key found.");
} }
const pinKey = await this.makePinKey(pin, salt, kdf, kdfConfig); const pinKey = await this.makePinKey(pin, salt, kdfConfig);
const userKey = await this.encryptService.decryptToBytes(pinProtectedUserKey, pinKey); const userKey = await this.encryptService.decryptToBytes(pinProtectedUserKey, pinKey);
return new SymmetricCryptoKey(userKey) as UserKey; return new SymmetricCryptoKey(userKey) as UserKey;
} }
@ -593,7 +578,6 @@ export class CryptoService implements CryptoServiceAbstraction {
async decryptMasterKeyWithPin( async decryptMasterKeyWithPin(
pin: string, pin: string,
salt: string, salt: string,
kdf: KdfType,
kdfConfig: KdfConfig, kdfConfig: KdfConfig,
pinProtectedMasterKey?: EncString, pinProtectedMasterKey?: EncString,
): Promise<MasterKey> { ): Promise<MasterKey> {
@ -604,7 +588,7 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
pinProtectedMasterKey = new EncString(pinProtectedMasterKeyString); pinProtectedMasterKey = new EncString(pinProtectedMasterKeyString);
} }
const pinKey = await this.makePinKey(pin, salt, kdf, kdfConfig); const pinKey = await this.makePinKey(pin, salt, kdfConfig);
const masterKey = await this.encryptService.decryptToBytes(pinProtectedMasterKey, pinKey); const masterKey = await this.encryptService.decryptToBytes(pinProtectedMasterKey, pinKey);
return new SymmetricCryptoKey(masterKey) as MasterKey; return new SymmetricCryptoKey(masterKey) as MasterKey;
} }
@ -831,8 +815,7 @@ export class CryptoService implements CryptoServiceAbstraction {
const pinKey = await this.makePinKey( const pinKey = await this.makePinKey(
pin, pin,
await this.stateService.getEmail({ userId: userId }), await this.stateService.getEmail({ userId: userId }),
await this.stateService.getKdfType({ userId: userId }), await this.kdfConfigService.getKdfConfig(),
await this.stateService.getKdfConfig({ userId: userId }),
); );
const encPin = await this.encryptService.encrypt(key.key, pinKey); const encPin = await this.encryptService.encrypt(key.key, pinKey);
@ -873,43 +856,6 @@ export class CryptoService implements CryptoServiceAbstraction {
return null; return null;
} }
/**
* Validate that the KDF config follows the requirements for the given KDF type.
*
* @remarks
* Should always be called before updating a users KDF config.
*/
validateKdfConfig(kdf: KdfType, kdfConfig: KdfConfig): void {
switch (kdf) {
case KdfType.PBKDF2_SHA256:
if (!PBKDF2_ITERATIONS.inRange(kdfConfig.iterations)) {
throw new Error(
`PBKDF2 iterations must be between ${PBKDF2_ITERATIONS.min} and ${PBKDF2_ITERATIONS.max}`,
);
}
break;
case KdfType.Argon2id:
if (!ARGON2_ITERATIONS.inRange(kdfConfig.iterations)) {
throw new Error(
`Argon2 iterations must be between ${ARGON2_ITERATIONS.min} and ${ARGON2_ITERATIONS.max}`,
);
}
if (!ARGON2_MEMORY.inRange(kdfConfig.memory)) {
throw new Error(
`Argon2 memory must be between ${ARGON2_MEMORY.min}mb and ${ARGON2_MEMORY.max}mb`,
);
}
if (!ARGON2_PARALLELISM.inRange(kdfConfig.parallelism)) {
throw new Error(
`Argon2 parallelism must be between ${ARGON2_PARALLELISM.min} and ${ARGON2_PARALLELISM.max}.`,
);
}
break;
}
}
protected async clearAllStoredUserKeys(userId?: UserId): Promise<void> { protected async clearAllStoredUserKeys(userId?: UserId): Promise<void> {
await this.stateService.setUserKeyAutoUnlock(null, { userId: userId }); await this.stateService.setUserKeyAutoUnlock(null, { userId: userId });
await this.stateService.setPinKeyEncryptedUserKeyEphemeral(null, { userId: userId }); await this.stateService.setPinKeyEncryptedUserKeyEphemeral(null, { userId: userId });
@ -1007,16 +953,15 @@ export class CryptoService implements CryptoServiceAbstraction {
masterPasswordOnRestart: boolean, masterPasswordOnRestart: boolean,
pin: string, pin: string,
email: string, email: string,
kdf: KdfType,
kdfConfig: KdfConfig, kdfConfig: KdfConfig,
oldPinKey: EncString, oldPinKey: EncString,
): Promise<UserKey> { ): Promise<UserKey> {
// Decrypt // Decrypt
const masterKey = await this.decryptMasterKeyWithPin(pin, email, kdf, kdfConfig, oldPinKey); const masterKey = await this.decryptMasterKeyWithPin(pin, email, kdfConfig, oldPinKey);
const encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey(); const encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey();
const userKey = await this.decryptUserKeyWithMasterKey(masterKey, new EncString(encUserKey)); const userKey = await this.decryptUserKeyWithMasterKey(masterKey, new EncString(encUserKey));
// Migrate // Migrate
const pinKey = await this.makePinKey(pin, email, kdf, kdfConfig); const pinKey = await this.makePinKey(pin, email, kdfConfig);
const pinProtectedKey = await this.encryptService.encrypt(userKey.key, pinKey); const pinProtectedKey = await this.encryptService.encrypt(userKey.key, pinKey);
if (masterPasswordOnRestart) { if (masterPasswordOnRestart) {
await this.stateService.setDecryptedPinProtected(null); await this.stateService.setDecryptedPinProtected(null);

View File

@ -1,9 +1,8 @@
import { mock } from "jest-mock-extended"; import { mock } from "jest-mock-extended";
import { KdfConfig } from "../../auth/models/domain/kdf-config"; import { Argon2KdfConfig, PBKDF2KdfConfig } from "../../auth/models/domain/kdf-config";
import { CsprngArray } from "../../types/csprng"; import { CsprngArray } from "../../types/csprng";
import { CryptoFunctionService } from "../abstractions/crypto-function.service"; import { CryptoFunctionService } from "../abstractions/crypto-function.service";
import { KdfType } from "../enums";
import { KeyGenerationService } from "./key-generation.service"; import { KeyGenerationService } from "./key-generation.service";
@ -75,12 +74,11 @@ describe("KeyGenerationService", () => {
it("should derive a 32 byte key from a password using pbkdf2", async () => { it("should derive a 32 byte key from a password using pbkdf2", async () => {
const password = "password"; const password = "password";
const salt = "salt"; const salt = "salt";
const kdf = KdfType.PBKDF2_SHA256; const kdfConfig = new PBKDF2KdfConfig(600_000);
const kdfConfig = new KdfConfig(600_000);
cryptoFunctionService.pbkdf2.mockResolvedValue(new Uint8Array(32)); cryptoFunctionService.pbkdf2.mockResolvedValue(new Uint8Array(32));
const key = await sut.deriveKeyFromPassword(password, salt, kdf, kdfConfig); const key = await sut.deriveKeyFromPassword(password, salt, kdfConfig);
expect(key.key.length).toEqual(32); expect(key.key.length).toEqual(32);
}); });
@ -88,13 +86,12 @@ describe("KeyGenerationService", () => {
it("should derive a 32 byte key from a password using argon2id", async () => { it("should derive a 32 byte key from a password using argon2id", async () => {
const password = "password"; const password = "password";
const salt = "salt"; const salt = "salt";
const kdf = KdfType.Argon2id; const kdfConfig = new Argon2KdfConfig(3, 16, 4);
const kdfConfig = new KdfConfig(600_000, 15);
cryptoFunctionService.hash.mockResolvedValue(new Uint8Array(32)); cryptoFunctionService.hash.mockResolvedValue(new Uint8Array(32));
cryptoFunctionService.argon2.mockResolvedValue(new Uint8Array(32)); cryptoFunctionService.argon2.mockResolvedValue(new Uint8Array(32));
const key = await sut.deriveKeyFromPassword(password, salt, kdf, kdfConfig); const key = await sut.deriveKeyFromPassword(password, salt, kdfConfig);
expect(key.key.length).toEqual(32); expect(key.key.length).toEqual(32);
}); });

View File

@ -46,17 +46,16 @@ export class KeyGenerationService implements KeyGenerationServiceAbstraction {
async deriveKeyFromPassword( async deriveKeyFromPassword(
password: string | Uint8Array, password: string | Uint8Array,
salt: string | Uint8Array, salt: string | Uint8Array,
kdf: KdfType,
kdfConfig: KdfConfig, kdfConfig: KdfConfig,
): Promise<SymmetricCryptoKey> { ): Promise<SymmetricCryptoKey> {
let key: Uint8Array = null; let key: Uint8Array = null;
if (kdf == null || kdf === KdfType.PBKDF2_SHA256) { if (kdfConfig.kdfType == null || kdfConfig.kdfType === KdfType.PBKDF2_SHA256) {
if (kdfConfig.iterations == null) { if (kdfConfig.iterations == null) {
kdfConfig.iterations = PBKDF2_ITERATIONS.defaultValue; kdfConfig.iterations = PBKDF2_ITERATIONS.defaultValue;
} }
key = await this.cryptoFunctionService.pbkdf2(password, salt, "sha256", kdfConfig.iterations); key = await this.cryptoFunctionService.pbkdf2(password, salt, "sha256", kdfConfig.iterations);
} else if (kdf == KdfType.Argon2id) { } else if (kdfConfig.kdfType == KdfType.Argon2id) {
if (kdfConfig.iterations == null) { if (kdfConfig.iterations == null) {
kdfConfig.iterations = ARGON2_ITERATIONS.defaultValue; kdfConfig.iterations = ARGON2_ITERATIONS.defaultValue;
} }

View File

@ -3,7 +3,6 @@ import { Jsonify, JsonValue } from "type-fest";
import { AccountService } from "../../auth/abstractions/account.service"; import { AccountService } from "../../auth/abstractions/account.service";
import { TokenService } from "../../auth/abstractions/token.service"; import { TokenService } from "../../auth/abstractions/token.service";
import { KdfConfig } from "../../auth/models/domain/kdf-config";
import { BiometricKey } from "../../auth/types/biometric-key"; import { BiometricKey } from "../../auth/types/biometric-key";
import { GeneratorOptions } from "../../tools/generator/generator-options"; import { GeneratorOptions } from "../../tools/generator/generator-options";
import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password"; import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password";
@ -19,7 +18,7 @@ import {
AbstractMemoryStorageService, AbstractMemoryStorageService,
AbstractStorageService, AbstractStorageService,
} from "../abstractions/storage.service"; } from "../abstractions/storage.service";
import { HtmlStorageLocation, KdfType, StorageLocation } from "../enums"; import { HtmlStorageLocation, StorageLocation } from "../enums";
import { StateFactory } from "../factories/state-factory"; import { StateFactory } from "../factories/state-factory";
import { Utils } from "../misc/utils"; import { Utils } from "../misc/utils";
import { Account, AccountData, AccountSettings } from "../models/domain/account"; import { Account, AccountData, AccountSettings } from "../models/domain/account";
@ -643,49 +642,6 @@ export class StateService<
); );
} }
async getKdfConfig(options?: StorageOptions): Promise<KdfConfig> {
const iterations = (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.profile?.kdfIterations;
const memory = (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.profile?.kdfMemory;
const parallelism = (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.profile?.kdfParallelism;
return new KdfConfig(iterations, memory, parallelism);
}
async setKdfConfig(config: KdfConfig, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.profile.kdfIterations = config.iterations;
account.profile.kdfMemory = config.memory;
account.profile.kdfParallelism = config.parallelism;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getKdfType(options?: StorageOptions): Promise<KdfType> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.profile?.kdfType;
}
async setKdfType(value: KdfType, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.profile.kdfType = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getLastActive(options?: StorageOptions): Promise<number> { async getLastActive(options?: StorageOptions): Promise<number> {
options = this.reconcileOptions(options, await this.defaultOnDiskOptions()); options = this.reconcileOptions(options, await this.defaultOnDiskOptions());

View File

@ -0,0 +1,25 @@
import { record } from "./deserialization-helpers";
describe("deserialization helpers", () => {
describe("record", () => {
it("deserializes a record when keys are strings", () => {
const deserializer = record((value: number) => value);
const input = {
a: 1,
b: 2,
};
const output = deserializer(input);
expect(output).toEqual(input);
});
it("deserializes a record when keys are numbers", () => {
const deserializer = record((value: number) => value);
const input = {
1: 1,
2: 2,
};
const output = deserializer(input);
expect(output).toEqual(input);
});
});
});

View File

@ -21,7 +21,7 @@ export function array<T>(
* *
* @param valueDeserializer * @param valueDeserializer
*/ */
export function record<T, TKey extends string = string>( export function record<T, TKey extends string | number = string>(
valueDeserializer: (value: Jsonify<T>) => T, valueDeserializer: (value: Jsonify<T>) => T,
): (record: Jsonify<Record<TKey, T>>) => Record<TKey, T> { ): (record: Jsonify<Record<TKey, T>>) => Record<TKey, T> {
return (jsonValue: Jsonify<Record<TKey, T> | null>) => { return (jsonValue: Jsonify<Record<TKey, T> | null>) => {
@ -29,10 +29,10 @@ export function record<T, TKey extends string = string>(
return null; return null;
} }
const output: Record<string, T> = {}; const output: Record<TKey, T> = {} as any;
for (const key in jsonValue) { Object.entries(jsonValue).forEach(([key, value]) => {
output[key] = valueDeserializer((jsonValue as Record<string, Jsonify<T>>)[key]); output[key as TKey] = valueDeserializer(value);
} });
return output; return output;
}; };
} }

View File

@ -113,7 +113,7 @@ export class KeyDefinition<T> {
* }); * });
* ``` * ```
*/ */
static record<T, TKey extends string = string>( static record<T, TKey extends string | number = string>(
stateDefinition: StateDefinition, stateDefinition: StateDefinition,
key: string, key: string,
// We have them provide options for the value of the record, depending on future options we add, this could get a little weird. // We have them provide options for the value of the record, depending on future options we add, this could get a little weird.

Some files were not shown because too many files have changed in this diff Show More