From 1b5faae7c3a7f550e1ffd4e2b404e0182b99aaae Mon Sep 17 00:00:00 2001 From: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com> Date: Tue, 28 May 2024 21:23:54 +0530 Subject: [PATCH 1/7] [PM-5011][PM-7284] migrate user subscription along with update license dialog (#8659) * migrate user subscription along with update license dialog * migrate user subscription along with update license dialog --- .../user-subscription.component.html | 90 ++++++------------- .../individual/user-subscription.component.ts | 21 +++-- .../billing/shared/billing-shared.module.ts | 3 + .../update-license-dialog.component.html | 40 +++++++++ .../shared/update-license-dialog.component.ts | 38 ++++++++ 5 files changed, 118 insertions(+), 74 deletions(-) create mode 100644 apps/web/src/app/billing/shared/update-license-dialog.component.html create mode 100644 apps/web/src/app/billing/shared/update-license-dialog.component.ts diff --git a/apps/web/src/app/billing/individual/user-subscription.component.html b/apps/web/src/app/billing/individual/user-subscription.component.html index bed41c8dc5..5f9e6463f1 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.html +++ b/apps/web/src/app/billing/individual/user-subscription.component.html @@ -1,10 +1,10 @@ - {{ "loading" | i18n }} + {{ "loading" | i18n }} -

{{ "subscriptionPendingCanceled" | i18n }}

+

{{ "subscriptionPendingCanceled" | i18n }}

@@ -39,12 +37,12 @@
{{ sub.expiration | date: "mediumDate" }}
{{ "neverExpires" | i18n }}
-
-
+
+
{{ "status" | i18n }}
- {{ (subscription && subscription.status) || "-" }} + {{ (subscription && subscription.status) || "-" }} {{ "pendingCancellation" | i18n }} @@ -61,19 +59,19 @@
-
- {{ "details" | i18n }} - - +
+ {{ "details" | i18n }} + +
- - + - -
+ {{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @ {{ i.amount | currency: "$" }} {{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}{{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}
+ +
@@ -91,27 +89,9 @@ {{ "launchCloudSubscription" | i18n }}
-
-
- -

{{ "updateLicense" | i18n }}

- - -
-
-
+
-

{{ "storage" | i18n }}

-

{{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0 : sub.storageName || "0 MB" }}

-
-
- {{ storagePercentage / 100 | percent }} -
-
+

{{ "storage" | i18n }}

+

+ {{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0 : sub.storageName || "0 MB" }} +

+ -
-
- -
diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index fa21317c18..8535f23f82 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -20,6 +20,10 @@ import { OffboardingSurveyDialogResultType, openOffboardingSurvey, } from "../shared/offboarding-survey.component"; +import { + UpdateLicenseDialogComponent, + UpdateLicenseDialogResult, +} from "../shared/update-license-dialog.component"; @Component({ templateUrl: "user-subscription.component.html", @@ -131,21 +135,16 @@ export class UserSubscriptionComponent implements OnInit { }); } - updateLicense() { + updateLicense = async () => { if (this.loading) { return; } - this.showUpdateLicense = true; - } - - closeUpdateLicense(load: boolean) { - this.showUpdateLicense = false; - if (load) { - // 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.load(); + const dialogRef = UpdateLicenseDialogComponent.open(this.dialogService); + const result = await lastValueFrom(dialogRef.closed); + if (result === UpdateLicenseDialogResult.Updated) { + await this.load(); } - } + }; adjustStorage = (add: boolean) => { return async () => { diff --git a/apps/web/src/app/billing/shared/billing-shared.module.ts b/apps/web/src/app/billing/shared/billing-shared.module.ts index 35fe33c7e0..0031cff775 100644 --- a/apps/web/src/app/billing/shared/billing-shared.module.ts +++ b/apps/web/src/app/billing/shared/billing-shared.module.ts @@ -12,6 +12,7 @@ import { PaymentMethodComponent } from "./payment-method.component"; import { PaymentComponent } from "./payment.component"; import { SecretsManagerSubscribeComponent } from "./sm-subscribe.component"; import { TaxInfoComponent } from "./tax-info.component"; +import { UpdateLicenseDialogComponent } from "./update-license-dialog.component"; import { UpdateLicenseComponent } from "./update-license.component"; @NgModule({ @@ -24,6 +25,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; PaymentMethodComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent, + UpdateLicenseDialogComponent, OffboardingSurveyComponent, ], exports: [ @@ -34,6 +36,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; BillingHistoryComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent, + UpdateLicenseDialogComponent, OffboardingSurveyComponent, ], }) diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.html b/apps/web/src/app/billing/shared/update-license-dialog.component.html new file mode 100644 index 0000000000..6430c47528 --- /dev/null +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.html @@ -0,0 +1,40 @@ +
+ + + + {{ "licenseFile" | i18n }} +
+ + {{ licenseFile ? licenseFile.name : ("noFileChosen" | i18n) }} +
+ + {{ "licenseFileDesc" | i18n: "bitwarden_premium_license.json" }} +
+
+ + + + +
+
diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.ts b/apps/web/src/app/billing/shared/update-license-dialog.component.ts new file mode 100644 index 0000000000..5f9a1e94be --- /dev/null +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.ts @@ -0,0 +1,38 @@ +import { Component } from "@angular/core"; +import { FormBuilder } from "@angular/forms"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { DialogService } from "@bitwarden/components"; + +import { UpdateLicenseComponent } from "./update-license.component"; + +export enum UpdateLicenseDialogResult { + Updated = "updated", + Cancelled = "cancelled", +} +@Component({ + templateUrl: "update-license-dialog.component.html", +}) +export class UpdateLicenseDialogComponent extends UpdateLicenseComponent { + constructor( + apiService: ApiService, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + organizationApiService: OrganizationApiServiceAbstraction, + formBuilder: FormBuilder, + ) { + super(apiService, i18nService, platformUtilsService, organizationApiService, formBuilder); + } + async submitLicense() { + await this.submit(); + } + submitLicenseDialog = async () => { + await this.submitLicense(); + }; + static open(dialogService: DialogService) { + return dialogService.open(UpdateLicenseDialogComponent); + } +} From 5a25024f591d590d6a1c297b97cf3168ff6fb96c Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 28 May 2024 17:38:15 +0100 Subject: [PATCH 2/7] Change Addons to Add-ons (#9392) --- apps/web/src/locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index c21955179d..e090f6a7ab 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -2102,7 +2102,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" From 3fc2570a0e2437242b880fd1662f31f573ab11c9 Mon Sep 17 00:00:00 2001 From: DanHillesheim <79476558+DanHillesheim@users.noreply.github.com> Date: Tue, 28 May 2024 10:42:53 -0600 Subject: [PATCH 3/7] Trial initiation content updates (#9138) --- .../content/enterprise-content.component.html | 73 +++++++++++------- .../content/logo-badges.component.html | 11 +++ .../content/logo-badges.component.ts | 7 ++ .../content/teams1-content.component.html | 43 +++++++---- .../trial-initiation.module.ts | 2 + .../register-layout/vault-signup-badges.png | Bin 0 -> 20376 bytes 6 files changed, 94 insertions(+), 42 deletions(-) create mode 100644 apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html create mode 100644 apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts create mode 100644 apps/web/src/images/register-layout/vault-signup-badges.png diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html b/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html index 120748d4c0..4abb44db4f 100644 --- a/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html +++ b/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html @@ -1,34 +1,51 @@ -

The Password Manager Trusted by Millions

-
-

Everything enterprises need out of a password manager:

+

Start your 7-day free trial of Bitwarden

+
+

+ Strengthen business security with the password manager designed for seamless administration and + employee usability. +

    -
  • Secure password sharing
  • -
  • - Easy, flexible SSO and SCIM integrations +
  • + Instantly and securely share credentials with the groups and individuals who need them +
  • +
  • + Strengthen employee security practices through centralized administrative control and + policies +
  • +
  • + Streamline user onboarding and automate account provisioning with turnkey SSO and SCIM + integrations +
  • +
  • + Migrate to Bitwarden in minutes with comprehensive import options +
  • +
  • + Save time and increase productivity with autofill and instant device syncing +
  • +
  • + Empower employees to secure their digital life at home, at work, and on the go by offering a + free Families plan to all Enterprise users
  • -
  • Free families plan for users
  • -
  • Quick import and migration tools
  • -
  • Simple, streamlined user experience
  • -
  • Priority support and trainers
- -
- - - -
+
diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html new file mode 100644 index 0000000000..d1b33eab3a --- /dev/null +++ b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html @@ -0,0 +1,11 @@ +
+
+ + third party awards + +
+
diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts new file mode 100644 index 0000000000..c23432b67c --- /dev/null +++ b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts @@ -0,0 +1,7 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: "app-logo-badges", + templateUrl: "logo-badges.component.html", +}) +export class LogoBadgesComponent {} diff --git a/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html b/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html index d26bbabaef..42f99be26b 100644 --- a/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html +++ b/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html @@ -1,21 +1,36 @@ -

Start Your Teams Free Trial Now

-
-
$4 per month / per user
-
Annual subscription
-
+

Start your 7-day free trial for Teams

+

- Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure password - storage and sharing. + Strengthen business security with an easy-to-use password manager your team will love.

-
    -
  • Collaborate and share securely
  • -
  • Deploy and manage quickly and easily
  • -
  • Access anywhere on any device
  • -
  • Create your account to get started
  • +
      +
    • + Instantly and securely share credentials with the groups and individuals who need them +
    • +
    • + Migrate to Bitwarden in minutes with comprehensive import options +
    • +
    • + Save time and increase productivity with autofill and instant device syncing +
    • +
    • + Enhance security practices across your team with easy user management +
    - - +
    diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts index 6e25e97858..57d982fd00 100644 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts +++ b/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts @@ -24,6 +24,7 @@ import { DefaultContentComponent } from "./content/default-content.component"; import { EnterpriseContentComponent } from "./content/enterprise-content.component"; import { Enterprise1ContentComponent } from "./content/enterprise1-content.component"; import { Enterprise2ContentComponent } from "./content/enterprise2-content.component"; +import { LogoBadgesComponent } from "./content/logo-badges.component"; import { LogoCnet5StarsComponent } from "./content/logo-cnet-5-stars.component"; import { LogoCnetComponent } from "./content/logo-cnet.component"; import { LogoForbesComponent } from "./content/logo-forbes.component"; @@ -69,6 +70,7 @@ import { VerticalStepperModule } from "./vertical-stepper/vertical-stepper.modul CnetTeamsContentComponent, AbmEnterpriseContentComponent, AbmTeamsContentComponent, + LogoBadgesComponent, LogoCnet5StarsComponent, LogoCnetComponent, LogoForbesComponent, diff --git a/apps/web/src/images/register-layout/vault-signup-badges.png b/apps/web/src/images/register-layout/vault-signup-badges.png new file mode 100644 index 0000000000000000000000000000000000000000..7a80ffaebb9ad3d6ba162b5237f0b465205f974d GIT binary patch literal 20376 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 Date: Tue, 28 May 2024 20:35:10 +0200 Subject: [PATCH 4/7] Fix missing confirmation toast on vault export (#9398) Co-authored-by: Daniel James Smith --- apps/web/src/app/tools/vault-export/export.component.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/web/src/app/tools/vault-export/export.component.ts b/apps/web/src/app/tools/vault-export/export.component.ts index 7902d2818d..df53e599ed 100644 --- a/apps/web/src/app/tools/vault-export/export.component.ts +++ b/apps/web/src/app/tools/vault-export/export.component.ts @@ -42,4 +42,9 @@ export class ExportComponent extends BaseExportComponent { organizationService, ); } + + protected saved() { + super.saved(); + this.platformUtilsService.showToast("success", null, this.i18nService.t("exportSuccess")); + } } From e14e2627ad7409b5a538bf928a9abdf5858957fe Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Tue, 28 May 2024 14:05:24 -0500 Subject: [PATCH 5/7] [PM-8447] Autofill On Load Causes Extension to Hang After Browser Startup (#9400) --- .../services/autofill.service.spec.ts | 20 +++++++++++++++++++ .../src/autofill/services/autofill.service.ts | 7 ++++++- .../browser/src/background/main.background.ts | 1 + .../src/popup/services/services.module.ts | 1 + 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 24b1c90b3f..23f690544d 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -1,6 +1,8 @@ import { mock, mockReset, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service"; import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service"; @@ -78,12 +80,17 @@ describe("AutofillService", () => { const userVerificationService = mock(); const billingAccountProfileStateService = mock(); const platformUtilsService = mock(); + let activeAccountStatusMock$: BehaviorSubject; + let authService: MockProxy; beforeEach(() => { scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService); inlineMenuVisibilityMock$ = new BehaviorSubject(AutofillOverlayVisibility.OnFieldFocus); autofillSettingsService = mock(); (autofillSettingsService as any).inlineMenuVisibility$ = inlineMenuVisibilityMock$; + activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked); + authService = mock(); + authService.activeAccountStatus$ = activeAccountStatusMock$; autofillService = new AutofillService( cipherService, autofillSettingsService, @@ -95,6 +102,7 @@ describe("AutofillService", () => { billingAccountProfileStateService, scriptInjectorService, accountService, + authService, ); domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); @@ -287,6 +295,18 @@ describe("AutofillService", () => { }); }); + it("skips injecting the autofiller script when the user's account is not unlocked", async () => { + activeAccountStatusMock$.next(AuthenticationStatus.Locked); + + await autofillService.injectAutofillScripts(sender.tab, sender.frameId, true); + + expect(BrowserApi.executeScriptInTab).not.toHaveBeenCalledWith(tabMock.id, { + file: "content/autofiller.js", + frameId: sender.frameId, + ...defaultExecuteScriptOptions, + }); + }); + it("will inject the bootstrap-autofill-overlay script if the user has the autofill overlay enabled", async () => { await autofillService.injectAutofillScripts(sender.tab, sender.frameId); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 5348ca5b9a..9ec2052381 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -3,7 +3,9 @@ import { pairwise } from "rxjs/operators"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; @@ -61,6 +63,7 @@ export default class AutofillService implements AutofillServiceInterface { private billingAccountProfileStateService: BillingAccountProfileStateService, private scriptInjectorService: ScriptInjectorService, private accountService: AccountService, + private authService: AuthService, ) {} /** @@ -113,6 +116,8 @@ export default class AutofillService implements AutofillServiceInterface { // Autofill user settings loaded from state can await the active account state indefinitely // if not guarded by an active account check (e.g. the user is logged in) const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + const authStatus = await firstValueFrom(this.authService.activeAccountStatus$); + const accountIsUnlocked = authStatus === AuthenticationStatus.Unlocked; let overlayVisibility: InlineMenuVisibilitySetting = AutofillOverlayVisibility.Off; let autoFillOnPageLoadIsEnabled = false; @@ -126,7 +131,7 @@ export default class AutofillService implements AutofillServiceInterface { const injectedScripts = [mainAutofillScript]; - if (activeAccount) { + if (activeAccount && accountIsUnlocked) { autoFillOnPageLoadIsEnabled = await this.getAutofillOnPageLoad(); } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index a382a76781..2072f8396e 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -872,6 +872,7 @@ export default class MainBackground { this.billingAccountProfileStateService, this.scriptInjectorService, this.accountService, + this.authService, ); this.auditService = new AuditService(this.cryptoFunctionService, this.apiService); diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index d260b4ec7b..342b3d26a6 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -341,6 +341,7 @@ const safeProviders: SafeProvider[] = [ BillingAccountProfileStateService, ScriptInjectorService, AccountServiceAbstraction, + AuthService, ], }), safeProvider({ From e47f83db80546a6fec4bbd04d2cb49ab05c31c49 Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Tue, 28 May 2024 14:17:20 -0500 Subject: [PATCH 6/7] fix: remove view events menu option for users without permission, refs PM-7024 (#9355) --- apps/web/src/app/vault/org-vault/vault.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/vault/org-vault/vault.component.html b/apps/web/src/app/vault/org-vault/vault.component.html index 06907b9e5d..af8789c906 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.html +++ b/apps/web/src/app/vault/org-vault/vault.component.html @@ -62,7 +62,7 @@ [showPremiumFeatures]="organization?.useTotp" [showBulkMove]="false" [showBulkTrashOptions]="filter.type === 'trash'" - [useEvents]="organization?.useEvents" + [useEvents]="organization?.canAccessEventLogs" [showAdminActions]="true" (onEvent)="onVaultItemsEvent($event)" [showBulkEditCollectionAccess]="organization?.flexibleCollections" From bc170f520748a8f320e0ccbc6fb2edcb31972fb2 Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Tue, 28 May 2024 15:17:30 -0400 Subject: [PATCH 7/7] use valid userId when logging out (#9334) --- apps/browser/src/background/main.background.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 2072f8396e..51d18f15a3 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1333,7 +1333,7 @@ export default class MainBackground { ]); //Needs to be checked before state is cleaned - const needStorageReseed = await this.needsStorageReseed(userId); + const needStorageReseed = await this.needsStorageReseed(userBeingLoggedOut); await this.stateService.clean({ userId: userBeingLoggedOut }); await this.accountService.clean(userBeingLoggedOut);