[PM-5404, PM-3518] Migrate user decryption options to new service (#7344)
* create new user decryption options service * rename new service to user decryption options * add hasMasterPassword to user decryption options service * migrate device trust service to new user decryption options service * add migration for user-decryption-options * migrate sync service and calls to trust-device-service * rename abstraction file * migrate two factor component * migrate two factor spec * migrate sso component * migrate set-password component * migrate base login decryption component * migrate organization options component * fix component imports * add missing imports - remove state service calls - add update user decryption options method * remove acct decryption options from account * lint * fix tests and linting * fix browser * fix desktop * add user decryption options service to cli * remove default value from migration * bump migration number * fix merge conflict * fix vault timeout settings * fix cli * more fixes * add user decryption options service to deps of vault timeout settings service * update login strategy service with user decryption options * remove early return from sync bandaid for user decryption options * move user decryption options service to lib/auth * move user decryption options to libs/auth * fix reference * fix browser * check user decryption options after 2fa check * update migration and revert tsconfig changes * add more documentation * clear user decryption options on logout * fix tests by creating helper for user decryption options * fix tests * pr feedback * fix factory * update migration * add tests * update missed migration num in test
This commit is contained in:
parent
e2fe1e1567
commit
2111b37c32
|
@ -15,8 +15,8 @@ import {
|
||||||
factory,
|
factory,
|
||||||
} from "../../../platform/background/service-factories/factory-options";
|
} from "../../../platform/background/service-factories/factory-options";
|
||||||
import {
|
import {
|
||||||
messagingServiceFactory,
|
|
||||||
MessagingServiceInitOptions,
|
MessagingServiceInitOptions,
|
||||||
|
messagingServiceFactory,
|
||||||
} from "../../../platform/background/service-factories/messaging-service.factory";
|
} from "../../../platform/background/service-factories/messaging-service.factory";
|
||||||
import {
|
import {
|
||||||
StateServiceInitOptions,
|
StateServiceInitOptions,
|
||||||
|
|
|
@ -43,6 +43,11 @@ import {
|
||||||
stateServiceFactory,
|
stateServiceFactory,
|
||||||
} from "../../../platform/background/service-factories/state-service.factory";
|
} from "../../../platform/background/service-factories/state-service.factory";
|
||||||
|
|
||||||
|
import {
|
||||||
|
UserDecryptionOptionsServiceInitOptions,
|
||||||
|
userDecryptionOptionsServiceFactory,
|
||||||
|
} from "./user-decryption-options-service.factory";
|
||||||
|
|
||||||
type DeviceTrustCryptoServiceFactoryOptions = FactoryOptions;
|
type DeviceTrustCryptoServiceFactoryOptions = FactoryOptions;
|
||||||
|
|
||||||
export type DeviceTrustCryptoServiceInitOptions = DeviceTrustCryptoServiceFactoryOptions &
|
export type DeviceTrustCryptoServiceInitOptions = DeviceTrustCryptoServiceFactoryOptions &
|
||||||
|
@ -54,7 +59,8 @@ export type DeviceTrustCryptoServiceInitOptions = DeviceTrustCryptoServiceFactor
|
||||||
AppIdServiceInitOptions &
|
AppIdServiceInitOptions &
|
||||||
DevicesApiServiceInitOptions &
|
DevicesApiServiceInitOptions &
|
||||||
I18nServiceInitOptions &
|
I18nServiceInitOptions &
|
||||||
PlatformUtilsServiceInitOptions;
|
PlatformUtilsServiceInitOptions &
|
||||||
|
UserDecryptionOptionsServiceInitOptions;
|
||||||
|
|
||||||
export function deviceTrustCryptoServiceFactory(
|
export function deviceTrustCryptoServiceFactory(
|
||||||
cache: { deviceTrustCryptoService?: DeviceTrustCryptoServiceAbstraction } & CachedServices,
|
cache: { deviceTrustCryptoService?: DeviceTrustCryptoServiceAbstraction } & CachedServices,
|
||||||
|
@ -75,6 +81,7 @@ export function deviceTrustCryptoServiceFactory(
|
||||||
await devicesApiServiceFactory(cache, opts),
|
await devicesApiServiceFactory(cache, opts),
|
||||||
await i18nServiceFactory(cache, opts),
|
await i18nServiceFactory(cache, opts),
|
||||||
await platformUtilsServiceFactory(cache, opts),
|
await platformUtilsServiceFactory(cache, opts),
|
||||||
|
await userDecryptionOptionsServiceFactory(cache, opts),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,10 @@ import {
|
||||||
ApiServiceInitOptions,
|
ApiServiceInitOptions,
|
||||||
} from "../../../platform/background/service-factories/api-service.factory";
|
} from "../../../platform/background/service-factories/api-service.factory";
|
||||||
import { appIdServiceFactory } from "../../../platform/background/service-factories/app-id-service.factory";
|
import { appIdServiceFactory } from "../../../platform/background/service-factories/app-id-service.factory";
|
||||||
import { billingAccountProfileStateServiceFactory } from "../../../platform/background/service-factories/billing-account-profile-state-service.factory";
|
import {
|
||||||
|
billingAccountProfileStateServiceFactory,
|
||||||
|
BillingAccountProfileStateServiceInitOptions,
|
||||||
|
} from "../../../platform/background/service-factories/billing-account-profile-state-service.factory";
|
||||||
import {
|
import {
|
||||||
CryptoServiceInitOptions,
|
CryptoServiceInitOptions,
|
||||||
cryptoServiceFactory,
|
cryptoServiceFactory,
|
||||||
|
@ -70,6 +73,10 @@ import {
|
||||||
} from "./key-connector-service.factory";
|
} from "./key-connector-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 {
|
||||||
|
internalUserDecryptionOptionServiceFactory,
|
||||||
|
UserDecryptionOptionsServiceInitOptions,
|
||||||
|
} from "./user-decryption-options-service.factory";
|
||||||
|
|
||||||
type LoginStrategyServiceFactoryOptions = FactoryOptions;
|
type LoginStrategyServiceFactoryOptions = FactoryOptions;
|
||||||
|
|
||||||
|
@ -90,7 +97,9 @@ export type LoginStrategyServiceInitOptions = LoginStrategyServiceFactoryOptions
|
||||||
PasswordStrengthServiceInitOptions &
|
PasswordStrengthServiceInitOptions &
|
||||||
DeviceTrustCryptoServiceInitOptions &
|
DeviceTrustCryptoServiceInitOptions &
|
||||||
AuthRequestServiceInitOptions &
|
AuthRequestServiceInitOptions &
|
||||||
GlobalStateProviderInitOptions;
|
UserDecryptionOptionsServiceInitOptions &
|
||||||
|
GlobalStateProviderInitOptions &
|
||||||
|
BillingAccountProfileStateServiceInitOptions;
|
||||||
|
|
||||||
export function loginStrategyServiceFactory(
|
export function loginStrategyServiceFactory(
|
||||||
cache: { loginStrategyService?: LoginStrategyServiceAbstraction } & CachedServices,
|
cache: { loginStrategyService?: LoginStrategyServiceAbstraction } & CachedServices,
|
||||||
|
@ -119,6 +128,7 @@ export function loginStrategyServiceFactory(
|
||||||
await policyServiceFactory(cache, opts),
|
await policyServiceFactory(cache, opts),
|
||||||
await deviceTrustCryptoServiceFactory(cache, opts),
|
await deviceTrustCryptoServiceFactory(cache, opts),
|
||||||
await authRequestServiceFactory(cache, opts),
|
await authRequestServiceFactory(cache, opts),
|
||||||
|
await internalUserDecryptionOptionServiceFactory(cache, opts),
|
||||||
await globalStateProviderFactory(cache, opts),
|
await globalStateProviderFactory(cache, opts),
|
||||||
await billingAccountProfileStateServiceFactory(cache, opts),
|
await billingAccountProfileStateServiceFactory(cache, opts),
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import {
|
||||||
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
|
UserDecryptionOptionsService,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CachedServices,
|
||||||
|
factory,
|
||||||
|
FactoryOptions,
|
||||||
|
} from "../../../platform/background/service-factories/factory-options";
|
||||||
|
import {
|
||||||
|
stateProviderFactory,
|
||||||
|
StateProviderInitOptions,
|
||||||
|
} from "../../../platform/background/service-factories/state-provider.factory";
|
||||||
|
|
||||||
|
type UserDecryptionOptionsServiceFactoryOptions = FactoryOptions;
|
||||||
|
|
||||||
|
export type UserDecryptionOptionsServiceInitOptions = UserDecryptionOptionsServiceFactoryOptions &
|
||||||
|
StateProviderInitOptions;
|
||||||
|
|
||||||
|
export function userDecryptionOptionsServiceFactory(
|
||||||
|
cache: {
|
||||||
|
userDecryptionOptionsService?: InternalUserDecryptionOptionsServiceAbstraction;
|
||||||
|
} & CachedServices,
|
||||||
|
opts: UserDecryptionOptionsServiceInitOptions,
|
||||||
|
): Promise<UserDecryptionOptionsServiceAbstraction> {
|
||||||
|
return factory(
|
||||||
|
cache,
|
||||||
|
"userDecryptionOptionsService",
|
||||||
|
opts,
|
||||||
|
async () => new UserDecryptionOptionsService(await stateProviderFactory(cache, opts)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function internalUserDecryptionOptionServiceFactory(
|
||||||
|
cache: {
|
||||||
|
userDecryptionOptionsService?: InternalUserDecryptionOptionsServiceAbstraction;
|
||||||
|
} & CachedServices,
|
||||||
|
opts: UserDecryptionOptionsServiceInitOptions,
|
||||||
|
): Promise<InternalUserDecryptionOptionsServiceAbstraction> {
|
||||||
|
return (await userDecryptionOptionsServiceFactory(
|
||||||
|
cache,
|
||||||
|
opts,
|
||||||
|
)) as InternalUserDecryptionOptionsServiceAbstraction;
|
||||||
|
}
|
|
@ -32,6 +32,10 @@ import {
|
||||||
} from "../../../platform/background/service-factories/state-service.factory";
|
} from "../../../platform/background/service-factories/state-service.factory";
|
||||||
|
|
||||||
import { PinCryptoServiceInitOptions, pinCryptoServiceFactory } from "./pin-crypto-service.factory";
|
import { PinCryptoServiceInitOptions, pinCryptoServiceFactory } from "./pin-crypto-service.factory";
|
||||||
|
import {
|
||||||
|
userDecryptionOptionsServiceFactory,
|
||||||
|
UserDecryptionOptionsServiceInitOptions,
|
||||||
|
} from "./user-decryption-options-service.factory";
|
||||||
import {
|
import {
|
||||||
UserVerificationApiServiceInitOptions,
|
UserVerificationApiServiceInitOptions,
|
||||||
userVerificationApiServiceFactory,
|
userVerificationApiServiceFactory,
|
||||||
|
@ -44,6 +48,7 @@ export type UserVerificationServiceInitOptions = UserVerificationServiceFactoryO
|
||||||
CryptoServiceInitOptions &
|
CryptoServiceInitOptions &
|
||||||
I18nServiceInitOptions &
|
I18nServiceInitOptions &
|
||||||
UserVerificationApiServiceInitOptions &
|
UserVerificationApiServiceInitOptions &
|
||||||
|
UserDecryptionOptionsServiceInitOptions &
|
||||||
PinCryptoServiceInitOptions &
|
PinCryptoServiceInitOptions &
|
||||||
LogServiceInitOptions &
|
LogServiceInitOptions &
|
||||||
VaultTimeoutSettingsServiceInitOptions &
|
VaultTimeoutSettingsServiceInitOptions &
|
||||||
|
@ -63,6 +68,7 @@ export function userVerificationServiceFactory(
|
||||||
await cryptoServiceFactory(cache, opts),
|
await cryptoServiceFactory(cache, opts),
|
||||||
await i18nServiceFactory(cache, opts),
|
await i18nServiceFactory(cache, opts),
|
||||||
await userVerificationApiServiceFactory(cache, opts),
|
await userVerificationApiServiceFactory(cache, opts),
|
||||||
|
await userDecryptionOptionsServiceFactory(cache, opts),
|
||||||
await pinCryptoServiceFactory(cache, opts),
|
await pinCryptoServiceFactory(cache, opts),
|
||||||
await logServiceFactory(cache, opts),
|
await logServiceFactory(cache, opts),
|
||||||
await vaultTimeoutSettingsServiceFactory(cache, opts),
|
await vaultTimeoutSettingsServiceFactory(cache, opts),
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Component } from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
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 { 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 { 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";
|
||||||
|
@ -37,6 +38,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
|
||||||
route: ActivatedRoute,
|
route: ActivatedRoute,
|
||||||
organizationApiService: OrganizationApiServiceAbstraction,
|
organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
organizationUserService: OrganizationUserService,
|
organizationUserService: OrganizationUserService,
|
||||||
|
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
ssoLoginService: SsoLoginServiceAbstraction,
|
ssoLoginService: SsoLoginServiceAbstraction,
|
||||||
dialogService: DialogService,
|
dialogService: DialogService,
|
||||||
) {
|
) {
|
||||||
|
@ -55,6 +57,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
|
||||||
stateService,
|
stateService,
|
||||||
organizationApiService,
|
organizationApiService,
|
||||||
organizationUserService,
|
organizationUserService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
ssoLoginService,
|
ssoLoginService,
|
||||||
dialogService,
|
dialogService,
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,7 +3,10 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||||
|
|
||||||
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
||||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
import {
|
||||||
|
LoginStrategyServiceAbstraction,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||||
|
@ -39,6 +42,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||||
syncService: SyncService,
|
syncService: SyncService,
|
||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
|
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
configService: ConfigServiceAbstraction,
|
configService: ConfigServiceAbstraction,
|
||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
@Inject(WINDOW) private win: Window,
|
@Inject(WINDOW) private win: Window,
|
||||||
|
@ -56,6 +60,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||||
environmentService,
|
environmentService,
|
||||||
passwordGenerationService,
|
passwordGenerationService,
|
||||||
logService,
|
logService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
configService,
|
configService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,10 @@ import { filter, first, takeUntil } from "rxjs/operators";
|
||||||
|
|
||||||
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
|
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
|
||||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
import {
|
||||||
|
LoginStrategyServiceAbstraction,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||||
|
@ -55,6 +58,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService,
|
||||||
appIdService: AppIdService,
|
appIdService: AppIdService,
|
||||||
loginService: LoginService,
|
loginService: LoginService,
|
||||||
|
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
configService: ConfigServiceAbstraction,
|
configService: ConfigServiceAbstraction,
|
||||||
ssoLoginService: SsoLoginServiceAbstraction,
|
ssoLoginService: SsoLoginServiceAbstraction,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
|
@ -75,6 +79,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
appIdService,
|
appIdService,
|
||||||
loginService,
|
loginService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
ssoLoginService,
|
ssoLoginService,
|
||||||
configService,
|
configService,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,8 @@ import {
|
||||||
PinCryptoService,
|
PinCryptoService,
|
||||||
LoginStrategyServiceAbstraction,
|
LoginStrategyServiceAbstraction,
|
||||||
LoginStrategyService,
|
LoginStrategyService,
|
||||||
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
|
UserDecryptionOptionsService,
|
||||||
AuthRequestServiceAbstraction,
|
AuthRequestServiceAbstraction,
|
||||||
AuthRequestService,
|
AuthRequestService,
|
||||||
} from "@bitwarden/auth/common";
|
} from "@bitwarden/auth/common";
|
||||||
|
@ -242,6 +244,7 @@ export default class MainBackground {
|
||||||
environmentService: BrowserEnvironmentService;
|
environmentService: BrowserEnvironmentService;
|
||||||
cipherService: CipherServiceAbstraction;
|
cipherService: CipherServiceAbstraction;
|
||||||
folderService: InternalFolderServiceAbstraction;
|
folderService: InternalFolderServiceAbstraction;
|
||||||
|
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction;
|
||||||
collectionService: CollectionServiceAbstraction;
|
collectionService: CollectionServiceAbstraction;
|
||||||
vaultTimeoutService: VaultTimeoutService;
|
vaultTimeoutService: VaultTimeoutService;
|
||||||
vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction;
|
vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction;
|
||||||
|
@ -539,6 +542,8 @@ export default class MainBackground {
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
|
||||||
|
|
||||||
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
|
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
|
||||||
this.deviceTrustCryptoService = new DeviceTrustCryptoService(
|
this.deviceTrustCryptoService = new DeviceTrustCryptoService(
|
||||||
this.keyGenerationService,
|
this.keyGenerationService,
|
||||||
|
@ -550,6 +555,7 @@ export default class MainBackground {
|
||||||
this.devicesApiService,
|
this.devicesApiService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.devicesService = new DevicesServiceImplementation(this.devicesApiService);
|
this.devicesService = new DevicesServiceImplementation(this.devicesApiService);
|
||||||
|
@ -590,6 +596,7 @@ export default class MainBackground {
|
||||||
this.policyService,
|
this.policyService,
|
||||||
this.deviceTrustCryptoService,
|
this.deviceTrustCryptoService,
|
||||||
this.authRequestService,
|
this.authRequestService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.globalStateProvider,
|
this.globalStateProvider,
|
||||||
this.billingAccountProfileStateService,
|
this.billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
|
@ -631,6 +638,7 @@ export default class MainBackground {
|
||||||
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
||||||
|
|
||||||
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
|
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.tokenService,
|
this.tokenService,
|
||||||
this.policyService,
|
this.policyService,
|
||||||
|
@ -650,6 +658,7 @@ export default class MainBackground {
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.userVerificationApiService,
|
this.userVerificationApiService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.pinCryptoService,
|
this.pinCryptoService,
|
||||||
this.logService,
|
this.logService,
|
||||||
this.vaultTimeoutSettingsService,
|
this.vaultTimeoutSettingsService,
|
||||||
|
@ -717,6 +726,7 @@ export default class MainBackground {
|
||||||
this.folderApiService,
|
this.folderApiService,
|
||||||
this.organizationService,
|
this.organizationService,
|
||||||
this.sendApiService,
|
this.sendApiService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.avatarService,
|
this.avatarService,
|
||||||
logoutCallback,
|
logoutCallback,
|
||||||
this.billingAccountProfileStateService,
|
this.billingAccountProfileStateService,
|
||||||
|
|
|
@ -9,6 +9,10 @@ import {
|
||||||
tokenServiceFactory,
|
tokenServiceFactory,
|
||||||
TokenServiceInitOptions,
|
TokenServiceInitOptions,
|
||||||
} from "../../auth/background/service-factories/token-service.factory";
|
} from "../../auth/background/service-factories/token-service.factory";
|
||||||
|
import {
|
||||||
|
userDecryptionOptionsServiceFactory,
|
||||||
|
UserDecryptionOptionsServiceInitOptions,
|
||||||
|
} from "../../auth/background/service-factories/user-decryption-options-service.factory";
|
||||||
import {
|
import {
|
||||||
biometricStateServiceFactory,
|
biometricStateServiceFactory,
|
||||||
BiometricStateServiceInitOptions,
|
BiometricStateServiceInitOptions,
|
||||||
|
@ -30,6 +34,7 @@ import {
|
||||||
type VaultTimeoutSettingsServiceFactoryOptions = FactoryOptions;
|
type VaultTimeoutSettingsServiceFactoryOptions = FactoryOptions;
|
||||||
|
|
||||||
export type VaultTimeoutSettingsServiceInitOptions = VaultTimeoutSettingsServiceFactoryOptions &
|
export type VaultTimeoutSettingsServiceInitOptions = VaultTimeoutSettingsServiceFactoryOptions &
|
||||||
|
UserDecryptionOptionsServiceInitOptions &
|
||||||
CryptoServiceInitOptions &
|
CryptoServiceInitOptions &
|
||||||
TokenServiceInitOptions &
|
TokenServiceInitOptions &
|
||||||
PolicyServiceInitOptions &
|
PolicyServiceInitOptions &
|
||||||
|
@ -46,6 +51,7 @@ export function vaultTimeoutSettingsServiceFactory(
|
||||||
opts,
|
opts,
|
||||||
async () =>
|
async () =>
|
||||||
new VaultTimeoutSettingsService(
|
new VaultTimeoutSettingsService(
|
||||||
|
await userDecryptionOptionsServiceFactory(cache, opts),
|
||||||
await cryptoServiceFactory(cache, opts),
|
await cryptoServiceFactory(cache, opts),
|
||||||
await tokenServiceFactory(cache, opts),
|
await tokenServiceFactory(cache, opts),
|
||||||
await policyServiceFactory(cache, opts),
|
await policyServiceFactory(cache, opts),
|
||||||
|
|
|
@ -5,11 +5,13 @@ import { program } from "commander";
|
||||||
import * as jsdom from "jsdom";
|
import * as jsdom from "jsdom";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
AuthRequestService,
|
AuthRequestService,
|
||||||
LoginStrategyService,
|
LoginStrategyService,
|
||||||
LoginStrategyServiceAbstraction,
|
LoginStrategyServiceAbstraction,
|
||||||
PinCryptoService,
|
PinCryptoService,
|
||||||
PinCryptoServiceAbstraction,
|
PinCryptoServiceAbstraction,
|
||||||
|
UserDecryptionOptionsService,
|
||||||
} from "@bitwarden/auth/common";
|
} from "@bitwarden/auth/common";
|
||||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||||
|
@ -169,6 +171,7 @@ export class Main {
|
||||||
eventUploadService: EventUploadServiceAbstraction;
|
eventUploadService: EventUploadServiceAbstraction;
|
||||||
passwordGenerationService: PasswordGenerationServiceAbstraction;
|
passwordGenerationService: PasswordGenerationServiceAbstraction;
|
||||||
passwordStrengthService: PasswordStrengthServiceAbstraction;
|
passwordStrengthService: PasswordStrengthServiceAbstraction;
|
||||||
|
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction;
|
||||||
totpService: TotpService;
|
totpService: TotpService;
|
||||||
containerService: ContainerService;
|
containerService: ContainerService;
|
||||||
auditService: AuditService;
|
auditService: AuditService;
|
||||||
|
@ -436,6 +439,8 @@ export class Main {
|
||||||
this.stateService,
|
this.stateService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
|
||||||
|
|
||||||
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
|
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
|
||||||
this.deviceTrustCryptoService = new DeviceTrustCryptoService(
|
this.deviceTrustCryptoService = new DeviceTrustCryptoService(
|
||||||
this.keyGenerationService,
|
this.keyGenerationService,
|
||||||
|
@ -447,6 +452,7 @@ export class Main {
|
||||||
this.devicesApiService,
|
this.devicesApiService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.authRequestService = new AuthRequestService(
|
this.authRequestService = new AuthRequestService(
|
||||||
|
@ -478,6 +484,7 @@ export class Main {
|
||||||
this.policyService,
|
this.policyService,
|
||||||
this.deviceTrustCryptoService,
|
this.deviceTrustCryptoService,
|
||||||
this.authRequestService,
|
this.authRequestService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.globalStateProvider,
|
this.globalStateProvider,
|
||||||
this.billingAccountProfileStateService,
|
this.billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
|
@ -529,6 +536,7 @@ export class Main {
|
||||||
this.biometricStateService = new DefaultBiometricStateService(this.stateProvider);
|
this.biometricStateService = new DefaultBiometricStateService(this.stateProvider);
|
||||||
|
|
||||||
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
|
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.tokenService,
|
this.tokenService,
|
||||||
this.policyService,
|
this.policyService,
|
||||||
|
@ -548,6 +556,7 @@ export class Main {
|
||||||
this.cryptoService,
|
this.cryptoService,
|
||||||
this.i18nService,
|
this.i18nService,
|
||||||
this.userVerificationApiService,
|
this.userVerificationApiService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.pinCryptoService,
|
this.pinCryptoService,
|
||||||
this.logService,
|
this.logService,
|
||||||
this.vaultTimeoutSettingsService,
|
this.vaultTimeoutSettingsService,
|
||||||
|
@ -589,6 +598,7 @@ export class Main {
|
||||||
this.folderApiService,
|
this.folderApiService,
|
||||||
this.organizationService,
|
this.organizationService,
|
||||||
this.sendApiService,
|
this.sendApiService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.avatarService,
|
this.avatarService,
|
||||||
async (expired: boolean) => await this.logout(),
|
async (expired: boolean) => await this.logout(),
|
||||||
this.billingAccountProfileStateService,
|
this.billingAccountProfileStateService,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Component, NgZone, OnDestroy } from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
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 { 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 { 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";
|
||||||
|
@ -44,6 +45,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
organizationApiService: OrganizationApiServiceAbstraction,
|
organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
organizationUserService: OrganizationUserService,
|
organizationUserService: OrganizationUserService,
|
||||||
|
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
ssoLoginService: SsoLoginServiceAbstraction,
|
ssoLoginService: SsoLoginServiceAbstraction,
|
||||||
dialogService: DialogService,
|
dialogService: DialogService,
|
||||||
) {
|
) {
|
||||||
|
@ -62,6 +64,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
|
||||||
stateService,
|
stateService,
|
||||||
organizationApiService,
|
organizationApiService,
|
||||||
organizationUserService,
|
organizationUserService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
ssoLoginService,
|
ssoLoginService,
|
||||||
dialogService,
|
dialogService,
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,10 @@ import { Component } from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
|
|
||||||
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
||||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
import {
|
||||||
|
LoginStrategyServiceAbstraction,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
|
@ -34,6 +37,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
|
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
configService: ConfigServiceAbstraction,
|
configService: ConfigServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
|
@ -49,6 +53,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||||
environmentService,
|
environmentService,
|
||||||
passwordGenerationService,
|
passwordGenerationService,
|
||||||
logService,
|
logService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
configService,
|
configService,
|
||||||
);
|
);
|
||||||
super.onSuccessfulLogin = async () => {
|
super.onSuccessfulLogin = async () => {
|
||||||
|
|
|
@ -4,7 +4,10 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
|
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
|
||||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
import {
|
||||||
|
LoginStrategyServiceAbstraction,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||||
|
@ -53,6 +56,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService,
|
||||||
appIdService: AppIdService,
|
appIdService: AppIdService,
|
||||||
loginService: LoginService,
|
loginService: LoginService,
|
||||||
|
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
ssoLoginService: SsoLoginServiceAbstraction,
|
ssoLoginService: SsoLoginServiceAbstraction,
|
||||||
configService: ConfigServiceAbstraction,
|
configService: ConfigServiceAbstraction,
|
||||||
@Inject(WINDOW) protected win: Window,
|
@Inject(WINDOW) protected win: Window,
|
||||||
|
@ -71,6 +75,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
appIdService,
|
appIdService,
|
||||||
loginService,
|
loginService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
ssoLoginService,
|
ssoLoginService,
|
||||||
configService,
|
configService,
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,7 +3,10 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { first } from "rxjs/operators";
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
||||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
import {
|
||||||
|
LoginStrategyServiceAbstraction,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
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";
|
||||||
|
@ -41,6 +44,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
private orgDomainApiService: OrgDomainApiServiceAbstraction,
|
private orgDomainApiService: OrgDomainApiServiceAbstraction,
|
||||||
private validationService: ValidationService,
|
private validationService: ValidationService,
|
||||||
|
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
configService: ConfigServiceAbstraction,
|
configService: ConfigServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
|
@ -56,6 +60,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||||
environmentService,
|
environmentService,
|
||||||
passwordGenerationService,
|
passwordGenerationService,
|
||||||
logService,
|
logService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
configService,
|
configService,
|
||||||
);
|
);
|
||||||
this.redirectUri = window.location.origin + "/sso-connector.html";
|
this.redirectUri = window.location.origin + "/sso-connector.html";
|
||||||
|
|
|
@ -4,7 +4,10 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
|
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
|
||||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
import {
|
||||||
|
LoginStrategyServiceAbstraction,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||||
|
@ -44,6 +47,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
|
||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService,
|
||||||
appIdService: AppIdService,
|
appIdService: AppIdService,
|
||||||
loginService: LoginService,
|
loginService: LoginService,
|
||||||
|
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
ssoLoginService: SsoLoginServiceAbstraction,
|
ssoLoginService: SsoLoginServiceAbstraction,
|
||||||
configService: ConfigServiceAbstraction,
|
configService: ConfigServiceAbstraction,
|
||||||
@Inject(WINDOW) protected win: Window,
|
@Inject(WINDOW) protected win: Window,
|
||||||
|
@ -62,6 +66,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
appIdService,
|
appIdService,
|
||||||
loginService,
|
loginService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
ssoLoginService,
|
ssoLoginService,
|
||||||
configService,
|
configService,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { combineLatest, map, Observable, Subject, takeUntil } from "rxjs";
|
import { combineLatest, map, Observable, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
|
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
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 { 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";
|
||||||
|
@ -12,7 +13,6 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
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 { 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";
|
||||||
|
|
||||||
|
@ -44,8 +44,8 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
private organizationUserService: OrganizationUserService,
|
private organizationUserService: OrganizationUserService,
|
||||||
|
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private stateService: StateService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
@ -56,7 +56,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
|
||||||
combineLatest([
|
combineLatest([
|
||||||
this.organization$,
|
this.organization$,
|
||||||
resetPasswordPolicies$,
|
resetPasswordPolicies$,
|
||||||
this.stateService.getAccountDecryptionOptions(),
|
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||||
])
|
])
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe(([organization, resetPasswordPolicies, decryptionOptions]) => {
|
.subscribe(([organization, resetPasswordPolicies, decryptionOptions]) => {
|
||||||
|
|
|
@ -14,6 +14,10 @@ import {
|
||||||
throwError,
|
throwError,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
UserDecryptionOptions,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
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 { 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";
|
||||||
|
@ -30,7 +34,6 @@ 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 { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
NewUser,
|
NewUser,
|
||||||
|
@ -88,6 +91,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||||
protected validationService: ValidationService,
|
protected validationService: ValidationService,
|
||||||
protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
|
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
protected passwordResetEnrollmentService: PasswordResetEnrollmentServiceAbstraction,
|
protected passwordResetEnrollmentService: PasswordResetEnrollmentServiceAbstraction,
|
||||||
protected ssoLoginService: SsoLoginServiceAbstraction,
|
protected ssoLoginService: SsoLoginServiceAbstraction,
|
||||||
) {}
|
) {}
|
||||||
|
@ -101,14 +105,15 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||||
await this.setRememberDeviceDefaultValue();
|
await this.setRememberDeviceDefaultValue();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const accountDecryptionOptions: AccountDecryptionOptions =
|
const userDecryptionOptions = await firstValueFrom(
|
||||||
await this.stateService.getAccountDecryptionOptions();
|
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||||
|
);
|
||||||
|
|
||||||
// see sso-login.strategy - to determine if a user is new or not it just checks if there is a key on the token response..
|
// see sso-login.strategy - to determine if a user is new or not it just checks if there is a key on the token response..
|
||||||
// can we check if they have a user key or master key in crypto service? Would that be sufficient?
|
// can we check if they have a user key or master key in crypto service? Would that be sufficient?
|
||||||
if (
|
if (
|
||||||
!accountDecryptionOptions?.trustedDeviceOption?.hasAdminApproval &&
|
!userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval &&
|
||||||
!accountDecryptionOptions?.hasMasterPassword
|
!userDecryptionOptions?.hasMasterPassword
|
||||||
) {
|
) {
|
||||||
// We are dealing with a new account if:
|
// We are dealing with a new account if:
|
||||||
// - User does not have admin approval (i.e. has not enrolled into admin reset)
|
// - User does not have admin approval (i.e. has not enrolled into admin reset)
|
||||||
|
@ -118,7 +123,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.loadNewUserData();
|
this.loadNewUserData();
|
||||||
} else {
|
} else {
|
||||||
this.loadUntrustedDeviceData(accountDecryptionOptions);
|
this.loadUntrustedDeviceData(userDecryptionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: this is probably not a comprehensive write up of all scenarios:
|
// Note: this is probably not a comprehensive write up of all scenarios:
|
||||||
|
@ -195,7 +200,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadUntrustedDeviceData(accountDecryptionOptions: AccountDecryptionOptions) {
|
loadUntrustedDeviceData(userDecryptionOptions: UserDecryptionOptions) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
const email$ = from(this.stateService.getEmail()).pipe(
|
const email$ = from(this.stateService.getEmail()).pipe(
|
||||||
|
@ -215,13 +220,12 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||||
)
|
)
|
||||||
.subscribe((email) => {
|
.subscribe((email) => {
|
||||||
const showApproveFromOtherDeviceBtn =
|
const showApproveFromOtherDeviceBtn =
|
||||||
accountDecryptionOptions?.trustedDeviceOption?.hasLoginApprovingDevice || false;
|
userDecryptionOptions?.trustedDeviceOption?.hasLoginApprovingDevice || false;
|
||||||
|
|
||||||
const showReqAdminApprovalBtn =
|
const showReqAdminApprovalBtn =
|
||||||
!!accountDecryptionOptions?.trustedDeviceOption?.hasAdminApproval || false;
|
!!userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval || false;
|
||||||
|
|
||||||
const showApproveWithMasterPasswordBtn =
|
const showApproveWithMasterPasswordBtn = userDecryptionOptions?.hasMasterPassword || false;
|
||||||
accountDecryptionOptions?.hasMasterPassword || false;
|
|
||||||
|
|
||||||
const userEmail = email;
|
const userEmail = email;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { Directive } from "@angular/core";
|
import { Directive } from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { of } from "rxjs";
|
import { firstValueFrom, of } from "rxjs";
|
||||||
import { filter, first, switchMap, tap } from "rxjs/operators";
|
import { filter, first, switchMap, tap } from "rxjs/operators";
|
||||||
|
|
||||||
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
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 { 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";
|
||||||
|
@ -26,7 +27,6 @@ import {
|
||||||
DEFAULT_KDF_CONFIG,
|
DEFAULT_KDF_CONFIG,
|
||||||
} from "@bitwarden/common/platform/enums";
|
} from "@bitwarden/common/platform/enums";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { AccountDecryptionOptions } 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 { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||||
|
@ -64,6 +64,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
private organizationUserService: OrganizationUserService,
|
private organizationUserService: OrganizationUserService,
|
||||||
|
private userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
private ssoLoginService: SsoLoginServiceAbstraction,
|
private ssoLoginService: SsoLoginServiceAbstraction,
|
||||||
dialogService: DialogService,
|
dialogService: DialogService,
|
||||||
) {
|
) {
|
||||||
|
@ -228,11 +229,11 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||||
await this.stateService.setForceSetPasswordReason(ForceSetPasswordReason.None);
|
await this.stateService.setForceSetPasswordReason(ForceSetPasswordReason.None);
|
||||||
|
|
||||||
// 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 acctDecryptionOpts: AccountDecryptionOptions =
|
const userDecryptionOpts = await firstValueFrom(
|
||||||
await this.stateService.getAccountDecryptionOptions();
|
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||||
|
);
|
||||||
acctDecryptionOpts.hasMasterPassword = true;
|
userDecryptionOpts.hasMasterPassword = true;
|
||||||
await this.stateService.setAccountDecryptionOptions(acctDecryptionOpts);
|
await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts);
|
||||||
|
|
||||||
await this.stateService.setKdfType(this.kdf);
|
await this.stateService.setKdfType(this.kdf);
|
||||||
await this.stateService.setKdfConfig(this.kdfConfig);
|
await this.stateService.setKdfConfig(this.kdfConfig);
|
||||||
|
|
|
@ -2,16 +2,20 @@ import { Component } from "@angular/core";
|
||||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { MockProxy, mock } from "jest-mock-extended";
|
import { MockProxy, mock } from "jest-mock-extended";
|
||||||
import { Observable, of } from "rxjs";
|
import { BehaviorSubject, Observable, of } from "rxjs";
|
||||||
|
|
||||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
import {
|
||||||
|
FakeKeyConnectorUserDecryptionOption as KeyConnectorUserDecryptionOption,
|
||||||
|
LoginStrategyServiceAbstraction,
|
||||||
|
FakeTrustedDeviceUserDecryptionOption as TrustedDeviceUserDecryptionOption,
|
||||||
|
FakeUserDecryptionOptions as UserDecryptionOptions,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
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 { KeyConnectorUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/key-connector-user-decryption-option";
|
|
||||||
import { TrustedDeviceUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
|
|
||||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
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";
|
||||||
|
@ -19,7 +23,6 @@ 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 { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
|
|
||||||
import { SsoComponent } from "./sso.component";
|
import { SsoComponent } from "./sso.component";
|
||||||
|
@ -62,6 +65,7 @@ describe("SsoComponent", () => {
|
||||||
let mockEnvironmentService: MockProxy<EnvironmentService>;
|
let mockEnvironmentService: MockProxy<EnvironmentService>;
|
||||||
let mockPasswordGenerationService: MockProxy<PasswordGenerationServiceAbstraction>;
|
let mockPasswordGenerationService: MockProxy<PasswordGenerationServiceAbstraction>;
|
||||||
let mockLogService: MockProxy<LogService>;
|
let mockLogService: MockProxy<LogService>;
|
||||||
|
let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
|
||||||
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
||||||
|
|
||||||
// Mock authService.logIn params
|
// Mock authService.logIn params
|
||||||
|
@ -77,17 +81,19 @@ describe("SsoComponent", () => {
|
||||||
let mockOnSuccessfulLoginForceResetNavigate: jest.Mock;
|
let mockOnSuccessfulLoginForceResetNavigate: jest.Mock;
|
||||||
let mockOnSuccessfulLoginTdeNavigate: jest.Mock;
|
let mockOnSuccessfulLoginTdeNavigate: jest.Mock;
|
||||||
|
|
||||||
let mockAcctDecryptionOpts: {
|
let mockUserDecryptionOpts: {
|
||||||
noMasterPassword: AccountDecryptionOptions;
|
noMasterPassword: UserDecryptionOptions;
|
||||||
withMasterPassword: AccountDecryptionOptions;
|
withMasterPassword: UserDecryptionOptions;
|
||||||
withMasterPasswordAndTrustedDevice: AccountDecryptionOptions;
|
withMasterPasswordAndTrustedDevice: UserDecryptionOptions;
|
||||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
withMasterPasswordAndTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||||
withMasterPasswordAndKeyConnector: AccountDecryptionOptions;
|
withMasterPasswordAndKeyConnector: UserDecryptionOptions;
|
||||||
noMasterPasswordWithTrustedDevice: AccountDecryptionOptions;
|
noMasterPasswordWithTrustedDevice: UserDecryptionOptions;
|
||||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
noMasterPasswordWithTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||||
noMasterPasswordWithKeyConnector: AccountDecryptionOptions;
|
noMasterPasswordWithKeyConnector: UserDecryptionOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let selectedUserDecryptionOptions: BehaviorSubject<UserDecryptionOptions>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Mock Services
|
// Mock Services
|
||||||
mockLoginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
mockLoginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
||||||
|
@ -109,6 +115,7 @@ describe("SsoComponent", () => {
|
||||||
mockEnvironmentService = mock<EnvironmentService>();
|
mockEnvironmentService = mock<EnvironmentService>();
|
||||||
mockPasswordGenerationService = mock<PasswordGenerationServiceAbstraction>();
|
mockPasswordGenerationService = mock<PasswordGenerationServiceAbstraction>();
|
||||||
mockLogService = mock<LogService>();
|
mockLogService = mock<LogService>();
|
||||||
|
mockUserDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>();
|
||||||
mockConfigService = mock<ConfigServiceAbstraction>();
|
mockConfigService = mock<ConfigServiceAbstraction>();
|
||||||
|
|
||||||
// Mock loginStrategyService.logIn params
|
// Mock loginStrategyService.logIn params
|
||||||
|
@ -124,49 +131,52 @@ describe("SsoComponent", () => {
|
||||||
mockOnSuccessfulLoginForceResetNavigate = jest.fn();
|
mockOnSuccessfulLoginForceResetNavigate = jest.fn();
|
||||||
mockOnSuccessfulLoginTdeNavigate = jest.fn();
|
mockOnSuccessfulLoginTdeNavigate = jest.fn();
|
||||||
|
|
||||||
mockAcctDecryptionOpts = {
|
mockUserDecryptionOpts = {
|
||||||
noMasterPassword: new AccountDecryptionOptions({
|
noMasterPassword: new UserDecryptionOptions({
|
||||||
hasMasterPassword: false,
|
hasMasterPassword: false,
|
||||||
trustedDeviceOption: undefined,
|
trustedDeviceOption: undefined,
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
withMasterPassword: new AccountDecryptionOptions({
|
withMasterPassword: new UserDecryptionOptions({
|
||||||
hasMasterPassword: true,
|
hasMasterPassword: true,
|
||||||
trustedDeviceOption: undefined,
|
trustedDeviceOption: undefined,
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
withMasterPasswordAndTrustedDevice: new AccountDecryptionOptions({
|
withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({
|
||||||
hasMasterPassword: true,
|
hasMasterPassword: true,
|
||||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||||
hasMasterPassword: true,
|
hasMasterPassword: true,
|
||||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
withMasterPasswordAndKeyConnector: new AccountDecryptionOptions({
|
withMasterPasswordAndKeyConnector: new UserDecryptionOptions({
|
||||||
hasMasterPassword: true,
|
hasMasterPassword: true,
|
||||||
trustedDeviceOption: undefined,
|
trustedDeviceOption: undefined,
|
||||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||||
}),
|
}),
|
||||||
noMasterPasswordWithTrustedDevice: new AccountDecryptionOptions({
|
noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({
|
||||||
hasMasterPassword: false,
|
hasMasterPassword: false,
|
||||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||||
hasMasterPassword: false,
|
hasMasterPassword: false,
|
||||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
noMasterPasswordWithKeyConnector: new AccountDecryptionOptions({
|
noMasterPasswordWithKeyConnector: new UserDecryptionOptions({
|
||||||
hasMasterPassword: false,
|
hasMasterPassword: false,
|
||||||
trustedDeviceOption: undefined,
|
trustedDeviceOption: undefined,
|
||||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
selectedUserDecryptionOptions = new BehaviorSubject<UserDecryptionOptions>(null);
|
||||||
|
mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions;
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [TestSsoComponent],
|
declarations: [TestSsoComponent],
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -183,6 +193,10 @@ describe("SsoComponent", () => {
|
||||||
{ provide: EnvironmentService, useValue: mockEnvironmentService },
|
{ provide: EnvironmentService, useValue: mockEnvironmentService },
|
||||||
{ provide: PasswordGenerationServiceAbstraction, useValue: mockPasswordGenerationService },
|
{ provide: PasswordGenerationServiceAbstraction, useValue: mockPasswordGenerationService },
|
||||||
|
|
||||||
|
{
|
||||||
|
provide: UserDecryptionOptionsServiceAbstraction,
|
||||||
|
useValue: mockUserDecryptionOptionsService,
|
||||||
|
},
|
||||||
{ provide: LogService, useValue: mockLogService },
|
{ provide: LogService, useValue: mockLogService },
|
||||||
{ provide: ConfigServiceAbstraction, useValue: mockConfigService },
|
{ provide: ConfigServiceAbstraction, useValue: mockConfigService },
|
||||||
],
|
],
|
||||||
|
@ -230,9 +244,7 @@ describe("SsoComponent", () => {
|
||||||
authResult.twoFactorProviders = new Map([[TwoFactorProviderType.Authenticator, {}]]);
|
authResult.twoFactorProviders = new Map([[TwoFactorProviderType.Authenticator, {}]]);
|
||||||
|
|
||||||
// use standard user with MP because this test is not concerned with password reset.
|
// use standard user with MP because this test is not concerned with password reset.
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||||
mockAcctDecryptionOpts.withMasterPassword,
|
|
||||||
);
|
|
||||||
|
|
||||||
mockLoginStrategyService.logIn.mockResolvedValue(authResult);
|
mockLoginStrategyService.logIn.mockResolvedValue(authResult);
|
||||||
});
|
});
|
||||||
|
@ -341,8 +353,8 @@ describe("SsoComponent", () => {
|
||||||
describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => {
|
describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => {
|
||||||
let authResult;
|
let authResult;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(
|
||||||
mockAcctDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
mockUserDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
||||||
);
|
);
|
||||||
|
|
||||||
authResult = new AuthResult();
|
authResult = new AuthResult();
|
||||||
|
@ -377,8 +389,8 @@ describe("SsoComponent", () => {
|
||||||
const reasonString = ForceSetPasswordReason[forceResetPasswordReason];
|
const reasonString = ForceSetPasswordReason[forceResetPasswordReason];
|
||||||
let authResult;
|
let authResult;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(
|
||||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||||
);
|
);
|
||||||
|
|
||||||
authResult = new AuthResult();
|
authResult = new AuthResult();
|
||||||
|
@ -394,8 +406,8 @@ describe("SsoComponent", () => {
|
||||||
describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is not required", () => {
|
describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is not required", () => {
|
||||||
let authResult;
|
let authResult;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(
|
||||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||||
);
|
);
|
||||||
|
|
||||||
authResult = new AuthResult();
|
authResult = new AuthResult();
|
||||||
|
@ -440,9 +452,7 @@ describe("SsoComponent", () => {
|
||||||
describe("Given user needs to set a master password", () => {
|
describe("Given user needs to set a master password", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Only need to test the case where the user has no master password to test the primary change mp flow here
|
// Only need to test the case where the user has no master password to test the primary change mp flow here
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPassword);
|
||||||
mockAcctDecryptionOpts.noMasterPassword,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testChangePasswordOnSuccessfulLogin();
|
testChangePasswordOnSuccessfulLogin();
|
||||||
|
@ -450,9 +460,7 @@ describe("SsoComponent", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not navigate to the change password route when the user has key connector even if user has no master password", async () => {
|
it("does not navigate to the change password route when the user has key connector even if user has no master password", async () => {
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPasswordWithKeyConnector);
|
||||||
mockAcctDecryptionOpts.noMasterPasswordWithKeyConnector,
|
|
||||||
);
|
|
||||||
|
|
||||||
await _component.logIn(code, codeVerifier, orgIdFromState);
|
await _component.logIn(code, codeVerifier, orgIdFromState);
|
||||||
expect(mockLoginStrategyService.logIn).toHaveBeenCalledTimes(1);
|
expect(mockLoginStrategyService.logIn).toHaveBeenCalledTimes(1);
|
||||||
|
@ -475,9 +483,7 @@ describe("SsoComponent", () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// use standard user with MP because this test is not concerned with password reset.
|
// use standard user with MP because this test is not concerned with password reset.
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||||
mockAcctDecryptionOpts.withMasterPassword,
|
|
||||||
);
|
|
||||||
|
|
||||||
const authResult = new AuthResult();
|
const authResult = new AuthResult();
|
||||||
authResult.forcePasswordReset = forceResetPasswordReason;
|
authResult.forcePasswordReset = forceResetPasswordReason;
|
||||||
|
@ -494,9 +500,7 @@ describe("SsoComponent", () => {
|
||||||
const authResult = new AuthResult();
|
const authResult = new AuthResult();
|
||||||
authResult.twoFactorProviders = null;
|
authResult.twoFactorProviders = null;
|
||||||
// use standard user with MP because this test is not concerned with password reset.
|
// use standard user with MP because this test is not concerned with password reset.
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||||
mockAcctDecryptionOpts.withMasterPassword,
|
|
||||||
);
|
|
||||||
authResult.forcePasswordReset = ForceSetPasswordReason.None;
|
authResult.forcePasswordReset = ForceSetPasswordReason.None;
|
||||||
mockLoginStrategyService.logIn.mockResolvedValue(authResult);
|
mockLoginStrategyService.logIn.mockResolvedValue(authResult);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
import { Directive } from "@angular/core";
|
import { Directive } from "@angular/core";
|
||||||
import { ActivatedRoute, NavigationExtras, Router } from "@angular/router";
|
import { ActivatedRoute, NavigationExtras, Router } from "@angular/router";
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
import { first } from "rxjs/operators";
|
import { first } from "rxjs/operators";
|
||||||
|
|
||||||
import { LoginStrategyServiceAbstraction, SsoLoginCredentials } from "@bitwarden/auth/common";
|
import {
|
||||||
|
LoginStrategyServiceAbstraction,
|
||||||
|
SsoLoginCredentials,
|
||||||
|
TrustedDeviceUserDecryptionOption,
|
||||||
|
UserDecryptionOptions,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
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";
|
||||||
import { TrustedDeviceUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
|
|
||||||
import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response";
|
import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response";
|
||||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
|
@ -17,7 +23,6 @@ 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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
|
@ -59,6 +64,7 @@ export class SsoComponent {
|
||||||
protected environmentService: EnvironmentService,
|
protected environmentService: EnvironmentService,
|
||||||
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
|
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
protected logService: LogService,
|
protected logService: LogService,
|
||||||
|
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
protected configService: ConfigServiceAbstraction,
|
protected configService: ConfigServiceAbstraction,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -194,9 +200,6 @@ export class SsoComponent {
|
||||||
this.formPromise = this.loginStrategyService.logIn(credentials);
|
this.formPromise = this.loginStrategyService.logIn(credentials);
|
||||||
const authResult = await this.formPromise;
|
const authResult = await this.formPromise;
|
||||||
|
|
||||||
const acctDecryptionOpts: AccountDecryptionOptions =
|
|
||||||
await this.stateService.getAccountDecryptionOptions();
|
|
||||||
|
|
||||||
if (authResult.requiresTwoFactor) {
|
if (authResult.requiresTwoFactor) {
|
||||||
return await this.handleTwoFactorRequired(orgSsoIdentifier);
|
return await this.handleTwoFactorRequired(orgSsoIdentifier);
|
||||||
}
|
}
|
||||||
|
@ -217,15 +220,20 @@ export class SsoComponent {
|
||||||
return await this.handleForcePasswordReset(orgSsoIdentifier);
|
return await this.handleForcePasswordReset(orgSsoIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// must come after 2fa check since user decryption options aren't available if 2fa is required
|
||||||
|
const userDecryptionOpts = await firstValueFrom(
|
||||||
|
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||||
|
);
|
||||||
|
|
||||||
const tdeEnabled = await this.isTrustedDeviceEncEnabled(
|
const tdeEnabled = await this.isTrustedDeviceEncEnabled(
|
||||||
acctDecryptionOpts.trustedDeviceOption,
|
userDecryptionOpts.trustedDeviceOption,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tdeEnabled) {
|
if (tdeEnabled) {
|
||||||
return await this.handleTrustedDeviceEncryptionEnabled(
|
return await this.handleTrustedDeviceEncryptionEnabled(
|
||||||
authResult,
|
authResult,
|
||||||
orgSsoIdentifier,
|
orgSsoIdentifier,
|
||||||
acctDecryptionOpts,
|
userDecryptionOpts,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,8 +241,8 @@ export class SsoComponent {
|
||||||
// have one and they aren't using key connector.
|
// have one and they aren't using key connector.
|
||||||
// Note: TDE & Key connector are mutually exclusive org config options.
|
// Note: TDE & Key connector are mutually exclusive org config options.
|
||||||
const requireSetPassword =
|
const requireSetPassword =
|
||||||
!acctDecryptionOpts.hasMasterPassword &&
|
!userDecryptionOpts.hasMasterPassword &&
|
||||||
acctDecryptionOpts.keyConnectorOption === undefined;
|
userDecryptionOpts.keyConnectorOption === undefined;
|
||||||
|
|
||||||
if (requireSetPassword || authResult.resetMasterPassword) {
|
if (requireSetPassword || authResult.resetMasterPassword) {
|
||||||
// Change implies going no password -> password in this case
|
// Change implies going no password -> password in this case
|
||||||
|
@ -270,12 +278,12 @@ export class SsoComponent {
|
||||||
private async handleTrustedDeviceEncryptionEnabled(
|
private async handleTrustedDeviceEncryptionEnabled(
|
||||||
authResult: AuthResult,
|
authResult: AuthResult,
|
||||||
orgIdentifier: string,
|
orgIdentifier: string,
|
||||||
acctDecryptionOpts: AccountDecryptionOptions,
|
userDecryptionOpts: UserDecryptionOptions,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||||
if (
|
if (
|
||||||
!acctDecryptionOpts.hasMasterPassword &&
|
!userDecryptionOpts.hasMasterPassword &&
|
||||||
acctDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
||||||
) {
|
) {
|
||||||
// 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
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
import { ActivatedRoute, Router, convertToParamMap } from "@angular/router";
|
import { ActivatedRoute, convertToParamMap, Router } from "@angular/router";
|
||||||
import { MockProxy, mock } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
import {
|
||||||
|
FakeKeyConnectorUserDecryptionOption as KeyConnectorUserDecryptionOption,
|
||||||
|
LoginStrategyServiceAbstraction,
|
||||||
|
FakeTrustedDeviceUserDecryptionOption as TrustedDeviceUserDecryptionOption,
|
||||||
|
FakeUserDecryptionOptions as UserDecryptionOptions,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
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 { KeyConnectorUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/key-connector-user-decryption-option";
|
|
||||||
import { TrustedDeviceUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
|
|
||||||
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 { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
|
@ -22,7 +27,6 @@ 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 { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
|
||||||
|
|
||||||
import { TwoFactorComponent } from "./two-factor.component";
|
import { TwoFactorComponent } from "./two-factor.component";
|
||||||
|
|
||||||
|
@ -56,20 +60,23 @@ describe("TwoFactorComponent", () => {
|
||||||
let mockTwoFactorService: MockProxy<TwoFactorService>;
|
let mockTwoFactorService: MockProxy<TwoFactorService>;
|
||||||
let mockAppIdService: MockProxy<AppIdService>;
|
let mockAppIdService: MockProxy<AppIdService>;
|
||||||
let mockLoginService: MockProxy<LoginService>;
|
let mockLoginService: MockProxy<LoginService>;
|
||||||
|
let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
|
||||||
let mockSsoLoginService: MockProxy<SsoLoginServiceAbstraction>;
|
let mockSsoLoginService: MockProxy<SsoLoginServiceAbstraction>;
|
||||||
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
||||||
|
|
||||||
let mockAcctDecryptionOpts: {
|
let mockUserDecryptionOpts: {
|
||||||
noMasterPassword: AccountDecryptionOptions;
|
noMasterPassword: UserDecryptionOptions;
|
||||||
withMasterPassword: AccountDecryptionOptions;
|
withMasterPassword: UserDecryptionOptions;
|
||||||
withMasterPasswordAndTrustedDevice: AccountDecryptionOptions;
|
withMasterPasswordAndTrustedDevice: UserDecryptionOptions;
|
||||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
withMasterPasswordAndTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||||
withMasterPasswordAndKeyConnector: AccountDecryptionOptions;
|
withMasterPasswordAndKeyConnector: UserDecryptionOptions;
|
||||||
noMasterPasswordWithTrustedDevice: AccountDecryptionOptions;
|
noMasterPasswordWithTrustedDevice: UserDecryptionOptions;
|
||||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
noMasterPasswordWithTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||||
noMasterPasswordWithKeyConnector: AccountDecryptionOptions;
|
noMasterPasswordWithKeyConnector: UserDecryptionOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let selectedUserDecryptionOptions: BehaviorSubject<UserDecryptionOptions>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockLoginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
mockLoginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
||||||
mockRouter = mock<Router>();
|
mockRouter = mock<Router>();
|
||||||
|
@ -83,52 +90,56 @@ describe("TwoFactorComponent", () => {
|
||||||
mockTwoFactorService = mock<TwoFactorService>();
|
mockTwoFactorService = mock<TwoFactorService>();
|
||||||
mockAppIdService = mock<AppIdService>();
|
mockAppIdService = mock<AppIdService>();
|
||||||
mockLoginService = mock<LoginService>();
|
mockLoginService = mock<LoginService>();
|
||||||
|
mockUserDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>();
|
||||||
mockSsoLoginService = mock<SsoLoginServiceAbstraction>();
|
mockSsoLoginService = mock<SsoLoginServiceAbstraction>();
|
||||||
mockConfigService = mock<ConfigServiceAbstraction>();
|
mockConfigService = mock<ConfigServiceAbstraction>();
|
||||||
|
|
||||||
mockAcctDecryptionOpts = {
|
mockUserDecryptionOpts = {
|
||||||
noMasterPassword: new AccountDecryptionOptions({
|
noMasterPassword: new UserDecryptionOptions({
|
||||||
hasMasterPassword: false,
|
hasMasterPassword: false,
|
||||||
trustedDeviceOption: undefined,
|
trustedDeviceOption: undefined,
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
withMasterPassword: new AccountDecryptionOptions({
|
withMasterPassword: new UserDecryptionOptions({
|
||||||
hasMasterPassword: true,
|
hasMasterPassword: true,
|
||||||
trustedDeviceOption: undefined,
|
trustedDeviceOption: undefined,
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
withMasterPasswordAndTrustedDevice: new AccountDecryptionOptions({
|
withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({
|
||||||
hasMasterPassword: true,
|
hasMasterPassword: true,
|
||||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||||
hasMasterPassword: true,
|
hasMasterPassword: true,
|
||||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
withMasterPasswordAndKeyConnector: new AccountDecryptionOptions({
|
withMasterPasswordAndKeyConnector: new UserDecryptionOptions({
|
||||||
hasMasterPassword: true,
|
hasMasterPassword: true,
|
||||||
trustedDeviceOption: undefined,
|
trustedDeviceOption: undefined,
|
||||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||||
}),
|
}),
|
||||||
noMasterPasswordWithTrustedDevice: new AccountDecryptionOptions({
|
noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({
|
||||||
hasMasterPassword: false,
|
hasMasterPassword: false,
|
||||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||||
hasMasterPassword: false,
|
hasMasterPassword: false,
|
||||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||||
keyConnectorOption: undefined,
|
keyConnectorOption: undefined,
|
||||||
}),
|
}),
|
||||||
noMasterPasswordWithKeyConnector: new AccountDecryptionOptions({
|
noMasterPasswordWithKeyConnector: new UserDecryptionOptions({
|
||||||
hasMasterPassword: false,
|
hasMasterPassword: false,
|
||||||
trustedDeviceOption: undefined,
|
trustedDeviceOption: undefined,
|
||||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
selectedUserDecryptionOptions = new BehaviorSubject<UserDecryptionOptions>(null);
|
||||||
|
mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions;
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [TestTwoFactorComponent],
|
declarations: [TestTwoFactorComponent],
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -153,6 +164,10 @@ describe("TwoFactorComponent", () => {
|
||||||
{ provide: TwoFactorService, useValue: mockTwoFactorService },
|
{ provide: TwoFactorService, useValue: mockTwoFactorService },
|
||||||
{ provide: AppIdService, useValue: mockAppIdService },
|
{ provide: AppIdService, useValue: mockAppIdService },
|
||||||
{ provide: LoginService, useValue: mockLoginService },
|
{ provide: LoginService, useValue: mockLoginService },
|
||||||
|
{
|
||||||
|
provide: UserDecryptionOptionsServiceAbstraction,
|
||||||
|
useValue: mockUserDecryptionOptionsService,
|
||||||
|
},
|
||||||
{ provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService },
|
{ provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService },
|
||||||
{ provide: ConfigServiceAbstraction, useValue: mockConfigService },
|
{ provide: ConfigServiceAbstraction, useValue: mockConfigService },
|
||||||
],
|
],
|
||||||
|
@ -213,9 +228,7 @@ describe("TwoFactorComponent", () => {
|
||||||
component.remember = remember;
|
component.remember = remember;
|
||||||
component.captchaToken = captchaToken;
|
component.captchaToken = captchaToken;
|
||||||
|
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||||
mockAcctDecryptionOpts.withMasterPassword,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls authService.logInTwoFactor with correct parameters when form is submitted", async () => {
|
it("calls authService.logInTwoFactor with correct parameters when form is submitted", async () => {
|
||||||
|
@ -289,17 +302,15 @@ describe("TwoFactorComponent", () => {
|
||||||
describe("Given user needs to set a master password", () => {
|
describe("Given user needs to set a master password", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Only need to test the case where the user has no master password to test the primary change mp flow here
|
// Only need to test the case where the user has no master password to test the primary change mp flow here
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPassword);
|
||||||
mockAcctDecryptionOpts.noMasterPassword,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testChangePasswordOnSuccessfulLogin();
|
testChangePasswordOnSuccessfulLogin();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not navigate to the change password route when the user has key connector even if user has no master password", async () => {
|
it("does not navigate to the change password route when the user has key connector even if user has no master password", async () => {
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(
|
||||||
mockAcctDecryptionOpts.noMasterPasswordWithKeyConnector,
|
mockUserDecryptionOpts.noMasterPasswordWithKeyConnector,
|
||||||
);
|
);
|
||||||
|
|
||||||
await component.doSubmit();
|
await component.doSubmit();
|
||||||
|
@ -321,9 +332,7 @@ describe("TwoFactorComponent", () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// use standard user with MP because this test is not concerned with password reset.
|
// use standard user with MP because this test is not concerned with password reset.
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||||
mockAcctDecryptionOpts.withMasterPassword,
|
|
||||||
);
|
|
||||||
|
|
||||||
const authResult = new AuthResult();
|
const authResult = new AuthResult();
|
||||||
authResult.forcePasswordReset = forceResetPasswordReason;
|
authResult.forcePasswordReset = forceResetPasswordReason;
|
||||||
|
@ -385,8 +394,8 @@ describe("TwoFactorComponent", () => {
|
||||||
|
|
||||||
describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => {
|
describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(
|
||||||
mockAcctDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
mockUserDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
||||||
);
|
);
|
||||||
|
|
||||||
const authResult = new AuthResult();
|
const authResult = new AuthResult();
|
||||||
|
@ -420,8 +429,8 @@ describe("TwoFactorComponent", () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// use standard user with MP because this test is not concerned with password reset.
|
// use standard user with MP because this test is not concerned with password reset.
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(
|
||||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||||
);
|
);
|
||||||
|
|
||||||
const authResult = new AuthResult();
|
const authResult = new AuthResult();
|
||||||
|
@ -436,8 +445,8 @@ describe("TwoFactorComponent", () => {
|
||||||
describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is not required", () => {
|
describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is not required", () => {
|
||||||
let authResult;
|
let authResult;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
selectedUserDecryptionOptions.next(
|
||||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||||
);
|
);
|
||||||
|
|
||||||
authResult = new AuthResult();
|
authResult = new AuthResult();
|
||||||
|
|
|
@ -6,7 +6,12 @@ import { first } from "rxjs/operators";
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
|
import {
|
||||||
|
LoginStrategyServiceAbstraction,
|
||||||
|
TrustedDeviceUserDecryptionOption,
|
||||||
|
UserDecryptionOptions,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||||
|
@ -15,7 +20,6 @@ import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-
|
||||||
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 { TrustedDeviceUserDecryptionOption } from "@bitwarden/common/auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
|
|
||||||
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 { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
|
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
|
||||||
import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service";
|
import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service";
|
||||||
|
@ -27,7 +31,6 @@ 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 { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
|
||||||
|
|
||||||
import { CaptchaProtectedComponent } from "./captcha-protected.component";
|
import { CaptchaProtectedComponent } from "./captcha-protected.component";
|
||||||
|
|
||||||
|
@ -86,6 +89,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||||
protected twoFactorService: TwoFactorService,
|
protected twoFactorService: TwoFactorService,
|
||||||
protected appIdService: AppIdService,
|
protected appIdService: AppIdService,
|
||||||
protected loginService: LoginService,
|
protected loginService: LoginService,
|
||||||
|
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
protected ssoLoginService: SsoLoginServiceAbstraction,
|
protected ssoLoginService: SsoLoginServiceAbstraction,
|
||||||
protected configService: ConfigServiceAbstraction,
|
protected configService: ConfigServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
|
@ -290,22 +294,23 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||||
return await this.handleForcePasswordReset(this.orgIdentifier);
|
return await this.handleForcePasswordReset(this.orgIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
const acctDecryptionOpts: AccountDecryptionOptions =
|
const userDecryptionOpts = await firstValueFrom(
|
||||||
await this.stateService.getAccountDecryptionOptions();
|
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||||
|
);
|
||||||
|
|
||||||
const tdeEnabled = await this.isTrustedDeviceEncEnabled(acctDecryptionOpts.trustedDeviceOption);
|
const tdeEnabled = await this.isTrustedDeviceEncEnabled(userDecryptionOpts.trustedDeviceOption);
|
||||||
|
|
||||||
if (tdeEnabled) {
|
if (tdeEnabled) {
|
||||||
return await this.handleTrustedDeviceEncryptionEnabled(
|
return await this.handleTrustedDeviceEncryptionEnabled(
|
||||||
authResult,
|
authResult,
|
||||||
this.orgIdentifier,
|
this.orgIdentifier,
|
||||||
acctDecryptionOpts,
|
userDecryptionOpts,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// User must set password if they don't have one and they aren't using either TDE or key connector.
|
// User must set password if they don't have one and they aren't using either TDE or key connector.
|
||||||
const requireSetPassword =
|
const requireSetPassword =
|
||||||
!acctDecryptionOpts.hasMasterPassword && acctDecryptionOpts.keyConnectorOption === undefined;
|
!userDecryptionOpts.hasMasterPassword && userDecryptionOpts.keyConnectorOption === undefined;
|
||||||
|
|
||||||
if (requireSetPassword || authResult.resetMasterPassword) {
|
if (requireSetPassword || authResult.resetMasterPassword) {
|
||||||
// Change implies going no password -> password in this case
|
// Change implies going no password -> password in this case
|
||||||
|
@ -326,12 +331,12 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||||
private async handleTrustedDeviceEncryptionEnabled(
|
private async handleTrustedDeviceEncryptionEnabled(
|
||||||
authResult: AuthResult,
|
authResult: AuthResult,
|
||||||
orgIdentifier: string,
|
orgIdentifier: string,
|
||||||
acctDecryptionOpts: AccountDecryptionOptions,
|
userDecryptionOpts: UserDecryptionOptions,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||||
if (
|
if (
|
||||||
!acctDecryptionOpts.hasMasterPassword &&
|
!userDecryptionOpts.hasMasterPassword &&
|
||||||
acctDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
||||||
) {
|
) {
|
||||||
// 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
|
||||||
|
|
|
@ -53,7 +53,7 @@ export function lockGuard(): CanActivateFn {
|
||||||
|
|
||||||
// User is authN and in locked state.
|
// User is authN and in locked state.
|
||||||
|
|
||||||
const tdeEnabled = await deviceTrustCryptoService.supportsDeviceTrust();
|
const tdeEnabled = await firstValueFrom(deviceTrustCryptoService.supportsDeviceTrust$);
|
||||||
|
|
||||||
// Create special exception which allows users to go from the login-initiated page to the lock page for the approve w/ MP flow
|
// Create special exception which allows users to go from the login-initiated page to the lock page for the approve w/ MP flow
|
||||||
// The MP check is necessary to prevent direct manual navigation from other locked state pages for users who don't have a MP
|
// The MP check is necessary to prevent direct manual navigation from other locked state pages for users who don't have a MP
|
||||||
|
|
|
@ -46,7 +46,7 @@ export function redirectGuard(overrides: Partial<RedirectRoutes> = {}): CanActiv
|
||||||
|
|
||||||
// If locked, TDE is enabled, and the user hasn't decrypted yet, then redirect to the
|
// If locked, TDE is enabled, and the user hasn't decrypted yet, then redirect to the
|
||||||
// login decryption options component.
|
// login decryption options component.
|
||||||
const tdeEnabled = await deviceTrustCryptoService.supportsDeviceTrust();
|
const tdeEnabled = await firstValueFrom(deviceTrustCryptoService.supportsDeviceTrust$);
|
||||||
const everHadUserKey = await firstValueFrom(cryptoService.everHadUserKey$);
|
const everHadUserKey = await firstValueFrom(cryptoService.everHadUserKey$);
|
||||||
if (authStatus === AuthenticationStatus.Locked && tdeEnabled && !everHadUserKey) {
|
if (authStatus === AuthenticationStatus.Locked && tdeEnabled && !everHadUserKey) {
|
||||||
return router.createUrlTree([routes.notDecrypted], { queryParams: route.queryParams });
|
return router.createUrlTree([routes.notDecrypted], { queryParams: route.queryParams });
|
||||||
|
|
|
@ -26,7 +26,7 @@ export function tdeDecryptionRequiredGuard(): CanActivateFn {
|
||||||
const router = inject(Router);
|
const router = inject(Router);
|
||||||
|
|
||||||
const authStatus = await authService.getAuthStatus();
|
const authStatus = await authService.getAuthStatus();
|
||||||
const tdeEnabled = await deviceTrustCryptoService.supportsDeviceTrust();
|
const tdeEnabled = await firstValueFrom(deviceTrustCryptoService.supportsDeviceTrust$);
|
||||||
const everHadUserKey = await firstValueFrom(cryptoService.everHadUserKey$);
|
const everHadUserKey = await firstValueFrom(cryptoService.everHadUserKey$);
|
||||||
if (authStatus !== AuthenticationStatus.Locked || !tdeEnabled || everHadUserKey) {
|
if (authStatus !== AuthenticationStatus.Locked || !tdeEnabled || everHadUserKey) {
|
||||||
return router.createUrlTree(["/"]);
|
return router.createUrlTree(["/"]);
|
||||||
|
|
|
@ -8,6 +8,9 @@ import {
|
||||||
PinCryptoService,
|
PinCryptoService,
|
||||||
LoginStrategyServiceAbstraction,
|
LoginStrategyServiceAbstraction,
|
||||||
LoginStrategyService,
|
LoginStrategyService,
|
||||||
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
|
UserDecryptionOptionsService,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
} from "@bitwarden/auth/common";
|
} from "@bitwarden/auth/common";
|
||||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
||||||
|
@ -243,8 +246,8 @@ import { safeProvider, SafeProvider } from "../platform/utils/safe-provider";
|
||||||
import {
|
import {
|
||||||
LOCALES_DIRECTORY,
|
LOCALES_DIRECTORY,
|
||||||
LOCKED_CALLBACK,
|
LOCKED_CALLBACK,
|
||||||
LOG_MAC_FAILURES,
|
|
||||||
LOGOUT_CALLBACK,
|
LOGOUT_CALLBACK,
|
||||||
|
LOG_MAC_FAILURES,
|
||||||
MEMORY_STORAGE,
|
MEMORY_STORAGE,
|
||||||
OBSERVABLE_DISK_STORAGE,
|
OBSERVABLE_DISK_STORAGE,
|
||||||
OBSERVABLE_MEMORY_STORAGE,
|
OBSERVABLE_MEMORY_STORAGE,
|
||||||
|
@ -369,6 +372,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||||
PolicyServiceAbstraction,
|
PolicyServiceAbstraction,
|
||||||
DeviceTrustCryptoServiceAbstraction,
|
DeviceTrustCryptoServiceAbstraction,
|
||||||
AuthRequestServiceAbstraction,
|
AuthRequestServiceAbstraction,
|
||||||
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
GlobalStateProvider,
|
GlobalStateProvider,
|
||||||
BillingAccountProfileStateService,
|
BillingAccountProfileStateService,
|
||||||
],
|
],
|
||||||
|
@ -477,6 +481,15 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||||
useClass: EnvironmentService,
|
useClass: EnvironmentService,
|
||||||
deps: [StateProvider, AccountServiceAbstraction],
|
deps: [StateProvider, AccountServiceAbstraction],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
|
useClass: UserDecryptionOptionsService,
|
||||||
|
deps: [StateProvider],
|
||||||
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: UserDecryptionOptionsServiceAbstraction,
|
||||||
|
useExisting: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: TotpServiceAbstraction,
|
provide: TotpServiceAbstraction,
|
||||||
useClass: TotpService,
|
useClass: TotpService,
|
||||||
|
@ -577,6 +590,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||||
FolderApiServiceAbstraction,
|
FolderApiServiceAbstraction,
|
||||||
InternalOrganizationServiceAbstraction,
|
InternalOrganizationServiceAbstraction,
|
||||||
SendApiServiceAbstraction,
|
SendApiServiceAbstraction,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
AvatarServiceAbstraction,
|
AvatarServiceAbstraction,
|
||||||
LOGOUT_CALLBACK,
|
LOGOUT_CALLBACK,
|
||||||
BillingAccountProfileStateService,
|
BillingAccountProfileStateService,
|
||||||
|
@ -587,6 +601,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||||
provide: VaultTimeoutSettingsServiceAbstraction,
|
provide: VaultTimeoutSettingsServiceAbstraction,
|
||||||
useClass: VaultTimeoutSettingsService,
|
useClass: VaultTimeoutSettingsService,
|
||||||
deps: [
|
deps: [
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
CryptoServiceAbstraction,
|
CryptoServiceAbstraction,
|
||||||
TokenServiceAbstraction,
|
TokenServiceAbstraction,
|
||||||
PolicyServiceAbstraction,
|
PolicyServiceAbstraction,
|
||||||
|
@ -765,6 +780,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||||
CryptoServiceAbstraction,
|
CryptoServiceAbstraction,
|
||||||
I18nServiceAbstraction,
|
I18nServiceAbstraction,
|
||||||
UserVerificationApiServiceAbstraction,
|
UserVerificationApiServiceAbstraction,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
PinCryptoServiceAbstraction,
|
PinCryptoServiceAbstraction,
|
||||||
LogService,
|
LogService,
|
||||||
VaultTimeoutSettingsServiceAbstraction,
|
VaultTimeoutSettingsServiceAbstraction,
|
||||||
|
@ -902,6 +918,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||||
DevicesApiServiceAbstraction,
|
DevicesApiServiceAbstraction,
|
||||||
I18nServiceAbstraction,
|
I18nServiceAbstraction,
|
||||||
PlatformUtilsServiceAbstraction,
|
PlatformUtilsServiceAbstraction,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export * from "./pin-crypto.service.abstraction";
|
export * from "./pin-crypto.service.abstraction";
|
||||||
export * from "./login-strategy.service";
|
export * from "./login-strategy.service";
|
||||||
|
export * from "./user-decryption-options.service.abstraction";
|
||||||
export * from "./auth-request.service.abstraction";
|
export * from "./auth-request.service.abstraction";
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
import { UserDecryptionOptions } from "../models";
|
||||||
|
|
||||||
|
export abstract class UserDecryptionOptionsServiceAbstraction {
|
||||||
|
/**
|
||||||
|
* Returns what decryption options are available for the current user.
|
||||||
|
* @remark This is sent from the server on authentication.
|
||||||
|
*/
|
||||||
|
abstract userDecryptionOptions$: Observable<UserDecryptionOptions>;
|
||||||
|
/**
|
||||||
|
* Uses user decryption options to determine if current user has a master password.
|
||||||
|
* @remark This is sent from the server, and does not indicate if the master password
|
||||||
|
* was used to login and/or if a master key is saved locally.
|
||||||
|
*/
|
||||||
|
abstract hasMasterPassword$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user decryption options for the given user id.
|
||||||
|
* @param userId The user id to check.
|
||||||
|
*/
|
||||||
|
abstract userDecryptionOptionsById$(userId: string): Observable<UserDecryptionOptions>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class InternalUserDecryptionOptionsServiceAbstraction extends UserDecryptionOptionsServiceAbstraction {
|
||||||
|
/**
|
||||||
|
* Sets the current decryption options for the user, contains the current configuration
|
||||||
|
* of the users account related to how they can decrypt their vault.
|
||||||
|
* @remark Intended to be used when user decryption options are received from server, does
|
||||||
|
* not update the server. Consider syncing instead of updating locally.
|
||||||
|
* @param userDecryptionOptions Current user decryption options received from server.
|
||||||
|
*/
|
||||||
|
abstract setUserDecryptionOptions(userDecryptionOptions: UserDecryptionOptions): Promise<void>;
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
|
||||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||||
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 { AuthRequestLoginCredentials } from "../models/domain/login-credentials";
|
import { AuthRequestLoginCredentials } from "../models/domain/login-credentials";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -37,6 +38,7 @@ describe("AuthRequestLoginStrategy", () => {
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
let stateService: MockProxy<StateService>;
|
let stateService: MockProxy<StateService>;
|
||||||
let twoFactorService: MockProxy<TwoFactorService>;
|
let twoFactorService: MockProxy<TwoFactorService>;
|
||||||
|
let userDecryptionOptions: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||||
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
|
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
|
||||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||||
|
|
||||||
|
@ -65,6 +67,7 @@ describe("AuthRequestLoginStrategy", () => {
|
||||||
logService = mock<LogService>();
|
logService = mock<LogService>();
|
||||||
stateService = mock<StateService>();
|
stateService = mock<StateService>();
|
||||||
twoFactorService = mock<TwoFactorService>();
|
twoFactorService = mock<TwoFactorService>();
|
||||||
|
userDecryptionOptions = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||||
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
|
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
|
||||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||||
|
|
||||||
|
@ -83,6 +86,7 @@ describe("AuthRequestLoginStrategy", () => {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptions,
|
||||||
deviceTrustCryptoService,
|
deviceTrustCryptoService,
|
||||||
billingAccountProfileStateService,
|
billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
|
||||||
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||||
import { AuthRequestLoginCredentials } from "../models/domain/login-credentials";
|
import { AuthRequestLoginCredentials } from "../models/domain/login-credentials";
|
||||||
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||||
|
|
||||||
|
@ -54,6 +55,7 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService,
|
||||||
|
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||||
) {
|
) {
|
||||||
|
@ -67,6 +69,7 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
billingAccountProfileStateService,
|
billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ import {
|
||||||
AccountProfile,
|
AccountProfile,
|
||||||
AccountTokens,
|
AccountTokens,
|
||||||
AccountKeys,
|
AccountKeys,
|
||||||
AccountDecryptionOptions,
|
|
||||||
} 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";
|
||||||
|
@ -39,8 +38,10 @@ import {
|
||||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||||
import { UserKey, MasterKey, DeviceKey } from "@bitwarden/common/types/key";
|
import { UserKey, MasterKey, DeviceKey } from "@bitwarden/common/types/key";
|
||||||
|
|
||||||
import { LoginStrategyServiceAbstraction } from "../abstractions/login-strategy.service";
|
import { LoginStrategyServiceAbstraction } from "../abstractions";
|
||||||
import { PasswordLoginCredentials } from "../models/domain/login-credentials";
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||||
|
import { PasswordLoginCredentials } from "../models";
|
||||||
|
import { UserDecryptionOptions } from "../models/domain/user-decryption-options";
|
||||||
|
|
||||||
import { PasswordLoginStrategy, PasswordLoginStrategyData } from "./password-login.strategy";
|
import { PasswordLoginStrategy, PasswordLoginStrategyData } from "./password-login.strategy";
|
||||||
|
|
||||||
|
@ -108,6 +109,7 @@ describe("LoginStrategy", () => {
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
let stateService: MockProxy<StateService>;
|
let stateService: MockProxy<StateService>;
|
||||||
let twoFactorService: MockProxy<TwoFactorService>;
|
let twoFactorService: MockProxy<TwoFactorService>;
|
||||||
|
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||||
let policyService: MockProxy<PolicyService>;
|
let policyService: MockProxy<PolicyService>;
|
||||||
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
|
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
|
||||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||||
|
@ -126,7 +128,7 @@ describe("LoginStrategy", () => {
|
||||||
logService = mock<LogService>();
|
logService = mock<LogService>();
|
||||||
stateService = mock<StateService>();
|
stateService = mock<StateService>();
|
||||||
twoFactorService = mock<TwoFactorService>();
|
twoFactorService = mock<TwoFactorService>();
|
||||||
|
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||||
policyService = mock<PolicyService>();
|
policyService = mock<PolicyService>();
|
||||||
passwordStrengthService = mock<PasswordStrengthService>();
|
passwordStrengthService = mock<PasswordStrengthService>();
|
||||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||||
|
@ -146,6 +148,7 @@ describe("LoginStrategy", () => {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
passwordStrengthService,
|
passwordStrengthService,
|
||||||
policyService,
|
policyService,
|
||||||
loginStrategyService,
|
loginStrategyService,
|
||||||
|
@ -204,9 +207,11 @@ describe("LoginStrategy", () => {
|
||||||
...new AccountTokens(),
|
...new AccountTokens(),
|
||||||
},
|
},
|
||||||
keys: new AccountKeys(),
|
keys: new AccountKeys(),
|
||||||
decryptionOptions: AccountDecryptionOptions.fromResponse(idTokenResponse),
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
expect(userDecryptionOptionsService.setUserDecryptionOptions).toHaveBeenCalledWith(
|
||||||
|
UserDecryptionOptions.fromResponse(idTokenResponse),
|
||||||
|
);
|
||||||
expect(messagingService.send).toHaveBeenCalledWith("loggedIn");
|
expect(messagingService.send).toHaveBeenCalledWith("loggedIn");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -409,6 +414,7 @@ describe("LoginStrategy", () => {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
passwordStrengthService,
|
passwordStrengthService,
|
||||||
policyService,
|
policyService,
|
||||||
loginStrategyService,
|
loginStrategyService,
|
||||||
|
|
|
@ -30,9 +30,9 @@ import {
|
||||||
Account,
|
Account,
|
||||||
AccountProfile,
|
AccountProfile,
|
||||||
AccountTokens,
|
AccountTokens,
|
||||||
AccountDecryptionOptions,
|
|
||||||
} from "@bitwarden/common/platform/models/domain/account";
|
} from "@bitwarden/common/platform/models/domain/account";
|
||||||
|
|
||||||
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||||
import {
|
import {
|
||||||
UserApiLoginCredentials,
|
UserApiLoginCredentials,
|
||||||
PasswordLoginCredentials,
|
PasswordLoginCredentials,
|
||||||
|
@ -40,6 +40,7 @@ import {
|
||||||
AuthRequestLoginCredentials,
|
AuthRequestLoginCredentials,
|
||||||
WebAuthnLoginCredentials,
|
WebAuthnLoginCredentials,
|
||||||
} from "../models/domain/login-credentials";
|
} from "../models/domain/login-credentials";
|
||||||
|
import { UserDecryptionOptions } from "../models/domain/user-decryption-options";
|
||||||
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||||
|
|
||||||
type IdentityResponse = IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse;
|
type IdentityResponse = IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse;
|
||||||
|
@ -69,6 +70,7 @@ export abstract class LoginStrategy {
|
||||||
protected logService: LogService,
|
protected logService: LogService,
|
||||||
protected stateService: StateService,
|
protected stateService: StateService,
|
||||||
protected twoFactorService: TwoFactorService,
|
protected twoFactorService: TwoFactorService,
|
||||||
|
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
protected billingAccountProfileStateService: BillingAccountProfileStateService,
|
protected billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -203,11 +205,14 @@ export abstract class LoginStrategy {
|
||||||
...new AccountTokens(),
|
...new AccountTokens(),
|
||||||
},
|
},
|
||||||
keys: accountKeys,
|
keys: accountKeys,
|
||||||
decryptionOptions: AccountDecryptionOptions.fromResponse(tokenResponse),
|
|
||||||
adminAuthRequest: adminAuthRequest?.toJSON(),
|
adminAuthRequest: adminAuthRequest?.toJSON(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.userDecryptionOptionsService.setUserDecryptionOptions(
|
||||||
|
UserDecryptionOptions.fromResponse(tokenResponse),
|
||||||
|
);
|
||||||
|
|
||||||
await this.billingAccountProfileStateService.setHasPremium(accountInformation.premium, false);
|
await this.billingAccountProfileStateService.setHasPremium(accountInformation.premium, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||||
|
|
||||||
import { LoginStrategyServiceAbstraction } from "../abstractions";
|
import { LoginStrategyServiceAbstraction } from "../abstractions";
|
||||||
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||||
import { PasswordLoginCredentials } from "../models/domain/login-credentials";
|
import { PasswordLoginCredentials } from "../models/domain/login-credentials";
|
||||||
|
|
||||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||||
|
@ -60,6 +61,7 @@ describe("PasswordLoginStrategy", () => {
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
let stateService: MockProxy<StateService>;
|
let stateService: MockProxy<StateService>;
|
||||||
let twoFactorService: MockProxy<TwoFactorService>;
|
let twoFactorService: MockProxy<TwoFactorService>;
|
||||||
|
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||||
let policyService: MockProxy<PolicyService>;
|
let policyService: MockProxy<PolicyService>;
|
||||||
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
|
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
|
||||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||||
|
@ -79,6 +81,7 @@ describe("PasswordLoginStrategy", () => {
|
||||||
logService = mock<LogService>();
|
logService = mock<LogService>();
|
||||||
stateService = mock<StateService>();
|
stateService = mock<StateService>();
|
||||||
twoFactorService = mock<TwoFactorService>();
|
twoFactorService = mock<TwoFactorService>();
|
||||||
|
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||||
policyService = mock<PolicyService>();
|
policyService = mock<PolicyService>();
|
||||||
passwordStrengthService = mock<PasswordStrengthService>();
|
passwordStrengthService = mock<PasswordStrengthService>();
|
||||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||||
|
@ -108,6 +111,7 @@ describe("PasswordLoginStrategy", () => {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
passwordStrengthService,
|
passwordStrengthService,
|
||||||
policyService,
|
policyService,
|
||||||
loginStrategyService,
|
loginStrategyService,
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/pass
|
||||||
import { MasterKey } from "@bitwarden/common/types/key";
|
import { MasterKey } from "@bitwarden/common/types/key";
|
||||||
|
|
||||||
import { LoginStrategyServiceAbstraction } from "../abstractions";
|
import { LoginStrategyServiceAbstraction } from "../abstractions";
|
||||||
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||||
import { PasswordLoginCredentials } from "../models/domain/login-credentials";
|
import { PasswordLoginCredentials } from "../models/domain/login-credentials";
|
||||||
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||||
|
|
||||||
|
@ -84,6 +85,7 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
protected stateService: StateService,
|
protected stateService: StateService,
|
||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService,
|
||||||
|
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
private passwordStrengthService: PasswordStrengthServiceAbstraction,
|
private passwordStrengthService: PasswordStrengthServiceAbstraction,
|
||||||
private policyService: PolicyService,
|
private policyService: PolicyService,
|
||||||
private loginStrategyService: LoginStrategyServiceAbstraction,
|
private loginStrategyService: LoginStrategyServiceAbstraction,
|
||||||
|
@ -99,6 +101,7 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
billingAccountProfileStateService,
|
billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,10 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
|
||||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||||
import { DeviceKey, UserKey, MasterKey } from "@bitwarden/common/types/key";
|
import { DeviceKey, UserKey, MasterKey } from "@bitwarden/common/types/key";
|
||||||
|
|
||||||
import { AuthRequestServiceAbstraction } from "../abstractions";
|
import {
|
||||||
|
AuthRequestServiceAbstraction,
|
||||||
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "../abstractions";
|
||||||
import { SsoLoginCredentials } from "../models/domain/login-credentials";
|
import { SsoLoginCredentials } from "../models/domain/login-credentials";
|
||||||
|
|
||||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||||
|
@ -39,6 +42,7 @@ describe("SsoLoginStrategy", () => {
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
let stateService: MockProxy<StateService>;
|
let stateService: MockProxy<StateService>;
|
||||||
let twoFactorService: MockProxy<TwoFactorService>;
|
let twoFactorService: MockProxy<TwoFactorService>;
|
||||||
|
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||||
let keyConnectorService: MockProxy<KeyConnectorService>;
|
let keyConnectorService: MockProxy<KeyConnectorService>;
|
||||||
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
|
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
|
||||||
let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
|
let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
|
||||||
|
@ -66,6 +70,7 @@ describe("SsoLoginStrategy", () => {
|
||||||
logService = mock<LogService>();
|
logService = mock<LogService>();
|
||||||
stateService = mock<StateService>();
|
stateService = mock<StateService>();
|
||||||
twoFactorService = mock<TwoFactorService>();
|
twoFactorService = mock<TwoFactorService>();
|
||||||
|
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||||
keyConnectorService = mock<KeyConnectorService>();
|
keyConnectorService = mock<KeyConnectorService>();
|
||||||
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
|
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
|
||||||
authRequestService = mock<AuthRequestServiceAbstraction>();
|
authRequestService = mock<AuthRequestServiceAbstraction>();
|
||||||
|
@ -87,6 +92,7 @@ describe("SsoLoginStrategy", () => {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
keyConnectorService,
|
keyConnectorService,
|
||||||
deviceTrustCryptoService,
|
deviceTrustCryptoService,
|
||||||
authRequestService,
|
authRequestService,
|
||||||
|
|
|
@ -21,7 +21,10 @@ 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 { AuthRequestServiceAbstraction } from "../abstractions";
|
import {
|
||||||
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
|
AuthRequestServiceAbstraction,
|
||||||
|
} from "../abstractions";
|
||||||
import { SsoLoginCredentials } from "../models/domain/login-credentials";
|
import { SsoLoginCredentials } from "../models/domain/login-credentials";
|
||||||
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||||
|
|
||||||
|
@ -84,6 +87,7 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService,
|
||||||
|
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
private keyConnectorService: KeyConnectorService,
|
private keyConnectorService: KeyConnectorService,
|
||||||
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||||
private authRequestService: AuthRequestServiceAbstraction,
|
private authRequestService: AuthRequestServiceAbstraction,
|
||||||
|
@ -100,6 +104,7 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
billingAccountProfileStateService,
|
billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
|
||||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||||
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 { UserApiLoginCredentials } from "../models/domain/login-credentials";
|
import { UserApiLoginCredentials } from "../models/domain/login-credentials";
|
||||||
|
|
||||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||||
|
@ -35,6 +36,7 @@ describe("UserApiLoginStrategy", () => {
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
let stateService: MockProxy<StateService>;
|
let stateService: MockProxy<StateService>;
|
||||||
let twoFactorService: MockProxy<TwoFactorService>;
|
let twoFactorService: MockProxy<TwoFactorService>;
|
||||||
|
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||||
let keyConnectorService: MockProxy<KeyConnectorService>;
|
let keyConnectorService: MockProxy<KeyConnectorService>;
|
||||||
let environmentService: MockProxy<EnvironmentService>;
|
let environmentService: MockProxy<EnvironmentService>;
|
||||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||||
|
@ -57,6 +59,7 @@ describe("UserApiLoginStrategy", () => {
|
||||||
logService = mock<LogService>();
|
logService = mock<LogService>();
|
||||||
stateService = mock<StateService>();
|
stateService = mock<StateService>();
|
||||||
twoFactorService = mock<TwoFactorService>();
|
twoFactorService = mock<TwoFactorService>();
|
||||||
|
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||||
keyConnectorService = mock<KeyConnectorService>();
|
keyConnectorService = mock<KeyConnectorService>();
|
||||||
environmentService = mock<EnvironmentService>();
|
environmentService = mock<EnvironmentService>();
|
||||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||||
|
@ -76,6 +79,7 @@ describe("UserApiLoginStrategy", () => {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
environmentService,
|
environmentService,
|
||||||
keyConnectorService,
|
keyConnectorService,
|
||||||
billingAccountProfileStateService,
|
billingAccountProfileStateService,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
|
||||||
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||||
import { UserApiLoginCredentials } from "../models/domain/login-credentials";
|
import { UserApiLoginCredentials } from "../models/domain/login-credentials";
|
||||||
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ export class UserApiLoginStrategy extends LoginStrategy {
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService,
|
||||||
|
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
private environmentService: EnvironmentService,
|
private environmentService: EnvironmentService,
|
||||||
private keyConnectorService: KeyConnectorService,
|
private keyConnectorService: KeyConnectorService,
|
||||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||||
|
@ -61,6 +63,7 @@ export class UserApiLoginStrategy extends LoginStrategy {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
billingAccountProfileStateService,
|
billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
this.cache = new BehaviorSubject(data);
|
this.cache = new BehaviorSubject(data);
|
||||||
|
|
|
@ -18,6 +18,7 @@ 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 { 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 { WebAuthnLoginCredentials } from "../models/domain/login-credentials";
|
import { WebAuthnLoginCredentials } from "../models/domain/login-credentials";
|
||||||
|
|
||||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||||
|
@ -35,6 +36,7 @@ describe("WebAuthnLoginStrategy", () => {
|
||||||
let logService!: MockProxy<LogService>;
|
let logService!: MockProxy<LogService>;
|
||||||
let stateService!: MockProxy<StateService>;
|
let stateService!: MockProxy<StateService>;
|
||||||
let twoFactorService!: MockProxy<TwoFactorService>;
|
let twoFactorService!: MockProxy<TwoFactorService>;
|
||||||
|
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||||
|
|
||||||
let webAuthnLoginStrategy!: WebAuthnLoginStrategy;
|
let webAuthnLoginStrategy!: WebAuthnLoginStrategy;
|
||||||
|
@ -70,6 +72,7 @@ describe("WebAuthnLoginStrategy", () => {
|
||||||
logService = mock<LogService>();
|
logService = mock<LogService>();
|
||||||
stateService = mock<StateService>();
|
stateService = mock<StateService>();
|
||||||
twoFactorService = mock<TwoFactorService>();
|
twoFactorService = mock<TwoFactorService>();
|
||||||
|
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||||
|
|
||||||
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
||||||
|
@ -87,6 +90,7 @@ describe("WebAuthnLoginStrategy", () => {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
billingAccountProfileStateService,
|
billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { UserKey } from "@bitwarden/common/types/key";
|
import { UserKey } from "@bitwarden/common/types/key";
|
||||||
|
|
||||||
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions";
|
||||||
import { WebAuthnLoginCredentials } from "../models/domain/login-credentials";
|
import { WebAuthnLoginCredentials } from "../models/domain/login-credentials";
|
||||||
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService,
|
||||||
|
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
|
@ -61,6 +63,7 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
|
||||||
logService,
|
logService,
|
||||||
stateService,
|
stateService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
billingAccountProfileStateService,
|
billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export * from "./rotateable-key-set";
|
export * from "./rotateable-key-set";
|
||||||
export * from "./login-credentials";
|
export * from "./login-credentials";
|
||||||
|
export * from "./user-decryption-options";
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
|
import { KeyConnectorUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/key-connector-user-decryption-option.response";
|
||||||
|
import { TrustedDeviceUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response";
|
||||||
|
import { IdentityTokenResponse } from "@bitwarden/common/src/auth/models/response/identity-token.response";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key Connector decryption options. Intended to be sent to the client for use after authentication.
|
||||||
|
* @see {@link UserDecryptionOptions}
|
||||||
|
*/
|
||||||
|
export class KeyConnectorUserDecryptionOption {
|
||||||
|
/** The URL of the key connector configured for this user. */
|
||||||
|
keyConnectorUrl: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new instance of the KeyConnectorUserDecryptionOption from a response object.
|
||||||
|
* @param response The key connector user decryption option response object.
|
||||||
|
* @returns A new instance of the KeyConnectorUserDecryptionOption. Will initialize even if the response is nullish.
|
||||||
|
*/
|
||||||
|
static fromResponse(
|
||||||
|
response: KeyConnectorUserDecryptionOptionResponse,
|
||||||
|
): KeyConnectorUserDecryptionOption {
|
||||||
|
const options = new KeyConnectorUserDecryptionOption();
|
||||||
|
options.keyConnectorUrl = response?.keyConnectorUrl ?? null;
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new instance of a KeyConnectorUserDecryptionOption from a JSON object.
|
||||||
|
* @param obj JSON object to deserialize.
|
||||||
|
* @returns A new instance of the KeyConnectorUserDecryptionOption. Will initialize even if the JSON object is nullish.
|
||||||
|
*/
|
||||||
|
static fromJSON(
|
||||||
|
obj: Jsonify<KeyConnectorUserDecryptionOption>,
|
||||||
|
): KeyConnectorUserDecryptionOption {
|
||||||
|
return Object.assign(new KeyConnectorUserDecryptionOption(), obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trusted device decryption options. Intended to be sent to the client for use after authentication.
|
||||||
|
* @see {@link UserDecryptionOptions}
|
||||||
|
*/
|
||||||
|
export class TrustedDeviceUserDecryptionOption {
|
||||||
|
/** True if an admin has approved an admin auth request previously made from this device. */
|
||||||
|
hasAdminApproval: boolean;
|
||||||
|
/** True if the user has a device capable of approving an auth request. */
|
||||||
|
hasLoginApprovingDevice: boolean;
|
||||||
|
/** True if the user has manage reset password permission, as these users must be forced to have a master password. */
|
||||||
|
hasManageResetPasswordPermission: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new instance of the TrustedDeviceUserDecryptionOption from a response object.
|
||||||
|
* @param response The trusted device user decryption option response object.
|
||||||
|
* @returns A new instance of the TrustedDeviceUserDecryptionOption. Will initialize even if the response is nullish.
|
||||||
|
*/
|
||||||
|
static fromResponse(
|
||||||
|
response: TrustedDeviceUserDecryptionOptionResponse,
|
||||||
|
): TrustedDeviceUserDecryptionOption {
|
||||||
|
const options = new TrustedDeviceUserDecryptionOption();
|
||||||
|
options.hasAdminApproval = response?.hasAdminApproval ?? false;
|
||||||
|
options.hasLoginApprovingDevice = response?.hasLoginApprovingDevice ?? false;
|
||||||
|
options.hasManageResetPasswordPermission = response?.hasManageResetPasswordPermission ?? false;
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new instance of the TrustedDeviceUserDecryptionOption from a JSON object.
|
||||||
|
* @param obj JSON object to deserialize.
|
||||||
|
* @returns A new instance of the TrustedDeviceUserDecryptionOption. Will initialize even if the JSON object is nullish.
|
||||||
|
*/
|
||||||
|
static fromJSON(
|
||||||
|
obj: Jsonify<TrustedDeviceUserDecryptionOption>,
|
||||||
|
): TrustedDeviceUserDecryptionOption {
|
||||||
|
return Object.assign(new TrustedDeviceUserDecryptionOption(), obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the decryption options the user has configured on the server. This is intended to be sent
|
||||||
|
* to the client on authentication, and can be used to determine how to decrypt the user's vault.
|
||||||
|
*/
|
||||||
|
export class UserDecryptionOptions {
|
||||||
|
/** True if the user has a master password configured on the server. */
|
||||||
|
hasMasterPassword: boolean;
|
||||||
|
/** {@link TrustedDeviceUserDecryptionOption} */
|
||||||
|
trustedDeviceOption?: TrustedDeviceUserDecryptionOption;
|
||||||
|
/** {@link KeyConnectorUserDecryptionOption} */
|
||||||
|
keyConnectorOption?: KeyConnectorUserDecryptionOption;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new instance of the UserDecryptionOptions from a response object.
|
||||||
|
* @param response user decryption options response object
|
||||||
|
* @returns A new instance of the UserDecryptionOptions.
|
||||||
|
* @throws If the response is nullish, this method will throw an error. User decryption options
|
||||||
|
* are required for client initialization.
|
||||||
|
*/
|
||||||
|
// TODO: Change response type to `UserDecryptionOptionsResponse` after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537)
|
||||||
|
static fromResponse(response: IdentityTokenResponse): UserDecryptionOptions {
|
||||||
|
if (response == null) {
|
||||||
|
throw new Error("User Decryption Options are required for client initialization.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const decryptionOptions = new UserDecryptionOptions();
|
||||||
|
|
||||||
|
if (response.userDecryptionOptions) {
|
||||||
|
// If the response has userDecryptionOptions, this means it's on a post-TDE server version and can interrogate
|
||||||
|
// the new decryption options.
|
||||||
|
const responseOptions = response.userDecryptionOptions;
|
||||||
|
decryptionOptions.hasMasterPassword = responseOptions.hasMasterPassword;
|
||||||
|
|
||||||
|
decryptionOptions.trustedDeviceOption = TrustedDeviceUserDecryptionOption.fromResponse(
|
||||||
|
responseOptions.trustedDeviceOption,
|
||||||
|
);
|
||||||
|
|
||||||
|
decryptionOptions.keyConnectorOption = KeyConnectorUserDecryptionOption.fromResponse(
|
||||||
|
responseOptions.keyConnectorOption,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// If the response does not have userDecryptionOptions, this means it's on a pre-TDE server version and so
|
||||||
|
// we must base our decryption options on the presence of the keyConnectorUrl.
|
||||||
|
// Note that the presence of keyConnectorUrl implies that the user does not have a master password, as in pre-TDE
|
||||||
|
// server versions, a master password short-circuited the addition of the keyConnectorUrl to the response.
|
||||||
|
// TODO: remove this check after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537)
|
||||||
|
const usingKeyConnector = response.keyConnectorUrl != null;
|
||||||
|
decryptionOptions.hasMasterPassword = !usingKeyConnector;
|
||||||
|
if (usingKeyConnector) {
|
||||||
|
decryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption();
|
||||||
|
decryptionOptions.keyConnectorOption.keyConnectorUrl = response.keyConnectorUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decryptionOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new instance of the UserDecryptionOptions from a JSON object.
|
||||||
|
* @param obj JSON object to deserialize.
|
||||||
|
* @returns A new instance of the UserDecryptionOptions. Will initialize even if the JSON object is nullish.
|
||||||
|
*/
|
||||||
|
static fromJSON(obj: Jsonify<UserDecryptionOptions>): UserDecryptionOptions {
|
||||||
|
const decryptionOptions = Object.assign(new UserDecryptionOptions(), obj);
|
||||||
|
|
||||||
|
decryptionOptions.trustedDeviceOption = TrustedDeviceUserDecryptionOption.fromJSON(
|
||||||
|
obj?.trustedDeviceOption,
|
||||||
|
);
|
||||||
|
|
||||||
|
decryptionOptions.keyConnectorOption = KeyConnectorUserDecryptionOption.fromJSON(
|
||||||
|
obj?.keyConnectorOption,
|
||||||
|
);
|
||||||
|
|
||||||
|
return decryptionOptions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
export * from "./domain";
|
export * from "./domain";
|
||||||
|
export * from "./spec";
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import {
|
||||||
|
KeyConnectorUserDecryptionOption,
|
||||||
|
TrustedDeviceUserDecryptionOption,
|
||||||
|
UserDecryptionOptions,
|
||||||
|
} from "../domain";
|
||||||
|
|
||||||
|
// To discourage creating new user decryption options, we don't expose a constructor.
|
||||||
|
// These helpers are for testing purposes only.
|
||||||
|
|
||||||
|
/** Testing helper for creating new instances of `UserDecryptionOptions` */
|
||||||
|
export class FakeUserDecryptionOptions extends UserDecryptionOptions {
|
||||||
|
constructor(init: Partial<UserDecryptionOptions>) {
|
||||||
|
super();
|
||||||
|
Object.assign(this, init);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Testing helper for creating new instances of `KeyConnectorUserDecryptionOption` */
|
||||||
|
export class FakeKeyConnectorUserDecryptionOption extends KeyConnectorUserDecryptionOption {
|
||||||
|
constructor(keyConnectorUrl: string) {
|
||||||
|
super();
|
||||||
|
this.keyConnectorUrl = keyConnectorUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Testing helper for creating new instances of `TrustedDeviceUserDecryptionOption` */
|
||||||
|
export class FakeTrustedDeviceUserDecryptionOption extends TrustedDeviceUserDecryptionOption {
|
||||||
|
constructor(
|
||||||
|
hasAdminApproval: boolean,
|
||||||
|
hasLoginApprovingDevice: boolean,
|
||||||
|
hasManageResetPasswordPermission: boolean,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.hasAdminApproval = hasAdminApproval;
|
||||||
|
this.hasLoginApprovingDevice = hasLoginApprovingDevice;
|
||||||
|
this.hasManageResetPasswordPermission = hasManageResetPasswordPermission;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./fake-user-decryption-options";
|
|
@ -1,3 +1,4 @@
|
||||||
export * from "./pin-crypto/pin-crypto.service.implementation";
|
export * from "./pin-crypto/pin-crypto.service.implementation";
|
||||||
export * from "./login-strategies/login-strategy.service";
|
export * from "./login-strategies/login-strategy.service";
|
||||||
|
export * from "./user-decryption-options/user-decryption-options.service";
|
||||||
export * from "./auth-request/auth-request.service";
|
export * from "./auth-request/auth-request.service";
|
||||||
|
|
|
@ -25,8 +25,12 @@ import { KdfType } from "@bitwarden/common/platform/enums";
|
||||||
import { FakeGlobalState, FakeGlobalStateProvider } from "@bitwarden/common/spec";
|
import { FakeGlobalState, FakeGlobalStateProvider } from "@bitwarden/common/spec";
|
||||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||||
|
|
||||||
import { AuthRequestServiceAbstraction } from "../../abstractions";
|
import {
|
||||||
|
AuthRequestServiceAbstraction,
|
||||||
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "../../abstractions";
|
||||||
import { PasswordLoginCredentials } from "../../models";
|
import { PasswordLoginCredentials } from "../../models";
|
||||||
|
import { UserDecryptionOptionsService } from "../user-decryption-options/user-decryption-options.service";
|
||||||
|
|
||||||
import { LoginStrategyService } from "./login-strategy.service";
|
import { LoginStrategyService } from "./login-strategy.service";
|
||||||
import { CACHE_EXPIRATION_KEY } from "./login-strategy.state";
|
import { CACHE_EXPIRATION_KEY } from "./login-strategy.state";
|
||||||
|
@ -51,6 +55,7 @@ describe("LoginStrategyService", () => {
|
||||||
let policyService: MockProxy<PolicyService>;
|
let policyService: MockProxy<PolicyService>;
|
||||||
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
|
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
|
||||||
let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
|
let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
|
||||||
|
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||||
|
|
||||||
let stateProvider: FakeGlobalStateProvider;
|
let stateProvider: FakeGlobalStateProvider;
|
||||||
|
@ -74,6 +79,7 @@ describe("LoginStrategyService", () => {
|
||||||
policyService = mock<PolicyService>();
|
policyService = mock<PolicyService>();
|
||||||
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
|
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
|
||||||
authRequestService = mock<AuthRequestServiceAbstraction>();
|
authRequestService = mock<AuthRequestServiceAbstraction>();
|
||||||
|
userDecryptionOptionsService = mock<UserDecryptionOptionsService>();
|
||||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||||
stateProvider = new FakeGlobalStateProvider();
|
stateProvider = new FakeGlobalStateProvider();
|
||||||
|
|
||||||
|
@ -95,6 +101,7 @@ describe("LoginStrategyService", () => {
|
||||||
policyService,
|
policyService,
|
||||||
deviceTrustCryptoService,
|
deviceTrustCryptoService,
|
||||||
authRequestService,
|
authRequestService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
stateProvider,
|
stateProvider,
|
||||||
billingAccountProfileStateService,
|
billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/pass
|
||||||
import { MasterKey } from "@bitwarden/common/types/key";
|
import { MasterKey } from "@bitwarden/common/types/key";
|
||||||
|
|
||||||
import { AuthRequestServiceAbstraction, LoginStrategyServiceAbstraction } from "../../abstractions";
|
import { AuthRequestServiceAbstraction, LoginStrategyServiceAbstraction } from "../../abstractions";
|
||||||
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "../../abstractions/user-decryption-options.service.abstraction";
|
||||||
import { AuthRequestLoginStrategy } from "../../login-strategies/auth-request-login.strategy";
|
import { AuthRequestLoginStrategy } from "../../login-strategies/auth-request-login.strategy";
|
||||||
import { PasswordLoginStrategy } from "../../login-strategies/password-login.strategy";
|
import { PasswordLoginStrategy } from "../../login-strategies/password-login.strategy";
|
||||||
import { SsoLoginStrategy } from "../../login-strategies/sso-login.strategy";
|
import { SsoLoginStrategy } from "../../login-strategies/sso-login.strategy";
|
||||||
|
@ -101,6 +102,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||||
protected policyService: PolicyService,
|
protected policyService: PolicyService,
|
||||||
protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||||
protected authRequestService: AuthRequestServiceAbstraction,
|
protected authRequestService: AuthRequestServiceAbstraction,
|
||||||
|
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
protected stateProvider: GlobalStateProvider,
|
protected stateProvider: GlobalStateProvider,
|
||||||
protected billingAccountProfileStateService: BillingAccountProfileStateService,
|
protected billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||||
) {
|
) {
|
||||||
|
@ -354,6 +356,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||||
this.logService,
|
this.logService,
|
||||||
this.stateService,
|
this.stateService,
|
||||||
this.twoFactorService,
|
this.twoFactorService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.passwordStrengthService,
|
this.passwordStrengthService,
|
||||||
this.policyService,
|
this.policyService,
|
||||||
this,
|
this,
|
||||||
|
@ -371,6 +374,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||||
this.logService,
|
this.logService,
|
||||||
this.stateService,
|
this.stateService,
|
||||||
this.twoFactorService,
|
this.twoFactorService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.keyConnectorService,
|
this.keyConnectorService,
|
||||||
this.deviceTrustCryptoService,
|
this.deviceTrustCryptoService,
|
||||||
this.authRequestService,
|
this.authRequestService,
|
||||||
|
@ -389,6 +393,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||||
this.logService,
|
this.logService,
|
||||||
this.stateService,
|
this.stateService,
|
||||||
this.twoFactorService,
|
this.twoFactorService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.environmentService,
|
this.environmentService,
|
||||||
this.keyConnectorService,
|
this.keyConnectorService,
|
||||||
this.billingAccountProfileStateService,
|
this.billingAccountProfileStateService,
|
||||||
|
@ -405,6 +410,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||||
this.logService,
|
this.logService,
|
||||||
this.stateService,
|
this.stateService,
|
||||||
this.twoFactorService,
|
this.twoFactorService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.deviceTrustCryptoService,
|
this.deviceTrustCryptoService,
|
||||||
this.billingAccountProfileStateService,
|
this.billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
|
@ -420,6 +426,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||||
this.logService,
|
this.logService,
|
||||||
this.stateService,
|
this.stateService,
|
||||||
this.twoFactorService,
|
this.twoFactorService,
|
||||||
|
this.userDecryptionOptionsService,
|
||||||
this.billingAccountProfileStateService,
|
this.billingAccountProfileStateService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import {
|
||||||
|
FakeAccountService,
|
||||||
|
FakeStateProvider,
|
||||||
|
mockAccountServiceWith,
|
||||||
|
} from "@bitwarden/common/spec";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
|
import {
|
||||||
|
USER_DECRYPTION_OPTIONS,
|
||||||
|
UserDecryptionOptionsService,
|
||||||
|
} from "./user-decryption-options.service";
|
||||||
|
|
||||||
|
describe("UserDecryptionOptionsService", () => {
|
||||||
|
let sut: UserDecryptionOptionsService;
|
||||||
|
|
||||||
|
const fakeUserId = Utils.newGuid() as UserId;
|
||||||
|
let fakeAccountService: FakeAccountService;
|
||||||
|
let fakeStateProvider: FakeStateProvider;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fakeAccountService = mockAccountServiceWith(fakeUserId);
|
||||||
|
fakeStateProvider = new FakeStateProvider(fakeAccountService);
|
||||||
|
|
||||||
|
sut = new UserDecryptionOptionsService(fakeStateProvider);
|
||||||
|
});
|
||||||
|
|
||||||
|
const userDecryptionOptions = {
|
||||||
|
hasMasterPassword: true,
|
||||||
|
trustedDeviceOption: {
|
||||||
|
hasAdminApproval: false,
|
||||||
|
hasLoginApprovingDevice: false,
|
||||||
|
hasManageResetPasswordPermission: true,
|
||||||
|
},
|
||||||
|
keyConnectorOption: {
|
||||||
|
keyConnectorUrl: "https://keyconnector.bitwarden.com",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("userDecryptionOptions$", () => {
|
||||||
|
it("should return the active user's decryption options", async () => {
|
||||||
|
await fakeStateProvider.setUserState(USER_DECRYPTION_OPTIONS, userDecryptionOptions);
|
||||||
|
|
||||||
|
const result = await firstValueFrom(sut.userDecryptionOptions$);
|
||||||
|
|
||||||
|
expect(result).toEqual(userDecryptionOptions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("hasMasterPassword$", () => {
|
||||||
|
it("should return the hasMasterPassword property of the active user's decryption options", async () => {
|
||||||
|
await fakeStateProvider.setUserState(USER_DECRYPTION_OPTIONS, userDecryptionOptions);
|
||||||
|
|
||||||
|
const result = await firstValueFrom(sut.hasMasterPassword$);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("userDecryptionOptionsById$", () => {
|
||||||
|
it("should return the user decryption options for the given user", async () => {
|
||||||
|
const givenUser = Utils.newGuid() as UserId;
|
||||||
|
await fakeAccountService.addAccount(givenUser, {
|
||||||
|
name: "Test User 1",
|
||||||
|
email: "test1@email.com",
|
||||||
|
status: AuthenticationStatus.Locked,
|
||||||
|
});
|
||||||
|
await fakeStateProvider.setUserState(
|
||||||
|
USER_DECRYPTION_OPTIONS,
|
||||||
|
userDecryptionOptions,
|
||||||
|
givenUser,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await firstValueFrom(sut.userDecryptionOptionsById$(givenUser));
|
||||||
|
|
||||||
|
expect(result).toEqual(userDecryptionOptions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("setUserDecryptionOptions", () => {
|
||||||
|
it("should set the active user's decryption options", async () => {
|
||||||
|
await sut.setUserDecryptionOptions(userDecryptionOptions);
|
||||||
|
|
||||||
|
const result = await firstValueFrom(
|
||||||
|
fakeStateProvider.getActive(USER_DECRYPTION_OPTIONS).state$,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual(userDecryptionOptions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { map } from "rxjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ActiveUserState,
|
||||||
|
StateProvider,
|
||||||
|
USER_DECRYPTION_OPTIONS_DISK,
|
||||||
|
UserKeyDefinition,
|
||||||
|
} from "@bitwarden/common/platform/state";
|
||||||
|
import { UserId } from "@bitwarden/common/src/types/guid";
|
||||||
|
|
||||||
|
import { InternalUserDecryptionOptionsServiceAbstraction } from "../../abstractions/user-decryption-options.service.abstraction";
|
||||||
|
import { UserDecryptionOptions } from "../../models";
|
||||||
|
|
||||||
|
export const USER_DECRYPTION_OPTIONS = new UserKeyDefinition<UserDecryptionOptions>(
|
||||||
|
USER_DECRYPTION_OPTIONS_DISK,
|
||||||
|
"decryptionOptions",
|
||||||
|
{
|
||||||
|
deserializer: (decryptionOptions) => UserDecryptionOptions.fromJSON(decryptionOptions),
|
||||||
|
clearOn: ["logout"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export class UserDecryptionOptionsService
|
||||||
|
implements InternalUserDecryptionOptionsServiceAbstraction
|
||||||
|
{
|
||||||
|
private userDecryptionOptionsState: ActiveUserState<UserDecryptionOptions>;
|
||||||
|
|
||||||
|
userDecryptionOptions$;
|
||||||
|
hasMasterPassword$;
|
||||||
|
|
||||||
|
constructor(private stateProvider: StateProvider) {
|
||||||
|
this.userDecryptionOptionsState = this.stateProvider.getActive(USER_DECRYPTION_OPTIONS);
|
||||||
|
|
||||||
|
this.userDecryptionOptions$ = this.userDecryptionOptionsState.state$;
|
||||||
|
this.hasMasterPassword$ = this.userDecryptionOptions$.pipe(
|
||||||
|
map((options) => options?.hasMasterPassword ?? false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
userDecryptionOptionsById$(userId: UserId) {
|
||||||
|
return this.stateProvider.getUser(userId, USER_DECRYPTION_OPTIONS).state$;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setUserDecryptionOptions(userDecryptionOptions: UserDecryptionOptions): Promise<void> {
|
||||||
|
await this.userDecryptionOptionsState.update((_) => userDecryptionOptions);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { EncString } from "../../platform/models/domain/enc-string";
|
import { EncString } from "../../platform/models/domain/enc-string";
|
||||||
import { DeviceKey, UserKey } from "../../types/key";
|
import { DeviceKey, UserKey } from "../../types/key";
|
||||||
import { DeviceResponse } from "../abstractions/devices/responses/device.response";
|
import { DeviceResponse } from "../abstractions/devices/responses/device.response";
|
||||||
|
|
||||||
export abstract class DeviceTrustCryptoServiceAbstraction {
|
export abstract class DeviceTrustCryptoServiceAbstraction {
|
||||||
|
supportsDeviceTrust$: Observable<boolean>;
|
||||||
/**
|
/**
|
||||||
* @description Retrieves the users choice to trust the device which can only happen after decryption
|
* @description Retrieves the users choice to trust the device which can only happen after decryption
|
||||||
* Note: this value should only be used once and then reset
|
* Note: this value should only be used once and then reset
|
||||||
|
@ -20,6 +23,4 @@ export abstract class DeviceTrustCryptoServiceAbstraction {
|
||||||
deviceKey?: DeviceKey,
|
deviceKey?: DeviceKey,
|
||||||
) => Promise<UserKey | null>;
|
) => Promise<UserKey | null>;
|
||||||
rotateDevicesTrust: (newUserKey: UserKey, masterPasswordHash: string) => Promise<void>;
|
rotateDevicesTrust: (newUserKey: UserKey, masterPasswordHash: string) => Promise<void>;
|
||||||
|
|
||||||
supportsDeviceTrust: () => Promise<boolean>;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ export class AuthResult {
|
||||||
// TODO: PM-3287 - Remove this after 3 releases of backwards compatibility. - Target release 2023.12 for removal
|
// TODO: PM-3287 - Remove this after 3 releases of backwards compatibility. - Target release 2023.12 for removal
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* Replace with using AccountDecryptionOptions to determine if the user does
|
* Replace with using UserDecryptionOptions to determine if the user does
|
||||||
* not have a master password and is not using Key Connector.
|
* not have a master password and is not using Key Connector.
|
||||||
* */
|
* */
|
||||||
resetMasterPassword = false;
|
resetMasterPassword = false;
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export class KeyConnectorUserDecryptionOption {
|
|
||||||
constructor(public keyConnectorUrl: string) {}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
export class TrustedDeviceUserDecryptionOption {
|
|
||||||
constructor(
|
|
||||||
public hasAdminApproval: boolean,
|
|
||||||
public hasLoginApprovingDevice: boolean,
|
|
||||||
public hasManageResetPasswordPermission: boolean,
|
|
||||||
) {}
|
|
||||||
}
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom, map, Observable } from "rxjs";
|
||||||
|
|
||||||
|
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
|
|
||||||
import { AppIdService } from "../../platform/abstractions/app-id.service";
|
import { AppIdService } from "../../platform/abstractions/app-id.service";
|
||||||
import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service";
|
||||||
|
@ -21,6 +23,8 @@ import {
|
||||||
} from "../models/request/update-devices-trust.request";
|
} from "../models/request/update-devices-trust.request";
|
||||||
|
|
||||||
export class DeviceTrustCryptoService implements DeviceTrustCryptoServiceAbstraction {
|
export class DeviceTrustCryptoService implements DeviceTrustCryptoServiceAbstraction {
|
||||||
|
supportsDeviceTrust$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private keyGenerationService: KeyGenerationService,
|
private keyGenerationService: KeyGenerationService,
|
||||||
private cryptoFunctionService: CryptoFunctionService,
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
|
@ -31,7 +35,12 @@ export class DeviceTrustCryptoService implements DeviceTrustCryptoServiceAbstrac
|
||||||
private devicesApiService: DevicesApiServiceAbstraction,
|
private devicesApiService: DevicesApiServiceAbstraction,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
) {}
|
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
|
) {
|
||||||
|
this.supportsDeviceTrust$ = this.userDecryptionOptionsService.userDecryptionOptions$.pipe(
|
||||||
|
map((options) => options?.trustedDeviceOption != null ?? false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Retrieves the users choice to trust the device which can only happen after decryption
|
* @description Retrieves the users choice to trust the device which can only happen after decryption
|
||||||
|
@ -203,9 +212,4 @@ export class DeviceTrustCryptoService implements DeviceTrustCryptoServiceAbstrac
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async supportsDeviceTrust(): Promise<boolean> {
|
|
||||||
const decryptionOptions = await this.stateService.getAccountDecryptionOptions();
|
|
||||||
return decryptionOptions?.trustedDeviceOption != null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { matches, mock } from "jest-mock-extended";
|
import { matches, mock } from "jest-mock-extended";
|
||||||
import { of } from "rxjs";
|
import { BehaviorSubject, of } from "rxjs";
|
||||||
|
|
||||||
|
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
|
|
||||||
|
import { UserDecryptionOptions } from "../../../../auth/src/common/models/domain/user-decryption-options";
|
||||||
import { DeviceType } from "../../enums";
|
import { DeviceType } from "../../enums";
|
||||||
import { AppIdService } from "../../platform/abstractions/app-id.service";
|
import { AppIdService } from "../../platform/abstractions/app-id.service";
|
||||||
import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service";
|
||||||
|
@ -34,10 +37,16 @@ describe("deviceTrustCryptoService", () => {
|
||||||
const devicesApiService = mock<DevicesApiServiceAbstraction>();
|
const devicesApiService = mock<DevicesApiServiceAbstraction>();
|
||||||
const i18nService = mock<I18nService>();
|
const i18nService = mock<I18nService>();
|
||||||
const platformUtilsService = mock<PlatformUtilsService>();
|
const platformUtilsService = mock<PlatformUtilsService>();
|
||||||
|
const userDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>();
|
||||||
|
|
||||||
|
const decryptionOptions = new BehaviorSubject<UserDecryptionOptions>(null);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
decryptionOptions.next({} as any);
|
||||||
|
userDecryptionOptionsService.userDecryptionOptions$ = decryptionOptions;
|
||||||
|
|
||||||
deviceTrustCryptoService = new DeviceTrustCryptoService(
|
deviceTrustCryptoService = new DeviceTrustCryptoService(
|
||||||
keyGenerationService,
|
keyGenerationService,
|
||||||
cryptoFunctionService,
|
cryptoFunctionService,
|
||||||
|
@ -48,6 +57,7 @@ describe("deviceTrustCryptoService", () => {
|
||||||
devicesApiService,
|
devicesApiService,
|
||||||
i18nService,
|
i18nService,
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
|
userDecryptionOptionsService,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
|
|
||||||
import { PinCryptoServiceAbstraction } from "../../../../../auth/src/common/abstractions/pin-crypto.service.abstraction";
|
import { PinCryptoServiceAbstraction } from "../../../../../auth/src/common/abstractions/pin-crypto.service.abstraction";
|
||||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../../abstractions/vault-timeout/vault-timeout-settings.service";
|
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../../abstractions/vault-timeout/vault-timeout-settings.service";
|
||||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||||
|
@ -33,6 +37,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private userVerificationApiService: UserVerificationApiServiceAbstraction,
|
private userVerificationApiService: UserVerificationApiServiceAbstraction,
|
||||||
|
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
private pinCryptoService: PinCryptoServiceAbstraction,
|
private pinCryptoService: PinCryptoServiceAbstraction,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction,
|
private vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction,
|
||||||
|
@ -135,7 +140,6 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||||
case VerificationType.MasterPassword:
|
case VerificationType.MasterPassword:
|
||||||
return this.verifyUserByMasterPassword(verification);
|
return this.verifyUserByMasterPassword(verification);
|
||||||
case VerificationType.PIN:
|
case VerificationType.PIN:
|
||||||
return this.verifyUserByPIN(verification);
|
|
||||||
break;
|
break;
|
||||||
case VerificationType.Biometrics:
|
case VerificationType.Biometrics:
|
||||||
return this.verifyUserByBiometrics();
|
return this.verifyUserByBiometrics();
|
||||||
|
@ -210,16 +214,19 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||||
* Note: This only checks the server, not the local state
|
* Note: This only checks the server, not the local state
|
||||||
* @param userId The user id to check. If not provided, the current user is used
|
* @param userId The user id to check. If not provided, the current user is used
|
||||||
* @returns True if the user has a master password
|
* @returns True if the user has a master password
|
||||||
|
* @deprecated Use UserDecryptionOptionsService.hasMasterPassword$ instead
|
||||||
*/
|
*/
|
||||||
async hasMasterPassword(userId?: string): Promise<boolean> {
|
async hasMasterPassword(userId?: string): Promise<boolean> {
|
||||||
const decryptionOptions = await this.stateService.getAccountDecryptionOptions({ userId });
|
if (userId) {
|
||||||
|
const decryptionOptions = await firstValueFrom(
|
||||||
|
this.userDecryptionOptionsService.userDecryptionOptionsById$(userId),
|
||||||
|
);
|
||||||
|
|
||||||
if (decryptionOptions?.hasMasterPassword != undefined) {
|
if (decryptionOptions?.hasMasterPassword != undefined) {
|
||||||
return decryptionOptions.hasMasterPassword;
|
return decryptionOptions.hasMasterPassword;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return await firstValueFrom(this.userDecryptionOptionsService.hasMasterPassword$);
|
||||||
// TODO: PM-3518 - Left for backwards compatibility, remove after 2023.12.0
|
|
||||||
return !(await this.stateService.getUsesKeyConnector({ userId }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasMasterPasswordAndMasterKeyHash(userId?: string): Promise<boolean> {
|
async hasMasterPasswordAndMasterKeyHash(userId?: string): Promise<boolean> {
|
||||||
|
|
|
@ -18,7 +18,7 @@ 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";
|
||||||
import { KdfType } from "../enums";
|
import { KdfType } from "../enums";
|
||||||
import { ServerConfigData } from "../models/data/server-config.data";
|
import { ServerConfigData } from "../models/data/server-config.data";
|
||||||
import { Account, AccountDecryptionOptions } 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";
|
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||||
|
@ -180,13 +180,6 @@ export abstract class StateService<T extends Account = Account> {
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
getShouldTrustDevice: (options?: StorageOptions) => Promise<boolean | null>;
|
getShouldTrustDevice: (options?: StorageOptions) => Promise<boolean | null>;
|
||||||
setShouldTrustDevice: (value: boolean, options?: StorageOptions) => Promise<void>;
|
setShouldTrustDevice: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||||
getAccountDecryptionOptions: (
|
|
||||||
options?: StorageOptions,
|
|
||||||
) => Promise<AccountDecryptionOptions | null>;
|
|
||||||
setAccountDecryptionOptions: (
|
|
||||||
value: AccountDecryptionOptions,
|
|
||||||
options?: StorageOptions,
|
|
||||||
) => Promise<void>;
|
|
||||||
getEmail: (options?: StorageOptions) => Promise<string>;
|
getEmail: (options?: StorageOptions) => Promise<string>;
|
||||||
setEmail: (value: string, options?: StorageOptions) => Promise<void>;
|
setEmail: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
getEmailVerified: (options?: StorageOptions) => Promise<boolean>;
|
getEmailVerified: (options?: StorageOptions) => Promise<boolean>;
|
||||||
|
|
|
@ -2,9 +2,6 @@ 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 { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
|
||||||
import { KeyConnectorUserDecryptionOption } from "../../../auth/models/domain/user-decryption-options/key-connector-user-decryption-option";
|
|
||||||
import { TrustedDeviceUserDecryptionOption } from "../../../auth/models/domain/user-decryption-options/trusted-device-user-decryption-option";
|
|
||||||
import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response";
|
|
||||||
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 {
|
||||||
|
@ -235,103 +232,12 @@ export class AccountTokens {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AccountDecryptionOptions {
|
|
||||||
hasMasterPassword: boolean;
|
|
||||||
trustedDeviceOption?: TrustedDeviceUserDecryptionOption;
|
|
||||||
keyConnectorOption?: KeyConnectorUserDecryptionOption;
|
|
||||||
|
|
||||||
constructor(init?: Partial<AccountDecryptionOptions>) {
|
|
||||||
if (init) {
|
|
||||||
Object.assign(this, init);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: these nice getters don't work because the Account object is not properly being deserialized out of
|
|
||||||
// JSON (the Account static fromJSON method is not running) so these getters don't exist on the
|
|
||||||
// account decryptions options object when pulled out of state. This is a bug that needs to be fixed later on
|
|
||||||
// get hasTrustedDeviceOption(): boolean {
|
|
||||||
// return this.trustedDeviceOption !== null && this.trustedDeviceOption !== undefined;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// get hasKeyConnectorOption(): boolean {
|
|
||||||
// return this.keyConnectorOption !== null && this.keyConnectorOption !== undefined;
|
|
||||||
// }
|
|
||||||
|
|
||||||
static fromResponse(response: IdentityTokenResponse): AccountDecryptionOptions {
|
|
||||||
if (response == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountDecryptionOptions = new AccountDecryptionOptions();
|
|
||||||
|
|
||||||
if (response.userDecryptionOptions) {
|
|
||||||
// If the response has userDecryptionOptions, this means it's on a post-TDE server version and can interrogate
|
|
||||||
// the new decryption options.
|
|
||||||
const responseOptions = response.userDecryptionOptions;
|
|
||||||
accountDecryptionOptions.hasMasterPassword = responseOptions.hasMasterPassword;
|
|
||||||
|
|
||||||
if (responseOptions.trustedDeviceOption) {
|
|
||||||
accountDecryptionOptions.trustedDeviceOption = new TrustedDeviceUserDecryptionOption(
|
|
||||||
responseOptions.trustedDeviceOption.hasAdminApproval,
|
|
||||||
responseOptions.trustedDeviceOption.hasLoginApprovingDevice,
|
|
||||||
responseOptions.trustedDeviceOption.hasManageResetPasswordPermission,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseOptions.keyConnectorOption) {
|
|
||||||
accountDecryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption(
|
|
||||||
responseOptions.keyConnectorOption.keyConnectorUrl,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If the response does not have userDecryptionOptions, this means it's on a pre-TDE server version and so
|
|
||||||
// we must base our decryption options on the presence of the keyConnectorUrl.
|
|
||||||
// Note that the presence of keyConnectorUrl implies that the user does not have a master password, as in pre-TDE
|
|
||||||
// server versions, a master password short-circuited the addition of the keyConnectorUrl to the response.
|
|
||||||
// TODO: remove this check after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537)
|
|
||||||
const usingKeyConnector = response.keyConnectorUrl != null;
|
|
||||||
accountDecryptionOptions.hasMasterPassword = !usingKeyConnector;
|
|
||||||
if (usingKeyConnector) {
|
|
||||||
accountDecryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption(
|
|
||||||
response.keyConnectorUrl,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return accountDecryptionOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJSON(obj: Jsonify<AccountDecryptionOptions>): AccountDecryptionOptions {
|
|
||||||
if (obj == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountDecryptionOptions = Object.assign(new AccountDecryptionOptions(), obj);
|
|
||||||
|
|
||||||
if (obj.trustedDeviceOption) {
|
|
||||||
accountDecryptionOptions.trustedDeviceOption = new TrustedDeviceUserDecryptionOption(
|
|
||||||
obj.trustedDeviceOption.hasAdminApproval,
|
|
||||||
obj.trustedDeviceOption.hasLoginApprovingDevice,
|
|
||||||
obj.trustedDeviceOption.hasManageResetPasswordPermission,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.keyConnectorOption) {
|
|
||||||
accountDecryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption(
|
|
||||||
obj.keyConnectorOption.keyConnectorUrl,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return accountDecryptionOptions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Account {
|
export class Account {
|
||||||
data?: AccountData = new AccountData();
|
data?: AccountData = new AccountData();
|
||||||
keys?: AccountKeys = new AccountKeys();
|
keys?: AccountKeys = new AccountKeys();
|
||||||
profile?: AccountProfile = new AccountProfile();
|
profile?: AccountProfile = new AccountProfile();
|
||||||
settings?: AccountSettings = new AccountSettings();
|
settings?: AccountSettings = new AccountSettings();
|
||||||
tokens?: AccountTokens = new AccountTokens();
|
tokens?: AccountTokens = new AccountTokens();
|
||||||
decryptionOptions?: AccountDecryptionOptions = new AccountDecryptionOptions();
|
|
||||||
adminAuthRequest?: Jsonify<AdminAuthRequestStorable> = null;
|
adminAuthRequest?: Jsonify<AdminAuthRequestStorable> = null;
|
||||||
|
|
||||||
constructor(init: Partial<Account>) {
|
constructor(init: Partial<Account>) {
|
||||||
|
@ -356,10 +262,6 @@ export class Account {
|
||||||
...new AccountTokens(),
|
...new AccountTokens(),
|
||||||
...init?.tokens,
|
...init?.tokens,
|
||||||
},
|
},
|
||||||
decryptionOptions: {
|
|
||||||
...new AccountDecryptionOptions(),
|
|
||||||
...init?.decryptionOptions,
|
|
||||||
},
|
|
||||||
adminAuthRequest: init?.adminAuthRequest,
|
adminAuthRequest: init?.adminAuthRequest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -375,7 +277,6 @@ export class Account {
|
||||||
profile: AccountProfile.fromJSON(json?.profile),
|
profile: AccountProfile.fromJSON(json?.profile),
|
||||||
settings: AccountSettings.fromJSON(json?.settings),
|
settings: AccountSettings.fromJSON(json?.settings),
|
||||||
tokens: AccountTokens.fromJSON(json?.tokens),
|
tokens: AccountTokens.fromJSON(json?.tokens),
|
||||||
decryptionOptions: AccountDecryptionOptions.fromJSON(json?.decryptionOptions),
|
|
||||||
adminAuthRequest: AdminAuthRequestStorable.fromJSON(json?.adminAuthRequest),
|
adminAuthRequest: AdminAuthRequestStorable.fromJSON(json?.adminAuthRequest),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,12 +34,7 @@ import { HtmlStorageLocation, KdfType, StorageLocation } from "../enums";
|
||||||
import { StateFactory } from "../factories/state-factory";
|
import { StateFactory } from "../factories/state-factory";
|
||||||
import { Utils } from "../misc/utils";
|
import { Utils } from "../misc/utils";
|
||||||
import { ServerConfigData } from "../models/data/server-config.data";
|
import { ServerConfigData } from "../models/data/server-config.data";
|
||||||
import {
|
import { Account, AccountData, AccountSettings } from "../models/domain/account";
|
||||||
Account,
|
|
||||||
AccountData,
|
|
||||||
AccountDecryptionOptions,
|
|
||||||
AccountSettings,
|
|
||||||
} from "../models/domain/account";
|
|
||||||
import { EncString } from "../models/domain/enc-string";
|
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";
|
||||||
|
@ -817,37 +812,6 @@ export class StateService<
|
||||||
await this.saveAccount(account, options);
|
await this.saveAccount(account, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccountDecryptionOptions(
|
|
||||||
options?: StorageOptions,
|
|
||||||
): Promise<AccountDecryptionOptions | null> {
|
|
||||||
options = this.reconcileOptions(options, await this.defaultOnDiskLocalOptions());
|
|
||||||
|
|
||||||
if (options?.userId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await this.getAccount(options);
|
|
||||||
|
|
||||||
return account?.decryptionOptions as AccountDecryptionOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setAccountDecryptionOptions(
|
|
||||||
value: AccountDecryptionOptions,
|
|
||||||
options?: StorageOptions,
|
|
||||||
): Promise<void> {
|
|
||||||
options = this.reconcileOptions(options, await this.defaultOnDiskLocalOptions());
|
|
||||||
|
|
||||||
if (options?.userId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const account = await this.getAccount(options);
|
|
||||||
|
|
||||||
account.decryptionOptions = value;
|
|
||||||
|
|
||||||
await this.saveAccount(account, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEmail(options?: StorageOptions): Promise<string> {
|
async getEmail(options?: StorageOptions): Promise<string> {
|
||||||
return (
|
return (
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||||
|
|
|
@ -44,6 +44,7 @@ export const TOKEN_DISK_LOCAL = new StateDefinition("tokenDiskLocal", "disk", {
|
||||||
});
|
});
|
||||||
export const TOKEN_MEMORY = new StateDefinition("token", "memory");
|
export const TOKEN_MEMORY = new StateDefinition("token", "memory");
|
||||||
export const LOGIN_STRATEGY_MEMORY = new StateDefinition("loginStrategy", "memory");
|
export const LOGIN_STRATEGY_MEMORY = new StateDefinition("loginStrategy", "memory");
|
||||||
|
export const USER_DECRYPTION_OPTIONS_DISK = new StateDefinition("userDecryptionOptions", "disk");
|
||||||
|
|
||||||
// Autofill
|
// Autofill
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { firstValueFrom, of } from "rxjs";
|
import { BehaviorSubject, firstValueFrom, map, of } from "rxjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
FakeUserDecryptionOptions as UserDecryptionOptions,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
|
|
||||||
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { Policy } from "../../admin-console/models/domain/policy";
|
import { Policy } from "../../admin-console/models/domain/policy";
|
||||||
|
@ -8,12 +13,12 @@ import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
|
||||||
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
import { StateService } from "../../platform/abstractions/state.service";
|
||||||
import { BiometricStateService } from "../../platform/biometrics/biometric-state.service";
|
import { BiometricStateService } from "../../platform/biometrics/biometric-state.service";
|
||||||
import { AccountDecryptionOptions } from "../../platform/models/domain/account";
|
|
||||||
import { EncString } from "../../platform/models/domain/enc-string";
|
import { EncString } from "../../platform/models/domain/enc-string";
|
||||||
|
|
||||||
import { VaultTimeoutSettingsService } from "./vault-timeout-settings.service";
|
import { VaultTimeoutSettingsService } from "./vault-timeout-settings.service";
|
||||||
|
|
||||||
describe("VaultTimeoutSettingsService", () => {
|
describe("VaultTimeoutSettingsService", () => {
|
||||||
|
let userDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
|
||||||
let cryptoService: MockProxy<CryptoService>;
|
let cryptoService: MockProxy<CryptoService>;
|
||||||
let tokenService: MockProxy<TokenService>;
|
let tokenService: MockProxy<TokenService>;
|
||||||
let policyService: MockProxy<PolicyService>;
|
let policyService: MockProxy<PolicyService>;
|
||||||
|
@ -21,12 +26,26 @@ describe("VaultTimeoutSettingsService", () => {
|
||||||
const biometricStateService = mock<BiometricStateService>();
|
const biometricStateService = mock<BiometricStateService>();
|
||||||
let service: VaultTimeoutSettingsService;
|
let service: VaultTimeoutSettingsService;
|
||||||
|
|
||||||
|
let userDecryptionOptionsSubject: BehaviorSubject<UserDecryptionOptions>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
userDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>();
|
||||||
cryptoService = mock<CryptoService>();
|
cryptoService = mock<CryptoService>();
|
||||||
tokenService = mock<TokenService>();
|
tokenService = mock<TokenService>();
|
||||||
policyService = mock<PolicyService>();
|
policyService = mock<PolicyService>();
|
||||||
stateService = mock<StateService>();
|
stateService = mock<StateService>();
|
||||||
|
|
||||||
|
userDecryptionOptionsSubject = new BehaviorSubject(null);
|
||||||
|
userDecryptionOptionsService.userDecryptionOptions$ = userDecryptionOptionsSubject;
|
||||||
|
userDecryptionOptionsService.hasMasterPassword$ = userDecryptionOptionsSubject.pipe(
|
||||||
|
map((options) => options?.hasMasterPassword ?? false),
|
||||||
|
);
|
||||||
|
userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue(
|
||||||
|
userDecryptionOptionsSubject,
|
||||||
|
);
|
||||||
|
|
||||||
service = new VaultTimeoutSettingsService(
|
service = new VaultTimeoutSettingsService(
|
||||||
|
userDecryptionOptionsService,
|
||||||
cryptoService,
|
cryptoService,
|
||||||
tokenService,
|
tokenService,
|
||||||
policyService,
|
policyService,
|
||||||
|
@ -49,9 +68,7 @@ describe("VaultTimeoutSettingsService", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("contains Lock when the user has a master password", async () => {
|
it("contains Lock when the user has a master password", async () => {
|
||||||
stateService.getAccountDecryptionOptions.mockResolvedValue(
|
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true }));
|
||||||
new AccountDecryptionOptions({ hasMasterPassword: true }),
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await firstValueFrom(service.availableVaultTimeoutActions$());
|
const result = await firstValueFrom(service.availableVaultTimeoutActions$());
|
||||||
|
|
||||||
|
@ -83,9 +100,7 @@ describe("VaultTimeoutSettingsService", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("not contains Lock when the user does not have a master password, PIN, or biometrics", async () => {
|
it("not contains Lock when the user does not have a master password, PIN, or biometrics", async () => {
|
||||||
stateService.getAccountDecryptionOptions.mockResolvedValue(
|
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: false }));
|
||||||
new AccountDecryptionOptions({ hasMasterPassword: false }),
|
|
||||||
);
|
|
||||||
stateService.getPinKeyEncryptedUserKey.mockResolvedValue(null);
|
stateService.getPinKeyEncryptedUserKey.mockResolvedValue(null);
|
||||||
stateService.getProtectedPin.mockResolvedValue(null);
|
stateService.getProtectedPin.mockResolvedValue(null);
|
||||||
biometricStateService.biometricUnlockEnabled$ = of(false);
|
biometricStateService.biometricUnlockEnabled$ = of(false);
|
||||||
|
@ -107,9 +122,7 @@ describe("VaultTimeoutSettingsService", () => {
|
||||||
`(
|
`(
|
||||||
"returns $expected when policy is $policy, and user preference is $userPreference",
|
"returns $expected when policy is $policy, and user preference is $userPreference",
|
||||||
async ({ policy, userPreference, expected }) => {
|
async ({ policy, userPreference, expected }) => {
|
||||||
stateService.getAccountDecryptionOptions.mockResolvedValue(
|
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true }));
|
||||||
new AccountDecryptionOptions({ hasMasterPassword: true }),
|
|
||||||
);
|
|
||||||
policyService.getAll$.mockReturnValue(
|
policyService.getAll$.mockReturnValue(
|
||||||
of(policy === null ? [] : ([{ data: { action: policy } }] as unknown as Policy[])),
|
of(policy === null ? [] : ([{ data: { action: policy } }] as unknown as Policy[])),
|
||||||
);
|
);
|
||||||
|
@ -136,8 +149,8 @@ describe("VaultTimeoutSettingsService", () => {
|
||||||
"returns $expected when policy is $policy, has unlock method is $unlockMethod, and user preference is $userPreference",
|
"returns $expected when policy is $policy, has unlock method is $unlockMethod, and user preference is $userPreference",
|
||||||
async ({ unlockMethod, policy, userPreference, expected }) => {
|
async ({ unlockMethod, policy, userPreference, expected }) => {
|
||||||
biometricStateService.biometricUnlockEnabled$ = of(unlockMethod);
|
biometricStateService.biometricUnlockEnabled$ = of(unlockMethod);
|
||||||
stateService.getAccountDecryptionOptions.mockResolvedValue(
|
userDecryptionOptionsSubject.next(
|
||||||
new AccountDecryptionOptions({ hasMasterPassword: false }),
|
new UserDecryptionOptions({ hasMasterPassword: false }),
|
||||||
);
|
);
|
||||||
policyService.getAll$.mockReturnValue(
|
policyService.getAll$.mockReturnValue(
|
||||||
of(policy === null ? [] : ([{ data: { action: policy } }] as unknown as Policy[])),
|
of(policy === null ? [] : ([{ data: { action: policy } }] as unknown as Policy[])),
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { defer, firstValueFrom } from "rxjs";
|
import { defer, firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
|
|
||||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service";
|
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service";
|
||||||
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { PolicyType } from "../../admin-console/enums";
|
import { PolicyType } from "../../admin-console/enums";
|
||||||
|
@ -19,6 +21,7 @@ export type PinLockType = "DISABLED" | "PERSISTANT" | "TRANSIENT";
|
||||||
|
|
||||||
export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceAbstraction {
|
export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceAbstraction {
|
||||||
constructor(
|
constructor(
|
||||||
|
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private tokenService: TokenService,
|
private tokenService: TokenService,
|
||||||
private policyService: PolicyService,
|
private policyService: PolicyService,
|
||||||
|
@ -174,12 +177,15 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||||
}
|
}
|
||||||
|
|
||||||
private async userHasMasterPassword(userId: string): Promise<boolean> {
|
private async userHasMasterPassword(userId: string): Promise<boolean> {
|
||||||
const acctDecryptionOpts = await this.stateService.getAccountDecryptionOptions({
|
if (userId) {
|
||||||
userId: userId,
|
const decryptionOptions = await firstValueFrom(
|
||||||
});
|
this.userDecryptionOptionsService.userDecryptionOptionsById$(userId),
|
||||||
|
);
|
||||||
|
|
||||||
if (acctDecryptionOpts?.hasMasterPassword != undefined) {
|
if (decryptionOptions?.hasMasterPassword != undefined) {
|
||||||
return acctDecryptionOpts.hasMasterPassword;
|
return decryptionOptions.hasMasterPassword;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return await firstValueFrom(this.userDecryptionOptionsService.hasMasterPassword$);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import { OrganizationMigrator } from "./migrations/40-move-organization-state-to
|
||||||
import { EventCollectionMigrator } from "./migrations/41-move-event-collection-to-state-provider";
|
import { EventCollectionMigrator } from "./migrations/41-move-event-collection-to-state-provider";
|
||||||
import { EnableFaviconMigrator } from "./migrations/42-move-enable-favicon-to-domain-settings-state-provider";
|
import { EnableFaviconMigrator } from "./migrations/42-move-enable-favicon-to-domain-settings-state-provider";
|
||||||
import { AutoConfirmFingerPrintsMigrator } from "./migrations/43-move-auto-confirm-finger-prints-to-state-provider";
|
import { AutoConfirmFingerPrintsMigrator } from "./migrations/43-move-auto-confirm-finger-prints-to-state-provider";
|
||||||
|
import { UserDecryptionOptionsMigrator } from "./migrations/44-move-user-decryption-options-to-state-provider";
|
||||||
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
|
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
|
||||||
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";
|
||||||
|
@ -47,7 +48,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 = 43;
|
export const CURRENT_VERSION = 44;
|
||||||
export type MinVersion = typeof MIN_VERSION;
|
export type MinVersion = typeof MIN_VERSION;
|
||||||
|
|
||||||
export function createMigrationBuilder() {
|
export function createMigrationBuilder() {
|
||||||
|
@ -92,7 +93,8 @@ export function createMigrationBuilder() {
|
||||||
.with(OrganizationMigrator, 39, 40)
|
.with(OrganizationMigrator, 39, 40)
|
||||||
.with(EventCollectionMigrator, 40, 41)
|
.with(EventCollectionMigrator, 40, 41)
|
||||||
.with(EnableFaviconMigrator, 41, 42)
|
.with(EnableFaviconMigrator, 41, 42)
|
||||||
.with(AutoConfirmFingerPrintsMigrator, 42, CURRENT_VERSION);
|
.with(AutoConfirmFingerPrintsMigrator, 42, 43)
|
||||||
|
.with(UserDecryptionOptionsMigrator, 43, CURRENT_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function currentVersion(
|
export async function currentVersion(
|
||||||
|
|
|
@ -0,0 +1,238 @@
|
||||||
|
import { any, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { MigrationHelper } from "../migration-helper";
|
||||||
|
import { mockMigrationHelper } from "../migration-helper.spec";
|
||||||
|
|
||||||
|
import { UserDecryptionOptionsMigrator } from "./44-move-user-decryption-options-to-state-provider";
|
||||||
|
|
||||||
|
function exampleJSON() {
|
||||||
|
return {
|
||||||
|
global: {
|
||||||
|
otherStuff: "otherStuff1",
|
||||||
|
},
|
||||||
|
authenticatedAccounts: ["FirstAccount", "SecondAccount", "ThirdAccount"],
|
||||||
|
FirstAccount: {
|
||||||
|
decryptionOptions: {
|
||||||
|
hasMasterPassword: true,
|
||||||
|
trustedDeviceOption: {
|
||||||
|
hasAdminApproval: false,
|
||||||
|
hasLoginApprovingDevice: false,
|
||||||
|
hasManageResetPasswordPermission: true,
|
||||||
|
},
|
||||||
|
keyConnectorOption: {
|
||||||
|
keyConnectorUrl: "https://keyconnector.bitwarden.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
otherStuff: "overStuff2",
|
||||||
|
},
|
||||||
|
otherStuff: "otherStuff3",
|
||||||
|
},
|
||||||
|
SecondAccount: {
|
||||||
|
decryptionOptions: {
|
||||||
|
hasMasterPassword: false,
|
||||||
|
trustedDeviceOption: {
|
||||||
|
hasAdminApproval: true,
|
||||||
|
hasLoginApprovingDevice: true,
|
||||||
|
hasManageResetPasswordPermission: true,
|
||||||
|
},
|
||||||
|
keyConnectorOption: {
|
||||||
|
keyConnectorUrl: "https://selfhosted.bitwarden.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
otherStuff: "otherStuff4",
|
||||||
|
},
|
||||||
|
otherStuff: "otherStuff5",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function rollbackJSON() {
|
||||||
|
return {
|
||||||
|
user_FirstAccount_decryptionOptions_userDecryptionOptions: {
|
||||||
|
hasMasterPassword: true,
|
||||||
|
trustedDeviceOption: {
|
||||||
|
hasAdminApproval: false,
|
||||||
|
hasLoginApprovingDevice: false,
|
||||||
|
hasManageResetPasswordPermission: true,
|
||||||
|
},
|
||||||
|
keyConnectorOption: {
|
||||||
|
keyConnectorUrl: "https://keyconnector.bitwarden.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user_SecondAccount_decryptionOptions_userDecryptionOptions: {
|
||||||
|
hasMasterPassword: false,
|
||||||
|
trustedDeviceOption: {
|
||||||
|
hasAdminApproval: true,
|
||||||
|
hasLoginApprovingDevice: true,
|
||||||
|
hasManageResetPasswordPermission: true,
|
||||||
|
},
|
||||||
|
keyConnectorOption: {
|
||||||
|
keyConnectorUrl: "https://selfhosted.bitwarden.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user_ThirdAccount_decryptionOptions_userDecryptionOptions: {},
|
||||||
|
global: {
|
||||||
|
otherStuff: "otherStuff1",
|
||||||
|
},
|
||||||
|
authenticatedAccounts: ["FirstAccount", "SecondAccount", "ThirdAccount"],
|
||||||
|
FirstAccount: {
|
||||||
|
decryptionOptions: {
|
||||||
|
hasMasterPassword: true,
|
||||||
|
trustedDeviceOption: {
|
||||||
|
hasAdminApproval: false,
|
||||||
|
hasLoginApprovingDevice: false,
|
||||||
|
hasManageResetPasswordPermission: true,
|
||||||
|
},
|
||||||
|
keyConnectorOption: {
|
||||||
|
keyConnectorUrl: "https://keyconnector.bitwarden.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
otherStuff: "overStuff2",
|
||||||
|
},
|
||||||
|
otherStuff: "otherStuff3",
|
||||||
|
},
|
||||||
|
SecondAccount: {
|
||||||
|
decryptionOptions: {
|
||||||
|
hasMasterPassword: false,
|
||||||
|
trustedDeviceOption: {
|
||||||
|
hasAdminApproval: true,
|
||||||
|
hasLoginApprovingDevice: true,
|
||||||
|
hasManageResetPasswordPermission: true,
|
||||||
|
},
|
||||||
|
keyConnectorOption: {
|
||||||
|
keyConnectorUrl: "https://selfhosted.bitwarden.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
otherStuff: "otherStuff4",
|
||||||
|
},
|
||||||
|
otherStuff: "otherStuff5",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("UserDecryptionOptionsMigrator", () => {
|
||||||
|
let helper: MockProxy<MigrationHelper>;
|
||||||
|
let sut: UserDecryptionOptionsMigrator;
|
||||||
|
const keyDefinitionLike = {
|
||||||
|
key: "decryptionOptions",
|
||||||
|
stateDefinition: {
|
||||||
|
name: "userDecryptionOptions",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("migrate", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
helper = mockMigrationHelper(exampleJSON(), 43);
|
||||||
|
sut = new UserDecryptionOptionsMigrator(43, 44);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove decryptionOptions from all accounts", async () => {
|
||||||
|
await sut.migrate(helper);
|
||||||
|
expect(helper.set).toHaveBeenCalledWith("FirstAccount", {
|
||||||
|
profile: {
|
||||||
|
otherStuff: "overStuff2",
|
||||||
|
},
|
||||||
|
otherStuff: "otherStuff3",
|
||||||
|
});
|
||||||
|
expect(helper.set).toHaveBeenCalledWith("SecondAccount", {
|
||||||
|
profile: {
|
||||||
|
otherStuff: "otherStuff4",
|
||||||
|
},
|
||||||
|
otherStuff: "otherStuff5",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set decryptionOptions provider value for each account", async () => {
|
||||||
|
await sut.migrate(helper);
|
||||||
|
|
||||||
|
expect(helper.setToUser).toHaveBeenCalledWith("FirstAccount", keyDefinitionLike, {
|
||||||
|
hasMasterPassword: true,
|
||||||
|
trustedDeviceOption: {
|
||||||
|
hasAdminApproval: false,
|
||||||
|
hasLoginApprovingDevice: false,
|
||||||
|
hasManageResetPasswordPermission: true,
|
||||||
|
},
|
||||||
|
keyConnectorOption: {
|
||||||
|
keyConnectorUrl: "https://keyconnector.bitwarden.com",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(helper.setToUser).toHaveBeenCalledWith("SecondAccount", keyDefinitionLike, {
|
||||||
|
hasMasterPassword: false,
|
||||||
|
trustedDeviceOption: {
|
||||||
|
hasAdminApproval: true,
|
||||||
|
hasLoginApprovingDevice: true,
|
||||||
|
hasManageResetPasswordPermission: true,
|
||||||
|
},
|
||||||
|
keyConnectorOption: {
|
||||||
|
keyConnectorUrl: "https://selfhosted.bitwarden.com",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("rollback", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
helper = mockMigrationHelper(rollbackJSON(), 44);
|
||||||
|
sut = new UserDecryptionOptionsMigrator(43, 44);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(["FirstAccount", "SecondAccount", "ThirdAccount"])(
|
||||||
|
"should null out new values",
|
||||||
|
async (userId) => {
|
||||||
|
await sut.rollback(helper);
|
||||||
|
|
||||||
|
expect(helper.setToUser).toHaveBeenCalledWith(userId, keyDefinitionLike, null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("should add explicit value back to accounts", async () => {
|
||||||
|
await sut.rollback(helper);
|
||||||
|
|
||||||
|
expect(helper.set).toHaveBeenCalledWith("FirstAccount", {
|
||||||
|
decryptionOptions: {
|
||||||
|
hasMasterPassword: true,
|
||||||
|
trustedDeviceOption: {
|
||||||
|
hasAdminApproval: false,
|
||||||
|
hasLoginApprovingDevice: false,
|
||||||
|
hasManageResetPasswordPermission: true,
|
||||||
|
},
|
||||||
|
keyConnectorOption: {
|
||||||
|
keyConnectorUrl: "https://keyconnector.bitwarden.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
otherStuff: "overStuff2",
|
||||||
|
},
|
||||||
|
otherStuff: "otherStuff3",
|
||||||
|
});
|
||||||
|
expect(helper.set).toHaveBeenCalledWith("SecondAccount", {
|
||||||
|
decryptionOptions: {
|
||||||
|
hasMasterPassword: false,
|
||||||
|
trustedDeviceOption: {
|
||||||
|
hasAdminApproval: true,
|
||||||
|
hasLoginApprovingDevice: true,
|
||||||
|
hasManageResetPasswordPermission: true,
|
||||||
|
},
|
||||||
|
keyConnectorOption: {
|
||||||
|
keyConnectorUrl: "https://selfhosted.bitwarden.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
otherStuff: "otherStuff4",
|
||||||
|
},
|
||||||
|
otherStuff: "otherStuff5",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not try to restore values to missing accounts", async () => {
|
||||||
|
await sut.rollback(helper);
|
||||||
|
|
||||||
|
expect(helper.set).not.toHaveBeenCalledWith("ThirdAccount", any());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
|
||||||
|
import { Migrator } from "../migrator";
|
||||||
|
|
||||||
|
type DecryptionOptionsType = {
|
||||||
|
hasMasterPassword: boolean;
|
||||||
|
trustedDeviceOption?: {
|
||||||
|
hasAdminApproval: boolean;
|
||||||
|
hasLoginApprovingDevice: boolean;
|
||||||
|
hasManageResetPasswordPermission: boolean;
|
||||||
|
};
|
||||||
|
keyConnectorOption?: {
|
||||||
|
keyConnectorUrl: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ExpectedAccountType = {
|
||||||
|
decryptionOptions?: DecryptionOptionsType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const USER_DECRYPTION_OPTIONS: KeyDefinitionLike = {
|
||||||
|
key: "decryptionOptions",
|
||||||
|
stateDefinition: {
|
||||||
|
name: "userDecryptionOptions",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export class UserDecryptionOptionsMigrator extends Migrator<43, 44> {
|
||||||
|
async migrate(helper: MigrationHelper): Promise<void> {
|
||||||
|
const accounts = await helper.getAccounts<ExpectedAccountType>();
|
||||||
|
async function migrateAccount(userId: string, account: ExpectedAccountType): Promise<void> {
|
||||||
|
const value = account?.decryptionOptions;
|
||||||
|
if (value != null) {
|
||||||
|
await helper.setToUser(userId, USER_DECRYPTION_OPTIONS, value);
|
||||||
|
delete account.decryptionOptions;
|
||||||
|
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 value: DecryptionOptionsType = await helper.getFromUser(
|
||||||
|
userId,
|
||||||
|
USER_DECRYPTION_OPTIONS,
|
||||||
|
);
|
||||||
|
if (account) {
|
||||||
|
account.decryptionOptions = Object.assign(account.decryptionOptions, value);
|
||||||
|
await helper.set(userId, account);
|
||||||
|
}
|
||||||
|
await helper.setToUser(userId, USER_DECRYPTION_OPTIONS, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
|
|
||||||
import { ApiService } from "../../../abstractions/api.service";
|
import { ApiService } from "../../../abstractions/api.service";
|
||||||
import { InternalOrganizationServiceAbstraction } from "../../../admin-console/abstractions/organization/organization.service.abstraction";
|
import { InternalOrganizationServiceAbstraction } from "../../../admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { InternalPolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction";
|
import { InternalPolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
@ -24,7 +28,6 @@ import { LogService } from "../../../platform/abstractions/log.service";
|
||||||
import { MessagingService } from "../../../platform/abstractions/messaging.service";
|
import { MessagingService } from "../../../platform/abstractions/messaging.service";
|
||||||
import { StateService } from "../../../platform/abstractions/state.service";
|
import { StateService } from "../../../platform/abstractions/state.service";
|
||||||
import { sequentialize } from "../../../platform/misc/sequentialize";
|
import { sequentialize } from "../../../platform/misc/sequentialize";
|
||||||
import { AccountDecryptionOptions } from "../../../platform/models/domain/account";
|
|
||||||
import { SendData } from "../../../tools/send/models/data/send.data";
|
import { SendData } from "../../../tools/send/models/data/send.data";
|
||||||
import { SendResponse } from "../../../tools/send/models/response/send.response";
|
import { SendResponse } from "../../../tools/send/models/response/send.response";
|
||||||
import { SendApiService } from "../../../tools/send/services/send-api.service.abstraction";
|
import { SendApiService } from "../../../tools/send/services/send-api.service.abstraction";
|
||||||
|
@ -62,6 +65,7 @@ export class SyncService implements SyncServiceAbstraction {
|
||||||
private folderApiService: FolderApiServiceAbstraction,
|
private folderApiService: FolderApiServiceAbstraction,
|
||||||
private organizationService: InternalOrganizationServiceAbstraction,
|
private organizationService: InternalOrganizationServiceAbstraction,
|
||||||
private sendApiService: SendApiService,
|
private sendApiService: SendApiService,
|
||||||
|
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
private avatarService: AvatarService,
|
private avatarService: AvatarService,
|
||||||
private logoutCallback: (expired: boolean) => Promise<void>,
|
private logoutCallback: (expired: boolean) => Promise<void>,
|
||||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||||
|
@ -353,19 +357,12 @@ export class SyncService implements SyncServiceAbstraction {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const acctDecryptionOpts: AccountDecryptionOptions =
|
const userDecryptionOptions = await firstValueFrom(
|
||||||
await this.stateService.getAccountDecryptionOptions();
|
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||||
|
);
|
||||||
|
|
||||||
// Account decryption options should never be null or undefined b/c it is always initialized
|
if (userDecryptionOptions === null || userDecryptionOptions === undefined) {
|
||||||
// during the processing of the ID token response, but there might be a state issue
|
|
||||||
// where it is being overwritten with undefined affecting browser extension + FireFox users.
|
|
||||||
// TODO: Consider removing this once we figure out the root cause of the state issue or after the state provider refactor.
|
|
||||||
if (acctDecryptionOpts === null || acctDecryptionOpts === undefined) {
|
|
||||||
this.logService.error("Sync: Account decryption options are null or undefined.");
|
this.logService.error("Sync: Account decryption options are null or undefined.");
|
||||||
// Early return as a bandaid to allow the rest of the sync to continue so users can access
|
|
||||||
// their data that they might have added from another device.
|
|
||||||
// Otherwise, trying to access properties on undefined below will throw an error.
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Even though TDE users should only be in a single org (per single org policy), check
|
// Even though TDE users should only be in a single org (per single org policy), check
|
||||||
|
@ -384,8 +381,8 @@ export class SyncService implements SyncServiceAbstraction {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
acctDecryptionOpts.trustedDeviceOption !== undefined &&
|
userDecryptionOptions.trustedDeviceOption !== undefined &&
|
||||||
!acctDecryptionOpts.hasMasterPassword &&
|
!userDecryptionOptions.hasMasterPassword &&
|
||||||
hasManageResetPasswordPermission
|
hasManageResetPasswordPermission
|
||||||
) {
|
) {
|
||||||
// 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.
|
||||||
|
|
Loading…
Reference in New Issue