diff --git a/.github/DISCUSSION_TEMPLATE/password-manager.yml b/.github/DISCUSSION_TEMPLATE/password-manager.yml index bf2d0900db9..bc3938c1962 100644 --- a/.github/DISCUSSION_TEMPLATE/password-manager.yml +++ b/.github/DISCUSSION_TEMPLATE/password-manager.yml @@ -1,8 +1,19 @@ +labels: ["discussions-new"] body: - type: markdown attributes: value: | If you would like to contribute code to the Bitwarden codebase for consideration, please review [https://contributing.bitwarden.com/](https://contributing.bitwarden.com/) before posting. To keep discussion on topic, posts that do not include a proposal for a code contribution you wish to develop will be removed. For feature requests and community discussion, please visit https://community.bitwarden.com/ + - type: dropdown + attributes: + label: Select Topic Area + description: "What would you like to discuss? :warning: For feature requests and product feedback, please visit https://community.bitwarden.com/" + options: + - "✅ Code Contribution Proposal" + - "🚫 Product Feedback" + - "🚫 Feature Request" + validations: + required: true - type: textarea attributes: label: Code Contribution Proposal diff --git a/.github/DISCUSSION_TEMPLATE/secrets-manager.yml b/.github/DISCUSSION_TEMPLATE/secrets-manager.yml index bf2d0900db9..bc3938c1962 100644 --- a/.github/DISCUSSION_TEMPLATE/secrets-manager.yml +++ b/.github/DISCUSSION_TEMPLATE/secrets-manager.yml @@ -1,8 +1,19 @@ +labels: ["discussions-new"] body: - type: markdown attributes: value: | If you would like to contribute code to the Bitwarden codebase for consideration, please review [https://contributing.bitwarden.com/](https://contributing.bitwarden.com/) before posting. To keep discussion on topic, posts that do not include a proposal for a code contribution you wish to develop will be removed. For feature requests and community discussion, please visit https://community.bitwarden.com/ + - type: dropdown + attributes: + label: Select Topic Area + description: "What would you like to discuss? :warning: For feature requests and product feedback, please visit https://community.bitwarden.com/" + options: + - "✅ Code Contribution Proposal" + - "🚫 Product Feedback" + - "🚫 Feature Request" + validations: + required: true - type: textarea attributes: label: Code Contribution Proposal diff --git a/.github/renovate.json5 b/.github/renovate.json5 index fe5ae09deaa..973ba700349 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -4,10 +4,10 @@ enabledManagers: ["cargo", "github-actions", "npm"], packageRules: [ { - // Group all build/test/lint workflows for GitHub Actions together for Platform - // Since they are code owners we don't need to assign a review team in Renovate - // Any changes here should also be reflected in CODEOWNERS - groupName: "github-action minor", + // Group all build/test/lint workflows for GitHub Actions together for Platform. + // Since they are code owners we don't need to assign a review team in Renovate. + // Any changes here should also be reflected in CODEOWNERS. + groupName: "github-action", matchManagers: ["github-actions"], matchFileNames: [ "./github/workflows/automatic-issue-responses.yml", @@ -30,10 +30,10 @@ commitMessagePrefix: "[deps] Platform:", }, { - // Group all release-related workflows for GitHub Actions together for BRE - // Since they are code owners we don't need to assign a review team in Renovate - // Any changes here should also be reflected in CODEOWNERS - groupName: "github-action minor", + // Group all release-related workflows for GitHub Actions together for BRE. + // Since they are code owners we don't need to assign a review team in Renovate. + // Any changes here should also be reflected in CODEOWNERS. + groupName: "github-action", matchManagers: ["github-actions"], matchFileNames: [ "./github/workflows/brew-bump-desktop.yml", @@ -51,7 +51,7 @@ commitMessagePrefix: "[deps] BRE:", }, { - // Disable major and minor updates for TypeScript and Zone.js because they are managed by Angular + // Disable major and minor updates for TypeScript and Zone.js because they are managed by Angular. matchPackageNames: ["typescript", "zone.js"], matchUpdateTypes: ["major", "minor"], description: "Determined by Angular", @@ -72,27 +72,27 @@ enabled: false, }, { - // Renovate should manage patch updates for TypeScript and Zone.js, despite ignoring major and minor + // Renovate should manage patch updates for TypeScript and Zone.js, despite ignoring major and minor. matchPackageNames: ["typescript", "zone.js"], matchUpdateTypes: "patch", }, { - // We want to update all the Jest-related packages together, to reduce PR noise + // We want to update all the Jest-related packages together, to reduce PR noise. groupName: "jest", matchPackageNames: ["@types/jest", "jest", "ts-jest", "jest-preset-angular"], }, { - // We need to group all napi-related packages together to avoid build errors caused by version incompatibilities + // We need to group all napi-related packages together to avoid build errors caused by version incompatibilities. groupName: "napi", matchPackageNames: ["napi", "napi-build", "napi-derive"], }, { - // We need to group all macOS/iOS binding-related packages together to avoid build errors caused by version incompatibilities + // We need to group all macOS/iOS binding-related packages together to avoid build errors caused by version incompatibilities. groupName: "macOS/iOS bindings", matchPackageNames: ["core-foundation", "security-framework", "security-framework-sys"], }, { - // We need to group all zbus-related packages together to avoid build errors caused by version incompatibilities + // We need to group all zbus-related packages together to avoid build errors caused by version incompatibilities. groupName: "zbus", matchPackageNames: ["zbus", "zbus_polkit"], }, diff --git a/.github/workflows/auto-reply-discussions.yml b/.github/workflows/auto-reply-discussions.yml new file mode 100644 index 00000000000..8becc7471c5 --- /dev/null +++ b/.github/workflows/auto-reply-discussions.yml @@ -0,0 +1,112 @@ +name: Auto-reply to Discussions + +on: + discussion: + types: created + +jobs: + reply: + name: Auto-reply + runs-on: ubuntu-22.04 + + steps: + - name: Get discussion label and template name + id: discussion-label + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const discussion = context.payload.discussion; + const template_name = discussion.category.slug; + const label = discussion.labels?.[0]?.name ?? ''; + console.log('Discussion label:', label); + console.log('Discussion category slug:', template_name); + + core.setOutput('label', label); + core.setOutput('template_name', template_name); + + - name: Get selected topic + id: get_selected_topic + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + result-encoding: string + script: | + try { + const body = context.payload.discussion.body; + const match = body.match(/### Select Topic Area\n\n(.*?)\n\n/); + console.log('Match:', match); + console.log('Match1:', match[1]); + return match ? match[1].trim() : ""; + } catch (error) { + console.error('Error getting selected topic:', error); + return ""; + } + + - name: Reply or close Discussion + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + TEMPLATE_NAME: ${{ steps.discussion-label.outputs.template_name }} + TOPIC: ${{ steps.get_selected_topic.outputs.result }} + with: + script: | + async function addComment(discussionId, body) { + await github.graphql(` + mutation AddDiscussionComment($discussionId: ID!, $body: String!) { + addDiscussionComment(input: {discussionId: $discussionId, body: $body}) { + comment { + id + } + } + } + `, { + discussionId, + body + }); + } + + async function closeDiscussion(discussionId) { + await github.graphql(` + mutation { + closeDiscussion(input: {discussionId: "${discussionId}"}) { + discussion { + id + } + } + } + `); + console.log('♻️ Closing discussion'); + } + + const discussion = context.payload.discussion; + const isFeatureRequest = process.env.TEMPLATE_NAME === 'feature-request'; + const isTopicEmpty = !process.env.TOPIC || process.env.TOPIC.trim() === ''; // topic step may have failed + const isCodeTopic = process.env.TOPIC.includes('Code Contribution Proposal'); + const shouldClose = isFeatureRequest || (!isTopicEmpty && !isCodeTopic); + + console.log('Template name:', process.env.TEMPLATE_NAME); + console.log('Topic:', process.env.TOPIC); + console.log('isTopicEmpty:', isTopicEmpty); + console.log('isCodeTopic:', isCodeTopic); + console.log('shouldClose:', shouldClose); + + if (shouldClose) { + const closeMessage = + "Thank you for your contribution! GitHub Discussions is specifically for [proposing code](https://contributing.bitwarden.com/) that you would like to write for the Bitwarden codebase. Since this post does not appear to include a proposal, it will be closed. If you believe this was done in error or have any questions, please feel free to reach out to us!\n\n" + + "- :bulb: For feature requests and general discussions, please visit the [Bitwarden Community Forums](https://community.bitwarden.com/).\n" + + "- :information_source: For questions and support, visit the [Bitwarden Help Center](https://bitwarden.com/help/).\n" + + "- :bug: To report a potential bug, please visit the appropriate repository: [Server](https://github.com/bitwarden/server/issues) | [Clients](https://github.com/bitwarden/clients/issues) | [iOS](https://github.com/bitwarden/ios/issues) | [Android](https://github.com/bitwarden/android/issues)."; + + await addComment(discussion.node_id, closeMessage); + await closeDiscussion(discussion.node_id); + return; + } + + const replyMessage = + ":sparkles: Thank you for your code contribution proposal! While the Bitwarden team reviews your submission, we encourage you to check out our [contribution guidelines](https://contributing.bitwarden.com/).\n\n" + + "Please ensure that your code contribution includes a detailed description of what you would like to contribute, along with any relevant screenshots and links to existing feature requests. This information helps us gather feedback from the community and Bitwarden team members before you start writing code.\n\n" + + "To keep discussions focused, posts that do not include a proposal for a code contribution will be removed.\n\n" + + "- :bulb: For feature requests and general discussion, please visit the [Bitwarden Community Forums](https://community.bitwarden.com/).\n" + + "- :information_source: For questions and support, visit the [Bitwarden Help Center](https://bitwarden.com/help/).\n" + + "- :bug: To report a potential bug, please visit the corresponding repo. [Server](https://github.com/bitwarden/server/issues) | [Clients](https://github.com/bitwarden/clients/issues) | [iOS](https://github.com/bitwarden/ios/issues) | [Android](https://github.com/bitwarden/android/issues)\n\n" + + "Thank you for contributing to Bitwarden!"; + + await addComment(discussion.node_id, replyMessage); diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index 027a2f11e55..32907699747 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -22,7 +22,7 @@ jobs: crowdin_project_id: "308189" steps: - name: Generate GH App token - uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index ac2733e765b..06daf70d7c9 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -66,7 +66,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Generate GH App token - uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} @@ -115,7 +115,7 @@ jobs: version: ${{ inputs.version_number_override }} - name: Generate GH App token - uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} @@ -452,7 +452,7 @@ jobs: - setup steps: - name: Generate GH App token - uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index ef46dbc867d..9431eab37c5 100644 --- a/.github/workflows/version-auto-bump.yml +++ b/.github/workflows/version-auto-bump.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Generate GH App token - uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000..cffe8cdef13 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 54024b83f98..586e7e1f2cf 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2297,6 +2297,9 @@ "privacyPolicy": { "message": "Privacy Policy" }, + "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { + "message": "Your new password cannot be the same as your current password." + }, "hintEqualsPassword": { "message": "Your password hint cannot be the same as your password." }, diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 663fc863f5e..aeee58c6fdf 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -111,6 +111,7 @@ import { } from "@bitwarden/common/platform/abstractions/storage.service"; import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; +import { IpcService } from "@bitwarden/common/platform/ipc"; import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- Used for dependency creation import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; @@ -259,6 +260,7 @@ import { BackgroundBrowserBiometricsService } from "../key-management/biometrics import VaultTimeoutService from "../key-management/vault-timeout/vault-timeout.service"; import { BrowserApi } from "../platform/browser/browser-api"; import { flagEnabled } from "../platform/flags"; +import { IpcBackgroundService } from "../platform/ipc/ipc-background.service"; import { UpdateBadge } from "../platform/listeners/update-badge"; /* eslint-disable no-restricted-imports */ import { ChromeMessageSender } from "../platform/messaging/chrome-message.sender"; @@ -403,6 +405,8 @@ export default class MainBackground { inlineMenuFieldQualificationService: InlineMenuFieldQualificationService; taskService: TaskService; + ipcService: IpcService; + onUpdatedRan: boolean; onReplacedRan: boolean; loginToAutoFill: CipherView = null; @@ -1309,6 +1313,8 @@ export default class MainBackground { ); this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(); + + this.ipcService = new IpcBackgroundService(this.logService); } async bootstrap() { @@ -1382,6 +1388,7 @@ export default class MainBackground { } await this.initOverlayAndTabsBackground(); + await this.ipcService.init(); return new Promise((resolve) => { setTimeout(async () => { diff --git a/apps/browser/src/platform/ipc/background-communication-backend.ts b/apps/browser/src/platform/ipc/background-communication-backend.ts new file mode 100644 index 00000000000..6c5b374dd56 --- /dev/null +++ b/apps/browser/src/platform/ipc/background-communication-backend.ts @@ -0,0 +1,41 @@ +import { IpcMessage, isIpcMessage } from "@bitwarden/common/platform/ipc"; +import { MessageQueue } from "@bitwarden/common/platform/ipc/message-queue"; +import { CommunicationBackend, IncomingMessage, OutgoingMessage } from "@bitwarden/sdk-internal"; + +import { BrowserApi } from "../browser/browser-api"; + +export class BackgroundCommunicationBackend implements CommunicationBackend { + private queue = new MessageQueue(); + + constructor() { + BrowserApi.messageListener("platform.ipc", (message, sender) => { + if (!isIpcMessage(message)) { + return; + } + + if (sender.tab?.id === undefined || sender.tab.id === chrome.tabs.TAB_ID_NONE) { + // Ignore messages from non-tab sources + return; + } + + void this.queue.enqueue({ ...message.message, source: { Web: { id: sender.tab.id } } }); + }); + } + + async send(message: OutgoingMessage): Promise { + if (typeof message.destination === "object" && "Web" in message.destination) { + await BrowserApi.tabSendMessage( + { id: message.destination.Web.id } as chrome.tabs.Tab, + { type: "bitwarden-ipc-message", message } satisfies IpcMessage, + { frameId: 0 }, + ); + return; + } + + throw new Error("Destination not supported."); + } + + async receive(): Promise { + return this.queue.dequeue(); + } +} diff --git a/apps/browser/src/platform/ipc/content/ipc-content-script.ts b/apps/browser/src/platform/ipc/content/ipc-content-script.ts new file mode 100644 index 00000000000..a21ad07be4b --- /dev/null +++ b/apps/browser/src/platform/ipc/content/ipc-content-script.ts @@ -0,0 +1,51 @@ +// TODO: This content script should be dynamically reloaded when the extension is updated, +// to avoid "Extension context invalidated." errors. + +import { isIpcMessage } from "@bitwarden/common/platform/ipc/ipc-message"; + +// Web -> Background +export function sendExtensionMessage(message: unknown) { + if ( + typeof browser !== "undefined" && + typeof browser.runtime !== "undefined" && + typeof browser.runtime.sendMessage !== "undefined" + ) { + void browser.runtime.sendMessage(message); + return; + } + + void chrome.runtime.sendMessage(message); +} + +window.addEventListener("message", (event) => { + if (event.origin !== window.origin) { + return; + } + + if (isIpcMessage(event.data)) { + sendExtensionMessage(event.data); + } +}); + +// Background -> Web +function setupMessageListener() { + function listener(message: unknown) { + if (isIpcMessage(message)) { + void window.postMessage(message); + } + } + + if ( + typeof browser !== "undefined" && + typeof browser.runtime !== "undefined" && + typeof browser.runtime.onMessage !== "undefined" + ) { + browser.runtime.onMessage.addListener(listener); + return; + } + + // eslint-disable-next-line no-restricted-syntax -- This doesn't run in the popup but in the content script + chrome.runtime.onMessage.addListener(listener); +} + +setupMessageListener(); diff --git a/apps/browser/src/platform/ipc/ipc-background.service.ts b/apps/browser/src/platform/ipc/ipc-background.service.ts new file mode 100644 index 00000000000..a87f6bb4acb --- /dev/null +++ b/apps/browser/src/platform/ipc/ipc-background.service.ts @@ -0,0 +1,26 @@ +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; +import { IpcService } from "@bitwarden/common/platform/ipc"; +import { IpcClient } from "@bitwarden/sdk-internal"; + +import { BackgroundCommunicationBackend } from "./background-communication-backend"; + +export class IpcBackgroundService extends IpcService { + private communicationProvider?: BackgroundCommunicationBackend; + + constructor(private logService: LogService) { + super(); + } + + override async init() { + try { + // This function uses classes and functions defined in the SDK, so we need to wait for the SDK to load. + await SdkLoadService.Ready; + this.communicationProvider = new BackgroundCommunicationBackend(); + + await super.initWithClient(new IpcClient(this.communicationProvider)); + } catch (e) { + this.logService.error("[IPC] Initialization failed", e); + } + } +} diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index f386a3167ec..09d1133a4df 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -205,6 +205,7 @@ const mainConfig = { "content/content-message-handler": "./src/autofill/content/content-message-handler.ts", "content/fido2-content-script": "./src/autofill/fido2/content/fido2-content-script.ts", "content/fido2-page-script": "./src/autofill/fido2/content/fido2-page-script.ts", + "content/ipc-content-script": "./src/platform/ipc/content/ipc-content-script.ts", "notification/bar": "./src/autofill/notification/bar.ts", "overlay/menu-button": "./src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts", diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index f1c63780793..8850cbe5a3f 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2072,6 +2072,9 @@ "personalOwnershipSubmitError": { "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." }, + "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { + "message": "Your new password cannot be the same as your current password." + }, "hintEqualsPassword": { "message": "Your password hint cannot be the same as your password." }, diff --git a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts index 48b74dc5e2e..aa02e28b3b3 100644 --- a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts +++ b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts @@ -187,11 +187,11 @@ describe("WebRegistrationFinishService", () => { masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey; passwordInputResult = { masterKey: masterKey, - masterKeyHash: "masterKeyHash", + serverMasterKeyHash: "serverMasterKeyHash", localMasterKeyHash: "localMasterKeyHash", kdfConfig: DEFAULT_KDF_CONFIG, hint: "hint", - password: "password", + newPassword: "newPassword", }; userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey; @@ -239,7 +239,7 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: emailVerificationToken, - masterPasswordHash: passwordInputResult.masterKeyHash, + masterPasswordHash: passwordInputResult.serverMasterKeyHash, masterPasswordHint: passwordInputResult.hint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { @@ -277,7 +277,7 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.masterKeyHash, + masterPasswordHash: passwordInputResult.serverMasterKeyHash, masterPasswordHint: passwordInputResult.hint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { @@ -320,7 +320,7 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.masterKeyHash, + masterPasswordHash: passwordInputResult.serverMasterKeyHash, masterPasswordHint: passwordInputResult.hint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { @@ -365,7 +365,7 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.masterKeyHash, + masterPasswordHash: passwordInputResult.serverMasterKeyHash, masterPasswordHint: passwordInputResult.hint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { @@ -412,7 +412,7 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.masterKeyHash, + masterPasswordHash: passwordInputResult.serverMasterKeyHash, masterPasswordHint: passwordInputResult.hint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { diff --git a/apps/web/src/app/billing/services/free-families-policy.service.ts b/apps/web/src/app/billing/services/free-families-policy.service.ts index 8d4e89d40a0..81cb970cdbe 100644 --- a/apps/web/src/app/billing/services/free-families-policy.service.ts +++ b/apps/web/src/app/billing/services/free-families-policy.service.ts @@ -59,8 +59,8 @@ export class FreeFamiliesPolicyService { return false; } const { belongToOneEnterpriseOrgs, isFreeFamilyPolicyEnabled } = orgStatus; - const canManageSponsorships = organizations.filter((org) => org.canManageSponsorships); - return canManageSponsorships && !(belongToOneEnterpriseOrgs && isFreeFamilyPolicyEnabled); + const hasSponsorshipOrgs = organizations.some((org) => org.canManageSponsorships); + return hasSponsorshipOrgs && !(belongToOneEnterpriseOrgs && isFreeFamilyPolicyEnabled); } checkEnterpriseOrganizationsAndFetchPolicy(): Observable { diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html index 416d4004260..8118b1e512d 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html @@ -1,19 +1,21 @@
diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts index 27ce4dc9f5d..e2a4149a58d 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts @@ -6,7 +6,11 @@ import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom, Subject, switchMap, takeUntil } from "rxjs"; -import { PasswordInputResult, RegistrationFinishService } from "@bitwarden/auth/angular"; +import { + InputPasswordFlow, + PasswordInputResult, + RegistrationFinishService, +} from "@bitwarden/auth/angular"; import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -47,6 +51,8 @@ export type InitiationPath = export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { @ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent; + InputPasswordFlow = InputPasswordFlow; + /** Password Manager or Secrets Manager */ product: ProductType; /** The tier of product being subscribed to */ @@ -363,7 +369,7 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { return; } - await this.logIn(passwordInputResult.password, captchaToken); + await this.logIn(passwordInputResult.newPassword, captchaToken); this.submitting = false; diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 9e6f88d18d6..1fa4fd80e52 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -75,6 +75,7 @@ import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sd import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { ThemeTypes } from "@bitwarden/common/platform/enums"; +import { IpcService } from "@bitwarden/common/platform/ipc"; // eslint-disable-next-line no-restricted-imports -- Needed for DI import { UnsupportedWebPushConnectionService, @@ -122,9 +123,11 @@ import { WebSsoComponentService } from "../auth/core/services/login/web-sso-comp import { AcceptOrganizationInviteService } from "../auth/organization-invite/accept-organization.service"; import { HtmlStorageService } from "../core/html-storage.service"; import { I18nService } from "../core/i18n.service"; +import { WebFileDownloadService } from "../core/web-file-download.service"; import { WebLockComponentService } from "../key-management/lock/services/web-lock-component.service"; import { WebProcessReloadService } from "../key-management/services/web-process-reload.service"; import { WebBiometricsService } from "../key-management/web-biometric.service"; +import { WebIpcService } from "../platform/ipc/web-ipc.service"; import { WebEnvironmentService } from "../platform/web-environment.service"; import { WebMigrationRunner } from "../platform/web-migration-runner"; import { WebSdkLoadService } from "../platform/web-sdk-load.service"; @@ -135,7 +138,6 @@ import { InitService } from "./init.service"; import { ENV_URLS } from "./injection-tokens"; import { ModalService } from "./modal.service"; import { RouterService } from "./router.service"; -import { WebFileDownloadService } from "./web-file-download.service"; import { WebPlatformUtilsService } from "./web-platform-utils.service"; /** @@ -368,6 +370,11 @@ const safeProviders: SafeProvider[] = [ useClass: WebLoginDecryptionOptionsService, deps: [MessagingService, RouterService, AcceptOrganizationInviteService], }), + safeProvider({ + provide: IpcService, + useClass: WebIpcService, + deps: [], + }), safeProvider({ provide: SshImportPromptService, useClass: DefaultSshImportPromptService, diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 24b5631abcc..43547ff5d57 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -14,6 +14,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; +import { IpcService } from "@bitwarden/common/platform/ipc"; import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; @@ -38,6 +39,7 @@ export class InitService { private userAutoUnlockKeyService: UserAutoUnlockKeyService, private accountService: AccountService, private versionService: VersionService, + private ipcService: IpcService, private sdkLoadService: SdkLoadService, private configService: ConfigService, private bulkEncryptService: BulkEncryptService, @@ -72,6 +74,7 @@ export class InitService { htmlEl.classList.add("locale_" + this.i18nService.translationLocale); this.themingService.applyThemeChangesTo(this.document); this.versionService.applyVersionToWindow(); + void this.ipcService.init(); const containerService = new ContainerService(this.keyService, this.encryptService); containerService.attachToGlobal(this.win); diff --git a/apps/web/src/app/platform/ipc/web-communication-provider.ts b/apps/web/src/app/platform/ipc/web-communication-provider.ts new file mode 100644 index 00000000000..85353ab77af --- /dev/null +++ b/apps/web/src/app/platform/ipc/web-communication-provider.ts @@ -0,0 +1,41 @@ +import { Injectable } from "@angular/core"; + +import { IpcMessage, isIpcMessage } from "@bitwarden/common/platform/ipc"; +import { MessageQueue } from "@bitwarden/common/platform/ipc/message-queue"; +import { CommunicationBackend, IncomingMessage, OutgoingMessage } from "@bitwarden/sdk-internal"; + +@Injectable({ providedIn: "root" }) +export class WebCommunicationProvider implements CommunicationBackend { + private queue = new MessageQueue(); + + constructor() { + window.addEventListener("message", async (event: MessageEvent) => { + if (event.origin !== window.origin) { + return; + } + + const message = event.data; + if (!isIpcMessage(message)) { + return; + } + + await this.queue.enqueue({ ...message.message, source: "BrowserBackground" }); + }); + } + + async send(message: OutgoingMessage): Promise { + if (message.destination === "BrowserBackground") { + window.postMessage( + { type: "bitwarden-ipc-message", message } satisfies IpcMessage, + window.location.origin, + ); + return; + } + + throw new Error(`Destination not supported: ${message.destination}`); + } + + receive(): Promise { + return this.queue.dequeue(); + } +} diff --git a/apps/web/src/app/platform/ipc/web-ipc.service.ts b/apps/web/src/app/platform/ipc/web-ipc.service.ts new file mode 100644 index 00000000000..59a715f31f5 --- /dev/null +++ b/apps/web/src/app/platform/ipc/web-ipc.service.ts @@ -0,0 +1,25 @@ +import { inject } from "@angular/core"; + +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; +import { IpcService } from "@bitwarden/common/platform/ipc"; +import { IpcClient } from "@bitwarden/sdk-internal"; + +import { WebCommunicationProvider } from "./web-communication-provider"; + +export class WebIpcService extends IpcService { + private logService = inject(LogService); + private communicationProvider?: WebCommunicationProvider; + + override async init() { + try { + // This function uses classes and functions defined in the SDK, so we need to wait for the SDK to load. + await SdkLoadService.Ready; + this.communicationProvider = new WebCommunicationProvider(); + + await super.initWithClient(new IpcClient(this.communicationProvider)); + } catch (e) { + this.logService.error("[IPC] Initialization failed", e); + } + } +} diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 6c730338fea..3300f73eb3b 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5707,6 +5707,9 @@ "webAuthnSuccess": { "message": "WebAuthn verified successfully! You may close this tab." }, + "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { + "message": "Your new password cannot be the same as your current password." + }, "hintEqualsPassword": { "message": "Your password hint cannot be the same as your password." }, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html index 74aa468c42e..38b4c3bc9de 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html @@ -1,18 +1,18 @@ {{ "loading" | i18n }} -
-