1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-09-29 04:17:41 +02:00

[PM-5362] Add MP Service (attempt #2) (#8619)

* create mp and kdf service

* update mp service interface to not rely on active user

* rename observable methods

* update crypto service with new MP service

* add master password service to login strategies
- make fake service for easier testing
- fix crypto service tests

* update auth service and finish strategies

* auth request refactors

* more service refactors and constructor updates

* setMasterKey refactors

* remove master key methods from crypto service

* remove master key and hash from state service

* missed fixes

* create migrations and fix references

* fix master key imports

* default force set password reason to none

* add password reset reason observable factory to service

* remove kdf changes and migrate only disk data

* update migration number

* fix sync service deps

* use disk for force set password state

* fix desktop migration

* fix sso test

* fix tests

* fix more tests

* fix even more tests

* fix even more tests

* fix cli

* remove kdf service abstraction

* add missing deps for browser

* fix merge conflicts

* clear reset password reason on lock or logout

* fix tests

* fix other tests

* add jsdocs to abstraction

* use state provider in crypto service

* inverse master password service factory

* add clearOn to master password service

* add parameter validation to master password service

* add component level userId

* add missed userId

* migrate key hash

* fix login strategy service

* delete crypto master key from account

* migrate master key encrypted user key

* rename key hash to master key hash

* use mp service for getMasterKeyEncryptedUserKey

* fix tests

* fix user key decryption logic

* add clear methods to mp service

* fix circular dep and encryption issue

* fix test

* remove extra account service call

* use EncString in state provider

* fix tests

* return to using encrypted string for serialization
This commit is contained in:
Jake Fink 2024-04-09 20:50:20 -04:00 committed by GitHub
parent c02723d6a6
commit 9d10825dbd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
79 changed files with 1373 additions and 501 deletions

View File

@ -17,18 +17,21 @@ import {
FactoryOptions, FactoryOptions,
factory, factory,
} from "../../../platform/background/service-factories/factory-options"; } from "../../../platform/background/service-factories/factory-options";
import { accountServiceFactory, AccountServiceInitOptions } from "./account-service.factory";
import { import {
stateServiceFactory, internalMasterPasswordServiceFactory,
StateServiceInitOptions, MasterPasswordServiceInitOptions,
} from "../../../platform/background/service-factories/state-service.factory"; } from "./master-password-service.factory";
type AuthRequestServiceFactoryOptions = FactoryOptions; type AuthRequestServiceFactoryOptions = FactoryOptions;
export type AuthRequestServiceInitOptions = AuthRequestServiceFactoryOptions & export type AuthRequestServiceInitOptions = AuthRequestServiceFactoryOptions &
AppIdServiceInitOptions & AppIdServiceInitOptions &
AccountServiceInitOptions &
MasterPasswordServiceInitOptions &
CryptoServiceInitOptions & CryptoServiceInitOptions &
ApiServiceInitOptions & ApiServiceInitOptions;
StateServiceInitOptions;
export function authRequestServiceFactory( export function authRequestServiceFactory(
cache: { authRequestService?: AuthRequestServiceAbstraction } & CachedServices, cache: { authRequestService?: AuthRequestServiceAbstraction } & CachedServices,
@ -41,9 +44,10 @@ export function authRequestServiceFactory(
async () => async () =>
new AuthRequestService( new AuthRequestService(
await appIdServiceFactory(cache, opts), await appIdServiceFactory(cache, opts),
await accountServiceFactory(cache, opts),
await internalMasterPasswordServiceFactory(cache, opts),
await cryptoServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts),
await apiServiceFactory(cache, opts), await apiServiceFactory(cache, opts),
await stateServiceFactory(cache, opts),
), ),
); );
} }

View File

@ -31,6 +31,11 @@ import {
StateProviderInitOptions, StateProviderInitOptions,
} from "../../../platform/background/service-factories/state-provider.factory"; } from "../../../platform/background/service-factories/state-provider.factory";
import { accountServiceFactory, AccountServiceInitOptions } from "./account-service.factory";
import {
internalMasterPasswordServiceFactory,
MasterPasswordServiceInitOptions,
} from "./master-password-service.factory";
import { TokenServiceInitOptions, tokenServiceFactory } from "./token-service.factory"; import { TokenServiceInitOptions, tokenServiceFactory } from "./token-service.factory";
type KeyConnectorServiceFactoryOptions = FactoryOptions & { type KeyConnectorServiceFactoryOptions = FactoryOptions & {
@ -40,6 +45,8 @@ type KeyConnectorServiceFactoryOptions = FactoryOptions & {
}; };
export type KeyConnectorServiceInitOptions = KeyConnectorServiceFactoryOptions & export type KeyConnectorServiceInitOptions = KeyConnectorServiceFactoryOptions &
AccountServiceInitOptions &
MasterPasswordServiceInitOptions &
CryptoServiceInitOptions & CryptoServiceInitOptions &
ApiServiceInitOptions & ApiServiceInitOptions &
TokenServiceInitOptions & TokenServiceInitOptions &
@ -58,6 +65,8 @@ export function keyConnectorServiceFactory(
opts, opts,
async () => async () =>
new KeyConnectorService( new KeyConnectorService(
await accountServiceFactory(cache, opts),
await internalMasterPasswordServiceFactory(cache, opts),
await cryptoServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts),
await apiServiceFactory(cache, opts), await apiServiceFactory(cache, opts),
await tokenServiceFactory(cache, opts), await tokenServiceFactory(cache, opts),

View File

@ -59,6 +59,7 @@ import {
PasswordStrengthServiceInitOptions, PasswordStrengthServiceInitOptions,
} from "../../../tools/background/service_factories/password-strength-service.factory"; } from "../../../tools/background/service_factories/password-strength-service.factory";
import { accountServiceFactory, AccountServiceInitOptions } from "./account-service.factory";
import { import {
authRequestServiceFactory, authRequestServiceFactory,
AuthRequestServiceInitOptions, AuthRequestServiceInitOptions,
@ -71,6 +72,10 @@ import {
keyConnectorServiceFactory, keyConnectorServiceFactory,
KeyConnectorServiceInitOptions, KeyConnectorServiceInitOptions,
} from "./key-connector-service.factory"; } from "./key-connector-service.factory";
import {
internalMasterPasswordServiceFactory,
MasterPasswordServiceInitOptions,
} from "./master-password-service.factory";
import { tokenServiceFactory, TokenServiceInitOptions } from "./token-service.factory"; import { tokenServiceFactory, TokenServiceInitOptions } from "./token-service.factory";
import { twoFactorServiceFactory, TwoFactorServiceInitOptions } from "./two-factor-service.factory"; import { twoFactorServiceFactory, TwoFactorServiceInitOptions } from "./two-factor-service.factory";
import { import {
@ -81,6 +86,8 @@ import {
type LoginStrategyServiceFactoryOptions = FactoryOptions; type LoginStrategyServiceFactoryOptions = FactoryOptions;
export type LoginStrategyServiceInitOptions = LoginStrategyServiceFactoryOptions & export type LoginStrategyServiceInitOptions = LoginStrategyServiceFactoryOptions &
AccountServiceInitOptions &
MasterPasswordServiceInitOptions &
CryptoServiceInitOptions & CryptoServiceInitOptions &
ApiServiceInitOptions & ApiServiceInitOptions &
TokenServiceInitOptions & TokenServiceInitOptions &
@ -111,6 +118,8 @@ export function loginStrategyServiceFactory(
opts, opts,
async () => async () =>
new LoginStrategyService( new LoginStrategyService(
await accountServiceFactory(cache, opts),
await internalMasterPasswordServiceFactory(cache, opts),
await cryptoServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts),
await apiServiceFactory(cache, opts), await apiServiceFactory(cache, opts),
await tokenServiceFactory(cache, opts), await tokenServiceFactory(cache, opts),

View File

@ -0,0 +1,42 @@
import {
InternalMasterPasswordServiceAbstraction,
MasterPasswordServiceAbstraction,
} from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service";
import {
CachedServices,
factory,
FactoryOptions,
} from "../../../platform/background/service-factories/factory-options";
import {
stateProviderFactory,
StateProviderInitOptions,
} from "../../../platform/background/service-factories/state-provider.factory";
type MasterPasswordServiceFactoryOptions = FactoryOptions;
export type MasterPasswordServiceInitOptions = MasterPasswordServiceFactoryOptions &
StateProviderInitOptions;
export function internalMasterPasswordServiceFactory(
cache: { masterPasswordService?: InternalMasterPasswordServiceAbstraction } & CachedServices,
opts: MasterPasswordServiceInitOptions,
): Promise<InternalMasterPasswordServiceAbstraction> {
return factory(
cache,
"masterPasswordService",
opts,
async () => new MasterPasswordService(await stateProviderFactory(cache, opts)),
);
}
export async function masterPasswordServiceFactory(
cache: { masterPasswordService?: InternalMasterPasswordServiceAbstraction } & CachedServices,
opts: MasterPasswordServiceInitOptions,
): Promise<MasterPasswordServiceAbstraction> {
return (await internalMasterPasswordServiceFactory(
cache,
opts,
)) as MasterPasswordServiceAbstraction;
}

View File

@ -31,6 +31,11 @@ import {
stateServiceFactory, stateServiceFactory,
} from "../../../platform/background/service-factories/state-service.factory"; } from "../../../platform/background/service-factories/state-service.factory";
import { accountServiceFactory, AccountServiceInitOptions } from "./account-service.factory";
import {
internalMasterPasswordServiceFactory,
MasterPasswordServiceInitOptions,
} from "./master-password-service.factory";
import { PinCryptoServiceInitOptions, pinCryptoServiceFactory } from "./pin-crypto-service.factory"; import { PinCryptoServiceInitOptions, pinCryptoServiceFactory } from "./pin-crypto-service.factory";
import { import {
userDecryptionOptionsServiceFactory, userDecryptionOptionsServiceFactory,
@ -46,6 +51,8 @@ type UserVerificationServiceFactoryOptions = FactoryOptions;
export type UserVerificationServiceInitOptions = UserVerificationServiceFactoryOptions & export type UserVerificationServiceInitOptions = UserVerificationServiceFactoryOptions &
StateServiceInitOptions & StateServiceInitOptions &
CryptoServiceInitOptions & CryptoServiceInitOptions &
AccountServiceInitOptions &
MasterPasswordServiceInitOptions &
I18nServiceInitOptions & I18nServiceInitOptions &
UserVerificationApiServiceInitOptions & UserVerificationApiServiceInitOptions &
UserDecryptionOptionsServiceInitOptions & UserDecryptionOptionsServiceInitOptions &
@ -66,6 +73,8 @@ export function userVerificationServiceFactory(
new UserVerificationService( new UserVerificationService(
await stateServiceFactory(cache, opts), await stateServiceFactory(cache, opts),
await cryptoServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts),
await accountServiceFactory(cache, opts),
await internalMasterPasswordServiceFactory(cache, opts),
await i18nServiceFactory(cache, opts), await i18nServiceFactory(cache, opts),
await userVerificationApiServiceFactory(cache, opts), await userVerificationApiServiceFactory(cache, opts),
await userDecryptionOptionsServiceFactory(cache, opts), await userDecryptionOptionsServiceFactory(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 { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.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";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -41,6 +42,7 @@ export class LockComponent extends BaseLockComponent {
fido2PopoutSessionData$ = fido2PopoutSessionData$(); fido2PopoutSessionData$ = fido2PopoutSessionData$();
constructor( constructor(
masterPasswordService: InternalMasterPasswordServiceAbstraction,
router: Router, router: Router,
i18nService: I18nService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
@ -66,6 +68,7 @@ export class LockComponent extends BaseLockComponent {
accountService: AccountService, accountService: AccountService,
) { ) {
super( super(
masterPasswordService,
router, router,
i18nService, i18nService,
platformUtilsService, platformUtilsService,

View File

@ -1,65 +1,9 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component"; import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
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 { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
@Component({ @Component({
selector: "app-set-password", selector: "app-set-password",
templateUrl: "set-password.component.html", templateUrl: "set-password.component.html",
}) })
export class SetPasswordComponent extends BaseSetPasswordComponent { export class SetPasswordComponent extends BaseSetPasswordComponent {}
constructor(
apiService: ApiService,
i18nService: I18nService,
cryptoService: CryptoService,
messagingService: MessagingService,
stateService: StateService,
passwordGenerationService: PasswordGenerationServiceAbstraction,
platformUtilsService: PlatformUtilsService,
policyApiService: PolicyApiServiceAbstraction,
policyService: PolicyService,
router: Router,
syncService: SyncService,
route: ActivatedRoute,
organizationApiService: OrganizationApiServiceAbstraction,
organizationUserService: OrganizationUserService,
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction,
dialogService: DialogService,
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyApiService,
policyService,
router,
apiService,
syncService,
route,
stateService,
organizationApiService,
organizationUserService,
userDecryptionOptionsService,
ssoLoginService,
dialogService,
);
}
}

View File

@ -9,7 +9,9 @@ import {
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
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 { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
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 { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@ -45,7 +47,9 @@ export class SsoComponent extends BaseSsoComponent {
logService: LogService, logService: LogService,
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
configService: ConfigService, configService: ConfigService,
protected authService: AuthService, masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
private authService: AuthService,
@Inject(WINDOW) private win: Window, @Inject(WINDOW) private win: Window,
) { ) {
super( super(
@ -63,6 +67,8 @@ export class SsoComponent extends BaseSsoComponent {
logService, logService,
userDecryptionOptionsService, userDecryptionOptionsService,
configService, configService,
masterPasswordService,
accountService,
); );
environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => { environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => {

View File

@ -11,6 +11,8 @@ import {
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
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 { 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 { 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";
@ -58,6 +60,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
configService: ConfigService, configService: ConfigService,
ssoLoginService: SsoLoginServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction,
private dialogService: DialogService, private dialogService: DialogService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
@Inject(WINDOW) protected win: Window, @Inject(WINDOW) protected win: Window,
private browserMessagingApi: ZonedMessageListenerService, private browserMessagingApi: ZonedMessageListenerService,
) { ) {
@ -78,6 +82,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
userDecryptionOptionsService, userDecryptionOptionsService,
ssoLoginService, ssoLoginService,
configService, configService,
masterPasswordService,
accountService,
); );
super.onSuccessfulLogin = async () => { super.onSuccessfulLogin = async () => {
// 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.

View File

@ -32,6 +32,7 @@ import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abst
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 { 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 { 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 { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
@ -46,6 +47,7 @@ import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device
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 { 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 { 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 { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
@ -242,6 +244,7 @@ export default class MainBackground {
keyGenerationService: KeyGenerationServiceAbstraction; keyGenerationService: KeyGenerationServiceAbstraction;
cryptoService: CryptoServiceAbstraction; cryptoService: CryptoServiceAbstraction;
cryptoFunctionService: CryptoFunctionServiceAbstraction; cryptoFunctionService: CryptoFunctionServiceAbstraction;
masterPasswordService: InternalMasterPasswordServiceAbstraction;
tokenService: TokenServiceAbstraction; tokenService: TokenServiceAbstraction;
appIdService: AppIdServiceAbstraction; appIdService: AppIdServiceAbstraction;
apiService: ApiServiceAbstraction; apiService: ApiServiceAbstraction;
@ -480,8 +483,11 @@ export default class MainBackground {
const themeStateService = new DefaultThemeStateService(this.globalStateProvider); const themeStateService = new DefaultThemeStateService(this.globalStateProvider);
this.masterPasswordService = new MasterPasswordService(this.stateProvider);
this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider); this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider);
this.cryptoService = new BrowserCryptoService( this.cryptoService = new BrowserCryptoService(
this.masterPasswordService,
this.keyGenerationService, this.keyGenerationService,
this.cryptoFunctionService, this.cryptoFunctionService,
this.encryptService, this.encryptService,
@ -525,6 +531,8 @@ export default class MainBackground {
this.badgeSettingsService = new BadgeSettingsService(this.stateProvider); this.badgeSettingsService = new BadgeSettingsService(this.stateProvider);
this.policyApiService = new PolicyApiService(this.policyService, this.apiService); this.policyApiService = new PolicyApiService(this.policyService, this.apiService);
this.keyConnectorService = new KeyConnectorService( this.keyConnectorService = new KeyConnectorService(
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.tokenService, this.tokenService,
@ -578,9 +586,10 @@ export default class MainBackground {
this.authRequestService = new AuthRequestService( this.authRequestService = new AuthRequestService(
this.appIdService, this.appIdService,
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.stateService,
); );
this.authService = new AuthService( this.authService = new AuthService(
@ -597,6 +606,8 @@ export default class MainBackground {
); );
this.loginStrategyService = new LoginStrategyService( this.loginStrategyService = new LoginStrategyService(
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.tokenService, this.tokenService,
@ -672,6 +683,8 @@ export default class MainBackground {
this.userVerificationService = new UserVerificationService( this.userVerificationService = new UserVerificationService(
this.stateService, this.stateService,
this.cryptoService, this.cryptoService,
this.accountService,
this.masterPasswordService,
this.i18nService, this.i18nService,
this.userVerificationApiService, this.userVerificationApiService,
this.userDecryptionOptionsService, this.userDecryptionOptionsService,
@ -694,6 +707,8 @@ export default class MainBackground {
this.vaultSettingsService = new VaultSettingsService(this.stateProvider); this.vaultSettingsService = new VaultSettingsService(this.stateProvider);
this.vaultTimeoutService = new VaultTimeoutService( this.vaultTimeoutService = new VaultTimeoutService(
this.accountService,
this.masterPasswordService,
this.cipherService, this.cipherService,
this.folderService, this.folderService,
this.collectionService, this.collectionService,
@ -729,6 +744,8 @@ export default class MainBackground {
this.providerService = new ProviderService(this.stateProvider); this.providerService = new ProviderService(this.stateProvider);
this.syncService = new SyncService( this.syncService = new SyncService(
this.masterPasswordService,
this.accountService,
this.apiService, this.apiService,
this.domainSettingsService, this.domainSettingsService,
this.folderService, this.folderService,
@ -878,6 +895,8 @@ export default class MainBackground {
this.fido2Service, this.fido2Service,
); );
this.nativeMessagingBackground = new NativeMessagingBackground( this.nativeMessagingBackground = new NativeMessagingBackground(
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.cryptoFunctionService, this.cryptoFunctionService,
this.runtimeBackground, this.runtimeBackground,
@ -1107,7 +1126,7 @@ export default class MainBackground {
const status = await this.authService.getAuthStatus(userId); const status = await this.authService.getAuthStatus(userId);
const forcePasswordReset = const forcePasswordReset =
(await this.stateService.getForceSetPasswordReason({ userId: userId })) != (await firstValueFrom(this.masterPasswordService.forceSetPasswordReason$(userId))) !=
ForceSetPasswordReason.None; ForceSetPasswordReason.None;
await this.systemService.clearPendingClipboard(); await this.systemService.clearPendingClipboard();

View File

@ -1,6 +1,8 @@
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
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 { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@ -71,6 +73,8 @@ export class NativeMessagingBackground {
private validatingFingerprint: boolean; private validatingFingerprint: boolean;
constructor( constructor(
private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService, private cryptoFunctionService: CryptoFunctionService,
private runtimeBackground: RuntimeBackground, private runtimeBackground: RuntimeBackground,
@ -336,10 +340,14 @@ export class NativeMessagingBackground {
) as UserKey; ) as UserKey;
await this.cryptoService.setUserKey(userKey); await this.cryptoService.setUserKey(userKey);
} else if (message.keyB64) { } else if (message.keyB64) {
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
// Backwards compatibility to support cases in which the user hasn't updated their desktop app // Backwards compatibility to support cases in which the user hasn't updated their desktop app
// TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3472) // TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3472)
let encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey(); const encUserKeyPrim = await this.stateService.getEncryptedCryptoSymmetricKey();
encUserKey ||= await this.stateService.getMasterKeyEncryptedUserKey(); const encUserKey =
encUserKeyPrim != null
? new EncString(encUserKeyPrim)
: await this.masterPasswordService.getMasterKeyEncryptedUserKey(userId);
if (!encUserKey) { if (!encUserKey) {
throw new Error("No encrypted user key found"); throw new Error("No encrypted user key found");
} }
@ -348,9 +356,9 @@ export class NativeMessagingBackground {
) as MasterKey; ) as MasterKey;
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey( const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(
masterKey, masterKey,
new EncString(encUserKey), encUserKey,
); );
await this.cryptoService.setMasterKey(masterKey); await this.masterPasswordService.setMasterKey(masterKey, userId);
await this.cryptoService.setUserKey(userKey); await this.cryptoService.setUserKey(userKey);
} else { } else {
throw new Error("No key received"); throw new Error("No key received");

View File

@ -1,9 +1,17 @@
import { VaultTimeoutService as AbstractVaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { VaultTimeoutService as AbstractVaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import {
accountServiceFactory,
AccountServiceInitOptions,
} from "../../auth/background/service-factories/account-service.factory";
import { import {
authServiceFactory, authServiceFactory,
AuthServiceInitOptions, AuthServiceInitOptions,
} from "../../auth/background/service-factories/auth-service.factory"; } from "../../auth/background/service-factories/auth-service.factory";
import {
internalMasterPasswordServiceFactory,
MasterPasswordServiceInitOptions,
} from "../../auth/background/service-factories/master-password-service.factory";
import { import {
CryptoServiceInitOptions, CryptoServiceInitOptions,
cryptoServiceFactory, cryptoServiceFactory,
@ -57,6 +65,8 @@ type VaultTimeoutServiceFactoryOptions = FactoryOptions & {
}; };
export type VaultTimeoutServiceInitOptions = VaultTimeoutServiceFactoryOptions & export type VaultTimeoutServiceInitOptions = VaultTimeoutServiceFactoryOptions &
AccountServiceInitOptions &
MasterPasswordServiceInitOptions &
CipherServiceInitOptions & CipherServiceInitOptions &
FolderServiceInitOptions & FolderServiceInitOptions &
CollectionServiceInitOptions & CollectionServiceInitOptions &
@ -79,6 +89,8 @@ export function vaultTimeoutServiceFactory(
opts, opts,
async () => async () =>
new VaultTimeoutService( new VaultTimeoutService(
await accountServiceFactory(cache, opts),
await internalMasterPasswordServiceFactory(cache, opts),
await cipherServiceFactory(cache, opts), await cipherServiceFactory(cache, opts),
await folderServiceFactory(cache, opts), await folderServiceFactory(cache, opts),
await collectionServiceFactory(cache, opts), await collectionServiceFactory(cache, opts),

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 {
internalMasterPasswordServiceFactory,
MasterPasswordServiceInitOptions,
} from "../../../auth/background/service-factories/master-password-service.factory";
import { import {
StateServiceInitOptions, StateServiceInitOptions,
stateServiceFactory, stateServiceFactory,
@ -34,6 +38,7 @@ import { StateProviderInitOptions, stateProviderFactory } from "./state-provider
type CryptoServiceFactoryOptions = FactoryOptions; type CryptoServiceFactoryOptions = FactoryOptions;
export type CryptoServiceInitOptions = CryptoServiceFactoryOptions & export type CryptoServiceInitOptions = CryptoServiceFactoryOptions &
MasterPasswordServiceInitOptions &
KeyGenerationServiceInitOptions & KeyGenerationServiceInitOptions &
CryptoFunctionServiceInitOptions & CryptoFunctionServiceInitOptions &
EncryptServiceInitOptions & EncryptServiceInitOptions &
@ -53,6 +58,7 @@ export function cryptoServiceFactory(
opts, opts,
async () => async () =>
new BrowserCryptoService( new BrowserCryptoService(
await internalMasterPasswordServiceFactory(cache, opts),
await keyGenerationServiceFactory(cache, opts), await keyGenerationServiceFactory(cache, opts),
await cryptoFunctionServiceFactory(cache, opts), await cryptoFunctionServiceFactory(cache, opts),
await encryptServiceFactory(cache, opts), await encryptServiceFactory(cache, opts),

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 { 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";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
@ -17,6 +18,7 @@ import { UserKey } from "@bitwarden/common/types/key";
export class BrowserCryptoService extends CryptoService { export class BrowserCryptoService extends CryptoService {
constructor( constructor(
masterPasswordService: InternalMasterPasswordServiceAbstraction,
keyGenerationService: KeyGenerationService, keyGenerationService: KeyGenerationService,
cryptoFunctionService: CryptoFunctionService, cryptoFunctionService: CryptoFunctionService,
encryptService: EncryptService, encryptService: EncryptService,
@ -28,6 +30,7 @@ export class BrowserCryptoService extends CryptoService {
private biometricStateService: BiometricStateService, private biometricStateService: BiometricStateService,
) { ) {
super( super(
masterPasswordService,
keyGenerationService, keyGenerationService,
cryptoFunctionService, cryptoFunctionService,
encryptService, encryptService,

View File

@ -1,6 +1,10 @@
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 { 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 { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -18,6 +22,8 @@ import { CliUtils } from "../../utils";
export class UnlockCommand { export class UnlockCommand {
constructor( constructor(
private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private stateService: StateService, private stateService: StateService,
private cryptoFunctionService: CryptoFunctionService, private cryptoFunctionService: CryptoFunctionService,
@ -45,11 +51,14 @@ export class UnlockCommand {
const kdf = await this.stateService.getKdfType(); const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig(); const kdfConfig = await this.stateService.getKdfConfig();
const masterKey = await this.cryptoService.makeMasterKey(password, email, kdf, kdfConfig); const masterKey = await this.cryptoService.makeMasterKey(password, email, kdf, kdfConfig);
const storedKeyHash = await this.cryptoService.getMasterKeyHash(); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const storedMasterKeyHash = await firstValueFrom(
this.masterPasswordService.masterKeyHash$(userId),
);
let passwordValid = false; let passwordValid = false;
if (masterKey != null) { if (masterKey != null) {
if (storedKeyHash != null) { if (storedMasterKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, masterKey); passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, masterKey);
} else { } else {
const serverKeyHash = await this.cryptoService.hashMasterKey( const serverKeyHash = await this.cryptoService.hashMasterKey(
@ -67,7 +76,7 @@ export class UnlockCommand {
masterKey, masterKey,
HashPurpose.LocalAuthorization, HashPurpose.LocalAuthorization,
); );
await this.cryptoService.setMasterKeyHash(localKeyHash); await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId);
} catch { } catch {
// Ignore // Ignore
} }
@ -75,7 +84,7 @@ export class UnlockCommand {
} }
if (passwordValid) { if (passwordValid) {
await this.cryptoService.setMasterKey(masterKey); await this.masterPasswordService.setMasterKey(masterKey, userId);
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey); const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey); await this.cryptoService.setUserKey(userKey);

View File

@ -28,6 +28,7 @@ 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 { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.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 { 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";
@ -168,6 +169,7 @@ export class Main {
organizationUserService: OrganizationUserService; organizationUserService: OrganizationUserService;
collectionService: CollectionService; collectionService: CollectionService;
vaultTimeoutService: VaultTimeoutService; vaultTimeoutService: VaultTimeoutService;
masterPasswordService: InternalMasterPasswordServiceAbstraction;
vaultTimeoutSettingsService: VaultTimeoutSettingsService; vaultTimeoutSettingsService: VaultTimeoutSettingsService;
syncService: SyncService; syncService: SyncService;
eventCollectionService: EventCollectionServiceAbstraction; eventCollectionService: EventCollectionServiceAbstraction;
@ -352,6 +354,7 @@ export class Main {
); );
this.cryptoService = new CryptoService( this.cryptoService = new CryptoService(
this.masterPasswordService,
this.keyGenerationService, this.keyGenerationService,
this.cryptoFunctionService, this.cryptoFunctionService,
this.encryptService, this.encryptService,
@ -432,6 +435,8 @@ export class Main {
this.policyApiService = new PolicyApiService(this.policyService, this.apiService); this.policyApiService = new PolicyApiService(this.policyService, this.apiService);
this.keyConnectorService = new KeyConnectorService( this.keyConnectorService = new KeyConnectorService(
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.tokenService, this.tokenService,
@ -471,9 +476,10 @@ export class Main {
this.authRequestService = new AuthRequestService( this.authRequestService = new AuthRequestService(
this.appIdService, this.appIdService,
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.stateService,
); );
this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService( this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService(
@ -481,6 +487,8 @@ export class Main {
); );
this.loginStrategyService = new LoginStrategyService( this.loginStrategyService = new LoginStrategyService(
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.tokenService, this.tokenService,
@ -568,6 +576,8 @@ export class Main {
this.userVerificationService = new UserVerificationService( this.userVerificationService = new UserVerificationService(
this.stateService, this.stateService,
this.cryptoService, this.cryptoService,
this.accountService,
this.masterPasswordService,
this.i18nService, this.i18nService,
this.userVerificationApiService, this.userVerificationApiService,
this.userDecryptionOptionsService, this.userDecryptionOptionsService,
@ -578,6 +588,8 @@ export class Main {
); );
this.vaultTimeoutService = new VaultTimeoutService( this.vaultTimeoutService = new VaultTimeoutService(
this.accountService,
this.masterPasswordService,
this.cipherService, this.cipherService,
this.folderService, this.folderService,
this.collectionService, this.collectionService,
@ -596,6 +608,8 @@ export class Main {
this.avatarService = new AvatarService(this.apiService, this.stateProvider); this.avatarService = new AvatarService(this.apiService, this.stateProvider);
this.syncService = new SyncService( this.syncService = new SyncService(
this.masterPasswordService,
this.accountService,
this.apiService, this.apiService,
this.domainSettingsService, this.domainSettingsService,
this.folderService, this.folderService,

View File

@ -122,6 +122,8 @@ export class ServeCommand {
this.shareCommand = new ShareCommand(this.main.cipherService); this.shareCommand = new ShareCommand(this.main.cipherService);
this.lockCommand = new LockCommand(this.main.vaultTimeoutService); this.lockCommand = new LockCommand(this.main.vaultTimeoutService);
this.unlockCommand = new UnlockCommand( this.unlockCommand = new UnlockCommand(
this.main.accountService,
this.main.masterPasswordService,
this.main.cryptoService, this.main.cryptoService,
this.main.stateService, this.main.stateService,
this.main.cryptoFunctionService, this.main.cryptoFunctionService,

View File

@ -253,6 +253,8 @@ export class Program {
if (!cmd.check) { if (!cmd.check) {
await this.exitIfNotAuthed(); await this.exitIfNotAuthed();
const command = new UnlockCommand( const command = new UnlockCommand(
this.main.accountService,
this.main.masterPasswordService,
this.main.cryptoService, this.main.cryptoService,
this.main.stateService, this.main.stateService,
this.main.cryptoFunctionService, this.main.cryptoFunctionService,
@ -613,6 +615,8 @@ export class Program {
this.processResponse(response, true); this.processResponse(response, true);
} else { } else {
const command = new UnlockCommand( const command = new UnlockCommand(
this.main.accountService,
this.main.masterPasswordService,
this.main.cryptoService, this.main.cryptoService,
this.main.stateService, this.main.stateService,
this.main.cryptoFunctionService, this.main.cryptoFunctionService,

View File

@ -26,6 +26,7 @@ import { InternalPolicyService } from "@bitwarden/common/admin-console/abstracti
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { MasterPasswordServiceAbstraction } 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";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
@ -120,6 +121,7 @@ export class AppComponent implements OnInit, OnDestroy {
private accountCleanUpInProgress: { [userId: string]: boolean } = {}; private accountCleanUpInProgress: { [userId: string]: boolean } = {};
constructor( constructor(
private masterPasswordService: MasterPasswordServiceAbstraction,
private broadcasterService: BroadcasterService, private broadcasterService: BroadcasterService,
private folderService: InternalFolderService, private folderService: InternalFolderService,
private syncService: SyncService, private syncService: SyncService,
@ -408,8 +410,9 @@ export class AppComponent implements OnInit, OnDestroy {
(await this.authService.getAuthStatus(message.userId)) === (await this.authService.getAuthStatus(message.userId)) ===
AuthenticationStatus.Locked; AuthenticationStatus.Locked;
const forcedPasswordReset = const forcedPasswordReset =
(await this.stateService.getForceSetPasswordReason({ userId: message.userId })) != (await firstValueFrom(
ForceSetPasswordReason.None; this.masterPasswordService.forceSetPasswordReason$(message.userId),
)) != ForceSetPasswordReason.None;
if (locked) { if (locked) {
this.messagingService.send("locked", { userId: message.userId }); this.messagingService.send("locked", { userId: message.userId });
} else if (forcedPasswordReset) { } else if (forcedPasswordReset) {

View File

@ -20,6 +20,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 { 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";
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service";
@ -228,6 +229,7 @@ const safeProviders: SafeProvider[] = [
provide: CryptoServiceAbstraction, provide: CryptoServiceAbstraction,
useClass: ElectronCryptoService, useClass: ElectronCryptoService,
deps: [ deps: [
InternalMasterPasswordServiceAbstraction,
KeyGenerationServiceAbstraction, KeyGenerationServiceAbstraction,
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
EncryptService, EncryptService,

View File

@ -14,7 +14,9 @@ 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 { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.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 { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@ -52,6 +54,7 @@ describe("LockComponent", () => {
let broadcasterServiceMock: MockProxy<BroadcasterService>; let broadcasterServiceMock: MockProxy<BroadcasterService>;
let platformUtilsServiceMock: MockProxy<PlatformUtilsService>; let platformUtilsServiceMock: MockProxy<PlatformUtilsService>;
let activatedRouteMock: MockProxy<ActivatedRoute>; let activatedRouteMock: MockProxy<ActivatedRoute>;
let mockMasterPasswordService: FakeMasterPasswordService;
const mockUserId = Utils.newGuid() as UserId; const mockUserId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
@ -67,6 +70,8 @@ describe("LockComponent", () => {
activatedRouteMock = mock<ActivatedRoute>(); activatedRouteMock = mock<ActivatedRoute>();
activatedRouteMock.queryParams = mock<ActivatedRoute["queryParams"]>(); activatedRouteMock.queryParams = mock<ActivatedRoute["queryParams"]>();
mockMasterPasswordService = new FakeMasterPasswordService();
biometricStateService.dismissedRequirePasswordOnStartCallout$ = of(false); biometricStateService.dismissedRequirePasswordOnStartCallout$ = of(false);
biometricStateService.promptAutomatically$ = of(false); biometricStateService.promptAutomatically$ = of(false);
biometricStateService.promptCancelled$ = of(false); biometricStateService.promptCancelled$ = of(false);
@ -74,6 +79,7 @@ describe("LockComponent", () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [LockComponent, I18nPipe], declarations: [LockComponent, I18nPipe],
providers: [ providers: [
{ provide: InternalMasterPasswordServiceAbstraction, useValue: mockMasterPasswordService },
{ {
provide: I18nService, provide: I18nService,
useValue: mock<I18nService>(), useValue: mock<I18nService>(),

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 { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.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";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
@ -38,6 +39,7 @@ export class LockComponent extends BaseLockComponent {
private autoPromptBiometric = false; private autoPromptBiometric = false;
constructor( constructor(
masterPasswordService: InternalMasterPasswordServiceAbstraction,
router: Router, router: Router,
i18nService: I18nService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
@ -63,6 +65,7 @@ export class LockComponent extends BaseLockComponent {
accountService: AccountService, accountService: AccountService,
) { ) {
super( super(
masterPasswordService,
router, router,
i18nService, i18nService,
platformUtilsService, platformUtilsService,

View File

@ -8,6 +8,8 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
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 { 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";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -29,6 +31,8 @@ const BroadcasterSubscriptionId = "SetPasswordComponent";
}) })
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnDestroy { export class SetPasswordComponent extends BaseSetPasswordComponent implements OnDestroy {
constructor( constructor(
accountService: AccountService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
apiService: ApiService, apiService: ApiService,
i18nService: I18nService, i18nService: I18nService,
cryptoService: CryptoService, cryptoService: CryptoService,
@ -50,6 +54,8 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
dialogService: DialogService, dialogService: DialogService,
) { ) {
super( super(
accountService,
masterPasswordService,
i18nService, i18nService,
cryptoService, cryptoService,
messagingService, messagingService,

View File

@ -7,6 +7,8 @@ import {
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
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 { 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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@ -39,6 +41,8 @@ export class SsoComponent extends BaseSsoComponent {
logService: LogService, logService: LogService,
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
configService: ConfigService, configService: ConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
) { ) {
super( super(
ssoLoginService, ssoLoginService,
@ -55,6 +59,8 @@ export class SsoComponent extends BaseSsoComponent {
logService, logService,
userDecryptionOptionsService, userDecryptionOptionsService,
configService, configService,
masterPasswordService,
accountService,
); );
super.onSuccessfulLogin = async () => { super.onSuccessfulLogin = async () => {
// 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.

View File

@ -11,6 +11,8 @@ import {
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
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 { 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 { 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";
@ -60,6 +62,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction,
configService: ConfigService, configService: ConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
@Inject(WINDOW) protected win: Window, @Inject(WINDOW) protected win: Window,
) { ) {
super( super(
@ -79,6 +83,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
userDecryptionOptionsService, userDecryptionOptionsService,
ssoLoginService, ssoLoginService,
configService, configService,
masterPasswordService,
accountService,
); );
super.onSuccessfulLogin = async () => { super.onSuccessfulLogin = async () => {
// 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.

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 { 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";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
@ -30,6 +31,7 @@ describe("electronCryptoService", () => {
const platformUtilService = mock<PlatformUtilsService>(); const platformUtilService = mock<PlatformUtilsService>();
const logService = mock<LogService>(); const logService = mock<LogService>();
const stateService = mock<StateService>(); const stateService = mock<StateService>();
let masterPasswordService: FakeMasterPasswordService;
let accountService: FakeAccountService; let accountService: FakeAccountService;
let stateProvider: FakeStateProvider; let stateProvider: FakeStateProvider;
const biometricStateService = mock<BiometricStateService>(); const biometricStateService = mock<BiometricStateService>();
@ -38,9 +40,11 @@ describe("electronCryptoService", () => {
beforeEach(() => { beforeEach(() => {
accountService = mockAccountServiceWith("userId" as UserId); accountService = mockAccountServiceWith("userId" as UserId);
masterPasswordService = new FakeMasterPasswordService();
stateProvider = new FakeStateProvider(accountService); stateProvider = new FakeStateProvider(accountService);
sut = new ElectronCryptoService( sut = new ElectronCryptoService(
masterPasswordService,
keyGenerationService, keyGenerationService,
cryptoFunctionService, cryptoFunctionService,
encryptService, encryptService,

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 { 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";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
@ -20,6 +21,7 @@ import { UserKey, MasterKey } from "@bitwarden/common/types/key";
export class ElectronCryptoService extends CryptoService { export class ElectronCryptoService extends CryptoService {
constructor( constructor(
masterPasswordService: InternalMasterPasswordServiceAbstraction,
keyGenerationService: KeyGenerationService, keyGenerationService: KeyGenerationService,
cryptoFunctionService: CryptoFunctionService, cryptoFunctionService: CryptoFunctionService,
encryptService: EncryptService, encryptService: EncryptService,
@ -31,6 +33,7 @@ export class ElectronCryptoService extends CryptoService {
private biometricStateService: BiometricStateService, private biometricStateService: BiometricStateService,
) { ) {
super( super(
masterPasswordService,
keyGenerationService, keyGenerationService,
cryptoFunctionService, cryptoFunctionService,
encryptService, encryptService,
@ -159,12 +162,16 @@ export class ElectronCryptoService extends CryptoService {
const oldBiometricKey = await this.stateService.getCryptoMasterKeyBiometric({ userId }); const oldBiometricKey = await this.stateService.getCryptoMasterKeyBiometric({ userId });
// decrypt // decrypt
const masterKey = new SymmetricCryptoKey(Utils.fromB64ToArray(oldBiometricKey)) as MasterKey; const masterKey = new SymmetricCryptoKey(Utils.fromB64ToArray(oldBiometricKey)) as MasterKey;
let encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey(); userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id;
encUserKey = encUserKey ?? (await this.stateService.getMasterKeyEncryptedUserKey()); const encUserKeyPrim = await this.stateService.getEncryptedCryptoSymmetricKey();
const encUserKey =
encUserKeyPrim != null
? new EncString(encUserKeyPrim)
: await this.masterPasswordService.getMasterKeyEncryptedUserKey(userId);
if (!encUserKey) { if (!encUserKey) {
throw new Error("No user key found during biometric migration"); throw new Error("No user key found during biometric migration");
} }
const userKey = await this.decryptUserKeyWithMasterKey(masterKey, new EncString(encUserKey)); const userKey = await this.decryptUserKeyWithMasterKey(masterKey, encUserKey);
// migrate // migrate
await this.storeBiometricKey(userKey, userId); await this.storeBiometricKey(userKey, userId);
await this.stateService.setCryptoMasterKeyBiometric(null, { userId }); await this.stateService.setCryptoMasterKeyBiometric(null, { userId });

View File

@ -1,6 +1,7 @@
import { Injectable, NgZone } from "@angular/core"; import { Injectable, NgZone } from "@angular/core";
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
import { MasterPasswordServiceAbstraction } 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 { 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";
@ -30,6 +31,7 @@ export class NativeMessagingService {
private sharedSecrets = new Map<string, SymmetricCryptoKey>(); private sharedSecrets = new Map<string, SymmetricCryptoKey>();
constructor( constructor(
private masterPasswordService: MasterPasswordServiceAbstraction,
private cryptoFunctionService: CryptoFunctionService, private cryptoFunctionService: CryptoFunctionService,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private platformUtilService: PlatformUtilsService, private platformUtilService: PlatformUtilsService,
@ -162,7 +164,9 @@ export class NativeMessagingService {
KeySuffixOptions.Biometric, KeySuffixOptions.Biometric,
message.userId, message.userId,
); );
const masterKey = await this.cryptoService.getMasterKey(message.userId); const masterKey = await firstValueFrom(
this.masterPasswordService.masterKey$(message.userId as UserId),
);
if (userKey != null) { if (userKey != null) {
// we send the master key still for backwards compatibility // we send the master key still for backwards compatibility

View File

@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
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";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@ -9,7 +10,6 @@ import { EncryptionType } 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 { 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 { Send } from "@bitwarden/common/tools/send/models/domain/send"; import { Send } from "@bitwarden/common/tools/send/models/domain/send";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { UserId } from "@bitwarden/common/types/guid"; import { UserId } from "@bitwarden/common/types/guid";
@ -22,6 +22,10 @@ import { Folder } from "@bitwarden/common/vault/models/domain/folder";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import {
FakeAccountService,
mockAccountServiceWith,
} from "../../../../../../libs/common/spec/fake-account-service";
import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service"; import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service";
import { StateService } from "../../core"; import { StateService } from "../../core";
import { EmergencyAccessService } from "../emergency-access"; import { EmergencyAccessService } from "../emergency-access";
@ -46,8 +50,10 @@ describe("KeyRotationService", () => {
const mockUserId = Utils.newGuid() as UserId; const mockUserId = Utils.newGuid() as UserId;
const mockAccountService: FakeAccountService = mockAccountServiceWith(mockUserId); const mockAccountService: FakeAccountService = mockAccountServiceWith(mockUserId);
let mockMasterPasswordService: FakeMasterPasswordService = new FakeMasterPasswordService();
beforeAll(() => { beforeAll(() => {
mockMasterPasswordService = new FakeMasterPasswordService();
mockApiService = mock<UserKeyRotationApiService>(); mockApiService = mock<UserKeyRotationApiService>();
mockCipherService = mock<CipherService>(); mockCipherService = mock<CipherService>();
mockFolderService = mock<FolderService>(); mockFolderService = mock<FolderService>();
@ -61,6 +67,7 @@ describe("KeyRotationService", () => {
mockConfigService = mock<ConfigService>(); mockConfigService = mock<ConfigService>();
keyRotationService = new UserKeyRotationService( keyRotationService = new UserKeyRotationService(
mockMasterPasswordService,
mockApiService, mockApiService,
mockCipherService, mockCipherService,
mockFolderService, mockFolderService,
@ -174,7 +181,10 @@ describe("KeyRotationService", () => {
it("saves the master key in state after creation", async () => { it("saves the master key in state after creation", async () => {
await keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword"); await keyRotationService.rotateUserKeyAndEncryptedData("mockMasterPassword");
expect(mockCryptoService.setMasterKey).toHaveBeenCalledWith("mockMasterKey" as any); expect(mockMasterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(
"mockMasterKey" as any,
mockUserId,
);
}); });
it("uses legacy rotation if feature flag is off", async () => { it("uses legacy rotation if feature flag is off", async () => {

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 { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.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";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -25,6 +26,7 @@ import { UserKeyRotationApiService } from "./user-key-rotation-api.service";
@Injectable() @Injectable()
export class UserKeyRotationService { export class UserKeyRotationService {
constructor( constructor(
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private apiService: UserKeyRotationApiService, private apiService: UserKeyRotationApiService,
private cipherService: CipherService, private cipherService: CipherService,
private folderService: FolderService, private folderService: FolderService,
@ -61,7 +63,8 @@ export class UserKeyRotationService {
} }
// Set master key again in case it was lost (could be lost on refresh) // Set master key again in case it was lost (could be lost on refresh)
await this.cryptoService.setMasterKey(masterKey); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setMasterKey(masterKey, userId);
const [newUserKey, newEncUserKey] = await this.cryptoService.makeUserKey(masterKey); const [newUserKey, newEncUserKey] = await this.cryptoService.makeUserKey(masterKey);
if (!newUserKey || !newEncUserKey) { if (!newUserKey || !newEncUserKey) {

View File

@ -1,80 +1,12 @@
import { Component, NgZone } from "@angular/core"; import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component";
import { PinCryptoServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.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 { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { DialogService } from "@bitwarden/components";
@Component({ @Component({
selector: "app-lock", selector: "app-lock",
templateUrl: "lock.component.html", templateUrl: "lock.component.html",
}) })
export class LockComponent extends BaseLockComponent { export class LockComponent extends BaseLockComponent {
constructor(
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
messagingService: MessagingService,
cryptoService: CryptoService,
vaultTimeoutService: VaultTimeoutService,
vaultTimeoutSettingsService: VaultTimeoutSettingsService,
environmentService: EnvironmentService,
stateService: StateService,
apiService: ApiService,
logService: LogService,
ngZone: NgZone,
policyApiService: PolicyApiServiceAbstraction,
policyService: InternalPolicyService,
passwordStrengthService: PasswordStrengthServiceAbstraction,
dialogService: DialogService,
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
userVerificationService: UserVerificationService,
pinCryptoService: PinCryptoServiceAbstraction,
biometricStateService: BiometricStateService,
accountService: AccountService,
) {
super(
router,
i18nService,
platformUtilsService,
messagingService,
cryptoService,
vaultTimeoutService,
vaultTimeoutSettingsService,
environmentService,
stateService,
apiService,
logService,
ngZone,
policyApiService,
policyService,
passwordStrengthService,
dialogService,
deviceTrustCryptoService,
userVerificationService,
pinCryptoService,
biometricStateService,
accountService,
);
}
async ngOnInit() { async ngOnInit() {
await super.ngOnInit(); await super.ngOnInit();
this.onSuccessfulSubmit = async () => { this.onSuccessfulSubmit = async () => {

View File

@ -10,6 +10,8 @@ import {
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction"; import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction";
import { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain-sso-details.response"; import { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain-sso-details.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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 { HttpStatusCode } from "@bitwarden/common/enums"; import { HttpStatusCode } from "@bitwarden/common/enums";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
@ -46,6 +48,8 @@ export class SsoComponent extends BaseSsoComponent {
private validationService: ValidationService, private validationService: ValidationService,
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
configService: ConfigService, configService: ConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
) { ) {
super( super(
ssoLoginService, ssoLoginService,
@ -62,6 +66,8 @@ export class SsoComponent extends BaseSsoComponent {
logService, logService,
userDecryptionOptionsService, userDecryptionOptionsService,
configService, configService,
masterPasswordService,
accountService,
); );
this.redirectUri = window.location.origin + "/sso-connector.html"; this.redirectUri = window.location.origin + "/sso-connector.html";
this.clientId = "web"; this.clientId = "web";

View File

@ -10,6 +10,8 @@ import {
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
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 { 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 { 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";
@ -50,6 +52,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction,
configService: ConfigService, configService: ConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
@Inject(WINDOW) protected win: Window, @Inject(WINDOW) protected win: Window,
) { ) {
super( super(
@ -69,6 +73,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
userDecryptionOptionsService, userDecryptionOptionsService,
ssoLoginService, ssoLoginService,
configService, configService,
masterPasswordService,
accountService,
); );
this.onSuccessfulLoginNavigate = this.goAfterLogIn; this.onSuccessfulLoginNavigate = this.goAfterLogIn;
} }

View File

@ -10,7 +10,11 @@ module.exports = {
displayName: "libs/angular tests", displayName: "libs/angular tests",
preset: "jest-preset-angular", preset: "jest-preset-angular",
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { moduleNameMapper: pathsToModuleNameMapper(
prefix: "<rootDir>/", // lets us use @bitwarden/common/spec in tests
}), { "@bitwarden/common/spec": ["../common/spec"], ...(compilerOptions?.paths ?? {}) },
{
prefix: "<rootDir>/",
},
),
}; };

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 { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.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";
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
@ -56,6 +57,7 @@ export class LockComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
constructor( constructor(
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected router: Router, protected router: Router,
protected i18nService: I18nService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected platformUtilsService: PlatformUtilsService,
@ -206,6 +208,7 @@ export class LockComponent implements OnInit, OnDestroy {
} }
private async doUnlockWithMasterPassword() { private async doUnlockWithMasterPassword() {
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const kdf = await this.stateService.getKdfType(); const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig(); const kdfConfig = await this.stateService.getKdfConfig();
@ -215,11 +218,13 @@ export class LockComponent implements OnInit, OnDestroy {
kdf, kdf,
kdfConfig, kdfConfig,
); );
const storedPasswordHash = await this.cryptoService.getMasterKeyHash(); const storedMasterKeyHash = await firstValueFrom(
this.masterPasswordService.masterKeyHash$(userId),
);
let passwordValid = false; let passwordValid = false;
if (storedPasswordHash != null) { if (storedMasterKeyHash != null) {
// Offline unlock possible // Offline unlock possible
passwordValid = await this.cryptoService.compareAndUpdateKeyHash( passwordValid = await this.cryptoService.compareAndUpdateKeyHash(
this.masterPassword, this.masterPassword,
@ -244,7 +249,7 @@ export class LockComponent implements OnInit, OnDestroy {
masterKey, masterKey,
HashPurpose.LocalAuthorization, HashPurpose.LocalAuthorization,
); );
await this.cryptoService.setMasterKeyHash(localKeyHash); await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId);
} catch (e) { } catch (e) {
this.logService.error(e); this.logService.error(e);
} finally { } finally {
@ -262,7 +267,7 @@ export class LockComponent implements OnInit, OnDestroy {
} }
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey); const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setMasterKey(masterKey); await this.masterPasswordService.setMasterKey(masterKey, userId);
await this.setUserKeyAndContinue(userKey, true); await this.setUserKeyAndContinue(userKey, true);
} }
@ -292,8 +297,10 @@ export class LockComponent implements OnInit, OnDestroy {
} }
if (this.requirePasswordChange()) { if (this.requirePasswordChange()) {
await this.stateService.setForceSetPasswordReason( const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.WeakMasterPassword, ForceSetPasswordReason.WeakMasterPassword,
userId,
); );
// 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

View File

@ -12,6 +12,8 @@ import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abs
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 { 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 { 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";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
@ -29,6 +31,7 @@ import {
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";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
@ -45,11 +48,14 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
resetPasswordAutoEnroll = false; resetPasswordAutoEnroll = false;
onSuccessfulChangePassword: () => Promise<void>; onSuccessfulChangePassword: () => Promise<void>;
successRoute = "vault"; successRoute = "vault";
userId: UserId;
forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None; forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None;
ForceSetPasswordReason = ForceSetPasswordReason; ForceSetPasswordReason = ForceSetPasswordReason;
constructor( constructor(
private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
i18nService: I18nService, i18nService: I18nService,
cryptoService: CryptoService, cryptoService: CryptoService,
messagingService: MessagingService, messagingService: MessagingService,
@ -88,7 +94,11 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
await this.syncService.fullSync(true); await this.syncService.fullSync(true);
this.syncLoading = false; this.syncLoading = false;
this.forceSetPasswordReason = await this.stateService.getForceSetPasswordReason(); this.userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
this.forceSetPasswordReason = await firstValueFrom(
this.masterPasswordService.forceSetPasswordReason$(this.userId),
);
this.route.queryParams this.route.queryParams
.pipe( .pipe(
@ -176,7 +186,6 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
if (response == null) { if (response == null) {
throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
} }
const userId = await this.stateService.getUserId();
const publicKey = Utils.fromB64ToArray(response.publicKey); const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user key with organization public key // RSA Encrypt user key with organization public key
@ -189,7 +198,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
return this.organizationUserService.putOrganizationUserResetPasswordEnrollment( return this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
this.orgId, this.orgId,
userId, this.userId,
resetRequest, resetRequest,
); );
}); });
@ -226,7 +235,10 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
keyPair: [string, EncString] | null, keyPair: [string, EncString] | null,
) { ) {
// Clear force set password reason to allow navigation back to vault. // Clear force set password reason to allow navigation back to vault.
await this.stateService.setForceSetPasswordReason(ForceSetPasswordReason.None); await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.None,
this.userId,
);
// User now has a password so update account decryption options in state // User now has a password so update account decryption options in state
const userDecryptionOpts = await firstValueFrom( const userDecryptionOpts = await firstValueFrom(
@ -237,7 +249,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
await this.stateService.setKdfType(this.kdf); await this.stateService.setKdfType(this.kdf);
await this.stateService.setKdfConfig(this.kdfConfig); await this.stateService.setKdfConfig(this.kdfConfig);
await this.cryptoService.setMasterKey(masterKey); await this.masterPasswordService.setMasterKey(masterKey, this.userId);
await this.cryptoService.setUserKey(userKey[0]); await this.cryptoService.setUserKey(userKey[0]);
// Set private key only for new JIT provisioned users in MP encryption orgs // Set private key only for new JIT provisioned users in MP encryption orgs
@ -255,6 +267,6 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
masterKey, masterKey,
HashPurpose.LocalAuthorization, HashPurpose.LocalAuthorization,
); );
await this.cryptoService.setMasterKeyHash(localMasterKeyHash); await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, this.userId);
} }
} }

View File

@ -12,10 +12,13 @@ import {
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
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 { 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 { 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 { 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 { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@ -23,7 +26,9 @@ 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 { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { UserId } from "@bitwarden/common/types/guid";
import { SsoComponent } from "./sso.component"; import { SsoComponent } from "./sso.component";
// test component that extends the SsoComponent // test component that extends the SsoComponent
@ -48,6 +53,7 @@ describe("SsoComponent", () => {
let component: TestSsoComponent; let component: TestSsoComponent;
let _component: SsoComponentProtected; let _component: SsoComponentProtected;
let fixture: ComponentFixture<TestSsoComponent>; let fixture: ComponentFixture<TestSsoComponent>;
const userId = "userId" as UserId;
// Mock Services // Mock Services
let mockLoginStrategyService: MockProxy<LoginStrategyServiceAbstraction>; let mockLoginStrategyService: MockProxy<LoginStrategyServiceAbstraction>;
@ -67,6 +73,8 @@ describe("SsoComponent", () => {
let mockLogService: MockProxy<LogService>; let mockLogService: MockProxy<LogService>;
let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>; let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
let mockConfigService: MockProxy<ConfigService>; let mockConfigService: MockProxy<ConfigService>;
let mockMasterPasswordService: FakeMasterPasswordService;
let mockAccountService: FakeAccountService;
// Mock authService.logIn params // Mock authService.logIn params
let code: string; let code: string;
@ -117,6 +125,8 @@ describe("SsoComponent", () => {
mockLogService = mock(); mockLogService = mock();
mockUserDecryptionOptionsService = mock(); mockUserDecryptionOptionsService = mock();
mockConfigService = mock(); mockConfigService = mock();
mockAccountService = mockAccountServiceWith(userId);
mockMasterPasswordService = new FakeMasterPasswordService();
// Mock loginStrategyService.logIn params // Mock loginStrategyService.logIn params
code = "code"; code = "code";
@ -199,6 +209,8 @@ describe("SsoComponent", () => {
}, },
{ provide: LogService, useValue: mockLogService }, { provide: LogService, useValue: mockLogService },
{ provide: ConfigService, useValue: mockConfigService }, { provide: ConfigService, useValue: mockConfigService },
{ provide: InternalMasterPasswordServiceAbstraction, useValue: mockMasterPasswordService },
{ provide: AccountService, useValue: mockAccountService },
], ],
}); });
@ -365,8 +377,9 @@ describe("SsoComponent", () => {
await _component.logIn(code, codeVerifier, orgIdFromState); await _component.logIn(code, codeVerifier, orgIdFromState);
expect(mockLoginStrategyService.logIn).toHaveBeenCalledTimes(1); expect(mockLoginStrategyService.logIn).toHaveBeenCalledTimes(1);
expect(mockStateService.setForceSetPasswordReason).toHaveBeenCalledWith( expect(mockMasterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission,
userId,
); );
expect(mockOnSuccessfulLoginTdeNavigate).not.toHaveBeenCalled(); expect(mockOnSuccessfulLoginTdeNavigate).not.toHaveBeenCalled();

View File

@ -11,6 +11,8 @@ import {
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
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 { 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 { 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";
@ -66,6 +68,8 @@ export class SsoComponent {
protected logService: LogService, protected logService: LogService,
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
protected configService: ConfigService, protected configService: ConfigService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected accountService: AccountService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -290,8 +294,10 @@ export class SsoComponent {
// Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device) // Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device)
// Note: we cannot directly navigate in this scenario as we are in a pre-decryption state, and // Note: we cannot directly navigate in this scenario as we are in a pre-decryption state, and
// if you try to set a new MP before decrypting, you will invalidate the user's data by making a new user key. // if you try to set a new MP before decrypting, you will invalidate the user's data by making a new user key.
await this.stateService.setForceSetPasswordReason( const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission,
userId,
); );
} }

View File

@ -15,11 +15,14 @@ import {
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
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 { 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 { 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";
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 { 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 { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@ -27,6 +30,8 @@ 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 { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { TwoFactorComponent } from "./two-factor.component"; import { TwoFactorComponent } from "./two-factor.component";
@ -46,6 +51,7 @@ describe("TwoFactorComponent", () => {
let _component: TwoFactorComponentProtected; let _component: TwoFactorComponentProtected;
let fixture: ComponentFixture<TestTwoFactorComponent>; let fixture: ComponentFixture<TestTwoFactorComponent>;
const userId = "userId" as UserId;
// Mock Services // Mock Services
let mockLoginStrategyService: MockProxy<LoginStrategyServiceAbstraction>; let mockLoginStrategyService: MockProxy<LoginStrategyServiceAbstraction>;
@ -63,6 +69,8 @@ describe("TwoFactorComponent", () => {
let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>; let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
let mockSsoLoginService: MockProxy<SsoLoginServiceAbstraction>; let mockSsoLoginService: MockProxy<SsoLoginServiceAbstraction>;
let mockConfigService: MockProxy<ConfigService>; let mockConfigService: MockProxy<ConfigService>;
let mockMasterPasswordService: FakeMasterPasswordService;
let mockAccountService: FakeAccountService;
let mockUserDecryptionOpts: { let mockUserDecryptionOpts: {
noMasterPassword: UserDecryptionOptions; noMasterPassword: UserDecryptionOptions;
@ -93,6 +101,8 @@ describe("TwoFactorComponent", () => {
mockUserDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>(); mockUserDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>();
mockSsoLoginService = mock<SsoLoginServiceAbstraction>(); mockSsoLoginService = mock<SsoLoginServiceAbstraction>();
mockConfigService = mock<ConfigService>(); mockConfigService = mock<ConfigService>();
mockAccountService = mockAccountServiceWith(userId);
mockMasterPasswordService = new FakeMasterPasswordService();
mockUserDecryptionOpts = { mockUserDecryptionOpts = {
noMasterPassword: new UserDecryptionOptions({ noMasterPassword: new UserDecryptionOptions({
@ -170,6 +180,8 @@ describe("TwoFactorComponent", () => {
}, },
{ provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService }, { provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService },
{ provide: ConfigService, useValue: mockConfigService }, { provide: ConfigService, useValue: mockConfigService },
{ provide: InternalMasterPasswordServiceAbstraction, useValue: mockMasterPasswordService },
{ provide: AccountService, useValue: mockAccountService },
], ],
}); });
@ -407,9 +419,9 @@ describe("TwoFactorComponent", () => {
await component.doSubmit(); await component.doSubmit();
// Assert // Assert
expect(mockMasterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
expect(mockStateService.setForceSetPasswordReason).toHaveBeenCalledWith(
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission,
userId,
); );
expect(mockRouter.navigate).toHaveBeenCalledTimes(1); expect(mockRouter.navigate).toHaveBeenCalledTimes(1);

View File

@ -14,6 +14,8 @@ import {
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
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 { 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 { 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";
@ -92,6 +94,8 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction, protected ssoLoginService: SsoLoginServiceAbstraction,
protected configService: ConfigService, protected configService: ConfigService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected accountService: AccountService,
) { ) {
super(environmentService, i18nService, platformUtilsService); super(environmentService, i18nService, platformUtilsService);
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
@ -342,8 +346,10 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
// Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device) // Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device)
// Note: we cannot directly navigate to the set password screen in this scenario as we are in a pre-decryption state, and // Note: we cannot directly navigate to the set password screen in this scenario as we are in a pre-decryption state, and
// if you try to set a new MP before decrypting, you will invalidate the user's data by making a new user key. // if you try to set a new MP before decrypting, you will invalidate the user's data by making a new user key.
await this.stateService.setForceSetPasswordReason( const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission,
userId,
); );
} }

View File

@ -1,9 +1,12 @@
import { Directive } from "@angular/core"; import { Directive } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
@ -56,6 +59,8 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
protected router: Router, protected router: Router,
dialogService: DialogService, dialogService: DialogService,
private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
) { ) {
super( super(
i18nService, i18nService,
@ -72,7 +77,8 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
async ngOnInit() { async ngOnInit() {
await this.syncService.fullSync(true); await this.syncService.fullSync(true);
this.reason = await this.stateService.getForceSetPasswordReason(); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
this.reason = await firstValueFrom(this.masterPasswordService.forceSetPasswordReason$(userId));
// If we somehow end up here without a reason, go back to the home page // If we somehow end up here without a reason, go back to the home page
if (this.reason == ForceSetPasswordReason.None) { if (this.reason == ForceSetPasswordReason.None) {
@ -163,7 +169,11 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
this.i18nService.t("updatedMasterPassword"), this.i18nService.t("updatedMasterPassword"),
); );
await this.stateService.setForceSetPasswordReason(ForceSetPasswordReason.None); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.None,
userId,
);
if (this.onSuccessfulChangePassword != null) { if (this.onSuccessfulChangePassword != 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.

View File

@ -1,12 +1,14 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router"; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
import { firstValueFrom } from "rxjs";
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 { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
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 { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
@Injectable() @Injectable()
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
@ -15,7 +17,8 @@ export class AuthGuard implements CanActivate {
private router: Router, private router: Router,
private messagingService: MessagingService, private messagingService: MessagingService,
private keyConnectorService: KeyConnectorService, private keyConnectorService: KeyConnectorService,
private stateService: StateService, private accountService: AccountService,
private masterPasswordService: MasterPasswordServiceAbstraction,
) {} ) {}
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) { async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
@ -40,7 +43,10 @@ export class AuthGuard implements CanActivate {
return this.router.createUrlTree(["/remove-password"]); return this.router.createUrlTree(["/remove-password"]);
} }
const forceSetPasswordReason = await this.stateService.getForceSetPasswordReason(); const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
const forceSetPasswordReason = await firstValueFrom(
this.masterPasswordService.forceSetPasswordReason$(userId),
);
if ( if (
forceSetPasswordReason === forceSetPasswordReason ===

View File

@ -60,6 +60,10 @@ import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abst
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 { 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,
MasterPasswordServiceAbstraction,
} from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.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";
@ -78,6 +82,7 @@ import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device
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 { 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 { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation"; import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation";
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";
@ -359,6 +364,8 @@ const safeProviders: SafeProvider[] = [
provide: LoginStrategyServiceAbstraction, provide: LoginStrategyServiceAbstraction,
useClass: LoginStrategyService, useClass: LoginStrategyService,
deps: [ deps: [
AccountServiceAbstraction,
InternalMasterPasswordServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
TokenServiceAbstraction, TokenServiceAbstraction,
@ -521,6 +528,7 @@ const safeProviders: SafeProvider[] = [
provide: CryptoServiceAbstraction, provide: CryptoServiceAbstraction,
useClass: CryptoService, useClass: CryptoService,
deps: [ deps: [
InternalMasterPasswordServiceAbstraction,
KeyGenerationServiceAbstraction, KeyGenerationServiceAbstraction,
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
EncryptService, EncryptService,
@ -587,6 +595,8 @@ const safeProviders: SafeProvider[] = [
provide: SyncServiceAbstraction, provide: SyncServiceAbstraction,
useClass: SyncService, useClass: SyncService,
deps: [ deps: [
InternalMasterPasswordServiceAbstraction,
AccountServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
DomainSettingsService, DomainSettingsService,
InternalFolderService, InternalFolderService,
@ -626,6 +636,8 @@ const safeProviders: SafeProvider[] = [
provide: VaultTimeoutService, provide: VaultTimeoutService,
useClass: VaultTimeoutService, useClass: VaultTimeoutService,
deps: [ deps: [
AccountServiceAbstraction,
InternalMasterPasswordServiceAbstraction,
CipherServiceAbstraction, CipherServiceAbstraction,
FolderServiceAbstraction, FolderServiceAbstraction,
CollectionServiceAbstraction, CollectionServiceAbstraction,
@ -771,10 +783,21 @@ const safeProviders: SafeProvider[] = [
useClass: PolicyApiService, useClass: PolicyApiService,
deps: [InternalPolicyService, ApiServiceAbstraction], deps: [InternalPolicyService, ApiServiceAbstraction],
}), }),
safeProvider({
provide: InternalMasterPasswordServiceAbstraction,
useClass: MasterPasswordService,
deps: [StateProvider],
}),
safeProvider({
provide: MasterPasswordServiceAbstraction,
useExisting: InternalMasterPasswordServiceAbstraction,
}),
safeProvider({ safeProvider({
provide: KeyConnectorServiceAbstraction, provide: KeyConnectorServiceAbstraction,
useClass: KeyConnectorService, useClass: KeyConnectorService,
deps: [ deps: [
AccountServiceAbstraction,
InternalMasterPasswordServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
TokenServiceAbstraction, TokenServiceAbstraction,
@ -791,6 +814,8 @@ const safeProviders: SafeProvider[] = [
deps: [ deps: [
StateServiceAbstraction, StateServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
AccountServiceAbstraction,
InternalMasterPasswordServiceAbstraction,
I18nServiceAbstraction, I18nServiceAbstraction,
UserVerificationApiServiceAbstraction, UserVerificationApiServiceAbstraction,
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
@ -934,9 +959,10 @@ const safeProviders: SafeProvider[] = [
useClass: AuthRequestService, useClass: AuthRequestService,
deps: [ deps: [
AppIdServiceAbstraction, AppIdServiceAbstraction,
AccountServiceAbstraction,
InternalMasterPasswordServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
ApiServiceAbstraction, ApiServiceAbstraction,
StateServiceAbstraction,
], ],
}), }),
safeProvider({ safeProvider({

View File

@ -5,6 +5,7 @@ import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abst
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";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
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";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -14,7 +15,9 @@ 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, mockAccountServiceWith } from "@bitwarden/common/spec";
import { CsprngArray } from "@bitwarden/common/types/csprng"; import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { MasterKey, 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,6 +45,10 @@ describe("AuthRequestLoginStrategy", () => {
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>; let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>; let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
let authRequestLoginStrategy: AuthRequestLoginStrategy; let authRequestLoginStrategy: AuthRequestLoginStrategy;
let credentials: AuthRequestLoginCredentials; let credentials: AuthRequestLoginCredentials;
let tokenResponse: IdentityTokenResponse; let tokenResponse: IdentityTokenResponse;
@ -71,12 +78,17 @@ describe("AuthRequestLoginStrategy", () => {
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>(); deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
billingAccountProfileStateService = mock<BillingAccountProfileStateService>(); billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
accountService = mockAccountServiceWith(mockUserId);
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({});
authRequestLoginStrategy = new AuthRequestLoginStrategy( authRequestLoginStrategy = new AuthRequestLoginStrategy(
cache, cache,
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@ -108,13 +120,16 @@ describe("AuthRequestLoginStrategy", () => {
const masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey; const masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey; const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
cryptoService.getMasterKey.mockResolvedValue(masterKey); masterPasswordService.masterKeySubject.next(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey); cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
await authRequestLoginStrategy.logIn(credentials); await authRequestLoginStrategy.logIn(credentials);
expect(cryptoService.setMasterKey).toHaveBeenCalledWith(masterKey); expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(masterKey, mockUserId);
expect(cryptoService.setMasterKeyHash).toHaveBeenCalledWith(decMasterKeyHash); expect(masterPasswordService.mock.setMasterKeyHash).toHaveBeenCalledWith(
decMasterKeyHash,
mockUserId,
);
expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key); expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key);
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey); expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey);
expect(deviceTrustCryptoService.trustDeviceIfRequired).toHaveBeenCalled(); expect(deviceTrustCryptoService.trustDeviceIfRequired).toHaveBeenCalled();
@ -136,8 +151,8 @@ describe("AuthRequestLoginStrategy", () => {
await authRequestLoginStrategy.logIn(credentials); await authRequestLoginStrategy.logIn(credentials);
// setMasterKey and setMasterKeyHash should not be called // setMasterKey and setMasterKeyHash should not be called
expect(cryptoService.setMasterKey).not.toHaveBeenCalled(); expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled();
expect(cryptoService.setMasterKeyHash).not.toHaveBeenCalled(); expect(masterPasswordService.mock.setMasterKeyHash).not.toHaveBeenCalled();
// setMasterKeyEncryptedUserKey, setUserKey, and setPrivateKey should still be called // setMasterKeyEncryptedUserKey, setUserKey, and setPrivateKey should still be called
expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key); expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key);

View File

@ -1,8 +1,10 @@
import { Observable, map, BehaviorSubject } from "rxjs"; import { firstValueFrom, Observable, map, BehaviorSubject } from "rxjs";
import { Jsonify } from "type-fest"; 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 { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.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 { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
@ -47,6 +49,8 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
constructor( constructor(
data: AuthRequestLoginStrategyData, data: AuthRequestLoginStrategyData,
accountService: AccountService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
cryptoService: CryptoService, cryptoService: CryptoService,
apiService: ApiService, apiService: ApiService,
tokenService: TokenService, tokenService: TokenService,
@ -61,6 +65,8 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@ -114,8 +120,15 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
authRequestCredentials.decryptedMasterKey && authRequestCredentials.decryptedMasterKey &&
authRequestCredentials.decryptedMasterKeyHash authRequestCredentials.decryptedMasterKeyHash
) { ) {
await this.cryptoService.setMasterKey(authRequestCredentials.decryptedMasterKey); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.cryptoService.setMasterKeyHash(authRequestCredentials.decryptedMasterKeyHash); await this.masterPasswordService.setMasterKey(
authRequestCredentials.decryptedMasterKey,
userId,
);
await this.masterPasswordService.setMasterKeyHash(
authRequestCredentials.decryptedMasterKeyHash,
userId,
);
} }
} }
@ -137,7 +150,8 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
} }
private async trySetUserKeyWithMasterKey(): Promise<void> { private async trySetUserKeyWithMasterKey(): Promise<void> {
const masterKey = await this.cryptoService.getMasterKey(); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
if (masterKey) { if (masterKey) {
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey); const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey); await this.cryptoService.setUserKey(userKey);

View File

@ -14,6 +14,7 @@ import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/id
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response";
import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
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";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@ -31,11 +32,13 @@ import {
} from "@bitwarden/common/platform/models/domain/account"; } 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 { import {
PasswordStrengthServiceAbstraction, PasswordStrengthServiceAbstraction,
PasswordStrengthService, PasswordStrengthService,
} from "@bitwarden/common/tools/password-strength"; } from "@bitwarden/common/tools/password-strength";
import { CsprngArray } from "@bitwarden/common/types/csprng"; import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import { UserKey, MasterKey } from "@bitwarden/common/types/key"; import { UserKey, MasterKey } from "@bitwarden/common/types/key";
import { LoginStrategyServiceAbstraction } from "../abstractions"; import { LoginStrategyServiceAbstraction } from "../abstractions";
@ -56,7 +59,7 @@ const privateKey = "PRIVATE_KEY";
const captchaSiteKey = "CAPTCHA_SITE_KEY"; const captchaSiteKey = "CAPTCHA_SITE_KEY";
const kdf = 0; const kdf = 0;
const kdfIterations = 10000; const kdfIterations = 10000;
const userId = Utils.newGuid(); const userId = Utils.newGuid() as UserId;
const masterPasswordHash = "MASTER_PASSWORD_HASH"; const masterPasswordHash = "MASTER_PASSWORD_HASH";
const name = "NAME"; const name = "NAME";
const defaultUserDecryptionOptionsServerResponse: IUserDecryptionOptionsServerResponse = { const defaultUserDecryptionOptionsServerResponse: IUserDecryptionOptionsServerResponse = {
@ -98,6 +101,8 @@ export function identityTokenResponseFactory(
// TODO: add tests for latest changes to base class for TDE // TODO: add tests for latest changes to base class for TDE
describe("LoginStrategy", () => { describe("LoginStrategy", () => {
let cache: PasswordLoginStrategyData; let cache: PasswordLoginStrategyData;
let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
let loginStrategyService: MockProxy<LoginStrategyServiceAbstraction>; let loginStrategyService: MockProxy<LoginStrategyServiceAbstraction>;
let cryptoService: MockProxy<CryptoService>; let cryptoService: MockProxy<CryptoService>;
@ -118,6 +123,9 @@ describe("LoginStrategy", () => {
let credentials: PasswordLoginCredentials; let credentials: PasswordLoginCredentials;
beforeEach(async () => { beforeEach(async () => {
accountService = mockAccountServiceWith(userId);
masterPasswordService = new FakeMasterPasswordService();
loginStrategyService = mock<LoginStrategyServiceAbstraction>(); loginStrategyService = mock<LoginStrategyServiceAbstraction>();
cryptoService = mock<CryptoService>(); cryptoService = mock<CryptoService>();
apiService = mock<ApiService>(); apiService = mock<ApiService>();
@ -139,6 +147,8 @@ describe("LoginStrategy", () => {
// The base class is abstract so we test it via PasswordLoginStrategy // The base class is abstract so we test it via PasswordLoginStrategy
passwordLoginStrategy = new PasswordLoginStrategy( passwordLoginStrategy = new PasswordLoginStrategy(
cache, cache,
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@ -241,7 +251,7 @@ describe("LoginStrategy", () => {
}); });
apiService.postIdentityToken.mockResolvedValue(tokenResponse); apiService.postIdentityToken.mockResolvedValue(tokenResponse);
cryptoService.getMasterKey.mockResolvedValue(masterKey); masterPasswordService.masterKeySubject.next(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey); cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
const result = await passwordLoginStrategy.logIn(credentials); const result = await passwordLoginStrategy.logIn(credentials);
@ -260,7 +270,7 @@ describe("LoginStrategy", () => {
cryptoService.makeKeyPair.mockResolvedValue(["PUBLIC_KEY", new EncString("PRIVATE_KEY")]); cryptoService.makeKeyPair.mockResolvedValue(["PUBLIC_KEY", new EncString("PRIVATE_KEY")]);
apiService.postIdentityToken.mockResolvedValue(tokenResponse); apiService.postIdentityToken.mockResolvedValue(tokenResponse);
cryptoService.getMasterKey.mockResolvedValue(masterKey); masterPasswordService.masterKeySubject.next(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey); cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
await passwordLoginStrategy.logIn(credentials); await passwordLoginStrategy.logIn(credentials);
@ -382,6 +392,8 @@ describe("LoginStrategy", () => {
passwordLoginStrategy = new PasswordLoginStrategy( passwordLoginStrategy = new PasswordLoginStrategy(
cache, cache,
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,

View File

@ -1,6 +1,8 @@
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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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";
@ -60,6 +62,8 @@ export abstract class LoginStrategy {
protected abstract cache: BehaviorSubject<LoginStrategyData>; protected abstract cache: BehaviorSubject<LoginStrategyData>;
constructor( constructor(
protected accountService: AccountService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected cryptoService: CryptoService, protected cryptoService: CryptoService,
protected apiService: ApiService, protected apiService: ApiService,
protected tokenService: TokenService, protected tokenService: TokenService,

View File

@ -9,6 +9,7 @@ import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/for
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
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";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -19,11 +20,13 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
import { HashPurpose } from "@bitwarden/common/platform/enums"; import { HashPurpose } from "@bitwarden/common/platform/enums";
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, mockAccountServiceWith } from "@bitwarden/common/spec";
import { import {
PasswordStrengthServiceAbstraction, PasswordStrengthServiceAbstraction,
PasswordStrengthService, PasswordStrengthService,
} from "@bitwarden/common/tools/password-strength"; } from "@bitwarden/common/tools/password-strength";
import { CsprngArray } from "@bitwarden/common/types/csprng"; import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { LoginStrategyServiceAbstraction } from "../abstractions"; import { LoginStrategyServiceAbstraction } from "../abstractions";
@ -42,6 +45,7 @@ const masterKey = new SymmetricCryptoKey(
"N2KWjlLpfi5uHjv+YcfUKIpZ1l+W+6HRensmIqD+BFYBf6N/dvFpJfWwYnVBdgFCK2tJTAIMLhqzIQQEUmGFgg==", "N2KWjlLpfi5uHjv+YcfUKIpZ1l+W+6HRensmIqD+BFYBf6N/dvFpJfWwYnVBdgFCK2tJTAIMLhqzIQQEUmGFgg==",
), ),
) as MasterKey; ) as MasterKey;
const userId = Utils.newGuid() as UserId;
const deviceId = Utils.newGuid(); const deviceId = Utils.newGuid();
const masterPasswordPolicy = new MasterPasswordPolicyResponse({ const masterPasswordPolicy = new MasterPasswordPolicyResponse({
EnforceOnLogin: true, EnforceOnLogin: true,
@ -50,6 +54,8 @@ const masterPasswordPolicy = new MasterPasswordPolicyResponse({
describe("PasswordLoginStrategy", () => { describe("PasswordLoginStrategy", () => {
let cache: PasswordLoginStrategyData; let cache: PasswordLoginStrategyData;
let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
let loginStrategyService: MockProxy<LoginStrategyServiceAbstraction>; let loginStrategyService: MockProxy<LoginStrategyServiceAbstraction>;
let cryptoService: MockProxy<CryptoService>; let cryptoService: MockProxy<CryptoService>;
@ -71,6 +77,9 @@ describe("PasswordLoginStrategy", () => {
let tokenResponse: IdentityTokenResponse; let tokenResponse: IdentityTokenResponse;
beforeEach(async () => { beforeEach(async () => {
accountService = mockAccountServiceWith(userId);
masterPasswordService = new FakeMasterPasswordService();
loginStrategyService = mock<LoginStrategyServiceAbstraction>(); loginStrategyService = mock<LoginStrategyServiceAbstraction>();
cryptoService = mock<CryptoService>(); cryptoService = mock<CryptoService>();
apiService = mock<ApiService>(); apiService = mock<ApiService>();
@ -102,6 +111,8 @@ describe("PasswordLoginStrategy", () => {
passwordLoginStrategy = new PasswordLoginStrategy( passwordLoginStrategy = new PasswordLoginStrategy(
cache, cache,
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@ -145,13 +156,16 @@ describe("PasswordLoginStrategy", () => {
it("sets keys after a successful authentication", async () => { it("sets keys after a successful authentication", async () => {
const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey; const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
cryptoService.getMasterKey.mockResolvedValue(masterKey); masterPasswordService.masterKeySubject.next(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey); cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
await passwordLoginStrategy.logIn(credentials); await passwordLoginStrategy.logIn(credentials);
expect(cryptoService.setMasterKey).toHaveBeenCalledWith(masterKey); expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(masterKey, userId);
expect(cryptoService.setMasterKeyHash).toHaveBeenCalledWith(localHashedPassword); expect(masterPasswordService.mock.setMasterKeyHash).toHaveBeenCalledWith(
localHashedPassword,
userId,
);
expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key); expect(cryptoService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(tokenResponse.key);
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey); expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey);
expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey); expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey);
@ -183,8 +197,9 @@ describe("PasswordLoginStrategy", () => {
const result = await passwordLoginStrategy.logIn(credentials); const result = await passwordLoginStrategy.logIn(credentials);
expect(policyService.evaluateMasterPassword).toHaveBeenCalled(); expect(policyService.evaluateMasterPassword).toHaveBeenCalled();
expect(stateService.setForceSetPasswordReason).toHaveBeenCalledWith( expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
ForceSetPasswordReason.WeakMasterPassword, ForceSetPasswordReason.WeakMasterPassword,
userId,
); );
expect(result.forcePasswordReset).toEqual(ForceSetPasswordReason.WeakMasterPassword); expect(result.forcePasswordReset).toEqual(ForceSetPasswordReason.WeakMasterPassword);
}); });
@ -222,8 +237,9 @@ describe("PasswordLoginStrategy", () => {
expect(firstResult.forcePasswordReset).toEqual(ForceSetPasswordReason.None); expect(firstResult.forcePasswordReset).toEqual(ForceSetPasswordReason.None);
// Second login attempt should save the force password reset options and return in result // Second login attempt should save the force password reset options and return in result
expect(stateService.setForceSetPasswordReason).toHaveBeenCalledWith( expect(masterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
ForceSetPasswordReason.WeakMasterPassword, ForceSetPasswordReason.WeakMasterPassword,
userId,
); );
expect(secondResult.forcePasswordReset).toEqual(ForceSetPasswordReason.WeakMasterPassword); expect(secondResult.forcePasswordReset).toEqual(ForceSetPasswordReason.WeakMasterPassword);
}); });

View File

@ -1,9 +1,11 @@
import { BehaviorSubject, map, Observable } from "rxjs"; import { BehaviorSubject, firstValueFrom, map, Observable } from "rxjs";
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
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 { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
@ -70,6 +72,8 @@ export class PasswordLoginStrategy extends LoginStrategy {
constructor( constructor(
data: PasswordLoginStrategyData, data: PasswordLoginStrategyData,
accountService: AccountService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
cryptoService: CryptoService, cryptoService: CryptoService,
apiService: ApiService, apiService: ApiService,
tokenService: TokenService, tokenService: TokenService,
@ -86,6 +90,8 @@ export class PasswordLoginStrategy extends LoginStrategy {
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@ -157,8 +163,10 @@ export class PasswordLoginStrategy extends LoginStrategy {
}); });
} else { } else {
// Authentication was successful, save the force update password options with the state service // Authentication was successful, save the force update password options with the state service
await this.stateService.setForceSetPasswordReason( const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.WeakMasterPassword, ForceSetPasswordReason.WeakMasterPassword,
userId,
); );
authResult.forcePasswordReset = ForceSetPasswordReason.WeakMasterPassword; authResult.forcePasswordReset = ForceSetPasswordReason.WeakMasterPassword;
} }
@ -184,7 +192,8 @@ export class PasswordLoginStrategy extends LoginStrategy {
!result.requiresCaptcha && !result.requiresCaptcha &&
forcePasswordResetReason != ForceSetPasswordReason.None forcePasswordResetReason != ForceSetPasswordReason.None
) { ) {
await this.stateService.setForceSetPasswordReason(forcePasswordResetReason); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(forcePasswordResetReason, userId);
result.forcePasswordReset = forcePasswordResetReason; result.forcePasswordReset = forcePasswordResetReason;
} }
@ -193,8 +202,9 @@ export class PasswordLoginStrategy extends LoginStrategy {
protected override async setMasterKey(response: IdentityTokenResponse) { protected override async setMasterKey(response: IdentityTokenResponse) {
const { masterKey, localMasterKeyHash } = this.cache.value; const { masterKey, localMasterKeyHash } = this.cache.value;
await this.cryptoService.setMasterKey(masterKey); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.cryptoService.setMasterKeyHash(localMasterKeyHash); await this.masterPasswordService.setMasterKey(masterKey, userId);
await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, userId);
} }
protected override async setUserKey(response: IdentityTokenResponse): Promise<void> { protected override async setUserKey(response: IdentityTokenResponse): Promise<void> {
@ -204,7 +214,8 @@ export class PasswordLoginStrategy extends LoginStrategy {
} }
await this.cryptoService.setMasterKeyEncryptedUserKey(response.key); await this.cryptoService.setMasterKeyEncryptedUserKey(response.key);
const masterKey = await this.cryptoService.getMasterKey(); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
if (masterKey) { if (masterKey) {
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey); const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey); await this.cryptoService.setUserKey(userKey);

View File

@ -9,6 +9,7 @@ import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/a
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
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";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@ -20,7 +21,9 @@ 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, mockAccountServiceWith } from "@bitwarden/common/spec";
import { CsprngArray } from "@bitwarden/common/types/csprng"; import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import { DeviceKey, UserKey, MasterKey } from "@bitwarden/common/types/key"; import { DeviceKey, UserKey, MasterKey } from "@bitwarden/common/types/key";
import { import {
@ -33,6 +36,9 @@ import { identityTokenResponseFactory } from "./login.strategy.spec";
import { SsoLoginStrategy } from "./sso-login.strategy"; import { SsoLoginStrategy } from "./sso-login.strategy";
describe("SsoLoginStrategy", () => { describe("SsoLoginStrategy", () => {
let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
let cryptoService: MockProxy<CryptoService>; let cryptoService: MockProxy<CryptoService>;
let apiService: MockProxy<ApiService>; let apiService: MockProxy<ApiService>;
let tokenService: MockProxy<TokenService>; let tokenService: MockProxy<TokenService>;
@ -52,6 +58,7 @@ describe("SsoLoginStrategy", () => {
let ssoLoginStrategy: SsoLoginStrategy; let ssoLoginStrategy: SsoLoginStrategy;
let credentials: SsoLoginCredentials; let credentials: SsoLoginCredentials;
const userId = Utils.newGuid() as UserId;
const deviceId = Utils.newGuid(); const deviceId = Utils.newGuid();
const keyConnectorUrl = "KEY_CONNECTOR_URL"; const keyConnectorUrl = "KEY_CONNECTOR_URL";
@ -61,6 +68,9 @@ describe("SsoLoginStrategy", () => {
const ssoOrgId = "SSO_ORG_ID"; const ssoOrgId = "SSO_ORG_ID";
beforeEach(async () => { beforeEach(async () => {
accountService = mockAccountServiceWith(userId);
masterPasswordService = new FakeMasterPasswordService();
cryptoService = mock<CryptoService>(); cryptoService = mock<CryptoService>();
apiService = mock<ApiService>(); apiService = mock<ApiService>();
tokenService = mock<TokenService>(); tokenService = mock<TokenService>();
@ -83,6 +93,8 @@ describe("SsoLoginStrategy", () => {
ssoLoginStrategy = new SsoLoginStrategy( ssoLoginStrategy = new SsoLoginStrategy(
null, null,
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@ -130,7 +142,7 @@ describe("SsoLoginStrategy", () => {
await ssoLoginStrategy.logIn(credentials); await ssoLoginStrategy.logIn(credentials);
expect(cryptoService.setMasterKey).not.toHaveBeenCalled(); expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled();
expect(cryptoService.setUserKey).not.toHaveBeenCalled(); expect(cryptoService.setUserKey).not.toHaveBeenCalled();
expect(cryptoService.setPrivateKey).not.toHaveBeenCalled(); expect(cryptoService.setPrivateKey).not.toHaveBeenCalled();
}); });
@ -395,7 +407,7 @@ describe("SsoLoginStrategy", () => {
) as MasterKey; ) as MasterKey;
apiService.postIdentityToken.mockResolvedValue(tokenResponse); apiService.postIdentityToken.mockResolvedValue(tokenResponse);
cryptoService.getMasterKey.mockResolvedValue(masterKey); masterPasswordService.masterKeySubject.next(masterKey);
await ssoLoginStrategy.logIn(credentials); await ssoLoginStrategy.logIn(credentials);
@ -422,7 +434,7 @@ describe("SsoLoginStrategy", () => {
) as MasterKey; ) as MasterKey;
apiService.postIdentityToken.mockResolvedValue(tokenResponse); apiService.postIdentityToken.mockResolvedValue(tokenResponse);
cryptoService.getMasterKey.mockResolvedValue(masterKey); masterPasswordService.masterKeySubject.next(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey); cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
await ssoLoginStrategy.logIn(credentials); await ssoLoginStrategy.logIn(credentials);
@ -446,7 +458,7 @@ describe("SsoLoginStrategy", () => {
) as MasterKey; ) as MasterKey;
apiService.postIdentityToken.mockResolvedValue(tokenResponse); apiService.postIdentityToken.mockResolvedValue(tokenResponse);
cryptoService.getMasterKey.mockResolvedValue(masterKey); masterPasswordService.masterKeySubject.next(masterKey);
await ssoLoginStrategy.logIn(credentials); await ssoLoginStrategy.logIn(credentials);
@ -473,7 +485,7 @@ describe("SsoLoginStrategy", () => {
) as MasterKey; ) as MasterKey;
apiService.postIdentityToken.mockResolvedValue(tokenResponse); apiService.postIdentityToken.mockResolvedValue(tokenResponse);
cryptoService.getMasterKey.mockResolvedValue(masterKey); masterPasswordService.masterKeySubject.next(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey); cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
await ssoLoginStrategy.logIn(credentials); await ssoLoginStrategy.logIn(credentials);

View File

@ -1,9 +1,11 @@
import { Observable, map, BehaviorSubject } from "rxjs"; import { firstValueFrom, Observable, map, BehaviorSubject } from "rxjs";
import { Jsonify } from "type-fest"; 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 { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
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 { 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 { 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 +81,8 @@ export class SsoLoginStrategy extends LoginStrategy {
constructor( constructor(
data: SsoLoginStrategyData, data: SsoLoginStrategyData,
accountService: AccountService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
cryptoService: CryptoService, cryptoService: CryptoService,
apiService: ApiService, apiService: ApiService,
tokenService: TokenService, tokenService: TokenService,
@ -96,6 +100,8 @@ export class SsoLoginStrategy extends LoginStrategy {
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@ -138,7 +144,11 @@ export class SsoLoginStrategy extends LoginStrategy {
// Auth guard currently handles redirects for this. // Auth guard currently handles redirects for this.
if (ssoAuthResult.forcePasswordReset == ForceSetPasswordReason.AdminForcePasswordReset) { if (ssoAuthResult.forcePasswordReset == ForceSetPasswordReason.AdminForcePasswordReset) {
await this.stateService.setForceSetPasswordReason(ssoAuthResult.forcePasswordReset); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(
ssoAuthResult.forcePasswordReset,
userId,
);
} }
this.cache.next({ this.cache.next({
@ -323,7 +333,8 @@ export class SsoLoginStrategy extends LoginStrategy {
} }
private async trySetUserKeyWithMasterKey(): Promise<void> { private async trySetUserKeyWithMasterKey(): Promise<void> {
const masterKey = await this.cryptoService.getMasterKey(); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
// There is a scenario in which the master key is not set here. That will occur if the user // There is a scenario in which the master key is not set here. That will occur if the user
// has a master password and is using Key Connector. In that case, we cannot set the master key // has a master password and is using Key Connector. In that case, we cannot set the master key

View File

@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.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";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
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";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@ -19,7 +20,9 @@ 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, mockAccountServiceWith } from "@bitwarden/common/spec";
import { CsprngArray } from "@bitwarden/common/types/csprng"; import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import { UserKey, MasterKey } from "@bitwarden/common/types/key"; import { UserKey, MasterKey } from "@bitwarden/common/types/key";
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
@ -30,6 +33,8 @@ import { UserApiLoginStrategy, UserApiLoginStrategyData } from "./user-api-login
describe("UserApiLoginStrategy", () => { describe("UserApiLoginStrategy", () => {
let cache: UserApiLoginStrategyData; let cache: UserApiLoginStrategyData;
let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
let cryptoService: MockProxy<CryptoService>; let cryptoService: MockProxy<CryptoService>;
let apiService: MockProxy<ApiService>; let apiService: MockProxy<ApiService>;
@ -48,12 +53,16 @@ describe("UserApiLoginStrategy", () => {
let apiLogInStrategy: UserApiLoginStrategy; let apiLogInStrategy: UserApiLoginStrategy;
let credentials: UserApiLoginCredentials; let credentials: UserApiLoginCredentials;
const userId = Utils.newGuid() as UserId;
const deviceId = Utils.newGuid(); const deviceId = Utils.newGuid();
const keyConnectorUrl = "KEY_CONNECTOR_URL"; const keyConnectorUrl = "KEY_CONNECTOR_URL";
const apiClientId = "API_CLIENT_ID"; const apiClientId = "API_CLIENT_ID";
const apiClientSecret = "API_CLIENT_SECRET"; const apiClientSecret = "API_CLIENT_SECRET";
beforeEach(async () => { beforeEach(async () => {
accountService = mockAccountServiceWith(userId);
masterPasswordService = new FakeMasterPasswordService();
cryptoService = mock<CryptoService>(); cryptoService = mock<CryptoService>();
apiService = mock<ApiService>(); apiService = mock<ApiService>();
tokenService = mock<TokenService>(); tokenService = mock<TokenService>();
@ -74,6 +83,8 @@ describe("UserApiLoginStrategy", () => {
apiLogInStrategy = new UserApiLoginStrategy( apiLogInStrategy = new UserApiLoginStrategy(
cache, cache,
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@ -172,7 +183,7 @@ describe("UserApiLoginStrategy", () => {
environmentService.environment$ = new BehaviorSubject(env); environmentService.environment$ = new BehaviorSubject(env);
apiService.postIdentityToken.mockResolvedValue(tokenResponse); apiService.postIdentityToken.mockResolvedValue(tokenResponse);
cryptoService.getMasterKey.mockResolvedValue(masterKey); masterPasswordService.masterKeySubject.next(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey); cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
await apiLogInStrategy.logIn(credentials); await apiLogInStrategy.logIn(credentials);

View File

@ -2,7 +2,9 @@ import { firstValueFrom, BehaviorSubject } from "rxjs";
import { Jsonify } from "type-fest"; 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 { 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 { 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 { UserApiTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/user-api-token.request"; import { UserApiTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/user-api-token.request";
@ -39,6 +41,8 @@ export class UserApiLoginStrategy extends LoginStrategy {
constructor( constructor(
data: UserApiLoginStrategyData, data: UserApiLoginStrategyData,
accountService: AccountService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
cryptoService: CryptoService, cryptoService: CryptoService,
apiService: ApiService, apiService: ApiService,
tokenService: TokenService, tokenService: TokenService,
@ -54,6 +58,8 @@ export class UserApiLoginStrategy extends LoginStrategy {
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@ -95,7 +101,8 @@ export class UserApiLoginStrategy extends LoginStrategy {
await this.cryptoService.setMasterKeyEncryptedUserKey(response.key); await this.cryptoService.setMasterKeyEncryptedUserKey(response.key);
if (response.apiUseKeyConnector) { if (response.apiUseKeyConnector) {
const masterKey = await this.cryptoService.getMasterKey(); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
if (masterKey) { if (masterKey) {
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey); const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey); await this.cryptoService.setUserKey(userKey);

View File

@ -6,6 +6,7 @@ import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request"; import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request";
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";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@ -16,6 +17,7 @@ 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 { 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";
@ -26,6 +28,8 @@ import { WebAuthnLoginStrategy, WebAuthnLoginStrategyData } from "./webauthn-log
describe("WebAuthnLoginStrategy", () => { describe("WebAuthnLoginStrategy", () => {
let cache: WebAuthnLoginStrategyData; let cache: WebAuthnLoginStrategyData;
let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
let cryptoService!: MockProxy<CryptoService>; let cryptoService!: MockProxy<CryptoService>;
let apiService!: MockProxy<ApiService>; let apiService!: MockProxy<ApiService>;
@ -63,6 +67,9 @@ describe("WebAuthnLoginStrategy", () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
accountService = new FakeAccountService(null);
masterPasswordService = new FakeMasterPasswordService();
cryptoService = mock<CryptoService>(); cryptoService = mock<CryptoService>();
apiService = mock<ApiService>(); apiService = mock<ApiService>();
tokenService = mock<TokenService>(); tokenService = mock<TokenService>();
@ -81,6 +88,8 @@ describe("WebAuthnLoginStrategy", () => {
webAuthnLoginStrategy = new WebAuthnLoginStrategy( webAuthnLoginStrategy = new WebAuthnLoginStrategy(
cache, cache,
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@ -207,7 +216,7 @@ describe("WebAuthnLoginStrategy", () => {
expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(idTokenResponse.privateKey); expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(idTokenResponse.privateKey);
// Master key and private key should not be set // Master key and private key should not be set
expect(cryptoService.setMasterKey).not.toHaveBeenCalled(); expect(masterPasswordService.mock.setMasterKey).not.toHaveBeenCalled();
}); });
it("does not try to set the user key when prfKey is missing", async () => { it("does not try to set the user key when prfKey is missing", async () => {

View File

@ -2,6 +2,8 @@ import { BehaviorSubject } from "rxjs";
import { Jsonify } from "type-fest"; 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 { 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 { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
@ -41,6 +43,8 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
constructor( constructor(
data: WebAuthnLoginStrategyData, data: WebAuthnLoginStrategyData,
accountService: AccountService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
cryptoService: CryptoService, cryptoService: CryptoService,
apiService: ApiService, apiService: ApiService,
tokenService: TokenService, tokenService: TokenService,
@ -54,6 +58,8 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,

View File

@ -2,13 +2,15 @@ import { mock } from "jest-mock-extended";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response"; import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
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 { 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 { 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 { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { AuthRequestService } from "./auth-request.service"; import { AuthRequestService } from "./auth-request.service";
@ -16,17 +18,27 @@ import { AuthRequestService } from "./auth-request.service";
describe("AuthRequestService", () => { describe("AuthRequestService", () => {
let sut: AuthRequestService; let sut: AuthRequestService;
let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
const appIdService = mock<AppIdService>(); const appIdService = mock<AppIdService>();
const cryptoService = mock<CryptoService>(); const cryptoService = mock<CryptoService>();
const apiService = mock<ApiService>(); const apiService = mock<ApiService>();
const stateService = mock<StateService>();
let mockPrivateKey: Uint8Array; let mockPrivateKey: Uint8Array;
const mockUserId = Utils.newGuid() as UserId;
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
accountService = mockAccountServiceWith(mockUserId);
masterPasswordService = new FakeMasterPasswordService();
sut = new AuthRequestService(appIdService, cryptoService, apiService, stateService); sut = new AuthRequestService(
appIdService,
accountService,
masterPasswordService,
cryptoService,
apiService,
);
mockPrivateKey = new Uint8Array(64); mockPrivateKey = new Uint8Array(64);
}); });
@ -67,8 +79,8 @@ describe("AuthRequestService", () => {
}); });
it("should use the master key and hash if they exist", async () => { it("should use the master key and hash if they exist", async () => {
cryptoService.getMasterKey.mockResolvedValueOnce({ encKey: new Uint8Array(64) } as MasterKey); masterPasswordService.masterKeySubject.next({ encKey: new Uint8Array(64) } as MasterKey);
stateService.getKeyHash.mockResolvedValueOnce("KEY_HASH"); masterPasswordService.masterKeyHashSubject.next("MASTER_KEY_HASH");
await sut.approveOrDenyAuthRequest( await sut.approveOrDenyAuthRequest(
true, true,
@ -130,8 +142,8 @@ describe("AuthRequestService", () => {
masterKeyHash: mockDecryptedMasterKeyHash, masterKeyHash: mockDecryptedMasterKeyHash,
}); });
cryptoService.setMasterKey.mockResolvedValueOnce(undefined); masterPasswordService.masterKeySubject.next(undefined);
cryptoService.setMasterKeyHash.mockResolvedValueOnce(undefined); masterPasswordService.masterKeyHashSubject.next(undefined);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValueOnce(mockDecryptedUserKey); cryptoService.decryptUserKeyWithMasterKey.mockResolvedValueOnce(mockDecryptedUserKey);
cryptoService.setUserKey.mockResolvedValueOnce(undefined); cryptoService.setUserKey.mockResolvedValueOnce(undefined);
@ -144,10 +156,18 @@ describe("AuthRequestService", () => {
mockAuthReqResponse.masterPasswordHash, mockAuthReqResponse.masterPasswordHash,
mockPrivateKey, mockPrivateKey,
); );
expect(cryptoService.setMasterKey).toBeCalledWith(mockDecryptedMasterKey); expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(
expect(cryptoService.setMasterKeyHash).toBeCalledWith(mockDecryptedMasterKeyHash); mockDecryptedMasterKey,
expect(cryptoService.decryptUserKeyWithMasterKey).toBeCalledWith(mockDecryptedMasterKey); mockUserId,
expect(cryptoService.setUserKey).toBeCalledWith(mockDecryptedUserKey); );
expect(masterPasswordService.mock.setMasterKeyHash).toHaveBeenCalledWith(
mockDecryptedMasterKeyHash,
mockUserId,
);
expect(cryptoService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(
mockDecryptedMasterKey,
);
expect(cryptoService.setUserKey).toHaveBeenCalledWith(mockDecryptedUserKey);
}); });
}); });

View File

@ -1,12 +1,13 @@
import { Observable, Subject } from "rxjs"; import { firstValueFrom, Observable, Subject } 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 { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
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";
import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response"; import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
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 { 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 { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { MasterKey, UserKey } from "@bitwarden/common/types/key";
@ -19,9 +20,10 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
constructor( constructor(
private appIdService: AppIdService, private appIdService: AppIdService,
private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private apiService: ApiService, private apiService: ApiService,
private stateService: StateService,
) { ) {
this.authRequestPushNotification$ = this.authRequestPushNotificationSubject.asObservable(); this.authRequestPushNotification$ = this.authRequestPushNotificationSubject.asObservable();
} }
@ -38,8 +40,9 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
} }
const pubKey = Utils.fromB64ToArray(authRequest.publicKey); const pubKey = Utils.fromB64ToArray(authRequest.publicKey);
const masterKey = await this.cryptoService.getMasterKey(); const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
const masterKeyHash = await this.stateService.getKeyHash(); const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
const masterKeyHash = await firstValueFrom(this.masterPasswordService.masterKeyHash$(userId));
let encryptedMasterKeyHash; let encryptedMasterKeyHash;
let keyToEncrypt; let keyToEncrypt;
@ -92,8 +95,9 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey); const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
// Set masterKey + masterKeyHash in state after decryption (in case decryption fails) // Set masterKey + masterKeyHash in state after decryption (in case decryption fails)
await this.cryptoService.setMasterKey(masterKey); const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
await this.cryptoService.setMasterKeyHash(masterKeyHash); await this.masterPasswordService.setMasterKey(masterKey, userId);
await this.masterPasswordService.setMasterKeyHash(masterKeyHash, userId);
await this.cryptoService.setUserKey(userKey); await this.cryptoService.setUserKey(userKey);
} }

View File

@ -11,6 +11,7 @@ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
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 { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
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";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -22,8 +23,14 @@ 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 { KdfType } from "@bitwarden/common/platform/enums"; import { KdfType } from "@bitwarden/common/platform/enums";
import { FakeGlobalState, FakeGlobalStateProvider } from "@bitwarden/common/spec"; import {
FakeAccountService,
FakeGlobalState,
FakeGlobalStateProvider,
mockAccountServiceWith,
} from "@bitwarden/common/spec";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { UserId } from "@bitwarden/common/types/guid";
import { import {
AuthRequestServiceAbstraction, AuthRequestServiceAbstraction,
@ -38,6 +45,8 @@ import { CACHE_EXPIRATION_KEY } from "./login-strategy.state";
describe("LoginStrategyService", () => { describe("LoginStrategyService", () => {
let sut: LoginStrategyService; let sut: LoginStrategyService;
let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
let cryptoService: MockProxy<CryptoService>; let cryptoService: MockProxy<CryptoService>;
let apiService: MockProxy<ApiService>; let apiService: MockProxy<ApiService>;
let tokenService: MockProxy<TokenService>; let tokenService: MockProxy<TokenService>;
@ -61,7 +70,11 @@ describe("LoginStrategyService", () => {
let stateProvider: FakeGlobalStateProvider; let stateProvider: FakeGlobalStateProvider;
let loginStrategyCacheExpirationState: FakeGlobalState<Date | null>; let loginStrategyCacheExpirationState: FakeGlobalState<Date | null>;
const userId = "USER_ID" as UserId;
beforeEach(() => { beforeEach(() => {
accountService = mockAccountServiceWith(userId);
masterPasswordService = new FakeMasterPasswordService();
cryptoService = mock<CryptoService>(); cryptoService = mock<CryptoService>();
apiService = mock<ApiService>(); apiService = mock<ApiService>();
tokenService = mock<TokenService>(); tokenService = mock<TokenService>();
@ -84,6 +97,8 @@ describe("LoginStrategyService", () => {
stateProvider = new FakeGlobalStateProvider(); stateProvider = new FakeGlobalStateProvider();
sut = new LoginStrategyService( sut = new LoginStrategyService(
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,

View File

@ -9,8 +9,10 @@ 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 { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
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 { 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";
@ -81,6 +83,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
currentAuthType$: Observable<AuthenticationType | null>; currentAuthType$: Observable<AuthenticationType | null>;
constructor( constructor(
protected accountService: AccountService,
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected cryptoService: CryptoService, protected cryptoService: CryptoService,
protected apiService: ApiService, protected apiService: ApiService,
protected tokenService: TokenService, protected tokenService: TokenService,
@ -257,7 +261,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
): Promise<AuthRequestResponse> { ): Promise<AuthRequestResponse> {
const pubKey = Utils.fromB64ToArray(key); const pubKey = Utils.fromB64ToArray(key);
const masterKey = await this.cryptoService.getMasterKey(); const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
let keyToEncrypt; let keyToEncrypt;
let encryptedMasterKeyHash = null; let encryptedMasterKeyHash = null;
@ -266,7 +271,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
// Only encrypt the master password hash if masterKey exists as // Only encrypt the master password hash if masterKey exists as
// we won't have a masterKeyHash without a masterKey // we won't have a masterKeyHash without a masterKey
const masterKeyHash = await this.stateService.getKeyHash(); const masterKeyHash = await firstValueFrom(this.masterPasswordService.masterKeyHash$(userId));
if (masterKeyHash != null) { if (masterKeyHash != null) {
encryptedMasterKeyHash = await this.cryptoService.rsaEncrypt( encryptedMasterKeyHash = await this.cryptoService.rsaEncrypt(
Utils.fromUtf8ToArray(masterKeyHash), Utils.fromUtf8ToArray(masterKeyHash),
@ -333,6 +338,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
case AuthenticationType.Password: case AuthenticationType.Password:
return new PasswordLoginStrategy( return new PasswordLoginStrategy(
data?.password, data?.password,
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.tokenService, this.tokenService,
@ -351,6 +358,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
case AuthenticationType.Sso: case AuthenticationType.Sso:
return new SsoLoginStrategy( return new SsoLoginStrategy(
data?.sso, data?.sso,
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.tokenService, this.tokenService,
@ -370,6 +379,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
case AuthenticationType.UserApiKey: case AuthenticationType.UserApiKey:
return new UserApiLoginStrategy( return new UserApiLoginStrategy(
data?.userApiKey, data?.userApiKey,
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.tokenService, this.tokenService,
@ -387,6 +398,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
case AuthenticationType.AuthRequest: case AuthenticationType.AuthRequest:
return new AuthRequestLoginStrategy( return new AuthRequestLoginStrategy(
data?.authRequest, data?.authRequest,
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.tokenService, this.tokenService,
@ -403,6 +416,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
case AuthenticationType.WebAuthn: case AuthenticationType.WebAuthn:
return new WebAuthnLoginStrategy( return new WebAuthnLoginStrategy(
data?.webAuthn, data?.webAuthn,
this.accountService,
this.masterPasswordService,
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
this.tokenService, this.tokenService,

View File

@ -0,0 +1,82 @@
import { Observable } from "rxjs";
import { EncString } from "../../platform/models/domain/enc-string";
import { UserId } from "../../types/guid";
import { MasterKey } from "../../types/key";
import { ForceSetPasswordReason } from "../models/domain/force-set-password-reason";
export abstract class MasterPasswordServiceAbstraction {
/**
* An observable that emits if the user is being forced to set a password on login and why.
* @param userId The user ID.
* @throws If the user ID is missing.
*/
abstract forceSetPasswordReason$: (userId: UserId) => Observable<ForceSetPasswordReason>;
/**
* An observable that emits the master key for the user.
* @param userId The user ID.
* @throws If the user ID is missing.
*/
abstract masterKey$: (userId: UserId) => Observable<MasterKey>;
/**
* An observable that emits the master key hash for the user.
* @param userId The user ID.
* @throws If the user ID is missing.
*/
abstract masterKeyHash$: (userId: UserId) => Observable<string>;
/**
* Returns the master key encrypted user key for the user.
* @param userId The user ID.
* @throws If the user ID is missing.
*/
abstract getMasterKeyEncryptedUserKey: (userId: UserId) => Promise<EncString>;
}
export abstract class InternalMasterPasswordServiceAbstraction extends MasterPasswordServiceAbstraction {
/**
* Set the master key for the user.
* Note: Use {@link clearMasterKey} to clear the master key.
* @param masterKey The master key.
* @param userId The user ID.
* @throws If the user ID or master key is missing.
*/
abstract setMasterKey: (masterKey: MasterKey, userId: UserId) => Promise<void>;
/**
* Clear the master key for the user.
* @param userId The user ID.
* @throws If the user ID is missing.
*/
abstract clearMasterKey: (userId: UserId) => Promise<void>;
/**
* Set the master key hash for the user.
* Note: Use {@link clearMasterKeyHash} to clear the master key hash.
* @param masterKeyHash The master key hash.
* @param userId The user ID.
* @throws If the user ID or master key hash is missing.
*/
abstract setMasterKeyHash: (masterKeyHash: string, userId: UserId) => Promise<void>;
/**
* Clear the master key hash for the user.
* @param userId The user ID.
* @throws If the user ID is missing.
*/
abstract clearMasterKeyHash: (userId: UserId) => Promise<void>;
/**
* Set the master key encrypted user key for the user.
* @param encryptedKey The master key encrypted user key.
* @param userId The user ID.
* @throws If the user ID or encrypted key is missing.
*/
abstract setMasterKeyEncryptedUserKey: (encryptedKey: EncString, userId: UserId) => Promise<void>;
/**
* Set the force set password reason for the user.
* @param reason The reason the user is being forced to set a password.
* @param userId The user ID.
* @throws If the user ID or reason is missing.
*/
abstract setForceSetPasswordReason: (
reason: ForceSetPasswordReason,
userId: UserId,
) => Promise<void>;
}

View File

@ -21,6 +21,7 @@ import {
CONVERT_ACCOUNT_TO_KEY_CONNECTOR, CONVERT_ACCOUNT_TO_KEY_CONNECTOR,
KeyConnectorService, KeyConnectorService,
} from "./key-connector.service"; } from "./key-connector.service";
import { FakeMasterPasswordService } from "./master-password/fake-master-password.service";
import { TokenService } from "./token.service"; import { TokenService } from "./token.service";
describe("KeyConnectorService", () => { describe("KeyConnectorService", () => {
@ -36,6 +37,7 @@ describe("KeyConnectorService", () => {
let stateProvider: FakeStateProvider; let stateProvider: FakeStateProvider;
let accountService: FakeAccountService; let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
const mockUserId = Utils.newGuid() as UserId; const mockUserId = Utils.newGuid() as UserId;
const mockOrgId = Utils.newGuid() as OrganizationId; const mockOrgId = Utils.newGuid() as OrganizationId;
@ -47,10 +49,13 @@ describe("KeyConnectorService", () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
masterPasswordService = new FakeMasterPasswordService();
accountService = mockAccountServiceWith(mockUserId); accountService = mockAccountServiceWith(mockUserId);
stateProvider = new FakeStateProvider(accountService); stateProvider = new FakeStateProvider(accountService);
keyConnectorService = new KeyConnectorService( keyConnectorService = new KeyConnectorService(
accountService,
masterPasswordService,
cryptoService, cryptoService,
apiService, apiService,
tokenService, tokenService,
@ -214,7 +219,10 @@ describe("KeyConnectorService", () => {
// Assert // Assert
expect(apiService.getMasterKeyFromKeyConnector).toHaveBeenCalledWith(url); expect(apiService.getMasterKeyFromKeyConnector).toHaveBeenCalledWith(url);
expect(cryptoService.setMasterKey).toHaveBeenCalledWith(masterKey); expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(
masterKey,
expect.any(String),
);
}); });
it("should handle errors thrown during the process", async () => { it("should handle errors thrown during the process", async () => {
@ -241,10 +249,10 @@ describe("KeyConnectorService", () => {
// Arrange // Arrange
const organization = organizationData(true, true, "https://key-connector-url.com", 2, false); const organization = organizationData(true, true, "https://key-connector-url.com", 2, false);
const masterKey = getMockMasterKey(); const masterKey = getMockMasterKey();
masterPasswordService.masterKeySubject.next(masterKey);
const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64);
jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization); jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization);
jest.spyOn(cryptoService, "getMasterKey").mockResolvedValue(masterKey);
jest.spyOn(apiService, "postUserKeyToKeyConnector").mockResolvedValue(); jest.spyOn(apiService, "postUserKeyToKeyConnector").mockResolvedValue();
// Act // Act
@ -252,7 +260,6 @@ describe("KeyConnectorService", () => {
// Assert // Assert
expect(keyConnectorService.getManagingOrganization).toHaveBeenCalled(); expect(keyConnectorService.getManagingOrganization).toHaveBeenCalled();
expect(cryptoService.getMasterKey).toHaveBeenCalled();
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith( expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
organization.keyConnectorUrl, organization.keyConnectorUrl,
keyConnectorRequest, keyConnectorRequest,
@ -268,8 +275,8 @@ describe("KeyConnectorService", () => {
const error = new Error("Failed to post user key to key connector"); const error = new Error("Failed to post user key to key connector");
organizationService.getAll.mockResolvedValue([organization]); organizationService.getAll.mockResolvedValue([organization]);
masterPasswordService.masterKeySubject.next(masterKey);
jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization); jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization);
jest.spyOn(cryptoService, "getMasterKey").mockResolvedValue(masterKey);
jest.spyOn(apiService, "postUserKeyToKeyConnector").mockRejectedValue(error); jest.spyOn(apiService, "postUserKeyToKeyConnector").mockRejectedValue(error);
jest.spyOn(logService, "error"); jest.spyOn(logService, "error");
@ -280,7 +287,6 @@ describe("KeyConnectorService", () => {
// Assert // Assert
expect(logService.error).toHaveBeenCalledWith(error); expect(logService.error).toHaveBeenCalledWith(error);
expect(keyConnectorService.getManagingOrganization).toHaveBeenCalled(); expect(keyConnectorService.getManagingOrganization).toHaveBeenCalled();
expect(cryptoService.getMasterKey).toHaveBeenCalled();
expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith( expect(apiService.postUserKeyToKeyConnector).toHaveBeenCalledWith(
organization.keyConnectorUrl, organization.keyConnectorUrl,
keyConnectorRequest, keyConnectorRequest,

View File

@ -16,7 +16,9 @@ import {
UserKeyDefinition, UserKeyDefinition,
} from "../../platform/state"; } from "../../platform/state";
import { MasterKey } from "../../types/key"; import { MasterKey } from "../../types/key";
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 { TokenService } from "../abstractions/token.service"; import { TokenService } from "../abstractions/token.service";
import { KdfConfig } from "../models/domain/kdf-config"; import { KdfConfig } 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";
@ -45,6 +47,8 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
private usesKeyConnectorState: ActiveUserState<boolean>; private usesKeyConnectorState: ActiveUserState<boolean>;
private convertAccountToKeyConnectorState: ActiveUserState<boolean>; private convertAccountToKeyConnectorState: ActiveUserState<boolean>;
constructor( constructor(
private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private apiService: ApiService, private apiService: ApiService,
private tokenService: TokenService, private tokenService: TokenService,
@ -78,7 +82,8 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
async migrateUser() { async migrateUser() {
const organization = await this.getManagingOrganization(); const organization = await this.getManagingOrganization();
const masterKey = await this.cryptoService.getMasterKey(); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64);
try { try {
@ -99,7 +104,8 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
const masterKeyResponse = await this.apiService.getMasterKeyFromKeyConnector(url); const masterKeyResponse = await this.apiService.getMasterKeyFromKeyConnector(url);
const keyArr = Utils.fromB64ToArray(masterKeyResponse.key); const keyArr = Utils.fromB64ToArray(masterKeyResponse.key);
const masterKey = new SymmetricCryptoKey(keyArr) as MasterKey; const masterKey = new SymmetricCryptoKey(keyArr) as MasterKey;
await this.cryptoService.setMasterKey(masterKey); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setMasterKey(masterKey, userId);
} catch (e) { } catch (e) {
this.handleKeyConnectorError(e); this.handleKeyConnectorError(e);
} }
@ -136,7 +142,8 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
kdfConfig, kdfConfig,
); );
const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64);
await this.cryptoService.setMasterKey(masterKey); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setMasterKey(masterKey, userId);
const userKey = await this.cryptoService.makeUserKey(masterKey); const userKey = await this.cryptoService.makeUserKey(masterKey);
await this.cryptoService.setUserKey(userKey[0]); await this.cryptoService.setUserKey(userKey[0]);

View File

@ -0,0 +1,64 @@
import { mock } from "jest-mock-extended";
import { ReplaySubject, Observable } from "rxjs";
import { EncString } from "../../../platform/models/domain/enc-string";
import { UserId } from "../../../types/guid";
import { MasterKey } from "../../../types/key";
import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction";
import { ForceSetPasswordReason } from "../../models/domain/force-set-password-reason";
export class FakeMasterPasswordService implements InternalMasterPasswordServiceAbstraction {
mock = mock<InternalMasterPasswordServiceAbstraction>();
// eslint-disable-next-line rxjs/no-exposed-subjects -- test class
masterKeySubject = new ReplaySubject<MasterKey>(1);
// eslint-disable-next-line rxjs/no-exposed-subjects -- test class
masterKeyHashSubject = new ReplaySubject<string>(1);
// eslint-disable-next-line rxjs/no-exposed-subjects -- test class
forceSetPasswordReasonSubject = new ReplaySubject<ForceSetPasswordReason>(1);
constructor(initialMasterKey?: MasterKey, initialMasterKeyHash?: string) {
this.masterKeySubject.next(initialMasterKey);
this.masterKeyHashSubject.next(initialMasterKeyHash);
}
masterKey$(userId: UserId): Observable<MasterKey> {
return this.masterKeySubject.asObservable();
}
setMasterKey(masterKey: MasterKey, userId: UserId): Promise<void> {
return this.mock.setMasterKey(masterKey, userId);
}
clearMasterKey(userId: UserId): Promise<void> {
return this.mock.clearMasterKey(userId);
}
masterKeyHash$(userId: UserId): Observable<string> {
return this.masterKeyHashSubject.asObservable();
}
getMasterKeyEncryptedUserKey(userId: UserId): Promise<EncString> {
return this.mock.getMasterKeyEncryptedUserKey(userId);
}
setMasterKeyEncryptedUserKey(encryptedKey: EncString, userId: UserId): Promise<void> {
return this.mock.setMasterKeyEncryptedUserKey(encryptedKey, userId);
}
setMasterKeyHash(masterKeyHash: string, userId: UserId): Promise<void> {
return this.mock.setMasterKeyHash(masterKeyHash, userId);
}
clearMasterKeyHash(userId: UserId): Promise<void> {
return this.mock.clearMasterKeyHash(userId);
}
forceSetPasswordReason$(userId: UserId): Observable<ForceSetPasswordReason> {
return this.forceSetPasswordReasonSubject.asObservable();
}
setForceSetPasswordReason(reason: ForceSetPasswordReason, userId: UserId): Promise<void> {
return this.mock.setForceSetPasswordReason(reason, userId);
}
}

View File

@ -0,0 +1,140 @@
import { firstValueFrom, map, Observable } from "rxjs";
import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import {
MASTER_PASSWORD_DISK,
MASTER_PASSWORD_MEMORY,
StateProvider,
UserKeyDefinition,
} from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { MasterKey } from "../../../types/key";
import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction";
import { ForceSetPasswordReason } from "../../models/domain/force-set-password-reason";
/** Memory since master key shouldn't be available on lock */
const MASTER_KEY = new UserKeyDefinition<MasterKey>(MASTER_PASSWORD_MEMORY, "masterKey", {
deserializer: (masterKey) => SymmetricCryptoKey.fromJSON(masterKey) as MasterKey,
clearOn: ["lock", "logout"],
});
/** Disk since master key hash is used for unlock */
const MASTER_KEY_HASH = new UserKeyDefinition<string>(MASTER_PASSWORD_DISK, "masterKeyHash", {
deserializer: (masterKeyHash) => masterKeyHash,
clearOn: ["logout"],
});
/** Disk to persist through lock */
const MASTER_KEY_ENCRYPTED_USER_KEY = new UserKeyDefinition<EncryptedString>(
MASTER_PASSWORD_DISK,
"masterKeyEncryptedUserKey",
{
deserializer: (key) => key,
clearOn: ["logout"],
},
);
/** Disk to persist through lock and account switches */
const FORCE_SET_PASSWORD_REASON = new UserKeyDefinition<ForceSetPasswordReason>(
MASTER_PASSWORD_DISK,
"forceSetPasswordReason",
{
deserializer: (reason) => reason,
clearOn: ["logout"],
},
);
export class MasterPasswordService implements InternalMasterPasswordServiceAbstraction {
constructor(private stateProvider: StateProvider) {}
masterKey$(userId: UserId): Observable<MasterKey> {
if (userId == null) {
throw new Error("User ID is required.");
}
return this.stateProvider.getUser(userId, MASTER_KEY).state$;
}
masterKeyHash$(userId: UserId): Observable<string> {
if (userId == null) {
throw new Error("User ID is required.");
}
return this.stateProvider.getUser(userId, MASTER_KEY_HASH).state$;
}
forceSetPasswordReason$(userId: UserId): Observable<ForceSetPasswordReason> {
if (userId == null) {
throw new Error("User ID is required.");
}
return this.stateProvider
.getUser(userId, FORCE_SET_PASSWORD_REASON)
.state$.pipe(map((reason) => reason ?? ForceSetPasswordReason.None));
}
// TODO: Remove this method and decrypt directly in the service instead
async getMasterKeyEncryptedUserKey(userId: UserId): Promise<EncString> {
if (userId == null) {
throw new Error("User ID is required.");
}
const key = await firstValueFrom(
this.stateProvider.getUser(userId, MASTER_KEY_ENCRYPTED_USER_KEY).state$,
);
return EncString.fromJSON(key);
}
async setMasterKey(masterKey: MasterKey, userId: UserId): Promise<void> {
if (masterKey == null) {
throw new Error("Master key is required.");
}
if (userId == null) {
throw new Error("User ID is required.");
}
await this.stateProvider.getUser(userId, MASTER_KEY).update((_) => masterKey);
}
async clearMasterKey(userId: UserId): Promise<void> {
if (userId == null) {
throw new Error("User ID is required.");
}
await this.stateProvider.getUser(userId, MASTER_KEY).update((_) => null);
}
async setMasterKeyHash(masterKeyHash: string, userId: UserId): Promise<void> {
if (masterKeyHash == null) {
throw new Error("Master key hash is required.");
}
if (userId == null) {
throw new Error("User ID is required.");
}
await this.stateProvider.getUser(userId, MASTER_KEY_HASH).update((_) => masterKeyHash);
}
async clearMasterKeyHash(userId: UserId): Promise<void> {
if (userId == null) {
throw new Error("User ID is required.");
}
await this.stateProvider.getUser(userId, MASTER_KEY_HASH).update((_) => null);
}
async setMasterKeyEncryptedUserKey(encryptedKey: EncString, userId: UserId): Promise<void> {
if (encryptedKey == null) {
throw new Error("Encrypted Key is required.");
}
if (userId == null) {
throw new Error("User ID is required.");
}
await this.stateProvider
.getUser(userId, MASTER_KEY_ENCRYPTED_USER_KEY)
.update((_) => encryptedKey.toJSON() as EncryptedString);
}
async setForceSetPasswordReason(reason: ForceSetPasswordReason, userId: UserId): Promise<void> {
if (reason == null) {
throw new Error("Reason is required.");
}
if (userId == null) {
throw new Error("User ID is required.");
}
await this.stateProvider.getUser(userId, FORCE_SET_PASSWORD_REASON).update((_) => reason);
}
}

View File

@ -10,7 +10,10 @@ import { LogService } from "../../../platform/abstractions/log.service";
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
import { StateService } from "../../../platform/abstractions/state.service"; import { StateService } from "../../../platform/abstractions/state.service";
import { KeySuffixOptions } from "../../../platform/enums/key-suffix-options.enum"; import { KeySuffixOptions } from "../../../platform/enums/key-suffix-options.enum";
import { UserId } from "../../../types/guid";
import { UserKey } from "../../../types/key"; import { UserKey } from "../../../types/key";
import { AccountService } from "../../abstractions/account.service";
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";
import { VerificationType } from "../../enums/verification-type"; import { VerificationType } from "../../enums/verification-type";
@ -35,6 +38,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
constructor( constructor(
private stateService: StateService, private stateService: StateService,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private i18nService: I18nService, private i18nService: I18nService,
private userVerificationApiService: UserVerificationApiServiceAbstraction, private userVerificationApiService: UserVerificationApiServiceAbstraction,
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
@ -107,7 +112,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
if (verification.type === VerificationType.OTP) { if (verification.type === VerificationType.OTP) {
request.otp = verification.secret; request.otp = verification.secret;
} else { } else {
let masterKey = await this.cryptoService.getMasterKey(); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
let masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
if (!masterKey && !alreadyHashed) { if (!masterKey && !alreadyHashed) {
masterKey = await this.cryptoService.makeMasterKey( masterKey = await this.cryptoService.makeMasterKey(
verification.secret, verification.secret,
@ -164,7 +170,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
private async verifyUserByMasterPassword( private async verifyUserByMasterPassword(
verification: MasterPasswordVerification, verification: MasterPasswordVerification,
): Promise<boolean> { ): Promise<boolean> {
let masterKey = await this.cryptoService.getMasterKey(); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
let masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
if (!masterKey) { if (!masterKey) {
masterKey = await this.cryptoService.makeMasterKey( masterKey = await this.cryptoService.makeMasterKey(
verification.secret, verification.secret,
@ -181,7 +188,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
throw new Error(this.i18nService.t("invalidMasterPassword")); throw new Error(this.i18nService.t("invalidMasterPassword"));
} }
// TODO: we should re-evaluate later on if user verification should have the side effect of modifying state. Probably not. // TODO: we should re-evaluate later on if user verification should have the side effect of modifying state. Probably not.
await this.cryptoService.setMasterKey(masterKey); await this.masterPasswordService.setMasterKey(masterKey, userId);
return true; return true;
} }
@ -230,9 +237,10 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
} }
async hasMasterPasswordAndMasterKeyHash(userId?: string): Promise<boolean> { async hasMasterPasswordAndMasterKeyHash(userId?: string): Promise<boolean> {
userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id;
return ( return (
(await this.hasMasterPassword(userId)) && (await this.hasMasterPassword(userId)) &&
(await this.cryptoService.getMasterKeyHash()) != null (await firstValueFrom(this.masterPasswordService.masterKeyHash$(userId as UserId))) != null
); );
} }

View File

@ -105,18 +105,6 @@ export abstract class CryptoService {
* @param userId The desired user * @param userId The desired user
*/ */
abstract setMasterKeyEncryptedUserKey(UserKeyMasterKey: string, userId?: string): Promise<void>; abstract setMasterKeyEncryptedUserKey(UserKeyMasterKey: string, userId?: string): Promise<void>;
/**
* Sets the user's master key
* @param key The user's master key to set
* @param userId The desired user
*/
abstract setMasterKey(key: MasterKey, userId?: string): Promise<void>;
/**
* @param userId The desired user
* @returns The user's master key
*/
abstract getMasterKey(userId?: string): Promise<MasterKey>;
/** /**
* @param password The user's master password that will be used to derive a master key if one isn't found * @param password The user's master password that will be used to derive a master key if one isn't found
* @param userId The desired user * @param userId The desired user
@ -136,11 +124,6 @@ export abstract class CryptoService {
kdf: KdfType, kdf: KdfType,
KdfConfig: KdfConfig, KdfConfig: KdfConfig,
): Promise<MasterKey>; ): Promise<MasterKey>;
/**
* Clears the user's master key
* @param userId The desired user
*/
abstract clearMasterKey(userId?: string): Promise<void>;
/** /**
* Encrypts the existing (or provided) user key with the * Encrypts the existing (or provided) user key with the
* provided master key * provided master key
@ -178,20 +161,6 @@ export abstract class CryptoService {
key: MasterKey, key: MasterKey,
hashPurpose?: HashPurpose, hashPurpose?: HashPurpose,
): Promise<string>; ): Promise<string>;
/**
* Sets the user's master password hash
* @param keyHash The user's master password hash to set
*/
abstract setMasterKeyHash(keyHash: string): Promise<void>;
/**
* @returns The user's master password hash
*/
abstract getMasterKeyHash(): Promise<string>;
/**
* Clears the user's stored master password hash
* @param userId The desired user
*/
abstract clearMasterKeyHash(userId?: string): Promise<void>;
/** /**
* Compares the provided master password to the stored password hash and server password hash. * Compares the provided master password to the stored password hash and server password hash.
* Updates the stored hash if outdated. * Updates the stored hash if outdated.

View File

@ -1,14 +1,12 @@
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { AdminAuthRequestStorable } from "../../auth/models/domain/admin-auth-req-storable"; import { AdminAuthRequestStorable } from "../../auth/models/domain/admin-auth-req-storable";
import { ForceSetPasswordReason } from "../../auth/models/domain/force-set-password-reason";
import { KdfConfig } from "../../auth/models/domain/kdf-config"; 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 { MasterKey } from "../../types/key";
import { CipherData } from "../../vault/models/data/cipher.data"; import { CipherData } from "../../vault/models/data/cipher.data";
import { LocalData } from "../../vault/models/data/local.data"; import { LocalData } from "../../vault/models/data/local.data";
import { CipherView } from "../../vault/models/view/cipher.view"; import { CipherView } from "../../vault/models/view/cipher.view";
@ -17,7 +15,6 @@ 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";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
/** /**
* Options for customizing the initiation behavior. * Options for customizing the initiation behavior.
@ -48,22 +45,6 @@ export abstract class StateService<T extends Account = Account> {
getAddEditCipherInfo: (options?: StorageOptions) => Promise<AddEditCipherInfo>; getAddEditCipherInfo: (options?: StorageOptions) => Promise<AddEditCipherInfo>;
setAddEditCipherInfo: (value: AddEditCipherInfo, options?: StorageOptions) => Promise<void>; setAddEditCipherInfo: (value: AddEditCipherInfo, options?: StorageOptions) => Promise<void>;
/**
* Gets the user's master key
*/
getMasterKey: (options?: StorageOptions) => Promise<MasterKey>;
/**
* Sets the user's master key
*/
setMasterKey: (value: MasterKey, options?: StorageOptions) => Promise<void>;
/**
* Gets the user key encrypted by the master key
*/
getMasterKeyEncryptedUserKey: (options?: StorageOptions) => Promise<string>;
/**
* Sets the user key encrypted by the master key
*/
setMasterKeyEncryptedUserKey: (value: string, options?: StorageOptions) => Promise<void>;
/** /**
* Gets the user's auto key * Gets the user's auto key
*/ */
@ -108,10 +89,6 @@ export abstract class StateService<T extends Account = Account> {
* @deprecated For migration purposes only, use getUserKeyMasterKey instead * @deprecated For migration purposes only, use getUserKeyMasterKey instead
*/ */
getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>; getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>;
/**
* @deprecated For legacy purposes only, use getMasterKey instead
*/
getCryptoMasterKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
/** /**
* @deprecated For migration purposes only, use getUserKeyAuto instead * @deprecated For migration purposes only, use getUserKeyAuto instead
*/ */
@ -189,18 +166,11 @@ export abstract class StateService<T extends Account = Account> {
setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>; setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>;
getEverBeenUnlocked: (options?: StorageOptions) => Promise<boolean>; getEverBeenUnlocked: (options?: StorageOptions) => Promise<boolean>;
setEverBeenUnlocked: (value: boolean, options?: StorageOptions) => Promise<void>; setEverBeenUnlocked: (value: boolean, options?: StorageOptions) => Promise<void>;
getForceSetPasswordReason: (options?: StorageOptions) => Promise<ForceSetPasswordReason>;
setForceSetPasswordReason: (
value: ForceSetPasswordReason,
options?: StorageOptions,
) => Promise<void>;
getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>; getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>;
getKdfConfig: (options?: StorageOptions) => Promise<KdfConfig>; getKdfConfig: (options?: StorageOptions) => Promise<KdfConfig>;
setKdfConfig: (kdfConfig: KdfConfig, options?: StorageOptions) => Promise<void>; setKdfConfig: (kdfConfig: KdfConfig, options?: StorageOptions) => Promise<void>;
getKdfType: (options?: StorageOptions) => Promise<KdfType>; getKdfType: (options?: StorageOptions) => Promise<KdfType>;
setKdfType: (value: KdfType, options?: StorageOptions) => Promise<void>; setKdfType: (value: KdfType, options?: StorageOptions) => Promise<void>;
getKeyHash: (options?: StorageOptions) => Promise<string>;
setKeyHash: (value: string, 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

@ -2,7 +2,6 @@ import { makeStaticByteArray } from "../../../../spec";
import { Utils } from "../../misc/utils"; import { Utils } from "../../misc/utils";
import { AccountKeys, EncryptionPair } from "./account"; import { AccountKeys, EncryptionPair } from "./account";
import { SymmetricCryptoKey } from "./symmetric-crypto-key";
describe("AccountKeys", () => { describe("AccountKeys", () => {
describe("toJSON", () => { describe("toJSON", () => {
@ -32,12 +31,6 @@ describe("AccountKeys", () => {
expect(keys.publicKey).toEqual(Utils.fromByteStringToArray("hello")); expect(keys.publicKey).toEqual(Utils.fromByteStringToArray("hello"));
}); });
it("should deserialize cryptoMasterKey", () => {
const spy = jest.spyOn(SymmetricCryptoKey, "fromJSON");
AccountKeys.fromJSON({} as any);
expect(spy).toHaveBeenCalled();
});
it("should deserialize privateKey", () => { it("should deserialize privateKey", () => {
const spy = jest.spyOn(EncryptionPair, "fromJSON"); const spy = jest.spyOn(EncryptionPair, "fromJSON");
AccountKeys.fromJSON({ AccountKeys.fromJSON({

View File

@ -1,7 +1,6 @@
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { AdminAuthRequestStorable } from "../../../auth/models/domain/admin-auth-req-storable"; import { AdminAuthRequestStorable } from "../../../auth/models/domain/admin-auth-req-storable";
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { UriMatchStrategySetting } from "../../../models/domain/domain-service";
import { GeneratorOptions } from "../../../tools/generator/generator-options"; import { GeneratorOptions } from "../../../tools/generator/generator-options";
import { import {
@ -10,7 +9,6 @@ import {
} from "../../../tools/generator/password"; } from "../../../tools/generator/password";
import { UsernameGeneratorOptions } from "../../../tools/generator/username/username-generation-options"; import { UsernameGeneratorOptions } from "../../../tools/generator/username/username-generation-options";
import { DeepJsonify } from "../../../types/deep-jsonify"; import { DeepJsonify } from "../../../types/deep-jsonify";
import { MasterKey } from "../../../types/key";
import { CipherData } from "../../../vault/models/data/cipher.data"; import { CipherData } from "../../../vault/models/data/cipher.data";
import { CipherView } from "../../../vault/models/view/cipher.view"; import { CipherView } from "../../../vault/models/view/cipher.view";
import { AddEditCipherInfo } from "../../../vault/types/add-edit-cipher-info"; import { AddEditCipherInfo } from "../../../vault/types/add-edit-cipher-info";
@ -90,12 +88,8 @@ export class AccountData {
} }
export class AccountKeys { export class AccountKeys {
masterKey?: MasterKey;
masterKeyEncryptedUserKey?: string;
publicKey?: Uint8Array; publicKey?: Uint8Array;
/** @deprecated July 2023, left for migration purposes*/
cryptoMasterKey?: SymmetricCryptoKey;
/** @deprecated July 2023, left for migration purposes*/ /** @deprecated July 2023, left for migration purposes*/
cryptoMasterKeyAuto?: string; cryptoMasterKeyAuto?: string;
/** @deprecated July 2023, left for migration purposes*/ /** @deprecated July 2023, left for migration purposes*/
@ -120,8 +114,6 @@ export class AccountKeys {
return null; return null;
} }
return Object.assign(new AccountKeys(), obj, { return Object.assign(new AccountKeys(), obj, {
masterKey: SymmetricCryptoKey.fromJSON(obj?.masterKey),
cryptoMasterKey: SymmetricCryptoKey.fromJSON(obj?.cryptoMasterKey),
cryptoSymmetricKey: EncryptionPair.fromJSON( cryptoSymmetricKey: EncryptionPair.fromJSON(
obj?.cryptoSymmetricKey, obj?.cryptoSymmetricKey,
SymmetricCryptoKey.fromJSON, SymmetricCryptoKey.fromJSON,
@ -150,10 +142,8 @@ export class AccountProfile {
email?: string; email?: string;
emailVerified?: boolean; emailVerified?: boolean;
everBeenUnlocked?: boolean; everBeenUnlocked?: boolean;
forceSetPasswordReason?: ForceSetPasswordReason;
lastSync?: string; lastSync?: string;
userId?: string; userId?: string;
keyHash?: string;
kdfIterations?: number; kdfIterations?: number;
kdfMemory?: number; kdfMemory?: number;
kdfParallelism?: number; kdfParallelism?: number;

View File

@ -5,6 +5,7 @@ import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-a
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 { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { AuthenticationStatus } from "../../auth/enums/authentication-status";
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";
import { UserKey, MasterKey, PinKey } from "../../types/key"; import { UserKey, MasterKey, PinKey } from "../../types/key";
@ -41,12 +42,15 @@ describe("cryptoService", () => {
const mockUserId = Utils.newGuid() as UserId; const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService; let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
beforeEach(() => { beforeEach(() => {
accountService = mockAccountServiceWith(mockUserId); accountService = mockAccountServiceWith(mockUserId);
masterPasswordService = new FakeMasterPasswordService();
stateProvider = new FakeStateProvider(accountService); stateProvider = new FakeStateProvider(accountService);
cryptoService = new CryptoService( cryptoService = new CryptoService(
masterPasswordService,
keyGenerationService, keyGenerationService,
cryptoFunctionService, cryptoFunctionService,
encryptService, encryptService,
@ -158,14 +162,14 @@ describe("cryptoService", () => {
describe("getUserKeyWithLegacySupport", () => { describe("getUserKeyWithLegacySupport", () => {
let mockUserKey: UserKey; let mockUserKey: UserKey;
let mockMasterKey: MasterKey; let mockMasterKey: MasterKey;
let stateSvcGetMasterKey: jest.SpyInstance; let getMasterKey: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
const mockRandomBytes = new Uint8Array(64) as CsprngArray; const mockRandomBytes = new Uint8Array(64) as CsprngArray;
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey; mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
mockMasterKey = new SymmetricCryptoKey(new Uint8Array(64) as CsprngArray) as MasterKey; mockMasterKey = new SymmetricCryptoKey(new Uint8Array(64) as CsprngArray) as MasterKey;
stateSvcGetMasterKey = jest.spyOn(stateService, "getMasterKey"); getMasterKey = jest.spyOn(masterPasswordService, "masterKey$");
}); });
it("returns the User Key if available", async () => { it("returns the User Key if available", async () => {
@ -175,17 +179,17 @@ describe("cryptoService", () => {
const userKey = await cryptoService.getUserKeyWithLegacySupport(mockUserId); const userKey = await cryptoService.getUserKeyWithLegacySupport(mockUserId);
expect(getKeySpy).toHaveBeenCalledWith(mockUserId); expect(getKeySpy).toHaveBeenCalledWith(mockUserId);
expect(stateSvcGetMasterKey).not.toHaveBeenCalled(); expect(getMasterKey).not.toHaveBeenCalled();
expect(userKey).toEqual(mockUserKey); expect(userKey).toEqual(mockUserKey);
}); });
it("returns the user's master key when User Key is not available", async () => { it("returns the user's master key when User Key is not available", async () => {
stateSvcGetMasterKey.mockResolvedValue(mockMasterKey); masterPasswordService.masterKeySubject.next(mockMasterKey);
const userKey = await cryptoService.getUserKeyWithLegacySupport(mockUserId); const userKey = await cryptoService.getUserKeyWithLegacySupport(mockUserId);
expect(stateSvcGetMasterKey).toHaveBeenCalledWith({ userId: mockUserId }); expect(getMasterKey).toHaveBeenCalledWith(mockUserId);
expect(userKey).toEqual(mockMasterKey); expect(userKey).toEqual(mockMasterKey);
}); });
}); });
@ -340,9 +344,7 @@ describe("cryptoService", () => {
describe("clearKeys", () => { describe("clearKeys", () => {
it("resolves active user id when called with no user id", async () => { it("resolves active user id when called with no user id", async () => {
let callCount = 0; let callCount = 0;
accountService.activeAccount$ = accountService.activeAccountSubject.pipe( stateProvider.activeUserId$ = stateProvider.activeUserId$.pipe(tap(() => callCount++));
tap(() => callCount++),
);
await cryptoService.clearKeys(null); await cryptoService.clearKeys(null);
expect(callCount).toBe(1); expect(callCount).toBe(1);

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 { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction";
import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { AuthenticationStatus } from "../../auth/enums/authentication-status";
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";
@ -82,6 +83,7 @@ export class CryptoService implements CryptoServiceAbstraction {
readonly everHadUserKey$: Observable<boolean>; readonly everHadUserKey$: Observable<boolean>;
constructor( constructor(
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected keyGenerationService: KeyGenerationService, protected keyGenerationService: KeyGenerationService,
protected cryptoFunctionService: CryptoFunctionService, protected cryptoFunctionService: CryptoFunctionService,
protected encryptService: EncryptService, protected encryptService: EncryptService,
@ -181,12 +183,16 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
async isLegacyUser(masterKey?: MasterKey, userId?: UserId): Promise<boolean> { async isLegacyUser(masterKey?: MasterKey, userId?: UserId): Promise<boolean> {
return await this.validateUserKey( userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
(masterKey ?? (await this.getMasterKey(userId))) as unknown as UserKey, masterKey ??= await firstValueFrom(this.masterPasswordService.masterKey$(userId));
);
return await this.validateUserKey(masterKey as unknown as UserKey);
} }
// TODO: legacy support for user key is no longer needed since we require users to migrate on login
async getUserKeyWithLegacySupport(userId?: UserId): Promise<UserKey> { async getUserKeyWithLegacySupport(userId?: UserId): Promise<UserKey> {
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
const userKey = await this.getUserKey(userId); const userKey = await this.getUserKey(userId);
if (userKey) { if (userKey) {
return userKey; return userKey;
@ -194,7 +200,8 @@ export class CryptoService implements CryptoServiceAbstraction {
// Legacy support: encryption used to be done with the master key (derived from master password). // Legacy support: encryption used to be done with the master key (derived from master password).
// Users who have not migrated will have a null user key and must use the master key instead. // Users who have not migrated will have a null user key and must use the master key instead.
return (await this.getMasterKey(userId)) as unknown as UserKey; const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
return masterKey as unknown as UserKey;
} }
async getUserKeyFromStorage(keySuffix: KeySuffixOptions, userId?: UserId): Promise<UserKey> { async getUserKeyFromStorage(keySuffix: KeySuffixOptions, userId?: UserId): Promise<UserKey> {
@ -233,7 +240,10 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
async makeUserKey(masterKey: MasterKey): Promise<[UserKey, EncString]> { async makeUserKey(masterKey: MasterKey): Promise<[UserKey, EncString]> {
masterKey ||= await this.getMasterKey(); if (!masterKey) {
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
}
if (masterKey == null) { if (masterKey == null) {
throw new Error("No Master Key found."); throw new Error("No Master Key found.");
} }
@ -277,28 +287,17 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
async setMasterKeyEncryptedUserKey(userKeyMasterKey: string, userId?: UserId): Promise<void> { async setMasterKeyEncryptedUserKey(userKeyMasterKey: string, userId?: UserId): Promise<void> {
await this.stateService.setMasterKeyEncryptedUserKey(userKeyMasterKey, { userId: userId }); userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
} await this.masterPasswordService.setMasterKeyEncryptedUserKey(
new EncString(userKeyMasterKey),
async setMasterKey(key: MasterKey, userId?: UserId): Promise<void> { userId,
await this.stateService.setMasterKey(key, { userId: userId }); );
}
async getMasterKey(userId?: UserId): Promise<MasterKey> {
let masterKey = await this.stateService.getMasterKey({ userId: userId });
if (!masterKey) {
masterKey = (await this.stateService.getCryptoMasterKey({ userId: userId })) as MasterKey;
// if master key was null/undefined and getCryptoMasterKey also returned null/undefined,
// don't set master key as it is unnecessary
if (masterKey) {
await this.setMasterKey(masterKey, userId);
}
}
return masterKey;
} }
// TODO: Move to MasterPasswordService
async getOrDeriveMasterKey(password: string, userId?: UserId) { async getOrDeriveMasterKey(password: string, userId?: UserId) {
let masterKey = await this.getMasterKey(userId); userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
let masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
return (masterKey ||= await this.makeMasterKey( return (masterKey ||= await this.makeMasterKey(
password, password,
await this.stateService.getEmail({ userId: userId }), await this.stateService.getEmail({ userId: userId }),
@ -312,6 +311,7 @@ export class CryptoService implements CryptoServiceAbstraction {
* *
* @remarks * @remarks
* 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
*/ */
async makeMasterKey( async makeMasterKey(
password: string, password: string,
@ -327,10 +327,6 @@ export class CryptoService implements CryptoServiceAbstraction {
)) as MasterKey; )) as MasterKey;
} }
async clearMasterKey(userId?: UserId): Promise<void> {
await this.stateService.setMasterKey(null, { userId: userId });
}
async encryptUserKeyWithMasterKey( async encryptUserKeyWithMasterKey(
masterKey: MasterKey, masterKey: MasterKey,
userKey?: UserKey, userKey?: UserKey,
@ -339,32 +335,28 @@ export class CryptoService implements CryptoServiceAbstraction {
return await this.buildProtectedSymmetricKey(masterKey, userKey.key); return await this.buildProtectedSymmetricKey(masterKey, userKey.key);
} }
// TODO: move to master password service
async decryptUserKeyWithMasterKey( async decryptUserKeyWithMasterKey(
masterKey: MasterKey, masterKey: MasterKey,
userKey?: EncString, userKey?: EncString,
userId?: UserId, userId?: UserId,
): Promise<UserKey> { ): Promise<UserKey> {
masterKey ||= await this.getMasterKey(userId); userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
userKey ??= await this.masterPasswordService.getMasterKeyEncryptedUserKey(userId);
masterKey ??= await firstValueFrom(this.masterPasswordService.masterKey$(userId));
if (masterKey == null) { if (masterKey == null) {
throw new Error("No master key found."); throw new Error("No master key found.");
} }
if (!userKey) { // Try one more way to get the user key if it still wasn't found.
let masterKeyEncryptedUserKey = await this.stateService.getMasterKeyEncryptedUserKey({ if (userKey == null) {
const deprecatedKey = await this.stateService.getEncryptedCryptoSymmetricKey({
userId: userId, userId: userId,
}); });
if (deprecatedKey == null) {
// Try one more way to get the user key if it still wasn't found.
if (masterKeyEncryptedUserKey == null) {
masterKeyEncryptedUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({
userId: userId,
});
}
if (masterKeyEncryptedUserKey == null) {
throw new Error("No encrypted user key found."); throw new Error("No encrypted user key found.");
} }
userKey = new EncString(masterKeyEncryptedUserKey); userKey = new EncString(deprecatedKey);
} }
let decUserKey: Uint8Array; let decUserKey: Uint8Array;
@ -383,12 +375,16 @@ export class CryptoService implements CryptoServiceAbstraction {
return new SymmetricCryptoKey(decUserKey) as UserKey; return new SymmetricCryptoKey(decUserKey) as UserKey;
} }
// TODO: move to MasterPasswordService
async hashMasterKey( async hashMasterKey(
password: string, password: string,
key: MasterKey, key: MasterKey,
hashPurpose?: HashPurpose, hashPurpose?: HashPurpose,
): Promise<string> { ): Promise<string> {
key ||= await this.getMasterKey(); if (!key) {
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
key = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
}
if (password == null || key == null) { if (password == null || key == null) {
throw new Error("Invalid parameters."); throw new Error("Invalid parameters.");
@ -399,20 +395,12 @@ export class CryptoService implements CryptoServiceAbstraction {
return Utils.fromBufferToB64(hash); return Utils.fromBufferToB64(hash);
} }
async setMasterKeyHash(keyHash: string): Promise<void> { // TODO: move to MasterPasswordService
await this.stateService.setKeyHash(keyHash);
}
async getMasterKeyHash(): Promise<string> {
return await this.stateService.getKeyHash();
}
async clearMasterKeyHash(userId?: UserId): Promise<void> {
return await this.stateService.setKeyHash(null, { userId: userId });
}
async compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean> { async compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean> {
const storedPasswordHash = await this.getMasterKeyHash(); const userId = await firstValueFrom(this.stateProvider.activeUserId$);
const storedPasswordHash = await firstValueFrom(
this.masterPasswordService.masterKeyHash$(userId),
);
if (masterPassword != null && storedPasswordHash != null) { if (masterPassword != null && storedPasswordHash != null) {
const localKeyHash = await this.hashMasterKey( const localKeyHash = await this.hashMasterKey(
masterPassword, masterPassword,
@ -430,7 +418,7 @@ export class CryptoService implements CryptoServiceAbstraction {
HashPurpose.ServerAuthorization, HashPurpose.ServerAuthorization,
); );
if (serverKeyHash != null && storedPasswordHash === serverKeyHash) { if (serverKeyHash != null && storedPasswordHash === serverKeyHash) {
await this.setMasterKeyHash(localKeyHash); await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId);
return true; return true;
} }
} }
@ -652,14 +640,14 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
async clearKeys(userId?: UserId): Promise<any> { async clearKeys(userId?: UserId): Promise<any> {
userId ||= (await firstValueFrom(this.accountService.activeAccount$))?.id; userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
if (userId == null) { if (userId == null) {
throw new Error("Cannot clear keys, no user Id resolved."); throw new Error("Cannot clear keys, no user Id resolved.");
} }
await this.masterPasswordService.clearMasterKeyHash(userId);
await this.clearUserKey(userId); await this.clearUserKey(userId);
await this.clearMasterKeyHash(userId);
await this.clearOrgKeys(userId); await this.clearOrgKeys(userId);
await this.clearProviderKeys(userId); await this.clearProviderKeys(userId);
await this.clearKeyPair(userId); await this.clearKeyPair(userId);
@ -1014,7 +1002,8 @@ export class CryptoService implements CryptoServiceAbstraction {
if (await this.isLegacyUser(masterKey, userId)) { if (await this.isLegacyUser(masterKey, userId)) {
// Legacy users don't have a user key, so no need to migrate. // Legacy users don't have a user key, so no need to migrate.
// Instead, set the master key for additional isLegacyUser checks that will log the user out. // Instead, set the master key for additional isLegacyUser checks that will log the user out.
await this.setMasterKey(masterKey, userId); userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
await this.masterPasswordService.setMasterKey(masterKey, userId);
return; return;
} }
const encryptedUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({ const encryptedUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({

View File

@ -5,14 +5,12 @@ import { AccountService } from "../../auth/abstractions/account.service";
import { TokenService } from "../../auth/abstractions/token.service"; import { TokenService } from "../../auth/abstractions/token.service";
import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { AuthenticationStatus } from "../../auth/enums/authentication-status";
import { AdminAuthRequestStorable } from "../../auth/models/domain/admin-auth-req-storable"; import { AdminAuthRequestStorable } from "../../auth/models/domain/admin-auth-req-storable";
import { ForceSetPasswordReason } from "../../auth/models/domain/force-set-password-reason";
import { KdfConfig } from "../../auth/models/domain/kdf-config"; 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 { MasterKey } from "../../types/key";
import { CipherData } from "../../vault/models/data/cipher.data"; import { CipherData } from "../../vault/models/data/cipher.data";
import { LocalData } from "../../vault/models/data/local.data"; import { LocalData } from "../../vault/models/data/local.data";
import { CipherView } from "../../vault/models/view/cipher.view"; import { CipherView } from "../../vault/models/view/cipher.view";
@ -35,7 +33,6 @@ import { EncString } from "../models/domain/enc-string";
import { GlobalState } from "../models/domain/global-state"; import { GlobalState } from "../models/domain/global-state";
import { State } from "../models/domain/state"; import { State } from "../models/domain/state";
import { StorageOptions } from "../models/domain/storage-options"; import { StorageOptions } from "../models/domain/storage-options";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
import { MigrationRunner } from "./migration-runner"; import { MigrationRunner } from "./migration-runner";
@ -273,65 +270,6 @@ export class StateService<
); );
} }
/**
* @deprecated Do not save the Master Key. Use the User Symmetric Key instead
*/
async getCryptoMasterKey(options?: StorageOptions): Promise<SymmetricCryptoKey> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
return account?.keys?.cryptoMasterKey;
}
/**
* User's master key derived from MP, saved only if we decrypted with MP
*/
async getMasterKey(options?: StorageOptions): Promise<MasterKey> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
return account?.keys?.masterKey;
}
/**
* User's master key derived from MP, saved only if we decrypted with MP
*/
async setMasterKey(value: MasterKey, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
account.keys.masterKey = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
}
/**
* The master key encrypted User symmetric key, saved on every auth
* so we can unlock with MP offline
*/
async getMasterKeyEncryptedUserKey(options?: StorageOptions): Promise<string> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.keys.masterKeyEncryptedUserKey;
}
/**
* The master key encrypted User symmetric key, saved on every auth
* so we can unlock with MP offline
*/
async setMasterKeyEncryptedUserKey(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.keys.masterKeyEncryptedUserKey = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
/** /**
* user key when using the "never" option of vault timeout * user key when using the "never" option of vault timeout
*/ */
@ -823,30 +761,6 @@ export class StateService<
); );
} }
async getForceSetPasswordReason(options?: StorageOptions): Promise<ForceSetPasswordReason> {
return (
(
await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()),
)
)?.profile?.forceSetPasswordReason ?? ForceSetPasswordReason.None
);
}
async setForceSetPasswordReason(
value: ForceSetPasswordReason,
options?: StorageOptions,
): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()),
);
account.profile.forceSetPasswordReason = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()),
);
}
async getIsAuthenticated(options?: StorageOptions): Promise<boolean> { async getIsAuthenticated(options?: StorageOptions): Promise<boolean> {
return ( return (
(await this.tokenService.getAccessToken(options?.userId as UserId)) != null && (await this.tokenService.getAccessToken(options?.userId as UserId)) != null &&
@ -897,23 +811,6 @@ export class StateService<
); );
} }
async getKeyHash(options?: StorageOptions): Promise<string> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.profile?.keyHash;
}
async setKeyHash(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.profile.keyHash = 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

@ -37,6 +37,8 @@ export const BILLING_DISK = new StateDefinition("billing", "disk");
export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "disk"); export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "disk");
export const ACCOUNT_MEMORY = new StateDefinition("account", "memory"); export const ACCOUNT_MEMORY = new StateDefinition("account", "memory");
export const MASTER_PASSWORD_MEMORY = new StateDefinition("masterPassword", "memory");
export const MASTER_PASSWORD_DISK = new StateDefinition("masterPassword", "disk");
export const AVATAR_DISK = new StateDefinition("avatar", "disk", { web: "disk-local" }); export const AVATAR_DISK = new StateDefinition("avatar", "disk", { web: "disk-local" });
export const ROUTER_DISK = new StateDefinition("router", "disk"); export const ROUTER_DISK = new StateDefinition("router", "disk");
export const LOGIN_EMAIL_DISK = new StateDefinition("loginEmail", "disk", { export const LOGIN_EMAIL_DISK = new StateDefinition("loginEmail", "disk", {

View File

@ -1,17 +1,21 @@
import { MockProxy, any, mock } from "jest-mock-extended"; import { MockProxy, any, mock } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service";
import { SearchService } from "../../abstractions/search.service"; import { SearchService } from "../../abstractions/search.service";
import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service";
import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthService } from "../../auth/abstractions/auth.service";
import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { AuthenticationStatus } from "../../auth/enums/authentication-status";
import { FakeMasterPasswordService } from "../../auth/services/master-password/fake-master-password.service";
import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
import { CryptoService } from "../../platform/abstractions/crypto.service"; import { CryptoService } from "../../platform/abstractions/crypto.service";
import { MessagingService } from "../../platform/abstractions/messaging.service"; import { MessagingService } from "../../platform/abstractions/messaging.service";
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { StateService } from "../../platform/abstractions/state.service"; import { StateService } from "../../platform/abstractions/state.service";
import { Utils } from "../../platform/misc/utils";
import { Account } from "../../platform/models/domain/account"; import { Account } from "../../platform/models/domain/account";
import { StateEventRunnerService } from "../../platform/state"; import { StateEventRunnerService } from "../../platform/state";
import { UserId } from "../../types/guid";
import { CipherService } from "../../vault/abstractions/cipher.service"; import { CipherService } from "../../vault/abstractions/cipher.service";
import { CollectionService } from "../../vault/abstractions/collection.service"; import { CollectionService } from "../../vault/abstractions/collection.service";
import { FolderService } from "../../vault/abstractions/folder/folder.service.abstraction"; import { FolderService } from "../../vault/abstractions/folder/folder.service.abstraction";
@ -19,6 +23,8 @@ import { FolderService } from "../../vault/abstractions/folder/folder.service.ab
import { VaultTimeoutService } from "./vault-timeout.service"; import { VaultTimeoutService } from "./vault-timeout.service";
describe("VaultTimeoutService", () => { describe("VaultTimeoutService", () => {
let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
let cipherService: MockProxy<CipherService>; let cipherService: MockProxy<CipherService>;
let folderService: MockProxy<FolderService>; let folderService: MockProxy<FolderService>;
let collectionService: MockProxy<CollectionService>; let collectionService: MockProxy<CollectionService>;
@ -39,7 +45,11 @@ describe("VaultTimeoutService", () => {
let vaultTimeoutService: VaultTimeoutService; let vaultTimeoutService: VaultTimeoutService;
const userId = Utils.newGuid() as UserId;
beforeEach(() => { beforeEach(() => {
accountService = mockAccountServiceWith(userId);
masterPasswordService = new FakeMasterPasswordService();
cipherService = mock(); cipherService = mock();
folderService = mock(); folderService = mock();
collectionService = mock(); collectionService = mock();
@ -66,6 +76,8 @@ describe("VaultTimeoutService", () => {
availableVaultTimeoutActionsSubject = new BehaviorSubject<VaultTimeoutAction[]>([]); availableVaultTimeoutActionsSubject = new BehaviorSubject<VaultTimeoutAction[]>([]);
vaultTimeoutService = new VaultTimeoutService( vaultTimeoutService = new VaultTimeoutService(
accountService,
masterPasswordService,
cipherService, cipherService,
folderService, folderService,
collectionService, collectionService,
@ -123,6 +135,15 @@ describe("VaultTimeoutService", () => {
stateService.activeAccount$ = new BehaviorSubject<string>(globalSetups?.userId); stateService.activeAccount$ = new BehaviorSubject<string>(globalSetups?.userId);
if (globalSetups?.userId) {
accountService.activeAccountSubject.next({
id: globalSetups.userId as UserId,
status: accounts[globalSetups.userId]?.authStatus,
email: null,
name: null,
});
}
platformUtilsService.isViewOpen.mockResolvedValue(globalSetups?.isViewOpen ?? false); platformUtilsService.isViewOpen.mockResolvedValue(globalSetups?.isViewOpen ?? false);
vaultTimeoutSettingsService.vaultTimeoutAction$.mockImplementation((userId) => { vaultTimeoutSettingsService.vaultTimeoutAction$.mockImplementation((userId) => {
@ -156,7 +177,7 @@ describe("VaultTimeoutService", () => {
expect(vaultTimeoutSettingsService.availableVaultTimeoutActions$).toHaveBeenCalledWith(userId); expect(vaultTimeoutSettingsService.availableVaultTimeoutActions$).toHaveBeenCalledWith(userId);
expect(stateService.setEverBeenUnlocked).toHaveBeenCalledWith(true, { userId: userId }); expect(stateService.setEverBeenUnlocked).toHaveBeenCalledWith(true, { userId: userId });
expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, { userId: userId }); expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, { userId: userId });
expect(cryptoService.clearMasterKey).toHaveBeenCalledWith(userId); expect(masterPasswordService.mock.clearMasterKey).toHaveBeenCalledWith(userId);
expect(cipherService.clearCache).toHaveBeenCalledWith(userId); expect(cipherService.clearCache).toHaveBeenCalledWith(userId);
expect(lockedCallback).toHaveBeenCalledWith(userId); expect(lockedCallback).toHaveBeenCalledWith(userId);
}; };

View File

@ -3,7 +3,9 @@ import { firstValueFrom, timeout } from "rxjs";
import { SearchService } from "../../abstractions/search.service"; import { SearchService } from "../../abstractions/search.service";
import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout.service"; import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout.service";
import { AccountService } from "../../auth/abstractions/account.service";
import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthService } from "../../auth/abstractions/auth.service";
import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction";
import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { AuthenticationStatus } from "../../auth/enums/authentication-status";
import { ClientType } from "../../enums"; import { ClientType } from "../../enums";
import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
@ -21,6 +23,8 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
private inited = false; private inited = false;
constructor( constructor(
private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private cipherService: CipherService, private cipherService: CipherService,
private folderService: FolderService, private folderService: FolderService,
private collectionService: CollectionService, private collectionService: CollectionService,
@ -84,7 +88,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
await this.logOut(userId); await this.logOut(userId);
} }
const currentUserId = await this.stateService.getUserId(); const currentUserId = (await firstValueFrom(this.accountService.activeAccount$)).id;
if (userId == null || userId === currentUserId) { if (userId == null || userId === currentUserId) {
this.searchService.clearIndex(); this.searchService.clearIndex();
@ -92,12 +96,12 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
await this.collectionService.clearActiveUserCache(); await this.collectionService.clearActiveUserCache();
} }
await this.masterPasswordService.clearMasterKey((userId ?? currentUserId) as UserId);
await this.stateService.setEverBeenUnlocked(true, { userId: userId }); await this.stateService.setEverBeenUnlocked(true, { userId: userId });
await this.stateService.setUserKeyAutoUnlock(null, { userId: userId }); await this.stateService.setUserKeyAutoUnlock(null, { userId: userId });
await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId }); await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId });
await this.cryptoService.clearMasterKey(userId);
await this.cipherService.clearCache(userId); await this.cipherService.clearCache(userId);
await this.stateEventRunnerService.handleEvent("lock", (userId ?? currentUserId) as UserId); await this.stateEventRunnerService.handleEvent("lock", (userId ?? currentUserId) as UserId);

View File

@ -51,6 +51,7 @@ import { RememberedEmailMigrator } from "./migrations/51-move-remembered-email-t
import { DeleteInstalledVersion } from "./migrations/52-delete-installed-version"; import { DeleteInstalledVersion } from "./migrations/52-delete-installed-version";
import { DeviceTrustCryptoServiceStateProviderMigrator } from "./migrations/53-migrate-device-trust-crypto-svc-to-state-providers"; import { DeviceTrustCryptoServiceStateProviderMigrator } from "./migrations/53-migrate-device-trust-crypto-svc-to-state-providers";
import { SendMigrator } from "./migrations/54-move-encrypted-sends"; import { SendMigrator } from "./migrations/54-move-encrypted-sends";
import { MoveMasterKeyStateToProviderMigrator } from "./migrations/55-move-master-key-state-to-provider";
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key"; import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account"; import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version"; import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
@ -58,7 +59,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
import { MinVersionMigrator } from "./migrations/min-version"; import { MinVersionMigrator } from "./migrations/min-version";
export const MIN_VERSION = 3; export const MIN_VERSION = 3;
export const CURRENT_VERSION = 54; export const CURRENT_VERSION = 55;
export type MinVersion = typeof MIN_VERSION; export type MinVersion = typeof MIN_VERSION;
@ -115,7 +116,8 @@ export function createMigrationBuilder() {
.with(RememberedEmailMigrator, 50, 51) .with(RememberedEmailMigrator, 50, 51)
.with(DeleteInstalledVersion, 51, 52) .with(DeleteInstalledVersion, 51, 52)
.with(DeviceTrustCryptoServiceStateProviderMigrator, 52, 53) .with(DeviceTrustCryptoServiceStateProviderMigrator, 52, 53)
.with(SendMigrator, 53, 54); .with(SendMigrator, 53, 54)
.with(MoveMasterKeyStateToProviderMigrator, 54, CURRENT_VERSION);
} }
export async function currentVersion( export async function currentVersion(

View File

@ -0,0 +1,210 @@
import { any, MockProxy } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import {
FORCE_SET_PASSWORD_REASON_DEFINITION,
MASTER_KEY_ENCRYPTED_USER_KEY_DEFINITION,
MASTER_KEY_HASH_DEFINITION,
MoveMasterKeyStateToProviderMigrator,
} from "./55-move-master-key-state-to-provider";
function preMigrationState() {
return {
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["FirstAccount", "SecondAccount", "ThirdAccount"],
// prettier-ignore
"FirstAccount": {
profile: {
forceSetPasswordReason: "FirstAccount_forceSetPasswordReason",
keyHash: "FirstAccount_keyHash",
otherStuff: "overStuff2",
},
keys: {
masterKeyEncryptedUserKey: "FirstAccount_masterKeyEncryptedUserKey",
},
otherStuff: "otherStuff3",
},
// prettier-ignore
"SecondAccount": {
profile: {
forceSetPasswordReason: "SecondAccount_forceSetPasswordReason",
keyHash: "SecondAccount_keyHash",
otherStuff: "otherStuff4",
},
keys: {
masterKeyEncryptedUserKey: "SecondAccount_masterKeyEncryptedUserKey",
},
otherStuff: "otherStuff5",
},
// prettier-ignore
"ThirdAccount": {
profile: {
otherStuff: "otherStuff6",
},
},
};
}
function postMigrationState() {
return {
user_FirstAccount_masterPassword_forceSetPasswordReason: "FirstAccount_forceSetPasswordReason",
user_FirstAccount_masterPassword_masterKeyHash: "FirstAccount_keyHash",
user_FirstAccount_masterPassword_masterKeyEncryptedUserKey:
"FirstAccount_masterKeyEncryptedUserKey",
user_SecondAccount_masterPassword_forceSetPasswordReason:
"SecondAccount_forceSetPasswordReason",
user_SecondAccount_masterPassword_masterKeyHash: "SecondAccount_keyHash",
user_SecondAccount_masterPassword_masterKeyEncryptedUserKey:
"SecondAccount_masterKeyEncryptedUserKey",
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["FirstAccount", "SecondAccount"],
// prettier-ignore
"FirstAccount": {
profile: {
otherStuff: "overStuff2",
},
otherStuff: "otherStuff3",
},
// prettier-ignore
"SecondAccount": {
profile: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
// prettier-ignore
"ThirdAccount": {
profile: {
otherStuff: "otherStuff6",
},
},
};
}
describe("MoveForceSetPasswordReasonToStateProviderMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: MoveMasterKeyStateToProviderMigrator;
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(preMigrationState(), 54);
sut = new MoveMasterKeyStateToProviderMigrator(54, 55);
});
it("should remove properties from existing accounts", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledWith("FirstAccount", {
profile: {
otherStuff: "overStuff2",
},
keys: {},
otherStuff: "otherStuff3",
});
expect(helper.set).toHaveBeenCalledWith("SecondAccount", {
profile: {
otherStuff: "otherStuff4",
},
keys: {},
otherStuff: "otherStuff5",
});
});
it("should set properties for each account", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledWith(
"FirstAccount",
FORCE_SET_PASSWORD_REASON_DEFINITION,
"FirstAccount_forceSetPasswordReason",
);
expect(helper.setToUser).toHaveBeenCalledWith(
"FirstAccount",
MASTER_KEY_HASH_DEFINITION,
"FirstAccount_keyHash",
);
expect(helper.setToUser).toHaveBeenCalledWith(
"FirstAccount",
MASTER_KEY_ENCRYPTED_USER_KEY_DEFINITION,
"FirstAccount_masterKeyEncryptedUserKey",
);
expect(helper.setToUser).toHaveBeenCalledWith(
"SecondAccount",
FORCE_SET_PASSWORD_REASON_DEFINITION,
"SecondAccount_forceSetPasswordReason",
);
expect(helper.setToUser).toHaveBeenCalledWith(
"SecondAccount",
MASTER_KEY_HASH_DEFINITION,
"SecondAccount_keyHash",
);
expect(helper.setToUser).toHaveBeenCalledWith(
"SecondAccount",
MASTER_KEY_ENCRYPTED_USER_KEY_DEFINITION,
"SecondAccount_masterKeyEncryptedUserKey",
);
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(postMigrationState(), 55);
sut = new MoveMasterKeyStateToProviderMigrator(54, 55);
});
it.each(["FirstAccount", "SecondAccount"])("should null out new values", async (userId) => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledWith(
userId,
FORCE_SET_PASSWORD_REASON_DEFINITION,
null,
);
expect(helper.setToUser).toHaveBeenCalledWith(userId, MASTER_KEY_HASH_DEFINITION, null);
});
it("should add explicit value back to accounts", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledWith("FirstAccount", {
profile: {
forceSetPasswordReason: "FirstAccount_forceSetPasswordReason",
keyHash: "FirstAccount_keyHash",
otherStuff: "overStuff2",
},
keys: {
masterKeyEncryptedUserKey: "FirstAccount_masterKeyEncryptedUserKey",
},
otherStuff: "otherStuff3",
});
expect(helper.set).toHaveBeenCalledWith("SecondAccount", {
profile: {
forceSetPasswordReason: "SecondAccount_forceSetPasswordReason",
keyHash: "SecondAccount_keyHash",
otherStuff: "otherStuff4",
},
keys: {
masterKeyEncryptedUserKey: "SecondAccount_masterKeyEncryptedUserKey",
},
otherStuff: "otherStuff5",
});
});
it("should not try to restore values to missing accounts", async () => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith("ThirdAccount", any());
});
});
});

View File

@ -0,0 +1,111 @@
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedAccountType = {
keys?: {
masterKeyEncryptedUserKey?: string;
};
profile?: {
forceSetPasswordReason?: number;
keyHash?: string;
};
};
export const FORCE_SET_PASSWORD_REASON_DEFINITION: KeyDefinitionLike = {
key: "forceSetPasswordReason",
stateDefinition: {
name: "masterPassword",
},
};
export const MASTER_KEY_HASH_DEFINITION: KeyDefinitionLike = {
key: "masterKeyHash",
stateDefinition: {
name: "masterPassword",
},
};
export const MASTER_KEY_ENCRYPTED_USER_KEY_DEFINITION: KeyDefinitionLike = {
key: "masterKeyEncryptedUserKey",
stateDefinition: {
name: "masterPassword",
},
};
export class MoveMasterKeyStateToProviderMigrator extends Migrator<54, 55> {
async migrate(helper: MigrationHelper): Promise<void> {
const accounts = await helper.getAccounts<ExpectedAccountType>();
async function migrateAccount(userId: string, account: ExpectedAccountType): Promise<void> {
const forceSetPasswordReason = account?.profile?.forceSetPasswordReason;
if (forceSetPasswordReason != null) {
await helper.setToUser(
userId,
FORCE_SET_PASSWORD_REASON_DEFINITION,
forceSetPasswordReason,
);
delete account.profile.forceSetPasswordReason;
await helper.set(userId, account);
}
const masterKeyHash = account?.profile?.keyHash;
if (masterKeyHash != null) {
await helper.setToUser(userId, MASTER_KEY_HASH_DEFINITION, masterKeyHash);
delete account.profile.keyHash;
await helper.set(userId, account);
}
const masterKeyEncryptedUserKey = account?.keys?.masterKeyEncryptedUserKey;
if (masterKeyEncryptedUserKey != null) {
await helper.setToUser(
userId,
MASTER_KEY_ENCRYPTED_USER_KEY_DEFINITION,
masterKeyEncryptedUserKey,
);
delete account.keys.masterKeyEncryptedUserKey;
await helper.set(userId, account);
}
}
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
}
async rollback(helper: MigrationHelper): Promise<void> {
const accounts = await helper.getAccounts<ExpectedAccountType>();
async function rollbackAccount(userId: string, account: ExpectedAccountType): Promise<void> {
const forceSetPasswordReason = await helper.getFromUser(
userId,
FORCE_SET_PASSWORD_REASON_DEFINITION,
);
const masterKeyHash = await helper.getFromUser(userId, MASTER_KEY_HASH_DEFINITION);
const masterKeyEncryptedUserKey = await helper.getFromUser(
userId,
MASTER_KEY_ENCRYPTED_USER_KEY_DEFINITION,
);
if (account != null) {
if (forceSetPasswordReason != null) {
account.profile = Object.assign(account.profile ?? {}, {
forceSetPasswordReason,
});
}
if (masterKeyHash != null) {
account.profile = Object.assign(account.profile ?? {}, {
keyHash: masterKeyHash,
});
}
if (masterKeyEncryptedUserKey != null) {
account.keys = Object.assign(account.keys ?? {}, {
masterKeyEncryptedUserKey,
});
}
await helper.set(userId, account);
}
await helper.setToUser(userId, FORCE_SET_PASSWORD_REASON_DEFINITION, null);
await helper.setToUser(userId, MASTER_KEY_HASH_DEFINITION, null);
}
await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]);
}
}

View File

@ -11,8 +11,10 @@ import { OrganizationData } from "../../../admin-console/models/data/organizatio
import { PolicyData } from "../../../admin-console/models/data/policy.data"; import { PolicyData } from "../../../admin-console/models/data/policy.data";
import { ProviderData } from "../../../admin-console/models/data/provider.data"; import { ProviderData } from "../../../admin-console/models/data/provider.data";
import { PolicyResponse } from "../../../admin-console/models/response/policy.response"; import { PolicyResponse } from "../../../admin-console/models/response/policy.response";
import { AccountService } from "../../../auth/abstractions/account.service";
import { AvatarService } from "../../../auth/abstractions/avatar.service"; import { AvatarService } from "../../../auth/abstractions/avatar.service";
import { KeyConnectorService } from "../../../auth/abstractions/key-connector.service"; import { KeyConnectorService } from "../../../auth/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "../../../auth/abstractions/master-password.service.abstraction";
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; import { DomainSettingsService } from "../../../autofill/services/domain-settings.service";
import { BillingAccountProfileStateService } from "../../../billing/abstractions/account/billing-account-profile-state.service"; import { BillingAccountProfileStateService } from "../../../billing/abstractions/account/billing-account-profile-state.service";
@ -49,6 +51,8 @@ export class SyncService implements SyncServiceAbstraction {
syncInProgress = false; syncInProgress = false;
constructor( constructor(
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private accountService: AccountService,
private apiService: ApiService, private apiService: ApiService,
private domainSettingsService: DomainSettingsService, private domainSettingsService: DomainSettingsService,
private folderService: InternalFolderService, private folderService: InternalFolderService,
@ -352,8 +356,10 @@ export class SyncService implements SyncServiceAbstraction {
private async setForceSetPasswordReasonIfNeeded(profileResponse: ProfileResponse) { private async setForceSetPasswordReasonIfNeeded(profileResponse: ProfileResponse) {
// The `forcePasswordReset` flag indicates an admin has reset the user's password and must be updated // The `forcePasswordReset` flag indicates an admin has reset the user's password and must be updated
if (profileResponse.forcePasswordReset) { if (profileResponse.forcePasswordReset) {
await this.stateService.setForceSetPasswordReason( const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.AdminForcePasswordReset, ForceSetPasswordReason.AdminForcePasswordReset,
userId,
); );
} }
@ -387,8 +393,10 @@ export class SyncService implements SyncServiceAbstraction {
) { ) {
// TDE user w/out MP went from having no password reset permission to having it. // TDE user w/out MP went from having no password reset permission to having it.
// Must set the force password reset reason so the auth guard will redirect to the set password page. // Must set the force password reset reason so the auth guard will redirect to the set password page.
await this.stateService.setForceSetPasswordReason( const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission,
userId,
); );
} }
} }