diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 3bcca910194..e82a6f0d107 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -5600,17 +5600,37 @@
"hasItemsVaultNudgeTitle": {
"message": "Welcome to your vault!"
},
- "phishingPageTitle":{
- "message": "Phishing website"
+ "phishingPageTitleV2":{
+ "message": "Phishing attempt detected"
},
- "phishingPageCloseTab": {
- "message": "Close tab"
+ "phishingPageSummary": {
+ "message": "The site you are attempting to visit is a known malicious site and a security risk."
},
- "phishingPageContinue": {
- "message": "Continue"
+ "phishingPageCloseTabV2": {
+ "message": "Close this tab"
},
- "phishingPageLearnWhy": {
- "message": "Why are you seeing this?"
+ "phishingPageContinueV2": {
+ "message": "Continue to this site (not recommended)"
+ },
+ "phishingPageExplanation1": {
+ "message": "This site was found in ",
+ "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name follows this."
+ },
+ "phishingPageExplanation2": {
+ "message": ", an open-source list of known phishing sites used for stealing personal and sensitive information.",
+ "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name precedes this."
+ },
+ "phishingPageLearnMore" : {
+ "message": "Learn more about phishing detection"
+ },
+ "protectedBy": {
+ "message": "Protected by $PRODUCT$",
+ "placeholders": {
+ "product": {
+ "content": "$1",
+ "example": "Bitwarden Phishing Blocker"
+ }
+ }
},
"hasItemsVaultNudgeBodyOne": {
"message": "Autofill items for the current page"
diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index 21609432a4b..4cd61ebead1 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -990,6 +990,7 @@ export default class MainBackground {
this.sendStateProvider = new SendStateProvider(this.stateProvider);
this.sendService = new SendService(
+ this.accountService,
this.keyService,
this.i18nService,
this.keyGenerationService,
diff --git a/apps/browser/src/dirt/phishing-detection/pages/learn-more-component.html b/apps/browser/src/dirt/phishing-detection/pages/learn-more-component.html
deleted file mode 100644
index 5ea79c3f840..00000000000
--- a/apps/browser/src/dirt/phishing-detection/pages/learn-more-component.html
+++ /dev/null
@@ -1,4 +0,0 @@
-{{ "phishingPageLearnWhy"| i18n}}
-
- {{ "learnMore" | i18n }}
-
diff --git a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.html b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.html
index f6e3baf8766..5cac567c5c3 100644
--- a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.html
+++ b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.html
@@ -1,13 +1,46 @@
-
-
- {{ "phishingPageTitle" | i18n }}
-
-
+
+
+
+
{{ "phishingPageTitleV2" | i18n }}
+
-
- {{ "phishingPageCloseTab" | i18n }}
-
-
- {{ "phishingPageContinue" | i18n }}
-
+
+
+
{{ "phishingPageSummary" | i18n }}
+
+
+ {{ phishingHost$ | async }}
+
+
+
+
+ {{ "phishingPageExplanation1" | i18n }}Phishing.Database {{ "phishingPageExplanation2" | i18n }}
+
+
+
+ {{ "phishingPageLearnMore" | i18n }}
+
+
+
+
+
+ {{ "phishingPageCloseTabV2" | i18n }}
+
+
+ {{ "phishingPageContinueV2" | i18n }}
+
+
diff --git a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts
index dc6ab2d329e..4712c94c89e 100644
--- a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts
+++ b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts
@@ -1,10 +1,10 @@
// eslint-disable-next-line no-restricted-imports
import { CommonModule } from "@angular/common";
// eslint-disable-next-line no-restricted-imports
-import { Component, OnDestroy } from "@angular/core";
+import { Component, inject } from "@angular/core";
// eslint-disable-next-line no-restricted-imports
import { ActivatedRoute, RouterModule } from "@angular/router";
-import { Subject, takeUntil } from "rxjs";
+import { map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
@@ -13,12 +13,16 @@ import {
CheckboxModule,
FormFieldModule,
IconModule,
+ IconTileComponent,
LinkModule,
+ CalloutComponent,
+ TypographyModule,
} from "@bitwarden/components";
import { PhishingDetectionService } from "../services/phishing-detection.service";
@Component({
+ selector: "dirt-phishing-warning",
standalone: true,
templateUrl: "phishing-warning.component.html",
imports: [
@@ -31,18 +35,16 @@ import { PhishingDetectionService } from "../services/phishing-detection.service
CheckboxModule,
ButtonModule,
RouterModule,
+ IconTileComponent,
+ CalloutComponent,
+ TypographyModule,
],
})
-export class PhishingWarning implements OnDestroy {
- phishingHost = "";
-
- private destroy$ = new Subject
();
-
- constructor(private activatedRoute: ActivatedRoute) {
- this.activatedRoute.queryParamMap.pipe(takeUntil(this.destroy$)).subscribe((params) => {
- this.phishingHost = params.get("phishingHost") || "";
- });
- }
+export class PhishingWarning {
+ private activatedRoute = inject(ActivatedRoute);
+ protected phishingHost$ = this.activatedRoute.queryParamMap.pipe(
+ map((params) => params.get("phishingHost") || ""),
+ );
async closeTab() {
await PhishingDetectionService.requestClosePhishingWarningPage();
@@ -50,9 +52,4 @@ export class PhishingWarning implements OnDestroy {
async continueAnyway() {
await PhishingDetectionService.requestContinueToDangerousUrl();
}
-
- ngOnDestroy(): void {
- this.destroy$.next();
- this.destroy$.complete();
- }
}
diff --git a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.stories.ts b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.stories.ts
new file mode 100644
index 00000000000..30d3b7faeee
--- /dev/null
+++ b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.stories.ts
@@ -0,0 +1,137 @@
+// TODO: This needs to be dealt with by moving this folder or updating the lint rule.
+/* eslint-disable no-restricted-imports */
+import { ActivatedRoute, RouterModule } from "@angular/router";
+import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
+import { BehaviorSubject, of } from "rxjs";
+
+import { DeactivatedOrg } from "@bitwarden/assets/svg";
+import { ClientType } from "@bitwarden/common/enums";
+import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
+import { AnonLayoutComponent, I18nMockService } from "@bitwarden/components";
+
+import { PhishingWarning } from "./phishing-warning.component";
+import { ProtectedByComponent } from "./protected-by-component";
+
+class MockPlatformUtilsService implements Partial {
+ getApplicationVersion = () => Promise.resolve("Version 2024.1.1");
+ getClientType = () => ClientType.Web;
+}
+
+/**
+ * Helper function to create ActivatedRoute mock with query parameters
+ */
+function mockActivatedRoute(queryParams: Record) {
+ return {
+ provide: ActivatedRoute,
+ useValue: {
+ queryParamMap: of({
+ get: (key: string) => queryParams[key] || null,
+ }),
+ queryParams: of(queryParams),
+ },
+ };
+}
+
+type StoryArgs = {
+ phishingHost: string;
+};
+
+export default {
+ title: "Browser/DIRT/Phishing Warning",
+ component: PhishingWarning,
+ decorators: [
+ moduleMetadata({
+ imports: [AnonLayoutComponent, ProtectedByComponent, RouterModule],
+ providers: [
+ {
+ provide: PlatformUtilsService,
+ useClass: MockPlatformUtilsService,
+ },
+ {
+ provide: I18nService,
+ useFactory: () =>
+ new I18nMockService({
+ accessing: "Accessing",
+ appLogoLabel: "Bitwarden logo",
+ phishingPageTitleV2: "Phishing attempt detected",
+ phishingPageCloseTabV2: "Close this tab",
+ phishingPageSummary:
+ "The site you are attempting to visit is a known malicious site and a security risk.",
+ phishingPageContinueV2: "Continue to this site (not recommended)",
+ phishingPageExplanation1: "This site was found in ",
+ phishingPageExplanation2:
+ ", an open-source list of known phishing sites used for stealing personal and sensitive information.",
+ phishingPageLearnMore: "Learn more about phishing detection",
+ protectedBy: (product) => `Protected by ${product}`,
+ learnMore: "Learn more",
+ danger: "error",
+ }),
+ },
+ {
+ provide: EnvironmentService,
+ useValue: {
+ environment$: new BehaviorSubject({
+ getHostname() {
+ return "bitwarden.com";
+ },
+ }).asObservable(),
+ },
+ },
+ mockActivatedRoute({ phishingHost: "malicious-example.com" }),
+ ],
+ }),
+ ],
+ render: (args) => ({
+ props: args,
+ template: /*html*/ `
+
+
+
+
+ `,
+ }),
+ argTypes: {
+ phishingHost: {
+ control: "text",
+ description: "The suspicious host that was blocked",
+ },
+ },
+ args: {
+ phishingHost: "malicious-example.com",
+ pageIcon: DeactivatedOrg,
+ },
+} satisfies Meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ phishingHost: "malicious-example.com",
+ },
+ decorators: [
+ moduleMetadata({
+ providers: [mockActivatedRoute({ phishingHost: "malicious-example.com" })],
+ }),
+ ],
+};
+
+export const LongHostname: Story = {
+ args: {
+ phishingHost: "very-long-suspicious-phishing-domain-name-that-might-wrap.malicious-example.com",
+ },
+ decorators: [
+ moduleMetadata({
+ providers: [
+ mockActivatedRoute({
+ phishingHost:
+ "very-long-suspicious-phishing-domain-name-that-might-wrap.malicious-example.com",
+ }),
+ ],
+ }),
+ ],
+};
diff --git a/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.html b/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.html
new file mode 100644
index 00000000000..d9f26bc9c90
--- /dev/null
+++ b/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.html
@@ -0,0 +1 @@
+{{ "protectedBy" | i18n: "Bitwarden Phishing Blocker" }}
diff --git a/apps/browser/src/dirt/phishing-detection/pages/learn-more-component.ts b/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.ts
similarity index 63%
rename from apps/browser/src/dirt/phishing-detection/pages/learn-more-component.ts
rename to apps/browser/src/dirt/phishing-detection/pages/protected-by-component.ts
index 1a1e6059204..298c7acd38e 100644
--- a/apps/browser/src/dirt/phishing-detection/pages/learn-more-component.ts
+++ b/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.ts
@@ -4,13 +4,12 @@ import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
-import { ButtonModule } from "@bitwarden/components";
+import { ButtonModule, LinkModule } from "@bitwarden/components";
@Component({
+ selector: "dirt-phishing-protected-by",
standalone: true,
- templateUrl: "learn-more-component.html",
- imports: [CommonModule, CommonModule, JslibModule, ButtonModule],
+ templateUrl: "protected-by-component.html",
+ imports: [CommonModule, CommonModule, JslibModule, ButtonModule, LinkModule],
})
-export class LearnMoreComponent {
- constructor() {}
-}
+export class ProtectedByComponent {}
diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts
index 54245ae17b4..179431b155c 100644
--- a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts
+++ b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts
@@ -116,15 +116,15 @@ export class PhishingDetectionService {
/**
* Sends a message to the phishing detection service to close the warning page
*/
- static requestClosePhishingWarningPage(): void {
- void chrome.runtime.sendMessage({ command: PhishingDetectionMessage.Close });
+ static async requestClosePhishingWarningPage() {
+ await chrome.runtime.sendMessage({ command: PhishingDetectionMessage.Close });
}
/**
* Sends a message to the phishing detection service to continue to the caught url
*/
static async requestContinueToDangerousUrl() {
- void chrome.runtime.sendMessage({ command: PhishingDetectionMessage.Continue });
+ await chrome.runtime.sendMessage({ command: PhishingDetectionMessage.Continue });
}
/**
diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts
index 17a812f451c..cb5e597e78c 100644
--- a/apps/browser/src/popup/app-routing.module.ts
+++ b/apps/browser/src/popup/app-routing.module.ts
@@ -24,7 +24,6 @@ import {
VaultIcon,
LockIcon,
TwoFactorAuthSecurityKeyIcon,
- DeactivatedOrg,
} from "@bitwarden/assets/svg";
import {
LoginComponent,
@@ -54,8 +53,8 @@ import { BlockedDomainsComponent } from "../autofill/popup/settings/blocked-doma
import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component";
import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component";
import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component";
-import { LearnMoreComponent } from "../dirt/phishing-detection/pages/learn-more-component";
import { PhishingWarning } from "../dirt/phishing-detection/pages/phishing-warning.component";
+import { ProtectedByComponent } from "../dirt/phishing-detection/pages/protected-by-component";
import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component";
import BrowserPopupUtils from "../platform/browser/browser-popup-utils";
import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service";
@@ -718,14 +717,13 @@ const routes: Routes = [
},
{
path: "",
- component: LearnMoreComponent,
+ component: ProtectedByComponent,
outlet: "secondary",
},
],
data: {
- pageIcon: DeactivatedOrg,
- pageTitle: "Bitwarden blocked it!",
- pageSubtitle: "Bitwarden blocked a known phishing site from loading.",
+ hideIcon: true,
+ hideBackgroundIllustration: true,
showReadonlyHostname: true,
} satisfies AnonLayoutWrapperData,
},
diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss
index b3d14e65061..01b9d3f05d5 100644
--- a/apps/browser/src/popup/scss/base.scss
+++ b/apps/browser/src/popup/scss/base.scss
@@ -382,7 +382,7 @@ app-root {
}
}
-main:not(popup-page main) {
+main:not(popup-page main):not(auth-anon-layout main) {
position: absolute;
top: 44px;
bottom: 0;
diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts
index 3c80d12af2f..ccc2f3705b9 100644
--- a/apps/cli/src/oss-serve-configurator.ts
+++ b/apps/cli/src/oss-serve-configurator.ts
@@ -211,6 +211,7 @@ export class OssServeConfigurator {
this.serviceContainer.sendService,
this.serviceContainer.sendApiService,
this.serviceContainer.environmentService,
+ this.serviceContainer.accountService,
);
}
diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts
index d13d251bce0..26d07b774b2 100644
--- a/apps/cli/src/service-container/service-container.ts
+++ b/apps/cli/src/service-container/service-container.ts
@@ -552,6 +552,7 @@ export class ServiceContainer {
this.sendStateProvider = new SendStateProvider(this.stateProvider);
this.sendService = new SendService(
+ this.accountService,
this.keyService,
this.i18nService,
this.keyGenerationService,
diff --git a/apps/cli/src/tools/send/commands/create.command.ts b/apps/cli/src/tools/send/commands/create.command.ts
index d4f544d39b7..7803f6f94d4 100644
--- a/apps/cli/src/tools/send/commands/create.command.ts
+++ b/apps/cli/src/tools/send/commands/create.command.ts
@@ -6,6 +6,7 @@ import * as path from "path";
import { firstValueFrom, switchMap } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
@@ -142,7 +143,8 @@ export class SendCreateCommand {
await this.sendApiService.save([encSend, fileData]);
const newSend = await this.sendService.getFromState(encSend.id);
- const decSend = await newSend.decrypt();
+ const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
+ const decSend = await newSend.decrypt(activeUserId);
const env = await firstValueFrom(this.environmentService.environment$);
const res = new SendResponse(decSend, env.getWebVaultUrl());
return Response.success(res);
diff --git a/apps/cli/src/tools/send/commands/edit.command.ts b/apps/cli/src/tools/send/commands/edit.command.ts
index 09f89041cc5..bf53c8a5cb9 100644
--- a/apps/cli/src/tools/send/commands/edit.command.ts
+++ b/apps/cli/src/tools/send/commands/edit.command.ts
@@ -3,6 +3,7 @@
import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
@@ -83,7 +84,8 @@ export class SendEditCommand {
return Response.error("Premium status is required to use this feature.");
}
- let sendView = await send.decrypt();
+ const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
+ let sendView = await send.decrypt(activeUserId);
sendView = SendResponse.toView(req, sendView);
try {
diff --git a/apps/cli/src/tools/send/commands/get.command.ts b/apps/cli/src/tools/send/commands/get.command.ts
index 2d6cc93c781..d5248733490 100644
--- a/apps/cli/src/tools/send/commands/get.command.ts
+++ b/apps/cli/src/tools/send/commands/get.command.ts
@@ -12,6 +12,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
+import { isGuid } from "@bitwarden/guid";
import { DownloadCommand } from "../../../commands/download.command";
import { Response } from "../../../models/response";
@@ -74,13 +75,13 @@ export class SendGetCommand extends DownloadCommand {
}
private async getSendView(id: string): Promise {
- if (Utils.isGuid(id)) {
+ const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
+ if (isGuid(id)) {
const send = await this.sendService.getFromState(id);
if (send != null) {
- return await send.decrypt();
+ return await send.decrypt(activeUserId);
}
} else if (id.trim() !== "") {
- const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
let sends = await this.sendService.getAllDecryptedFromState(activeUserId);
sends = this.searchService.searchSends(sends, id);
if (sends.length > 1) {
diff --git a/apps/cli/src/tools/send/commands/remove-password.command.ts b/apps/cli/src/tools/send/commands/remove-password.command.ts
index 4f7add366be..74676d84a77 100644
--- a/apps/cli/src/tools/send/commands/remove-password.command.ts
+++ b/apps/cli/src/tools/send/commands/remove-password.command.ts
@@ -2,6 +2,8 @@
// @ts-strict-ignore
import { firstValueFrom } from "rxjs";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { SendService } from "@bitwarden/common/tools/send/services//send.service.abstraction";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
@@ -14,6 +16,7 @@ export class SendRemovePasswordCommand {
private sendService: SendService,
private sendApiService: SendApiService,
private environmentService: EnvironmentService,
+ private accountService: AccountService,
) {}
async run(id: string) {
@@ -21,7 +24,8 @@ export class SendRemovePasswordCommand {
await this.sendApiService.removePassword(id);
const updatedSend = await firstValueFrom(this.sendService.get$(id));
- const decSend = await updatedSend.decrypt();
+ const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
+ const decSend = await updatedSend.decrypt(activeUserId);
const env = await firstValueFrom(this.environmentService.environment$);
const webVaultUrl = env.getWebVaultUrl();
const res = new SendResponse(decSend, webVaultUrl);
diff --git a/apps/cli/src/tools/send/send.program.ts b/apps/cli/src/tools/send/send.program.ts
index 2ea73f8c5c8..6c643e04cd0 100644
--- a/apps/cli/src/tools/send/send.program.ts
+++ b/apps/cli/src/tools/send/send.program.ts
@@ -297,6 +297,7 @@ export class SendProgram extends BaseProgram {
this.serviceContainer.sendService,
this.serviceContainer.sendApiService,
this.serviceContainer.environmentService,
+ this.serviceContainer.accountService,
);
const response = await cmd.run(id);
this.processResponse(response);
diff --git a/apps/desktop/src/app/tools/send/add-edit.component.ts b/apps/desktop/src/app/tools/send/add-edit.component.ts
index 025bab66539..bee4f920eda 100644
--- a/apps/desktop/src/app/tools/send/add-edit.component.ts
+++ b/apps/desktop/src/app/tools/send/add-edit.component.ts
@@ -3,11 +3,13 @@
import { CommonModule, DatePipe } from "@angular/common";
import { Component } from "@angular/core";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
+import { firstValueFrom } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -63,7 +65,8 @@ export class AddEditComponent extends BaseAddEditComponent {
async refresh() {
const send = await this.loadSend();
- this.send = await send.decrypt();
+ const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
+ this.send = await send.decrypt(userId);
this.updateFormValues();
this.hasPassword = this.send.password != null && this.send.password.trim() !== "";
}
diff --git a/apps/desktop/src/platform/services/sso-localhost-callback.service.ts b/apps/desktop/src/platform/services/sso-localhost-callback.service.ts
index 14baee51b90..75a84919b07 100644
--- a/apps/desktop/src/platform/services/sso-localhost-callback.service.ts
+++ b/apps/desktop/src/platform/services/sso-localhost-callback.service.ts
@@ -12,10 +12,13 @@ import { MessageSender } from "@bitwarden/common/platform/messaging";
/**
* The SSO Localhost login service uses a local host listener as fallback in case scheme handling deeplinks does not work.
- * This way it is possible to log in with SSO on appimage, snap, and electron dev using the same methods that the cli uses.
+ * This way it is possible to log in with SSO on appimage and electron dev using the same methods that the cli uses.
*/
export class SSOLocalhostCallbackService {
private ssoRedirectUri = "";
+ // We will only track one server at a time for use-case and performance considerations.
+ // This will result in a last-one-wins behavior if multiple SSO flows are started simultaneously.
+ private currentServer: http.Server | null = null;
constructor(
private environmentService: EnvironmentService,
@@ -23,11 +26,30 @@ export class SSOLocalhostCallbackService {
private ssoUrlService: SsoUrlService,
) {
ipcMain.handle("openSsoPrompt", async (event, { codeChallenge, state, email }) => {
- const { ssoCode, recvState } = await this.openSsoPrompt(codeChallenge, state, email);
- this.messagingService.send("ssoCallback", {
- code: ssoCode,
- state: recvState,
- redirectUri: this.ssoRedirectUri,
+ // Close any existing server before starting new one
+ if (this.currentServer) {
+ await this.closeCurrentServer();
+ }
+
+ return this.openSsoPrompt(codeChallenge, state, email).then(({ ssoCode, recvState }) => {
+ this.messagingService.send("ssoCallback", {
+ code: ssoCode,
+ state: recvState,
+ redirectUri: this.ssoRedirectUri,
+ });
+ });
+ });
+ }
+
+ private async closeCurrentServer(): Promise {
+ if (!this.currentServer) {
+ return;
+ }
+
+ return new Promise((resolve) => {
+ this.currentServer!.close(() => {
+ this.currentServer = null;
+ resolve();
});
});
}
@@ -59,6 +81,7 @@ export class SSOLocalhostCallbackService {
"You may now close this tab and return to the app.
" +
"