1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 09:43:23 +00:00

[PM-5189] Merging main into current branch

This commit is contained in:
Cesar Gonzalez
2024-03-21 11:18:00 -05:00
141 changed files with 1900 additions and 469 deletions

View File

@@ -191,6 +191,15 @@
] ]
} }
}, },
{
"files": ["libs/tools/export/vault-export/vault-export-ui/src/**/*.ts"],
"rules": {
"no-restricted-imports": [
"error",
{ "patterns": ["@bitwarden/vault-export-ui/*", "src/**/*"] }
]
}
},
{ {
"files": ["libs/importer/src/**/*.ts"], "files": ["libs/importer/src/**/*.ts"],
"rules": { "rules": {

View File

@@ -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,

View File

@@ -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),
), ),
); );
} }

View File

@@ -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),
), ),

View File

@@ -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;
}

View File

@@ -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),

View File

@@ -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,
); );

View File

@@ -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,
); );

View File

@@ -3,6 +3,7 @@ import { ActivatedRoute, Router } from "@angular/router";
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component"; import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -16,9 +17,10 @@ export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
router: Router, router: Router,
i18nService: I18nService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
) { ) {
super(twoFactorService, router, i18nService, platformUtilsService, window); super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
} }
close() { close() {

View File

@@ -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,
); );

View File

@@ -625,6 +625,22 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
* Updates the position of the overlay button. * Updates the position of the overlay button.
*/ */
private updateOverlayButtonPosition() { private updateOverlayButtonPosition() {
// if (!this.overlayButtonElement) {
// this.createAutofillOverlayButton();
// this.updateCustomElementDefaultStyles(this.overlayButtonElement);
// }
//
// if (!this.isOverlayButtonVisible) {
// this.appendOverlayElementToBody(this.overlayButtonElement);
// this.isOverlayButtonVisible = true;
// this.setOverlayRepositionEventListeners();
// }
// // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// // eslint-disable-next-line @typescript-eslint/no-floating-promises
// this.sendExtensionMessage("updateAutofillOverlayPosition", {
// overlayElement: AutofillOverlayElement.Button,
// });
void this.sendExtensionMessage("updateAutofillOverlayPosition", { void this.sendExtensionMessage("updateAutofillOverlayPosition", {
overlayElement: AutofillOverlayElement.Button, overlayElement: AutofillOverlayElement.Button,
}); });
@@ -634,6 +650,22 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
* Updates the position of the overlay list. * Updates the position of the overlay list.
*/ */
private updateOverlayListPosition() { private updateOverlayListPosition() {
// if (!this.overlayListElement) {
// this.createAutofillOverlayList();
// this.updateCustomElementDefaultStyles(this.overlayListElement);
// }
//
// if (!this.isOverlayListVisible) {
// this.appendOverlayElementToBody(this.overlayListElement);
// this.isOverlayListVisible = true;
// }
//
// // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// // eslint-disable-next-line @typescript-eslint/no-floating-promises
// this.sendExtensionMessage("updateAutofillOverlayPosition", {
// overlayElement: AutofillOverlayElement.List,
// });
void this.sendExtensionMessage("updateAutofillOverlayPosition", { void this.sendExtensionMessage("updateAutofillOverlayPosition", {
overlayElement: AutofillOverlayElement.List, overlayElement: AutofillOverlayElement.List,
}); });

View File

@@ -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,

View File

@@ -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),

View File

@@ -16,6 +16,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe"; import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"; import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
import { AvatarModule, ButtonModule } from "@bitwarden/components"; import { AvatarModule, ButtonModule } from "@bitwarden/components";
import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui";
import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component";
import { AccountComponent } from "../auth/popup/account-switching/account.component"; import { AccountComponent } from "../auth/popup/account-switching/account.component";
@@ -107,6 +108,7 @@ import "../platform/popup/locales";
AvatarModule, AvatarModule,
AccountComponent, AccountComponent,
ButtonModule, ButtonModule,
ExportScopeCalloutComponent,
], ],
declarations: [ declarations: [
ActionButtonsComponent, ActionButtonsComponent,

View File

@@ -82,7 +82,6 @@ import {
import { SearchService } from "@bitwarden/common/services/search.service"; import { SearchService } from "@bitwarden/common/services/search.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username"; import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service";
import { SendApiService as SendApiServiceAbstraction } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendApiService as SendApiServiceAbstraction } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { import {
@@ -96,7 +95,6 @@ import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vau
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
import { ImportServiceAbstraction } from "@bitwarden/importer/core";
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
import { UnauthGuardService } from "../../auth/popup/services"; import { UnauthGuardService } from "../../auth/popup/services";
@@ -292,11 +290,6 @@ function getBgService<T>(service: keyof MainBackground) {
}, },
deps: [DomSanitizer, ToastrService], deps: [DomSanitizer, ToastrService],
}, },
{
provide: PasswordStrengthServiceAbstraction,
useFactory: getBgService<PasswordStrengthServiceAbstraction>("passwordStrengthService"),
deps: [],
},
{ {
provide: PasswordGenerationServiceAbstraction, provide: PasswordGenerationServiceAbstraction,
useFactory: getBgService<PasswordGenerationServiceAbstraction>("passwordGenerationService"), useFactory: getBgService<PasswordGenerationServiceAbstraction>("passwordGenerationService"),
@@ -350,11 +343,6 @@ function getBgService<T>(service: keyof MainBackground) {
useFactory: getBgService<AutofillService>("autofillService"), useFactory: getBgService<AutofillService>("autofillService"),
deps: [], deps: [],
}, },
{
provide: ImportServiceAbstraction,
useFactory: getBgService<ImportServiceAbstraction>("importService"),
deps: [],
},
{ {
provide: VaultExportServiceAbstraction, provide: VaultExportServiceAbstraction,
useFactory: getBgService<VaultExportServiceAbstraction>("exportService"), useFactory: getBgService<VaultExportServiceAbstraction>("exportService"),

View File

@@ -19,7 +19,7 @@
<app-callout type="warning" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy"> <app-callout type="warning" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
{{ "personalVaultExportPolicyInEffect" | i18n }} {{ "personalVaultExportPolicyInEffect" | i18n }}
</app-callout> </app-callout>
<app-export-scope-callout *ngIf="!disabledByPolicy"></app-export-scope-callout> <tools-export-scope-callout *ngIf="!disabledByPolicy"></tools-export-scope-callout>
<div class="box"> <div class="box">
<div class="box-content"> <div class="box-content">

View File

@@ -4,6 +4,7 @@ import { ActivatedRoute } from "@angular/router";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
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";
@@ -21,11 +22,19 @@ export class CollectionsComponent extends BaseCollectionsComponent {
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, i18nService: I18nService,
cipherService: CipherService, cipherService: CipherService,
organizationService: OrganizationService,
private route: ActivatedRoute, private route: ActivatedRoute,
private location: Location, private location: Location,
logService: LogService, logService: LogService,
) { ) {
super(collectionService, platformUtilsService, i18nService, cipherService, logService); super(
collectionService,
platformUtilsService,
i18nService,
cipherService,
organizationService,
logService,
);
} }
async ngOnInit() { async ngOnInit() {

View File

@@ -20,6 +20,7 @@
"@bitwarden/vault-export-core": [ "@bitwarden/vault-export-core": [
"../../libs/tools/export/vault-export/vault-export-core/src" "../../libs/tools/export/vault-export/vault-export-core/src"
], ],
"@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"],
"@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/core": ["../../libs/importer/src"],
"@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"],
"@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/platform": ["../../libs/platform/src"],

View File

@@ -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,

View File

@@ -84,9 +84,9 @@ dependencies = [
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.5" version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
[[package]] [[package]]
name = "bitflags" name = "bitflags"

View File

@@ -16,7 +16,7 @@ manual_test = []
aes = "=0.8.4" aes = "=0.8.4"
anyhow = "=1.0.80" anyhow = "=1.0.80"
arboard = { version = "=3.3.0", default-features = false, features = ["wayland-data-control"] } arboard = { version = "=3.3.0", default-features = false, features = ["wayland-data-control"] }
base64 = "=0.21.5" base64 = "=0.22.0"
cbc = { version = "=0.1.2", features = ["alloc"] } cbc = { version = "=0.1.2", features = ["alloc"] }
napi = { version = "=2.13.3", features = ["async"] } napi = { version = "=2.13.3", features = ["async"] }
napi-derive = "=2.13.0" napi-derive = "=2.13.0"

View File

@@ -412,6 +412,23 @@
"enableBrowserIntegrationFingerprintDesc" | i18n "enableBrowserIntegrationFingerprintDesc" | i18n
}}</small> }}</small>
</div> </div>
<div class="form-group">
<div class="checkbox">
<label for="enableHardwareAcceleration">
<input
id="enableHardwareAcceleration"
type="checkbox"
aria-describedby="enableHardwareAccelerationHelp"
formControlName="enableHardwareAcceleration"
(change)="saveHardwareAcceleration()"
/>
{{ "enableHardwareAcceleration" | i18n }}
</label>
</div>
<small id="enableHardwareAccelerationHelp" class="help-block">{{
"enableHardwareAccelerationDesc" | i18n
}}</small>
</div>
<div class="form-group" *ngIf="showDuckDuckGoIntegrationOption"> <div class="form-group" *ngIf="showDuckDuckGoIntegrationOption">
<div class="checkbox"> <div class="checkbox">
<label for="enableDuckDuckGoBrowserIntegration"> <label for="enableDuckDuckGoBrowserIntegration">

View File

@@ -24,6 +24,7 @@ import { DialogService } from "@bitwarden/components";
import { SetPinComponent } from "../../auth/components/set-pin.component"; import { SetPinComponent } from "../../auth/components/set-pin.component";
import { flagEnabled } from "../../platform/flags"; import { flagEnabled } from "../../platform/flags";
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
@Component({ @Component({
selector: "app-settings", selector: "app-settings",
@@ -96,6 +97,7 @@ export class SettingsComponent implements OnInit {
value: false, value: false,
disabled: true, disabled: true,
}), }),
enableHardwareAcceleration: true,
enableDuckDuckGoBrowserIntegration: false, enableDuckDuckGoBrowserIntegration: false,
theme: [null as ThemeType | null], theme: [null as ThemeType | null],
locale: [null as string | null], locale: [null as string | null],
@@ -119,6 +121,7 @@ export class SettingsComponent implements OnInit {
private dialogService: DialogService, private dialogService: DialogService,
private userVerificationService: UserVerificationServiceAbstraction, private userVerificationService: UserVerificationServiceAbstraction,
private biometricStateService: BiometricStateService, private biometricStateService: BiometricStateService,
private desktopSettingsService: DesktopSettingsService,
) { ) {
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop; const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
@@ -259,6 +262,9 @@ export class SettingsComponent implements OnInit {
enableBrowserIntegration: await this.stateService.getEnableBrowserIntegration(), enableBrowserIntegration: await this.stateService.getEnableBrowserIntegration(),
enableBrowserIntegrationFingerprint: enableBrowserIntegrationFingerprint:
await this.stateService.getEnableBrowserIntegrationFingerprint(), await this.stateService.getEnableBrowserIntegrationFingerprint(),
enableHardwareAcceleration: await firstValueFrom(
this.desktopSettingsService.hardwareAcceleration$,
),
enableDuckDuckGoBrowserIntegration: enableDuckDuckGoBrowserIntegration:
await this.stateService.getEnableDuckDuckGoBrowserIntegration(), await this.stateService.getEnableDuckDuckGoBrowserIntegration(),
theme: await firstValueFrom(this.themeStateService.selectedTheme$), theme: await firstValueFrom(this.themeStateService.selectedTheme$),
@@ -655,6 +661,12 @@ export class SettingsComponent implements OnInit {
); );
} }
async saveHardwareAcceleration() {
await this.desktopSettingsService.setHardwareAcceleration(
this.form.value.enableHardwareAcceleration,
);
}
async updateApproveLoginRequests() { async updateApproveLoginRequests() {
await this.stateService.setApproveLoginRequests(this.form.value.approveLoginRequests); await this.stateService.setApproveLoginRequests(this.form.value.approveLoginRequests);
} }

View File

@@ -8,6 +8,7 @@ import { NgModule } from "@angular/core";
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe"; import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"; import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
import { DialogModule } from "@bitwarden/components"; import { DialogModule } from "@bitwarden/components";
import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui";
import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component"; import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component";
import { DeleteAccountComponent } from "../auth/delete-account.component"; import { DeleteAccountComponent } from "../auth/delete-account.component";
@@ -61,6 +62,7 @@ import { SendComponent } from "./tools/send/send.component";
DialogModule, DialogModule,
DeleteAccountComponent, DeleteAccountComponent,
UserVerificationComponent, UserVerificationComponent,
ExportScopeCalloutComponent,
], ],
declarations: [ declarations: [
AccessibilityCookieComponent, AccessibilityCookieComponent,

View File

@@ -54,6 +54,7 @@ import { DialogService } from "@bitwarden/components";
import { LoginGuard } from "../../auth/guards/login.guard"; import { LoginGuard } from "../../auth/guards/login.guard";
import { Account } from "../../models/account"; import { Account } from "../../models/account";
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
import { ElectronCryptoService } from "../../platform/services/electron-crypto.service"; import { ElectronCryptoService } from "../../platform/services/electron-crypto.service";
import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service"; import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service";
import { import {
@@ -212,6 +213,11 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
BiometricStateService, BiometricStateService,
], ],
}, },
{
provide: DesktopSettingsService,
useClass: DesktopSettingsService,
deps: [StateProvider],
},
], ],
}) })
export class ServicesModule {} export class ServicesModule {}

View File

@@ -9,7 +9,7 @@
> >
{{ "personalVaultExportPolicyInEffect" | i18n }} {{ "personalVaultExportPolicyInEffect" | i18n }}
</app-callout> </app-callout>
<app-export-scope-callout *ngIf="!disabledByPolicy"></app-export-scope-callout> <tools-export-scope-callout *ngIf="!disabledByPolicy"></tools-export-scope-callout>
<div class="box"> <div class="box">
<h1 class="box-header" id="exportTitle"> <h1 class="box-header" id="exportTitle">
{{ "exportVault" | i18n }} {{ "exportVault" | i18n }}

View File

@@ -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,
); );

View File

@@ -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 () => {

View File

@@ -3,6 +3,7 @@ import { Router } from "@angular/router";
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component"; import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -16,7 +17,8 @@ export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
router: Router, router: Router,
i18nService: I18nService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
) { ) {
super(twoFactorService, router, i18nService, platformUtilsService, window); super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
} }
} }

View File

@@ -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,
); );

View File

@@ -1644,6 +1644,12 @@
"enableBrowserIntegrationFingerprintDesc": { "enableBrowserIntegrationFingerprintDesc": {
"message": "Add an additional layer of security by requiring fingerprint phrase confirmation when establishing a link between your desktop and browser. This requires user action and verification each time a connection is created." "message": "Add an additional layer of security by requiring fingerprint phrase confirmation when establishing a link between your desktop and browser. This requires user action and verification each time a connection is created."
}, },
"enableHardwareAcceleration": {
"message": "Use hardware acceleration"
},
"enableHardwareAccelerationDesc": {
"message": "By default this setting is ON. Turn OFF only if you experience graphical issues. Restart is required."
},
"approve": { "approve": {
"message": "Approve" "message": "Approve"
}, },

View File

@@ -1,6 +1,7 @@
import * as path from "path"; import * as path from "path";
import { app } from "electron"; import { app } from "electron";
import { firstValueFrom } from "rxjs";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
@@ -37,6 +38,7 @@ import { BiometricsService, BiometricsServiceAbstraction } from "./platform/main
import { ClipboardMain } from "./platform/main/clipboard.main"; import { ClipboardMain } from "./platform/main/clipboard.main";
import { DesktopCredentialStorageListener } from "./platform/main/desktop-credential-storage-listener"; import { DesktopCredentialStorageListener } from "./platform/main/desktop-credential-storage-listener";
import { MainCryptoFunctionService } from "./platform/main/main-crypto-function.service"; import { MainCryptoFunctionService } from "./platform/main/main-crypto-function.service";
import { DesktopSettingsService } from "./platform/services/desktop-settings.service";
import { ElectronLogMainService } from "./platform/services/electron-log.main.service"; import { ElectronLogMainService } from "./platform/services/electron-log.main.service";
import { ELECTRON_SUPPORTS_SECURE_STORAGE } from "./platform/services/electron-platform-utils.service"; import { ELECTRON_SUPPORTS_SECURE_STORAGE } from "./platform/services/electron-platform-utils.service";
import { ElectronStateService } from "./platform/services/electron-state.service"; import { ElectronStateService } from "./platform/services/electron-state.service";
@@ -56,6 +58,7 @@ export class Main {
mainCryptoFunctionService: MainCryptoFunctionService; mainCryptoFunctionService: MainCryptoFunctionService;
desktopCredentialStorageListener: DesktopCredentialStorageListener; desktopCredentialStorageListener: DesktopCredentialStorageListener;
migrationRunner: MigrationRunner; migrationRunner: MigrationRunner;
desktopSettingsService: DesktopSettingsService;
tokenService: TokenServiceAbstraction; tokenService: TokenServiceAbstraction;
windowMain: WindowMain; windowMain: WindowMain;
@@ -189,6 +192,7 @@ export class Main {
this.messagingMain = new MessagingMain(this, this.stateService); this.messagingMain = new MessagingMain(this, this.stateService);
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain); this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain);
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService); this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService);
this.desktopSettingsService = new DesktopSettingsService(stateProvider);
this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => { this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => {
this.messagingMain.onMessage(message); this.messagingMain.onMessage(message);
@@ -237,6 +241,7 @@ export class Main {
// Run migrations first, then other things // Run migrations first, then other things
this.migrationRunner.run().then( this.migrationRunner.run().then(
async () => { async () => {
await this.toggleHardwareAcceleration();
await this.windowMain.init(); await this.windowMain.init();
await this.i18nService.init(); await this.i18nService.init();
this.messagingMain.init(); this.messagingMain.init();
@@ -307,4 +312,15 @@ export class Main {
this.messagingService.send("deepLink", { urlString: s }); this.messagingService.send("deepLink", { urlString: s });
}); });
} }
private async toggleHardwareAcceleration(): Promise<void> {
const hardwareAcceleration = await firstValueFrom(
this.desktopSettingsService.hardwareAcceleration$,
);
if (!hardwareAcceleration) {
this.logService.warning("Hardware acceleration is disabled");
app.disableHardwareAcceleration();
}
}
} }

View File

@@ -0,0 +1,26 @@
import { map } from "rxjs";
import {
DESKTOP_SETTINGS_DISK,
KeyDefinition,
StateProvider,
} from "@bitwarden/common/platform/state";
export const HARDWARE_ACCELERATION = new KeyDefinition<boolean>(
DESKTOP_SETTINGS_DISK,
"hardwareAcceleration",
{
deserializer: (v: boolean) => v,
},
);
export class DesktopSettingsService {
private hwState = this.stateProvider.getGlobal(HARDWARE_ACCELERATION);
hardwareAcceleration$ = this.hwState.state$.pipe(map((v) => v ?? true));
constructor(private stateProvider: StateProvider) {}
async setHardwareAcceleration(enabled: boolean) {
await this.hwState.update(() => enabled);
}
}

View File

@@ -1,6 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
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";
@@ -17,8 +18,16 @@ export class CollectionsComponent extends BaseCollectionsComponent {
i18nService: I18nService, i18nService: I18nService,
collectionService: CollectionService, collectionService: CollectionService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
organizationService: OrganizationService,
logService: LogService, logService: LogService,
) { ) {
super(collectionService, platformUtilsService, i18nService, cipherService, logService); super(
collectionService,
platformUtilsService,
i18nService,
cipherService,
organizationService,
logService,
);
} }
} }

View File

@@ -20,6 +20,7 @@
"@bitwarden/vault-export-core": [ "@bitwarden/vault-export-core": [
"../../libs/tools/export/vault-export/vault-export-core/src" "../../libs/tools/export/vault-export/vault-export-core/src"
], ],
"@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"],
"@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/core": ["../../libs/importer/src"],
"@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"],
"@bitwarden/node/*": ["../../libs/node/src/*"], "@bitwarden/node/*": ["../../libs/node/src/*"],

View File

@@ -1,10 +1,12 @@
import { Directive, ViewChild, ViewContainerRef } from "@angular/core"; import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { import {
OrganizationUserStatusType, OrganizationUserStatusType,
OrganizationUserType, OrganizationUserType,
@@ -17,7 +19,6 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
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 { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
@@ -109,8 +110,8 @@ export abstract class BasePeopleComponent<
private logService: LogService, private logService: LogService,
private searchPipe: SearchPipe, private searchPipe: SearchPipe,
protected userNamePipe: UserNamePipe, protected userNamePipe: UserNamePipe,
protected stateService: StateService,
protected dialogService: DialogService, protected dialogService: DialogService,
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
) {} ) {}
abstract edit(user: UserType): void; abstract edit(user: UserType): void;
@@ -351,7 +352,9 @@ export abstract class BasePeopleComponent<
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId); const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints(); const autoConfirm = await firstValueFrom(
this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$,
);
if (autoConfirm == null || !autoConfirm) { if (autoConfirm == null || !autoConfirm) {
const [modal] = await this.modalService.openViewRef( const [modal] = await this.modalService.openViewRef(
UserConfirmComponent, UserConfirmComponent,

View File

@@ -1,8 +1,8 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
@Component({ @Component({
selector: "app-user-confirm", selector: "app-user-confirm",
@@ -22,7 +22,7 @@ export class UserConfirmComponent implements OnInit {
constructor( constructor(
private cryptoService: CryptoService, private cryptoService: CryptoService,
private logService: LogService, private logService: LogService,
private stateService: StateService, private organizationManagementPreferencesService: OrganizationManagementPreferencesService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@@ -45,7 +45,7 @@ export class UserConfirmComponent implements OnInit {
} }
if (this.dontAskAgain) { if (this.dontAskAgain) {
await this.stateService.setAutoConfirmFingerprints(true); await this.organizationManagementPreferencesService.autoConfirmFingerPrints.set(true);
} }
this.onConfirmedUser.emit(); this.onConfirmedUser.emit();

View File

@@ -21,6 +21,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { OrganizationUserConfirmRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; import { OrganizationUserConfirmRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
import { import {
@@ -43,7 +44,6 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
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 { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@@ -117,7 +117,6 @@ export class PeopleComponent
searchPipe: SearchPipe, searchPipe: SearchPipe,
userNamePipe: UserNamePipe, userNamePipe: UserNamePipe,
private syncService: SyncService, private syncService: SyncService,
stateService: StateService,
private organizationService: OrganizationService, private organizationService: OrganizationService,
private organizationApiService: OrganizationApiServiceAbstraction, private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService, private organizationUserService: OrganizationUserService,
@@ -125,6 +124,7 @@ export class PeopleComponent
private router: Router, private router: Router,
private groupService: GroupService, private groupService: GroupService,
private collectionService: CollectionService, private collectionService: CollectionService,
organizationManagementPreferencesService: OrganizationManagementPreferencesService,
) { ) {
super( super(
apiService, apiService,
@@ -137,8 +137,8 @@ export class PeopleComponent
logService, logService,
searchPipe, searchPipe,
userNamePipe, userNamePipe,
stateService,
dialogService, dialogService,
organizationManagementPreferencesService,
); );
} }

View File

@@ -1,12 +1,19 @@
import { NgModule } from "@angular/core"; import { NgModule } from "@angular/core";
import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui";
import { LooseComponentsModule, SharedModule } from "../../../../shared"; import { LooseComponentsModule, SharedModule } from "../../../../shared";
import { OrganizationVaultExportRoutingModule } from "./org-vault-export-routing.module"; import { OrganizationVaultExportRoutingModule } from "./org-vault-export-routing.module";
import { OrganizationVaultExportComponent } from "./org-vault-export.component"; import { OrganizationVaultExportComponent } from "./org-vault-export.component";
@NgModule({ @NgModule({
imports: [SharedModule, LooseComponentsModule, OrganizationVaultExportRoutingModule], imports: [
SharedModule,
LooseComponentsModule,
OrganizationVaultExportRoutingModule,
ExportScopeCalloutComponent,
],
declarations: [OrganizationVaultExportComponent], declarations: [OrganizationVaultExportComponent],
}) })
export class OrganizationVaultExportModule {} export class OrganizationVaultExportModule {}

View File

@@ -3,9 +3,9 @@ import { Component, OnInit, Inject } from "@angular/core";
import { FormBuilder } from "@angular/forms"; import { FormBuilder } from "@angular/forms";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
@@ -36,7 +36,7 @@ export class EmergencyAccessConfirmComponent implements OnInit {
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private apiService: ApiService, private apiService: ApiService,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private stateService: StateService, protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
private logService: LogService, private logService: LogService,
private dialogRef: DialogRef<EmergencyAccessConfirmDialogResult>, private dialogRef: DialogRef<EmergencyAccessConfirmDialogResult>,
) {} ) {}
@@ -63,7 +63,7 @@ export class EmergencyAccessConfirmComponent implements OnInit {
} }
if (this.confirmForm.get("dontAskAgain").value) { if (this.confirmForm.get("dontAskAgain").value) {
await this.stateService.setAutoConfirmFingerprints(true); await this.organizationManagementPreferencesService.autoConfirmFingerPrints.set(true);
} }
try { try {

View File

@@ -3,6 +3,7 @@ import { lastValueFrom, Observable, firstValueFrom } from "rxjs";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { 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";
@@ -64,6 +65,7 @@ export class EmergencyAccessComponent implements OnInit {
private organizationService: OrganizationService, private organizationService: OrganizationService,
protected dialogService: DialogService, protected dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService, billingAccountProfileStateService: BillingAccountProfileStateService,
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
) { ) {
this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$;
} }
@@ -136,7 +138,9 @@ export class EmergencyAccessComponent implements OnInit {
return; return;
} }
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints(); const autoConfirm = await firstValueFrom(
this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$,
);
if (autoConfirm == null || !autoConfirm) { if (autoConfirm == null || !autoConfirm) {
const dialogRef = EmergencyAccessConfirmComponent.open(this.dialogService, { const dialogRef = EmergencyAccessConfirmComponent.open(this.dialogService, {
data: { data: {

View File

@@ -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";

View File

@@ -3,6 +3,7 @@ import { Router } from "@angular/router";
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component"; import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -16,7 +17,8 @@ export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
router: Router, router: Router,
i18nService: I18nService, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
) { ) {
super(twoFactorService, router, i18nService, platformUtilsService, window); super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
} }
} }

View File

@@ -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,
); );

View File

@@ -5,10 +5,10 @@
<bit-callout type="danger" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy"> <bit-callout type="danger" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
{{ "personalVaultExportPolicyInEffect" | i18n }} {{ "personalVaultExportPolicyInEffect" | i18n }}
</bit-callout> </bit-callout>
<app-export-scope-callout <tools-export-scope-callout
[organizationId]="organizationId" [organizationId]="organizationId"
*ngIf="!disabledByPolicy" *ngIf="!disabledByPolicy"
></app-export-scope-callout> ></tools-export-scope-callout>
<ng-container *ngIf="organizations$ | async as organizations"> <ng-container *ngIf="organizations$ | async as organizations">
<bit-form-field *ngIf="organizations.length > 0"> <bit-form-field *ngIf="organizations.length > 0">

View File

@@ -1,12 +1,14 @@
import { NgModule } from "@angular/core"; import { NgModule } from "@angular/core";
import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui";
import { LooseComponentsModule, SharedModule } from "../../shared"; import { LooseComponentsModule, SharedModule } from "../../shared";
import { ExportRoutingModule } from "./export-routing.module"; import { ExportRoutingModule } from "./export-routing.module";
import { ExportComponent } from "./export.component"; import { ExportComponent } from "./export.component";
@NgModule({ @NgModule({
imports: [SharedModule, LooseComponentsModule, ExportRoutingModule], imports: [SharedModule, LooseComponentsModule, ExportRoutingModule, ExportScopeCalloutComponent],
declarations: [ExportComponent], declarations: [ExportComponent],
}) })
export class ExportModule {} export class ExportModule {}

View File

@@ -40,6 +40,7 @@
[(ngModel)]="$any(c).checked" [(ngModel)]="$any(c).checked"
name="Collection[{{ i }}].Checked" name="Collection[{{ i }}].Checked"
appStopProp appStopProp
[disabled]="!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)"
/> />
</td> </td>
<td> <td>

View File

@@ -1,6 +1,7 @@
import { Component, OnDestroy } from "@angular/core"; import { Component, OnDestroy } from "@angular/core";
import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
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";
@@ -18,9 +19,17 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, i18nService: I18nService,
cipherService: CipherService, cipherService: CipherService,
organizationSerivce: OrganizationService,
logService: LogService, logService: LogService,
) { ) {
super(collectionService, platformUtilsService, i18nService, cipherService, logService); super(
collectionService,
platformUtilsService,
i18nService,
cipherService,
organizationSerivce,
logService,
);
} }
ngOnDestroy() { ngOnDestroy() {
@@ -28,6 +37,9 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
} }
check(c: CollectionView, select?: boolean) { check(c: CollectionView, select?: boolean) {
if (!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)) {
return;
}
(c as any).checked = select == null ? !(c as any).checked : select; (c as any).checked = select == null ? !(c as any).checked : select;
} }

View File

@@ -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]) => {

View File

@@ -1,6 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { 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";
@@ -25,15 +26,27 @@ export class CollectionsComponent extends BaseCollectionsComponent {
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, i18nService: I18nService,
cipherService: CipherService, cipherService: CipherService,
organizationService: OrganizationService,
private apiService: ApiService, private apiService: ApiService,
logService: LogService, logService: LogService,
) { ) {
super(collectionService, platformUtilsService, i18nService, cipherService, logService); super(
collectionService,
platformUtilsService,
i18nService,
cipherService,
organizationService,
logService,
);
this.allowSelectNone = true; this.allowSelectNone = true;
} }
protected async loadCipher() { protected async loadCipher() {
if (!this.organization.canViewAllCollections) { // if cipher is unassigned use apiService. We can see this by looking at this.collectionIds
if (
!this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) &&
this.collectionIds.length !== 0
) {
return await super.loadCipher(); return await super.loadCipher();
} }
const response = await this.apiService.getCipherAdmin(this.cipherId); const response = await this.apiService.getCipherAdmin(this.cipherId);
@@ -55,7 +68,10 @@ export class CollectionsComponent extends BaseCollectionsComponent {
} }
protected saveCollections() { protected saveCollections() {
if (this.organization.canEditAnyCollection) { if (
this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) ||
this.collectionIds.length === 0
) {
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds); const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
return this.apiService.putCipherCollectionsAdmin(this.cipherId, request); return this.apiService.putCipherCollectionsAdmin(this.cipherId, request);
} else { } else {

View File

@@ -137,6 +137,7 @@ export class VaultComponent implements OnInit, OnDestroy {
protected showCollectionAccessRestricted: boolean; protected showCollectionAccessRestricted: boolean;
protected currentSearchText$: Observable<string>; protected currentSearchText$: Observable<string>;
protected editableCollections$: Observable<CollectionView[]>; protected editableCollections$: Observable<CollectionView[]>;
protected allCollectionsWithoutUnassigned$: Observable<CollectionAdminView[]>;
protected showBulkEditCollectionAccess$ = this.configService.getFeatureFlag$( protected showBulkEditCollectionAccess$ = this.configService.getFeatureFlag$(
FeatureFlag.BulkCollectionAccess, FeatureFlag.BulkCollectionAccess,
false, false,
@@ -253,7 +254,7 @@ export class VaultComponent implements OnInit, OnDestroy {
this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search)); this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
const allCollectionsWithoutUnassigned$ = combineLatest([ this.allCollectionsWithoutUnassigned$ = combineLatest([
organizationId$.pipe(switchMap((orgId) => this.collectionAdminService.getAll(orgId))), organizationId$.pipe(switchMap((orgId) => this.collectionAdminService.getAll(orgId))),
defer(() => this.collectionService.getAllDecrypted()), defer(() => this.collectionService.getAllDecrypted()),
]).pipe( ]).pipe(
@@ -276,7 +277,7 @@ export class VaultComponent implements OnInit, OnDestroy {
shareReplay({ refCount: true, bufferSize: 1 }), shareReplay({ refCount: true, bufferSize: 1 }),
); );
this.editableCollections$ = allCollectionsWithoutUnassigned$.pipe( this.editableCollections$ = this.allCollectionsWithoutUnassigned$.pipe(
map((collections) => { map((collections) => {
// Users that can edit all ciphers can implicitly edit all collections // Users that can edit all ciphers can implicitly edit all collections
if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) { if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
@@ -287,7 +288,10 @@ export class VaultComponent implements OnInit, OnDestroy {
shareReplay({ refCount: true, bufferSize: 1 }), shareReplay({ refCount: true, bufferSize: 1 }),
); );
const allCollections$ = combineLatest([organizationId$, allCollectionsWithoutUnassigned$]).pipe( const allCollections$ = combineLatest([
organizationId$,
this.allCollectionsWithoutUnassigned$,
]).pipe(
map(([organizationId, allCollections]) => { map(([organizationId, allCollections]) => {
const noneCollection = new CollectionAdminView(); const noneCollection = new CollectionAdminView();
noneCollection.name = this.i18nService.t("unassigned"); noneCollection.name = this.i18nService.t("unassigned");
@@ -680,16 +684,35 @@ export class VaultComponent implements OnInit, OnDestroy {
if (this.flexibleCollectionsV1Enabled) { if (this.flexibleCollectionsV1Enabled) {
// V1 limits admins to only adding items to collections they have access to. // V1 limits admins to only adding items to collections they have access to.
collections = await firstValueFrom(this.editableCollections$); collections = await firstValueFrom(
} else { this.allCollectionsWithoutUnassigned$.pipe(
collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter( map((c) => {
(c) => !c.readOnly && c.id != Unassigned, return c.sort((a, b) => {
if (
a.canEditItems(this.organization, true) &&
!b.canEditItems(this.organization, true)
) {
return -1;
} else if (
!a.canEditItems(this.organization, true) &&
b.canEditItems(this.organization, true)
) {
return 1;
} else {
return a.name.localeCompare(b.name);
}
});
}),
),
); );
} else {
collections = await firstValueFrom(this.allCollectionsWithoutUnassigned$);
} }
const [modal] = await this.modalService.openViewRef( const [modal] = await this.modalService.openViewRef(
CollectionsComponent, CollectionsComponent,
this.collectionsModalRef, this.collectionsModalRef,
(comp) => { (comp) => {
comp.flexibleCollectionsV1Enabled = this.flexibleCollectionsV1Enabled;
comp.collectionIds = cipher.collectionIds; comp.collectionIds = cipher.collectionIds;
comp.collections = collections; comp.collections = collections;
comp.organization = this.organization; comp.organization = this.organization;

View File

@@ -15,6 +15,7 @@
"@bitwarden/vault-export-core": [ "@bitwarden/vault-export-core": [
"../../libs/tools/export/vault-export/vault-export-core/src" "../../libs/tools/export/vault-export/vault-export-core/src"
], ],
"@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"],
"@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/core": ["../../libs/importer/src"],
"@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"],
"@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/platform": ["../../libs/platform/src"],

View File

@@ -7,6 +7,7 @@ import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { ProviderUserStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { ProviderUserStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums";
import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request"; import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request";
@@ -18,7 +19,6 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
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 { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
import { BasePeopleComponent } from "@bitwarden/web-vault/app/admin-console/common/base.people.component"; import { BasePeopleComponent } from "@bitwarden/web-vault/app/admin-console/common/base.people.component";
@@ -67,9 +67,9 @@ export class PeopleComponent
logService: LogService, logService: LogService,
searchPipe: SearchPipe, searchPipe: SearchPipe,
userNamePipe: UserNamePipe, userNamePipe: UserNamePipe,
stateService: StateService,
private providerService: ProviderService, private providerService: ProviderService,
dialogService: DialogService, dialogService: DialogService,
organizationManagementPreferencesService: OrganizationManagementPreferencesService,
) { ) {
super( super(
apiService, apiService,
@@ -82,8 +82,8 @@ export class PeopleComponent
logService, logService,
searchPipe, searchPipe,
userNamePipe, userNamePipe,
stateService,
dialogService, dialogService,
organizationManagementPreferencesService,
); );
} }

View File

@@ -13,6 +13,7 @@ const routes: Routes = [
}, },
{ {
path: "sm", path: "sm",
canActivate: [deepLinkGuard()],
loadChildren: async () => loadChildren: async () =>
(await import("./secrets-manager/secrets-manager.module")).SecretsManagerModule, (await import("./secrets-manager/secrets-manager.module")).SecretsManagerModule,
}, },

View File

@@ -11,6 +11,7 @@
"@bitwarden/vault-export-core": [ "@bitwarden/vault-export-core": [
"../../libs/tools/export/vault-export/vault-export-core/src" "../../libs/tools/export/vault-export/vault-export-core/src"
], ],
"@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-core/src"],
"@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/platform": ["../../libs/platform/src"],
"@bitwarden/vault": ["../../libs/vault/src"], "@bitwarden/vault": ["../../libs/vault/src"],
"@bitwarden/web-vault/*": ["../../apps/web/src/*"] "@bitwarden/web-vault/*": ["../../apps/web/src/*"]

View File

@@ -1,5 +1,7 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
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";
@@ -19,6 +21,8 @@ export class CollectionsComponent implements OnInit {
cipher: CipherView; cipher: CipherView;
collectionIds: string[]; collectionIds: string[];
collections: CollectionView[] = []; collections: CollectionView[] = [];
organization: Organization;
flexibleCollectionsV1Enabled: boolean;
protected cipherDomain: Cipher; protected cipherDomain: Cipher;
@@ -27,6 +31,7 @@ export class CollectionsComponent implements OnInit {
protected platformUtilsService: PlatformUtilsService, protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService, protected i18nService: I18nService,
protected cipherService: CipherService, protected cipherService: CipherService,
protected organizationService: OrganizationService,
private logService: LogService, private logService: LogService,
) {} ) {}
@@ -48,11 +53,21 @@ export class CollectionsComponent implements OnInit {
(c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1; (c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1;
}); });
} }
if (this.organization == null) {
this.organization = await this.organizationService.get(this.cipher.organizationId);
}
} }
async submit() { async submit() {
const selectedCollectionIds = this.collections const selectedCollectionIds = this.collections
.filter((c) => !!(c as any).checked) .filter((c) => {
if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
return !!(c as any).checked;
} else {
return !!(c as any).checked && c.readOnly == null;
}
})
.map((c) => c.id); .map((c) => c.id);
if (!this.allowSelectNone && selectedCollectionIds.length === 0) { if (!this.allowSelectNone && selectedCollectionIds.length === 0) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}); });

View File

@@ -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

View File

@@ -3,6 +3,7 @@ import { Router } from "@angular/router";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -19,6 +20,7 @@ export class TwoFactorOptionsComponent implements OnInit {
protected i18nService: I18nService, protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService, protected platformUtilsService: PlatformUtilsService,
protected win: Window, protected win: Window,
protected environmentService: EnvironmentService,
) {} ) {}
ngOnInit() { ngOnInit() {
@@ -30,7 +32,8 @@ export class TwoFactorOptionsComponent implements OnInit {
} }
recover() { recover() {
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/recover-2fa"); const webVault = this.environmentService.getWebVaultUrl();
this.platformUtilsService.launchUri(webVault + "/#/recover-2fa");
this.onRecoverSelected.emit(); this.onRecoverSelected.emit();
} }
} }

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -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 });

View File

@@ -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(["/"]);

View File

@@ -29,7 +29,6 @@ import { UserTypePipe } from "./pipes/user-type.pipe";
import { EllipsisPipe } from "./platform/pipes/ellipsis.pipe"; import { EllipsisPipe } from "./platform/pipes/ellipsis.pipe";
import { FingerprintPipe } from "./platform/pipes/fingerprint.pipe"; import { FingerprintPipe } from "./platform/pipes/fingerprint.pipe";
import { I18nPipe } from "./platform/pipes/i18n.pipe"; import { I18nPipe } from "./platform/pipes/i18n.pipe";
import { ExportScopeCalloutComponent } from "./tools/export/components/export-scope-callout.component";
import { PasswordStrengthComponent } from "./tools/password-strength/password-strength.component"; import { PasswordStrengthComponent } from "./tools/password-strength/password-strength.component";
import { IconComponent } from "./vault/components/icon.component"; import { IconComponent } from "./vault/components/icon.component";
@@ -54,7 +53,6 @@ import { IconComponent } from "./vault/components/icon.component";
CopyTextDirective, CopyTextDirective,
CreditCardNumberPipe, CreditCardNumberPipe,
EllipsisPipe, EllipsisPipe,
ExportScopeCalloutComponent,
FallbackSrcDirective, FallbackSrcDirective,
I18nPipe, I18nPipe,
IconComponent, IconComponent,
@@ -85,7 +83,6 @@ import { IconComponent } from "./vault/components/icon.component";
CopyTextDirective, CopyTextDirective,
CreditCardNumberPipe, CreditCardNumberPipe,
EllipsisPipe, EllipsisPipe,
ExportScopeCalloutComponent,
FallbackSrcDirective, FallbackSrcDirective,
I18nPipe, I18nPipe,
IconComponent, IconComponent,

View File

@@ -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";
@@ -27,6 +30,7 @@ import {
OrgDomainInternalServiceAbstraction, OrgDomainInternalServiceAbstraction,
OrgDomainServiceAbstraction, OrgDomainServiceAbstraction,
} from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain.service.abstraction"; } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain.service.abstraction";
import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { import {
@@ -38,6 +42,7 @@ import { OrganizationApiService } from "@bitwarden/common/admin-console/services
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service"; import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
import { OrgDomainApiService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain-api.service"; import { OrgDomainApiService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain-api.service";
import { OrgDomainService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain.service"; import { OrgDomainService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain.service";
import { DefaultOrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/services/organization-management-preferences/default-organization-management-preferences.service";
import { OrganizationUserServiceImplementation } from "@bitwarden/common/admin-console/services/organization-user/organization-user.service.implementation"; import { OrganizationUserServiceImplementation } from "@bitwarden/common/admin-console/services/organization-user/organization-user.service.implementation";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
@@ -241,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,
@@ -367,6 +372,7 @@ const typesafeProviders: Array<SafeProvider> = [
PolicyServiceAbstraction, PolicyServiceAbstraction,
DeviceTrustCryptoServiceAbstraction, DeviceTrustCryptoServiceAbstraction,
AuthRequestServiceAbstraction, AuthRequestServiceAbstraction,
InternalUserDecryptionOptionsServiceAbstraction,
GlobalStateProvider, GlobalStateProvider,
BillingAccountProfileStateService, BillingAccountProfileStateService,
], ],
@@ -475,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,
@@ -575,6 +590,7 @@ const typesafeProviders: Array<SafeProvider> = [
FolderApiServiceAbstraction, FolderApiServiceAbstraction,
InternalOrganizationServiceAbstraction, InternalOrganizationServiceAbstraction,
SendApiServiceAbstraction, SendApiServiceAbstraction,
UserDecryptionOptionsServiceAbstraction,
AvatarServiceAbstraction, AvatarServiceAbstraction,
LOGOUT_CALLBACK, LOGOUT_CALLBACK,
BillingAccountProfileStateService, BillingAccountProfileStateService,
@@ -585,6 +601,7 @@ const typesafeProviders: Array<SafeProvider> = [
provide: VaultTimeoutSettingsServiceAbstraction, provide: VaultTimeoutSettingsServiceAbstraction,
useClass: VaultTimeoutSettingsService, useClass: VaultTimeoutSettingsService,
deps: [ deps: [
UserDecryptionOptionsServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
TokenServiceAbstraction, TokenServiceAbstraction,
PolicyServiceAbstraction, PolicyServiceAbstraction,
@@ -763,6 +780,7 @@ const typesafeProviders: Array<SafeProvider> = [
CryptoServiceAbstraction, CryptoServiceAbstraction,
I18nServiceAbstraction, I18nServiceAbstraction,
UserVerificationApiServiceAbstraction, UserVerificationApiServiceAbstraction,
UserDecryptionOptionsServiceAbstraction,
PinCryptoServiceAbstraction, PinCryptoServiceAbstraction,
LogService, LogService,
VaultTimeoutSettingsServiceAbstraction, VaultTimeoutSettingsServiceAbstraction,
@@ -900,6 +918,7 @@ const typesafeProviders: Array<SafeProvider> = [
DevicesApiServiceAbstraction, DevicesApiServiceAbstraction,
I18nServiceAbstraction, I18nServiceAbstraction,
PlatformUtilsServiceAbstraction, PlatformUtilsServiceAbstraction,
UserDecryptionOptionsServiceAbstraction,
], ],
}), }),
safeProvider({ safeProvider({
@@ -1048,6 +1067,11 @@ const typesafeProviders: Array<SafeProvider> = [
useClass: DefaultBillingAccountProfileStateService, useClass: DefaultBillingAccountProfileStateService,
deps: [ActiveUserStateProvider], deps: [ActiveUserStateProvider],
}), }),
safeProvider({
provide: OrganizationManagementPreferencesService,
useClass: DefaultOrganizationManagementPreferencesService,
deps: [StateProvider],
}),
]; ];
function encryptServiceFactory( function encryptServiceFactory(

View File

@@ -18,12 +18,12 @@
{{ dialogOptions.bodyText | i18n }} {{ dialogOptions.bodyText | i18n }}
</p> </p>
<app-callout <bit-callout
*ngIf="dialogOptions.calloutOptions" *ngIf="dialogOptions.calloutOptions"
[type]="dialogOptions.calloutOptions.type" [type]="dialogOptions.calloutOptions.type"
> >
{{ dialogOptions.calloutOptions.text | i18n }} {{ dialogOptions.calloutOptions.text | i18n }}
</app-callout> </bit-callout>
</ng-container> </ng-container>
<!-- Shown when client side verification methods picked and no verification methods found --> <!-- Shown when client side verification methods picked and no verification methods found -->

View File

@@ -12,6 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { import {
AsyncActionsModule, AsyncActionsModule,
ButtonModule, ButtonModule,
CalloutModule,
DialogModule, DialogModule,
DialogService, DialogService,
} from "@bitwarden/components"; } from "@bitwarden/components";
@@ -34,6 +35,7 @@ import { UserVerificationFormInputComponent } from "./user-verification-form-inp
DialogModule, DialogModule,
AsyncActionsModule, AsyncActionsModule,
UserVerificationFormInputComponent, UserVerificationFormInputComponent,
CalloutModule,
], ],
}) })
export class UserVerificationDialogComponent { export class UserVerificationDialogComponent {

View File

@@ -11,9 +11,9 @@ export type UserVerificationCalloutOptions = {
/** /**
* The type of the callout. * The type of the callout.
* Can be "warning", "danger", "error", or "tip". * Can be "warning", "danger", "info", or "success".
*/ */
type: "warning" | "danger" | "error" | "tip"; type: "warning" | "danger" | "info" | "success";
}; };
/** /**

View File

@@ -49,12 +49,12 @@
</div> </div>
</div> </div>
<app-callout type="error" *ngIf="biometricsVerificationFailed"> <bit-callout type="danger" *ngIf="biometricsVerificationFailed">
{{ "couldNotCompleteBiometrics" | i18n }} {{ "couldNotCompleteBiometrics" | i18n }}
<button bitLink type="button" linkType="primary" (click)="verifyUserViaBiometrics()"> <button bitLink type="button" linkType="primary" (click)="verifyUserViaBiometrics()">
{{ "tryAgain" | i18n }} {{ "tryAgain" | i18n }}
</button> </button>
</app-callout> </bit-callout>
</ng-container> </ng-container>
<!-- Alternate verification options if user has more than 1 --> <!-- Alternate verification options if user has more than 1 -->

View File

@@ -20,6 +20,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
import { import {
AsyncActionsModule, AsyncActionsModule,
ButtonModule, ButtonModule,
CalloutModule,
FormFieldModule, FormFieldModule,
IconButtonModule, IconButtonModule,
IconModule, IconModule,
@@ -62,6 +63,7 @@ import { ActiveClientVerificationOption } from "./active-client-verification-opt
IconModule, IconModule,
LinkModule, LinkModule,
ButtonModule, ButtonModule,
CalloutModule,
], ],
}) })
// eslint-disable-next-line rxjs-angular/prefer-takeuntil // eslint-disable-next-line rxjs-angular/prefer-takeuntil

View File

@@ -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";

View File

@@ -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>;
}

View File

@@ -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,
); );

View File

@@ -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,
); );

View File

@@ -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,

View File

@@ -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);
} }

View File

@@ -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,

View File

@@ -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,
); );

View File

@@ -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,

View File

@@ -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,
); );

View File

@@ -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,

View File

@@ -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);

View File

@@ -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,
); );

View File

@@ -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,
); );

View File

@@ -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";

View File

@@ -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;
}
}

View File

@@ -1 +1,2 @@
export * from "./domain"; export * from "./domain";
export * from "./spec";

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
export * from "./fake-user-decryption-options";

View File

@@ -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";

View File

@@ -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,
); );

View File

@@ -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,
); );
} }

View File

@@ -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);
});
});
});

View File

@@ -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);
}
}

View File

@@ -0,0 +1,22 @@
import { Observable } from "rxjs";
/**
* Manages the state of a single organization management preference.
* Can be used to subscribe to or update a given property.
*/
export class OrganizationManagementPreference<T> {
state$: Observable<T>;
set: (value: T) => Promise<void>;
constructor(state$: Observable<T>, setFn: (value: T) => Promise<void>) {
this.state$ = state$;
this.set = setFn;
}
}
/**
* Publishes state of a given user's personal settings relating to the user experience of managing an organization.
*/
export abstract class OrganizationManagementPreferencesService {
autoConfirmFingerPrints: OrganizationManagementPreference<boolean>;
}

View File

@@ -0,0 +1,44 @@
import { MockProxy } from "jest-mock-extended";
import { firstValueFrom } from "rxjs";
import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec";
import { UserId } from "../../../types/guid";
import { DefaultOrganizationManagementPreferencesService } from "./default-organization-management-preferences.service";
describe("OrganizationManagementPreferencesService", () => {
let stateProvider: FakeStateProvider;
let organizationManagementPreferencesService: MockProxy<DefaultOrganizationManagementPreferencesService>;
beforeEach(() => {
const accountService = mockAccountServiceWith("userId" as UserId);
stateProvider = new FakeStateProvider(accountService);
organizationManagementPreferencesService = new DefaultOrganizationManagementPreferencesService(
stateProvider,
);
});
describe("autoConfirmFingerPrints", () => {
it("returns false by default", async () => {
const value = await firstValueFrom(
organizationManagementPreferencesService.autoConfirmFingerPrints.state$,
);
expect(value).toEqual(false);
});
it("returns true if set", async () => {
await organizationManagementPreferencesService.autoConfirmFingerPrints.set(true);
const value = await firstValueFrom(
organizationManagementPreferencesService.autoConfirmFingerPrints.state$,
);
expect(value).toEqual(true);
});
it("can be unset", async () => {
await organizationManagementPreferencesService.autoConfirmFingerPrints.set(true);
await organizationManagementPreferencesService.autoConfirmFingerPrints.set(false);
const value = await firstValueFrom(
organizationManagementPreferencesService.autoConfirmFingerPrints.state$,
);
expect(value).toEqual(false);
});
});
});

View File

@@ -0,0 +1,71 @@
import { map } from "rxjs";
import { Jsonify } from "type-fest";
import {
ORGANIZATION_MANAGEMENT_PREFERENCES_DISK,
StateProvider,
UserKeyDefinition,
} from "../../../platform/state";
import {
OrganizationManagementPreference,
OrganizationManagementPreferencesService,
} from "../../abstractions/organization-management-preferences/organization-management-preferences.service";
/**
* This helper function can be used to quickly create `KeyDefinitions` that
* target the `ORGANIZATION_MANAGEMENT_PREFERENCES_DISK` `StateDefinition`
* and that have the default deserializer and `clearOn` options. Any
* contenders for options to add to this service will likely use these same
* options.
*/
function buildKeyDefinition<T>(key: string): UserKeyDefinition<T> {
return new UserKeyDefinition<T>(ORGANIZATION_MANAGEMENT_PREFERENCES_DISK, key, {
deserializer: (obj: Jsonify<T>) => obj as T,
clearOn: ["logout"],
});
}
export const AUTO_CONFIRM_FINGERPRINTS = buildKeyDefinition<boolean>("autoConfirmFingerPrints");
export class DefaultOrganizationManagementPreferencesService
implements OrganizationManagementPreferencesService
{
constructor(private stateProvider: StateProvider) {}
autoConfirmFingerPrints = this.buildOrganizationManagementPreference(
AUTO_CONFIRM_FINGERPRINTS,
false,
);
/**
* Returns an `OrganizationManagementPreference` object for the provided
* `KeyDefinition`. This object can then be used by callers to subscribe to
* a given key, or set its value in state.
*/
private buildOrganizationManagementPreference<T>(
keyDefinition: UserKeyDefinition<T>,
defaultValue: T,
) {
return new OrganizationManagementPreference<T>(
this.getKeyFromState(keyDefinition).state$.pipe(map((x) => x ?? defaultValue)),
this.setKeyInStateFn(keyDefinition),
);
}
/**
* Returns the full `ActiveUserState` value for a given `keyDefinition`
* The returned value can then be called for subscription || set operations
*/
private getKeyFromState<T>(keyDefinition: UserKeyDefinition<T>) {
return this.stateProvider.getActive(keyDefinition);
}
/**
* Returns a function that can be called to set the given `keyDefinition` in state
*/
private setKeyInStateFn<T>(keyDefinition: UserKeyDefinition<T>) {
return async (value: T) => {
await this.getKeyFromState(keyDefinition).update(() => value);
};
}
}

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