1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-25 12:15:18 +01:00

Merge branch 'main' into vault/pm-5273

# Conflicts:
#	libs/common/src/platform/abstractions/state.service.ts
#	libs/common/src/platform/services/state.service.ts
#	libs/common/src/state-migrations/migrate.ts
This commit is contained in:
Carlos Gonçalves 2024-03-25 18:38:10 +00:00
commit 8a1df6671a
No known key found for this signature in database
GPG Key ID: 8147F618E732EF25
364 changed files with 9254 additions and 2586 deletions

72
.github/workflows/scan.yml vendored Normal file
View File

@ -0,0 +1,72 @@
name: Scan
on:
workflow_dispatch:
push:
branches:
- "main"
- "rc"
- "hotfix-rc"
pull_request_target:
types: [opened, synchronize]
permissions: read-all
jobs:
check-run:
name: Check PR run
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
sast:
name: SAST scan
runs-on: ubuntu-22.04
needs: check-run
permissions:
security-events: write
steps:
- name: Check out repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan with Checkmarx
uses: checkmarx/ast-github-action@749fec53e0db0f6404a97e2e0807c3e80e3583a7 #2.0.23
env:
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
with:
project_name: ${{ github.repository }}
cx_tenant: ${{ secrets.CHECKMARX_TENANT }}
base_uri: https://ast.checkmarx.net/
cx_client_id: ${{ secrets.CHECKMARX_CLIENT_ID }}
cx_client_secret: ${{ secrets.CHECKMARX_SECRET }}
additional_params: --report-format sarif --output-path . ${{ env.INCREMENTAL }}
- name: Upload Checkmarx results to GitHub
uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6
with:
sarif_file: cx_result.sarif
quality:
name: Quality scan
runs-on: ubuntu-22.04
needs: check-run
steps:
- name: Check out repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan with SonarCloud
uses: sonarsource/sonarcloud-github-action@49e6cd3b187936a73b8280d59ffd9da69df63ec9 # v2.1.1
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: >
-Dsonar.organization=${{ github.repository_owner }}
-Dsonar.projectKey=${{ github.repository_owner }}_${{ github.event.repository.name }}
-Dsonar.test.inclusions=**/*.spec.ts
-Dsonar.tests=.

View File

@ -119,7 +119,7 @@
"message": "Дадаць лагін" "message": "Дадаць лагін"
}, },
"addCardMenu": { "addCardMenu": {
"message": "Add card" "message": "Дадаць картку"
}, },
"addIdentityMenu": { "addIdentityMenu": {
"message": "Add identity" "message": "Add identity"
@ -269,7 +269,7 @@
"message": "Даўжыня" "message": "Даўжыня"
}, },
"passwordMinLength": { "passwordMinLength": {
"message": "Minimum password length" "message": "Мінімальная даўжыня пароля"
}, },
"uppercase": { "uppercase": {
"message": "Вялікія літары (A-Z)" "message": "Вялікія літары (A-Z)"
@ -369,7 +369,7 @@
"message": "Iншае" "message": "Iншае"
}, },
"unlockMethodNeededToChangeTimeoutActionDesc": { "unlockMethodNeededToChangeTimeoutActionDesc": {
"message": "Set up an unlock method to change your vault timeout action." "message": "Наладзіць метад разблакіроўкі для змянення дзеяння часу чакання вашага сховішча."
}, },
"unlockMethodNeeded": { "unlockMethodNeeded": {
"message": "Set up an unlock method in Settings" "message": "Set up an unlock method in Settings"
@ -415,7 +415,7 @@
"message": "Заблакіраваць зараз" "message": "Заблакіраваць зараз"
}, },
"lockAll": { "lockAll": {
"message": "Lock all" "message": "Заблакаваць усе"
}, },
"immediately": { "immediately": {
"message": "Адразу" "message": "Адразу"
@ -494,7 +494,7 @@
"message": "Ваш уліковы запіс створаны! Цяпер вы можаце ўвайсці ў яго." "message": "Ваш уліковы запіс створаны! Цяпер вы можаце ўвайсці ў яго."
}, },
"youSuccessfullyLoggedIn": { "youSuccessfullyLoggedIn": {
"message": "You successfully logged in" "message": "Вы паспяхова аўтарызаваны"
}, },
"youMayCloseThisWindow": { "youMayCloseThisWindow": {
"message": "You may close this window" "message": "You may close this window"
@ -2005,7 +2005,7 @@
"message": "Выбраць папку..." "message": "Выбраць папку..."
}, },
"noFoldersFound": { "noFoldersFound": {
"message": "No folders found", "message": "Папкі не знойдзены",
"description": "Used as a message within the notification bar when no folders are found" "description": "Used as a message within the notification bar when no folders are found"
}, },
"orgPermissionsUpdatedMustSetPassword": { "orgPermissionsUpdatedMustSetPassword": {
@ -2215,7 +2215,7 @@
"message": "Версія сервера" "message": "Версія сервера"
}, },
"selfHostedServer": { "selfHostedServer": {
"message": "self-hosted" "message": "уласнае размяшчэнне"
}, },
"thirdParty": { "thirdParty": {
"message": "Іншы пастаўшчык" "message": "Іншы пастаўшчык"
@ -2356,25 +2356,25 @@
} }
}, },
"loggingInOn": { "loggingInOn": {
"message": "Logging in on" "message": "Увайсці на"
}, },
"opensInANewWindow": { "opensInANewWindow": {
"message": "Адкрываць у новым акне" "message": "Адкрываць у новым акне"
}, },
"deviceApprovalRequired": { "deviceApprovalRequired": {
"message": "Device approval required. Select an approval option below:" "message": "Патрабуецца ўхваленне прылады. Выберыце параметры ўхвалення ніжэй:"
}, },
"rememberThisDevice": { "rememberThisDevice": {
"message": "Remember this device" "message": "Запомніць гэту прыладу"
}, },
"uncheckIfPublicDevice": { "uncheckIfPublicDevice": {
"message": "Uncheck if using a public device" "message": "Здыміце пазнаку, калі выкарыстоўваеце агульнадаступную прыладу"
}, },
"approveFromYourOtherDevice": { "approveFromYourOtherDevice": {
"message": "Approve from your other device" "message": "Ухваліць з іншай вашай прылады"
}, },
"requestAdminApproval": { "requestAdminApproval": {
"message": "Request admin approval" "message": "Запытаць ухваленне адміністратара"
}, },
"approveWithMasterPassword": { "approveWithMasterPassword": {
"message": "Approve with master password" "message": "Approve with master password"
@ -2402,7 +2402,7 @@
"message": "Адлюстраванне" "message": "Адлюстраванне"
}, },
"accountSuccessfullyCreated": { "accountSuccessfullyCreated": {
"message": "Account successfully created!" "message": "Уліковы запіс паспяхова створаны!"
}, },
"adminApprovalRequested": { "adminApprovalRequested": {
"message": "Admin approval requested" "message": "Admin approval requested"

View File

@ -688,7 +688,7 @@
"message": "A bejelentkezési jelszó frissítésének kérése, ha változást lett érzékelve egy webhelyen. Minden bejelentkezett fiókra vonatkozik." "message": "A bejelentkezési jelszó frissítésének kérése, ha változást lett érzékelve egy webhelyen. Minden bejelentkezett fiókra vonatkozik."
}, },
"enableUsePasskeys": { "enableUsePasskeys": {
"message": "Kérés a jhozzáférési kulcs mentésére és használatára" "message": "Kérés a hozzáférési kulcs mentésére és használatára"
}, },
"usePasskeysDesc": { "usePasskeysDesc": {
"message": "Kérés az új hozzáféréi kulcsok mentésére vagy bejelentkezés a széfben tárolt hozzáférési kulcsokkal. Minden bejelentkezett fiókra vonatkozik." "message": "Kérés az új hozzáféréi kulcsok mentésére vagy bejelentkezés a széfben tárolt hozzáférési kulcsokkal. Minden bejelentkezett fiókra vonatkozik."

View File

@ -1,4 +1,5 @@
import { OrganizationService as AbstractOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService as AbstractOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
import { import {
FactoryOptions, FactoryOptions,
@ -6,11 +7,7 @@ import {
factory, factory,
} from "../../../platform/background/service-factories/factory-options"; } from "../../../platform/background/service-factories/factory-options";
import { stateProviderFactory } from "../../../platform/background/service-factories/state-provider.factory"; import { stateProviderFactory } from "../../../platform/background/service-factories/state-provider.factory";
import { import { StateServiceInitOptions } from "../../../platform/background/service-factories/state-service.factory";
stateServiceFactory,
StateServiceInitOptions,
} from "../../../platform/background/service-factories/state-service.factory";
import { BrowserOrganizationService } from "../../services/browser-organization.service";
type OrganizationServiceFactoryOptions = FactoryOptions; type OrganizationServiceFactoryOptions = FactoryOptions;
@ -25,10 +22,6 @@ export function organizationServiceFactory(
cache, cache,
"organizationService", "organizationService",
opts, opts,
async () => async () => new OrganizationService(await stateProviderFactory(cache, opts)),
new BrowserOrganizationService(
await stateServiceFactory(cache, opts),
await stateProviderFactory(cache, opts),
),
); );
} }

View File

@ -1,12 +0,0 @@
import { BehaviorSubject } from "rxjs";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
import { browserSession, sessionSync } from "../../platform/decorators/session-sync-observable";
@browserSession
export class BrowserOrganizationService extends OrganizationService {
@sessionSync({ initializer: Organization.fromJSON, initializeAs: "array" })
protected _organizations: BehaviorSubject<Organization[]>;
}

View File

@ -0,0 +1,38 @@
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import {
ApiServiceInitOptions,
apiServiceFactory,
} from "../../../platform/background/service-factories/api-service.factory";
import {
CachedServices,
factory,
FactoryOptions,
} from "../../../platform/background/service-factories/factory-options";
import {
stateProviderFactory,
StateProviderInitOptions,
} from "../../../platform/background/service-factories/state-provider.factory";
type AvatarServiceFactoryOptions = FactoryOptions;
export type AvatarServiceInitOptions = AvatarServiceFactoryOptions &
ApiServiceInitOptions &
StateProviderInitOptions;
export function avatarServiceFactory(
cache: { avatarService?: AvatarServiceAbstraction } & CachedServices,
opts: AvatarServiceInitOptions,
): Promise<AvatarServiceAbstraction> {
return factory(
cache,
"avatarService",
opts,
async () =>
new AvatarService(
await apiServiceFactory(cache, opts),
await stateProviderFactory(cache, opts),
),
);
}

View File

@ -9,6 +9,7 @@ import {
ApiServiceInitOptions, ApiServiceInitOptions,
} from "../../../platform/background/service-factories/api-service.factory"; } from "../../../platform/background/service-factories/api-service.factory";
import { appIdServiceFactory } from "../../../platform/background/service-factories/app-id-service.factory"; import { appIdServiceFactory } from "../../../platform/background/service-factories/app-id-service.factory";
import { billingAccountProfileStateServiceFactory } from "../../../platform/background/service-factories/billing-account-profile-state-service.factory";
import { import {
CryptoServiceInitOptions, CryptoServiceInitOptions,
cryptoServiceFactory, cryptoServiceFactory,
@ -119,6 +120,7 @@ export function loginStrategyServiceFactory(
await deviceTrustCryptoServiceFactory(cache, opts), await deviceTrustCryptoServiceFactory(cache, opts),
await authRequestServiceFactory(cache, opts), await authRequestServiceFactory(cache, opts),
await globalStateProviderFactory(cache, opts), await globalStateProviderFactory(cache, opts),
await billingAccountProfileStateServiceFactory(cache, opts),
), ),
); );
} }

View File

@ -7,13 +7,29 @@ import {
factory, factory,
} from "../../../platform/background/service-factories/factory-options"; } from "../../../platform/background/service-factories/factory-options";
import { import {
stateServiceFactory, GlobalStateProviderInitOptions,
StateServiceInitOptions, globalStateProviderFactory,
} from "../../../platform/background/service-factories/state-service.factory"; } from "../../../platform/background/service-factories/global-state-provider.factory";
import {
PlatformUtilsServiceInitOptions,
platformUtilsServiceFactory,
} from "../../../platform/background/service-factories/platform-utils-service.factory";
import {
SingleUserStateProviderInitOptions,
singleUserStateProviderFactory,
} from "../../../platform/background/service-factories/single-user-state-provider.factory";
import {
SecureStorageServiceInitOptions,
secureStorageServiceFactory,
} from "../../../platform/background/service-factories/storage-service.factory";
type TokenServiceFactoryOptions = FactoryOptions; type TokenServiceFactoryOptions = FactoryOptions;
export type TokenServiceInitOptions = TokenServiceFactoryOptions & StateServiceInitOptions; export type TokenServiceInitOptions = TokenServiceFactoryOptions &
SingleUserStateProviderInitOptions &
GlobalStateProviderInitOptions &
PlatformUtilsServiceInitOptions &
SecureStorageServiceInitOptions;
export function tokenServiceFactory( export function tokenServiceFactory(
cache: { tokenService?: AbstractTokenService } & CachedServices, cache: { tokenService?: AbstractTokenService } & CachedServices,
@ -23,6 +39,12 @@ export function tokenServiceFactory(
cache, cache,
"tokenService", "tokenService",
opts, opts,
async () => new TokenService(await stateServiceFactory(cache, opts)), async () =>
new TokenService(
await singleUserStateProviderFactory(cache, opts),
await globalStateProviderFactory(cache, opts),
(await platformUtilsServiceFactory(cache, opts)).supportsSecureStorage(),
await secureStorageServiceFactory(cache, opts),
),
); );
} }

View File

@ -1,32 +1,61 @@
import { Location } from "@angular/common"; import { Location } from "@angular/common";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { Observable, combineLatest, switchMap } from "rxjs";
import { CurrentAccountService } from "./services/current-account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { UserId } from "@bitwarden/common/types/guid";
export type CurrentAccount = {
id: UserId;
name: string | undefined;
email: string;
status: AuthenticationStatus;
avatarColor: string;
};
@Component({ @Component({
selector: "app-current-account", selector: "app-current-account",
templateUrl: "current-account.component.html", templateUrl: "current-account.component.html",
}) })
export class CurrentAccountComponent { export class CurrentAccountComponent {
currentAccount$: Observable<CurrentAccount>;
constructor( constructor(
private currentAccountService: CurrentAccountService, private accountService: AccountService,
private avatarService: AvatarService,
private router: Router, private router: Router,
private location: Location, private location: Location,
private route: ActivatedRoute, private route: ActivatedRoute,
) {} ) {
this.currentAccount$ = combineLatest([
this.accountService.activeAccount$,
this.avatarService.avatarColor$,
]).pipe(
switchMap(async ([account, avatarColor]) => {
if (account == null) {
return null;
}
const currentAccount: CurrentAccount = {
id: account.id,
name: account.name || account.email,
email: account.email,
status: account.status,
avatarColor,
};
get currentAccount$() { return currentAccount;
return this.currentAccountService.currentAccount$; }),
);
} }
async currentAccountClicked() { async currentAccountClicked() {
if (this.route.snapshot.data.state.includes("account-switcher")) { if (this.route.snapshot.data.state.includes("account-switcher")) {
this.location.back(); this.location.back();
} else { } else {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.router.navigate(["/account-switcher"]);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/account-switcher"]);
} }
} }
} }

View File

@ -1,12 +1,12 @@
import { matches, mock } from "jest-mock-extended"; import { matches, mock } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom, timeout } from "rxjs"; import { BehaviorSubject, firstValueFrom, of, timeout } from "rxjs";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { UserId } from "@bitwarden/common/types/guid"; import { UserId } from "@bitwarden/common/types/guid";
import { AccountSwitcherService } from "./account-switcher.service"; import { AccountSwitcherService } from "./account-switcher.service";
@ -16,7 +16,7 @@ describe("AccountSwitcherService", () => {
const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>(null); const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>(null);
const accountService = mock<AccountService>(); const accountService = mock<AccountService>();
const stateService = mock<StateService>(); const avatarService = mock<AvatarService>();
const messagingService = mock<MessagingService>(); const messagingService = mock<MessagingService>();
const environmentService = mock<EnvironmentService>(); const environmentService = mock<EnvironmentService>();
const logService = mock<LogService>(); const logService = mock<LogService>();
@ -25,11 +25,13 @@ describe("AccountSwitcherService", () => {
beforeEach(() => { beforeEach(() => {
jest.resetAllMocks(); jest.resetAllMocks();
accountService.accounts$ = accountsSubject; accountService.accounts$ = accountsSubject;
accountService.activeAccount$ = activeAccountSubject; accountService.activeAccount$ = activeAccountSubject;
accountSwitcherService = new AccountSwitcherService( accountSwitcherService = new AccountSwitcherService(
accountService, accountService,
stateService, avatarService,
messagingService, messagingService,
environmentService, environmentService,
logService, logService,
@ -44,6 +46,7 @@ describe("AccountSwitcherService", () => {
status: AuthenticationStatus.Unlocked, status: AuthenticationStatus.Unlocked,
}; };
avatarService.getUserAvatarColor$.mockReturnValue(of("#cccccc"));
accountsSubject.next({ accountsSubject.next({
"1": user1AccountInfo, "1": user1AccountInfo,
} as Record<UserId, AccountInfo>); } as Record<UserId, AccountInfo>);
@ -72,6 +75,7 @@ describe("AccountSwitcherService", () => {
status: AuthenticationStatus.Unlocked, status: AuthenticationStatus.Unlocked,
}; };
} }
avatarService.getUserAvatarColor$.mockReturnValue(of("#cccccc"));
accountsSubject.next(seedAccounts); accountsSubject.next(seedAccounts);
activeAccountSubject.next( activeAccountSubject.next(
Object.assign(seedAccounts["1" as UserId], { id: "1" as UserId }), Object.assign(seedAccounts["1" as UserId], { id: "1" as UserId }),

View File

@ -11,11 +11,11 @@ import {
} from "rxjs"; } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { UserId } from "@bitwarden/common/types/guid"; import { UserId } from "@bitwarden/common/types/guid";
import { fromChromeEvent } from "../../../../platform/browser/from-chrome-event"; import { fromChromeEvent } from "../../../../platform/browser/from-chrome-event";
@ -44,7 +44,7 @@ export class AccountSwitcherService {
constructor( constructor(
private accountService: AccountService, private accountService: AccountService,
private stateService: StateService, private avatarService: AvatarService,
private messagingService: MessagingService, private messagingService: MessagingService,
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private logService: LogService, private logService: LogService,
@ -68,7 +68,9 @@ export class AccountSwitcherService {
server: await this.environmentService.getHost(id), server: await this.environmentService.getHost(id),
status: account.status, status: account.status,
isActive: id === activeAccount?.id, isActive: id === activeAccount?.id,
avatarColor: await this.stateService.getAvatarColor({ userId: id }), avatarColor: await firstValueFrom(
this.avatarService.getUserAvatarColor$(id as UserId),
),
}; };
}), }),
); );

View File

@ -1,44 +0,0 @@
import { Injectable } from "@angular/core";
import { Observable, switchMap } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { UserId } from "@bitwarden/common/types/guid";
export type CurrentAccount = {
id: UserId;
name: string | undefined;
email: string;
status: AuthenticationStatus;
avatarColor: string;
};
@Injectable({
providedIn: "root",
})
export class CurrentAccountService {
currentAccount$: Observable<CurrentAccount>;
constructor(
private accountService: AccountService,
private stateService: StateService,
) {
this.currentAccount$ = this.accountService.activeAccount$.pipe(
switchMap(async (account) => {
if (account == null) {
return null;
}
const currentAccount: CurrentAccount = {
id: account.id,
name: account.name || account.email,
email: account.email,
status: account.status,
avatarColor: await this.stateService.getAvatarColor({ userId: account.id }),
};
return currentAccount;
}),
);
}
}

View File

@ -91,6 +91,8 @@ export class LoginComponent extends BaseLoginComponent {
} }
async launchSsoBrowser() { async launchSsoBrowser() {
// Save off email for SSO
await this.ssoLoginService.setSsoEmail(this.formGroup.value.email);
await this.loginService.saveEmailSettings(); await this.loginService.saveEmailSettings();
// Generate necessary sso params // Generate necessary sso params
const passwordOptions: any = { const passwordOptions: any = {

View File

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

View File

@ -8,11 +8,21 @@ import {
AutofillOverlayVisibility, AutofillOverlayVisibility,
} from "@bitwarden/common/autofill/constants"; } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service";
import {
DefaultDomainSettingsService,
DomainSettingsService,
} from "@bitwarden/common/autofill/services/domain-settings.service";
import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service";
import { I18nService } from "@bitwarden/common/platform/services/i18n.service"; import { I18nService } from "@bitwarden/common/platform/services/i18n.service";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { SettingsService } from "@bitwarden/common/services/settings.service"; import {
FakeStateProvider,
FakeAccountService,
mockAccountServiceWith,
} from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@ -41,6 +51,10 @@ import OverlayBackground from "./overlay.background";
const iconServerUrl = "https://icons.bitwarden.com/"; const iconServerUrl = "https://icons.bitwarden.com/";
describe("OverlayBackground", () => { describe("OverlayBackground", () => {
const mockUserId = Utils.newGuid() as UserId;
const accountService: FakeAccountService = mockAccountServiceWith(mockUserId);
const fakeStateProvider: FakeStateProvider = new FakeStateProvider(accountService);
let domainSettingsService: DomainSettingsService;
let buttonPortSpy: chrome.runtime.Port; let buttonPortSpy: chrome.runtime.Port;
let listPortSpy: chrome.runtime.Port; let listPortSpy: chrome.runtime.Port;
let overlayBackground: OverlayBackground; let overlayBackground: OverlayBackground;
@ -50,7 +64,6 @@ describe("OverlayBackground", () => {
const environmentService = mock<EnvironmentService>({ const environmentService = mock<EnvironmentService>({
getIconsUrl: () => iconServerUrl, getIconsUrl: () => iconServerUrl,
}); });
const settingsService = mock<SettingsService>();
const stateService = mock<BrowserStateService>(); const stateService = mock<BrowserStateService>();
const autofillSettingsService = mock<AutofillSettingsService>(); const autofillSettingsService = mock<AutofillSettingsService>();
const i18nService = mock<I18nService>(); const i18nService = mock<I18nService>();
@ -72,12 +85,13 @@ describe("OverlayBackground", () => {
}; };
beforeEach(() => { beforeEach(() => {
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
overlayBackground = new OverlayBackground( overlayBackground = new OverlayBackground(
cipherService, cipherService,
autofillService, autofillService,
authService, authService,
environmentService, environmentService,
settingsService, domainSettingsService,
stateService, stateService,
autofillSettingsService, autofillSettingsService,
i18nService, i18nService,
@ -90,6 +104,7 @@ describe("OverlayBackground", () => {
.mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus); .mockResolvedValue(AutofillOverlayVisibility.OnFieldFocus);
themeStateService.selectedTheme$ = of(ThemeType.Light); themeStateService.selectedTheme$ = of(ThemeType.Light);
domainSettingsService.showFavicons$ = of(true);
void overlayBackground.init(); void overlayBackground.init();
}); });
@ -274,7 +289,7 @@ describe("OverlayBackground", () => {
card: { subTitle: "Mastercard, *1234" }, card: { subTitle: "Mastercard, *1234" },
}); });
it("formats and returns the cipher data", () => { it("formats and returns the cipher data", async () => {
overlayBackground["overlayLoginCiphers"] = new Map([ overlayBackground["overlayLoginCiphers"] = new Map([
["overlay-cipher-0", cipher2], ["overlay-cipher-0", cipher2],
["overlay-cipher-1", cipher1], ["overlay-cipher-1", cipher1],
@ -282,7 +297,7 @@ describe("OverlayBackground", () => {
["overlay-cipher-3", cipher4], ["overlay-cipher-3", cipher4],
]); ]);
const overlayCipherData = overlayBackground["getOverlayCipherData"](); const overlayCipherData = await overlayBackground["getOverlayCipherData"]();
expect(overlayCipherData).toStrictEqual([ expect(overlayCipherData).toStrictEqual([
{ {

View File

@ -1,10 +1,10 @@
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants"; import { SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -92,7 +92,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
private autofillService: AutofillService, private autofillService: AutofillService,
private authService: AuthService, private authService: AuthService,
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private settingsService: SettingsService, private domainSettingsService: DomainSettingsService,
private stateService: StateService, private stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction, private autofillSettingsService: AutofillSettingsServiceAbstraction,
private i18nService: I18nService, private i18nService: I18nService,
@ -145,7 +145,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
this.overlayLoginCiphers.set(`overlay-cipher-${cipherIndex}`, ciphersViews[cipherIndex]); this.overlayLoginCiphers.set(`overlay-cipher-${cipherIndex}`, ciphersViews[cipherIndex]);
} }
const ciphers = this.getOverlayCipherData(); const ciphers = await this.getOverlayCipherData();
this.overlayListPort?.postMessage({ command: "updateOverlayListCiphers", ciphers }); this.overlayListPort?.postMessage({ command: "updateOverlayListCiphers", ciphers });
await BrowserApi.tabSendMessageData(currentTab, "updateIsOverlayCiphersPopulated", { await BrowserApi.tabSendMessageData(currentTab, "updateIsOverlayCiphersPopulated", {
isOverlayCiphersPopulated: Boolean(ciphers.length), isOverlayCiphersPopulated: Boolean(ciphers.length),
@ -156,8 +156,8 @@ class OverlayBackground implements OverlayBackgroundInterface {
* Strips out unnecessary data from the ciphers and returns an array of * Strips out unnecessary data from the ciphers and returns an array of
* objects that contain the cipher data needed for the overlay list. * objects that contain the cipher data needed for the overlay list.
*/ */
private getOverlayCipherData(): OverlayCipherData[] { private async getOverlayCipherData(): Promise<OverlayCipherData[]> {
const isFaviconDisabled = this.settingsService.getDisableFavicon(); const showFavicons = await firstValueFrom(this.domainSettingsService.showFavicons$);
const overlayCiphersArray = Array.from(this.overlayLoginCiphers); const overlayCiphersArray = Array.from(this.overlayLoginCiphers);
const overlayCipherData = []; const overlayCipherData = [];
let loginCipherIcon: WebsiteIconData; let loginCipherIcon: WebsiteIconData;
@ -165,7 +165,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
for (let cipherIndex = 0; cipherIndex < overlayCiphersArray.length; cipherIndex++) { for (let cipherIndex = 0; cipherIndex < overlayCiphersArray.length; cipherIndex++) {
const [overlayCipherId, cipher] = overlayCiphersArray[cipherIndex]; const [overlayCipherId, cipher] = overlayCiphersArray[cipherIndex];
if (!loginCipherIcon && cipher.type === CipherType.Login) { if (!loginCipherIcon && cipher.type === CipherType.Login) {
loginCipherIcon = buildCipherIcon(this.iconsServerUrl, cipher, isFaviconDisabled); loginCipherIcon = buildCipherIcon(this.iconsServerUrl, cipher, showFavicons);
} }
overlayCipherData.push({ overlayCipherData.push({
@ -177,7 +177,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
icon: icon:
cipher.type === CipherType.Login cipher.type === CipherType.Login
? loginCipherIcon ? loginCipherIcon
: buildCipherIcon(this.iconsServerUrl, cipher, isFaviconDisabled), : buildCipherIcon(this.iconsServerUrl, cipher, showFavicons),
login: cipher.type === CipherType.Login ? { username: cipher.login.username } : null, login: cipher.type === CipherType.Login ? { username: cipher.login.username } : null,
card: cipher.type === CipherType.Card ? cipher.card.subTitle : null, card: cipher.type === CipherType.Card ? cipher.card.subTitle : null,
}); });
@ -699,7 +699,7 @@ class OverlayBackground implements OverlayBackgroundInterface {
styleSheetUrl: chrome.runtime.getURL(`overlay/${isOverlayListPort ? "list" : "button"}.css`), styleSheetUrl: chrome.runtime.getURL(`overlay/${isOverlayListPort ? "list" : "button"}.css`),
theme: await firstValueFrom(this.themeStateService.selectedTheme$), theme: await firstValueFrom(this.themeStateService.selectedTheme$),
translations: this.getTranslations(), translations: this.getTranslations(),
ciphers: isOverlayListPort ? this.getOverlayCipherData() : null, ciphers: isOverlayListPort ? await this.getOverlayCipherData() : null,
}); });
this.updateOverlayPosition({ this.updateOverlayPosition({
overlayElement: isOverlayListPort overlayElement: isOverlayListPort

View File

@ -6,6 +6,7 @@ import {
EventCollectionServiceInitOptions, EventCollectionServiceInitOptions,
eventCollectionServiceFactory, eventCollectionServiceFactory,
} from "../../../background/service-factories/event-collection-service.factory"; } from "../../../background/service-factories/event-collection-service.factory";
import { billingAccountProfileStateServiceFactory } from "../../../platform/background/service-factories/billing-account-profile-state-service.factory";
import { import {
CachedServices, CachedServices,
factory, factory,
@ -69,6 +70,7 @@ export function autofillServiceFactory(
await logServiceFactory(cache, opts), await logServiceFactory(cache, opts),
await domainSettingsServiceFactory(cache, opts), await domainSettingsServiceFactory(cache, opts),
await userVerificationServiceFactory(cache, opts), await userVerificationServiceFactory(cache, opts),
await billingAccountProfileStateServiceFactory(cache, opts),
), ),
); );
} }

View File

@ -3,6 +3,7 @@ import { of } from "rxjs";
import { NOOP_COMMAND_SUFFIX } from "@bitwarden/common/autofill/constants"; import { NOOP_COMMAND_SUFFIX } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
@ -18,6 +19,7 @@ describe("context-menu", () => {
let autofillSettingsService: MockProxy<AutofillSettingsServiceAbstraction>; let autofillSettingsService: MockProxy<AutofillSettingsServiceAbstraction>;
let i18nService: MockProxy<I18nService>; let i18nService: MockProxy<I18nService>;
let logService: MockProxy<LogService>; let logService: MockProxy<LogService>;
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
let removeAllSpy: jest.SpyInstance<void, [callback?: () => void]>; let removeAllSpy: jest.SpyInstance<void, [callback?: () => void]>;
let createSpy: jest.SpyInstance< let createSpy: jest.SpyInstance<
@ -32,6 +34,7 @@ describe("context-menu", () => {
autofillSettingsService = mock(); autofillSettingsService = mock();
i18nService = mock(); i18nService = mock();
logService = mock(); logService = mock();
billingAccountProfileStateService = mock();
removeAllSpy = jest removeAllSpy = jest
.spyOn(chrome.contextMenus, "removeAll") .spyOn(chrome.contextMenus, "removeAll")
@ -50,6 +53,7 @@ describe("context-menu", () => {
autofillSettingsService, autofillSettingsService,
i18nService, i18nService,
logService, logService,
billingAccountProfileStateService,
); );
autofillSettingsService.enableContextMenu$ = of(true); autofillSettingsService.enableContextMenu$ = of(true);
}); });
@ -66,7 +70,7 @@ describe("context-menu", () => {
}); });
it("has menu enabled, but does not have premium", async () => { it("has menu enabled, but does not have premium", async () => {
stateService.getCanAccessPremium.mockResolvedValue(false); billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false);
const createdMenu = await sut.init(); const createdMenu = await sut.init();
expect(createdMenu).toBeTruthy(); expect(createdMenu).toBeTruthy();
@ -74,7 +78,7 @@ describe("context-menu", () => {
}); });
it("has menu enabled and has premium", async () => { it("has menu enabled and has premium", async () => {
stateService.getCanAccessPremium.mockResolvedValue(true); billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);
const createdMenu = await sut.init(); const createdMenu = await sut.init();
expect(createdMenu).toBeTruthy(); expect(createdMenu).toBeTruthy();
@ -128,7 +132,7 @@ describe("context-menu", () => {
}); });
it("create entry for each cipher piece", async () => { it("create entry for each cipher piece", async () => {
stateService.getCanAccessPremium.mockResolvedValue(true); billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);
await sut.loadOptions("TEST_TITLE", "1", createCipher()); await sut.loadOptions("TEST_TITLE", "1", createCipher());
@ -137,7 +141,7 @@ describe("context-menu", () => {
}); });
it("creates a login/unlock item for each context menu action option when user is not authenticated", async () => { it("creates a login/unlock item for each context menu action option when user is not authenticated", async () => {
stateService.getCanAccessPremium.mockResolvedValue(true); billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);
await sut.loadOptions("TEST_TITLE", "NOOP"); await sut.loadOptions("TEST_TITLE", "NOOP");

View File

@ -17,6 +17,7 @@ import {
SEPARATOR_ID, SEPARATOR_ID,
} from "@bitwarden/common/autofill/constants"; } from "@bitwarden/common/autofill/constants";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
@ -27,6 +28,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { autofillSettingsServiceFactory } from "../../autofill/background/service_factories/autofill-settings-service.factory"; import { autofillSettingsServiceFactory } from "../../autofill/background/service_factories/autofill-settings-service.factory";
import { Account } from "../../models/account"; import { Account } from "../../models/account";
import { billingAccountProfileStateServiceFactory } from "../../platform/background/service-factories/billing-account-profile-state-service.factory";
import { CachedServices } from "../../platform/background/service-factories/factory-options"; import { CachedServices } from "../../platform/background/service-factories/factory-options";
import { import {
i18nServiceFactory, i18nServiceFactory,
@ -163,6 +165,7 @@ export class MainContextMenuHandler {
private autofillSettingsService: AutofillSettingsServiceAbstraction, private autofillSettingsService: AutofillSettingsServiceAbstraction,
private i18nService: I18nService, private i18nService: I18nService,
private logService: LogService, private logService: LogService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
) {} ) {}
static async mv3Create(cachedServices: CachedServices) { static async mv3Create(cachedServices: CachedServices) {
@ -184,6 +187,11 @@ export class MainContextMenuHandler {
stateServiceOptions: { stateServiceOptions: {
stateFactory: stateFactory, stateFactory: stateFactory,
}, },
platformUtilsServiceOptions: {
clipboardWriteCallback: () => Promise.resolve(),
biometricCallback: () => Promise.resolve(false),
win: self,
},
}; };
return new MainContextMenuHandler( return new MainContextMenuHandler(
@ -191,6 +199,7 @@ export class MainContextMenuHandler {
await autofillSettingsServiceFactory(cachedServices, serviceOptions), await autofillSettingsServiceFactory(cachedServices, serviceOptions),
await i18nServiceFactory(cachedServices, serviceOptions), await i18nServiceFactory(cachedServices, serviceOptions),
await logServiceFactory(cachedServices, serviceOptions), await logServiceFactory(cachedServices, serviceOptions),
await billingAccountProfileStateServiceFactory(cachedServices, serviceOptions),
); );
} }
@ -212,7 +221,10 @@ export class MainContextMenuHandler {
try { try {
for (const options of this.initContextMenuItems) { for (const options of this.initContextMenuItems) {
if (options.checkPremiumAccess && !(await this.stateService.getCanAccessPremium())) { if (
options.checkPremiumAccess &&
!(await firstValueFrom(this.billingAccountProfileStateService.hasPremiumFromAnySource$))
) {
continue; continue;
} }
@ -307,7 +319,9 @@ export class MainContextMenuHandler {
await createChildItem(COPY_USERNAME_ID); await createChildItem(COPY_USERNAME_ID);
} }
const canAccessPremium = await this.stateService.getCanAccessPremium(); const canAccessPremium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$,
);
if (canAccessPremium && (!cipher || !Utils.isNullOrEmpty(cipher.login?.totp))) { if (canAccessPremium && (!cipher || !Utils.isNullOrEmpty(cipher.login?.totp))) {
await createChildItem(COPY_VERIFICATION_CODE_ID); await createChildItem(COPY_VERIFICATION_CODE_ID);
} }

View File

@ -1,8 +1,15 @@
import AutofillOverlayButtonIframe from "./autofill-overlay-button-iframe"; import AutofillOverlayButtonIframe from "./autofill-overlay-button-iframe";
import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element";
describe("AutofillOverlayButtonIframe", () => { describe("AutofillOverlayButtonIframe", () => {
window.customElements.define("autofill-overlay-button-iframe", AutofillOverlayButtonIframe); window.customElements.define(
"autofill-overlay-button-iframe",
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayButtonIframe(this);
}
},
);
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@ -13,7 +20,7 @@ describe("AutofillOverlayButtonIframe", () => {
const iframe = document.querySelector("autofill-overlay-button-iframe"); const iframe = document.querySelector("autofill-overlay-button-iframe");
expect(iframe).toBeInstanceOf(AutofillOverlayButtonIframe); expect(iframe).toBeInstanceOf(HTMLElement);
expect(iframe).toBeInstanceOf(AutofillOverlayIframeElement); expect(iframe.shadowRoot).toBeDefined();
}); });
}); });

View File

@ -3,8 +3,9 @@ import { AutofillOverlayPort } from "../../utils/autofill-overlay.enum";
import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element"; import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element";
class AutofillOverlayButtonIframe extends AutofillOverlayIframeElement { class AutofillOverlayButtonIframe extends AutofillOverlayIframeElement {
constructor() { constructor(element: HTMLElement) {
super( super(
element,
"overlay/button.html", "overlay/button.html",
AutofillOverlayPort.Button, AutofillOverlayPort.Button,
{ {

View File

@ -4,7 +4,21 @@ import AutofillOverlayIframeService from "./autofill-overlay-iframe.service";
jest.mock("./autofill-overlay-iframe.service"); jest.mock("./autofill-overlay-iframe.service");
describe("AutofillOverlayIframeElement", () => { describe("AutofillOverlayIframeElement", () => {
window.customElements.define("autofill-overlay-iframe", AutofillOverlayIframeElement); window.customElements.define(
"autofill-overlay-iframe",
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayIframeElement(
this,
"overlay/button.html",
"overlay/button",
{ background: "transparent", border: "none" },
"bitwardenOverlayButton",
);
}
},
);
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();

View File

@ -1,16 +1,15 @@
import AutofillOverlayIframeService from "./autofill-overlay-iframe.service"; import AutofillOverlayIframeService from "./autofill-overlay-iframe.service";
class AutofillOverlayIframeElement extends HTMLElement { class AutofillOverlayIframeElement {
constructor( constructor(
element: HTMLElement,
iframePath: string, iframePath: string,
portName: string, portName: string,
initStyles: Partial<CSSStyleDeclaration>, initStyles: Partial<CSSStyleDeclaration>,
iframeTitle: string, iframeTitle: string,
ariaAlert?: string, ariaAlert?: string,
) { ) {
super(); const shadow: ShadowRoot = element.attachShadow({ mode: "closed" });
const shadow: ShadowRoot = this.attachShadow({ mode: "closed" });
const autofillOverlayIframeService = new AutofillOverlayIframeService( const autofillOverlayIframeService = new AutofillOverlayIframeService(
iframePath, iframePath,
portName, portName,

View File

@ -1,8 +1,15 @@
import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element";
import AutofillOverlayListIframe from "./autofill-overlay-list-iframe"; import AutofillOverlayListIframe from "./autofill-overlay-list-iframe";
describe("AutofillOverlayListIframe", () => { describe("AutofillOverlayListIframe", () => {
window.customElements.define("autofill-overlay-list-iframe", AutofillOverlayListIframe); window.customElements.define(
"autofill-overlay-list-iframe",
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayListIframe(this);
}
},
);
afterAll(() => { afterAll(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@ -13,7 +20,7 @@ describe("AutofillOverlayListIframe", () => {
const iframe = document.querySelector("autofill-overlay-list-iframe"); const iframe = document.querySelector("autofill-overlay-list-iframe");
expect(iframe).toBeInstanceOf(AutofillOverlayListIframe); expect(iframe).toBeInstanceOf(HTMLElement);
expect(iframe).toBeInstanceOf(AutofillOverlayIframeElement); expect(iframe.shadowRoot).toBeDefined();
}); });
}); });

View File

@ -3,8 +3,9 @@ import { AutofillOverlayPort } from "../../utils/autofill-overlay.enum";
import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element"; import AutofillOverlayIframeElement from "./autofill-overlay-iframe-element";
class AutofillOverlayListIframe extends AutofillOverlayIframeElement { class AutofillOverlayListIframe extends AutofillOverlayIframeElement {
constructor() { constructor(element: HTMLElement) {
super( super(
element,
"overlay/list.html", "overlay/list.html",
AutofillOverlayPort.List, AutofillOverlayPort.List,
{ {

View File

@ -877,6 +877,44 @@ describe("AutofillOverlayContentService", () => {
sender: "autofillOverlayContentService", sender: "autofillOverlayContentService",
}); });
}); });
it("builds the overlay elements as custom web components if the user's browser is not Firefox", () => {
let namesIndex = 0;
const customNames = ["op-autofill-overlay-button", "op-autofill-overlay-list"];
jest
.spyOn(autofillOverlayContentService as any, "generateRandomCustomElementName")
.mockImplementation(() => {
if (namesIndex > 1) {
return "";
}
const customName = customNames[namesIndex];
namesIndex++;
return customName;
});
autofillOverlayContentService["isFirefoxBrowser"] = false;
autofillOverlayContentService.openAutofillOverlay();
expect(autofillOverlayContentService["overlayButtonElement"]).toBeInstanceOf(HTMLElement);
expect(autofillOverlayContentService["overlayButtonElement"].tagName).toEqual(
customNames[0].toUpperCase(),
);
expect(autofillOverlayContentService["overlayListElement"]).toBeInstanceOf(HTMLElement);
expect(autofillOverlayContentService["overlayListElement"].tagName).toEqual(
customNames[1].toUpperCase(),
);
});
it("builds the overlay elements as `div` elements if the user's browser is Firefox", () => {
autofillOverlayContentService["isFirefoxBrowser"] = true;
autofillOverlayContentService.openAutofillOverlay();
expect(autofillOverlayContentService["overlayButtonElement"]).toBeInstanceOf(HTMLDivElement);
expect(autofillOverlayContentService["overlayListElement"]).toBeInstanceOf(HTMLDivElement);
});
}); });
describe("focusMostRecentOverlayField", () => { describe("focusMostRecentOverlayField", () => {

View File

@ -30,6 +30,10 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
isOverlayCiphersPopulated = false; isOverlayCiphersPopulated = false;
pageDetailsUpdateRequired = false; pageDetailsUpdateRequired = false;
autofillOverlayVisibility: number; autofillOverlayVisibility: number;
private isFirefoxBrowser =
globalThis.navigator.userAgent.indexOf(" Firefox/") !== -1 ||
globalThis.navigator.userAgent.indexOf(" Gecko/") !== -1;
private readonly generateRandomCustomElementName = generateRandomCustomElementName;
private readonly findTabs = tabbable; private readonly findTabs = tabbable;
private readonly sendExtensionMessage = sendExtensionMessage; private readonly sendExtensionMessage = sendExtensionMessage;
private formFieldElements: Set<ElementWithOpId<FormFieldElement>> = new Set([]); private formFieldElements: Set<ElementWithOpId<FormFieldElement>> = new Set([]);
@ -593,6 +597,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
private updateOverlayButtonPosition() { private updateOverlayButtonPosition() {
if (!this.overlayButtonElement) { if (!this.overlayButtonElement) {
this.createAutofillOverlayButton(); this.createAutofillOverlayButton();
this.updateCustomElementDefaultStyles(this.overlayButtonElement);
} }
if (!this.isOverlayButtonVisible) { if (!this.isOverlayButtonVisible) {
@ -613,6 +618,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
private updateOverlayListPosition() { private updateOverlayListPosition() {
if (!this.overlayListElement) { if (!this.overlayListElement) {
this.createAutofillOverlayList(); this.createAutofillOverlayList();
this.updateCustomElementDefaultStyles(this.overlayListElement);
} }
if (!this.isOverlayListVisible) { if (!this.isOverlayListVisible) {
@ -765,11 +771,24 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
return; return;
} }
const customElementName = generateRandomCustomElementName(); if (this.isFirefoxBrowser) {
globalThis.customElements?.define(customElementName, AutofillOverlayButtonIframe); this.overlayButtonElement = globalThis.document.createElement("div");
this.overlayButtonElement = globalThis.document.createElement(customElementName); new AutofillOverlayButtonIframe(this.overlayButtonElement);
this.updateCustomElementDefaultStyles(this.overlayButtonElement); return;
}
const customElementName = this.generateRandomCustomElementName();
globalThis.customElements?.define(
customElementName,
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayButtonIframe(this);
}
},
);
this.overlayButtonElement = globalThis.document.createElement(customElementName);
} }
/** /**
@ -781,11 +800,24 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
return; return;
} }
const customElementName = generateRandomCustomElementName(); if (this.isFirefoxBrowser) {
globalThis.customElements?.define(customElementName, AutofillOverlayListIframe); this.overlayListElement = globalThis.document.createElement("div");
this.overlayListElement = globalThis.document.createElement(customElementName); new AutofillOverlayListIframe(this.overlayListElement);
this.updateCustomElementDefaultStyles(this.overlayListElement); return;
}
const customElementName = this.generateRandomCustomElementName();
globalThis.customElements?.define(
customElementName,
class extends HTMLElement {
constructor() {
super();
new AutofillOverlayListIframe(this);
}
},
);
this.overlayListElement = globalThis.document.createElement(customElementName);
} }
/** /**

View File

@ -8,6 +8,7 @@ import {
DefaultDomainSettingsService, DefaultDomainSettingsService,
DomainSettingsService, DomainSettingsService,
} from "@bitwarden/common/autofill/services/domain-settings.service"; } from "@bitwarden/common/autofill/services/domain-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -72,6 +73,7 @@ describe("AutofillService", () => {
const eventCollectionService = mock<EventCollectionService>(); const eventCollectionService = mock<EventCollectionService>();
const logService = mock<LogService>(); const logService = mock<LogService>();
const userVerificationService = mock<UserVerificationService>(); const userVerificationService = mock<UserVerificationService>();
const billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
beforeEach(() => { beforeEach(() => {
autofillService = new AutofillService( autofillService = new AutofillService(
@ -83,6 +85,7 @@ describe("AutofillService", () => {
logService, logService,
domainSettingsService, domainSettingsService,
userVerificationService, userVerificationService,
billingAccountProfileStateService,
); );
domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider);
@ -476,6 +479,7 @@ describe("AutofillService", () => {
it("throws an error if an autofill did not occur for any of the passed pages", async () => { it("throws an error if an autofill did not occur for any of the passed pages", async () => {
autofillOptions.tab.url = "https://a-different-url.com"; autofillOptions.tab.url = "https://a-different-url.com";
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);
try { try {
await autofillService.doAutoFill(autofillOptions); await autofillService.doAutoFill(autofillOptions);
@ -487,7 +491,6 @@ describe("AutofillService", () => {
}); });
it("will autofill login data for a page", async () => { it("will autofill login data for a page", async () => {
jest.spyOn(stateService, "getCanAccessPremium");
jest.spyOn(autofillService as any, "generateFillScript"); jest.spyOn(autofillService as any, "generateFillScript");
jest.spyOn(autofillService as any, "generateLoginFillScript"); jest.spyOn(autofillService as any, "generateLoginFillScript");
jest.spyOn(logService, "info"); jest.spyOn(logService, "info");
@ -497,8 +500,6 @@ describe("AutofillService", () => {
const autofillResult = await autofillService.doAutoFill(autofillOptions); const autofillResult = await autofillService.doAutoFill(autofillOptions);
const currentAutofillPageDetails = autofillOptions.pageDetails[0]; const currentAutofillPageDetails = autofillOptions.pageDetails[0];
expect(stateService.getCanAccessPremium).toHaveBeenCalled();
expect(autofillService["getDefaultUriMatchStrategy"]).toHaveBeenCalled();
expect(autofillService["generateFillScript"]).toHaveBeenCalledWith( expect(autofillService["generateFillScript"]).toHaveBeenCalledWith(
currentAutofillPageDetails.details, currentAutofillPageDetails.details,
{ {
@ -660,7 +661,7 @@ describe("AutofillService", () => {
it("returns a TOTP value", async () => { it("returns a TOTP value", async () => {
const totpCode = "123456"; const totpCode = "123456";
autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.login.totp = "totp";
jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(true); billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);
jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true);
jest.spyOn(totpService, "getCode").mockResolvedValue(totpCode); jest.spyOn(totpService, "getCode").mockResolvedValue(totpCode);
@ -673,7 +674,7 @@ describe("AutofillService", () => {
it("does not return a TOTP value if the user does not have premium features", async () => { it("does not return a TOTP value if the user does not have premium features", async () => {
autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.login.totp = "totp";
jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(false); billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false);
jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(true);
const autofillResult = await autofillService.doAutoFill(autofillOptions); const autofillResult = await autofillService.doAutoFill(autofillOptions);
@ -707,7 +708,7 @@ describe("AutofillService", () => {
it("returns a null value if the user cannot access premium and the organization does not use TOTP", async () => { it("returns a null value if the user cannot access premium and the organization does not use TOTP", async () => {
autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.login.totp = "totp";
autofillOptions.cipher.organizationUseTotp = false; autofillOptions.cipher.organizationUseTotp = false;
jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValueOnce(false); billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false);
const autofillResult = await autofillService.doAutoFill(autofillOptions); const autofillResult = await autofillService.doAutoFill(autofillOptions);
@ -717,13 +718,12 @@ describe("AutofillService", () => {
it("returns a null value if the user has disabled `auto TOTP copy`", async () => { it("returns a null value if the user has disabled `auto TOTP copy`", async () => {
autofillOptions.cipher.login.totp = "totp"; autofillOptions.cipher.login.totp = "totp";
autofillOptions.cipher.organizationUseTotp = true; autofillOptions.cipher.organizationUseTotp = true;
jest.spyOn(stateService, "getCanAccessPremium").mockResolvedValue(true); billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);
jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(false); jest.spyOn(autofillService, "getShouldAutoCopyTotp").mockResolvedValue(false);
jest.spyOn(totpService, "getCode"); jest.spyOn(totpService, "getCode");
const autofillResult = await autofillService.doAutoFill(autofillOptions); const autofillResult = await autofillService.doAutoFill(autofillOptions);
expect(stateService.getCanAccessPremium).toHaveBeenCalled();
expect(autofillService.getShouldAutoCopyTotp).toHaveBeenCalled(); expect(autofillService.getShouldAutoCopyTotp).toHaveBeenCalled();
expect(totpService.getCode).not.toHaveBeenCalled(); expect(totpService.getCode).not.toHaveBeenCalled();
expect(autofillResult).toBeNull(); expect(autofillResult).toBeNull();
@ -3380,6 +3380,34 @@ describe("AutofillService", () => {
expect(value).toBe(false); expect(value).toBe(false);
}); });
it("validates attribute identifiers with mixed camel case and non-alpha characters", () => {
const attributes: Record<string, boolean> = {
_$1_go_look: true,
go_look: true,
goLook: true,
go1look: true,
"go look": true,
look_go: true,
findPerson: true,
query$1: true,
look_goo: false,
golook: false,
lookgo: false,
logonField: false,
ego_input: false,
"Gold Password": false,
searching_for: false,
person_finder: false,
};
const autofillFieldMocks = Object.keys(attributes).map((key) =>
createAutofillFieldMock({ htmlID: key }),
);
autofillFieldMocks.forEach((field) => {
const value = AutofillService["isSearchField"](field);
expect(value).toBe(attributes[field.htmlID]);
});
});
}); });
describe("isFieldMatch", () => { describe("isFieldMatch", () => {

View File

@ -5,6 +5,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { import {
UriMatchStrategySetting, UriMatchStrategySetting,
@ -44,6 +45,7 @@ export default class AutofillService implements AutofillServiceInterface {
private openPasswordRepromptPopoutDebounce: NodeJS.Timeout; private openPasswordRepromptPopoutDebounce: NodeJS.Timeout;
private currentlyOpeningPasswordRepromptPopout = false; private currentlyOpeningPasswordRepromptPopout = false;
private autofillScriptPortsSet = new Set<chrome.runtime.Port>(); private autofillScriptPortsSet = new Set<chrome.runtime.Port>();
static searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames);
constructor( constructor(
private cipherService: CipherService, private cipherService: CipherService,
@ -54,6 +56,7 @@ export default class AutofillService implements AutofillServiceInterface {
private logService: LogService, private logService: LogService,
private domainSettingsService: DomainSettingsService, private domainSettingsService: DomainSettingsService,
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
) {} ) {}
/** /**
@ -239,7 +242,9 @@ export default class AutofillService implements AutofillServiceInterface {
let totp: string | null = null; let totp: string | null = null;
const canAccessPremium = await this.stateService.getCanAccessPremium(); const canAccessPremium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$,
);
const defaultUriMatch = await this.getDefaultUriMatchStrategy(); const defaultUriMatch = await this.getDefaultUriMatchStrategy();
if (!canAccessPremium) { if (!canAccessPremium) {
@ -1380,11 +1385,33 @@ export default class AutofillService implements AutofillServiceInterface {
return excludedTypes.indexOf(type) > -1; return excludedTypes.indexOf(type) > -1;
} }
/**
* Identifies if a passed field contains text artifacts that identify it as a search field.
*
* @param field - The autofill field that we are validating as a search field
*/
private static isSearchField(field: AutofillField) { private static isSearchField(field: AutofillField) {
const matchFieldAttributeValues = [field.type, field.htmlName, field.htmlID, field.placeholder]; const matchFieldAttributeValues = [field.type, field.htmlName, field.htmlID, field.placeholder];
const matchPattern = new RegExp(AutoFillConstants.SearchFieldNames.join("|"), "gi"); for (let attrIndex = 0; attrIndex < matchFieldAttributeValues.length; attrIndex++) {
if (!matchFieldAttributeValues[attrIndex]) {
continue;
}
return Boolean(matchFieldAttributeValues.join(" ").match(matchPattern)); // Separate camel case words and case them to lower case values
const camelCaseSeparatedFieldAttribute = matchFieldAttributeValues[attrIndex]
.replace(/([a-z])([A-Z])/g, "$1 $2")
.toLowerCase();
// Split the attribute by non-alphabetical characters to get the keywords
const attributeKeywords = camelCaseSeparatedFieldAttribute.split(/[^a-z]/gi);
for (let keywordIndex = 0; keywordIndex < attributeKeywords.length; keywordIndex++) {
if (AutofillService.searchFieldNamesSet.has(attributeKeywords[keywordIndex])) {
return true;
}
}
}
return false;
} }
static isExcludedFieldType(field: AutofillField, excludedTypes: string[]) { static isExcludedFieldType(field: AutofillField, excludedTypes: string[]) {
@ -1397,11 +1424,7 @@ export default class AutofillService implements AutofillServiceInterface {
} }
// Check if the input is an untyped/mistyped search input // Check if the input is an untyped/mistyped search input
if (this.isSearchField(field)) { return this.isSearchField(field);
return true;
}
return false;
} }
/** /**
@ -1525,11 +1548,7 @@ export default class AutofillService implements AutofillServiceInterface {
return false; return false;
} }
if (AutoFillConstants.PasswordFieldExcludeList.some((i) => cleanedValue.indexOf(i) > -1)) { return !AutoFillConstants.PasswordFieldExcludeList.some((i) => cleanedValue.indexOf(i) > -1);
return false;
}
return true;
} }
static fieldHasDisqualifyingAttributeValue(field: AutofillField) { static fieldHasDisqualifyingAttributeValue(field: AutofillField) {
@ -1572,7 +1591,11 @@ export default class AutofillService implements AutofillServiceInterface {
const arr: AutofillField[] = []; const arr: AutofillField[] = [];
pageDetails.fields.forEach((f) => { pageDetails.fields.forEach((f) => {
if (AutofillService.isExcludedFieldType(f, AutoFillConstants.ExcludedAutofillLoginTypes)) { const isPassword = f.type === "password";
if (
!isPassword &&
AutofillService.isExcludedFieldType(f, AutoFillConstants.ExcludedAutofillLoginTypes)
) {
return; return;
} }
@ -1581,23 +1604,16 @@ export default class AutofillService implements AutofillServiceInterface {
return; return;
} }
const isPassword = f.type === "password";
const isLikePassword = () => { const isLikePassword = () => {
if (f.type !== "text") { if (f.type !== "text") {
return false; return false;
} }
if (AutofillService.valueIsLikePassword(f.htmlID)) { const testedValues = [f.htmlID, f.htmlName, f.placeholder];
for (let i = 0; i < testedValues.length; i++) {
if (AutofillService.valueIsLikePassword(testedValues[i])) {
return true; return true;
} }
if (AutofillService.valueIsLikePassword(f.htmlName)) {
return true;
}
if (AutofillService.valueIsLikePassword(f.placeholder)) {
return true;
} }
return false; return false;

View File

@ -755,6 +755,9 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
// Prioritize capturing text content from elements rather than nodes. // Prioritize capturing text content from elements rather than nodes.
currentElement = currentElement.parentElement || currentElement.parentNode; currentElement = currentElement.parentElement || currentElement.parentNode;
if (!currentElement) {
return textContentItems;
}
let siblingElement = nodeIsElement(currentElement) let siblingElement = nodeIsElement(currentElement)
? currentElement.previousElementSibling ? currentElement.previousElementSibling

View File

@ -553,17 +553,30 @@ describe("InsertAutofillContentService", () => {
insertAutofillContentService as any, insertAutofillContentService as any,
"simulateUserMouseClickAndFocusEventInteractions", "simulateUserMouseClickAndFocusEventInteractions",
); );
jest.spyOn(targetInput, "blur");
insertAutofillContentService["handleFocusOnFieldByOpidAction"]("__0"); insertAutofillContentService["handleFocusOnFieldByOpidAction"]("__0");
expect( expect(
insertAutofillContentService["collectAutofillContentService"].getAutofillFieldElementByOpid, insertAutofillContentService["collectAutofillContentService"].getAutofillFieldElementByOpid,
).toBeCalledWith("__0"); ).toBeCalledWith("__0");
expect(targetInput.blur).not.toHaveBeenCalled();
expect( expect(
insertAutofillContentService["simulateUserMouseClickAndFocusEventInteractions"], insertAutofillContentService["simulateUserMouseClickAndFocusEventInteractions"],
).toHaveBeenCalledWith(targetInput, true); ).toHaveBeenCalledWith(targetInput, true);
expect(elementEventCount).toEqual(expectedElementEventCount); expect(elementEventCount).toEqual(expectedElementEventCount);
}); });
it("blurs the element if it is currently the active element before simulating click and focus events", () => {
const targetInput = document.querySelector('input[type="text"]') as FormElementWithAttribute;
targetInput.opid = "__0";
targetInput.focus();
jest.spyOn(targetInput, "blur");
insertAutofillContentService["handleFocusOnFieldByOpidAction"]("__0");
expect(targetInput.blur).toHaveBeenCalled();
});
}); });
describe("insertValueIntoField", () => { describe("insertValueIntoField", () => {
@ -710,7 +723,7 @@ describe("InsertAutofillContentService", () => {
}); });
describe("triggerPostInsertEventsOnElement", () => { describe("triggerPostInsertEventsOnElement", () => {
it("triggers simulated event interactions and blurs the element after", () => { it("triggers simulated event interactions", () => {
const elementValue = "test"; const elementValue = "test";
document.body.innerHTML = `<input type="text" id="username" value="${elementValue}"/>`; document.body.innerHTML = `<input type="text" id="username" value="${elementValue}"/>`;
const element = document.getElementById("username") as FillableFormFieldElement; const element = document.getElementById("username") as FillableFormFieldElement;
@ -726,7 +739,6 @@ describe("InsertAutofillContentService", () => {
expect(insertAutofillContentService["simulateInputElementChangedEvent"]).toHaveBeenCalledWith( expect(insertAutofillContentService["simulateInputElementChangedEvent"]).toHaveBeenCalledWith(
element, element,
); );
expect(element.blur).toHaveBeenCalled();
expect(element.value).toBe(elementValue); expect(element.value).toBe(elementValue);
}); });
}); });

View File

@ -185,11 +185,18 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
/** /**
* Handles finding an element by opid and triggering click and focus events on the element. * Handles finding an element by opid and triggering click and focus events on the element.
* @param {string} opid * To ensure that we trigger a blur event correctly on a filled field, we first check if the
* @private * element is already focused. If it is, we blur the element before focusing on it again.
*
* @param {string} opid - The opid of the element to focus on.
*/ */
private handleFocusOnFieldByOpidAction(opid: string) { private handleFocusOnFieldByOpidAction(opid: string) {
const element = this.collectAutofillContentService.getAutofillFieldElementByOpid(opid); const element = this.collectAutofillContentService.getAutofillFieldElementByOpid(opid);
if (document.activeElement === element) {
element.blur();
}
this.simulateUserMouseClickAndFocusEventInteractions(element, true); this.simulateUserMouseClickAndFocusEventInteractions(element, true);
} }
@ -282,7 +289,6 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
} }
this.simulateInputElementChangedEvent(element); this.simulateInputElementChangedEvent(element);
element.blur();
} }
/** /**
@ -379,10 +385,6 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
element.dispatchEvent(new Event(simulatedInputEvents[index], { bubbles: true })); element.dispatchEvent(new Event(simulatedInputEvents[index], { bubbles: true }));
} }
} }
private nodeIsElement(node: Node): node is HTMLElement {
return node.nodeType === Node.ELEMENT_NODE;
}
} }
export default InsertAutofillContentService; export default InsertAutofillContentService;

View File

@ -8,19 +8,18 @@ import {
AuthRequestServiceAbstraction, AuthRequestServiceAbstraction,
AuthRequestService, AuthRequestService,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { AvatarUpdateService as AvatarUpdateServiceAbstraction } from "@bitwarden/common/abstractions/account/avatar-update.service";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service"; import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { InternalPolicyService as InternalPolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
@ -39,6 +38,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation"; import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation";
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
@ -64,6 +64,8 @@ import {
UserNotificationSettingsService, UserNotificationSettingsService,
UserNotificationSettingsServiceAbstraction, UserNotificationSettingsServiceAbstraction,
} from "@bitwarden/common/autofill/services/user-notification-settings.service"; } from "@bitwarden/common/autofill/services/user-notification-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@ -117,7 +119,6 @@ import { DefaultStateProvider } from "@bitwarden/common/platform/state/implement
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service"; import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
/* eslint-enable import/no-restricted-paths */ /* eslint-enable import/no-restricted-paths */
import { DefaultThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { DefaultThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
import { ApiService } from "@bitwarden/common/services/api.service"; import { ApiService } from "@bitwarden/common/services/api.service";
import { AuditService } from "@bitwarden/common/services/audit.service"; import { AuditService } from "@bitwarden/common/services/audit.service";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
@ -125,6 +126,7 @@ import { EventUploadService } from "@bitwarden/common/services/event/event-uploa
import { NotificationsService } from "@bitwarden/common/services/notifications.service"; import { NotificationsService } from "@bitwarden/common/services/notifications.service";
import { SearchService } from "@bitwarden/common/services/search.service"; import { SearchService } from "@bitwarden/common/services/search.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/avatar.service";
import { import {
PasswordGenerationService, PasswordGenerationService,
PasswordGenerationServiceAbstraction, PasswordGenerationServiceAbstraction,
@ -180,7 +182,6 @@ import {
VaultExportServiceAbstraction, VaultExportServiceAbstraction,
} from "@bitwarden/vault-export-core"; } from "@bitwarden/vault-export-core";
import { BrowserOrganizationService } from "../admin-console/services/browser-organization.service";
import ContextMenusBackground from "../autofill/background/context-menus.background"; import ContextMenusBackground from "../autofill/background/context-menus.background";
import NotificationBackground from "../autofill/background/notification.background"; import NotificationBackground from "../autofill/background/notification.background";
import OverlayBackground from "../autofill/background/overlay.background"; import OverlayBackground from "../autofill/background/overlay.background";
@ -211,7 +212,6 @@ import { BrowserPlatformUtilsService } from "../platform/services/platform-utils
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider"; import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service"; import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
import { BrowserSendService } from "../services/browser-send.service"; import { BrowserSendService } from "../services/browser-send.service";
import { BrowserSettingsService } from "../services/browser-settings.service";
import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service"; import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
import FilelessImporterBackground from "../tools/background/fileless-importer.background"; import FilelessImporterBackground from "../tools/background/fileless-importer.background";
import { BrowserFido2UserInterfaceService } from "../vault/fido2/browser-fido2-user-interface.service"; import { BrowserFido2UserInterfaceService } from "../vault/fido2/browser-fido2-user-interface.service";
@ -240,7 +240,6 @@ export default class MainBackground {
appIdService: AppIdServiceAbstraction; appIdService: AppIdServiceAbstraction;
apiService: ApiServiceAbstraction; apiService: ApiServiceAbstraction;
environmentService: BrowserEnvironmentService; environmentService: BrowserEnvironmentService;
settingsService: SettingsServiceAbstraction;
cipherService: CipherServiceAbstraction; cipherService: CipherServiceAbstraction;
folderService: InternalFolderServiceAbstraction; folderService: InternalFolderServiceAbstraction;
collectionService: CollectionServiceAbstraction; collectionService: CollectionServiceAbstraction;
@ -288,7 +287,7 @@ export default class MainBackground {
fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction; fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction;
fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction; fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction;
fido2ClientService: Fido2ClientServiceAbstraction; fido2ClientService: Fido2ClientServiceAbstraction;
avatarUpdateService: AvatarUpdateServiceAbstraction; avatarService: AvatarServiceAbstraction;
mainContextMenuHandler: MainContextMenuHandler; mainContextMenuHandler: MainContextMenuHandler;
cipherContextMenuHandler: CipherContextMenuHandler; cipherContextMenuHandler: CipherContextMenuHandler;
configService: BrowserConfigService; configService: BrowserConfigService;
@ -311,6 +310,7 @@ export default class MainBackground {
biometricStateService: BiometricStateService; biometricStateService: BiometricStateService;
stateEventRunnerService: StateEventRunnerService; stateEventRunnerService: StateEventRunnerService;
ssoLoginService: SsoLoginServiceAbstraction; ssoLoginService: SsoLoginServiceAbstraction;
billingAccountProfileStateService: BillingAccountProfileStateService;
onUpdatedRan: boolean; onUpdatedRan: boolean;
onReplacedRan: boolean; onReplacedRan: boolean;
@ -409,8 +409,7 @@ export default class MainBackground {
); );
this.activeUserStateProvider = new DefaultActiveUserStateProvider( this.activeUserStateProvider = new DefaultActiveUserStateProvider(
this.accountService, this.accountService,
storageServiceProvider, this.singleUserStateProvider,
stateEventRegistrarService,
); );
this.derivedStateProvider = new BackgroundDerivedStateProvider( this.derivedStateProvider = new BackgroundDerivedStateProvider(
this.memoryStorageForStateProviders, this.memoryStorageForStateProviders,
@ -428,6 +427,21 @@ export default class MainBackground {
); );
this.biometricStateService = new DefaultBiometricStateService(this.stateProvider); this.biometricStateService = new DefaultBiometricStateService(this.stateProvider);
this.userNotificationSettingsService = new UserNotificationSettingsService(this.stateProvider);
this.platformUtilsService = new BackgroundPlatformUtilsService(
this.messagingService,
(clipboardValue, clearMs) => this.clearClipboard(clipboardValue, clearMs),
async () => this.biometricUnlock(),
self,
);
this.tokenService = new TokenService(
this.singleUserStateProvider,
this.globalStateProvider,
this.platformUtilsService.supportsSecureStorage(),
this.secureStorageService,
);
const migrationRunner = new MigrationRunner( const migrationRunner = new MigrationRunner(
this.storageService, this.storageService,
this.logService, this.logService,
@ -442,15 +456,9 @@ export default class MainBackground {
new StateFactory(GlobalState, Account), new StateFactory(GlobalState, Account),
this.accountService, this.accountService,
this.environmentService, this.environmentService,
this.tokenService,
migrationRunner, migrationRunner,
); );
this.userNotificationSettingsService = new UserNotificationSettingsService(this.stateProvider);
this.platformUtilsService = new BackgroundPlatformUtilsService(
this.messagingService,
(clipboardValue, clearMs) => this.clearClipboard(clipboardValue, clearMs),
async () => this.biometricUnlock(),
self,
);
const themeStateService = new DefaultThemeStateService(this.globalStateProvider); const themeStateService = new DefaultThemeStateService(this.globalStateProvider);
@ -466,17 +474,17 @@ export default class MainBackground {
this.stateProvider, this.stateProvider,
this.biometricStateService, this.biometricStateService,
); );
this.tokenService = new TokenService(this.stateService);
this.appIdService = new AppIdService(this.globalStateProvider); this.appIdService = new AppIdService(this.globalStateProvider);
this.apiService = new ApiService( this.apiService = new ApiService(
this.tokenService, this.tokenService,
this.platformUtilsService, this.platformUtilsService,
this.environmentService, this.environmentService,
this.appIdService, this.appIdService,
this.stateService,
(expired: boolean) => this.logout(expired), (expired: boolean) => this.logout(expired),
); );
this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider); this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider);
this.settingsService = new BrowserSettingsService(this.stateService);
this.fileUploadService = new FileUploadService(this.logService); this.fileUploadService = new FileUploadService(this.logService);
this.cipherFileUploadService = new CipherFileUploadService( this.cipherFileUploadService = new CipherFileUploadService(
this.apiService, this.apiService,
@ -490,10 +498,7 @@ export default class MainBackground {
this.stateProvider, this.stateProvider,
); );
this.syncNotifierService = new SyncNotifierService(); this.syncNotifierService = new SyncNotifierService();
this.organizationService = new BrowserOrganizationService( this.organizationService = new OrganizationService(this.stateProvider);
this.stateService,
this.stateProvider,
);
this.policyService = new PolicyService(this.stateProvider, this.organizationService); this.policyService = new PolicyService(this.stateProvider, this.organizationService);
this.autofillSettingsService = new AutofillSettingsService( this.autofillSettingsService = new AutofillSettingsService(
this.stateProvider, this.stateProvider,
@ -563,6 +568,10 @@ export default class MainBackground {
this.stateService, this.stateService,
); );
this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService(
this.activeUserStateProvider,
);
this.loginStrategyService = new LoginStrategyService( this.loginStrategyService = new LoginStrategyService(
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
@ -582,6 +591,7 @@ export default class MainBackground {
this.deviceTrustCryptoService, this.deviceTrustCryptoService,
this.authRequestService, this.authRequestService,
this.globalStateProvider, this.globalStateProvider,
this.billingAccountProfileStateService,
); );
this.ssoLoginService = new SsoLoginService(this.stateProvider); this.ssoLoginService = new SsoLoginService(this.stateProvider);
@ -685,7 +695,11 @@ export default class MainBackground {
this.fileUploadService, this.fileUploadService,
this.sendService, this.sendService,
); );
this.avatarService = new AvatarService(this.apiService, this.stateProvider);
this.providerService = new ProviderService(this.stateProvider); this.providerService = new ProviderService(this.stateProvider);
this.syncService = new SyncService( this.syncService = new SyncService(
this.apiService, this.apiService,
this.domainSettingsService, this.domainSettingsService,
@ -703,18 +717,22 @@ export default class MainBackground {
this.folderApiService, this.folderApiService,
this.organizationService, this.organizationService,
this.sendApiService, this.sendApiService,
this.avatarService,
logoutCallback, logoutCallback,
this.billingAccountProfileStateService,
); );
this.eventUploadService = new EventUploadService( this.eventUploadService = new EventUploadService(
this.apiService, this.apiService,
this.stateService, this.stateProvider,
this.logService, this.logService,
this.accountService,
); );
this.eventCollectionService = new EventCollectionService( this.eventCollectionService = new EventCollectionService(
this.cipherService, this.cipherService,
this.stateService, this.stateProvider,
this.organizationService, this.organizationService,
this.eventUploadService, this.eventUploadService,
this.accountService,
); );
this.totpService = new TotpService(this.cryptoFunctionService, this.logService); this.totpService = new TotpService(this.cryptoFunctionService, this.logService);
@ -727,6 +745,7 @@ export default class MainBackground {
this.logService, this.logService,
this.domainSettingsService, this.domainSettingsService,
this.userVerificationService, this.userVerificationService,
this.billingAccountProfileStateService,
); );
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService); this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
@ -787,7 +806,6 @@ export default class MainBackground {
this.fido2AuthenticatorService, this.fido2AuthenticatorService,
this.configService, this.configService,
this.authService, this.authService,
this.stateService,
this.vaultSettingsService, this.vaultSettingsService,
this.domainSettingsService, this.domainSettingsService,
this.logService, this.logService,
@ -835,7 +853,6 @@ export default class MainBackground {
this.cryptoService, this.cryptoService,
this.cryptoFunctionService, this.cryptoFunctionService,
this.runtimeBackground, this.runtimeBackground,
this.i18nService,
this.messagingService, this.messagingService,
this.appIdService, this.appIdService,
this.platformUtilsService, this.platformUtilsService,
@ -869,7 +886,7 @@ export default class MainBackground {
this.autofillService, this.autofillService,
this.authService, this.authService,
this.environmentService, this.environmentService,
this.settingsService, this.domainSettingsService,
this.stateService, this.stateService,
this.autofillSettingsService, this.autofillSettingsService,
this.i18nService, this.i18nService,
@ -943,14 +960,13 @@ export default class MainBackground {
this.apiService, this.apiService,
); );
this.avatarUpdateService = new AvatarUpdateService(this.apiService, this.stateService);
if (!this.popupOnlyContext) { if (!this.popupOnlyContext) {
this.mainContextMenuHandler = new MainContextMenuHandler( this.mainContextMenuHandler = new MainContextMenuHandler(
this.stateService, this.stateService,
this.autofillSettingsService, this.autofillSettingsService,
this.i18nService, this.i18nService,
this.logService, this.logService,
this.billingAccountProfileStateService,
); );
this.cipherContextMenuHandler = new CipherContextMenuHandler( this.cipherContextMenuHandler = new CipherContextMenuHandler(
@ -1090,7 +1106,7 @@ export default class MainBackground {
async logout(expired: boolean, userId?: UserId) { async logout(expired: boolean, userId?: UserId) {
userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id; userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.eventUploadService.uploadEvents(userId); await this.eventUploadService.uploadEvents(userId as UserId);
await Promise.all([ await Promise.all([
this.syncService.setLastSync(new Date(0), userId), this.syncService.setLastSync(new Date(0), userId),

View File

@ -5,7 +5,6 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -75,7 +74,6 @@ export class NativeMessagingBackground {
private cryptoService: CryptoService, private cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService, private cryptoFunctionService: CryptoFunctionService,
private runtimeBackground: RuntimeBackground, private runtimeBackground: RuntimeBackground,
private i18nService: I18nService,
private messagingService: MessagingService, private messagingService: MessagingService,
private appIdService: AppIdService, private appIdService: AppIdService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,

View File

@ -97,6 +97,10 @@ export default class RuntimeBackground {
case "unlocked": { case "unlocked": {
let item: LockedVaultPendingNotificationsData; let item: LockedVaultPendingNotificationsData;
if (msg.command === "loggedIn") {
await this.sendBwInstalledMessageToVault();
}
if (this.lockedVaultPendingNotifications?.length > 0) { if (this.lockedVaultPendingNotifications?.length > 0) {
item = this.lockedVaultPendingNotifications.pop(); item = this.lockedVaultPendingNotifications.pop();
await closeUnlockPopout(); await closeUnlockPopout();
@ -130,9 +134,6 @@ export default class RuntimeBackground {
await this.main.refreshBadge(); await this.main.refreshBadge();
await this.main.refreshMenu(); await this.main.refreshMenu();
}, 2000); }, 2000);
// 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.main.avatarUpdateService.loadColorFromState();
this.configService.triggerServerConfigFetch(); this.configService.triggerServerConfigFetch();
} }
break; break;
@ -354,8 +355,6 @@ export default class RuntimeBackground {
if (await this.environmentService.hasManagedEnvironment()) { if (await this.environmentService.hasManagedEnvironment()) {
await this.environmentService.setUrlsToManagedEnvironment(); await this.environmentService.setUrlsToManagedEnvironment();
} }
await this.sendBwInstalledMessageToVault();
} }
this.onInstalledReason = null; this.onInstalledReason = null;

View File

@ -5,15 +5,14 @@ import {
organizationServiceFactory, organizationServiceFactory,
OrganizationServiceInitOptions, OrganizationServiceInitOptions,
} from "../../admin-console/background/service-factories/organization-service.factory"; } from "../../admin-console/background/service-factories/organization-service.factory";
import { accountServiceFactory } from "../../auth/background/service-factories/account-service.factory";
import { import {
FactoryOptions, FactoryOptions,
CachedServices, CachedServices,
factory, factory,
} from "../../platform/background/service-factories/factory-options"; } from "../../platform/background/service-factories/factory-options";
import { import { stateProviderFactory } from "../../platform/background/service-factories/state-provider.factory";
stateServiceFactory, import { StateServiceInitOptions } from "../../platform/background/service-factories/state-service.factory";
StateServiceInitOptions,
} from "../../platform/background/service-factories/state-service.factory";
import { import {
cipherServiceFactory, cipherServiceFactory,
CipherServiceInitOptions, CipherServiceInitOptions,
@ -43,9 +42,10 @@ export function eventCollectionServiceFactory(
async () => async () =>
new EventCollectionService( new EventCollectionService(
await cipherServiceFactory(cache, opts), await cipherServiceFactory(cache, opts),
await stateServiceFactory(cache, opts), await stateProviderFactory(cache, opts),
await organizationServiceFactory(cache, opts), await organizationServiceFactory(cache, opts),
await eventUploadServiceFactory(cache, opts), await eventUploadServiceFactory(cache, opts),
await accountServiceFactory(cache, opts),
), ),
); );
} }

View File

@ -1,6 +1,7 @@
import { EventUploadService as AbstractEventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; import { EventUploadService as AbstractEventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { accountServiceFactory } from "../../auth/background/service-factories/account-service.factory";
import { import {
ApiServiceInitOptions, ApiServiceInitOptions,
apiServiceFactory, apiServiceFactory,
@ -14,10 +15,8 @@ import {
logServiceFactory, logServiceFactory,
LogServiceInitOptions, LogServiceInitOptions,
} from "../../platform/background/service-factories/log-service.factory"; } from "../../platform/background/service-factories/log-service.factory";
import { import { stateProviderFactory } from "../../platform/background/service-factories/state-provider.factory";
stateServiceFactory, import { StateServiceInitOptions } from "../../platform/background/service-factories/state-service.factory";
StateServiceInitOptions,
} from "../../platform/background/service-factories/state-service.factory";
type EventUploadServiceOptions = FactoryOptions; type EventUploadServiceOptions = FactoryOptions;
@ -37,8 +36,9 @@ export function eventUploadServiceFactory(
async () => async () =>
new EventUploadService( new EventUploadService(
await apiServiceFactory(cache, opts), await apiServiceFactory(cache, opts),
await stateServiceFactory(cache, opts), await stateProviderFactory(cache, opts),
await logServiceFactory(cache, opts), await logServiceFactory(cache, opts),
await accountServiceFactory(cache, opts),
), ),
); );
} }

View File

@ -1,28 +0,0 @@
import { SettingsService as AbstractSettingsService } from "@bitwarden/common/abstractions/settings.service";
import {
FactoryOptions,
CachedServices,
factory,
} from "../../platform/background/service-factories/factory-options";
import {
stateServiceFactory,
StateServiceInitOptions,
} from "../../platform/background/service-factories/state-service.factory";
import { BrowserSettingsService } from "../../services/browser-settings.service";
type SettingsServiceFactoryOptions = FactoryOptions;
export type SettingsServiceInitOptions = SettingsServiceFactoryOptions & StateServiceInitOptions;
export function settingsServiceFactory(
cache: { settingsService?: AbstractSettingsService } & CachedServices,
opts: SettingsServiceInitOptions,
): Promise<AbstractSettingsService> {
return factory(
cache,
"settingsService",
opts,
async () => new BrowserSettingsService(await stateServiceFactory(cache, opts)),
);
}

View File

@ -9,20 +9,15 @@ import {
import { CachedServices, FactoryOptions, factory } from "./factory-options"; import { CachedServices, FactoryOptions, factory } from "./factory-options";
import { import {
StateEventRegistrarServiceInitOptions, SingleUserStateProviderInitOptions,
stateEventRegistrarServiceFactory, singleUserStateProviderFactory,
} from "./state-event-registrar-service.factory"; } from "./single-user-state-provider.factory";
import {
StorageServiceProviderInitOptions,
storageServiceProviderFactory,
} from "./storage-service-provider.factory";
type ActiveUserStateProviderFactory = FactoryOptions; type ActiveUserStateProviderFactory = FactoryOptions;
export type ActiveUserStateProviderInitOptions = ActiveUserStateProviderFactory & export type ActiveUserStateProviderInitOptions = ActiveUserStateProviderFactory &
AccountServiceInitOptions & AccountServiceInitOptions &
StorageServiceProviderInitOptions & SingleUserStateProviderInitOptions;
StateEventRegistrarServiceInitOptions;
export async function activeUserStateProviderFactory( export async function activeUserStateProviderFactory(
cache: { activeUserStateProvider?: ActiveUserStateProvider } & CachedServices, cache: { activeUserStateProvider?: ActiveUserStateProvider } & CachedServices,
@ -35,8 +30,7 @@ export async function activeUserStateProviderFactory(
async () => async () =>
new DefaultActiveUserStateProvider( new DefaultActiveUserStateProvider(
await accountServiceFactory(cache, opts), await accountServiceFactory(cache, opts),
await storageServiceProviderFactory(cache, opts), await singleUserStateProviderFactory(cache, opts),
await stateEventRegistrarServiceFactory(cache, opts),
), ),
); );
} }

View File

@ -20,6 +20,7 @@ import {
PlatformUtilsServiceInitOptions, PlatformUtilsServiceInitOptions,
platformUtilsServiceFactory, platformUtilsServiceFactory,
} from "./platform-utils-service.factory"; } from "./platform-utils-service.factory";
import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory";
type ApiServiceFactoryOptions = FactoryOptions & { type ApiServiceFactoryOptions = FactoryOptions & {
apiServiceOptions: { apiServiceOptions: {
@ -32,7 +33,8 @@ export type ApiServiceInitOptions = ApiServiceFactoryOptions &
TokenServiceInitOptions & TokenServiceInitOptions &
PlatformUtilsServiceInitOptions & PlatformUtilsServiceInitOptions &
EnvironmentServiceInitOptions & EnvironmentServiceInitOptions &
AppIdServiceInitOptions; AppIdServiceInitOptions &
StateServiceInitOptions;
export function apiServiceFactory( export function apiServiceFactory(
cache: { apiService?: AbstractApiService } & CachedServices, cache: { apiService?: AbstractApiService } & CachedServices,
@ -48,6 +50,7 @@ export function apiServiceFactory(
await platformUtilsServiceFactory(cache, opts), await platformUtilsServiceFactory(cache, opts),
await environmentServiceFactory(cache, opts), await environmentServiceFactory(cache, opts),
await appIdServiceFactory(cache, opts), await appIdServiceFactory(cache, opts),
await stateServiceFactory(cache, opts),
opts.apiServiceOptions.logoutCallback, opts.apiServiceOptions.logoutCallback,
opts.apiServiceOptions.customUserAgent, opts.apiServiceOptions.customUserAgent,
), ),

View File

@ -0,0 +1,28 @@
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { activeUserStateProviderFactory } from "./active-user-state-provider.factory";
import { FactoryOptions, CachedServices, factory } from "./factory-options";
import { StateProviderInitOptions } from "./state-provider.factory";
type BillingAccountProfileStateServiceFactoryOptions = FactoryOptions;
export type BillingAccountProfileStateServiceInitOptions =
BillingAccountProfileStateServiceFactoryOptions & StateProviderInitOptions;
export function billingAccountProfileStateServiceFactory(
cache: {
billingAccountProfileStateService?: BillingAccountProfileStateService;
} & CachedServices,
opts: BillingAccountProfileStateServiceInitOptions,
): Promise<BillingAccountProfileStateService> {
return factory(
cache,
"billingAccountProfileStateService",
opts,
async () =>
new DefaultBillingAccountProfileStateService(
await activeUserStateProviderFactory(cache, opts),
),
);
}

View File

@ -5,6 +5,10 @@ import {
accountServiceFactory, accountServiceFactory,
AccountServiceInitOptions, AccountServiceInitOptions,
} from "../../../auth/background/service-factories/account-service.factory"; } from "../../../auth/background/service-factories/account-service.factory";
import {
tokenServiceFactory,
TokenServiceInitOptions,
} from "../../../auth/background/service-factories/token-service.factory";
import { Account } from "../../../models/account"; import { Account } from "../../../models/account";
import { BrowserStateService } from "../../services/browser-state.service"; import { BrowserStateService } from "../../services/browser-state.service";
@ -38,6 +42,7 @@ export type StateServiceInitOptions = StateServiceFactoryOptions &
LogServiceInitOptions & LogServiceInitOptions &
AccountServiceInitOptions & AccountServiceInitOptions &
EnvironmentServiceInitOptions & EnvironmentServiceInitOptions &
TokenServiceInitOptions &
MigrationRunnerInitOptions; MigrationRunnerInitOptions;
export async function stateServiceFactory( export async function stateServiceFactory(
@ -57,6 +62,7 @@ export async function stateServiceFactory(
opts.stateServiceOptions.stateFactory, opts.stateServiceOptions.stateFactory,
await accountServiceFactory(cache, opts), await accountServiceFactory(cache, opts),
await environmentServiceFactory(cache, opts), await environmentServiceFactory(cache, opts),
await tokenServiceFactory(cache, opts),
await migrationRunnerFactory(cache, opts), await migrationRunnerFactory(cache, opts),
opts.stateServiceOptions.useAccountCache, opts.stateServiceOptions.useAccountCache,
), ),

View File

@ -7,6 +7,7 @@ import localeBn from "@angular/common/locales/bn";
import localeBs from "@angular/common/locales/bs"; import localeBs from "@angular/common/locales/bs";
import localeCa from "@angular/common/locales/ca"; import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs"; import localeCs from "@angular/common/locales/cs";
import localeCy from "@angular/common/locales/cy";
import localeDa from "@angular/common/locales/da"; import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de"; import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el"; import localeEl from "@angular/common/locales/el";
@ -19,6 +20,7 @@ import localeFa from "@angular/common/locales/fa";
import localeFi from "@angular/common/locales/fi"; import localeFi from "@angular/common/locales/fi";
import localeFil from "@angular/common/locales/fil"; import localeFil from "@angular/common/locales/fil";
import localeFr from "@angular/common/locales/fr"; import localeFr from "@angular/common/locales/fr";
import localeGl from "@angular/common/locales/gl";
import localeHe from "@angular/common/locales/he"; import localeHe from "@angular/common/locales/he";
import localeHi from "@angular/common/locales/hi"; import localeHi from "@angular/common/locales/hi";
import localeHr from "@angular/common/locales/hr"; import localeHr from "@angular/common/locales/hr";
@ -33,9 +35,13 @@ import localeKo from "@angular/common/locales/ko";
import localeLt from "@angular/common/locales/lt"; import localeLt from "@angular/common/locales/lt";
import localeLv from "@angular/common/locales/lv"; import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml"; import localeMl from "@angular/common/locales/ml";
import localeMr from "@angular/common/locales/mr";
import localeMy from "@angular/common/locales/my";
import localeNb from "@angular/common/locales/nb"; import localeNb from "@angular/common/locales/nb";
import localeNe from "@angular/common/locales/ne";
import localeNl from "@angular/common/locales/nl"; import localeNl from "@angular/common/locales/nl";
import localeNn from "@angular/common/locales/nn"; import localeNn from "@angular/common/locales/nn";
import localeOr from "@angular/common/locales/or";
import localePl from "@angular/common/locales/pl"; import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt"; import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT"; import localePtPt from "@angular/common/locales/pt-PT";
@ -46,6 +52,7 @@ import localeSk from "@angular/common/locales/sk";
import localeSl from "@angular/common/locales/sl"; import localeSl from "@angular/common/locales/sl";
import localeSr from "@angular/common/locales/sr"; import localeSr from "@angular/common/locales/sr";
import localeSv from "@angular/common/locales/sv"; import localeSv from "@angular/common/locales/sv";
import localeTe from "@angular/common/locales/te";
import localeTh from "@angular/common/locales/th"; import localeTh from "@angular/common/locales/th";
import localeTr from "@angular/common/locales/tr"; import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk"; import localeUk from "@angular/common/locales/uk";
@ -61,6 +68,7 @@ registerLocaleData(localeBn, "bn");
registerLocaleData(localeBs, "bs"); registerLocaleData(localeBs, "bs");
registerLocaleData(localeCa, "ca"); registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs"); registerLocaleData(localeCs, "cs");
registerLocaleData(localeCy, "cy");
registerLocaleData(localeDa, "da"); registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de"); registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el"); registerLocaleData(localeEl, "el");
@ -73,6 +81,7 @@ registerLocaleData(localeFa, "fa");
registerLocaleData(localeFi, "fi"); registerLocaleData(localeFi, "fi");
registerLocaleData(localeFil, "fil"); registerLocaleData(localeFil, "fil");
registerLocaleData(localeFr, "fr"); registerLocaleData(localeFr, "fr");
registerLocaleData(localeGl, "gl");
registerLocaleData(localeHe, "he"); registerLocaleData(localeHe, "he");
registerLocaleData(localeHi, "hi"); registerLocaleData(localeHi, "hi");
registerLocaleData(localeHr, "hr"); registerLocaleData(localeHr, "hr");
@ -87,9 +96,13 @@ registerLocaleData(localeKo, "ko");
registerLocaleData(localeLt, "lt"); registerLocaleData(localeLt, "lt");
registerLocaleData(localeLv, "lv"); registerLocaleData(localeLv, "lv");
registerLocaleData(localeMl, "ml"); registerLocaleData(localeMl, "ml");
registerLocaleData(localeMr, "mr");
registerLocaleData(localeMy, "my");
registerLocaleData(localeNb, "nb"); registerLocaleData(localeNb, "nb");
registerLocaleData(localeNe, "ne");
registerLocaleData(localeNl, "nl"); registerLocaleData(localeNl, "nl");
registerLocaleData(localeNn, "nn"); registerLocaleData(localeNn, "nn");
registerLocaleData(localeOr, "or");
registerLocaleData(localePl, "pl"); registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR"); registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT"); registerLocaleData(localePtPt, "pt-PT");
@ -100,6 +113,7 @@ registerLocaleData(localeSk, "sk");
registerLocaleData(localeSl, "sl"); registerLocaleData(localeSl, "sl");
registerLocaleData(localeSr, "sr"); registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv"); registerLocaleData(localeSv, "sv");
registerLocaleData(localeTe, "te");
registerLocaleData(localeTh, "th"); registerLocaleData(localeTh, "th");
registerLocaleData(localeTr, "tr"); registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk"); registerLocaleData(localeUk, "uk");

View File

@ -1,5 +1,6 @@
import { mock, MockProxy } from "jest-mock-extended"; import { mock, MockProxy } from "jest-mock-extended";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { import {
@ -32,6 +33,7 @@ describe("Browser State Service", () => {
let stateFactory: MockProxy<StateFactory<GlobalState, Account>>; let stateFactory: MockProxy<StateFactory<GlobalState, Account>>;
let useAccountCache: boolean; let useAccountCache: boolean;
let environmentService: MockProxy<EnvironmentService>; let environmentService: MockProxy<EnvironmentService>;
let tokenService: MockProxy<TokenService>;
let migrationRunner: MockProxy<MigrationRunner>; let migrationRunner: MockProxy<MigrationRunner>;
let state: State<GlobalState, Account>; let state: State<GlobalState, Account>;
@ -46,6 +48,7 @@ describe("Browser State Service", () => {
logService = mock(); logService = mock();
stateFactory = mock(); stateFactory = mock();
environmentService = mock(); environmentService = mock();
tokenService = mock();
migrationRunner = mock(); migrationRunner = mock();
// turn off account cache for tests // turn off account cache for tests
useAccountCache = false; useAccountCache = false;
@ -77,6 +80,7 @@ describe("Browser State Service", () => {
stateFactory, stateFactory,
accountService, accountService,
environmentService, environmentService,
tokenService,
migrationRunner, migrationRunner,
useAccountCache, useAccountCache,
); );

View File

@ -1,6 +1,7 @@
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { import {
@ -45,6 +46,7 @@ export class BrowserStateService
stateFactory: StateFactory<GlobalState, Account>, stateFactory: StateFactory<GlobalState, Account>,
accountService: AccountService, accountService: AccountService,
environmentService: EnvironmentService, environmentService: EnvironmentService,
tokenService: TokenService,
migrationRunner: MigrationRunner, migrationRunner: MigrationRunner,
useAccountCache = true, useAccountCache = true,
) { ) {
@ -56,6 +58,7 @@ export class BrowserStateService
stateFactory, stateFactory,
accountService, accountService,
environmentService, environmentService,
tokenService,
migrationRunner, migrationRunner,
useAccountCache, useAccountCache,
); );

View File

@ -17,11 +17,8 @@ import {
LoginStrategyServiceAbstraction, LoginStrategyServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service"; import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@ -42,6 +39,10 @@ import {
AutofillSettingsService, AutofillSettingsService,
AutofillSettingsServiceAbstraction, AutofillSettingsServiceAbstraction,
} from "@bitwarden/common/autofill/services/autofill-settings.service"; } from "@bitwarden/common/autofill/services/autofill-settings.service";
import {
DefaultDomainSettingsService,
DomainSettingsService,
} from "@bitwarden/common/autofill/services/domain-settings.service";
import { import {
UserNotificationSettingsService, UserNotificationSettingsService,
UserNotificationSettingsServiceAbstraction, UserNotificationSettingsServiceAbstraction,
@ -98,7 +99,6 @@ import { DialogService } from "@bitwarden/components";
import { ImportServiceAbstraction } from "@bitwarden/importer/core"; import { ImportServiceAbstraction } from "@bitwarden/importer/core";
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
import { BrowserOrganizationService } from "../../admin-console/services/browser-organization.service";
import { UnauthGuardService } from "../../auth/popup/services"; import { UnauthGuardService } from "../../auth/popup/services";
import { AutofillService } from "../../autofill/services/abstractions/autofill.service"; import { AutofillService } from "../../autofill/services/abstractions/autofill.service";
import MainBackground from "../../background/main.background"; import MainBackground from "../../background/main.background";
@ -118,7 +118,6 @@ import { ForegroundPlatformUtilsService } from "../../platform/services/platform
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider"; import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service"; import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
import { BrowserSendService } from "../../services/browser-send.service"; import { BrowserSendService } from "../../services/browser-send.service";
import { BrowserSettingsService } from "../../services/browser-settings.service";
import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service"; import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service";
import { VaultFilterService } from "../../vault/services/vault-filter.service"; import { VaultFilterService } from "../../vault/services/vault-filter.service";
@ -233,7 +232,6 @@ function getBgService<T>(service: keyof MainBackground) {
deps: [], deps: [],
}, },
{ provide: TotpService, useFactory: getBgService<TotpService>("totpService"), deps: [] }, { provide: TotpService, useFactory: getBgService<TotpService>("totpService"), deps: [] },
{ provide: TokenService, useFactory: getBgService<TokenService>("tokenService"), deps: [] },
{ {
provide: I18nServiceAbstraction, provide: I18nServiceAbstraction,
useFactory: (globalStateProvider: GlobalStateProvider) => { useFactory: (globalStateProvider: GlobalStateProvider) => {
@ -265,16 +263,6 @@ function getBgService<T>(service: keyof MainBackground) {
useFactory: getBgService<DevicesServiceAbstraction>("devicesService"), useFactory: getBgService<DevicesServiceAbstraction>("devicesService"),
deps: [], deps: [],
}, },
{
provide: EventUploadService,
useFactory: getBgService<EventUploadService>("eventUploadService"),
deps: [],
},
{
provide: EventCollectionService,
useFactory: getBgService<EventCollectionService>("eventCollectionService"),
deps: [],
},
{ {
provide: PlatformUtilsService, provide: PlatformUtilsService,
useExisting: ForegroundPlatformUtilsService, useExisting: ForegroundPlatformUtilsService,
@ -348,11 +336,9 @@ function getBgService<T>(service: keyof MainBackground) {
}, },
{ provide: SyncService, useFactory: getBgService<SyncService>("syncService"), deps: [] }, { provide: SyncService, useFactory: getBgService<SyncService>("syncService"), deps: [] },
{ {
provide: SettingsService, provide: DomainSettingsService,
useFactory: (stateService: StateServiceAbstraction) => { useClass: DefaultDomainSettingsService,
return new BrowserSettingsService(stateService); deps: [StateProvider],
},
deps: [StateServiceAbstraction],
}, },
{ {
provide: AbstractStorageService, provide: AbstractStorageService,
@ -399,13 +385,6 @@ function getBgService<T>(service: keyof MainBackground) {
useFactory: getBgService<NotificationsService>("notificationsService"), useFactory: getBgService<NotificationsService>("notificationsService"),
deps: [], deps: [],
}, },
{
provide: OrganizationService,
useFactory: (stateService: StateServiceAbstraction, stateProvider: StateProvider) => {
return new BrowserOrganizationService(stateService, stateProvider);
},
deps: [StateServiceAbstraction, StateProvider],
},
{ {
provide: VaultFilterService, provide: VaultFilterService,
useClass: VaultFilterService, useClass: VaultFilterService,
@ -445,6 +424,7 @@ function getBgService<T>(service: keyof MainBackground) {
logService: LogServiceAbstraction, logService: LogServiceAbstraction,
accountService: AccountServiceAbstraction, accountService: AccountServiceAbstraction,
environmentService: EnvironmentService, environmentService: EnvironmentService,
tokenService: TokenService,
migrationRunner: MigrationRunner, migrationRunner: MigrationRunner,
) => { ) => {
return new BrowserStateService( return new BrowserStateService(
@ -455,6 +435,7 @@ function getBgService<T>(service: keyof MainBackground) {
new StateFactory(GlobalState, Account), new StateFactory(GlobalState, Account),
accountService, accountService,
environmentService, environmentService,
tokenService,
migrationRunner, migrationRunner,
); );
}, },
@ -465,6 +446,7 @@ function getBgService<T>(service: keyof MainBackground) {
LogServiceAbstraction, LogServiceAbstraction,
AccountServiceAbstraction, AccountServiceAbstraction,
EnvironmentService, EnvironmentService,
TokenService,
MigrationRunner, MigrationRunner,
], ],
}, },

View File

@ -6,7 +6,6 @@ import { DomainSettingsService } from "@bitwarden/common/autofill/services/domai
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { BrowserApi } from "../../platform/browser/browser-api"; import { BrowserApi } from "../../platform/browser/browser-api";
@ -31,7 +30,6 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
accountSwitcherEnabled = false; accountSwitcherEnabled = false;
constructor( constructor(
private stateService: StateService,
private domainSettingsService: DomainSettingsService, private domainSettingsService: DomainSettingsService,
private i18nService: I18nService, private i18nService: I18nService,
private router: Router, private router: Router,

View File

@ -1,7 +1,6 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
@ -13,7 +12,6 @@ import {
} from "@bitwarden/common/models/domain/domain-service"; } from "@bitwarden/common/models/domain/domain-service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeType } from "@bitwarden/common/platform/enums";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
@ -51,14 +49,12 @@ export class OptionsComponent implements OnInit {
constructor( constructor(
private messagingService: MessagingService, private messagingService: MessagingService,
private stateService: StateService,
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
private autofillSettingsService: AutofillSettingsServiceAbstraction, private autofillSettingsService: AutofillSettingsServiceAbstraction,
private domainSettingsService: DomainSettingsService, private domainSettingsService: DomainSettingsService,
private badgeSettingsService: BadgeSettingsServiceAbstraction, private badgeSettingsService: BadgeSettingsServiceAbstraction,
i18nService: I18nService, i18nService: I18nService,
private themeStateService: ThemeStateService, private themeStateService: ThemeStateService,
private settingsService: SettingsService,
private vaultSettingsService: VaultSettingsService, private vaultSettingsService: VaultSettingsService,
) { ) {
this.themeOptions = [ this.themeOptions = [
@ -114,12 +110,14 @@ export class OptionsComponent implements OnInit {
this.autofillSettingsService.enableContextMenu$, this.autofillSettingsService.enableContextMenu$,
); );
this.showCardsCurrentTab = !(await this.stateService.getDontShowCardsCurrentTab()); this.showCardsCurrentTab = await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$);
this.showIdentitiesCurrentTab = !(await this.stateService.getDontShowIdentitiesCurrentTab()); this.showIdentitiesCurrentTab = await firstValueFrom(
this.vaultSettingsService.showIdentitiesCurrentTab$,
);
this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$); this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$);
this.enableFavicon = !this.settingsService.getDisableFavicon(); this.enableFavicon = await firstValueFrom(this.domainSettingsService.showFavicons$);
this.enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$); this.enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$);
@ -169,7 +167,7 @@ export class OptionsComponent implements OnInit {
} }
async updateFavicon() { async updateFavicon() {
await this.settingsService.setDisableFavicon(!this.enableFavicon); await this.domainSettingsService.setShowFavicons(this.enableFavicon);
} }
async updateBadgeCounter() { async updateBadgeCounter() {
@ -178,11 +176,11 @@ export class OptionsComponent implements OnInit {
} }
async updateShowCardsCurrentTab() { async updateShowCardsCurrentTab() {
await this.stateService.setDontShowCardsCurrentTab(!this.showCardsCurrentTab); await this.vaultSettingsService.setShowCardsCurrentTab(this.showCardsCurrentTab);
} }
async updateShowIdentitiesCurrentTab() { async updateShowIdentitiesCurrentTab() {
await this.stateService.setDontShowIdentitiesCurrentTab(!this.showIdentitiesCurrentTab); await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab);
} }
async saveTheme() { async saveTheme() {

View File

@ -12,7 +12,7 @@
</header> </header>
<main tabindex="-1"> <main tabindex="-1">
<div class="content"> <div class="content">
<ng-container *ngIf="!isPremium"> <ng-container *ngIf="!(isPremium$ | async)">
<p class="text-center lead">{{ "premiumNotCurrentMember" | i18n }}</p> <p class="text-center lead">{{ "premiumNotCurrentMember" | i18n }}</p>
<p>{{ "premiumSignUpAndGet" | i18n }}</p> <p>{{ "premiumSignUpAndGet" | i18n }}</p>
<ul class="bwi-ul"> <ul class="bwi-ul">
@ -61,7 +61,7 @@
></i> ></i>
</button> </button>
</ng-container> </ng-container>
<ng-container *ngIf="isPremium"> <ng-container *ngIf="isPremium$ | async">
<p class="text-center lead">{{ "premiumCurrentMember" | i18n }}</p> <p class="text-center lead">{{ "premiumCurrentMember" | i18n }}</p>
<p class="text-center">{{ "premiumCurrentMemberThanks" | i18n }}</p> <p class="text-center">{{ "premiumCurrentMemberThanks" | i18n }}</p>
<button type="button" class="btn block primary" (click)="manage()"> <button type="button" class="btn block primary" (click)="manage()">

View File

@ -3,6 +3,7 @@ import { Component } from "@angular/core";
import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component"; import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -27,6 +28,7 @@ export class PremiumComponent extends BasePremiumComponent {
private currencyPipe: CurrencyPipe, private currencyPipe: CurrencyPipe,
dialogService: DialogService, dialogService: DialogService,
environmentService: EnvironmentService, environmentService: EnvironmentService,
billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
i18nService, i18nService,
@ -36,6 +38,7 @@ export class PremiumComponent extends BasePremiumComponent {
stateService, stateService,
dialogService, dialogService,
environmentService, environmentService,
billingAccountProfileStateService,
); );
// Support old price string. Can be removed in future once all translations are properly updated. // Support old price string. Can be removed in future once all translations are properly updated.

View File

@ -16,7 +16,6 @@ import {
takeUntil, takeUntil,
} from "rxjs"; } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { FingerprintDialogComponent } from "@bitwarden/auth/angular"; import { FingerprintDialogComponent } from "@bitwarden/auth/angular";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
@ -98,7 +97,6 @@ export class SettingsComponent implements OnInit {
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private stateService: StateService, private stateService: StateService,
private modalService: ModalService,
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
private dialogService: DialogService, private dialogService: DialogService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,

View File

@ -1,11 +0,0 @@
import { BehaviorSubject } from "rxjs";
import { SettingsService } from "@bitwarden/common/services/settings.service";
import { browserSession, sessionSync } from "../platform/decorators/session-sync-observable";
@browserSession
export class BrowserSettingsService extends SettingsService {
@sessionSync({ initializer: (b: boolean) => b })
protected _disableFavicon: BehaviorSubject<boolean>;
}

View File

@ -6,6 +6,7 @@ import { first } from "rxjs/operators";
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component"; import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -49,6 +50,7 @@ export class SendAddEditComponent extends BaseAddEditComponent {
dialogService: DialogService, dialogService: DialogService,
formBuilder: FormBuilder, formBuilder: FormBuilder,
private filePopoutUtilsService: FilePopoutUtilsService, private filePopoutUtilsService: FilePopoutUtilsService,
billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
i18nService, i18nService,
@ -63,6 +65,7 @@ export class SendAddEditComponent extends BaseAddEditComponent {
sendApiService, sendApiService,
dialogService, dialogService,
formBuilder, formBuilder,
billingAccountProfileStateService,
); );
} }

View File

@ -1,10 +1,11 @@
import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
@ -15,7 +16,7 @@ import { PasswordRepromptService } from "@bitwarden/vault";
selector: "app-action-buttons", selector: "app-action-buttons",
templateUrl: "action-buttons.component.html", templateUrl: "action-buttons.component.html",
}) })
export class ActionButtonsComponent { export class ActionButtonsComponent implements OnInit, OnDestroy {
@Output() onView = new EventEmitter<CipherView>(); @Output() onView = new EventEmitter<CipherView>();
@Output() launchEvent = new EventEmitter<CipherView>(); @Output() launchEvent = new EventEmitter<CipherView>();
@Input() cipher: CipherView; @Input() cipher: CipherView;
@ -24,17 +25,28 @@ export class ActionButtonsComponent {
cipherType = CipherType; cipherType = CipherType;
userHasPremiumAccess = false; userHasPremiumAccess = false;
private componentIsDestroyed$ = new Subject<boolean>();
constructor( constructor(
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private eventCollectionService: EventCollectionService, private eventCollectionService: EventCollectionService,
private totpService: TotpService, private totpService: TotpService,
private stateService: StateService,
private passwordRepromptService: PasswordRepromptService, private passwordRepromptService: PasswordRepromptService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
) {} ) {}
async ngOnInit() { ngOnInit() {
this.userHasPremiumAccess = await this.stateService.getCanAccessPremium(); this.billingAccountProfileStateService.hasPremiumFromAnySource$
.pipe(takeUntil(this.componentIsDestroyed$))
.subscribe((canAccessPremium: boolean) => {
this.userHasPremiumAccess = canAccessPremium;
});
}
ngOnDestroy() {
this.componentIsDestroyed$.next(true);
this.componentIsDestroyed$.complete();
} }
launchCipher() { launchCipher() {

View File

@ -6,7 +6,6 @@ import { firstValueFrom } from "rxjs";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { BrowserFido2UserInterfaceSession } from "../../../fido2/browser-fido2-user-interface.service"; import { BrowserFido2UserInterfaceSession } from "../../../fido2/browser-fido2-user-interface.service";
@ -52,7 +51,6 @@ export class Fido2UseBrowserLinkComponent {
protected fido2PopoutSessionData$ = fido2PopoutSessionData$(); protected fido2PopoutSessionData$ = fido2PopoutSessionData$();
constructor( constructor(
private stateService: StateService,
private domainSettingsService: DomainSettingsService, private domainSettingsService: DomainSettingsService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService, private i18nService: I18nService,

View File

@ -5,6 +5,7 @@ import { first } from "rxjs/operators";
import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -34,6 +35,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
logService: LogService, logService: LogService,
fileDownloadService: FileDownloadService, fileDownloadService: FileDownloadService,
dialogService: DialogService, dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
cipherService, cipherService,
@ -46,6 +48,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
stateService, stateService,
fileDownloadService, fileDownloadService,
dialogService, dialogService,
billingAccountProfileStateService,
); );
} }

View File

@ -10,10 +10,10 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@ -65,11 +65,11 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private syncService: SyncService, private syncService: SyncService,
private searchService: SearchService, private searchService: SearchService,
private stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction, private autofillSettingsService: AutofillSettingsServiceAbstraction,
private passwordRepromptService: PasswordRepromptService, private passwordRepromptService: PasswordRepromptService,
private organizationService: OrganizationService, private organizationService: OrganizationService,
private vaultFilterService: VaultFilterService, private vaultFilterService: VaultFilterService,
private vaultSettingsService: VaultSettingsService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -271,9 +271,11 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
}); });
const otherTypes: CipherType[] = []; const otherTypes: CipherType[] = [];
const dontShowCards = await this.stateService.getDontShowCardsCurrentTab(); const dontShowCards = !(await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$));
const dontShowIdentities = await this.stateService.getDontShowIdentitiesCurrentTab(); const dontShowIdentities = !(await firstValueFrom(
this.showOrganizations = this.organizationService.hasOrganizations(); this.vaultSettingsService.showIdentitiesCurrentTab$,
));
this.showOrganizations = await this.organizationService.hasOrganizations();
if (!dontShowCards) { if (!dontShowCards) {
otherTypes.push(CipherType.Card); otherTypes.push(CipherType.Card);
} }

View File

@ -74,7 +74,7 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn
async ngOnInit() { async ngOnInit() {
this.searchTypeSearch = !this.platformUtilsService.isSafari(); this.searchTypeSearch = !this.platformUtilsService.isSafari();
this.showOrganizations = this.organizationService.hasOrganizations(); this.showOrganizations = await this.organizationService.hasOrganizations();
this.vaultFilter = this.vaultFilterService.getVaultFilter(); this.vaultFilter = this.vaultFilterService.getVaultFilter();
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (params) => { this.route.queryParams.pipe(first()).subscribe(async (params) => {

View File

@ -9,6 +9,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
@ -95,6 +96,7 @@ export class ViewComponent extends BaseViewComponent {
fileDownloadService: FileDownloadService, fileDownloadService: FileDownloadService,
dialogService: DialogService, dialogService: DialogService,
datePipe: DatePipe, datePipe: DatePipe,
billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
cipherService, cipherService,
@ -117,6 +119,7 @@ export class ViewComponent extends BaseViewComponent {
fileDownloadService, fileDownloadService,
dialogService, dialogService,
datePipe, datePipe,
billingAccountProfileStateService,
); );
} }

View File

@ -203,6 +203,7 @@ export class LoginCommand {
ssoCodeVerifier, ssoCodeVerifier,
this.ssoRedirectUri, this.ssoRedirectUri,
orgIdentifier, orgIdentifier,
undefined, // email to look up 2FA token not required as CLI can't remember 2FA token
twoFactor, twoFactor,
), ),
); );

View File

@ -23,10 +23,12 @@ import { PolicyApiService } from "@bitwarden/common/admin-console/services/polic
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation"; import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
@ -39,6 +41,8 @@ import {
DefaultDomainSettingsService, DefaultDomainSettingsService,
DomainSettingsService, DomainSettingsService,
} from "@bitwarden/common/autofill/services/domain-settings.service"; } from "@bitwarden/common/autofill/services/domain-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { ClientType } from "@bitwarden/common/enums"; import { ClientType } from "@bitwarden/common/enums";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
@ -86,7 +90,6 @@ import { AuditService } from "@bitwarden/common/services/audit.service";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { SearchService } from "@bitwarden/common/services/search.service"; import { SearchService } from "@bitwarden/common/services/search.service";
import { SettingsService } from "@bitwarden/common/services/settings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
import { import {
@ -155,7 +158,6 @@ export class Main {
appIdService: AppIdService; appIdService: AppIdService;
apiService: NodeApiService; apiService: NodeApiService;
environmentService: EnvironmentService; environmentService: EnvironmentService;
settingsService: SettingsService;
cipherService: CipherService; cipherService: CipherService;
folderService: InternalFolderService; folderService: InternalFolderService;
organizationUserService: OrganizationUserService; organizationUserService: OrganizationUserService;
@ -216,8 +218,10 @@ export class Main {
derivedStateProvider: DerivedStateProvider; derivedStateProvider: DerivedStateProvider;
stateProvider: StateProvider; stateProvider: StateProvider;
loginStrategyService: LoginStrategyServiceAbstraction; loginStrategyService: LoginStrategyServiceAbstraction;
avatarService: AvatarServiceAbstraction;
stateEventRunnerService: StateEventRunnerService; stateEventRunnerService: StateEventRunnerService;
biometricStateService: BiometricStateService; biometricStateService: BiometricStateService;
billingAccountProfileStateService: BillingAccountProfileStateService;
constructor() { constructor() {
let p = null; let p = null;
@ -291,8 +295,7 @@ export class Main {
this.activeUserStateProvider = new DefaultActiveUserStateProvider( this.activeUserStateProvider = new DefaultActiveUserStateProvider(
this.accountService, this.accountService,
storageServiceProvider, this.singleUserStateProvider,
stateEventRegistrarService,
); );
this.derivedStateProvider = new DefaultDerivedStateProvider( this.derivedStateProvider = new DefaultDerivedStateProvider(
@ -308,6 +311,13 @@ export class Main {
this.environmentService = new EnvironmentService(this.stateProvider, this.accountService); this.environmentService = new EnvironmentService(this.stateProvider, this.accountService);
this.tokenService = new TokenService(
this.singleUserStateProvider,
this.globalStateProvider,
this.platformUtilsService.supportsSecureStorage(),
this.secureStorageService,
);
const migrationRunner = new MigrationRunner( const migrationRunner = new MigrationRunner(
this.storageService, this.storageService,
this.logService, this.logService,
@ -322,6 +332,7 @@ export class Main {
new StateFactory(GlobalState, Account), new StateFactory(GlobalState, Account),
this.accountService, this.accountService,
this.environmentService, this.environmentService,
this.tokenService,
migrationRunner, migrationRunner,
); );
@ -339,7 +350,6 @@ export class Main {
); );
this.appIdService = new AppIdService(this.globalStateProvider); this.appIdService = new AppIdService(this.globalStateProvider);
this.tokenService = new TokenService(this.stateService);
const customUserAgent = const customUserAgent =
"Bitwarden_CLI/" + "Bitwarden_CLI/" +
@ -352,6 +362,7 @@ export class Main {
this.platformUtilsService, this.platformUtilsService,
this.environmentService, this.environmentService,
this.appIdService, this.appIdService,
this.stateService,
async (expired: boolean) => await this.logout(), async (expired: boolean) => await this.logout(),
customUserAgent, customUserAgent,
); );
@ -362,7 +373,6 @@ export class Main {
this.containerService = new ContainerService(this.cryptoService, this.encryptService); this.containerService = new ContainerService(this.cryptoService, this.encryptService);
this.settingsService = new SettingsService(this.stateService);
this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider); this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider);
this.fileUploadService = new FileUploadService(this.logService); this.fileUploadService = new FileUploadService(this.logService);
@ -397,7 +407,7 @@ export class Main {
this.providerService = new ProviderService(this.stateProvider); this.providerService = new ProviderService(this.stateProvider);
this.organizationService = new OrganizationService(this.stateService, this.stateProvider); this.organizationService = new OrganizationService(this.stateProvider);
this.organizationUserService = new OrganizationUserServiceImplementation(this.apiService); this.organizationUserService = new OrganizationUserServiceImplementation(this.apiService);
@ -446,6 +456,10 @@ export class Main {
this.stateService, this.stateService,
); );
this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService(
this.activeUserStateProvider,
);
this.loginStrategyService = new LoginStrategyService( this.loginStrategyService = new LoginStrategyService(
this.cryptoService, this.cryptoService,
this.apiService, this.apiService,
@ -465,6 +479,7 @@ export class Main {
this.deviceTrustCryptoService, this.deviceTrustCryptoService,
this.authRequestService, this.authRequestService,
this.globalStateProvider, this.globalStateProvider,
this.billingAccountProfileStateService,
); );
this.authService = new AuthService( this.authService = new AuthService(
@ -555,6 +570,8 @@ export class Main {
null, null,
); );
this.avatarService = new AvatarService(this.apiService, this.stateProvider);
this.syncService = new SyncService( this.syncService = new SyncService(
this.apiService, this.apiService,
this.domainSettingsService, this.domainSettingsService,
@ -572,7 +589,9 @@ export class Main {
this.folderApiService, this.folderApiService,
this.organizationService, this.organizationService,
this.sendApiService, this.sendApiService,
this.avatarService,
async (expired: boolean) => await this.logout(), async (expired: boolean) => await this.logout(),
this.billingAccountProfileStateService,
); );
this.totpService = new TotpService(this.cryptoFunctionService, this.logService); this.totpService = new TotpService(this.cryptoFunctionService, this.logService);
@ -619,15 +638,17 @@ export class Main {
this.eventUploadService = new EventUploadService( this.eventUploadService = new EventUploadService(
this.apiService, this.apiService,
this.stateService, this.stateProvider,
this.logService, this.logService,
this.accountService,
); );
this.eventCollectionService = new EventCollectionService( this.eventCollectionService = new EventCollectionService(
this.cipherService, this.cipherService,
this.stateService, this.stateProvider,
this.organizationService, this.organizationService,
this.eventUploadService, this.eventUploadService,
this.accountService,
); );
} }
@ -651,6 +672,7 @@ export class Main {
}); });
const userId = await this.stateService.getUserId(); const userId = await this.stateService.getUserId();
await Promise.all([ await Promise.all([
this.eventUploadService.uploadEvents(userId as UserId),
this.syncService.setLastSync(new Date(0)), this.syncService.setLastSync(new Date(0)),
this.cryptoService.clearKeys(), this.cryptoService.clearKeys(),
this.cipherService.clear(userId), this.cipherService.clear(userId),

View File

@ -1,9 +1,12 @@
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { CardExport } from "@bitwarden/common/models/export/card.export"; import { CardExport } from "@bitwarden/common/models/export/card.export";
import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
@ -57,6 +60,7 @@ export class GetCommand extends DownloadCommand {
private apiService: ApiService, private apiService: ApiService,
private organizationService: OrganizationService, private organizationService: OrganizationService,
private eventCollectionService: EventCollectionService, private eventCollectionService: EventCollectionService,
private accountProfileService: BillingAccountProfileStateService,
) { ) {
super(cryptoService); super(cryptoService);
} }
@ -251,7 +255,9 @@ export class GetCommand extends DownloadCommand {
return Response.error("Couldn't generate TOTP code."); return Response.error("Couldn't generate TOTP code.");
} }
const canAccessPremium = await this.stateService.getCanAccessPremium(); const canAccessPremium = await firstValueFrom(
this.accountProfileService.hasPremiumFromAnySource$,
);
if (!canAccessPremium) { if (!canAccessPremium) {
const originalCipher = await this.cipherService.get(cipher.id); const originalCipher = await this.cipherService.get(cipher.id);
if ( if (
@ -334,7 +340,10 @@ export class GetCommand extends DownloadCommand {
return Response.multipleResults(attachments.map((a) => a.id)); return Response.multipleResults(attachments.map((a) => a.id));
} }
if (!(await this.stateService.getCanAccessPremium())) { const canAccessPremium = await firstValueFrom(
this.accountProfileService.hasPremiumFromAnySource$,
);
if (!canAccessPremium) {
const originalCipher = await this.cipherService.get(cipher.id); const originalCipher = await this.cipherService.get(cipher.id);
if (originalCipher == null || originalCipher.organizationId == null) { if (originalCipher == null || originalCipher.organizationId == null) {
return Response.error("Premium status is required to use this feature."); return Response.error("Premium status is required to use this feature.");

View File

@ -68,6 +68,7 @@ export class ServeCommand {
this.main.apiService, this.main.apiService,
this.main.organizationService, this.main.organizationService,
this.main.eventCollectionService, this.main.eventCollectionService,
this.main.billingAccountProfileStateService,
); );
this.listCommand = new ListCommand( this.listCommand = new ListCommand(
this.main.cipherService, this.main.cipherService,
@ -82,10 +83,10 @@ export class ServeCommand {
this.createCommand = new CreateCommand( this.createCommand = new CreateCommand(
this.main.cipherService, this.main.cipherService,
this.main.folderService, this.main.folderService,
this.main.stateService,
this.main.cryptoService, this.main.cryptoService,
this.main.apiService, this.main.apiService,
this.main.folderApiService, this.main.folderApiService,
this.main.billingAccountProfileStateService,
); );
this.editCommand = new EditCommand( this.editCommand = new EditCommand(
this.main.cipherService, this.main.cipherService,
@ -108,9 +109,9 @@ export class ServeCommand {
this.deleteCommand = new DeleteCommand( this.deleteCommand = new DeleteCommand(
this.main.cipherService, this.main.cipherService,
this.main.folderService, this.main.folderService,
this.main.stateService,
this.main.apiService, this.main.apiService,
this.main.folderApiService, this.main.folderApiService,
this.main.billingAccountProfileStateService,
); );
this.confirmCommand = new ConfirmCommand( this.confirmCommand = new ConfirmCommand(
this.main.apiService, this.main.apiService,
@ -135,9 +136,9 @@ export class ServeCommand {
this.sendCreateCommand = new SendCreateCommand( this.sendCreateCommand = new SendCreateCommand(
this.main.sendService, this.main.sendService,
this.main.stateService,
this.main.environmentService, this.main.environmentService,
this.main.sendApiService, this.main.sendApiService,
this.main.billingAccountProfileStateService,
); );
this.sendDeleteCommand = new SendDeleteCommand(this.main.sendService, this.main.sendApiService); this.sendDeleteCommand = new SendDeleteCommand(this.main.sendService, this.main.sendApiService);
this.sendGetCommand = new SendGetCommand( this.sendGetCommand = new SendGetCommand(
@ -148,9 +149,9 @@ export class ServeCommand {
); );
this.sendEditCommand = new SendEditCommand( this.sendEditCommand = new SendEditCommand(
this.main.sendService, this.main.sendService,
this.main.stateService,
this.sendGetCommand, this.sendGetCommand,
this.main.sendApiService, this.main.sendApiService,
this.main.billingAccountProfileStateService,
); );
this.sendListCommand = new SendListCommand( this.sendListCommand = new SendListCommand(
this.main.sendService, this.main.sendService,

View File

@ -6,6 +6,7 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { ApiService } from "@bitwarden/common/services/api.service"; import { ApiService } from "@bitwarden/common/services/api.service";
(global as any).fetch = fe.default; (global as any).fetch = fe.default;
@ -20,6 +21,7 @@ export class NodeApiService extends ApiService {
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService, environmentService: EnvironmentService,
appIdService: AppIdService, appIdService: AppIdService,
stateService: StateService,
logoutCallback: (expired: boolean) => Promise<void>, logoutCallback: (expired: boolean) => Promise<void>,
customUserAgent: string = null, customUserAgent: string = null,
) { ) {
@ -28,6 +30,7 @@ export class NodeApiService extends ApiService {
platformUtilsService, platformUtilsService,
environmentService, environmentService,
appIdService, appIdService,
stateService,
logoutCallback, logoutCallback,
customUserAgent, customUserAgent,
); );

View File

@ -1,6 +1,9 @@
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DefaultPassphraseGenerationOptions } from "@bitwarden/common/tools/generator/passphrase"; import { DefaultPassphraseGenerationOptions } from "@bitwarden/common/tools/generator/passphrase";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import {
DefaultPasswordGenerationOptions,
PasswordGenerationServiceAbstraction,
} from "@bitwarden/common/tools/generator/password";
import { PasswordGeneratorOptions } from "@bitwarden/common/tools/generator/password/password-generator-options"; import { PasswordGeneratorOptions } from "@bitwarden/common/tools/generator/password/password-generator-options";
import { Response } from "../models/response"; import { Response } from "../models/response";
@ -64,7 +67,10 @@ class Options {
this.capitalize = CliUtils.convertBooleanOption(passedOptions?.capitalize); this.capitalize = CliUtils.convertBooleanOption(passedOptions?.capitalize);
this.includeNumber = CliUtils.convertBooleanOption(passedOptions?.includeNumber); this.includeNumber = CliUtils.convertBooleanOption(passedOptions?.includeNumber);
this.ambiguous = CliUtils.convertBooleanOption(passedOptions?.ambiguous); this.ambiguous = CliUtils.convertBooleanOption(passedOptions?.ambiguous);
this.length = CliUtils.convertNumberOption(passedOptions?.length, 14); this.length = CliUtils.convertNumberOption(
passedOptions?.length,
DefaultPasswordGenerationOptions.length,
);
this.type = passedOptions?.passphrase ? "passphrase" : "password"; this.type = passedOptions?.passphrase ? "passphrase" : "password";
this.separator = CliUtils.convertStringOption( this.separator = CliUtils.convertStringOption(
passedOptions?.separator, passedOptions?.separator,
@ -74,8 +80,14 @@ class Options {
passedOptions?.words, passedOptions?.words,
DefaultPassphraseGenerationOptions.numWords, DefaultPassphraseGenerationOptions.numWords,
); );
this.minNumber = CliUtils.convertNumberOption(passedOptions?.minNumber, 1); this.minNumber = CliUtils.convertNumberOption(
this.minSpecial = CliUtils.convertNumberOption(passedOptions?.minSpecial, 1); passedOptions?.minNumber,
DefaultPasswordGenerationOptions.minNumber,
);
this.minSpecial = CliUtils.convertNumberOption(
passedOptions?.minSpecial,
DefaultPasswordGenerationOptions.minSpecial,
);
if (!this.uppercase && !this.lowercase && !this.special && !this.number) { if (!this.uppercase && !this.lowercase && !this.special && !this.number) {
this.lowercase = true; this.lowercase = true;

View File

@ -1,8 +1,10 @@
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import { firstValueFrom } from "rxjs";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
@ -16,9 +18,9 @@ import { SendResponse } from "../models/send.response";
export class SendCreateCommand { export class SendCreateCommand {
constructor( constructor(
private sendService: SendService, private sendService: SendService,
private stateService: StateService,
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private sendApiService: SendApiService, private sendApiService: SendApiService,
private accountProfileService: BillingAccountProfileStateService,
) {} ) {}
async run(requestJson: any, cmdOptions: Record<string, any>) { async run(requestJson: any, cmdOptions: Record<string, any>) {
@ -82,7 +84,7 @@ export class SendCreateCommand {
); );
} }
if (!(await this.stateService.getCanAccessPremium())) { if (!(await firstValueFrom(this.accountProfileService.hasPremiumFromAnySource$))) {
return Response.error("Premium status is required to use this feature."); return Response.error("Premium status is required to use this feature.");
} }

View File

@ -1,4 +1,6 @@
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { firstValueFrom } from "rxjs";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
@ -12,9 +14,9 @@ import { SendGetCommand } from "./get.command";
export class SendEditCommand { export class SendEditCommand {
constructor( constructor(
private sendService: SendService, private sendService: SendService,
private stateService: StateService,
private getCommand: SendGetCommand, private getCommand: SendGetCommand,
private sendApiService: SendApiService, private sendApiService: SendApiService,
private accountProfileService: BillingAccountProfileStateService,
) {} ) {}
async run(requestJson: string, cmdOptions: Record<string, any>): Promise<Response> { async run(requestJson: string, cmdOptions: Record<string, any>): Promise<Response> {
@ -57,7 +59,10 @@ export class SendEditCommand {
return Response.badRequest("Cannot change a Send's type"); return Response.badRequest("Cannot change a Send's type");
} }
if (send.type === SendType.File && !(await this.stateService.getCanAccessPremium())) { const canAccessPremium = await firstValueFrom(
this.accountProfileService.hasPremiumFromAnySource$,
);
if (send.type === SendType.File && !canAccessPremium) {
return Response.error("Premium status is required to use this feature."); return Response.error("Premium status is required to use this feature.");
} }

View File

@ -153,6 +153,7 @@ export class SendProgram extends Program {
this.main.apiService, this.main.apiService,
this.main.organizationService, this.main.organizationService,
this.main.eventCollectionService, this.main.eventCollectionService,
this.main.billingAccountProfileStateService,
); );
const response = await cmd.run("template", object, null); const response = await cmd.run("template", object, null);
this.processResponse(response); this.processResponse(response);
@ -253,9 +254,9 @@ export class SendProgram extends Program {
); );
const cmd = new SendEditCommand( const cmd = new SendEditCommand(
this.main.sendService, this.main.sendService,
this.main.stateService,
getCmd, getCmd,
this.main.sendApiService, this.main.sendApiService,
this.main.billingAccountProfileStateService,
); );
const response = await cmd.run(encodedJson, options); const response = await cmd.run(encodedJson, options);
this.processResponse(response); this.processResponse(response);
@ -323,9 +324,9 @@ export class SendProgram extends Program {
await this.exitIfLocked(); await this.exitIfLocked();
const cmd = new SendCreateCommand( const cmd = new SendCreateCommand(
this.main.sendService, this.main.sendService,
this.main.stateService,
this.main.environmentService, this.main.environmentService,
this.main.sendApiService, this.main.sendApiService,
this.main.billingAccountProfileStateService,
); );
return await cmd.run(encodedJson, options); return await cmd.run(encodedJson, options);
} }

View File

@ -188,6 +188,7 @@ export class VaultProgram extends Program {
this.main.apiService, this.main.apiService,
this.main.organizationService, this.main.organizationService,
this.main.eventCollectionService, this.main.eventCollectionService,
this.main.billingAccountProfileStateService,
); );
const response = await command.run(object, id, cmd); const response = await command.run(object, id, cmd);
this.processResponse(response); this.processResponse(response);
@ -226,10 +227,10 @@ export class VaultProgram extends Program {
const command = new CreateCommand( const command = new CreateCommand(
this.main.cipherService, this.main.cipherService,
this.main.folderService, this.main.folderService,
this.main.stateService,
this.main.cryptoService, this.main.cryptoService,
this.main.apiService, this.main.apiService,
this.main.folderApiService, this.main.folderApiService,
this.main.billingAccountProfileStateService,
); );
const response = await command.run(object, encodedJson, cmd); const response = await command.run(object, encodedJson, cmd);
this.processResponse(response); this.processResponse(response);
@ -313,9 +314,9 @@ export class VaultProgram extends Program {
const command = new DeleteCommand( const command = new DeleteCommand(
this.main.cipherService, this.main.cipherService,
this.main.folderService, this.main.folderService,
this.main.stateService,
this.main.apiService, this.main.apiService,
this.main.folderApiService, this.main.folderApiService,
this.main.billingAccountProfileStateService,
); );
const response = await command.run(object, id, cmd); const response = await command.run(object, id, cmd);
this.processResponse(response); this.processResponse(response);

View File

@ -1,13 +1,15 @@
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
import { FolderExport } from "@bitwarden/common/models/export/folder.export"; import { FolderExport } from "@bitwarden/common/models/export/folder.export";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
@ -26,10 +28,10 @@ export class CreateCommand {
constructor( constructor(
private cipherService: CipherService, private cipherService: CipherService,
private folderService: FolderService, private folderService: FolderService,
private stateService: StateService,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private apiService: ApiService, private apiService: ApiService,
private folderApiService: FolderApiServiceAbstraction, private folderApiService: FolderApiServiceAbstraction,
private accountProfileService: BillingAccountProfileStateService,
) {} ) {}
async run( async run(
@ -124,7 +126,10 @@ export class CreateCommand {
return Response.notFound(); return Response.notFound();
} }
if (cipher.organizationId == null && !(await this.stateService.getCanAccessPremium())) { if (
cipher.organizationId == null &&
!(await firstValueFrom(this.accountProfileService.hasPremiumFromAnySource$))
) {
return Response.error("Premium status is required to use this feature."); return Response.error("Premium status is required to use this feature.");
} }

View File

@ -1,5 +1,7 @@
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
@ -12,9 +14,9 @@ export class DeleteCommand {
constructor( constructor(
private cipherService: CipherService, private cipherService: CipherService,
private folderService: FolderService, private folderService: FolderService,
private stateService: StateService,
private apiService: ApiService, private apiService: ApiService,
private folderApiService: FolderApiServiceAbstraction, private folderApiService: FolderApiServiceAbstraction,
private accountProfileService: BillingAccountProfileStateService,
) {} ) {}
async run(object: string, id: string, cmdOptions: Record<string, any>): Promise<Response> { async run(object: string, id: string, cmdOptions: Record<string, any>): Promise<Response> {
@ -75,7 +77,10 @@ export class DeleteCommand {
return Response.error("Attachment `" + id + "` was not found."); return Response.error("Attachment `" + id + "` was not found.");
} }
if (cipher.organizationId == null && !(await this.stateService.getCanAccessPremium())) { const canAccessPremium = await firstValueFrom(
this.accountProfileService.hasPremiumFromAnySource$,
);
if (cipher.organizationId == null && !canAccessPremium) {
return Response.error("Premium status is required to use this feature."); return Response.error("Premium status is required to use this feature.");
} }

View File

@ -130,12 +130,6 @@ version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205"
[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]] [[package]]
name = "cbc" name = "cbc"
version = "0.1.2" version = "0.1.2"
@ -333,7 +327,6 @@ dependencies = [
"security-framework-sys", "security-framework-sys",
"sha2", "sha2",
"thiserror", "thiserror",
"tokio",
"typenum", "typenum",
"widestring", "widestring",
"windows", "windows",
@ -377,7 +370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
@ -772,17 +765,6 @@ dependencies = [
"adler", "adler",
] ]
[[package]]
name = "mio"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
dependencies = [
"libc",
"wasi",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "napi" name = "napi"
version = "2.13.3" version = "2.13.3"
@ -925,7 +907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
@ -1114,7 +1096,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
@ -1204,15 +1186,6 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -1228,16 +1201,6 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "socket2"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
dependencies = [
"libc",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "str-buf" name = "str-buf"
version = "1.0.6" version = "1.0.6"
@ -1295,7 +1258,7 @@ dependencies = [
"fastrand", "fastrand",
"redox_syscall", "redox_syscall",
"rustix", "rustix",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
@ -1329,32 +1292,13 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.32.0" version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes",
"libc",
"mio",
"num_cpus", "num_cpus",
"parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
] ]
[[package]] [[package]]
@ -1606,15 +1550,6 @@ dependencies = [
"windows-targets 0.52.4", "windows-targets 0.52.4",
] ]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"

View File

@ -25,7 +25,6 @@ retry = "=2.0.0"
scopeguard = "=1.2.0" scopeguard = "=1.2.0"
sha2 = "=0.10.8" sha2 = "=0.10.8"
thiserror = "=1.0.51" thiserror = "=1.0.51"
tokio = { version = "=1.32.0", features = ["full"] }
typenum = "=1.17.0" typenum = "=1.17.0"
[build-dependencies] [build-dependencies]

View File

@ -24,7 +24,7 @@
"**/node_modules/argon2/package.json", "**/node_modules/argon2/package.json",
"**/node_modules/argon2/lib/binding/napi-v3/argon2.node" "**/node_modules/argon2/lib/binding/napi-v3/argon2.node"
], ],
"electronVersion": "28.2.6", "electronVersion": "28.2.7",
"generateUpdatesFilesForAllChannels": true, "generateUpdatesFilesForAllChannels": true,
"publish": { "publish": {
"provider": "generic", "provider": "generic",

View File

@ -3,13 +3,12 @@ import { FormBuilder } from "@angular/forms";
import { BehaviorSubject, firstValueFrom, Observable, Subject } from "rxjs"; import { BehaviorSubject, firstValueFrom, Observable, Subject } from "rxjs";
import { concatMap, debounceTime, filter, map, switchMap, takeUntil, tap } from "rxjs/operators"; import { concatMap, debounceTime, filter, map, switchMap, takeUntil, tap } from "rxjs/operators";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { DeviceType } from "@bitwarden/common/enums"; import { DeviceType } from "@bitwarden/common/enums";
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@ -115,9 +114,8 @@ export class SettingsComponent implements OnInit {
private autofillSettingsService: AutofillSettingsServiceAbstraction, private autofillSettingsService: AutofillSettingsServiceAbstraction,
private messagingService: MessagingService, private messagingService: MessagingService,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private modalService: ModalService,
private themeStateService: ThemeStateService, private themeStateService: ThemeStateService,
private settingsService: SettingsService, private domainSettingsService: DomainSettingsService,
private dialogService: DialogService, private dialogService: DialogService,
private userVerificationService: UserVerificationServiceAbstraction, private userVerificationService: UserVerificationServiceAbstraction,
private biometricStateService: BiometricStateService, private biometricStateService: BiometricStateService,
@ -251,7 +249,7 @@ export class SettingsComponent implements OnInit {
approveLoginRequests: (await this.stateService.getApproveLoginRequests()) ?? false, approveLoginRequests: (await this.stateService.getApproveLoginRequests()) ?? false,
clearClipboard: await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$), clearClipboard: await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$),
minimizeOnCopyToClipboard: await this.stateService.getMinimizeOnCopyToClipboard(), minimizeOnCopyToClipboard: await this.stateService.getMinimizeOnCopyToClipboard(),
enableFavicons: !(await this.stateService.getDisableFavicon()), enableFavicons: await firstValueFrom(this.domainSettingsService.showFavicons$),
enableTray: await this.stateService.getEnableTray(), enableTray: await this.stateService.getEnableTray(),
enableMinToTray: await this.stateService.getEnableMinimizeToTray(), enableMinToTray: await this.stateService.getEnableMinimizeToTray(),
enableCloseToTray: await this.stateService.getEnableCloseToTray(), enableCloseToTray: await this.stateService.getEnableCloseToTray(),
@ -498,7 +496,7 @@ export class SettingsComponent implements OnInit {
} }
async saveFavicons() { async saveFavicons() {
await this.settingsService.setDisableFavicon(!this.form.value.enableFavicons); await this.domainSettingsService.setShowFavicons(this.form.value.enableFavicons);
this.messagingService.send("refreshCiphers"); this.messagingService.send("refreshCiphers");
} }

View File

@ -19,9 +19,9 @@ import { FingerprintDialogComponent } from "@bitwarden/auth/angular";
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
@ -122,7 +122,6 @@ export class AppComponent implements OnInit, OnDestroy {
constructor( constructor(
private broadcasterService: BroadcasterService, private broadcasterService: BroadcasterService,
private folderService: InternalFolderService, private folderService: InternalFolderService,
private settingsService: SettingsService,
private syncService: SyncService, private syncService: SyncService,
private passwordGenerationService: PasswordGenerationServiceAbstraction, private passwordGenerationService: PasswordGenerationServiceAbstraction,
private cipherService: CipherService, private cipherService: CipherService,
@ -153,6 +152,7 @@ export class AppComponent implements OnInit, OnDestroy {
private biometricStateService: BiometricStateService, private biometricStateService: BiometricStateService,
private stateEventRunnerService: StateEventRunnerService, private stateEventRunnerService: StateEventRunnerService,
private providerService: ProviderService, private providerService: ProviderService,
private organizationService: InternalOrganizationServiceAbstraction,
) {} ) {}
ngOnInit() { ngOnInit() {
@ -574,7 +574,8 @@ export class AppComponent implements OnInit, OnDestroy {
let preLogoutActiveUserId; let preLogoutActiveUserId;
try { try {
await this.eventUploadService.uploadEvents(userBeingLoggedOut); // Provide the userId of the user to upload events for
await this.eventUploadService.uploadEvents(userBeingLoggedOut as UserId);
await this.syncService.setLastSync(new Date(0), userBeingLoggedOut); await this.syncService.setLastSync(new Date(0), userBeingLoggedOut);
await this.cryptoService.clearKeys(userBeingLoggedOut); await this.cryptoService.clearKeys(userBeingLoggedOut);
await this.cipherService.clear(userBeingLoggedOut); await this.cipherService.clear(userBeingLoggedOut);

View File

@ -2,9 +2,10 @@ import { animate, state, style, transition, trigger } from "@angular/animations"
import { ConnectedPosition } from "@angular/cdk/overlay"; import { ConnectedPosition } from "@angular/cdk/overlay";
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { concatMap, Subject, takeUntil } from "rxjs"; import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@ -12,6 +13,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { Account } from "@bitwarden/common/platform/models/domain/account"; import { Account } from "@bitwarden/common/platform/models/domain/account";
import { UserId } from "@bitwarden/common/types/guid";
type ActiveAccount = { type ActiveAccount = {
id: string; id: string;
@ -84,6 +86,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
constructor( constructor(
private stateService: StateService, private stateService: StateService,
private authService: AuthService, private authService: AuthService,
private avatarService: AvatarService,
private messagingService: MessagingService, private messagingService: MessagingService,
private router: Router, private router: Router,
private tokenService: TokenService, private tokenService: TokenService,
@ -101,7 +104,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
id: await this.tokenService.getUserId(), id: await this.tokenService.getUserId(),
name: (await this.tokenService.getName()) ?? (await this.tokenService.getEmail()), name: (await this.tokenService.getName()) ?? (await this.tokenService.getEmail()),
email: await this.tokenService.getEmail(), email: await this.tokenService.getEmail(),
avatarColor: await this.stateService.getAvatarColor(), avatarColor: await firstValueFrom(this.avatarService.avatarColor$),
server: await this.environmentService.getHost(), server: await this.environmentService.getHost(),
}; };
} catch { } catch {
@ -154,7 +157,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
name: baseAccounts[userId].profile.name, name: baseAccounts[userId].profile.name,
email: baseAccounts[userId].profile.email, email: baseAccounts[userId].profile.email,
authenticationStatus: await this.authService.getAuthStatus(userId), authenticationStatus: await this.authService.getAuthStatus(userId),
avatarColor: await this.stateService.getAvatarColor({ userId: userId }), avatarColor: await firstValueFrom(this.avatarService.getUserAvatarColor$(userId as UserId)),
server: await this.environmentService.getHost(userId), server: await this.environmentService.getHost(userId),
}; };
} }

View File

@ -10,6 +10,7 @@ import {
OBSERVABLE_MEMORY_STORAGE, OBSERVABLE_MEMORY_STORAGE,
OBSERVABLE_DISK_STORAGE, OBSERVABLE_DISK_STORAGE,
WINDOW, WINDOW,
SUPPORTS_SECURE_STORAGE,
SYSTEM_THEME_OBSERVABLE, SYSTEM_THEME_OBSERVABLE,
} from "@bitwarden/angular/services/injection-tokens"; } from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
@ -18,6 +19,7 @@ import { PolicyService as PolicyServiceAbstraction } from "@bitwarden/common/adm
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { LoginService } from "@bitwarden/common/auth/services/login.service"; import { LoginService } from "@bitwarden/common/auth/services/login.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service";
@ -54,7 +56,10 @@ import { LoginGuard } from "../../auth/guards/login.guard";
import { Account } from "../../models/account"; import { Account } from "../../models/account";
import { ElectronCryptoService } from "../../platform/services/electron-crypto.service"; import { ElectronCryptoService } from "../../platform/services/electron-crypto.service";
import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service"; import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service";
import { ElectronPlatformUtilsService } from "../../platform/services/electron-platform-utils.service"; import {
ELECTRON_SUPPORTS_SECURE_STORAGE,
ElectronPlatformUtilsService,
} from "../../platform/services/electron-platform-utils.service";
import { ElectronRendererMessagingService } from "../../platform/services/electron-renderer-messaging.service"; import { ElectronRendererMessagingService } from "../../platform/services/electron-renderer-messaging.service";
import { ElectronRendererSecureStorageService } from "../../platform/services/electron-renderer-secure-storage.service"; import { ElectronRendererSecureStorageService } from "../../platform/services/electron-renderer-secure-storage.service";
import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service"; import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service";
@ -101,6 +106,13 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
useClass: ElectronPlatformUtilsService, useClass: ElectronPlatformUtilsService,
deps: [I18nServiceAbstraction, MessagingServiceAbstraction], deps: [I18nServiceAbstraction, MessagingServiceAbstraction],
}, },
{
// We manually override the value of SUPPORTS_SECURE_STORAGE here to avoid
// the TokenService having to inject the PlatformUtilsService which introduces a
// circular dependency on Desktop only.
provide: SUPPORTS_SECURE_STORAGE,
useValue: ELECTRON_SUPPORTS_SECURE_STORAGE,
},
{ {
provide: I18nServiceAbstraction, provide: I18nServiceAbstraction,
useClass: I18nRendererService, useClass: I18nRendererService,
@ -140,6 +152,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
STATE_FACTORY, STATE_FACTORY,
AccountServiceAbstraction, AccountServiceAbstraction,
EnvironmentService, EnvironmentService,
TokenService,
MigrationRunner, MigrationRunner,
STATE_SERVICE_USE_CACHE, STATE_SERVICE_USE_CACHE,
], ],
@ -170,7 +183,6 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
CryptoServiceAbstraction, CryptoServiceAbstraction,
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
MessagingServiceAbstraction, MessagingServiceAbstraction,
I18nServiceAbstraction,
EncryptedMessageHandlerService, EncryptedMessageHandlerService,
DialogService, DialogService,
], ],

View File

@ -4,6 +4,7 @@ import { FormBuilder } from "@angular/forms";
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component"; import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -32,6 +33,7 @@ export class AddEditComponent extends BaseAddEditComponent {
sendApiService: SendApiService, sendApiService: SendApiService,
dialogService: DialogService, dialogService: DialogService,
formBuilder: FormBuilder, formBuilder: FormBuilder,
billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
i18nService, i18nService,
@ -46,6 +48,7 @@ export class AddEditComponent extends BaseAddEditComponent {
sendApiService, sendApiService,
dialogService, dialogService,
formBuilder, formBuilder,
billingAccountProfileStateService,
); );
} }

View File

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

View File

@ -2,7 +2,9 @@ import * as path from "path";
import { app } from "electron"; import { app } from "electron";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { TokenService } from "@bitwarden/common/auth/services/token.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DefaultBiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { DefaultBiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
@ -36,6 +38,7 @@ import { ClipboardMain } from "./platform/main/clipboard.main";
import { DesktopCredentialStorageListener } from "./platform/main/desktop-credential-storage-listener"; import { DesktopCredentialStorageListener } from "./platform/main/desktop-credential-storage-listener";
import { MainCryptoFunctionService } from "./platform/main/main-crypto-function.service"; import { MainCryptoFunctionService } from "./platform/main/main-crypto-function.service";
import { ElectronLogMainService } from "./platform/services/electron-log.main.service"; import { ElectronLogMainService } from "./platform/services/electron-log.main.service";
import { ELECTRON_SUPPORTS_SECURE_STORAGE } from "./platform/services/electron-platform-utils.service";
import { ElectronStateService } from "./platform/services/electron-state.service"; import { ElectronStateService } from "./platform/services/electron-state.service";
import { ElectronStorageService } from "./platform/services/electron-storage.service"; import { ElectronStorageService } from "./platform/services/electron-storage.service";
import { I18nMainService } from "./platform/services/i18n.main.service"; import { I18nMainService } from "./platform/services/i18n.main.service";
@ -53,6 +56,7 @@ export class Main {
mainCryptoFunctionService: MainCryptoFunctionService; mainCryptoFunctionService: MainCryptoFunctionService;
desktopCredentialStorageListener: DesktopCredentialStorageListener; desktopCredentialStorageListener: DesktopCredentialStorageListener;
migrationRunner: MigrationRunner; migrationRunner: MigrationRunner;
tokenService: TokenServiceAbstraction;
windowMain: WindowMain; windowMain: WindowMain;
messagingMain: MessagingMain; messagingMain: MessagingMain;
@ -124,19 +128,32 @@ export class Main {
storageServiceProvider, storageServiceProvider,
); );
const stateProvider = new DefaultStateProvider( const singleUserStateProvider = new DefaultSingleUserStateProvider(
new DefaultActiveUserStateProvider(
accountService,
storageServiceProvider, storageServiceProvider,
stateEventRegistrarService, stateEventRegistrarService,
), );
new DefaultSingleUserStateProvider(storageServiceProvider, stateEventRegistrarService),
const activeUserStateProvider = new DefaultActiveUserStateProvider(
accountService,
singleUserStateProvider,
);
const stateProvider = new DefaultStateProvider(
activeUserStateProvider,
singleUserStateProvider,
globalStateProvider, globalStateProvider,
new DefaultDerivedStateProvider(this.memoryStorageForStateProviders), new DefaultDerivedStateProvider(this.memoryStorageForStateProviders),
); );
this.environmentService = new EnvironmentService(stateProvider, accountService); this.environmentService = new EnvironmentService(stateProvider, accountService);
this.tokenService = new TokenService(
singleUserStateProvider,
globalStateProvider,
ELECTRON_SUPPORTS_SECURE_STORAGE,
this.storageService,
);
this.migrationRunner = new MigrationRunner( this.migrationRunner = new MigrationRunner(
this.storageService, this.storageService,
this.logService, this.logService,
@ -154,6 +171,7 @@ export class Main {
new StateFactory(GlobalState, Account), new StateFactory(GlobalState, Account),
accountService, // will not broadcast logouts. This is a hack until we can remove messaging dependency accountService, // will not broadcast logouts. This is a hack until we can remove messaging dependency
this.environmentService, this.environmentService,
this.tokenService,
this.migrationRunner, this.migrationRunner,
false, // Do not use disk caching because this will get out of sync with the renderer service false, // Do not use disk caching because this will get out of sync with the renderer service
); );
@ -175,6 +193,7 @@ export class Main {
this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => { this.messagingService = new ElectronMainMessagingService(this.windowMain, (message) => {
this.messagingMain.onMessage(message); this.messagingMain.onMessage(message);
}); });
this.powerMonitorMain = new PowerMonitorMain(this.messagingService); this.powerMonitorMain = new PowerMonitorMain(this.messagingService);
this.menuMain = new MenuMain( this.menuMain = new MenuMain(
this.i18nService, this.i18nService,

View File

@ -8,6 +8,7 @@ import localeBn from "@angular/common/locales/bn";
import localeBs from "@angular/common/locales/bs"; import localeBs from "@angular/common/locales/bs";
import localeCa from "@angular/common/locales/ca"; import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs"; import localeCs from "@angular/common/locales/cs";
import localeCy from "@angular/common/locales/cy";
import localeDa from "@angular/common/locales/da"; import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de"; import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el"; import localeEl from "@angular/common/locales/el";
@ -21,6 +22,7 @@ import localeFa from "@angular/common/locales/fa";
import localeFi from "@angular/common/locales/fi"; import localeFi from "@angular/common/locales/fi";
import localeFil from "@angular/common/locales/fil"; import localeFil from "@angular/common/locales/fil";
import localeFr from "@angular/common/locales/fr"; import localeFr from "@angular/common/locales/fr";
import localeGl from "@angular/common/locales/gl";
import localeHe from "@angular/common/locales/he"; import localeHe from "@angular/common/locales/he";
import localeHi from "@angular/common/locales/hi"; import localeHi from "@angular/common/locales/hi";
import localeHr from "@angular/common/locales/hr"; import localeHr from "@angular/common/locales/hr";
@ -32,11 +34,16 @@ import localeKa from "@angular/common/locales/ka";
import localeKm from "@angular/common/locales/km"; import localeKm from "@angular/common/locales/km";
import localeKn from "@angular/common/locales/kn"; import localeKn from "@angular/common/locales/kn";
import localeKo from "@angular/common/locales/ko"; import localeKo from "@angular/common/locales/ko";
import localeLt from "@angular/common/locales/lt";
import localeLv from "@angular/common/locales/lv"; import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml"; import localeMl from "@angular/common/locales/ml";
import localeMr from "@angular/common/locales/mr";
import localeMy from "@angular/common/locales/my";
import localeNb from "@angular/common/locales/nb"; import localeNb from "@angular/common/locales/nb";
import localeNe from "@angular/common/locales/ne";
import localeNl from "@angular/common/locales/nl"; import localeNl from "@angular/common/locales/nl";
import localeNn from "@angular/common/locales/nn"; import localeNn from "@angular/common/locales/nn";
import localeOr from "@angular/common/locales/or";
import localePl from "@angular/common/locales/pl"; import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt"; import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT"; import localePtPt from "@angular/common/locales/pt-PT";
@ -48,6 +55,7 @@ import localeSl from "@angular/common/locales/sl";
import localeSr from "@angular/common/locales/sr"; import localeSr from "@angular/common/locales/sr";
import localeMe from "@angular/common/locales/sr-Latn-ME"; import localeMe from "@angular/common/locales/sr-Latn-ME";
import localeSv from "@angular/common/locales/sv"; import localeSv from "@angular/common/locales/sv";
import localeTe from "@angular/common/locales/te";
import localeTh from "@angular/common/locales/th"; import localeTh from "@angular/common/locales/th";
import localeTr from "@angular/common/locales/tr"; import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk"; import localeUk from "@angular/common/locales/uk";
@ -64,6 +72,7 @@ registerLocaleData(localeBn, "bn");
registerLocaleData(localeBs, "bs"); registerLocaleData(localeBs, "bs");
registerLocaleData(localeCa, "ca"); registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs"); registerLocaleData(localeCs, "cs");
registerLocaleData(localeCy, "cy");
registerLocaleData(localeDa, "da"); registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de"); registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el"); registerLocaleData(localeEl, "el");
@ -77,6 +86,7 @@ registerLocaleData(localeFa, "fa");
registerLocaleData(localeFi, "fi"); registerLocaleData(localeFi, "fi");
registerLocaleData(localeFil, "fil"); registerLocaleData(localeFil, "fil");
registerLocaleData(localeFr, "fr"); registerLocaleData(localeFr, "fr");
registerLocaleData(localeGl, "gl");
registerLocaleData(localeHe, "he"); registerLocaleData(localeHe, "he");
registerLocaleData(localeHi, "hi"); registerLocaleData(localeHi, "hi");
registerLocaleData(localeHr, "hr"); registerLocaleData(localeHr, "hr");
@ -88,12 +98,17 @@ registerLocaleData(localeKa, "ka");
registerLocaleData(localeKm, "km"); registerLocaleData(localeKm, "km");
registerLocaleData(localeKn, "kn"); registerLocaleData(localeKn, "kn");
registerLocaleData(localeKo, "ko"); registerLocaleData(localeKo, "ko");
registerLocaleData(localeLt, "lt");
registerLocaleData(localeLv, "lv"); registerLocaleData(localeLv, "lv");
registerLocaleData(localeMe, "me"); registerLocaleData(localeMe, "me");
registerLocaleData(localeMl, "ml"); registerLocaleData(localeMl, "ml");
registerLocaleData(localeMr, "mr");
registerLocaleData(localeMy, "my");
registerLocaleData(localeNb, "nb"); registerLocaleData(localeNb, "nb");
registerLocaleData(localeNe, "ne");
registerLocaleData(localeNl, "nl"); registerLocaleData(localeNl, "nl");
registerLocaleData(localeNn, "nn"); registerLocaleData(localeNn, "nn");
registerLocaleData(localeOr, "or");
registerLocaleData(localePl, "pl"); registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR"); registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT"); registerLocaleData(localePtPt, "pt-PT");
@ -104,6 +119,7 @@ registerLocaleData(localeSk, "sk");
registerLocaleData(localeSl, "sl"); registerLocaleData(localeSl, "sl");
registerLocaleData(localeSr, "sr"); registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv"); registerLocaleData(localeSv, "sv");
registerLocaleData(localeTe, "te");
registerLocaleData(localeTh, "th"); registerLocaleData(localeTh, "th");
registerLocaleData(localeTr, "tr"); registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk"); registerLocaleData(localeUk, "uk");

View File

@ -8,6 +8,8 @@ import {
import { ClipboardWriteMessage } from "../types/clipboard"; import { ClipboardWriteMessage } from "../types/clipboard";
export const ELECTRON_SUPPORTS_SECURE_STORAGE = true;
export class ElectronPlatformUtilsService implements PlatformUtilsService { export class ElectronPlatformUtilsService implements PlatformUtilsService {
constructor( constructor(
protected i18nService: I18nService, protected i18nService: I18nService,
@ -142,7 +144,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
} }
supportsSecureStorage(): boolean { supportsSecureStorage(): boolean {
return true; return ELECTRON_SUPPORTS_SECURE_STORAGE;
} }
getAutofillKeyboardShortcut(): Promise<string> { getAutofillKeyboardShortcut(): Promise<string> {

View File

@ -4,7 +4,6 @@ import { firstValueFrom } from "rxjs";
import { NativeMessagingVersion } from "@bitwarden/common/enums"; import { NativeMessagingVersion } from "@bitwarden/common/enums";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
@ -33,7 +32,6 @@ export class NativeMessageHandlerService {
private cryptoService: CryptoService, private cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService, private cryptoFunctionService: CryptoFunctionService,
private messagingService: MessagingService, private messagingService: MessagingService,
private i18nService: I18nService,
private encryptedMessageHandlerService: EncryptedMessageHandlerService, private encryptedMessageHandlerService: EncryptedMessageHandlerService,
private dialogService: DialogService, private dialogService: DialogService,
) {} ) {}

View File

@ -3,7 +3,6 @@ import { firstValueFrom } from "rxjs";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -35,7 +34,6 @@ export class NativeMessagingService {
private cryptoService: CryptoService, private cryptoService: CryptoService,
private platformUtilService: PlatformUtilsService, private platformUtilService: PlatformUtilsService,
private logService: LogService, private logService: LogService,
private i18nService: I18nService,
private messagingService: MessagingService, private messagingService: MessagingService,
private stateService: StateService, private stateService: StateService,
private biometricStateService: BiometricStateService, private biometricStateService: BiometricStateService,

View File

@ -7,7 +7,7 @@
{{ "premiumMembership" | i18n }} {{ "premiumMembership" | i18n }}
</h1> </h1>
<div class="box-content box-content-padded"> <div class="box-content box-content-padded">
<div *ngIf="!isPremium"> <div *ngIf="!(isPremium$ | async)">
<p class="text-center lead">{{ "premiumNotCurrentMember" | i18n }}</p> <p class="text-center lead">{{ "premiumNotCurrentMember" | i18n }}</p>
<p>{{ "premiumSignUpAndGet" | i18n }}</p> <p>{{ "premiumSignUpAndGet" | i18n }}</p>
<ul class="bwi-ul"> <ul class="bwi-ul">
@ -40,7 +40,7 @@
{{ "premiumPrice" | i18n: (price | currency: "$") }} {{ "premiumPrice" | i18n: (price | currency: "$") }}
</p> </p>
</div> </div>
<div *ngIf="isPremium"> <div *ngIf="isPremium$ | async">
<p class="text-center lead">{{ "premiumCurrentMember" | i18n }}</p> <p class="text-center lead">{{ "premiumCurrentMember" | i18n }}</p>
<p class="text-center">{{ "premiumCurrentMemberThanks" | i18n }}</p> <p class="text-center">{{ "premiumCurrentMemberThanks" | i18n }}</p>
</div> </div>
@ -48,7 +48,7 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="primary" (click)="manage()" *ngIf="isPremium"> <button type="button" class="primary" (click)="manage()" *ngIf="isPremium$ | async">
<b>{{ "premiumManage" | i18n }}</b> <b>{{ "premiumManage" | i18n }}</b>
</button> </button>
<button <button
@ -56,13 +56,13 @@
type="button" type="button"
class="primary" class="primary"
(click)="purchase()" (click)="purchase()"
*ngIf="!isPremium" *ngIf="!(isPremium$ | async)"
[disabled]="$any(purchaseBtn).loading" [disabled]="$any(purchaseBtn).loading"
> >
<b>{{ "premiumPurchase" | i18n }}</b> <b>{{ "premiumPurchase" | i18n }}</b>
</button> </button>
<button type="button" data-dismiss="modal">{{ "close" | i18n }}</button> <button type="button" data-dismiss="modal">{{ "close" | i18n }}</button>
<div class="right" *ngIf="!isPremium"> <div class="right" *ngIf="!(isPremium$ | async)">
<button <button
#refreshBtn #refreshBtn
type="button" type="button"

View File

@ -2,6 +2,7 @@ import { Component } from "@angular/core";
import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component"; import { PremiumComponent as BasePremiumComponent } from "@bitwarden/angular/vault/components/premium.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@ -22,6 +23,7 @@ export class PremiumComponent extends BasePremiumComponent {
stateService: StateService, stateService: StateService,
dialogService: DialogService, dialogService: DialogService,
environmentService: EnvironmentService, environmentService: EnvironmentService,
billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
i18nService, i18nService,
@ -31,6 +33,7 @@ export class PremiumComponent extends BasePremiumComponent {
stateService, stateService,
dialogService, dialogService,
environmentService, environmentService,
billingAccountProfileStateService,
); );
} }
} }

View File

@ -2,6 +2,7 @@ import { Component } from "@angular/core";
import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -26,6 +27,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
stateService: StateService, stateService: StateService,
fileDownloadService: FileDownloadService, fileDownloadService: FileDownloadService,
dialogService: DialogService, dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
cipherService, cipherService,
@ -38,6 +40,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
stateService, stateService,
fileDownloadService, fileDownloadService,
dialogService, dialogService,
billingAccountProfileStateService,
); );
} }
} }

View File

@ -8,6 +8,7 @@ import {
ViewContainerRef, ViewContainerRef,
} from "@angular/core"; } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
@ -15,6 +16,7 @@ import { ModalService } from "@bitwarden/angular/services/modal.service";
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EventType } from "@bitwarden/common/enums"; import { EventType } from "@bitwarden/common/enums";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -84,6 +86,7 @@ export class VaultComponent implements OnInit, OnDestroy {
activeFilter: VaultFilter = new VaultFilter(); activeFilter: VaultFilter = new VaultFilter();
private modal: ModalRef = null; private modal: ModalRef = null;
private componentIsDestroyed$ = new Subject<boolean>();
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@ -103,10 +106,16 @@ export class VaultComponent implements OnInit, OnDestroy {
private searchBarService: SearchBarService, private searchBarService: SearchBarService,
private apiService: ApiService, private apiService: ApiService,
private dialogService: DialogService, private dialogService: DialogService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.userHasPremiumAccess = await this.stateService.getCanAccessPremium(); this.billingAccountProfileStateService.hasPremiumFromAnySource$
.pipe(takeUntil(this.componentIsDestroyed$))
.subscribe((canAccessPremium: boolean) => {
this.userHasPremiumAccess = canAccessPremium;
});
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
@ -229,6 +238,8 @@ export class VaultComponent implements OnInit, OnDestroy {
ngOnDestroy() { ngOnDestroy() {
this.searchBarService.setEnabled(false); this.searchBarService.setEnabled(false);
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.componentIsDestroyed$.next(true);
this.componentIsDestroyed$.complete();
} }
async load() { async load() {

View File

@ -13,6 +13,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
@ -58,6 +59,7 @@ export class ViewComponent extends BaseViewComponent implements OnChanges {
fileDownloadService: FileDownloadService, fileDownloadService: FileDownloadService,
dialogService: DialogService, dialogService: DialogService,
datePipe: DatePipe, datePipe: DatePipe,
billingAccountProfileStateService: BillingAccountProfileStateService,
) { ) {
super( super(
cipherService, cipherService,
@ -80,6 +82,7 @@ export class ViewComponent extends BaseViewComponent implements OnChanges {
fileDownloadService, fileDownloadService,
dialogService, dialogService,
datePipe, datePipe,
billingAccountProfileStateService,
); );
} }
ngOnInit() { ngOnInit() {

View File

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

View File

@ -17,7 +17,7 @@ export class IsPaidOrgGuard implements CanActivate {
) {} ) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const org = this.organizationService.get(route.params.organizationId); const org = await this.organizationService.get(route.params.organizationId);
if (org == null) { if (org == null) {
return this.router.createUrlTree(["/"]); return this.router.createUrlTree(["/"]);

View File

@ -66,7 +66,7 @@ describe("Organization Permissions Guard", () => {
it("permits navigation if no permissions are specified", async () => { it("permits navigation if no permissions are specified", async () => {
const org = orgFactory(); const org = orgFactory();
organizationService.get.calledWith(org.id).mockReturnValue(org); organizationService.get.calledWith(org.id).mockResolvedValue(org);
const actual = await organizationPermissionsGuard.canActivate(route, state); const actual = await organizationPermissionsGuard.canActivate(route, state);
@ -81,7 +81,7 @@ describe("Organization Permissions Guard", () => {
}; };
const org = orgFactory(); const org = orgFactory();
organizationService.get.calledWith(org.id).mockReturnValue(org); organizationService.get.calledWith(org.id).mockResolvedValue(org);
const actual = await organizationPermissionsGuard.canActivate(route, state); const actual = await organizationPermissionsGuard.canActivate(route, state);
@ -104,7 +104,7 @@ describe("Organization Permissions Guard", () => {
}); });
const org = orgFactory(); const org = orgFactory();
organizationService.get.calledWith(org.id).mockReturnValue(org); organizationService.get.calledWith(org.id).mockResolvedValue(org);
const actual = await organizationPermissionsGuard.canActivate(route, state); const actual = await organizationPermissionsGuard.canActivate(route, state);
@ -124,7 +124,7 @@ describe("Organization Permissions Guard", () => {
}), }),
}); });
const org = orgFactory(); const org = orgFactory();
organizationService.get.calledWith(org.id).mockReturnValue(org); organizationService.get.calledWith(org.id).mockResolvedValue(org);
const actual = await organizationPermissionsGuard.canActivate(route, state); const actual = await organizationPermissionsGuard.canActivate(route, state);
@ -141,7 +141,7 @@ describe("Organization Permissions Guard", () => {
type: OrganizationUserType.Admin, type: OrganizationUserType.Admin,
enabled: false, enabled: false,
}); });
organizationService.get.calledWith(org.id).mockReturnValue(org); organizationService.get.calledWith(org.id).mockResolvedValue(org);
const actual = await organizationPermissionsGuard.canActivate(route, state); const actual = await organizationPermissionsGuard.canActivate(route, state);
@ -153,7 +153,7 @@ describe("Organization Permissions Guard", () => {
type: OrganizationUserType.Owner, type: OrganizationUserType.Owner,
enabled: false, enabled: false,
}); });
organizationService.get.calledWith(org.id).mockReturnValue(org); organizationService.get.calledWith(org.id).mockResolvedValue(org);
const actual = await organizationPermissionsGuard.canActivate(route, state); const actual = await organizationPermissionsGuard.canActivate(route, state);

View File

@ -28,7 +28,7 @@ export class OrganizationPermissionsGuard implements CanActivate {
await this.syncService.fullSync(false); await this.syncService.fullSync(false);
} }
const org = this.organizationService.get(route.params.organizationId); const org = await this.organizationService.get(route.params.organizationId);
if (org == null) { if (org == null) {
return this.router.createUrlTree(["/"]); return this.router.createUrlTree(["/"]);
} }

View File

@ -16,7 +16,7 @@ export class OrganizationRedirectGuard implements CanActivate {
) {} ) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const org = this.organizationService.get(route.params.organizationId); const org = await this.organizationService.get(route.params.organizationId);
const customRedirect = route.data?.autoRedirectCallback; const customRedirect = route.data?.autoRedirectCallback;
if (customRedirect) { if (customRedirect) {

View File

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

View File

@ -2,6 +2,7 @@ import { Component, Input } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { BulkUserDetails } from "./bulk-status.component"; import { BulkUserDetails } from "./bulk-status.component";
@ -14,7 +15,9 @@ export class BulkRemoveComponent {
@Input() organizationId: string; @Input() organizationId: string;
@Input() set users(value: BulkUserDetails[]) { @Input() set users(value: BulkUserDetails[]) {
this._users = value; this._users = value;
this.showNoMasterPasswordWarning = this._users.some((u) => u.hasMasterPassword === false); this.showNoMasterPasswordWarning = this._users.some(
(u) => u.status > OrganizationUserStatusType.Invited && u.hasMasterPassword === false,
);
} }
get users(): BulkUserDetails[] { get users(): BulkUserDetails[] {

View File

@ -2,6 +2,7 @@ import { DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core"; import { Component, Inject } from "@angular/core";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
@ -37,7 +38,9 @@ export class BulkRestoreRevokeComponent {
this.isRevoking = data.isRevoking; this.isRevoking = data.isRevoking;
this.organizationId = data.organizationId; this.organizationId = data.organizationId;
this.users = data.users; this.users = data.users;
this.showNoMasterPasswordWarning = this.users.some((u) => u.hasMasterPassword === false); this.showNoMasterPasswordWarning = this.users.some(
(u) => u.status > OrganizationUserStatusType.Invited && u.hasMasterPassword === false,
);
} }
get bulkTitle() { get bulkTitle() {

View File

@ -399,7 +399,11 @@
</bit-tab> </bit-tab>
<bit-tab *ngIf="organization.useGroups" [label]="'groups' | i18n"> <bit-tab *ngIf="organization.useGroups" [label]="'groups' | i18n">
<div class="tw-mb-6"> <div class="tw-mb-6">
{{ "groupAccessUserDesc" | i18n }} {{
(restrictedAccess$ | async)
? ("restrictedGroupAccess" | i18n)
: ("groupAccessUserDesc" | i18n)
}}
</div> </div>
<bit-access-selector <bit-access-selector
formControlName="groups" formControlName="groups"
@ -408,10 +412,14 @@
[selectorLabelText]="'selectGroups' | i18n" [selectorLabelText]="'selectGroups' | i18n"
[emptySelectionText]="'noGroupsAdded' | i18n" [emptySelectionText]="'noGroupsAdded' | i18n"
[flexibleCollectionsEnabled]="organization.flexibleCollections" [flexibleCollectionsEnabled]="organization.flexibleCollections"
[hideMultiSelect]="restrictedAccess$ | async"
></bit-access-selector> ></bit-access-selector>
</bit-tab> </bit-tab>
<bit-tab [label]="'collections' | i18n"> <bit-tab [label]="'collections' | i18n">
<div *ngIf="organization.useGroups" class="tw-mb-6"> <div class="tw-mb-6" *ngIf="restrictedAccess$ | async">
{{ "restrictedCollectionAccess" | i18n }}
</div>
<div *ngIf="organization.useGroups && !(restrictedAccess$ | async)" class="tw-mb-6">
{{ "userPermissionOverrideHelper" | i18n }} {{ "userPermissionOverrideHelper" | i18n }}
</div> </div>
<div *ngIf="!organization.flexibleCollections" class="tw-mb-6"> <div *ngIf="!organization.flexibleCollections" class="tw-mb-6">
@ -441,6 +449,7 @@
[selectorLabelText]="'selectCollections' | i18n" [selectorLabelText]="'selectCollections' | i18n"
[emptySelectionText]="'noCollectionsAdded' | i18n" [emptySelectionText]="'noCollectionsAdded' | i18n"
[flexibleCollectionsEnabled]="organization.flexibleCollections" [flexibleCollectionsEnabled]="organization.flexibleCollections"
[hideMultiSelect]="restrictedAccess$ | async"
></bit-access-selector ></bit-access-selector
></bit-tab> ></bit-tab>
</bit-tab-group> </bit-tab-group>

View File

@ -4,6 +4,7 @@ import { FormBuilder, Validators } from "@angular/forms";
import { import {
combineLatest, combineLatest,
firstValueFrom, firstValueFrom,
map,
Observable, Observable,
of, of,
shareReplay, shareReplay,
@ -20,7 +21,9 @@ import {
} from "@bitwarden/common/admin-console/enums"; } from "@bitwarden/common/admin-console/enums";
import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ProductType } from "@bitwarden/common/enums"; import { ProductType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -99,6 +102,8 @@ export class MemberDialogComponent implements OnDestroy {
groups: [[] as AccessItemValue[]], groups: [[] as AccessItemValue[]],
}); });
protected restrictedAccess$: Observable<boolean>;
protected permissionsGroup = this.formBuilder.group({ protected permissionsGroup = this.formBuilder.group({
manageAssignedCollectionsGroup: this.formBuilder.group<Record<string, boolean>>({ manageAssignedCollectionsGroup: this.formBuilder.group<Record<string, boolean>>({
manageAssignedCollections: false, manageAssignedCollections: false,
@ -144,6 +149,7 @@ export class MemberDialogComponent implements OnDestroy {
private organizationUserService: OrganizationUserService, private organizationUserService: OrganizationUserService,
private dialogService: DialogService, private dialogService: DialogService,
private configService: ConfigServiceAbstraction, private configService: ConfigServiceAbstraction,
private accountService: AccountService,
organizationService: OrganizationService, organizationService: OrganizationService,
) { ) {
this.organization$ = organizationService this.organization$ = organizationService
@ -162,12 +168,43 @@ export class MemberDialogComponent implements OnDestroy {
), ),
); );
const userDetails$ = this.params.organizationUserId
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
: of(null);
// The orgUser cannot manage their own Group assignments if collection access is restricted
// TODO: fix disabled state of access-selector rows so that any controls are hidden
this.restrictedAccess$ = combineLatest([
this.organization$,
userDetails$,
this.accountService.activeAccount$,
this.configService.getFeatureFlag$(FeatureFlag.FlexibleCollectionsV1),
]).pipe(
map(
([organization, userDetails, activeAccount, flexibleCollectionsV1Enabled]) =>
// Feature flag conditionals
flexibleCollectionsV1Enabled &&
organization.flexibleCollections &&
// Business logic conditionals
userDetails != null &&
userDetails.userId == activeAccount.id &&
!organization.allowAdminAccessToAllCollectionItems,
),
shareReplay({ refCount: true, bufferSize: 1 }),
);
this.restrictedAccess$.pipe(takeUntil(this.destroy$)).subscribe((restrictedAccess) => {
if (restrictedAccess) {
this.formGroup.controls.groups.disable();
} else {
this.formGroup.controls.groups.enable();
}
});
combineLatest({ combineLatest({
organization: this.organization$, organization: this.organization$,
collections: this.collectionAdminService.getAll(this.params.organizationId), collections: this.collectionAdminService.getAll(this.params.organizationId),
userDetails: this.params.organizationUserId userDetails: userDetails$,
? this.userService.get(this.params.organizationId, this.params.organizationUserId)
: of(null),
groups: groups$, groups: groups$,
}) })
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
@ -369,7 +406,11 @@ export class MemberDialogComponent implements OnDestroy {
userView.collections = this.formGroup.value.access userView.collections = this.formGroup.value.access
.filter((v) => v.type === AccessItemType.Collection) .filter((v) => v.type === AccessItemType.Collection)
.map(convertToSelectionView); .map(convertToSelectionView);
userView.groups = this.formGroup.value.groups.map((m) => m.id);
userView.groups = (await firstValueFrom(this.restrictedAccess$))
? null
: this.formGroup.value.groups.map((m) => m.id);
userView.accessSecretsManager = this.formGroup.value.accessSecretsManager; userView.accessSecretsManager = this.formGroup.value.accessSecretsManager;
if (this.editMode) { if (this.editMode) {

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