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:
@@ -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"],
|
||||
"rules": {
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
factory,
|
||||
} from "../../../platform/background/service-factories/factory-options";
|
||||
import {
|
||||
messagingServiceFactory,
|
||||
MessagingServiceInitOptions,
|
||||
messagingServiceFactory,
|
||||
} from "../../../platform/background/service-factories/messaging-service.factory";
|
||||
import {
|
||||
StateServiceInitOptions,
|
||||
|
||||
@@ -43,6 +43,11 @@ import {
|
||||
stateServiceFactory,
|
||||
} from "../../../platform/background/service-factories/state-service.factory";
|
||||
|
||||
import {
|
||||
UserDecryptionOptionsServiceInitOptions,
|
||||
userDecryptionOptionsServiceFactory,
|
||||
} from "./user-decryption-options-service.factory";
|
||||
|
||||
type DeviceTrustCryptoServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type DeviceTrustCryptoServiceInitOptions = DeviceTrustCryptoServiceFactoryOptions &
|
||||
@@ -54,7 +59,8 @@ export type DeviceTrustCryptoServiceInitOptions = DeviceTrustCryptoServiceFactor
|
||||
AppIdServiceInitOptions &
|
||||
DevicesApiServiceInitOptions &
|
||||
I18nServiceInitOptions &
|
||||
PlatformUtilsServiceInitOptions;
|
||||
PlatformUtilsServiceInitOptions &
|
||||
UserDecryptionOptionsServiceInitOptions;
|
||||
|
||||
export function deviceTrustCryptoServiceFactory(
|
||||
cache: { deviceTrustCryptoService?: DeviceTrustCryptoServiceAbstraction } & CachedServices,
|
||||
@@ -75,6 +81,7 @@ export function deviceTrustCryptoServiceFactory(
|
||||
await devicesApiServiceFactory(cache, opts),
|
||||
await i18nServiceFactory(cache, opts),
|
||||
await platformUtilsServiceFactory(cache, opts),
|
||||
await userDecryptionOptionsServiceFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
ApiServiceInitOptions,
|
||||
} from "../../../platform/background/service-factories/api-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 {
|
||||
CryptoServiceInitOptions,
|
||||
cryptoServiceFactory,
|
||||
@@ -70,6 +73,10 @@ import {
|
||||
} from "./key-connector-service.factory";
|
||||
import { tokenServiceFactory, TokenServiceInitOptions } from "./token-service.factory";
|
||||
import { twoFactorServiceFactory, TwoFactorServiceInitOptions } from "./two-factor-service.factory";
|
||||
import {
|
||||
internalUserDecryptionOptionServiceFactory,
|
||||
UserDecryptionOptionsServiceInitOptions,
|
||||
} from "./user-decryption-options-service.factory";
|
||||
|
||||
type LoginStrategyServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
@@ -90,7 +97,9 @@ export type LoginStrategyServiceInitOptions = LoginStrategyServiceFactoryOptions
|
||||
PasswordStrengthServiceInitOptions &
|
||||
DeviceTrustCryptoServiceInitOptions &
|
||||
AuthRequestServiceInitOptions &
|
||||
GlobalStateProviderInitOptions;
|
||||
UserDecryptionOptionsServiceInitOptions &
|
||||
GlobalStateProviderInitOptions &
|
||||
BillingAccountProfileStateServiceInitOptions;
|
||||
|
||||
export function loginStrategyServiceFactory(
|
||||
cache: { loginStrategyService?: LoginStrategyServiceAbstraction } & CachedServices,
|
||||
@@ -119,6 +128,7 @@ export function loginStrategyServiceFactory(
|
||||
await policyServiceFactory(cache, opts),
|
||||
await deviceTrustCryptoServiceFactory(cache, opts),
|
||||
await authRequestServiceFactory(cache, opts),
|
||||
await internalUserDecryptionOptionServiceFactory(cache, opts),
|
||||
await globalStateProviderFactory(cache, opts),
|
||||
await billingAccountProfileStateServiceFactory(cache, opts),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
UserDecryptionOptionsService,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
|
||||
import {
|
||||
CachedServices,
|
||||
factory,
|
||||
FactoryOptions,
|
||||
} from "../../../platform/background/service-factories/factory-options";
|
||||
import {
|
||||
stateProviderFactory,
|
||||
StateProviderInitOptions,
|
||||
} from "../../../platform/background/service-factories/state-provider.factory";
|
||||
|
||||
type UserDecryptionOptionsServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type UserDecryptionOptionsServiceInitOptions = UserDecryptionOptionsServiceFactoryOptions &
|
||||
StateProviderInitOptions;
|
||||
|
||||
export function userDecryptionOptionsServiceFactory(
|
||||
cache: {
|
||||
userDecryptionOptionsService?: InternalUserDecryptionOptionsServiceAbstraction;
|
||||
} & CachedServices,
|
||||
opts: UserDecryptionOptionsServiceInitOptions,
|
||||
): Promise<UserDecryptionOptionsServiceAbstraction> {
|
||||
return factory(
|
||||
cache,
|
||||
"userDecryptionOptionsService",
|
||||
opts,
|
||||
async () => new UserDecryptionOptionsService(await stateProviderFactory(cache, opts)),
|
||||
);
|
||||
}
|
||||
|
||||
export async function internalUserDecryptionOptionServiceFactory(
|
||||
cache: {
|
||||
userDecryptionOptionsService?: InternalUserDecryptionOptionsServiceAbstraction;
|
||||
} & CachedServices,
|
||||
opts: UserDecryptionOptionsServiceInitOptions,
|
||||
): Promise<InternalUserDecryptionOptionsServiceAbstraction> {
|
||||
return (await userDecryptionOptionsServiceFactory(
|
||||
cache,
|
||||
opts,
|
||||
)) as InternalUserDecryptionOptionsServiceAbstraction;
|
||||
}
|
||||
@@ -32,6 +32,10 @@ import {
|
||||
} from "../../../platform/background/service-factories/state-service.factory";
|
||||
|
||||
import { PinCryptoServiceInitOptions, pinCryptoServiceFactory } from "./pin-crypto-service.factory";
|
||||
import {
|
||||
userDecryptionOptionsServiceFactory,
|
||||
UserDecryptionOptionsServiceInitOptions,
|
||||
} from "./user-decryption-options-service.factory";
|
||||
import {
|
||||
UserVerificationApiServiceInitOptions,
|
||||
userVerificationApiServiceFactory,
|
||||
@@ -44,6 +48,7 @@ export type UserVerificationServiceInitOptions = UserVerificationServiceFactoryO
|
||||
CryptoServiceInitOptions &
|
||||
I18nServiceInitOptions &
|
||||
UserVerificationApiServiceInitOptions &
|
||||
UserDecryptionOptionsServiceInitOptions &
|
||||
PinCryptoServiceInitOptions &
|
||||
LogServiceInitOptions &
|
||||
VaultTimeoutSettingsServiceInitOptions &
|
||||
@@ -63,6 +68,7 @@ export function userVerificationServiceFactory(
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await i18nServiceFactory(cache, opts),
|
||||
await userVerificationApiServiceFactory(cache, opts),
|
||||
await userDecryptionOptionsServiceFactory(cache, opts),
|
||||
await pinCryptoServiceFactory(cache, opts),
|
||||
await logServiceFactory(cache, opts),
|
||||
await vaultTimeoutSettingsServiceFactory(cache, opts),
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
@@ -37,6 +38,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
|
||||
route: ActivatedRoute,
|
||||
organizationApiService: OrganizationApiServiceAbstraction,
|
||||
organizationUserService: OrganizationUserService,
|
||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
dialogService: DialogService,
|
||||
) {
|
||||
@@ -55,6 +57,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent {
|
||||
stateService,
|
||||
organizationApiService,
|
||||
organizationUserService,
|
||||
userDecryptionOptionsService,
|
||||
ssoLoginService,
|
||||
dialogService,
|
||||
);
|
||||
|
||||
@@ -3,7 +3,10 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
||||
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 { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
@@ -39,6 +42,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||
syncService: SyncService,
|
||||
environmentService: EnvironmentService,
|
||||
logService: LogService,
|
||||
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
configService: ConfigServiceAbstraction,
|
||||
protected authService: AuthService,
|
||||
@Inject(WINDOW) private win: Window,
|
||||
@@ -56,6 +60,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||
environmentService,
|
||||
passwordGenerationService,
|
||||
logService,
|
||||
userDecryptionOptionsService,
|
||||
configService,
|
||||
);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component";
|
||||
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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@@ -16,9 +17,10 @@ export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
) {
|
||||
super(twoFactorService, router, i18nService, platformUtilsService, window);
|
||||
super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
|
||||
}
|
||||
|
||||
close() {
|
||||
|
||||
@@ -5,7 +5,10 @@ import { filter, first, takeUntil } from "rxjs/operators";
|
||||
|
||||
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
|
||||
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 { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
@@ -55,6 +58,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
twoFactorService: TwoFactorService,
|
||||
appIdService: AppIdService,
|
||||
loginService: LoginService,
|
||||
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
configService: ConfigServiceAbstraction,
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
private dialogService: DialogService,
|
||||
@@ -75,6 +79,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
twoFactorService,
|
||||
appIdService,
|
||||
loginService,
|
||||
userDecryptionOptionsService,
|
||||
ssoLoginService,
|
||||
configService,
|
||||
);
|
||||
|
||||
@@ -625,6 +625,22 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
* Updates the position of the overlay button.
|
||||
*/
|
||||
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", {
|
||||
overlayElement: AutofillOverlayElement.Button,
|
||||
});
|
||||
@@ -634,6 +650,22 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
||||
* Updates the position of the overlay list.
|
||||
*/
|
||||
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", {
|
||||
overlayElement: AutofillOverlayElement.List,
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
PinCryptoService,
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginStrategyService,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
UserDecryptionOptionsService,
|
||||
AuthRequestServiceAbstraction,
|
||||
AuthRequestService,
|
||||
} from "@bitwarden/auth/common";
|
||||
@@ -242,6 +244,7 @@ export default class MainBackground {
|
||||
environmentService: BrowserEnvironmentService;
|
||||
cipherService: CipherServiceAbstraction;
|
||||
folderService: InternalFolderServiceAbstraction;
|
||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction;
|
||||
collectionService: CollectionServiceAbstraction;
|
||||
vaultTimeoutService: VaultTimeoutService;
|
||||
vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction;
|
||||
@@ -539,6 +542,8 @@ export default class MainBackground {
|
||||
};
|
||||
})();
|
||||
|
||||
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
|
||||
|
||||
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
|
||||
this.deviceTrustCryptoService = new DeviceTrustCryptoService(
|
||||
this.keyGenerationService,
|
||||
@@ -550,6 +555,7 @@ export default class MainBackground {
|
||||
this.devicesApiService,
|
||||
this.i18nService,
|
||||
this.platformUtilsService,
|
||||
this.userDecryptionOptionsService,
|
||||
);
|
||||
|
||||
this.devicesService = new DevicesServiceImplementation(this.devicesApiService);
|
||||
@@ -590,6 +596,7 @@ export default class MainBackground {
|
||||
this.policyService,
|
||||
this.deviceTrustCryptoService,
|
||||
this.authRequestService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.globalStateProvider,
|
||||
this.billingAccountProfileStateService,
|
||||
);
|
||||
@@ -631,6 +638,7 @@ export default class MainBackground {
|
||||
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
||||
|
||||
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
|
||||
this.userDecryptionOptionsService,
|
||||
this.cryptoService,
|
||||
this.tokenService,
|
||||
this.policyService,
|
||||
@@ -650,6 +658,7 @@ export default class MainBackground {
|
||||
this.cryptoService,
|
||||
this.i18nService,
|
||||
this.userVerificationApiService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.pinCryptoService,
|
||||
this.logService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
@@ -717,6 +726,7 @@ export default class MainBackground {
|
||||
this.folderApiService,
|
||||
this.organizationService,
|
||||
this.sendApiService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.avatarService,
|
||||
logoutCallback,
|
||||
this.billingAccountProfileStateService,
|
||||
|
||||
@@ -9,6 +9,10 @@ import {
|
||||
tokenServiceFactory,
|
||||
TokenServiceInitOptions,
|
||||
} from "../../auth/background/service-factories/token-service.factory";
|
||||
import {
|
||||
userDecryptionOptionsServiceFactory,
|
||||
UserDecryptionOptionsServiceInitOptions,
|
||||
} from "../../auth/background/service-factories/user-decryption-options-service.factory";
|
||||
import {
|
||||
biometricStateServiceFactory,
|
||||
BiometricStateServiceInitOptions,
|
||||
@@ -30,6 +34,7 @@ import {
|
||||
type VaultTimeoutSettingsServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type VaultTimeoutSettingsServiceInitOptions = VaultTimeoutSettingsServiceFactoryOptions &
|
||||
UserDecryptionOptionsServiceInitOptions &
|
||||
CryptoServiceInitOptions &
|
||||
TokenServiceInitOptions &
|
||||
PolicyServiceInitOptions &
|
||||
@@ -46,6 +51,7 @@ export function vaultTimeoutSettingsServiceFactory(
|
||||
opts,
|
||||
async () =>
|
||||
new VaultTimeoutSettingsService(
|
||||
await userDecryptionOptionsServiceFactory(cache, opts),
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await tokenServiceFactory(cache, opts),
|
||||
await policyServiceFactory(cache, opts),
|
||||
|
||||
@@ -16,6 +16,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
|
||||
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
|
||||
import { AvatarModule, ButtonModule } from "@bitwarden/components";
|
||||
import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui";
|
||||
|
||||
import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component";
|
||||
import { AccountComponent } from "../auth/popup/account-switching/account.component";
|
||||
@@ -107,6 +108,7 @@ import "../platform/popup/locales";
|
||||
AvatarModule,
|
||||
AccountComponent,
|
||||
ButtonModule,
|
||||
ExportScopeCalloutComponent,
|
||||
],
|
||||
declarations: [
|
||||
ActionButtonsComponent,
|
||||
|
||||
@@ -82,7 +82,6 @@ import {
|
||||
import { SearchService } from "@bitwarden/common/services/search.service";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
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 as SendApiServiceAbstraction } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
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 { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { ImportServiceAbstraction } from "@bitwarden/importer/core";
|
||||
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
|
||||
|
||||
import { UnauthGuardService } from "../../auth/popup/services";
|
||||
@@ -292,11 +290,6 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
},
|
||||
deps: [DomSanitizer, ToastrService],
|
||||
},
|
||||
{
|
||||
provide: PasswordStrengthServiceAbstraction,
|
||||
useFactory: getBgService<PasswordStrengthServiceAbstraction>("passwordStrengthService"),
|
||||
deps: [],
|
||||
},
|
||||
{
|
||||
provide: PasswordGenerationServiceAbstraction,
|
||||
useFactory: getBgService<PasswordGenerationServiceAbstraction>("passwordGenerationService"),
|
||||
@@ -350,11 +343,6 @@ function getBgService<T>(service: keyof MainBackground) {
|
||||
useFactory: getBgService<AutofillService>("autofillService"),
|
||||
deps: [],
|
||||
},
|
||||
{
|
||||
provide: ImportServiceAbstraction,
|
||||
useFactory: getBgService<ImportServiceAbstraction>("importService"),
|
||||
deps: [],
|
||||
},
|
||||
{
|
||||
provide: VaultExportServiceAbstraction,
|
||||
useFactory: getBgService<VaultExportServiceAbstraction>("exportService"),
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<app-callout type="warning" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
|
||||
{{ "personalVaultExportPolicyInEffect" | i18n }}
|
||||
</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-content">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ActivatedRoute } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
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 { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -21,11 +22,19 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
cipherService: CipherService,
|
||||
organizationService: OrganizationService,
|
||||
private route: ActivatedRoute,
|
||||
private location: Location,
|
||||
logService: LogService,
|
||||
) {
|
||||
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
|
||||
super(
|
||||
collectionService,
|
||||
platformUtilsService,
|
||||
i18nService,
|
||||
cipherService,
|
||||
organizationService,
|
||||
logService,
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@bitwarden/vault-export-core": [
|
||||
"../../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/ui": ["../../libs/importer/src/components"],
|
||||
"@bitwarden/platform": ["../../libs/platform/src"],
|
||||
|
||||
@@ -5,11 +5,13 @@ import { program } from "commander";
|
||||
import * as jsdom from "jsdom";
|
||||
|
||||
import {
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
AuthRequestService,
|
||||
LoginStrategyService,
|
||||
LoginStrategyServiceAbstraction,
|
||||
PinCryptoService,
|
||||
PinCryptoServiceAbstraction,
|
||||
UserDecryptionOptionsService,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||
@@ -169,6 +171,7 @@ export class Main {
|
||||
eventUploadService: EventUploadServiceAbstraction;
|
||||
passwordGenerationService: PasswordGenerationServiceAbstraction;
|
||||
passwordStrengthService: PasswordStrengthServiceAbstraction;
|
||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction;
|
||||
totpService: TotpService;
|
||||
containerService: ContainerService;
|
||||
auditService: AuditService;
|
||||
@@ -436,6 +439,8 @@ export class Main {
|
||||
this.stateService,
|
||||
);
|
||||
|
||||
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
|
||||
|
||||
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
|
||||
this.deviceTrustCryptoService = new DeviceTrustCryptoService(
|
||||
this.keyGenerationService,
|
||||
@@ -447,6 +452,7 @@ export class Main {
|
||||
this.devicesApiService,
|
||||
this.i18nService,
|
||||
this.platformUtilsService,
|
||||
this.userDecryptionOptionsService,
|
||||
);
|
||||
|
||||
this.authRequestService = new AuthRequestService(
|
||||
@@ -478,6 +484,7 @@ export class Main {
|
||||
this.policyService,
|
||||
this.deviceTrustCryptoService,
|
||||
this.authRequestService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.globalStateProvider,
|
||||
this.billingAccountProfileStateService,
|
||||
);
|
||||
@@ -529,6 +536,7 @@ export class Main {
|
||||
this.biometricStateService = new DefaultBiometricStateService(this.stateProvider);
|
||||
|
||||
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
|
||||
this.userDecryptionOptionsService,
|
||||
this.cryptoService,
|
||||
this.tokenService,
|
||||
this.policyService,
|
||||
@@ -548,6 +556,7 @@ export class Main {
|
||||
this.cryptoService,
|
||||
this.i18nService,
|
||||
this.userVerificationApiService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.pinCryptoService,
|
||||
this.logService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
@@ -589,6 +598,7 @@ export class Main {
|
||||
this.folderApiService,
|
||||
this.organizationService,
|
||||
this.sendApiService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.avatarService,
|
||||
async (expired: boolean) => await this.logout(),
|
||||
this.billingAccountProfileStateService,
|
||||
|
||||
4
apps/desktop/desktop_native/Cargo.lock
generated
4
apps/desktop/desktop_native/Cargo.lock
generated
@@ -84,9 +84,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.5"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
|
||||
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
|
||||
@@ -16,7 +16,7 @@ manual_test = []
|
||||
aes = "=0.8.4"
|
||||
anyhow = "=1.0.80"
|
||||
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"] }
|
||||
napi = { version = "=2.13.3", features = ["async"] }
|
||||
napi-derive = "=2.13.0"
|
||||
|
||||
@@ -412,6 +412,23 @@
|
||||
"enableBrowserIntegrationFingerprintDesc" | i18n
|
||||
}}</small>
|
||||
</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="checkbox">
|
||||
<label for="enableDuckDuckGoBrowserIntegration">
|
||||
|
||||
@@ -24,6 +24,7 @@ import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { SetPinComponent } from "../../auth/components/set-pin.component";
|
||||
import { flagEnabled } from "../../platform/flags";
|
||||
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-settings",
|
||||
@@ -96,6 +97,7 @@ export class SettingsComponent implements OnInit {
|
||||
value: false,
|
||||
disabled: true,
|
||||
}),
|
||||
enableHardwareAcceleration: true,
|
||||
enableDuckDuckGoBrowserIntegration: false,
|
||||
theme: [null as ThemeType | null],
|
||||
locale: [null as string | null],
|
||||
@@ -119,6 +121,7 @@ export class SettingsComponent implements OnInit {
|
||||
private dialogService: DialogService,
|
||||
private userVerificationService: UserVerificationServiceAbstraction,
|
||||
private biometricStateService: BiometricStateService,
|
||||
private desktopSettingsService: DesktopSettingsService,
|
||||
) {
|
||||
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
|
||||
|
||||
@@ -259,6 +262,9 @@ export class SettingsComponent implements OnInit {
|
||||
enableBrowserIntegration: await this.stateService.getEnableBrowserIntegration(),
|
||||
enableBrowserIntegrationFingerprint:
|
||||
await this.stateService.getEnableBrowserIntegrationFingerprint(),
|
||||
enableHardwareAcceleration: await firstValueFrom(
|
||||
this.desktopSettingsService.hardwareAcceleration$,
|
||||
),
|
||||
enableDuckDuckGoBrowserIntegration:
|
||||
await this.stateService.getEnableDuckDuckGoBrowserIntegration(),
|
||||
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() {
|
||||
await this.stateService.setApproveLoginRequests(this.form.value.approveLoginRequests);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { NgModule } from "@angular/core";
|
||||
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
|
||||
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
|
||||
import { DialogModule } from "@bitwarden/components";
|
||||
import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui";
|
||||
|
||||
import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component";
|
||||
import { DeleteAccountComponent } from "../auth/delete-account.component";
|
||||
@@ -61,6 +62,7 @@ import { SendComponent } from "./tools/send/send.component";
|
||||
DialogModule,
|
||||
DeleteAccountComponent,
|
||||
UserVerificationComponent,
|
||||
ExportScopeCalloutComponent,
|
||||
],
|
||||
declarations: [
|
||||
AccessibilityCookieComponent,
|
||||
|
||||
@@ -54,6 +54,7 @@ import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { LoginGuard } from "../../auth/guards/login.guard";
|
||||
import { Account } from "../../models/account";
|
||||
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
|
||||
import { ElectronCryptoService } from "../../platform/services/electron-crypto.service";
|
||||
import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service";
|
||||
import {
|
||||
@@ -212,6 +213,11 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
|
||||
BiometricStateService,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: DesktopSettingsService,
|
||||
useClass: DesktopSettingsService,
|
||||
deps: [StateProvider],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class ServicesModule {}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
>
|
||||
{{ "personalVaultExportPolicyInEffect" | i18n }}
|
||||
</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">
|
||||
<h1 class="box-header" id="exportTitle">
|
||||
{{ "exportVault" | i18n }}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component, NgZone, OnDestroy } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component";
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
@@ -44,6 +45,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
|
||||
stateService: StateService,
|
||||
organizationApiService: OrganizationApiServiceAbstraction,
|
||||
organizationUserService: OrganizationUserService,
|
||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
dialogService: DialogService,
|
||||
) {
|
||||
@@ -62,6 +64,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
|
||||
stateService,
|
||||
organizationApiService,
|
||||
organizationUserService,
|
||||
userDecryptionOptionsService,
|
||||
ssoLoginService,
|
||||
dialogService,
|
||||
);
|
||||
|
||||
@@ -2,7 +2,10 @@ import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
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 { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
@@ -34,6 +37,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||
environmentService: EnvironmentService,
|
||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
logService: LogService,
|
||||
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
configService: ConfigServiceAbstraction,
|
||||
) {
|
||||
super(
|
||||
@@ -49,6 +53,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||
environmentService,
|
||||
passwordGenerationService,
|
||||
logService,
|
||||
userDecryptionOptionsService,
|
||||
configService,
|
||||
);
|
||||
super.onSuccessfulLogin = async () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Router } from "@angular/router";
|
||||
|
||||
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component";
|
||||
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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@@ -16,7 +17,8 @@ export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
) {
|
||||
super(twoFactorService, router, i18nService, platformUtilsService, window);
|
||||
super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
|
||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||
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 { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
@@ -53,6 +56,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
twoFactorService: TwoFactorService,
|
||||
appIdService: AppIdService,
|
||||
loginService: LoginService,
|
||||
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
configService: ConfigServiceAbstraction,
|
||||
@Inject(WINDOW) protected win: Window,
|
||||
@@ -71,6 +75,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
twoFactorService,
|
||||
appIdService,
|
||||
loginService,
|
||||
userDecryptionOptionsService,
|
||||
ssoLoginService,
|
||||
configService,
|
||||
);
|
||||
|
||||
@@ -1644,6 +1644,12 @@
|
||||
"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."
|
||||
},
|
||||
"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": {
|
||||
"message": "Approve"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as path from "path";
|
||||
|
||||
import { app } from "electron";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.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 { DesktopCredentialStorageListener } from "./platform/main/desktop-credential-storage-listener";
|
||||
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 { ELECTRON_SUPPORTS_SECURE_STORAGE } from "./platform/services/electron-platform-utils.service";
|
||||
import { ElectronStateService } from "./platform/services/electron-state.service";
|
||||
@@ -56,6 +58,7 @@ export class Main {
|
||||
mainCryptoFunctionService: MainCryptoFunctionService;
|
||||
desktopCredentialStorageListener: DesktopCredentialStorageListener;
|
||||
migrationRunner: MigrationRunner;
|
||||
desktopSettingsService: DesktopSettingsService;
|
||||
tokenService: TokenServiceAbstraction;
|
||||
|
||||
windowMain: WindowMain;
|
||||
@@ -189,6 +192,7 @@ export class Main {
|
||||
this.messagingMain = new MessagingMain(this, this.stateService);
|
||||
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain);
|
||||
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService);
|
||||
this.desktopSettingsService = new DesktopSettingsService(stateProvider);
|
||||
|
||||
this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => {
|
||||
this.messagingMain.onMessage(message);
|
||||
@@ -237,6 +241,7 @@ export class Main {
|
||||
// Run migrations first, then other things
|
||||
this.migrationRunner.run().then(
|
||||
async () => {
|
||||
await this.toggleHardwareAcceleration();
|
||||
await this.windowMain.init();
|
||||
await this.i18nService.init();
|
||||
this.messagingMain.init();
|
||||
@@ -307,4 +312,15 @@ export class Main {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
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 { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -17,8 +18,16 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
i18nService: I18nService,
|
||||
collectionService: CollectionService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
organizationService: OrganizationService,
|
||||
logService: LogService,
|
||||
) {
|
||||
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
|
||||
super(
|
||||
collectionService,
|
||||
platformUtilsService,
|
||||
i18nService,
|
||||
cipherService,
|
||||
organizationService,
|
||||
logService,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@bitwarden/vault-export-core": [
|
||||
"../../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/ui": ["../../libs/importer/src/components"],
|
||||
"@bitwarden/node/*": ["../../libs/node/src/*"],
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.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 {
|
||||
OrganizationUserStatusType,
|
||||
OrganizationUserType,
|
||||
@@ -17,7 +19,6 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
@@ -109,8 +110,8 @@ export abstract class BasePeopleComponent<
|
||||
private logService: LogService,
|
||||
private searchPipe: SearchPipe,
|
||||
protected userNamePipe: UserNamePipe,
|
||||
protected stateService: StateService,
|
||||
protected dialogService: DialogService,
|
||||
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
||||
) {}
|
||||
|
||||
abstract edit(user: UserType): void;
|
||||
@@ -351,7 +352,9 @@ export abstract class BasePeopleComponent<
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId);
|
||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||
|
||||
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
|
||||
const autoConfirm = await firstValueFrom(
|
||||
this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$,
|
||||
);
|
||||
if (autoConfirm == null || !autoConfirm) {
|
||||
const [modal] = await this.modalService.openViewRef(
|
||||
UserConfirmComponent,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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 { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-user-confirm",
|
||||
@@ -22,7 +22,7 @@ export class UserConfirmComponent implements OnInit {
|
||||
constructor(
|
||||
private cryptoService: CryptoService,
|
||||
private logService: LogService,
|
||||
private stateService: StateService,
|
||||
private organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -45,7 +45,7 @@ export class UserConfirmComponent implements OnInit {
|
||||
}
|
||||
|
||||
if (this.dontAskAgain) {
|
||||
await this.stateService.setAutoConfirmFingerprints(true);
|
||||
await this.organizationManagementPreferencesService.autoConfirmFingerPrints.set(true);
|
||||
}
|
||||
|
||||
this.onConfirmedUser.emit();
|
||||
|
||||
@@ -21,6 +21,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||
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 { 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 { OrganizationUserConfirmRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
|
||||
import {
|
||||
@@ -43,7 +44,6 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
@@ -117,7 +117,6 @@ export class PeopleComponent
|
||||
searchPipe: SearchPipe,
|
||||
userNamePipe: UserNamePipe,
|
||||
private syncService: SyncService,
|
||||
stateService: StateService,
|
||||
private organizationService: OrganizationService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
@@ -125,6 +124,7 @@ export class PeopleComponent
|
||||
private router: Router,
|
||||
private groupService: GroupService,
|
||||
private collectionService: CollectionService,
|
||||
organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
||||
) {
|
||||
super(
|
||||
apiService,
|
||||
@@ -137,8 +137,8 @@ export class PeopleComponent
|
||||
logService,
|
||||
searchPipe,
|
||||
userNamePipe,
|
||||
stateService,
|
||||
dialogService,
|
||||
organizationManagementPreferencesService,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui";
|
||||
|
||||
import { LooseComponentsModule, SharedModule } from "../../../../shared";
|
||||
|
||||
import { OrganizationVaultExportRoutingModule } from "./org-vault-export-routing.module";
|
||||
import { OrganizationVaultExportComponent } from "./org-vault-export.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, LooseComponentsModule, OrganizationVaultExportRoutingModule],
|
||||
imports: [
|
||||
SharedModule,
|
||||
LooseComponentsModule,
|
||||
OrganizationVaultExportRoutingModule,
|
||||
ExportScopeCalloutComponent,
|
||||
],
|
||||
declarations: [OrganizationVaultExportComponent],
|
||||
})
|
||||
export class OrganizationVaultExportModule {}
|
||||
|
||||
@@ -3,9 +3,9 @@ import { Component, OnInit, Inject } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
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 { 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 { DialogService } from "@bitwarden/components";
|
||||
|
||||
@@ -36,7 +36,7 @@ export class EmergencyAccessConfirmComponent implements OnInit {
|
||||
private formBuilder: FormBuilder,
|
||||
private apiService: ApiService,
|
||||
private cryptoService: CryptoService,
|
||||
private stateService: StateService,
|
||||
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
||||
private logService: LogService,
|
||||
private dialogRef: DialogRef<EmergencyAccessConfirmDialogResult>,
|
||||
) {}
|
||||
@@ -63,7 +63,7 @@ export class EmergencyAccessConfirmComponent implements OnInit {
|
||||
}
|
||||
|
||||
if (this.confirmForm.get("dontAskAgain").value) {
|
||||
await this.stateService.setAutoConfirmFingerprints(true);
|
||||
await this.organizationManagementPreferencesService.autoConfirmFingerPrints.set(true);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { lastValueFrom, Observable, firstValueFrom } from "rxjs";
|
||||
|
||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -64,6 +65,7 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
private organizationService: OrganizationService,
|
||||
protected dialogService: DialogService,
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
||||
) {
|
||||
this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$;
|
||||
}
|
||||
@@ -136,7 +138,9 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
|
||||
const autoConfirm = await firstValueFrom(
|
||||
this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$,
|
||||
);
|
||||
if (autoConfirm == null || !autoConfirm) {
|
||||
const dialogRef = EmergencyAccessConfirmComponent.open(this.dialogService, {
|
||||
data: {
|
||||
|
||||
@@ -3,7 +3,10 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
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 { 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";
|
||||
@@ -41,6 +44,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||
logService: LogService,
|
||||
private orgDomainApiService: OrgDomainApiServiceAbstraction,
|
||||
private validationService: ValidationService,
|
||||
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
configService: ConfigServiceAbstraction,
|
||||
) {
|
||||
super(
|
||||
@@ -56,6 +60,7 @@ export class SsoComponent extends BaseSsoComponent {
|
||||
environmentService,
|
||||
passwordGenerationService,
|
||||
logService,
|
||||
userDecryptionOptionsService,
|
||||
configService,
|
||||
);
|
||||
this.redirectUri = window.location.origin + "/sso-connector.html";
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Router } from "@angular/router";
|
||||
|
||||
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component";
|
||||
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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@@ -16,7 +17,8 @@ export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
) {
|
||||
super(twoFactorService, router, i18nService, platformUtilsService, window);
|
||||
super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
|
||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||
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 { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
@@ -44,6 +47,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
|
||||
twoFactorService: TwoFactorService,
|
||||
appIdService: AppIdService,
|
||||
loginService: LoginService,
|
||||
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
configService: ConfigServiceAbstraction,
|
||||
@Inject(WINDOW) protected win: Window,
|
||||
@@ -62,6 +66,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
|
||||
twoFactorService,
|
||||
appIdService,
|
||||
loginService,
|
||||
userDecryptionOptionsService,
|
||||
ssoLoginService,
|
||||
configService,
|
||||
);
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
<bit-callout type="danger" title="{{ 'vaultExportDisabled' | i18n }}" *ngIf="disabledByPolicy">
|
||||
{{ "personalVaultExportPolicyInEffect" | i18n }}
|
||||
</bit-callout>
|
||||
<app-export-scope-callout
|
||||
<tools-export-scope-callout
|
||||
[organizationId]="organizationId"
|
||||
*ngIf="!disabledByPolicy"
|
||||
></app-export-scope-callout>
|
||||
></tools-export-scope-callout>
|
||||
|
||||
<ng-container *ngIf="organizations$ | async as organizations">
|
||||
<bit-form-field *ngIf="organizations.length > 0">
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui";
|
||||
|
||||
import { LooseComponentsModule, SharedModule } from "../../shared";
|
||||
|
||||
import { ExportRoutingModule } from "./export-routing.module";
|
||||
import { ExportComponent } from "./export.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, LooseComponentsModule, ExportRoutingModule],
|
||||
imports: [SharedModule, LooseComponentsModule, ExportRoutingModule, ExportScopeCalloutComponent],
|
||||
declarations: [ExportComponent],
|
||||
})
|
||||
export class ExportModule {}
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
[(ngModel)]="$any(c).checked"
|
||||
name="Collection[{{ i }}].Checked"
|
||||
appStopProp
|
||||
[disabled]="!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, OnDestroy } from "@angular/core";
|
||||
|
||||
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 { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -18,9 +19,17 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
cipherService: CipherService,
|
||||
organizationSerivce: OrganizationService,
|
||||
logService: LogService,
|
||||
) {
|
||||
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
|
||||
super(
|
||||
collectionService,
|
||||
platformUtilsService,
|
||||
i18nService,
|
||||
cipherService,
|
||||
organizationSerivce,
|
||||
logService,
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -28,6 +37,9 @@ export class CollectionsComponent extends BaseCollectionsComponent implements On
|
||||
}
|
||||
|
||||
check(c: CollectionView, select?: boolean) {
|
||||
if (!c.canEditItems(this.organization, this.flexibleCollectionsV1Enabled)) {
|
||||
return;
|
||||
}
|
||||
(c as any).checked = select == null ? !(c as any).checked : select;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
||||
import { combineLatest, map, Observable, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
@@ -12,7 +13,6 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
@@ -44,8 +44,8 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
|
||||
private logService: LogService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
private dialogService: DialogService,
|
||||
private stateService: StateService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -56,7 +56,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
|
||||
combineLatest([
|
||||
this.organization$,
|
||||
resetPasswordPolicies$,
|
||||
this.stateService.getAccountDecryptionOptions(),
|
||||
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||
])
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(([organization, resetPasswordPolicies, decryptionOptions]) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -25,15 +26,27 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
cipherService: CipherService,
|
||||
organizationService: OrganizationService,
|
||||
private apiService: ApiService,
|
||||
logService: LogService,
|
||||
) {
|
||||
super(collectionService, platformUtilsService, i18nService, cipherService, logService);
|
||||
super(
|
||||
collectionService,
|
||||
platformUtilsService,
|
||||
i18nService,
|
||||
cipherService,
|
||||
organizationService,
|
||||
logService,
|
||||
);
|
||||
this.allowSelectNone = true;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
const response = await this.apiService.getCipherAdmin(this.cipherId);
|
||||
@@ -55,7 +68,10 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
}
|
||||
|
||||
protected saveCollections() {
|
||||
if (this.organization.canEditAnyCollection) {
|
||||
if (
|
||||
this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled) ||
|
||||
this.collectionIds.length === 0
|
||||
) {
|
||||
const request = new CipherCollectionsRequest(this.cipherDomain.collectionIds);
|
||||
return this.apiService.putCipherCollectionsAdmin(this.cipherId, request);
|
||||
} else {
|
||||
|
||||
@@ -137,6 +137,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
protected showCollectionAccessRestricted: boolean;
|
||||
protected currentSearchText$: Observable<string>;
|
||||
protected editableCollections$: Observable<CollectionView[]>;
|
||||
protected allCollectionsWithoutUnassigned$: Observable<CollectionAdminView[]>;
|
||||
protected showBulkEditCollectionAccess$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.BulkCollectionAccess,
|
||||
false,
|
||||
@@ -253,7 +254,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
|
||||
|
||||
const allCollectionsWithoutUnassigned$ = combineLatest([
|
||||
this.allCollectionsWithoutUnassigned$ = combineLatest([
|
||||
organizationId$.pipe(switchMap((orgId) => this.collectionAdminService.getAll(orgId))),
|
||||
defer(() => this.collectionService.getAllDecrypted()),
|
||||
]).pipe(
|
||||
@@ -276,7 +277,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
this.editableCollections$ = allCollectionsWithoutUnassigned$.pipe(
|
||||
this.editableCollections$ = this.allCollectionsWithoutUnassigned$.pipe(
|
||||
map((collections) => {
|
||||
// Users that can edit all ciphers can implicitly edit all collections
|
||||
if (this.organization.canEditAllCiphers(this.flexibleCollectionsV1Enabled)) {
|
||||
@@ -287,7 +288,10 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
const allCollections$ = combineLatest([organizationId$, allCollectionsWithoutUnassigned$]).pipe(
|
||||
const allCollections$ = combineLatest([
|
||||
organizationId$,
|
||||
this.allCollectionsWithoutUnassigned$,
|
||||
]).pipe(
|
||||
map(([organizationId, allCollections]) => {
|
||||
const noneCollection = new CollectionAdminView();
|
||||
noneCollection.name = this.i18nService.t("unassigned");
|
||||
@@ -680,16 +684,35 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
if (this.flexibleCollectionsV1Enabled) {
|
||||
// V1 limits admins to only adding items to collections they have access to.
|
||||
collections = await firstValueFrom(this.editableCollections$);
|
||||
} else {
|
||||
collections = (await firstValueFrom(this.vaultFilterService.filteredCollections$)).filter(
|
||||
(c) => !c.readOnly && c.id != Unassigned,
|
||||
collections = await firstValueFrom(
|
||||
this.allCollectionsWithoutUnassigned$.pipe(
|
||||
map((c) => {
|
||||
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(
|
||||
CollectionsComponent,
|
||||
this.collectionsModalRef,
|
||||
(comp) => {
|
||||
comp.flexibleCollectionsV1Enabled = this.flexibleCollectionsV1Enabled;
|
||||
comp.collectionIds = cipher.collectionIds;
|
||||
comp.collections = collections;
|
||||
comp.organization = this.organization;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@bitwarden/vault-export-core": [
|
||||
"../../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/ui": ["../../libs/importer/src/components"],
|
||||
"@bitwarden/platform": ["../../libs/platform/src"],
|
||||
|
||||
@@ -7,6 +7,7 @@ import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.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 { ProviderUserStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums";
|
||||
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 { LogService } from "@bitwarden/common/platform/abstractions/log.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 { DialogService } from "@bitwarden/components";
|
||||
import { BasePeopleComponent } from "@bitwarden/web-vault/app/admin-console/common/base.people.component";
|
||||
@@ -67,9 +67,9 @@ export class PeopleComponent
|
||||
logService: LogService,
|
||||
searchPipe: SearchPipe,
|
||||
userNamePipe: UserNamePipe,
|
||||
stateService: StateService,
|
||||
private providerService: ProviderService,
|
||||
dialogService: DialogService,
|
||||
organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
||||
) {
|
||||
super(
|
||||
apiService,
|
||||
@@ -82,8 +82,8 @@ export class PeopleComponent
|
||||
logService,
|
||||
searchPipe,
|
||||
userNamePipe,
|
||||
stateService,
|
||||
dialogService,
|
||||
organizationManagementPreferencesService,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: "sm",
|
||||
canActivate: [deepLinkGuard()],
|
||||
loadChildren: async () =>
|
||||
(await import("./secrets-manager/secrets-manager.module")).SecretsManagerModule,
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"@bitwarden/vault-export-core": [
|
||||
"../../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/vault": ["../../libs/vault/src"],
|
||||
"@bitwarden/web-vault/*": ["../../apps/web/src/*"]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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 { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -19,6 +21,8 @@ export class CollectionsComponent implements OnInit {
|
||||
cipher: CipherView;
|
||||
collectionIds: string[];
|
||||
collections: CollectionView[] = [];
|
||||
organization: Organization;
|
||||
flexibleCollectionsV1Enabled: boolean;
|
||||
|
||||
protected cipherDomain: Cipher;
|
||||
|
||||
@@ -27,6 +31,7 @@ export class CollectionsComponent implements OnInit {
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected i18nService: I18nService,
|
||||
protected cipherService: CipherService,
|
||||
protected organizationService: OrganizationService,
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
if (this.organization == null) {
|
||||
this.organization = await this.organizationService.get(this.cipher.organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
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);
|
||||
if (!this.allowSelectNone && selectedCollectionIds.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
|
||||
@@ -14,6 +14,10 @@ import {
|
||||
throwError,
|
||||
} from "rxjs";
|
||||
|
||||
import {
|
||||
UserDecryptionOptions,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
@@ -30,7 +34,6 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
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 { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
||||
|
||||
enum State {
|
||||
NewUser,
|
||||
@@ -88,6 +91,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
protected validationService: ValidationService,
|
||||
protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
protected passwordResetEnrollmentService: PasswordResetEnrollmentServiceAbstraction,
|
||||
protected ssoLoginService: SsoLoginServiceAbstraction,
|
||||
) {}
|
||||
@@ -101,14 +105,15 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
await this.setRememberDeviceDefaultValue();
|
||||
|
||||
try {
|
||||
const accountDecryptionOptions: AccountDecryptionOptions =
|
||||
await this.stateService.getAccountDecryptionOptions();
|
||||
const userDecryptionOptions = await firstValueFrom(
|
||||
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..
|
||||
// can we check if they have a user key or master key in crypto service? Would that be sufficient?
|
||||
if (
|
||||
!accountDecryptionOptions?.trustedDeviceOption?.hasAdminApproval &&
|
||||
!accountDecryptionOptions?.hasMasterPassword
|
||||
!userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval &&
|
||||
!userDecryptionOptions?.hasMasterPassword
|
||||
) {
|
||||
// We are dealing with a new account if:
|
||||
// - 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
|
||||
this.loadNewUserData();
|
||||
} else {
|
||||
this.loadUntrustedDeviceData(accountDecryptionOptions);
|
||||
this.loadUntrustedDeviceData(userDecryptionOptions);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
loadUntrustedDeviceData(accountDecryptionOptions: AccountDecryptionOptions) {
|
||||
loadUntrustedDeviceData(userDecryptionOptions: UserDecryptionOptions) {
|
||||
this.loading = true;
|
||||
|
||||
const email$ = from(this.stateService.getEmail()).pipe(
|
||||
@@ -215,13 +220,12 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||
)
|
||||
.subscribe((email) => {
|
||||
const showApproveFromOtherDeviceBtn =
|
||||
accountDecryptionOptions?.trustedDeviceOption?.hasLoginApprovingDevice || false;
|
||||
userDecryptionOptions?.trustedDeviceOption?.hasLoginApprovingDevice || false;
|
||||
|
||||
const showReqAdminApprovalBtn =
|
||||
!!accountDecryptionOptions?.trustedDeviceOption?.hasAdminApproval || false;
|
||||
!!userDecryptionOptions?.trustedDeviceOption?.hasAdminApproval || false;
|
||||
|
||||
const showApproveWithMasterPasswordBtn =
|
||||
accountDecryptionOptions?.hasMasterPassword || false;
|
||||
const showApproveWithMasterPasswordBtn = userDecryptionOptions?.hasMasterPassword || false;
|
||||
|
||||
const userEmail = email;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Directive } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { of } from "rxjs";
|
||||
import { firstValueFrom, of } from "rxjs";
|
||||
import { filter, first, switchMap, tap } from "rxjs/operators";
|
||||
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
DEFAULT_KDF_CONFIG,
|
||||
} from "@bitwarden/common/platform/enums";
|
||||
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 { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
@@ -64,6 +64,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||
stateService: StateService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
private userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
private ssoLoginService: SsoLoginServiceAbstraction,
|
||||
dialogService: DialogService,
|
||||
) {
|
||||
@@ -228,11 +229,11 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||
await this.stateService.setForceSetPasswordReason(ForceSetPasswordReason.None);
|
||||
|
||||
// User now has a password so update account decryption options in state
|
||||
const acctDecryptionOpts: AccountDecryptionOptions =
|
||||
await this.stateService.getAccountDecryptionOptions();
|
||||
|
||||
acctDecryptionOpts.hasMasterPassword = true;
|
||||
await this.stateService.setAccountDecryptionOptions(acctDecryptionOpts);
|
||||
const userDecryptionOpts = await firstValueFrom(
|
||||
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||
);
|
||||
userDecryptionOpts.hasMasterPassword = true;
|
||||
await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts);
|
||||
|
||||
await this.stateService.setKdfType(this.kdf);
|
||||
await this.stateService.setKdfConfig(this.kdfConfig);
|
||||
|
||||
@@ -2,16 +2,20 @@ import { Component } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
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 { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
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 { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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 { SsoComponent } from "./sso.component";
|
||||
@@ -62,6 +65,7 @@ describe("SsoComponent", () => {
|
||||
let mockEnvironmentService: MockProxy<EnvironmentService>;
|
||||
let mockPasswordGenerationService: MockProxy<PasswordGenerationServiceAbstraction>;
|
||||
let mockLogService: MockProxy<LogService>;
|
||||
let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
|
||||
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
||||
|
||||
// Mock authService.logIn params
|
||||
@@ -77,17 +81,19 @@ describe("SsoComponent", () => {
|
||||
let mockOnSuccessfulLoginForceResetNavigate: jest.Mock;
|
||||
let mockOnSuccessfulLoginTdeNavigate: jest.Mock;
|
||||
|
||||
let mockAcctDecryptionOpts: {
|
||||
noMasterPassword: AccountDecryptionOptions;
|
||||
withMasterPassword: AccountDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDevice: AccountDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
||||
withMasterPasswordAndKeyConnector: AccountDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDevice: AccountDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
||||
noMasterPasswordWithKeyConnector: AccountDecryptionOptions;
|
||||
let mockUserDecryptionOpts: {
|
||||
noMasterPassword: UserDecryptionOptions;
|
||||
withMasterPassword: UserDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDevice: UserDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||
withMasterPasswordAndKeyConnector: UserDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDevice: UserDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||
noMasterPasswordWithKeyConnector: UserDecryptionOptions;
|
||||
};
|
||||
|
||||
let selectedUserDecryptionOptions: BehaviorSubject<UserDecryptionOptions>;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock Services
|
||||
mockLoginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
||||
@@ -109,6 +115,7 @@ describe("SsoComponent", () => {
|
||||
mockEnvironmentService = mock<EnvironmentService>();
|
||||
mockPasswordGenerationService = mock<PasswordGenerationServiceAbstraction>();
|
||||
mockLogService = mock<LogService>();
|
||||
mockUserDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>();
|
||||
mockConfigService = mock<ConfigServiceAbstraction>();
|
||||
|
||||
// Mock loginStrategyService.logIn params
|
||||
@@ -124,49 +131,52 @@ describe("SsoComponent", () => {
|
||||
mockOnSuccessfulLoginForceResetNavigate = jest.fn();
|
||||
mockOnSuccessfulLoginTdeNavigate = jest.fn();
|
||||
|
||||
mockAcctDecryptionOpts = {
|
||||
noMasterPassword: new AccountDecryptionOptions({
|
||||
mockUserDecryptionOpts = {
|
||||
noMasterPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPassword: new AccountDecryptionOptions({
|
||||
withMasterPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndTrustedDevice: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndKeyConnector: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndKeyConnector: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||
}),
|
||||
noMasterPasswordWithTrustedDevice: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
noMasterPasswordWithKeyConnector: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithKeyConnector: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||
}),
|
||||
};
|
||||
|
||||
selectedUserDecryptionOptions = new BehaviorSubject<UserDecryptionOptions>(null);
|
||||
mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions;
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TestSsoComponent],
|
||||
providers: [
|
||||
@@ -183,6 +193,10 @@ describe("SsoComponent", () => {
|
||||
{ provide: EnvironmentService, useValue: mockEnvironmentService },
|
||||
{ provide: PasswordGenerationServiceAbstraction, useValue: mockPasswordGenerationService },
|
||||
|
||||
{
|
||||
provide: UserDecryptionOptionsServiceAbstraction,
|
||||
useValue: mockUserDecryptionOptionsService,
|
||||
},
|
||||
{ provide: LogService, useValue: mockLogService },
|
||||
{ provide: ConfigServiceAbstraction, useValue: mockConfigService },
|
||||
],
|
||||
@@ -230,9 +244,7 @@ describe("SsoComponent", () => {
|
||||
authResult.twoFactorProviders = new Map([[TwoFactorProviderType.Authenticator, {}]]);
|
||||
|
||||
// use standard user with MP because this test is not concerned with password reset.
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||
|
||||
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", () => {
|
||||
let authResult;
|
||||
beforeEach(() => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
||||
);
|
||||
|
||||
authResult = new AuthResult();
|
||||
@@ -377,8 +389,8 @@ describe("SsoComponent", () => {
|
||||
const reasonString = ForceSetPasswordReason[forceResetPasswordReason];
|
||||
let authResult;
|
||||
beforeEach(() => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
);
|
||||
|
||||
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", () => {
|
||||
let authResult;
|
||||
beforeEach(() => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
);
|
||||
|
||||
authResult = new AuthResult();
|
||||
@@ -440,9 +452,7 @@ describe("SsoComponent", () => {
|
||||
describe("Given user needs to set a master password", () => {
|
||||
beforeEach(() => {
|
||||
// Only need to test the case where the user has no master password to test the primary change mp flow here
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.noMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPassword);
|
||||
});
|
||||
|
||||
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 () => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.noMasterPasswordWithKeyConnector,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPasswordWithKeyConnector);
|
||||
|
||||
await _component.logIn(code, codeVerifier, orgIdFromState);
|
||||
expect(mockLoginStrategyService.logIn).toHaveBeenCalledTimes(1);
|
||||
@@ -475,9 +483,7 @@ describe("SsoComponent", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// use standard user with MP because this test is not concerned with password reset.
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||
|
||||
const authResult = new AuthResult();
|
||||
authResult.forcePasswordReset = forceResetPasswordReason;
|
||||
@@ -494,9 +500,7 @@ describe("SsoComponent", () => {
|
||||
const authResult = new AuthResult();
|
||||
authResult.twoFactorProviders = null;
|
||||
// use standard user with MP because this test is not concerned with password reset.
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||
authResult.forcePasswordReset = ForceSetPasswordReason.None;
|
||||
mockLoginStrategyService.logIn.mockResolvedValue(authResult);
|
||||
});
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { Directive } from "@angular/core";
|
||||
import { ActivatedRoute, NavigationExtras, Router } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
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 { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
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 { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
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 { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
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";
|
||||
|
||||
@Directive()
|
||||
@@ -59,6 +64,7 @@ export class SsoComponent {
|
||||
protected environmentService: EnvironmentService,
|
||||
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
protected logService: LogService,
|
||||
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
protected configService: ConfigServiceAbstraction,
|
||||
) {}
|
||||
|
||||
@@ -194,9 +200,6 @@ export class SsoComponent {
|
||||
this.formPromise = this.loginStrategyService.logIn(credentials);
|
||||
const authResult = await this.formPromise;
|
||||
|
||||
const acctDecryptionOpts: AccountDecryptionOptions =
|
||||
await this.stateService.getAccountDecryptionOptions();
|
||||
|
||||
if (authResult.requiresTwoFactor) {
|
||||
return await this.handleTwoFactorRequired(orgSsoIdentifier);
|
||||
}
|
||||
@@ -217,15 +220,20 @@ export class SsoComponent {
|
||||
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(
|
||||
acctDecryptionOpts.trustedDeviceOption,
|
||||
userDecryptionOpts.trustedDeviceOption,
|
||||
);
|
||||
|
||||
if (tdeEnabled) {
|
||||
return await this.handleTrustedDeviceEncryptionEnabled(
|
||||
authResult,
|
||||
orgSsoIdentifier,
|
||||
acctDecryptionOpts,
|
||||
userDecryptionOpts,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -233,8 +241,8 @@ export class SsoComponent {
|
||||
// have one and they aren't using key connector.
|
||||
// Note: TDE & Key connector are mutually exclusive org config options.
|
||||
const requireSetPassword =
|
||||
!acctDecryptionOpts.hasMasterPassword &&
|
||||
acctDecryptionOpts.keyConnectorOption === undefined;
|
||||
!userDecryptionOpts.hasMasterPassword &&
|
||||
userDecryptionOpts.keyConnectorOption === undefined;
|
||||
|
||||
if (requireSetPassword || authResult.resetMasterPassword) {
|
||||
// Change implies going no password -> password in this case
|
||||
@@ -270,12 +278,12 @@ export class SsoComponent {
|
||||
private async handleTrustedDeviceEncryptionEnabled(
|
||||
authResult: AuthResult,
|
||||
orgIdentifier: string,
|
||||
acctDecryptionOpts: AccountDecryptionOptions,
|
||||
userDecryptionOpts: UserDecryptionOptions,
|
||||
): Promise<void> {
|
||||
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||
if (
|
||||
!acctDecryptionOpts.hasMasterPassword &&
|
||||
acctDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
||||
!userDecryptionOpts.hasMasterPassword &&
|
||||
userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
||||
) {
|
||||
// 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
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Router } from "@angular/router";
|
||||
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
@@ -19,6 +20,7 @@ export class TwoFactorOptionsComponent implements OnInit {
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected win: Window,
|
||||
protected environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -30,7 +32,8 @@ export class TwoFactorOptionsComponent implements OnInit {
|
||||
}
|
||||
|
||||
recover() {
|
||||
this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/recover-2fa");
|
||||
const webVault = this.environmentService.getWebVaultUrl();
|
||||
this.platformUtilsService.launchUri(webVault + "/#/recover-2fa");
|
||||
this.onRecoverSelected.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ActivatedRoute, Router, convertToParamMap } from "@angular/router";
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { ActivatedRoute, convertToParamMap, Router } from "@angular/router";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
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 { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
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 { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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";
|
||||
|
||||
@@ -56,20 +60,23 @@ describe("TwoFactorComponent", () => {
|
||||
let mockTwoFactorService: MockProxy<TwoFactorService>;
|
||||
let mockAppIdService: MockProxy<AppIdService>;
|
||||
let mockLoginService: MockProxy<LoginService>;
|
||||
let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
|
||||
let mockSsoLoginService: MockProxy<SsoLoginServiceAbstraction>;
|
||||
let mockConfigService: MockProxy<ConfigServiceAbstraction>;
|
||||
|
||||
let mockAcctDecryptionOpts: {
|
||||
noMasterPassword: AccountDecryptionOptions;
|
||||
withMasterPassword: AccountDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDevice: AccountDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
||||
withMasterPasswordAndKeyConnector: AccountDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDevice: AccountDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: AccountDecryptionOptions;
|
||||
noMasterPasswordWithKeyConnector: AccountDecryptionOptions;
|
||||
let mockUserDecryptionOpts: {
|
||||
noMasterPassword: UserDecryptionOptions;
|
||||
withMasterPassword: UserDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDevice: UserDecryptionOptions;
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||
withMasterPasswordAndKeyConnector: UserDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDevice: UserDecryptionOptions;
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
|
||||
noMasterPasswordWithKeyConnector: UserDecryptionOptions;
|
||||
};
|
||||
|
||||
let selectedUserDecryptionOptions: BehaviorSubject<UserDecryptionOptions>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLoginStrategyService = mock<LoginStrategyServiceAbstraction>();
|
||||
mockRouter = mock<Router>();
|
||||
@@ -83,52 +90,56 @@ describe("TwoFactorComponent", () => {
|
||||
mockTwoFactorService = mock<TwoFactorService>();
|
||||
mockAppIdService = mock<AppIdService>();
|
||||
mockLoginService = mock<LoginService>();
|
||||
mockUserDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>();
|
||||
mockSsoLoginService = mock<SsoLoginServiceAbstraction>();
|
||||
mockConfigService = mock<ConfigServiceAbstraction>();
|
||||
|
||||
mockAcctDecryptionOpts = {
|
||||
noMasterPassword: new AccountDecryptionOptions({
|
||||
mockUserDecryptionOpts = {
|
||||
noMasterPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPassword: new AccountDecryptionOptions({
|
||||
withMasterPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndTrustedDevice: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
withMasterPasswordAndKeyConnector: new AccountDecryptionOptions({
|
||||
withMasterPasswordAndKeyConnector: new UserDecryptionOptions({
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||
}),
|
||||
noMasterPasswordWithTrustedDevice: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
|
||||
keyConnectorOption: undefined,
|
||||
}),
|
||||
noMasterPasswordWithKeyConnector: new AccountDecryptionOptions({
|
||||
noMasterPasswordWithKeyConnector: new UserDecryptionOptions({
|
||||
hasMasterPassword: false,
|
||||
trustedDeviceOption: undefined,
|
||||
keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
|
||||
}),
|
||||
};
|
||||
|
||||
selectedUserDecryptionOptions = new BehaviorSubject<UserDecryptionOptions>(null);
|
||||
mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions;
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TestTwoFactorComponent],
|
||||
providers: [
|
||||
@@ -153,6 +164,10 @@ describe("TwoFactorComponent", () => {
|
||||
{ provide: TwoFactorService, useValue: mockTwoFactorService },
|
||||
{ provide: AppIdService, useValue: mockAppIdService },
|
||||
{ provide: LoginService, useValue: mockLoginService },
|
||||
{
|
||||
provide: UserDecryptionOptionsServiceAbstraction,
|
||||
useValue: mockUserDecryptionOptionsService,
|
||||
},
|
||||
{ provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService },
|
||||
{ provide: ConfigServiceAbstraction, useValue: mockConfigService },
|
||||
],
|
||||
@@ -213,9 +228,7 @@ describe("TwoFactorComponent", () => {
|
||||
component.remember = remember;
|
||||
component.captchaToken = captchaToken;
|
||||
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||
});
|
||||
|
||||
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", () => {
|
||||
beforeEach(() => {
|
||||
// Only need to test the case where the user has no master password to test the primary change mp flow here
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.noMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPassword);
|
||||
});
|
||||
|
||||
testChangePasswordOnSuccessfulLogin();
|
||||
});
|
||||
|
||||
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(
|
||||
mockAcctDecryptionOpts.noMasterPasswordWithKeyConnector,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.noMasterPasswordWithKeyConnector,
|
||||
);
|
||||
|
||||
await component.doSubmit();
|
||||
@@ -321,9 +332,7 @@ describe("TwoFactorComponent", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// use standard user with MP because this test is not concerned with password reset.
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPassword,
|
||||
);
|
||||
selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
|
||||
|
||||
const authResult = new AuthResult();
|
||||
authResult.forcePasswordReset = forceResetPasswordReason;
|
||||
@@ -385,8 +394,8 @@ describe("TwoFactorComponent", () => {
|
||||
|
||||
describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => {
|
||||
beforeEach(() => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
|
||||
);
|
||||
|
||||
const authResult = new AuthResult();
|
||||
@@ -420,8 +429,8 @@ describe("TwoFactorComponent", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// use standard user with MP because this test is not concerned with password reset.
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
);
|
||||
|
||||
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", () => {
|
||||
let authResult;
|
||||
beforeEach(() => {
|
||||
mockStateService.getAccountDecryptionOptions.mockResolvedValue(
|
||||
mockAcctDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
selectedUserDecryptionOptions.next(
|
||||
mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
|
||||
);
|
||||
|
||||
authResult = new AuthResult();
|
||||
|
||||
@@ -6,7 +6,12 @@ import { first } from "rxjs/operators";
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
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 { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
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 { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
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 { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
|
||||
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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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";
|
||||
|
||||
@@ -86,6 +89,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
protected twoFactorService: TwoFactorService,
|
||||
protected appIdService: AppIdService,
|
||||
protected loginService: LoginService,
|
||||
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
protected ssoLoginService: SsoLoginServiceAbstraction,
|
||||
protected configService: ConfigServiceAbstraction,
|
||||
) {
|
||||
@@ -290,22 +294,23 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
return await this.handleForcePasswordReset(this.orgIdentifier);
|
||||
}
|
||||
|
||||
const acctDecryptionOpts: AccountDecryptionOptions =
|
||||
await this.stateService.getAccountDecryptionOptions();
|
||||
const userDecryptionOpts = await firstValueFrom(
|
||||
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||
);
|
||||
|
||||
const tdeEnabled = await this.isTrustedDeviceEncEnabled(acctDecryptionOpts.trustedDeviceOption);
|
||||
const tdeEnabled = await this.isTrustedDeviceEncEnabled(userDecryptionOpts.trustedDeviceOption);
|
||||
|
||||
if (tdeEnabled) {
|
||||
return await this.handleTrustedDeviceEncryptionEnabled(
|
||||
authResult,
|
||||
this.orgIdentifier,
|
||||
acctDecryptionOpts,
|
||||
userDecryptionOpts,
|
||||
);
|
||||
}
|
||||
|
||||
// User must set password if they don't have one and they aren't using either TDE or key connector.
|
||||
const requireSetPassword =
|
||||
!acctDecryptionOpts.hasMasterPassword && acctDecryptionOpts.keyConnectorOption === undefined;
|
||||
!userDecryptionOpts.hasMasterPassword && userDecryptionOpts.keyConnectorOption === undefined;
|
||||
|
||||
if (requireSetPassword || authResult.resetMasterPassword) {
|
||||
// Change implies going no password -> password in this case
|
||||
@@ -326,12 +331,12 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
private async handleTrustedDeviceEncryptionEnabled(
|
||||
authResult: AuthResult,
|
||||
orgIdentifier: string,
|
||||
acctDecryptionOpts: AccountDecryptionOptions,
|
||||
userDecryptionOpts: UserDecryptionOptions,
|
||||
): Promise<void> {
|
||||
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||
if (
|
||||
!acctDecryptionOpts.hasMasterPassword &&
|
||||
acctDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
||||
!userDecryptionOpts.hasMasterPassword &&
|
||||
userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
|
||||
) {
|
||||
// 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
|
||||
|
||||
@@ -53,7 +53,7 @@ export function lockGuard(): CanActivateFn {
|
||||
|
||||
// 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
|
||||
// The MP check is necessary to prevent direct manual navigation from other locked state pages for users who don't have a MP
|
||||
|
||||
@@ -46,7 +46,7 @@ export function redirectGuard(overrides: Partial<RedirectRoutes> = {}): CanActiv
|
||||
|
||||
// If locked, TDE is enabled, and the user hasn't decrypted yet, then redirect to the
|
||||
// login decryption options component.
|
||||
const tdeEnabled = await deviceTrustCryptoService.supportsDeviceTrust();
|
||||
const tdeEnabled = await firstValueFrom(deviceTrustCryptoService.supportsDeviceTrust$);
|
||||
const everHadUserKey = await firstValueFrom(cryptoService.everHadUserKey$);
|
||||
if (authStatus === AuthenticationStatus.Locked && tdeEnabled && !everHadUserKey) {
|
||||
return router.createUrlTree([routes.notDecrypted], { queryParams: route.queryParams });
|
||||
|
||||
@@ -26,7 +26,7 @@ export function tdeDecryptionRequiredGuard(): CanActivateFn {
|
||||
const router = inject(Router);
|
||||
|
||||
const authStatus = await authService.getAuthStatus();
|
||||
const tdeEnabled = await deviceTrustCryptoService.supportsDeviceTrust();
|
||||
const tdeEnabled = await firstValueFrom(deviceTrustCryptoService.supportsDeviceTrust$);
|
||||
const everHadUserKey = await firstValueFrom(cryptoService.everHadUserKey$);
|
||||
if (authStatus !== AuthenticationStatus.Locked || !tdeEnabled || everHadUserKey) {
|
||||
return router.createUrlTree(["/"]);
|
||||
|
||||
@@ -29,7 +29,6 @@ import { UserTypePipe } from "./pipes/user-type.pipe";
|
||||
import { EllipsisPipe } from "./platform/pipes/ellipsis.pipe";
|
||||
import { FingerprintPipe } from "./platform/pipes/fingerprint.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 { IconComponent } from "./vault/components/icon.component";
|
||||
|
||||
@@ -54,7 +53,6 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
CopyTextDirective,
|
||||
CreditCardNumberPipe,
|
||||
EllipsisPipe,
|
||||
ExportScopeCalloutComponent,
|
||||
FallbackSrcDirective,
|
||||
I18nPipe,
|
||||
IconComponent,
|
||||
@@ -85,7 +83,6 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
CopyTextDirective,
|
||||
CreditCardNumberPipe,
|
||||
EllipsisPipe,
|
||||
ExportScopeCalloutComponent,
|
||||
FallbackSrcDirective,
|
||||
I18nPipe,
|
||||
IconComponent,
|
||||
|
||||
@@ -8,6 +8,9 @@ import {
|
||||
PinCryptoService,
|
||||
LoginStrategyServiceAbstraction,
|
||||
LoginStrategyService,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
UserDecryptionOptionsService,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
||||
@@ -27,6 +30,7 @@ import {
|
||||
OrgDomainInternalServiceAbstraction,
|
||||
OrgDomainServiceAbstraction,
|
||||
} 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 { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
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 { 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 { 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 { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.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 {
|
||||
LOCALES_DIRECTORY,
|
||||
LOCKED_CALLBACK,
|
||||
LOG_MAC_FAILURES,
|
||||
LOGOUT_CALLBACK,
|
||||
LOG_MAC_FAILURES,
|
||||
MEMORY_STORAGE,
|
||||
OBSERVABLE_DISK_STORAGE,
|
||||
OBSERVABLE_MEMORY_STORAGE,
|
||||
@@ -367,6 +372,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
PolicyServiceAbstraction,
|
||||
DeviceTrustCryptoServiceAbstraction,
|
||||
AuthRequestServiceAbstraction,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
GlobalStateProvider,
|
||||
BillingAccountProfileStateService,
|
||||
],
|
||||
@@ -475,6 +481,15 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
useClass: EnvironmentService,
|
||||
deps: [StateProvider, AccountServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
useClass: UserDecryptionOptionsService,
|
||||
deps: [StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: UserDecryptionOptionsServiceAbstraction,
|
||||
useExisting: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
}),
|
||||
safeProvider({
|
||||
provide: TotpServiceAbstraction,
|
||||
useClass: TotpService,
|
||||
@@ -575,6 +590,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
FolderApiServiceAbstraction,
|
||||
InternalOrganizationServiceAbstraction,
|
||||
SendApiServiceAbstraction,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
AvatarServiceAbstraction,
|
||||
LOGOUT_CALLBACK,
|
||||
BillingAccountProfileStateService,
|
||||
@@ -585,6 +601,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
provide: VaultTimeoutSettingsServiceAbstraction,
|
||||
useClass: VaultTimeoutSettingsService,
|
||||
deps: [
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
CryptoServiceAbstraction,
|
||||
TokenServiceAbstraction,
|
||||
PolicyServiceAbstraction,
|
||||
@@ -763,6 +780,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
CryptoServiceAbstraction,
|
||||
I18nServiceAbstraction,
|
||||
UserVerificationApiServiceAbstraction,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
PinCryptoServiceAbstraction,
|
||||
LogService,
|
||||
VaultTimeoutSettingsServiceAbstraction,
|
||||
@@ -900,6 +918,7 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
DevicesApiServiceAbstraction,
|
||||
I18nServiceAbstraction,
|
||||
PlatformUtilsServiceAbstraction,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -1048,6 +1067,11 @@ const typesafeProviders: Array<SafeProvider> = [
|
||||
useClass: DefaultBillingAccountProfileStateService,
|
||||
deps: [ActiveUserStateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: OrganizationManagementPreferencesService,
|
||||
useClass: DefaultOrganizationManagementPreferencesService,
|
||||
deps: [StateProvider],
|
||||
}),
|
||||
];
|
||||
|
||||
function encryptServiceFactory(
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
{{ dialogOptions.bodyText | i18n }}
|
||||
</p>
|
||||
|
||||
<app-callout
|
||||
<bit-callout
|
||||
*ngIf="dialogOptions.calloutOptions"
|
||||
[type]="dialogOptions.calloutOptions.type"
|
||||
>
|
||||
{{ dialogOptions.calloutOptions.text | i18n }}
|
||||
</app-callout>
|
||||
</bit-callout>
|
||||
</ng-container>
|
||||
|
||||
<!-- Shown when client side verification methods picked and no verification methods found -->
|
||||
|
||||
@@ -12,6 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
DialogModule,
|
||||
DialogService,
|
||||
} from "@bitwarden/components";
|
||||
@@ -34,6 +35,7 @@ import { UserVerificationFormInputComponent } from "./user-verification-form-inp
|
||||
DialogModule,
|
||||
AsyncActionsModule,
|
||||
UserVerificationFormInputComponent,
|
||||
CalloutModule,
|
||||
],
|
||||
})
|
||||
export class UserVerificationDialogComponent {
|
||||
|
||||
@@ -11,9 +11,9 @@ export type UserVerificationCalloutOptions = {
|
||||
|
||||
/**
|
||||
* 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";
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,12 +49,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-callout type="error" *ngIf="biometricsVerificationFailed">
|
||||
<bit-callout type="danger" *ngIf="biometricsVerificationFailed">
|
||||
{{ "couldNotCompleteBiometrics" | i18n }}
|
||||
<button bitLink type="button" linkType="primary" (click)="verifyUserViaBiometrics()">
|
||||
{{ "tryAgain" | i18n }}
|
||||
</button>
|
||||
</app-callout>
|
||||
</bit-callout>
|
||||
</ng-container>
|
||||
|
||||
<!-- Alternate verification options if user has more than 1 -->
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
IconModule,
|
||||
@@ -62,6 +63,7 @@ import { ActiveClientVerificationOption } from "./active-client-verification-opt
|
||||
IconModule,
|
||||
LinkModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
],
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./pin-crypto.service.abstraction";
|
||||
export * from "./login-strategy.service";
|
||||
export * from "./user-decryption-options.service.abstraction";
|
||||
export * from "./auth-request.service.abstraction";
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { UserDecryptionOptions } from "../models";
|
||||
|
||||
export abstract class UserDecryptionOptionsServiceAbstraction {
|
||||
/**
|
||||
* Returns what decryption options are available for the current user.
|
||||
* @remark This is sent from the server on authentication.
|
||||
*/
|
||||
abstract userDecryptionOptions$: Observable<UserDecryptionOptions>;
|
||||
/**
|
||||
* Uses user decryption options to determine if current user has a master password.
|
||||
* @remark This is sent from the server, and does not indicate if the master password
|
||||
* was used to login and/or if a master key is saved locally.
|
||||
*/
|
||||
abstract hasMasterPassword$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Returns the user decryption options for the given user id.
|
||||
* @param userId The user id to check.
|
||||
*/
|
||||
abstract userDecryptionOptionsById$(userId: string): Observable<UserDecryptionOptions>;
|
||||
}
|
||||
|
||||
export abstract class InternalUserDecryptionOptionsServiceAbstraction extends UserDecryptionOptionsServiceAbstraction {
|
||||
/**
|
||||
* Sets the current decryption options for the user, contains the current configuration
|
||||
* of the users account related to how they can decrypt their vault.
|
||||
* @remark Intended to be used when user decryption options are received from server, does
|
||||
* not update the server. Consider syncing instead of updating locally.
|
||||
* @param userDecryptionOptions Current user decryption options received from server.
|
||||
*/
|
||||
abstract setUserDecryptionOptions(userDecryptionOptions: UserDecryptionOptions): Promise<void>;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||
import { AuthRequestLoginCredentials } from "../models/domain/login-credentials";
|
||||
|
||||
import {
|
||||
@@ -37,6 +38,7 @@ describe("AuthRequestLoginStrategy", () => {
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let userDecryptionOptions: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
|
||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||
|
||||
@@ -65,6 +67,7 @@ describe("AuthRequestLoginStrategy", () => {
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
userDecryptionOptions = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
|
||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||
|
||||
@@ -83,6 +86,7 @@ describe("AuthRequestLoginStrategy", () => {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptions,
|
||||
deviceTrustCryptoService,
|
||||
billingAccountProfileStateService,
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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 { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||
|
||||
@@ -54,6 +55,7 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {
|
||||
@@ -67,6 +69,7 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptionsService,
|
||||
billingAccountProfileStateService,
|
||||
);
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
AccountProfile,
|
||||
AccountTokens,
|
||||
AccountKeys,
|
||||
AccountDecryptionOptions,
|
||||
} from "@bitwarden/common/platform/models/domain/account";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
@@ -39,8 +38,10 @@ import {
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { UserKey, MasterKey, DeviceKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { LoginStrategyServiceAbstraction } from "../abstractions/login-strategy.service";
|
||||
import { PasswordLoginCredentials } from "../models/domain/login-credentials";
|
||||
import { LoginStrategyServiceAbstraction } from "../abstractions";
|
||||
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";
|
||||
|
||||
@@ -108,6 +109,7 @@ describe("LoginStrategy", () => {
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||
let policyService: MockProxy<PolicyService>;
|
||||
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
|
||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||
@@ -126,7 +128,7 @@ describe("LoginStrategy", () => {
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
|
||||
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||
policyService = mock<PolicyService>();
|
||||
passwordStrengthService = mock<PasswordStrengthService>();
|
||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||
@@ -146,6 +148,7 @@ describe("LoginStrategy", () => {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptionsService,
|
||||
passwordStrengthService,
|
||||
policyService,
|
||||
loginStrategyService,
|
||||
@@ -204,9 +207,11 @@ describe("LoginStrategy", () => {
|
||||
...new AccountTokens(),
|
||||
},
|
||||
keys: new AccountKeys(),
|
||||
decryptionOptions: AccountDecryptionOptions.fromResponse(idTokenResponse),
|
||||
}),
|
||||
);
|
||||
expect(userDecryptionOptionsService.setUserDecryptionOptions).toHaveBeenCalledWith(
|
||||
UserDecryptionOptions.fromResponse(idTokenResponse),
|
||||
);
|
||||
expect(messagingService.send).toHaveBeenCalledWith("loggedIn");
|
||||
});
|
||||
|
||||
@@ -409,6 +414,7 @@ describe("LoginStrategy", () => {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptionsService,
|
||||
passwordStrengthService,
|
||||
policyService,
|
||||
loginStrategyService,
|
||||
|
||||
@@ -30,9 +30,9 @@ import {
|
||||
Account,
|
||||
AccountProfile,
|
||||
AccountTokens,
|
||||
AccountDecryptionOptions,
|
||||
} from "@bitwarden/common/platform/models/domain/account";
|
||||
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||
import {
|
||||
UserApiLoginCredentials,
|
||||
PasswordLoginCredentials,
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
AuthRequestLoginCredentials,
|
||||
WebAuthnLoginCredentials,
|
||||
} from "../models/domain/login-credentials";
|
||||
import { UserDecryptionOptions } from "../models/domain/user-decryption-options";
|
||||
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||
|
||||
type IdentityResponse = IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse;
|
||||
@@ -69,6 +70,7 @@ export abstract class LoginStrategy {
|
||||
protected logService: LogService,
|
||||
protected stateService: StateService,
|
||||
protected twoFactorService: TwoFactorService,
|
||||
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
protected billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {}
|
||||
|
||||
@@ -203,11 +205,14 @@ export abstract class LoginStrategy {
|
||||
...new AccountTokens(),
|
||||
},
|
||||
keys: accountKeys,
|
||||
decryptionOptions: AccountDecryptionOptions.fromResponse(tokenResponse),
|
||||
adminAuthRequest: adminAuthRequest?.toJSON(),
|
||||
}),
|
||||
);
|
||||
|
||||
await this.userDecryptionOptionsService.setUserDecryptionOptions(
|
||||
UserDecryptionOptions.fromResponse(tokenResponse),
|
||||
);
|
||||
|
||||
await this.billingAccountProfileStateService.setHasPremium(accountInformation.premium, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { LoginStrategyServiceAbstraction } from "../abstractions";
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||
import { PasswordLoginCredentials } from "../models/domain/login-credentials";
|
||||
|
||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||
@@ -60,6 +61,7 @@ describe("PasswordLoginStrategy", () => {
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||
let policyService: MockProxy<PolicyService>;
|
||||
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
|
||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||
@@ -79,6 +81,7 @@ describe("PasswordLoginStrategy", () => {
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||
policyService = mock<PolicyService>();
|
||||
passwordStrengthService = mock<PasswordStrengthService>();
|
||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||
@@ -108,6 +111,7 @@ describe("PasswordLoginStrategy", () => {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptionsService,
|
||||
passwordStrengthService,
|
||||
policyService,
|
||||
loginStrategyService,
|
||||
|
||||
@@ -26,6 +26,7 @@ import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/pass
|
||||
import { MasterKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { LoginStrategyServiceAbstraction } from "../abstractions";
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||
import { PasswordLoginCredentials } from "../models/domain/login-credentials";
|
||||
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||
|
||||
@@ -84,6 +85,7 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
logService: LogService,
|
||||
protected stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
private passwordStrengthService: PasswordStrengthServiceAbstraction,
|
||||
private policyService: PolicyService,
|
||||
private loginStrategyService: LoginStrategyServiceAbstraction,
|
||||
@@ -99,6 +101,7 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptionsService,
|
||||
billingAccountProfileStateService,
|
||||
);
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
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 { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||
@@ -39,6 +42,7 @@ describe("SsoLoginStrategy", () => {
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||
let keyConnectorService: MockProxy<KeyConnectorService>;
|
||||
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
|
||||
let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
|
||||
@@ -66,6 +70,7 @@ describe("SsoLoginStrategy", () => {
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||
keyConnectorService = mock<KeyConnectorService>();
|
||||
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
|
||||
authRequestService = mock<AuthRequestServiceAbstraction>();
|
||||
@@ -87,6 +92,7 @@ describe("SsoLoginStrategy", () => {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptionsService,
|
||||
keyConnectorService,
|
||||
deviceTrustCryptoService,
|
||||
authRequestService,
|
||||
|
||||
@@ -21,7 +21,10 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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 { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||
|
||||
@@ -84,6 +87,7 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
private authRequestService: AuthRequestServiceAbstraction,
|
||||
@@ -100,6 +104,7 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptionsService,
|
||||
billingAccountProfileStateService,
|
||||
);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
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 { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||
@@ -35,6 +36,7 @@ describe("UserApiLoginStrategy", () => {
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||
let keyConnectorService: MockProxy<KeyConnectorService>;
|
||||
let environmentService: MockProxy<EnvironmentService>;
|
||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||
@@ -57,6 +59,7 @@ describe("UserApiLoginStrategy", () => {
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||
keyConnectorService = mock<KeyConnectorService>();
|
||||
environmentService = mock<EnvironmentService>();
|
||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||
@@ -76,6 +79,7 @@ describe("UserApiLoginStrategy", () => {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptionsService,
|
||||
environmentService,
|
||||
keyConnectorService,
|
||||
billingAccountProfileStateService,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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 { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||
|
||||
@@ -47,6 +48,7 @@ export class UserApiLoginStrategy extends LoginStrategy {
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
private environmentService: EnvironmentService,
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
@@ -61,6 +63,7 @@ export class UserApiLoginStrategy extends LoginStrategy {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptionsService,
|
||||
billingAccountProfileStateService,
|
||||
);
|
||||
this.cache = new BehaviorSubject(data);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { PrfKey, UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions/user-decryption-options.service.abstraction";
|
||||
import { WebAuthnLoginCredentials } from "../models/domain/login-credentials";
|
||||
|
||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||
@@ -35,6 +36,7 @@ describe("WebAuthnLoginStrategy", () => {
|
||||
let logService!: MockProxy<LogService>;
|
||||
let stateService!: MockProxy<StateService>;
|
||||
let twoFactorService!: MockProxy<TwoFactorService>;
|
||||
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||
|
||||
let webAuthnLoginStrategy!: WebAuthnLoginStrategy;
|
||||
@@ -70,6 +72,7 @@ describe("WebAuthnLoginStrategy", () => {
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||
|
||||
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
||||
@@ -87,6 +90,7 @@ describe("WebAuthnLoginStrategy", () => {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptionsService,
|
||||
billingAccountProfileStateService,
|
||||
);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { UserKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "../abstractions";
|
||||
import { WebAuthnLoginCredentials } from "../models/domain/login-credentials";
|
||||
import { CacheData } from "../services/login-strategies/login-strategy.state";
|
||||
|
||||
@@ -49,6 +50,7 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {
|
||||
super(
|
||||
@@ -61,6 +63,7 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
userDecryptionOptionsService,
|
||||
billingAccountProfileStateService,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./rotateable-key-set";
|
||||
export * from "./login-credentials";
|
||||
export * from "./user-decryption-options";
|
||||
|
||||
153
libs/auth/src/common/models/domain/user-decryption-options.ts
Normal file
153
libs/auth/src/common/models/domain/user-decryption-options.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./domain";
|
||||
export * from "./spec";
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
KeyConnectorUserDecryptionOption,
|
||||
TrustedDeviceUserDecryptionOption,
|
||||
UserDecryptionOptions,
|
||||
} from "../domain";
|
||||
|
||||
// To discourage creating new user decryption options, we don't expose a constructor.
|
||||
// These helpers are for testing purposes only.
|
||||
|
||||
/** Testing helper for creating new instances of `UserDecryptionOptions` */
|
||||
export class FakeUserDecryptionOptions extends UserDecryptionOptions {
|
||||
constructor(init: Partial<UserDecryptionOptions>) {
|
||||
super();
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
||||
|
||||
/** Testing helper for creating new instances of `KeyConnectorUserDecryptionOption` */
|
||||
export class FakeKeyConnectorUserDecryptionOption extends KeyConnectorUserDecryptionOption {
|
||||
constructor(keyConnectorUrl: string) {
|
||||
super();
|
||||
this.keyConnectorUrl = keyConnectorUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/** Testing helper for creating new instances of `TrustedDeviceUserDecryptionOption` */
|
||||
export class FakeTrustedDeviceUserDecryptionOption extends TrustedDeviceUserDecryptionOption {
|
||||
constructor(
|
||||
hasAdminApproval: boolean,
|
||||
hasLoginApprovingDevice: boolean,
|
||||
hasManageResetPasswordPermission: boolean,
|
||||
) {
|
||||
super();
|
||||
this.hasAdminApproval = hasAdminApproval;
|
||||
this.hasLoginApprovingDevice = hasLoginApprovingDevice;
|
||||
this.hasManageResetPasswordPermission = hasManageResetPasswordPermission;
|
||||
}
|
||||
}
|
||||
1
libs/auth/src/common/models/spec/index.ts
Normal file
1
libs/auth/src/common/models/spec/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./fake-user-decryption-options";
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./pin-crypto/pin-crypto.service.implementation";
|
||||
export * from "./login-strategies/login-strategy.service";
|
||||
export * from "./user-decryption-options/user-decryption-options.service";
|
||||
export * from "./auth-request/auth-request.service";
|
||||
|
||||
@@ -25,8 +25,12 @@ import { KdfType } from "@bitwarden/common/platform/enums";
|
||||
import { FakeGlobalState, FakeGlobalStateProvider } from "@bitwarden/common/spec";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
|
||||
import { AuthRequestServiceAbstraction } from "../../abstractions";
|
||||
import {
|
||||
AuthRequestServiceAbstraction,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
} from "../../abstractions";
|
||||
import { PasswordLoginCredentials } from "../../models";
|
||||
import { UserDecryptionOptionsService } from "../user-decryption-options/user-decryption-options.service";
|
||||
|
||||
import { LoginStrategyService } from "./login-strategy.service";
|
||||
import { CACHE_EXPIRATION_KEY } from "./login-strategy.state";
|
||||
@@ -51,6 +55,7 @@ describe("LoginStrategyService", () => {
|
||||
let policyService: MockProxy<PolicyService>;
|
||||
let deviceTrustCryptoService: MockProxy<DeviceTrustCryptoServiceAbstraction>;
|
||||
let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
|
||||
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||
|
||||
let stateProvider: FakeGlobalStateProvider;
|
||||
@@ -74,6 +79,7 @@ describe("LoginStrategyService", () => {
|
||||
policyService = mock<PolicyService>();
|
||||
deviceTrustCryptoService = mock<DeviceTrustCryptoServiceAbstraction>();
|
||||
authRequestService = mock<AuthRequestServiceAbstraction>();
|
||||
userDecryptionOptionsService = mock<UserDecryptionOptionsService>();
|
||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||
stateProvider = new FakeGlobalStateProvider();
|
||||
|
||||
@@ -95,6 +101,7 @@ describe("LoginStrategyService", () => {
|
||||
policyService,
|
||||
deviceTrustCryptoService,
|
||||
authRequestService,
|
||||
userDecryptionOptionsService,
|
||||
stateProvider,
|
||||
billingAccountProfileStateService,
|
||||
);
|
||||
|
||||
@@ -40,6 +40,7 @@ import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/pass
|
||||
import { MasterKey } from "@bitwarden/common/types/key";
|
||||
|
||||
import { AuthRequestServiceAbstraction, LoginStrategyServiceAbstraction } from "../../abstractions";
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "../../abstractions/user-decryption-options.service.abstraction";
|
||||
import { AuthRequestLoginStrategy } from "../../login-strategies/auth-request-login.strategy";
|
||||
import { PasswordLoginStrategy } from "../../login-strategies/password-login.strategy";
|
||||
import { SsoLoginStrategy } from "../../login-strategies/sso-login.strategy";
|
||||
@@ -101,6 +102,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
protected policyService: PolicyService,
|
||||
protected deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
|
||||
protected authRequestService: AuthRequestServiceAbstraction,
|
||||
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
protected stateProvider: GlobalStateProvider,
|
||||
protected billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {
|
||||
@@ -354,6 +356,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.passwordStrengthService,
|
||||
this.policyService,
|
||||
this,
|
||||
@@ -371,6 +374,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.keyConnectorService,
|
||||
this.deviceTrustCryptoService,
|
||||
this.authRequestService,
|
||||
@@ -389,6 +393,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.environmentService,
|
||||
this.keyConnectorService,
|
||||
this.billingAccountProfileStateService,
|
||||
@@ -405,6 +410,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.deviceTrustCryptoService,
|
||||
this.billingAccountProfileStateService,
|
||||
);
|
||||
@@ -420,6 +426,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.billingAccountProfileStateService,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import {
|
||||
FakeAccountService,
|
||||
FakeStateProvider,
|
||||
mockAccountServiceWith,
|
||||
} from "@bitwarden/common/spec";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import {
|
||||
USER_DECRYPTION_OPTIONS,
|
||||
UserDecryptionOptionsService,
|
||||
} from "./user-decryption-options.service";
|
||||
|
||||
describe("UserDecryptionOptionsService", () => {
|
||||
let sut: UserDecryptionOptionsService;
|
||||
|
||||
const fakeUserId = Utils.newGuid() as UserId;
|
||||
let fakeAccountService: FakeAccountService;
|
||||
let fakeStateProvider: FakeStateProvider;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeAccountService = mockAccountServiceWith(fakeUserId);
|
||||
fakeStateProvider = new FakeStateProvider(fakeAccountService);
|
||||
|
||||
sut = new UserDecryptionOptionsService(fakeStateProvider);
|
||||
});
|
||||
|
||||
const userDecryptionOptions = {
|
||||
hasMasterPassword: true,
|
||||
trustedDeviceOption: {
|
||||
hasAdminApproval: false,
|
||||
hasLoginApprovingDevice: false,
|
||||
hasManageResetPasswordPermission: true,
|
||||
},
|
||||
keyConnectorOption: {
|
||||
keyConnectorUrl: "https://keyconnector.bitwarden.com",
|
||||
},
|
||||
};
|
||||
|
||||
describe("userDecryptionOptions$", () => {
|
||||
it("should return the active user's decryption options", async () => {
|
||||
await fakeStateProvider.setUserState(USER_DECRYPTION_OPTIONS, userDecryptionOptions);
|
||||
|
||||
const result = await firstValueFrom(sut.userDecryptionOptions$);
|
||||
|
||||
expect(result).toEqual(userDecryptionOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasMasterPassword$", () => {
|
||||
it("should return the hasMasterPassword property of the active user's decryption options", async () => {
|
||||
await fakeStateProvider.setUserState(USER_DECRYPTION_OPTIONS, userDecryptionOptions);
|
||||
|
||||
const result = await firstValueFrom(sut.hasMasterPassword$);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("userDecryptionOptionsById$", () => {
|
||||
it("should return the user decryption options for the given user", async () => {
|
||||
const givenUser = Utils.newGuid() as UserId;
|
||||
await fakeAccountService.addAccount(givenUser, {
|
||||
name: "Test User 1",
|
||||
email: "test1@email.com",
|
||||
status: AuthenticationStatus.Locked,
|
||||
});
|
||||
await fakeStateProvider.setUserState(
|
||||
USER_DECRYPTION_OPTIONS,
|
||||
userDecryptionOptions,
|
||||
givenUser,
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(sut.userDecryptionOptionsById$(givenUser));
|
||||
|
||||
expect(result).toEqual(userDecryptionOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setUserDecryptionOptions", () => {
|
||||
it("should set the active user's decryption options", async () => {
|
||||
await sut.setUserDecryptionOptions(userDecryptionOptions);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
fakeStateProvider.getActive(USER_DECRYPTION_OPTIONS).state$,
|
||||
);
|
||||
|
||||
expect(result).toEqual(userDecryptionOptions);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { map } from "rxjs";
|
||||
|
||||
import {
|
||||
ActiveUserState,
|
||||
StateProvider,
|
||||
USER_DECRYPTION_OPTIONS_DISK,
|
||||
UserKeyDefinition,
|
||||
} from "@bitwarden/common/platform/state";
|
||||
import { UserId } from "@bitwarden/common/src/types/guid";
|
||||
|
||||
import { InternalUserDecryptionOptionsServiceAbstraction } from "../../abstractions/user-decryption-options.service.abstraction";
|
||||
import { UserDecryptionOptions } from "../../models";
|
||||
|
||||
export const USER_DECRYPTION_OPTIONS = new UserKeyDefinition<UserDecryptionOptions>(
|
||||
USER_DECRYPTION_OPTIONS_DISK,
|
||||
"decryptionOptions",
|
||||
{
|
||||
deserializer: (decryptionOptions) => UserDecryptionOptions.fromJSON(decryptionOptions),
|
||||
clearOn: ["logout"],
|
||||
},
|
||||
);
|
||||
|
||||
export class UserDecryptionOptionsService
|
||||
implements InternalUserDecryptionOptionsServiceAbstraction
|
||||
{
|
||||
private userDecryptionOptionsState: ActiveUserState<UserDecryptionOptions>;
|
||||
|
||||
userDecryptionOptions$;
|
||||
hasMasterPassword$;
|
||||
|
||||
constructor(private stateProvider: StateProvider) {
|
||||
this.userDecryptionOptionsState = this.stateProvider.getActive(USER_DECRYPTION_OPTIONS);
|
||||
|
||||
this.userDecryptionOptions$ = this.userDecryptionOptionsState.state$;
|
||||
this.hasMasterPassword$ = this.userDecryptionOptions$.pipe(
|
||||
map((options) => options?.hasMasterPassword ?? false),
|
||||
);
|
||||
}
|
||||
|
||||
userDecryptionOptionsById$(userId: UserId) {
|
||||
return this.stateProvider.getUser(userId, USER_DECRYPTION_OPTIONS).state$;
|
||||
}
|
||||
|
||||
async setUserDecryptionOptions(userDecryptionOptions: UserDecryptionOptions): Promise<void> {
|
||||
await this.userDecryptionOptionsState.update((_) => userDecryptionOptions);
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
Reference in New Issue
Block a user