1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-28 10:33:31 +00:00

Merge branch 'main' into reduce-desktop-disk-writes

This commit is contained in:
tangowithfoxtrot
2025-02-10 09:05:29 -08:00
committed by GitHub
614 changed files with 13932 additions and 9183 deletions

View File

@@ -2,7 +2,7 @@
// @ts-strict-ignore
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { KeyService } from "@bitwarden/key-management";

View File

@@ -1,7 +1,7 @@
import { mock } from "jest-mock-extended";
import { firstValueFrom, of } from "rxjs";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";

View File

@@ -3,7 +3,7 @@
import { combineLatest, firstValueFrom, map, Observable, of, switchMap } from "rxjs";
import { Jsonify } from "type-fest";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import {

View File

@@ -1,7 +1,7 @@
import { mock, MockProxy } from "jest-mock-extended";
import { first, firstValueFrom, of, ReplaySubject, takeWhile } from "rxjs";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";

View File

@@ -2,7 +2,7 @@
// @ts-strict-ignore
import { combineLatest, filter, firstValueFrom, map } from "rxjs";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { StateProvider, DerivedState } from "@bitwarden/common/platform/state";

View File

@@ -21,8 +21,8 @@ import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";

View File

@@ -1,7 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, NavigationExtras, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { first } from "rxjs/operators";
@@ -28,7 +27,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 { UserId } from "@bitwarden/common/types/guid";
import { ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
@@ -57,7 +55,6 @@ export class SsoComponent implements OnInit {
protected redirectUri: string;
protected state: string;
protected codeChallenge: string;
protected activeUserId: UserId;
constructor(
protected ssoLoginService: SsoLoginServiceAbstraction,
@@ -77,11 +74,7 @@ export class SsoComponent implements OnInit {
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected accountService: AccountService,
protected toastService: ToastService,
) {
this.accountService.activeAccount$.pipe(takeUntilDestroyed()).subscribe((account) => {
this.activeUserId = account?.id;
});
}
) {}
async ngOnInit() {
// eslint-disable-next-line rxjs/no-async-subscribe
@@ -233,10 +226,8 @@ export class SsoComponent implements OnInit {
// - TDE login decryption options component
// - Browser SSO on extension open
// Note: you cannot set this in state before 2FA b/c there won't be an account in state.
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(
orgSsoIdentifier,
this.activeUserId,
);
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(orgSsoIdentifier, userId);
// Users enrolled in admin acct recovery can be forced to set a new password after
// having the admin set a temp password for them (affects TDE & standard users)

View File

@@ -2,7 +2,6 @@
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { Component, Inject, OnInit, ViewChild } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { ActivatedRoute, NavigationExtras, Router, RouterLink } from "@angular/router";
import { Subject, takeUntil, lastValueFrom, first, firstValueFrom } from "rxjs";
@@ -32,7 +31,6 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
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 { UserId } from "@bitwarden/common/types/guid";
import {
AsyncActionsModule,
ButtonModule,
@@ -128,7 +126,6 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
protected changePasswordRoute = "set-password";
protected forcePasswordResetRoute = "update-temp-password";
protected successRoute = "vault";
protected activeUserId: UserId;
constructor(
protected loginStrategyService: LoginStrategyServiceAbstraction,
@@ -151,10 +148,6 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
protected toastService: ToastService,
) {
super(environmentService, i18nService, platformUtilsService, toastService);
this.accountService.activeAccount$.pipe(takeUntilDestroyed()).subscribe((account) => {
this.activeUserId = account?.id;
});
}
async ngOnInit() {
@@ -269,10 +262,8 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
// Save off the OrgSsoIdentifier for use in the TDE flows
// - TDE login decryption options component
// - Browser SSO on extension open
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(
this.orgIdentifier,
this.activeUserId,
);
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier, userId);
this.loginEmailService.clearValues();
// note: this flow affects both TDE & standard users

View File

@@ -35,7 +35,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 { UserId } from "@bitwarden/common/types/guid";
import { ToastService } from "@bitwarden/components";
import { CaptchaProtectedComponent } from "./captcha-protected.component";
@@ -74,8 +73,6 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
protected successRoute = "vault";
protected twoFactorTimeoutRoute = "authentication-timeout";
protected activeUserId: UserId;
get isDuoProvider(): boolean {
return (
this.selectedProviderType === TwoFactorProviderType.Duo ||
@@ -108,10 +105,6 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
this.accountService.activeAccount$.pipe(takeUntilDestroyed()).subscribe((account) => {
this.activeUserId = account?.id;
});
// Add subscription to authenticationSessionTimeout$ and navigate to twoFactorTimeoutRoute if expired
this.loginStrategyService.authenticationSessionTimeout$
.pipe(takeUntilDestroyed())
@@ -295,10 +288,8 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
// Save off the OrgSsoIdentifier for use in the TDE flows
// - TDE login decryption options component
// - Browser SSO on extension open
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(
this.orgIdentifier,
this.activeUserId,
);
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier, userId);
this.loginEmailService.clearValues();
// note: this flow affects both TDE & standard users

View File

@@ -147,13 +147,15 @@ import { BillingApiService } from "@bitwarden/common/billing/services/billing-ap
import { OrganizationBillingApiService } from "@bitwarden/common/billing/services/organization/organization-billing-api.service";
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
import { TaxService } from "@bitwarden/common/billing/services/tax.service";
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { BulkEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/bulk-encrypt.service.implementation";
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/multithread-encrypt.service.implementation";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { BulkEncryptService } from "@bitwarden/common/platform/abstractions/bulk-encrypt.service";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import {
EnvironmentService,
RegionConfig,
@@ -194,8 +196,6 @@ import { AppIdService } from "@bitwarden/common/platform/services/app-id.service
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
import { BulkEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/bulk-encrypt.service.implementation";
import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation";
import { DefaultBroadcasterService } from "@bitwarden/common/platform/services/default-broadcaster.service";
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
import { DefaultServerSettingsService } from "@bitwarden/common/platform/services/default-server-settings.service";

View File

@@ -1,389 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { BehaviorSubject, combineLatest, firstValueFrom, Subject } from "rxjs";
import { debounceTime, first, map, skipWhile, takeUntil } from "rxjs/operators";
import { PasswordGeneratorPolicyOptions } from "@bitwarden/common/admin-console/models/domain/password-generator-policy-options";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
import {
GeneratorType,
DefaultPasswordBoundaries as DefaultBoundaries,
} from "@bitwarden/generator-core";
import {
PasswordGenerationServiceAbstraction,
UsernameGenerationServiceAbstraction,
UsernameGeneratorOptions,
PasswordGeneratorOptions,
} from "@bitwarden/generator-legacy";
export class EmailForwarderOptions {
name: string;
value: string;
validForSelfHosted: boolean;
}
@Directive()
export class GeneratorComponent implements OnInit, OnDestroy {
@Input() comingFromAddEdit = false;
@Input() type: GeneratorType | "";
@Output() onSelected = new EventEmitter<string>();
usernameGeneratingPromise: Promise<string>;
typeOptions: any[];
usernameTypeOptions: any[];
subaddressOptions: any[];
catchallOptions: any[];
forwardOptions: EmailForwarderOptions[];
usernameOptions: UsernameGeneratorOptions = { website: null };
passwordOptions: PasswordGeneratorOptions = {};
username = "-";
password = "-";
showOptions = false;
avoidAmbiguous = false;
enforcedPasswordPolicyOptions: PasswordGeneratorPolicyOptions;
usernameWebsite: string = null;
get passTypeOptions() {
return this._passTypeOptions.filter((o) => !o.disabled);
}
private _passTypeOptions: { name: string; value: GeneratorType; disabled: boolean }[];
private destroy$ = new Subject<void>();
private isInitialized$ = new BehaviorSubject(false);
// update screen reader minimum password length with 500ms debounce
// so that the user isn't flooded with status updates
private _passwordOptionsMinLengthForReader = new BehaviorSubject<number>(
DefaultBoundaries.length.min,
);
protected passwordOptionsMinLengthForReader$ = this._passwordOptionsMinLengthForReader.pipe(
map((val) => val || DefaultBoundaries.length.min),
debounceTime(500),
);
private _password = new BehaviorSubject<string>("-");
constructor(
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
protected usernameGenerationService: UsernameGenerationServiceAbstraction,
protected platformUtilsService: PlatformUtilsService,
protected accountService: AccountService,
protected i18nService: I18nService,
protected logService: LogService,
protected route: ActivatedRoute,
protected ngZone: NgZone,
private win: Window,
protected toastService: ToastService,
) {
this.typeOptions = [
{ name: i18nService.t("password"), value: "password" },
{ name: i18nService.t("username"), value: "username" },
];
this._passTypeOptions = [
{ name: i18nService.t("password"), value: "password", disabled: false },
{ name: i18nService.t("passphrase"), value: "passphrase", disabled: false },
];
this.usernameTypeOptions = [
{
name: i18nService.t("plusAddressedEmail"),
value: "subaddress",
desc: i18nService.t("plusAddressedEmailDesc"),
},
{
name: i18nService.t("catchallEmail"),
value: "catchall",
desc: i18nService.t("catchallEmailDesc"),
},
{
name: i18nService.t("forwardedEmail"),
value: "forwarded",
desc: i18nService.t("forwardedEmailDesc"),
},
{ name: i18nService.t("randomWord"), value: "word" },
];
this.subaddressOptions = [{ name: i18nService.t("random"), value: "random" }];
this.catchallOptions = [{ name: i18nService.t("random"), value: "random" }];
this.forwardOptions = [
{ name: "", value: "", validForSelfHosted: false },
{ name: "addy.io", value: "anonaddy", validForSelfHosted: true },
{ name: "DuckDuckGo", value: "duckduckgo", validForSelfHosted: false },
{ name: "Fastmail", value: "fastmail", validForSelfHosted: true },
{ name: "Firefox Relay", value: "firefoxrelay", validForSelfHosted: false },
{ name: "SimpleLogin", value: "simplelogin", validForSelfHosted: true },
{ name: "Forward Email", value: "forwardemail", validForSelfHosted: true },
].sort((a, b) => a.name.localeCompare(b.name));
this._password.pipe(debounceTime(250)).subscribe((password) => {
ngZone.run(() => {
this.password = password;
});
this.passwordGenerationService.addHistory(this.password).catch((e) => {
this.logService.error(e);
});
});
}
cascadeOptions(navigationType: GeneratorType = undefined, accountEmail: string) {
this.avoidAmbiguous = !this.passwordOptions.ambiguous;
if (!this.type) {
if (navigationType) {
this.type = navigationType;
} else {
this.type = this.passwordOptions.type === "username" ? "username" : "password";
}
}
this.passwordOptions.type =
this.passwordOptions.type === "passphrase" ? "passphrase" : "password";
const overrideType = this.enforcedPasswordPolicyOptions.overridePasswordType ?? "";
const isDisabled = overrideType.length
? (value: string, policyValue: string) => value !== policyValue
: (_value: string, _policyValue: string) => false;
for (const option of this._passTypeOptions) {
option.disabled = isDisabled(option.value, overrideType);
}
if (this.usernameOptions.type == null) {
this.usernameOptions.type = "word";
}
if (
this.usernameOptions.subaddressEmail == null ||
this.usernameOptions.subaddressEmail === ""
) {
this.usernameOptions.subaddressEmail = accountEmail;
}
if (this.usernameWebsite == null) {
this.usernameOptions.subaddressType = this.usernameOptions.catchallType = "random";
} else {
this.usernameOptions.website = this.usernameWebsite;
}
}
async ngOnInit() {
combineLatest([
this.route.queryParams.pipe(first()),
this.accountService.activeAccount$.pipe(first()),
this.passwordGenerationService.getOptions$(),
this.usernameGenerationService.getOptions$(),
])
.pipe(
map(([qParams, account, [passwordOptions, passwordPolicy], usernameOptions]) => ({
navigationType: qParams.type as GeneratorType,
accountEmail: account.email,
passwordOptions,
passwordPolicy,
usernameOptions,
})),
takeUntil(this.destroy$),
)
.subscribe((options) => {
this.passwordOptions = options.passwordOptions;
this.enforcedPasswordPolicyOptions = options.passwordPolicy;
this.usernameOptions = options.usernameOptions;
this.cascadeOptions(options.navigationType, options.accountEmail);
this._passwordOptionsMinLengthForReader.next(this.passwordOptions.minLength);
if (this.regenerateWithoutButtonPress()) {
this.regenerate().catch((e) => {
this.logService.error(e);
});
}
this.isInitialized$.next(true);
});
// once initialization is complete, `ngOnInit` should return.
//
// FIXME(#6944): if a sync is in progress, wait to complete until after
// the sync completes.
await firstValueFrom(
this.isInitialized$.pipe(
skipWhile((initialized) => !initialized),
takeUntil(this.destroy$),
),
);
if (this.usernameWebsite !== null) {
const websiteOption = { name: this.i18nService.t("websiteName"), value: "website-name" };
this.subaddressOptions.push(websiteOption);
this.catchallOptions.push(websiteOption);
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
this.isInitialized$.complete();
this._passwordOptionsMinLengthForReader.complete();
}
async typeChanged() {
await this.savePasswordOptions();
}
async regenerate() {
if (this.type === "password") {
await this.regeneratePassword();
} else if (this.type === "username") {
await this.regenerateUsername();
}
}
async sliderChanged() {
// 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.savePasswordOptions();
await this.passwordGenerationService.addHistory(this.password);
}
async onPasswordOptionsMinNumberInput($event: Event) {
// `savePasswordOptions()` replaces the null
this.passwordOptions.number = null;
await this.savePasswordOptions();
// fixes UI desync that occurs when minNumber has a fixed value
// that is reset through normalization
($event.target as HTMLInputElement).value = `${this.passwordOptions.minNumber}`;
}
async setPasswordOptionsNumber($event: boolean) {
this.passwordOptions.number = $event;
// `savePasswordOptions()` replaces the null
this.passwordOptions.minNumber = null;
await this.savePasswordOptions();
}
async onPasswordOptionsMinSpecialInput($event: Event) {
// `savePasswordOptions()` replaces the null
this.passwordOptions.special = null;
await this.savePasswordOptions();
// fixes UI desync that occurs when minSpecial has a fixed value
// that is reset through normalization
($event.target as HTMLInputElement).value = `${this.passwordOptions.minSpecial}`;
}
async setPasswordOptionsSpecial($event: boolean) {
this.passwordOptions.special = $event;
// `savePasswordOptions()` replaces the null
this.passwordOptions.minSpecial = null;
await this.savePasswordOptions();
}
async sliderInput() {
await this.normalizePasswordOptions();
}
async savePasswordOptions() {
// map navigation state into generator type
const restoreType = this.passwordOptions.type;
if (this.type === "username") {
this.passwordOptions.type = this.type;
}
// save options
await this.normalizePasswordOptions();
await this.passwordGenerationService.saveOptions(this.passwordOptions);
// restore the original format
this.passwordOptions.type = restoreType;
}
async saveUsernameOptions() {
await this.usernameGenerationService.saveOptions(this.usernameOptions);
if (this.usernameOptions.type === "forwarded") {
this.username = "-";
}
}
async regeneratePassword() {
this._password.next(
await this.passwordGenerationService.generatePassword(this.passwordOptions),
);
}
regenerateUsername() {
return this.generateUsername();
}
async generateUsername() {
try {
this.usernameGeneratingPromise = this.usernameGenerationService.generateUsername(
this.usernameOptions,
);
this.username = await this.usernameGeneratingPromise;
if (this.username === "" || this.username === null) {
this.username = "-";
}
} catch (e) {
this.logService.error(e);
}
}
copy() {
const password = this.type === "password";
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(
password ? this.password : this.username,
copyOptions,
);
this.toastService.showToast({
variant: "info",
title: null,
message: this.i18nService.t(
"valueCopied",
this.i18nService.t(password ? "password" : "username"),
),
});
}
select() {
this.onSelected.emit(this.type === "password" ? this.password : this.username);
}
toggleOptions() {
this.showOptions = !this.showOptions;
}
regenerateWithoutButtonPress() {
return this.type !== "username" || this.usernameOptions.type !== "forwarded";
}
private async normalizePasswordOptions() {
// Application level normalize options dependent on class variables
this.passwordOptions.ambiguous = !this.avoidAmbiguous;
if (
!this.passwordOptions.uppercase &&
!this.passwordOptions.lowercase &&
!this.passwordOptions.number &&
!this.passwordOptions.special
) {
this.passwordOptions.lowercase = true;
if (this.win != null) {
const lowercase = this.win.document.querySelector("#lowercase") as HTMLInputElement;
if (lowercase) {
lowercase.checked = true;
}
}
}
await this.passwordGenerationService.enforcePasswordGeneratorPoliciesOnOptions(
this.passwordOptions,
);
}
}

View File

@@ -1,40 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, OnInit } from "@angular/core";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
import { GeneratedPasswordHistory } from "@bitwarden/generator-history";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
@Directive()
export class PasswordGeneratorHistoryComponent implements OnInit {
history: GeneratedPasswordHistory[] = [];
constructor(
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
private win: Window,
protected toastService: ToastService,
) {}
async ngOnInit() {
this.history = await this.passwordGenerationService.getHistory();
}
clear = async () => {
this.history = await this.passwordGenerationService.clear();
};
copy(password: string) {
const copyOptions = this.win != null ? { window: this.win } : null;
this.platformUtilsService.copyToClipboard(password, copyOptions);
this.toastService.showToast({
variant: "info",
title: null,
message: this.i18nService.t("valueCopied", this.i18nService.t("password")),
});
}
}

View File

@@ -1,32 +0,0 @@
import { Type, inject } from "@angular/core";
import { Route, Routes } from "@angular/router";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { componentRouteSwap } from "../../utils/component-route-swap";
/**
* Helper function to swap between two components based on the GeneratorToolsModernization feature flag.
* @param defaultComponent - The current non-refreshed component to render.
* @param refreshedComponent - The new refreshed component to render.
* @param options - The shared route options to apply to the default component, and to the alt component if altOptions is not provided.
* @param altOptions - The alt route options to apply to the alt component.
*/
export function generatorSwap(
defaultComponent: Type<any>,
refreshedComponent: Type<any>,
options: Route,
altOptions?: Route,
): Routes {
return componentRouteSwap(
defaultComponent,
refreshedComponent,
async () => {
const configService = inject(ConfigService);
return configService.getFeatureFlag(FeatureFlag.GeneratorToolsModernization);
},
options,
altOptions,
);
}

View File

@@ -1,62 +0,0 @@
import { TestBed } from "@angular/core/testing";
import { Navigation, Router, UrlTree } from "@angular/router";
import { mock, MockProxy } from "jest-mock-extended";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { extensionRefreshRedirect } from "./extension-refresh-redirect";
describe("extensionRefreshRedirect", () => {
let configService: MockProxy<ConfigService>;
let router: MockProxy<Router>;
beforeEach(() => {
configService = mock<ConfigService>();
router = mock<Router>();
TestBed.configureTestingModule({
providers: [
{ provide: ConfigService, useValue: configService },
{ provide: Router, useValue: router },
],
});
});
it("returns true when ExtensionRefresh flag is disabled", async () => {
configService.getFeatureFlag.mockResolvedValue(false);
const result = await TestBed.runInInjectionContext(() =>
extensionRefreshRedirect("/redirect")(),
);
expect(result).toBe(true);
expect(configService.getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.ExtensionRefresh);
expect(router.parseUrl).not.toHaveBeenCalled();
});
it("returns UrlTree when ExtensionRefresh flag is enabled and preserves query params", async () => {
configService.getFeatureFlag.mockResolvedValue(true);
const urlTree = new UrlTree();
urlTree.queryParams = { test: "test" };
const navigation: Navigation = {
extras: {},
id: 0,
initialUrl: new UrlTree(),
extractedUrl: urlTree,
trigger: "imperative",
previousNavigation: undefined,
};
router.getCurrentNavigation.mockReturnValue(navigation);
await TestBed.runInInjectionContext(() => extensionRefreshRedirect("/redirect")());
expect(configService.getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.ExtensionRefresh);
expect(router.createUrlTree).toHaveBeenCalledWith(["/redirect"], {
queryParams: urlTree.queryParams,
});
});
});

View File

@@ -1,28 +0,0 @@
import { inject } from "@angular/core";
import { UrlTree, Router } from "@angular/router";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
/**
* Helper function to redirect to a new URL based on the ExtensionRefresh feature flag.
* @param redirectUrl - The URL to redirect to if the ExtensionRefresh flag is enabled.
*/
export function extensionRefreshRedirect(redirectUrl: string): () => Promise<boolean | UrlTree> {
return async () => {
const configService = inject(ConfigService);
const router = inject(Router);
const shouldRedirect = await configService.getFeatureFlag(FeatureFlag.ExtensionRefresh);
if (shouldRedirect) {
const currentNavigation = router.getCurrentNavigation();
const queryParams = currentNavigation?.extractedUrl?.queryParams || {};
// Preserve query params when redirecting as it is likely that the refreshed component
// will be consuming the same query params.
return router.createUrlTree([redirectUrl], { queryParams });
} else {
return true;
}
};
}

View File

@@ -1,32 +0,0 @@
import { Type, inject } from "@angular/core";
import { Route, Routes } from "@angular/router";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { componentRouteSwap } from "./component-route-swap";
/**
* Helper function to swap between two components based on the ExtensionRefresh feature flag.
* @param defaultComponent - The current non-refreshed component to render.
* @param refreshedComponent - The new refreshed component to render.
* @param options - The shared route options to apply to the default component, and to the alt component if altOptions is not provided.
* @param altOptions - The alt route options to apply to the alt component.
*/
export function extensionRefreshSwap(
defaultComponent: Type<any>,
refreshedComponent: Type<any>,
options: Route,
altOptions?: Route,
): Routes {
return componentRouteSwap(
defaultComponent,
refreshedComponent,
async () => {
const configService = inject(ConfigService);
return configService.getFeatureFlag(FeatureFlag.ExtensionRefresh);
},
options,
altOptions,
);
}

View File

@@ -6,8 +6,8 @@ import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";

View File

@@ -11,7 +11,7 @@ import {
OnInit,
Output,
} from "@angular/core";
import { filter, firstValueFrom, map, Observable } from "rxjs";
import { filter, firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
@@ -20,9 +20,9 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -81,6 +81,7 @@ export class ViewComponent implements OnDestroy, OnInit {
private passwordReprompted = false;
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
private destroyed$ = new Subject<void>();
get fido2CredentialCreationDateValue(): string {
const dateCreated = this.i18nService.t("dateCreated");
@@ -146,12 +147,15 @@ export class ViewComponent implements OnDestroy, OnInit {
const activeUserId = await firstValueFrom(this.activeUserId$);
// Grab individual cipher from `cipherViews$` for the most up-to-date information
this.cipher = await firstValueFrom(
this.cipherService.cipherViews$.pipe(
map((ciphers) => ciphers.find((c) => c.id === this.cipherId)),
this.cipherService.cipherViews$
.pipe(
map((ciphers) => ciphers?.find((c) => c.id === this.cipherId)),
filter((cipher) => !!cipher),
),
);
takeUntil(this.destroyed$),
)
.subscribe((cipher) => {
this.cipher = cipher;
});
this.canAccessPremium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId),
@@ -524,6 +528,7 @@ export class ViewComponent implements OnDestroy, OnInit {
this.showCardNumber = false;
this.showCardCode = false;
this.passwordReprompted = false;
this.destroyed$.next();
if (this.totpInterval) {
clearInterval(this.totpInterval);
}

View File

@@ -2,9 +2,8 @@ import { TestBed } from "@angular/core/testing";
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from "@angular/router";
import { BehaviorSubject } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -36,17 +35,21 @@ describe("NewDeviceVerificationNoticeGuard", () => {
});
const isSelfHost = jest.fn().mockReturnValue(false);
const getProfileTwoFactorEnabled = jest.fn().mockResolvedValue(false);
const policyAppliesToActiveUser$ = jest.fn().mockReturnValue(new BehaviorSubject<boolean>(false));
const noticeState$ = jest.fn().mockReturnValue(new BehaviorSubject(null));
const getProfileCreationDate = jest.fn().mockResolvedValue(eightDaysAgo);
const hasMasterPasswordAndMasterKeyHash = jest.fn().mockResolvedValue(true);
const getUserSSOBound = jest.fn().mockResolvedValue(false);
const getUserSSOBoundAdminOwner = jest.fn().mockResolvedValue(false);
beforeEach(() => {
getFeatureFlag.mockClear();
isSelfHost.mockClear();
getProfileCreationDate.mockClear();
getProfileTwoFactorEnabled.mockClear();
policyAppliesToActiveUser$.mockClear();
createUrlTree.mockClear();
hasMasterPasswordAndMasterKeyHash.mockClear();
getUserSSOBound.mockClear();
getUserSSOBoundAdminOwner.mockClear();
TestBed.configureTestingModule({
providers: [
@@ -55,10 +58,15 @@ describe("NewDeviceVerificationNoticeGuard", () => {
{ provide: NewDeviceVerificationNoticeService, useValue: { noticeState$ } },
{ provide: AccountService, useValue: { activeAccount$ } },
{ provide: PlatformUtilsService, useValue: { isSelfHost } },
{ provide: PolicyService, useValue: { policyAppliesToActiveUser$ } },
{ provide: UserVerificationService, useValue: { hasMasterPasswordAndMasterKeyHash } },
{
provide: VaultProfileService,
useValue: { getProfileCreationDate, getProfileTwoFactorEnabled },
useValue: {
getProfileCreationDate,
getProfileTwoFactorEnabled,
getUserSSOBound,
getUserSSOBoundAdminOwner,
},
},
],
});
@@ -90,7 +98,7 @@ describe("NewDeviceVerificationNoticeGuard", () => {
expect(isSelfHost).not.toHaveBeenCalled();
expect(getProfileTwoFactorEnabled).not.toHaveBeenCalled();
expect(getProfileCreationDate).not.toHaveBeenCalled();
expect(policyAppliesToActiveUser$).not.toHaveBeenCalled();
expect(hasMasterPasswordAndMasterKeyHash).not.toHaveBeenCalled();
});
});
@@ -121,13 +129,6 @@ describe("NewDeviceVerificationNoticeGuard", () => {
expect(await newDeviceGuard()).toBe(true);
});
it("returns `true` SSO is required", async () => {
policyAppliesToActiveUser$.mockReturnValueOnce(new BehaviorSubject(true));
expect(await newDeviceGuard()).toBe(true);
expect(policyAppliesToActiveUser$).toHaveBeenCalledWith(PolicyType.RequireSso);
});
it("returns `true` when the profile was created less than a week ago", async () => {
const sixDaysAgo = new Date();
sixDaysAgo.setDate(sixDaysAgo.getDate() - 6);
@@ -143,6 +144,57 @@ describe("NewDeviceVerificationNoticeGuard", () => {
expect(await newDeviceGuard()).toBe(true);
});
describe("SSO bound", () => {
beforeEach(() => {
getFeatureFlag.mockImplementation((key) => {
if (key === FeatureFlag.NewDeviceVerificationPermanentDismiss) {
return Promise.resolve(true);
}
return Promise.resolve(false);
});
});
afterAll(() => {
getFeatureFlag.mockReturnValue(false);
});
it('returns "true" when the user is SSO bound and not an admin or owner', async () => {
getUserSSOBound.mockResolvedValueOnce(true);
getUserSSOBoundAdminOwner.mockResolvedValueOnce(false);
expect(await newDeviceGuard()).toBe(true);
});
it('returns "true" when the user is an admin or owner of an SSO bound organization and has not logged in with their master password', async () => {
getUserSSOBound.mockResolvedValueOnce(true);
getUserSSOBoundAdminOwner.mockResolvedValueOnce(true);
hasMasterPasswordAndMasterKeyHash.mockResolvedValueOnce(false);
expect(await newDeviceGuard()).toBe(true);
});
it("shows notice when the user is an admin or owner of an SSO bound organization and logged in with their master password", async () => {
getUserSSOBound.mockResolvedValueOnce(true);
getUserSSOBoundAdminOwner.mockResolvedValueOnce(true);
hasMasterPasswordAndMasterKeyHash.mockResolvedValueOnce(true);
await newDeviceGuard();
expect(createUrlTree).toHaveBeenCalledWith(["/new-device-notice"]);
});
it("shows notice when the user that is not in an SSO bound organization", async () => {
getUserSSOBound.mockResolvedValueOnce(false);
getUserSSOBoundAdminOwner.mockResolvedValueOnce(false);
hasMasterPasswordAndMasterKeyHash.mockResolvedValueOnce(true);
await newDeviceGuard();
expect(createUrlTree).toHaveBeenCalledWith(["/new-device-notice"]);
});
});
describe("temp flag", () => {
beforeEach(() => {
getFeatureFlag.mockImplementation((key) => {

View File

@@ -2,9 +2,8 @@ import { inject } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivateFn, Router } from "@angular/router";
import { firstValueFrom, Observable } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -20,8 +19,8 @@ export const NewDeviceVerificationNoticeGuard: CanActivateFn = async (
const newDeviceVerificationNoticeService = inject(NewDeviceVerificationNoticeService);
const accountService = inject(AccountService);
const platformUtilsService = inject(PlatformUtilsService);
const policyService = inject(PolicyService);
const vaultProfileService = inject(VaultProfileService);
const userVerificationService = inject(UserVerificationService);
if (route.queryParams["fromNewDeviceVerification"]) {
return true;
@@ -47,7 +46,11 @@ export const NewDeviceVerificationNoticeGuard: CanActivateFn = async (
try {
const isSelfHosted = platformUtilsService.isSelfHost();
const requiresSSO = await isSSORequired(policyService);
const userIsSSOUser = await ssoAppliesToUser(
userVerificationService,
vaultProfileService,
currentAcct.id,
);
const has2FAEnabled = await hasATwoFactorProviderEnabled(vaultProfileService, currentAcct.id);
const isProfileLessThanWeekOld = await profileIsLessThanWeekOld(
vaultProfileService,
@@ -55,8 +58,9 @@ export const NewDeviceVerificationNoticeGuard: CanActivateFn = async (
);
// When any of the following are true, the device verification notice is
// not applicable for the user.
if (has2FAEnabled || isSelfHosted || requiresSSO || isProfileLessThanWeekOld) {
// not applicable for the user. When the user has *not* logged in with their
// master password, assume they logged in with SSO.
if (has2FAEnabled || isSelfHosted || userIsSSOUser || isProfileLessThanWeekOld) {
return true;
}
} catch {
@@ -105,9 +109,39 @@ async function profileIsLessThanWeekOld(
return !isMoreThan7DaysAgo(creationDate);
}
/** Returns true when the user is required to login via SSO */
async function isSSORequired(policyService: PolicyService) {
return firstValueFrom(policyService.policyAppliesToActiveUser$(PolicyType.RequireSso));
/**
* Returns true when either:
* - The user is SSO bound to an organization and is not an Admin or Owner
* - The user is an Admin or Owner of an organization with SSO bound and has not logged in with their master password
*
* NOTE: There are edge cases where this does not satisfy the original requirement of showing the notice to
* users who are subject to the SSO required policy. When Owners and Admins log in with their MP they will see the notice
* when they log in with SSO they will not. This is a concession made because the original logic references policies would not work for TDE users.
* When this guard is run for those users a sync hasn't occurred and thus the policies are not available.
*/
async function ssoAppliesToUser(
userVerificationService: UserVerificationService,
vaultProfileService: VaultProfileService,
userId: string,
) {
const userSSOBound = await vaultProfileService.getUserSSOBound(userId);
const userSSOBoundAdminOwner = await vaultProfileService.getUserSSOBoundAdminOwner(userId);
const userLoggedInWithMP = await userLoggedInWithMasterPassword(userVerificationService, userId);
const nonOwnerAdminSsoUser = userSSOBound && !userSSOBoundAdminOwner;
const ssoAdminOwnerLoggedInWithMP = userSSOBoundAdminOwner && !userLoggedInWithMP;
return nonOwnerAdminSsoUser || ssoAdminOwnerLoggedInWithMP;
}
/**
* Returns true when the user logged in with their master password.
*/
async function userLoggedInWithMasterPassword(
userVerificationService: UserVerificationService,
userId: string,
) {
return userVerificationService.hasMasterPasswordAndMasterKeyHash(userId);
}
/** Returns the true when the date given is older than 7 days */

View File

@@ -1,6 +1,7 @@
import { TestBed } from "@angular/core/testing";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
import { VaultProfileService } from "./vault-profile.service";
@@ -13,6 +14,12 @@ describe("VaultProfileService", () => {
creationDate: hardcodedDateString,
twoFactorEnabled: true,
id: "new-user-id",
organizations: [
{
ssoBound: true,
type: OrganizationUserType.Admin,
},
],
});
beforeEach(() => {
@@ -91,4 +98,64 @@ describe("VaultProfileService", () => {
expect(getProfile).not.toHaveBeenCalled();
});
});
describe("getUserSSOBound", () => {
it("calls `getProfile` when stored ssoBound property is not stored", async () => {
expect(service["userIsSsoBound"]).toBeNull();
const userIsSsoBound = await service.getUserSSOBound(userId);
expect(userIsSsoBound).toBe(true);
expect(getProfile).toHaveBeenCalled();
});
it("calls `getProfile` when stored profile id does not match", async () => {
service["userIsSsoBound"] = false;
service["userId"] = "old-user-id";
const userIsSsoBound = await service.getUserSSOBound(userId);
expect(userIsSsoBound).toBe(true);
expect(getProfile).toHaveBeenCalled();
});
it("does not call `getProfile` when ssoBound property is already stored", async () => {
service["userIsSsoBound"] = false;
const userIsSsoBound = await service.getUserSSOBound(userId);
expect(userIsSsoBound).toBe(false);
expect(getProfile).not.toHaveBeenCalled();
});
});
describe("getUserSSOBoundAdminOwner", () => {
it("calls `getProfile` when stored userIsSsoBoundAdminOwner property is not stored", async () => {
expect(service["userIsSsoBoundAdminOwner"]).toBeNull();
const userIsSsoBoundAdminOwner = await service.getUserSSOBoundAdminOwner(userId);
expect(userIsSsoBoundAdminOwner).toBe(true);
expect(getProfile).toHaveBeenCalled();
});
it("calls `getProfile` when stored profile id does not match", async () => {
service["userIsSsoBoundAdminOwner"] = false;
service["userId"] = "old-user-id";
const userIsSsoBoundAdminOwner = await service.getUserSSOBoundAdminOwner(userId);
expect(userIsSsoBoundAdminOwner).toBe(true);
expect(getProfile).toHaveBeenCalled();
});
it("does not call `getProfile` when userIsSsoBoundAdminOwner property is already stored", async () => {
service["userIsSsoBoundAdminOwner"] = false;
const userIsSsoBoundAdminOwner = await service.getUserSSOBoundAdminOwner(userId);
expect(userIsSsoBoundAdminOwner).toBe(false);
expect(getProfile).not.toHaveBeenCalled();
});
});
});

View File

@@ -1,6 +1,7 @@
import { Injectable, inject } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationUserType } from "@bitwarden/common/admin-console/enums";
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
@Injectable({
@@ -24,6 +25,12 @@ export class VaultProfileService {
/** True when 2FA is enabled on the profile. */
private profile2FAEnabled: boolean | null = null;
/** True when ssoBound is true for any of the users organizations */
private userIsSsoBound: boolean | null = null;
/** True when the user is an admin or owner of the ssoBound organization */
private userIsSsoBoundAdminOwner: boolean | null = null;
/**
* Returns the creation date of the profile.
* Note: `Date`s are mutable in JS, creating a new
@@ -52,12 +59,43 @@ export class VaultProfileService {
return profile.twoFactorEnabled;
}
/**
* Returns whether the user logs in with SSO for any organization.
*/
async getUserSSOBound(userId: string): Promise<boolean> {
if (this.userIsSsoBound !== null && userId === this.userId) {
return Promise.resolve(this.userIsSsoBound);
}
await this.fetchAndCacheProfile();
return !!this.userIsSsoBound;
}
/**
* Returns true when the user is an Admin or Owner of an organization with `ssoBound` true.
*/
async getUserSSOBoundAdminOwner(userId: string): Promise<boolean> {
if (this.userIsSsoBoundAdminOwner !== null && userId === this.userId) {
return Promise.resolve(this.userIsSsoBoundAdminOwner);
}
await this.fetchAndCacheProfile();
return !!this.userIsSsoBoundAdminOwner;
}
private async fetchAndCacheProfile(): Promise<ProfileResponse> {
const profile = await this.apiService.getProfile();
this.userId = profile.id;
this.profileCreatedDate = profile.creationDate;
this.profile2FAEnabled = profile.twoFactorEnabled;
const ssoBoundOrg = profile.organizations.find((org) => org.ssoBound);
this.userIsSsoBound = !!ssoBoundOrg;
this.userIsSsoBoundAdminOwner =
ssoBoundOrg?.type === OrganizationUserType.Admin ||
ssoBoundOrg?.type === OrganizationUserType.Owner;
return profile;
}

View File

@@ -11,8 +11,8 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";

View File

@@ -12,8 +12,8 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";

View File

@@ -36,7 +36,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid";
import {
AsyncActionsModule,
ButtonModule,
@@ -90,7 +89,6 @@ export class SsoComponent implements OnInit {
protected state: string | undefined;
protected codeChallenge: string | undefined;
protected clientId: SsoClientType | undefined;
protected activeUserId: UserId | undefined;
formPromise: Promise<AuthResult> | undefined;
initiateSsoFormPromise: Promise<SsoPreValidateResponse> | undefined;
@@ -132,8 +130,6 @@ export class SsoComponent implements OnInit {
}
async ngOnInit() {
this.activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const qParams: QueryParams = await firstValueFrom(this.route.queryParams);
// This if statement will pass on the second portion of the SSO flow
@@ -371,7 +367,7 @@ export class SsoComponent implements OnInit {
codeVerifier,
redirectUri,
orgSsoIdentifier,
email,
email ?? undefined,
);
this.formPromise = this.loginStrategyService.logIn(credentials);
const authResult = await this.formPromise;
@@ -388,10 +384,10 @@ export class SsoComponent implements OnInit {
// - TDE login decryption options component
// - Browser SSO on extension open
// Note: you cannot set this in state before 2FA b/c there won't be an account in state.
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(
orgSsoIdentifier,
this.activeUserId,
);
// Grabbing the active user id right before making the state set to ensure it exists.
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(orgSsoIdentifier, userId);
// Users enrolled in admin acct recovery can be forced to set a new password after
// having the admin set a temp password for them (affects TDE & standard users)

View File

@@ -9,8 +9,8 @@ import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/id
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";

View File

@@ -21,8 +21,8 @@ import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/mod
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";

View File

@@ -22,9 +22,9 @@ import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/respons
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { ClientType } from "@bitwarden/common/enums";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";

View File

@@ -13,8 +13,8 @@ import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/resp
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";

View File

@@ -13,9 +13,9 @@ import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/mod
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";

View File

@@ -8,8 +8,8 @@ import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import {
Environment,
EnvironmentService,

View File

@@ -11,8 +11,8 @@ import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/maste
import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";

View File

@@ -3,9 +3,9 @@ import { mock } from "jest-mock-extended";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";

View File

@@ -9,9 +9,9 @@ import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth
import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable";
import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request";
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";

View File

@@ -17,8 +17,8 @@ import { PreloginResponse } from "@bitwarden/common/auth/models/response/prelogi
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";

View File

@@ -22,10 +22,10 @@ import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { PreloginRequest } from "@bitwarden/common/models/request/prelogin.request";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";

View File

@@ -4,8 +4,8 @@ import { firstValueFrom, map } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";

View File

@@ -1,8 +1,8 @@
import { mock } from "jest-mock-extended";
import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";

View File

@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { mock } from "jest-mock-extended";
import { ReplaySubject, combineLatest, map } from "rxjs";
import { ReplaySubject, combineLatest, map, Observable } from "rxjs";
import { Account, AccountInfo, AccountService } from "../src/auth/abstractions/account.service";
import { UserId } from "../src/types/guid";
@@ -55,7 +55,7 @@ export class FakeAccountService implements AccountService {
}),
);
}
get nextUpAccount$() {
get nextUpAccount$(): Observable<Account> {
return combineLatest([this.accounts$, this.activeAccount$, this.sortedUserIds$]).pipe(
map(([accounts, activeAccount, sortedUserIds]) => {
const nextId = sortedUserIds.find((id) => id !== activeAccount?.id && accounts[id] != null);

View File

@@ -225,9 +225,9 @@ export class FakeStateProvider implements StateProvider {
async setUserState<T>(
userKeyDefinition: UserKeyDefinition<T>,
value: T,
value: T | null,
userId?: UserId,
): Promise<[UserId, T]> {
): Promise<[UserId, T | null]> {
await this.mock.setUserState(userKeyDefinition, value, userId);
if (userId) {
return [userId, await this.getUser(userId, userKeyDefinition).update(() => value)];

View File

@@ -131,9 +131,9 @@ export class FakeSingleUserState<T> implements SingleUserState<T> {
}
async update<TCombine>(
configureState: (state: T, dependency: TCombine) => T,
configureState: (state: T | null, dependency: TCombine) => T | null,
options?: StateUpdateOptions<T, TCombine>,
): Promise<T> {
): Promise<T | null> {
options = populateOptionsWithDefault(options);
const current = await firstValueFrom(this.state$.pipe(timeout(options.msTimeout)));
const combinedDependencies =
@@ -206,9 +206,9 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> {
}
async update<TCombine>(
configureState: (state: T, dependency: TCombine) => T,
configureState: (state: T | null, dependency: TCombine) => T | null,
options?: StateUpdateOptions<T, TCombine>,
): Promise<[UserId, T]> {
): Promise<[UserId, T | null]> {
options = populateOptionsWithDefault(options);
const current = await firstValueFrom(this.state$.pipe(timeout(options.msTimeout)));
const combinedDependencies =

View File

@@ -1,4 +1,4 @@
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { EncString } from "../../../platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { OrgKey, UserPrivateKey } from "../../../types/key";

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { UserId } from "@bitwarden/common/types/guid";
export abstract class SsoLoginServiceAbstraction {
@@ -13,7 +11,7 @@ export abstract class SsoLoginServiceAbstraction {
* @see https://datatracker.ietf.org/doc/html/rfc7636
* @returns The code verifier used for SSO.
*/
getCodeVerifier: () => Promise<string>;
abstract getCodeVerifier: () => Promise<string | null>;
/**
* Sets the code verifier used for SSO.
*
@@ -23,7 +21,7 @@ export abstract class SsoLoginServiceAbstraction {
* and verify it matches the one sent in the request for the `authorization_code`.
* @see https://datatracker.ietf.org/doc/html/rfc7636
*/
setCodeVerifier: (codeVerifier: string) => Promise<void>;
abstract setCodeVerifier: (codeVerifier: string) => Promise<void>;
/**
* Gets the value of the SSO state.
*
@@ -33,7 +31,7 @@ export abstract class SsoLoginServiceAbstraction {
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
* @returns The SSO state.
*/
getSsoState: () => Promise<string>;
abstract getSsoState: () => Promise<string | null>;
/**
* Sets the value of the SSO state.
*
@@ -42,7 +40,7 @@ export abstract class SsoLoginServiceAbstraction {
* returns the `state` in the callback and the client verifies that the value returned matches the value sent.
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
*/
setSsoState: (ssoState: string) => Promise<void>;
abstract setSsoState: (ssoState: string) => Promise<void>;
/**
* Gets the value of the user's organization sso identifier.
*
@@ -50,20 +48,20 @@ export abstract class SsoLoginServiceAbstraction {
* Do not use this value outside of the SSO login flow.
* @returns The user's organization identifier.
*/
getOrganizationSsoIdentifier: () => Promise<string>;
abstract getOrganizationSsoIdentifier: () => Promise<string | null>;
/**
* Sets the value of the user's organization sso identifier.
*
* This should only be used during the SSO flow to identify the organization that the user is attempting to log in to.
* Do not use this value outside of the SSO login flow.
*/
setOrganizationSsoIdentifier: (organizationIdentifier: string) => Promise<void>;
abstract setOrganizationSsoIdentifier: (organizationIdentifier: string) => Promise<void>;
/**
* Gets the user's email.
* Note: This should only be used during the SSO flow to identify the user that is attempting to log in.
* @returns The user's email.
*/
getSsoEmail: () => Promise<string>;
abstract getSsoEmail: () => Promise<string | null>;
/**
* Sets the user's email.
* Note: This should only be used during the SSO flow to identify the user that is attempting to log in.
@@ -71,20 +69,20 @@ export abstract class SsoLoginServiceAbstraction {
* @returns A promise that resolves when the email has been set.
*
*/
setSsoEmail: (email: string) => Promise<void>;
abstract setSsoEmail: (email: string) => Promise<void>;
/**
* Gets the value of the active user's organization sso identifier.
*
* This should only be used post successful SSO login once the user is initialized.
* @param userId The user id for retrieving the org identifier state.
*/
getActiveUserOrganizationSsoIdentifier: (userId: UserId) => Promise<string>;
abstract getActiveUserOrganizationSsoIdentifier: (userId: UserId) => Promise<string | null>;
/**
* Sets the value of the active user's organization sso identifier.
*
* This should only be used post successful SSO login once the user is initialized.
*/
setActiveUserOrganizationSsoIdentifier: (
abstract setActiveUserOrganizationSsoIdentifier: (
organizationIdentifier: string,
userId: UserId | undefined,
) => Promise<void>;

View File

@@ -7,10 +7,10 @@ import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { KeyService } from "../../../../key-management/src/abstractions/key.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { AppIdService } from "../../platform/abstractions/app-id.service";
import { ConfigService } from "../../platform/abstractions/config/config.service";
import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { I18nService } from "../../platform/abstractions/i18n.service";
import { KeyGenerationService } from "../../platform/abstractions/key-generation.service";
import { LogService } from "../../platform/abstractions/log.service";

View File

@@ -15,10 +15,10 @@ import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-a
import { FakeActiveUserState } from "../../../spec/fake-state";
import { FakeStateProvider } from "../../../spec/fake-state-provider";
import { DeviceType } from "../../enums";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { AppIdService } from "../../platform/abstractions/app-id.service";
import { ConfigService } from "../../platform/abstractions/config/config.service";
import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { I18nService } from "../../platform/abstractions/i18n.service";
import { KeyGenerationService } from "../../platform/abstractions/key-generation.service";
import { LogService } from "../../platform/abstractions/log.service";

View File

@@ -2,7 +2,7 @@
// @ts-strict-ignore
import { firstValueFrom, map, Observable } from "rxjs";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { LogService } from "../../../platform/abstractions/log.service";
import { StateService } from "../../../platform/abstractions/state.service";

View File

@@ -8,7 +8,7 @@ import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
import { KeyService } from "../../../../key-management/src/abstractions/key.service";
import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationAutoEnrollStatusResponse } from "../../admin-console/models/response/organization-auto-enroll-status.response";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "../../platform/abstractions/i18n.service";
import { UserId } from "../../types/guid";
import { Account, AccountInfo, AccountService } from "../abstractions/account.service";

View File

@@ -11,7 +11,7 @@ import {
// eslint-disable-next-line no-restricted-imports
import { KeyService } from "../../../../key-management/src/abstractions/key.service";
import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "../../platform/abstractions/i18n.service";
import { Utils } from "../../platform/misc/utils";
import { UserKey } from "../../types/key";

View File

@@ -87,7 +87,7 @@ describe("SSOLoginService ", () => {
const orgIdentifier = "test-active-org-identifier";
await sut.setActiveUserOrganizationSsoIdentifier(orgIdentifier, undefined);
expect(mockLogService.warning).toHaveBeenCalledWith(
expect(mockLogService.error).toHaveBeenCalledWith(
"Tried to set a user organization sso identifier with an undefined user id.",
);
});

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { firstValueFrom } from "rxjs";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -75,7 +73,7 @@ export class SsoLoginService implements SsoLoginServiceAbstraction {
this.ssoEmailState = this.stateProvider.getGlobal(SSO_EMAIL);
}
getCodeVerifier(): Promise<string> {
getCodeVerifier(): Promise<string | null> {
return firstValueFrom(this.codeVerifierState.state$);
}
@@ -83,7 +81,7 @@ export class SsoLoginService implements SsoLoginServiceAbstraction {
await this.codeVerifierState.update((_) => codeVerifier);
}
getSsoState(): Promise<string> {
getSsoState(): Promise<string | null> {
return firstValueFrom(this.ssoState.state$);
}
@@ -91,7 +89,7 @@ export class SsoLoginService implements SsoLoginServiceAbstraction {
await this.ssoState.update((_) => ssoState);
}
getOrganizationSsoIdentifier(): Promise<string> {
getOrganizationSsoIdentifier(): Promise<string | null> {
return firstValueFrom(this.orgSsoIdentifierState.state$);
}
@@ -99,7 +97,7 @@ export class SsoLoginService implements SsoLoginServiceAbstraction {
await this.orgSsoIdentifierState.update((_) => organizationIdentifier);
}
getSsoEmail(): Promise<string> {
getSsoEmail(): Promise<string | null> {
return firstValueFrom(this.ssoEmailState.state$);
}
@@ -107,7 +105,7 @@ export class SsoLoginService implements SsoLoginServiceAbstraction {
await this.ssoEmailState.update((_) => email);
}
getActiveUserOrganizationSsoIdentifier(userId: UserId): Promise<string> {
getActiveUserOrganizationSsoIdentifier(userId: UserId): Promise<string | null> {
return firstValueFrom(this.userOrgSsoIdentifierState(userId).state$);
}
@@ -116,7 +114,7 @@ export class SsoLoginService implements SsoLoginServiceAbstraction {
userId: UserId | undefined,
): Promise<void> {
if (userId === undefined) {
this.logService.warning(
this.logService.error(
"Tried to set a user organization sso identifier with an undefined user id.",
);
return;

View File

@@ -5,7 +5,7 @@ import { LogoutReason } from "@bitwarden/auth/common";
import { FakeSingleUserStateProvider, FakeGlobalStateProvider } from "../../../spec";
import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { KeyGenerationService } from "../../platform/abstractions/key-generation.service";
import { LogService } from "../../platform/abstractions/log.service";
import { AbstractStorageService } from "../../platform/abstractions/storage.service";

View File

@@ -6,7 +6,7 @@ import { Opaque } from "type-fest";
import { LogoutReason, decodeJwtTokenToJson } from "@bitwarden/auth/common";
import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { KeyGenerationService } from "../../platform/abstractions/key-generation.service";
import { LogService } from "../../platform/abstractions/log.service";
import { AbstractStorageService } from "../../platform/abstractions/storage.service";

View File

@@ -7,7 +7,7 @@ import { OrganizationApiServiceAbstraction as OrganizationApiService } from "../
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
import { OrganizationKeysRequest } from "../../admin-console/models/request/organization-keys.request";
import { OrganizationResponse } from "../../admin-console/models/response/organization.response";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "../../platform/abstractions/i18n.service";
import { EncString } from "../../platform/models/domain/enc-string";
import { SyncService } from "../../platform/sync";

View File

@@ -26,7 +26,6 @@ export enum FeatureFlag {
/* Tools */
ItemShare = "item-share",
GeneratorToolsModernization = "generator-tools-modernization",
CriticalApps = "pm-14466-risk-insights-critical-application",
EnableRiskInsightsNotifications = "enable-risk-insights-notifications",
@@ -49,6 +48,7 @@ export enum FeatureFlag {
PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form",
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs",
AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner",
NewDeviceVerification = "new-device-verification",
PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal",
}
@@ -87,7 +87,6 @@ export const DefaultFeatureFlagValue = {
/* Tools */
[FeatureFlag.ItemShare]: FALSE,
[FeatureFlag.GeneratorToolsModernization]: FALSE,
[FeatureFlag.CriticalApps]: FALSE,
[FeatureFlag.EnableRiskInsightsNotifications]: FALSE,
@@ -110,6 +109,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE,
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
[FeatureFlag.ResellerManagedOrgAlert]: FALSE,
[FeatureFlag.AccountDeprovisioningBanner]: FALSE,
[FeatureFlag.NewDeviceVerification]: FALSE,
[FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE,
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;

View File

@@ -0,0 +1,10 @@
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
export abstract class BulkEncryptService {
abstract decryptItems<T extends InitializerMetadata>(
items: Decryptable<T>[],
key: SymmetricCryptoKey,
): Promise<T[]>;
}

View File

@@ -1,9 +1,9 @@
import { Decryptable } from "../interfaces/decryptable.interface";
import { Encrypted } from "../interfaces/encrypted";
import { InitializerMetadata } from "../interfaces/initializer-metadata.interface";
import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
import { EncString } from "../models/domain/enc-string";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted";
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
export abstract class EncryptService {
abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString>;

View File

@@ -3,15 +3,14 @@
import { firstValueFrom, fromEvent, filter, map, takeUntil, defaultIfEmpty, Subject } from "rxjs";
import { Jsonify } from "type-fest";
import { BulkEncryptService } from "../../abstractions/bulk-encrypt.service";
import { CryptoFunctionService } from "../../abstractions/crypto-function.service";
import { LogService } from "../../abstractions/log.service";
import { Decryptable } from "../../interfaces/decryptable.interface";
import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface";
import { Utils } from "../../misc/utils";
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
import { getClassInitializer } from "./get-class-initializer";
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { getClassInitializer } from "@bitwarden/common/platform/services/cryptography/get-class-initializer";
// TTL (time to live) is not strictly required but avoids tying up memory resources if inactive
const workerTTL = 60000; // 1 minute
@@ -88,7 +87,7 @@ export class BulkEncryptServiceImplementation implements BulkEncryptService {
new Worker(
new URL(
/* webpackChunkName: 'encrypt-worker' */
"@bitwarden/common/platform/services/cryptography/encrypt.worker.ts",
"@bitwarden/common/key-management/crypto/services/encrypt.worker.ts",
import.meta.url,
),
),

View File

@@ -1,17 +1,21 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Utils } from "../../../platform/misc/utils";
import { CryptoFunctionService } from "../../abstractions/crypto-function.service";
import { EncryptService } from "../../abstractions/encrypt.service";
import { LogService } from "../../abstractions/log.service";
import { EncryptionType, encryptionTypeToString as encryptionTypeName } from "../../enums";
import { Decryptable } from "../../interfaces/decryptable.interface";
import { Encrypted } from "../../interfaces/encrypted";
import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface";
import { EncArrayBuffer } from "../../models/domain/enc-array-buffer";
import { EncString } from "../../models/domain/enc-string";
import { EncryptedObject } from "../../models/domain/encrypted-object";
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import {
EncryptionType,
encryptionTypeToString as encryptionTypeName,
} from "@bitwarden/common/platform/enums";
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted";
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { EncryptedObject } from "@bitwarden/common/platform/models/domain/encrypted-object";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { EncryptService } from "../abstractions/encrypt.service";
export class EncryptServiceImplementation implements EncryptService {
constructor(

View File

@@ -1,15 +1,17 @@
import { mockReset, mock } from "jest-mock-extended";
import { makeStaticByteArray } from "../../../spec";
import { CsprngArray } from "../../types/csprng";
import { CryptoFunctionService } from "../abstractions/crypto-function.service";
import { LogService } from "../abstractions/log.service";
import { EncryptionType } from "../enums";
import { Utils } from "../misc/utils";
import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
import { EncString } from "../models/domain/enc-string";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
import { EncryptServiceImplementation } from "../services/cryptography/encrypt.service.implementation";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { EncryptionType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "@bitwarden/common/types/csprng";
import { makeStaticByteArray } from "../../../../spec";
import { EncryptServiceImplementation } from "./encrypt.service.implementation";
describe("EncryptService", () => {
const cryptoFunctionService = mock<CryptoFunctionService>();

View File

@@ -2,14 +2,14 @@
// @ts-strict-ignore
import { Jsonify } from "type-fest";
import { Decryptable } from "../../interfaces/decryptable.interface";
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
import { ConsoleLogService } from "../console-log.service";
import { ContainerService } from "../container.service";
import { WebCryptoFunctionService } from "../web-crypto-function.service";
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { getClassInitializer } from "@bitwarden/common/platform/services/cryptography/get-class-initializer";
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
import { EncryptServiceImplementation } from "./encrypt.service.implementation";
import { getClassInitializer } from "./get-class-initializer";
const workerApi: Worker = self as any;

View File

@@ -1,10 +1,11 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { BulkEncryptService } from "../../abstractions/bulk-encrypt.service";
import { EncryptService } from "../../abstractions/encrypt.service";
import { Decryptable } from "../../interfaces/decryptable.interface";
import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface";
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service";
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { EncryptService } from "../abstractions/encrypt.service";
/**
* @deprecated For the feature flag from PM-4154, remove once feature is rolled out

View File

@@ -3,13 +3,13 @@
import { defaultIfEmpty, filter, firstValueFrom, fromEvent, map, Subject, takeUntil } from "rxjs";
import { Jsonify } from "type-fest";
import { Utils } from "../../../platform/misc/utils";
import { Decryptable } from "../../interfaces/decryptable.interface";
import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface";
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface";
import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { getClassInitializer } from "@bitwarden/common/platform/services/cryptography/get-class-initializer";
import { EncryptServiceImplementation } from "./encrypt.service.implementation";
import { getClassInitializer } from "./get-class-initializer";
// TTL (time to live) is not strictly required but avoids tying up memory resources if inactive
const workerTTL = 3 * 60000; // 3 minutes
@@ -40,7 +40,7 @@ export class MultithreadEncryptServiceImplementation extends EncryptServiceImple
this.worker ??= new Worker(
new URL(
/* webpackChunkName: 'encrypt-worker' */
"@bitwarden/common/platform/services/cryptography/encrypt.worker.ts",
"@bitwarden/common/key-management/crypto/services/encrypt.worker.ts",
import.meta.url,
),
);

View File

@@ -1,10 +0,0 @@
import { Decryptable } from "../interfaces/decryptable.interface";
import { InitializerMetadata } from "../interfaces/initializer-metadata.interface";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
export abstract class BulkEncryptService {
abstract decryptItems<T extends InitializerMetadata>(
items: Decryptable<T>[],
key: SymmetricCryptoKey,
): Promise<T[]>;
}

View File

@@ -11,7 +11,7 @@ import { Merge } from "type-fest";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { KeyService } from "../../../../key-management/src/abstractions/key.service";
import { EncryptService } from "../abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "../abstractions/i18n.service";
// FIXME: Remove when updating file. Eslint update

View File

@@ -1,7 +1,7 @@
import { mock, MockProxy } from "jest-mock-extended";
import { makeEncString, makeSymmetricCryptoKey } from "../../../../spec";
import { EncryptService } from "../../abstractions/encrypt.service";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { Utils } from "../../misc/utils";
import Domain from "./domain-base";

View File

@@ -2,8 +2,8 @@
// @ts-strict-ignore
import { ConditionalExcept, ConditionalKeys, Constructor } from "type-fest";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { View } from "../../../models/view/view";
import { EncryptService } from "../../abstractions/encrypt.service";
import { EncString } from "./enc-string";
import { SymmetricCryptoKey } from "./symmetric-crypto-key";

View File

@@ -4,7 +4,7 @@ import { mock, MockProxy } from "jest-mock-extended";
// eslint-disable-next-line no-restricted-imports
import { KeyService } from "../../../../../key-management/src/abstractions/key.service";
import { makeEncString, makeStaticByteArray } from "../../../../spec";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { UserKey, OrgKey } from "../../../types/key";
import { EncryptionType } from "../../enums";

View File

@@ -2,7 +2,7 @@
// @ts-strict-ignore
import { Jsonify, Opaque } from "type-fest";
import { EncryptService } from "../../abstractions/encrypt.service";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../enums";
import { Encrypted } from "../../interfaces/encrypted";
import { Utils } from "../../misc/utils";

View File

@@ -1,7 +1,7 @@
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { KeyService } from "../../../../key-management/src/abstractions/key.service";
import { EncryptService } from "../abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
export class ContainerService {
constructor(

View File

@@ -18,13 +18,13 @@ export interface GlobalState<T> {
* Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state.
*/
update: <TCombine>(
configureState: (state: T, dependency: TCombine) => T,
configureState: (state: T | null, dependency: TCombine) => T | null,
options?: StateUpdateOptions<T, TCombine>,
) => Promise<T>;
) => Promise<T | null>;
/**
* An observable stream of this state, the first emission of this will be the current state on disk
* and subsequent updates will be from an update to that state.
*/
state$: Observable<T>;
state$: Observable<T | null>;
}

View File

@@ -16,7 +16,7 @@ export class DefaultSingleUserState<T>
extends StateBase<T, UserKeyDefinition<T>>
implements SingleUserState<T>
{
readonly combinedState$: Observable<CombinedState<T>>;
readonly combinedState$: Observable<CombinedState<T | null>>;
constructor(
readonly userId: UserId,

View File

@@ -54,9 +54,9 @@ export class DefaultStateProvider implements StateProvider {
async setUserState<T>(
userKeyDefinition: UserKeyDefinition<T>,
value: T,
value: T | null,
userId?: UserId,
): Promise<[UserId, T]> {
): Promise<[UserId, T | null]> {
if (userId) {
return [userId, await this.getUser<T>(userId, userKeyDefinition).update(() => value)];
} else {

View File

@@ -1,12 +1,12 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import {
Observable,
ReplaySubject,
defer,
filter,
firstValueFrom,
merge,
Observable,
ReplaySubject,
share,
switchMap,
tap,
@@ -22,7 +22,7 @@ import {
ObservableStorageService,
} from "../../abstractions/storage.service";
import { DebugOptions } from "../key-definition";
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
import { populateOptionsWithDefault, StateUpdateOptions } from "../state-update-options";
import { getStoredValue } from "./util";
@@ -36,7 +36,7 @@ type KeyDefinitionRequirements<T> = {
export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>> {
private updatePromise: Promise<T>;
readonly state$: Observable<T>;
readonly state$: Observable<T | null>;
constructor(
protected readonly key: StorageKey,
@@ -86,9 +86,9 @@ export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>>
}
async update<TCombine>(
configureState: (state: T, dependency: TCombine) => T,
configureState: (state: T | null, dependency: TCombine) => T | null,
options: StateUpdateOptions<T, TCombine> = {},
): Promise<T> {
): Promise<T | null> {
options = populateOptionsWithDefault(options);
if (this.updatePromise != null) {
await this.updatePromise;
@@ -96,17 +96,16 @@ export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>>
try {
this.updatePromise = this.internalUpdate(configureState, options);
const newState = await this.updatePromise;
return newState;
return await this.updatePromise;
} finally {
this.updatePromise = null;
}
}
private async internalUpdate<TCombine>(
configureState: (state: T, dependency: TCombine) => T,
configureState: (state: T | null, dependency: TCombine) => T | null,
options: StateUpdateOptions<T, TCombine>,
): Promise<T> {
): Promise<T | null> {
const currentState = await this.getStateForUpdate();
const combinedDependencies =
options.combineLatestWith != null
@@ -122,7 +121,7 @@ export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>>
return newState;
}
protected async doStorageSave(newState: T, oldState: T) {
protected async doStorageSave(newState: T | null, oldState: T) {
if (this.keyDefinition.debug.enableUpdateLogging) {
this.logService.info(
`Updating '${this.key}' from ${oldState == null ? "null" : "non-null"} to ${newState == null ? "null" : "non-null"}`,

View File

@@ -29,9 +29,20 @@ export const ORGANIZATION_MANAGEMENT_PREFERENCES_DISK = new StateDefinition(
web: "disk-local",
},
);
export const AC_BANNERS_DISMISSED_DISK = new StateDefinition("acBannersDismissed", "disk", {
web: "disk-local",
});
export const ACCOUNT_DEPROVISIONING_BANNER_DISK = new StateDefinition(
"showAccountDeprovisioningBanner",
"disk",
{
web: "disk-local",
},
);
export const DELETE_MANAGED_USER_WARNING = new StateDefinition(
"showDeleteManagedUserWarning",
"disk",
{
web: "disk-local",
},
);
// Billing
export const BILLING_DISK = new StateDefinition("billing", "disk");

View File

@@ -60,9 +60,9 @@ export abstract class StateProvider {
*/
abstract setUserState<T>(
keyDefinition: UserKeyDefinition<T>,
value: T,
value: T | null,
userId?: UserId,
): Promise<[UserId, T]>;
): Promise<[UserId, T | null]>;
/** @see{@link ActiveUserStateProvider.get} */
abstract getActive<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T>;

View File

@@ -12,7 +12,7 @@ export interface UserState<T> {
readonly state$: Observable<T | null>;
/** Emits a stream of tuples, with the first element being a user id and the second element being the data for that user. */
readonly combinedState$: Observable<CombinedState<T>>;
readonly combinedState$: Observable<CombinedState<T | null>>;
}
export const activeMarker: unique symbol = Symbol("active");
@@ -38,9 +38,9 @@ export interface ActiveUserState<T> extends UserState<T> {
* Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state.
*/
readonly update: <TCombine>(
configureState: (state: T, dependencies: TCombine) => T,
configureState: (state: T | null, dependencies: TCombine) => T | null,
options?: StateUpdateOptions<T, TCombine>,
) => Promise<[UserId, T]>;
) => Promise<[UserId, T | null]>;
}
export interface SingleUserState<T> extends UserState<T> {
@@ -58,7 +58,7 @@ export interface SingleUserState<T> extends UserState<T> {
* Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state.
*/
readonly update: <TCombine>(
configureState: (state: T, dependencies: TCombine) => T,
configureState: (state: T | null, dependencies: TCombine) => T | null,
options?: StateUpdateOptions<T, TCombine>,
) => Promise<T>;
) => Promise<T | null>;
}

View File

@@ -32,7 +32,11 @@ export class DefaultThemeStateService implements ThemeStateService {
map(([theme, isExtensionRefresh]) => {
// The extension refresh should not allow for Nord or SolarizedDark
// Default the user to their system theme
if (isExtensionRefresh && [ThemeType.Nord, ThemeType.SolarizedDark].includes(theme)) {
if (
isExtensionRefresh &&
theme != null &&
[ThemeType.Nord, ThemeType.SolarizedDark].includes(theme)
) {
return ThemeType.System;
}

View File

@@ -68,12 +68,13 @@ import { RemoveUnassignedItemsBannerDismissed } from "./migrations/67-remove-una
import { MoveLastSyncDate } from "./migrations/68-move-last-sync-date";
import { MigrateIncorrectFolderKey } from "./migrations/69-migrate-incorrect-folder-key";
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
import { RemoveAcBannersDismissed } from "./migrations/70-remove-ac-banner-dismissed";
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global";
import { MinVersionMigrator } from "./migrations/min-version";
export const MIN_VERSION = 3;
export const CURRENT_VERSION = 69;
export const CURRENT_VERSION = 70;
export type MinVersion = typeof MIN_VERSION;
export function createMigrationBuilder() {
@@ -144,7 +145,8 @@ export function createMigrationBuilder() {
.with(MoveFinalDesktopSettingsMigrator, 65, 66)
.with(RemoveUnassignedItemsBannerDismissed, 66, 67)
.with(MoveLastSyncDate, 67, 68)
.with(MigrateIncorrectFolderKey, 68, CURRENT_VERSION);
.with(MigrateIncorrectFolderKey, 68, 69)
.with(RemoveAcBannersDismissed, 69, CURRENT_VERSION);
}
export async function currentVersion(

View File

@@ -0,0 +1,50 @@
import { runMigrator } from "../migration-helper.spec";
import { IRREVERSIBLE } from "../migrator";
import { RemoveAcBannersDismissed } from "./70-remove-ac-banner-dismissed";
describe("RemoveAcBannersDismissed", () => {
const sut = new RemoveAcBannersDismissed(69, 70);
describe("migrate", () => {
it("deletes ac banner from all users", async () => {
const output = await runMigrator(sut, {
global_account_accounts: {
user1: {
email: "user1@email.com",
name: "User 1",
emailVerified: true,
},
user2: {
email: "user2@email.com",
name: "User 2",
emailVerified: true,
},
},
user_user1_showProviderClientVaultPrivacyBanner_acBannersDismissed: true,
user_user2_showProviderClientVaultPrivacyBanner_acBannersDismissed: true,
});
expect(output).toEqual({
global_account_accounts: {
user1: {
email: "user1@email.com",
name: "User 1",
emailVerified: true,
},
user2: {
email: "user2@email.com",
name: "User 2",
emailVerified: true,
},
},
});
});
});
describe("rollback", () => {
it("is irreversible", async () => {
await expect(runMigrator(sut, {}, "rollback")).rejects.toThrow(IRREVERSIBLE);
});
});
});

View File

@@ -0,0 +1,23 @@
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { IRREVERSIBLE, Migrator } from "../migrator";
export const SHOW_BANNER_KEY: KeyDefinitionLike = {
key: "acBannersDismissed",
stateDefinition: { name: "showProviderClientVaultPrivacyBanner" },
};
export class RemoveAcBannersDismissed extends Migrator<69, 70> {
async migrate(helper: MigrationHelper): Promise<void> {
await Promise.all(
(await helper.getAccounts()).map(async ({ userId }) => {
if (helper.getFromUser(userId, SHOW_BANNER_KEY) != null) {
await helper.removeFromUser(userId, SHOW_BANNER_KEY);
}
}),
);
}
async rollback(helper: MigrationHelper): Promise<void> {
throw IRREVERSIBLE;
}
}

View File

@@ -3,7 +3,7 @@ import { BehaviorSubject, Subject } from "rxjs";
import { KeyService } from "@bitwarden/key-management";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "../../types/csprng";
import { OrganizationId, UserId } from "../../types/guid";

View File

@@ -14,7 +14,7 @@ import {
import { KeyService } from "@bitwarden/key-management";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { OrganizationId, UserId } from "../../types/guid";
import {
OrganizationBound,

View File

@@ -1,6 +1,6 @@
import { mock } from "jest-mock-extended";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { EncString } from "../../platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "../../types/csprng";

View File

@@ -2,7 +2,7 @@
// @ts-strict-ignore
import { Jsonify } from "type-fest";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { EncString } from "../../platform/models/domain/enc-string";
import { OrganizationId } from "../../types/guid";
import { OrgKey } from "../../types/key";

View File

@@ -1,6 +1,6 @@
import { mock } from "jest-mock-extended";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { EncString } from "../../platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "../../types/csprng";

View File

@@ -2,7 +2,7 @@
// @ts-strict-ignore
import { Jsonify } from "type-fest";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service";
import { EncString } from "../../platform/models/domain/enc-string";
import { UserId } from "../../types/guid";
import { UserKey } from "../../types/key";

View File

@@ -0,0 +1,199 @@
import { mock } from "jest-mock-extended";
import { LogService } from "../../platform/abstractions/log.service";
import { LogLevelType } from "../../platform/enums";
import { DefaultSemanticLogger } from "./default-semantic-logger";
const logger = mock<LogService>();
describe("DefaultSemanticLogger", () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe("debug", () => {
it("writes structural log messages to console.log", () => {
const log = new DefaultSemanticLogger(logger, {});
log.debug("this is a debug message");
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Debug, {
message: "this is a debug message",
level: LogLevelType.Debug,
});
});
it("writes structural content to console.log", () => {
const log = new DefaultSemanticLogger(logger, {});
log.debug({ example: "this is content" });
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Debug, {
content: { example: "this is content" },
level: LogLevelType.Debug,
});
});
it("writes structural content to console.log with a message", () => {
const log = new DefaultSemanticLogger(logger, {});
log.info({ example: "this is content" }, "this is a message");
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, {
content: { example: "this is content" },
message: "this is a message",
level: LogLevelType.Info,
});
});
});
describe("info", () => {
it("writes structural log messages to console.log", () => {
const log = new DefaultSemanticLogger(logger, {});
log.info("this is an info message");
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, {
message: "this is an info message",
level: LogLevelType.Info,
});
});
it("writes structural content to console.log", () => {
const log = new DefaultSemanticLogger(logger, {});
log.info({ example: "this is content" });
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, {
content: { example: "this is content" },
level: LogLevelType.Info,
});
});
it("writes structural content to console.log with a message", () => {
const log = new DefaultSemanticLogger(logger, {});
log.info({ example: "this is content" }, "this is a message");
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Info, {
content: { example: "this is content" },
message: "this is a message",
level: LogLevelType.Info,
});
});
});
describe("warn", () => {
it("writes structural log messages to console.warn", () => {
const log = new DefaultSemanticLogger(logger, {});
log.warn("this is a warning message");
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Warning, {
message: "this is a warning message",
level: LogLevelType.Warning,
});
});
it("writes structural content to console.warn", () => {
const log = new DefaultSemanticLogger(logger, {});
log.warn({ example: "this is content" });
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Warning, {
content: { example: "this is content" },
level: LogLevelType.Warning,
});
});
it("writes structural content to console.warn with a message", () => {
const log = new DefaultSemanticLogger(logger, {});
log.warn({ example: "this is content" }, "this is a message");
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Warning, {
content: { example: "this is content" },
message: "this is a message",
level: LogLevelType.Warning,
});
});
});
describe("error", () => {
it("writes structural log messages to console.error", () => {
const log = new DefaultSemanticLogger(logger, {});
log.error("this is an error message");
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
message: "this is an error message",
level: LogLevelType.Error,
});
});
it("writes structural content to console.error", () => {
const log = new DefaultSemanticLogger(logger, {});
log.error({ example: "this is content" });
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
content: { example: "this is content" },
level: LogLevelType.Error,
});
});
it("writes structural content to console.error with a message", () => {
const log = new DefaultSemanticLogger(logger, {});
log.error({ example: "this is content" }, "this is a message");
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
content: { example: "this is content" },
message: "this is a message",
level: LogLevelType.Error,
});
});
});
describe("panic", () => {
it("writes structural log messages to console.error before throwing the message", () => {
const log = new DefaultSemanticLogger(logger, {});
expect(() => log.panic("this is an error message")).toThrow("this is an error message");
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
message: "this is an error message",
level: LogLevelType.Error,
});
});
it("writes structural log messages to console.error with a message before throwing the message", () => {
const log = new DefaultSemanticLogger(logger, {});
expect(() => log.panic({ example: "this is content" }, "this is an error message")).toThrow(
"this is an error message",
);
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
content: { example: "this is content" },
message: "this is an error message",
level: LogLevelType.Error,
});
});
it("writes structural log messages to console.error with a content before throwing the message", () => {
const log = new DefaultSemanticLogger(logger, {});
expect(() => log.panic("this is content", "this is an error message")).toThrow(
"this is an error message",
);
expect(logger.write).toHaveBeenCalledWith(LogLevelType.Error, {
content: "this is content",
message: "this is an error message",
level: LogLevelType.Error,
});
});
});
});

View File

@@ -0,0 +1,65 @@
import { Jsonify } from "type-fest";
import { LogService } from "../../platform/abstractions/log.service";
import { LogLevelType } from "../../platform/enums";
import { SemanticLogger } from "./semantic-logger.abstraction";
/** Sends semantic logs to the console.
* @remarks the behavior of this logger is based on `LogService`; it
* replaces dynamic messages (`%s`) with a JSON-formatted semantic log.
*/
export class DefaultSemanticLogger<Context extends object> implements SemanticLogger {
/** Instantiates a console semantic logger
* @param context a static payload that is cloned when the logger
* logs a message. The `messages`, `level`, and `content` fields
* are reserved for use by loggers.
*/
constructor(
private logger: LogService,
context: Jsonify<Context>,
) {
this.context = context && typeof context === "object" ? context : {};
}
readonly context: object;
debug<T>(content: Jsonify<T>, message?: string): void {
this.log(content, LogLevelType.Debug, message);
}
info<T>(content: Jsonify<T>, message?: string): void {
this.log(content, LogLevelType.Info, message);
}
warn<T>(content: Jsonify<T>, message?: string): void {
this.log(content, LogLevelType.Warning, message);
}
error<T>(content: Jsonify<T>, message?: string): void {
this.log(content, LogLevelType.Error, message);
}
panic<T>(content: Jsonify<T>, message?: string): never {
this.log(content, LogLevelType.Error, message);
const panicMessage =
message ?? (typeof content === "string" ? content : "a fatal error occurred");
throw new Error(panicMessage);
}
private log<T>(content: Jsonify<T>, level: LogLevelType, message?: string) {
const log = {
...this.context,
message,
content: content ?? undefined,
level,
};
if (typeof content === "string" && !message) {
log.message = content;
delete log.content;
}
this.logger.write(level, log);
}
}

View File

@@ -0,0 +1,18 @@
import { Jsonify } from "type-fest";
import { SemanticLogger } from "./semantic-logger.abstraction";
/** Disables semantic logs. Still panics. */
export class DisabledSemanticLogger implements SemanticLogger {
debug<T>(_content: Jsonify<T>, _message?: string): void {}
info<T>(_content: Jsonify<T>, _message?: string): void {}
warn<T>(_content: Jsonify<T>, _message?: string): void {}
error<T>(_content: Jsonify<T>, _message?: string): void {}
panic<T>(_content: Jsonify<T>, message?: string): never {
throw new Error(message);
}
}

View File

@@ -0,0 +1,30 @@
import { Jsonify } from "type-fest";
import { LogService } from "../../platform/abstractions/log.service";
import { DefaultSemanticLogger } from "./default-semantic-logger";
import { DisabledSemanticLogger } from "./disabled-semantic-logger";
import { SemanticLogger } from "./semantic-logger.abstraction";
/** Instantiates a semantic logger that emits nothing when a message
* is logged.
* @param _context a static payload that is cloned when the logger
* logs a message. The `messages`, `level`, and `content` fields
* are reserved for use by loggers.
*/
export function disabledSemanticLoggerProvider<Context extends object>(
_context: Jsonify<Context>,
): SemanticLogger {
return new DisabledSemanticLogger();
}
/** Instantiates a semantic logger that emits logs to the console.
* @param context a static payload that is cloned when the logger
* logs a message. The `messages`, `level`, and `content` fields
* are reserved for use by loggers.
* @param settings specializes how the semantic logger functions.
* If this is omitted, the logger suppresses debug messages.
*/
export function consoleSemanticLoggerProvider(logger: LogService): SemanticLogger {
return new DefaultSemanticLogger(logger, {});
}

View File

@@ -0,0 +1,2 @@
export { disabledSemanticLoggerProvider, consoleSemanticLoggerProvider } from "./factory";
export { SemanticLogger } from "./semantic-logger.abstraction";

View File

@@ -0,0 +1,94 @@
import { Jsonify } from "type-fest";
/** Semantic/structural logging component */
export interface SemanticLogger {
/** Logs a message at debug priority.
* Debug messages are used for diagnostics, and are typically disabled
* in production builds.
* @param message - a message to record in the log's `message` field.
*/
debug(message: string): void;
/** Logs the content at debug priority.
* Debug messages are used for diagnostics, and are typically disabled
* in production builds.
* @param content - JSON content included in the log's `content` field.
* @param message - a message to record in the log's `message` field.
*/
debug<T>(content: Jsonify<T>, message?: string): void;
/** combined signature for overloaded methods */
debug<T>(content: Jsonify<T> | string, message?: string): void;
/** Logs a message at informational priority.
* Information messages are used for status reports.
* @param message - a message to record in the log's `message` field.
*/
info(message: string): void;
/** Logs the content at informational priority.
* Information messages are used for status reports.
* @param content - JSON content included in the log's `content` field.
* @param message - a message to record in the log's `message` field.
*/
info<T>(content: Jsonify<T>, message?: string): void;
/** combined signature for overloaded methods */
info<T>(content: Jsonify<T> | string, message?: string): void;
/** Logs a message at warn priority.
* Warn messages are used to indicate a operation that may affect system
* stability occurred.
* @param message - a message to record in the log's `message` field.
*/
warn(message: string): void;
/** Logs the content at warn priority.
* Warn messages are used to indicate a operation that may affect system
* stability occurred.
* @param content - JSON content included in the log's `content` field.
* @param message - a message to record in the log's `message` field.
*/
warn<T>(content: Jsonify<T>, message?: string): void;
/** combined signature for overloaded methods */
warn<T>(content: Jsonify<T> | string, message?: string): void;
/** Logs a message at error priority.
* Error messages are used to indicate a operation that affects system
* stability occurred and the system was able to recover.
* @param message - a message to record in the log's `message` field.
*/
error(message: string): void;
/** Logs the content at debug priority.
* Error messages are used to indicate a operation that affects system
* stability occurred and the system was able to recover.
* @param content - JSON content included in the log's `content` field.
* @param message - a message to record in the log's `message` field.
*/
error<T>(content: Jsonify<T>, message?: string): void;
/** combined signature for overloaded methods */
error<T>(content: Jsonify<T> | string, message?: string): void;
/** Logs a message at panic priority and throws an error.
* Panic messages are used to indicate a operation that affects system
* stability occurred and the system cannot recover. Panic messages
* log an error and throw an `Error`.
* @param message - a message to record in the log's `message` field.
*/
panic(message: string): never;
/** Logs the content at debug priority and throws an error.
* Panic messages are used to indicate a operation that affects system
* stability occurred and the system cannot recover. Panic messages
* log an error and throw an `Error`.
* @param content - JSON content included in the log's `content` field.
* @param message - a message to record in the log's `message` field.
*/
panic<T>(content: Jsonify<T>, message?: string): never;
/** combined signature for overloaded methods */
panic<T>(content: Jsonify<T> | string, message?: string): never;
}

View File

@@ -3,7 +3,7 @@ import { mock } from "jest-mock-extended";
import { KeyService } from "@bitwarden/key-management";
import { makeStaticByteArray, mockEnc } from "../../../../../spec";
import { EncryptService } from "../../../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../../../key-management/crypto/abstractions/encrypt.service";
import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key";
import { ContainerService } from "../../../../platform/services/container.service";
import { UserKey } from "../../../../types/key";

View File

@@ -10,7 +10,7 @@ import {
awaitAsync,
mockAccountServiceWith,
} from "../../../../spec";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { EnvironmentService } from "../../../platform/abstractions/environment.service";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";

View File

@@ -4,7 +4,7 @@ import { Observable, concatMap, distinctUntilChanged, firstValueFrom, map } from
import { PBKDF2KdfConfig, KeyService } from "@bitwarden/key-management";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { Utils } from "../../../platform/misc/utils";

View File

@@ -3,7 +3,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { KeyService } from "@bitwarden/key-management";
import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { ContainerService } from "../../../platform/services/container.service";

View File

@@ -4,8 +4,8 @@ import { Jsonify } from "type-fest";
import { KeyService } from "@bitwarden/key-management";
import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { UriMatchStrategy } from "../../../models/domain/domain-service";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { EncString } from "../../../platform/models/domain/enc-string";
import { ContainerService } from "../../../platform/services/container.service";
import { InitializerKey } from "../../../platform/services/cryptography/initializer-key";

View File

@@ -1,7 +1,7 @@
import { mock, MockProxy } from "jest-mock-extended";
import { makeEncString, makeSymmetricCryptoKey, mockEnc, mockFromJson } from "../../../../spec";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string";
import { FolderData } from "../../models/data/folder.data";
import { Folder } from "../../models/domain/folder";

View File

@@ -2,7 +2,7 @@
// @ts-strict-ignore
import { Jsonify } from "type-fest";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import Domain from "../../../platform/models/domain/domain-base";
import { EncString } from "../../../platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";

View File

@@ -2,8 +2,8 @@ import { MockProxy, mock } from "jest-mock-extended";
import { Jsonify } from "type-fest";
import { mockEnc, mockFromJson } from "../../../../spec";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { UriMatchStrategy } from "../../../models/domain/domain-service";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { EncString } from "../../../platform/models/domain/enc-string";
import { LoginUriData } from "../data/login-uri.data";

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