diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index c96fadbd422..75d86dd2b41 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -83,7 +83,7 @@ "message": "Копиране на потребителското име" }, "copyNumber": { - "message": "Копиране на номера" + "message": "Копиране на но̀мера" }, "copySecurityCode": { "message": "Копиране на кода за сигурност" @@ -257,7 +257,7 @@ "message": "Избор" }, "generatePassword": { - "message": "Генериране на парола" + "message": "Нова парола" }, "regeneratePassword": { "message": "Регенериране на паролата" diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index bcfab0cdbc8..c366c3a00b6 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -726,7 +726,7 @@ "message": "درباره سازمان‌ها اطلاعات کسب کنید" }, "learnOrgConfirmation": { - "message": "Bitwarden به شما اجازه می‌دهد با استفاده از سازماندهی، موارد گاوصندوق خود را با دیگران به اشتراک بگذارید. آیا مایل به بازدید از وب سایت bitwarden.com برای کسب اطلاعات بیشتر هستید؟" + "message": "Bitwarden به شما اجازه می‌دهد با استفاده از سازمان، موارد گاوصندوق خود را با دیگران به اشتراک بگذارید. آیا مایل به بازدید از وب سایت bitwarden.com برای کسب اطلاعات بیشتر هستید؟" }, "moveToOrganization": { "message": "انتقال به سازمان" diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index d55a5bfe197..c496e7628d9 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -92,13 +92,13 @@ "message": "Аутоматско допуњавање" }, "autoFillLogin": { - "message": "Auto-fill login" + "message": "Ауто-пуњење пријаве" }, "autoFillCard": { - "message": "Auto-fill card" + "message": "Ауто-пуњење картице" }, "autoFillIdentity": { - "message": "Auto-fill identity" + "message": "Ауто-пуњење идентитета" }, "generatePasswordCopied": { "message": "Генериши Лозинку (копирано)" @@ -110,19 +110,19 @@ "message": "Нема одговарајућих пријављивања." }, "noCards": { - "message": "No cards" + "message": "Нема карте" }, "noIdentities": { - "message": "No identities" + "message": "Нема идентитета" }, "addLoginMenu": { - "message": "Add login" + "message": "Нема пријаве" }, "addCardMenu": { - "message": "Add card" + "message": "Додати картицу" }, "addIdentityMenu": { - "message": "Add identity" + "message": "Додати идентитет" }, "unlockVaultMenu": { "message": "Откључај свој сеф" diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index ca0b8de6580..283665f4977 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -92,10 +92,10 @@ "message": "Fyll i automatiskt" }, "autoFillLogin": { - "message": "Auto-fill login" + "message": "Autofyll inloggning" }, "autoFillCard": { - "message": "Auto-fill card" + "message": "Autofyll kort" }, "autoFillIdentity": { "message": "Auto-fill identity" diff --git a/apps/browser/src/auth/popup/two-factor.component.html b/apps/browser/src/auth/popup/two-factor.component.html index 7fec67378cc..d03c675abdc 100644 --- a/apps/browser/src/auth/popup/two-factor.component.html +++ b/apps/browser/src/auth/popup/two-factor.component.html @@ -113,7 +113,10 @@

" >
- +
diff --git a/apps/browser/src/autofill/content/autofill.js b/apps/browser/src/autofill/content/autofill.js index 2f3857d3fa8..ef0fb73408b 100644 --- a/apps/browser/src/autofill/content/autofill.js +++ b/apps/browser/src/autofill/content/autofill.js @@ -993,11 +993,6 @@ function fillTheElement(el, op) { var shouldCheck; if (el && null !== op && void 0 !== op && !(el.disabled || el.a || el.readOnly)) { - const tabURLChanged = !fillScript.savedUrls?.some(url => url.startsWith(window.location.origin)) - // Check to make sure the page location didn't change - if (tabURLChanged) { - return; - } switch (markTheFilling && el.form && !el.form.opfilled && (el.form.opfilled = true), el.type ? el.type.toLowerCase() : null) { case 'checkbox': diff --git a/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts b/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts index 0ab74875fbf..828d768ca25 100644 --- a/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts +++ b/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts @@ -108,7 +108,6 @@ describe("InsertAutofillContentService", () => { jest.spyOn(insertAutofillContentService as any, "fillingWithinSandboxedIframe"); jest.spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill"); jest.spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill"); - jest.spyOn(insertAutofillContentService as any, "tabURLChanged"); jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); insertAutofillContentService.fillForm(fillScript); @@ -120,7 +119,6 @@ describe("InsertAutofillContentService", () => { expect( insertAutofillContentService["userCancelledUntrustedIframeAutofill"] ).not.toHaveBeenCalled(); - expect(insertAutofillContentService["tabURLChanged"]).not.toHaveBeenCalled(); expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled(); }); @@ -130,7 +128,6 @@ describe("InsertAutofillContentService", () => { .mockReturnValue(true); jest.spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill"); jest.spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill"); - jest.spyOn(insertAutofillContentService as any, "tabURLChanged"); jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); insertAutofillContentService.fillForm(fillScript); @@ -142,7 +139,6 @@ describe("InsertAutofillContentService", () => { expect( insertAutofillContentService["userCancelledUntrustedIframeAutofill"] ).not.toHaveBeenCalled(); - expect(insertAutofillContentService["tabURLChanged"]).not.toHaveBeenCalled(); expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled(); }); @@ -154,7 +150,6 @@ describe("InsertAutofillContentService", () => { .spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill") .mockReturnValue(true); jest.spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill"); - jest.spyOn(insertAutofillContentService as any, "tabURLChanged"); jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); insertAutofillContentService.fillForm(fillScript); @@ -164,7 +159,6 @@ describe("InsertAutofillContentService", () => { expect( insertAutofillContentService["userCancelledUntrustedIframeAutofill"] ).not.toHaveBeenCalled(); - expect(insertAutofillContentService["tabURLChanged"]).not.toHaveBeenCalled(); expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled(); }); @@ -178,7 +172,6 @@ describe("InsertAutofillContentService", () => { jest .spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill") .mockReturnValue(true); - jest.spyOn(insertAutofillContentService as any, "tabURLChanged").mockReturnValue(false); jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); insertAutofillContentService.fillForm(fillScript); @@ -188,31 +181,6 @@ describe("InsertAutofillContentService", () => { expect( insertAutofillContentService["userCancelledUntrustedIframeAutofill"] ).toHaveBeenCalled(); - expect(insertAutofillContentService["tabURLChanged"]).not.toHaveBeenCalled(); - expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled(); - }); - - it("returns early if the page location origin does not match against any of the cipher saved URLs", () => { - jest - .spyOn(insertAutofillContentService as any, "fillingWithinSandboxedIframe") - .mockReturnValue(false); - jest - .spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill") - .mockReturnValue(false); - jest - .spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill") - .mockReturnValue(false); - jest.spyOn(insertAutofillContentService as any, "tabURLChanged").mockReturnValue(true); - jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); - - insertAutofillContentService.fillForm(fillScript); - - expect(insertAutofillContentService["fillingWithinSandboxedIframe"]).toHaveBeenCalled(); - expect(insertAutofillContentService["userCancelledInsecureUrlAutofill"]).toHaveBeenCalled(); - expect( - insertAutofillContentService["userCancelledUntrustedIframeAutofill"] - ).toHaveBeenCalled(); - expect(insertAutofillContentService["tabURLChanged"]).toHaveBeenCalled(); expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled(); }); @@ -226,7 +194,6 @@ describe("InsertAutofillContentService", () => { jest .spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill") .mockReturnValue(false); - jest.spyOn(insertAutofillContentService as any, "tabURLChanged").mockReturnValue(false); jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); insertAutofillContentService.fillForm(fillScript); @@ -236,7 +203,6 @@ describe("InsertAutofillContentService", () => { expect( insertAutofillContentService["userCancelledUntrustedIframeAutofill"] ).toHaveBeenCalled(); - expect(insertAutofillContentService["tabURLChanged"]).toHaveBeenCalled(); expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenCalledTimes(3); expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenNthCalledWith( 1, diff --git a/apps/browser/src/autofill/services/insert-autofill-content.service.ts b/apps/browser/src/autofill/services/insert-autofill-content.service.ts index ad40b76fbcd..46cb53d4f59 100644 --- a/apps/browser/src/autofill/services/insert-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/insert-autofill-content.service.ts @@ -38,8 +38,7 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf !fillScript.script?.length || this.fillingWithinSandboxedIframe() || this.userCancelledInsecureUrlAutofill(fillScript.savedUrls) || - this.userCancelledUntrustedIframeAutofill(fillScript) || - this.tabURLChanged(fillScript.savedUrls) + this.userCancelledUntrustedIframeAutofill(fillScript) ) { return; } @@ -47,16 +46,6 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf fillScript.script.forEach(this.runFillScriptAction); } - /** - * Determines if the page URL no longer matches one of the cipher's savedURL domains - * @param {string[] | null} savedUrls - * @returns {boolean} - * @private - */ - private tabURLChanged(savedUrls?: AutofillScript["savedUrls"]): boolean { - return savedUrls && !savedUrls.some((url) => url.startsWith(window.location.origin)); - } - /** * Identifies if the execution of this script is happening * within a sandboxed iframe. diff --git a/apps/browser/src/vault/popup/components/vault/view.component.ts b/apps/browser/src/vault/popup/components/vault/view.component.ts index 6c9f3967d56..29027b33505 100644 --- a/apps/browser/src/vault/popup/components/vault/view.component.ts +++ b/apps/browser/src/vault/popup/components/vault/view.component.ts @@ -331,11 +331,21 @@ export class ViewComponent extends BaseViewComponent { } private async doAutofill() { + const originalTabURL = this.tab.url?.length && new URL(this.tab.url); + if (!(await this.promptPassword())) { return false; } - if (this.pageDetails == null || this.pageDetails.length === 0) { + const currentTabURL = this.tab.url?.length && new URL(this.tab.url); + + const originalTabHostPath = + originalTabURL && `${originalTabURL.origin}${originalTabURL.pathname}`; + const currentTabHostPath = currentTabURL && `${currentTabURL.origin}${currentTabURL.pathname}`; + + const tabUrlChanged = originalTabHostPath !== currentTabHostPath; + + if (this.pageDetails == null || this.pageDetails.length === 0 || tabUrlChanged) { this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); return false; } diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 0830fabf13d..69d1c0074fa 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -19,7 +19,7 @@ "**/node_modules/@bitwarden/desktop-native/index.js", "**/node_modules/@bitwarden/desktop-native/desktop_native.${platform}-${arch}*.node" ], - "electronVersion": "26.3.0", + "electronVersion": "25.9.1", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 444753e9206..68d2cc0f87f 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2023.9.2", + "version": "2023.9.3", "keywords": [ "bitwarden", "password", @@ -19,8 +19,10 @@ "postinstall": "electron-rebuild", "start": "cross-env ELECTRON_IS_DEV=0 ELECTRON_NO_UPDATER=1 electron ./build", "build-native": "cd desktop_native && npm run build", - "build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"", + "build": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", "build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"", + "build:preload": "cross-env NODE_ENV=production webpack --config webpack.preload.js", + "build:preload:watch": "cross-env NODE_ENV=production webpack --config webpack.preload.js --watch", "build:main": "cross-env NODE_ENV=production webpack --config webpack.main.js", "build:main:dev": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js", "build:main:watch": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js --watch", diff --git a/apps/desktop/scripts/start.js b/apps/desktop/scripts/start.js index 19b11125061..388bf09405c 100644 --- a/apps/desktop/scripts/start.js +++ b/apps/desktop/scripts/start.js @@ -13,6 +13,11 @@ concurrently( command: "npm run build:main:watch", prefixColor: "yellow", }, + { + name: "Prel", + command: "npm run build:preload:watch", + prefixColor: "magenta", + }, { name: "Rend", command: "npm run build:renderer:watch", diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 7ccf4aca770..a2a9e71a32b 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -21,7 +21,6 @@ import { DialogService } from "@bitwarden/components"; import { flagEnabled } from "../../platform/flags"; import { ElectronStateService } from "../../platform/services/electron-state.service.abstraction"; -import { isWindowsStore } from "../../utils"; import { SetPinComponent } from "../components/set-pin.component"; @Component({ selector: "app-settings", @@ -589,7 +588,7 @@ export class SettingsComponent implements OnInit { this.form.controls.enableBrowserIntegration.setValue(false); return; - } else if (isWindowsStore()) { + } else if (ipc.platform.isWindowsStore) { await this.dialogService.openSimpleDialog({ title: { key: "browserIntegrationUnsupportedTitle" }, content: { key: "browserIntegrationWindowsStoreDesc" }, diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 34262c3a309..65ac83b59fd 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -10,7 +10,6 @@ import { } from "@angular/core"; import { DomSanitizer } from "@angular/platform-browser"; import { Router } from "@angular/router"; -import { ipcRenderer } from "electron"; import { IndividualConfig, ToastrService } from "ngx-toastr"; import { firstValueFrom, Subject, takeUntil } from "rxjs"; @@ -227,7 +226,7 @@ export class AppComponent implements OnInit, OnDestroy { this.systemService.cancelProcessReload(); break; case "reloadProcess": - ipcRenderer.send("reload-process"); + ipc.platform.reloadProcess(); break; case "syncStarted": break; diff --git a/apps/desktop/src/app/main.ts b/apps/desktop/src/app/main.ts index 7d99e48ea22..c22d4eb9e10 100644 --- a/apps/desktop/src/app/main.ts +++ b/apps/desktop/src/app/main.ts @@ -1,8 +1,12 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; +import { ipc } from "../preload"; import { isDev } from "../utils"; +// Temporary polyfill for preload script +(window as any).ipc = ipc; + require("../scss/styles.scss"); require("../scss/tailwind.css"); diff --git a/apps/desktop/src/app/services/desktop-theming.service.ts b/apps/desktop/src/app/services/desktop-theming.service.ts index 21277dfd736..3157ad9f661 100644 --- a/apps/desktop/src/app/services/desktop-theming.service.ts +++ b/apps/desktop/src/app/services/desktop-theming.service.ts @@ -1,5 +1,4 @@ import { Injectable } from "@angular/core"; -import { ipcRenderer } from "electron"; import { ThemingService } from "@bitwarden/angular/services/theming/theming.service"; import { ThemeType } from "@bitwarden/common/enums"; @@ -7,12 +6,10 @@ import { ThemeType } from "@bitwarden/common/enums"; @Injectable() export class DesktopThemingService extends ThemingService { protected async getSystemTheme(): Promise { - return await ipcRenderer.invoke("systemTheme"); + return await ipc.platform.getSystemTheme(); } protected monitorSystemThemeChanges(): void { - ipcRenderer.on("systemThemeUpdated", (_event, theme: ThemeType) => - this.updateSystemTheme(theme) - ); + ipc.platform.onSystemThemeUpdated((theme: ThemeType) => this.updateSystemTheme(theme)); } } diff --git a/apps/desktop/src/auth/two-factor.component.html b/apps/desktop/src/auth/two-factor.component.html index cd21f91f59e..2b9a1722ee0 100644 --- a/apps/desktop/src/auth/two-factor.component.html +++ b/apps/desktop/src/auth/two-factor.component.html @@ -83,7 +83,10 @@

{{ title }}

" >
- +
diff --git a/apps/desktop/src/global.d.ts b/apps/desktop/src/global.d.ts index 1b85bb1b6b1..4d103b2cdef 100644 --- a/apps/desktop/src/global.d.ts +++ b/apps/desktop/src/global.d.ts @@ -1 +1,2 @@ declare module "forcefocus"; +declare const ipc: typeof import("./preload").ipc; diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index 4ea28c74c5f..e326b9e9e1d 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -143,6 +143,7 @@ export class WindowMain { backgroundColor: await this.getBackgroundColor(), alwaysOnTop: this.enableAlwaysOnTop, webPreferences: { + // preload: path.join(__dirname, "preload.js"), spellcheck: false, nodeIntegration: true, backgroundThrottling: false, diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 6112806d32a..bf2744cd873 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2023.9.2", + "version": "2023.9.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2023.9.2", + "version": "2023.9.3", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 3f4163c160a..9421f7a5277 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2023.9.2", + "version": "2023.9.3", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts new file mode 100644 index 00000000000..1ea4f3b91b4 --- /dev/null +++ b/apps/desktop/src/platform/preload.ts @@ -0,0 +1,31 @@ +import { ipcRenderer } from "electron"; + +import { DeviceType, ThemeType } from "@bitwarden/common/enums"; + +import { isDev, isWindowsStore } from "../utils"; + +export default { + versions: { + app: (): Promise => ipcRenderer.invoke("appVersion"), + }, + deviceType: deviceType(), + isDev: isDev(), + isWindowsStore: isWindowsStore(), + reloadProcess: () => ipcRenderer.send("reload-process"), + + getSystemTheme: (): Promise => ipcRenderer.invoke("systemTheme"), + onSystemThemeUpdated: (callback: (theme: ThemeType) => void) => { + ipcRenderer.on("systemThemeUpdated", (_event, theme: ThemeType) => callback(theme)); + }, +}; + +function deviceType(): DeviceType { + switch (process.platform) { + case "win32": + return DeviceType.WindowsDesktop; + case "darwin": + return DeviceType.MacOsDesktop; + default: + return DeviceType.LinuxDesktop; + } +} diff --git a/apps/desktop/src/platform/services/electron-platform-utils.service.ts b/apps/desktop/src/platform/services/electron-platform-utils.service.ts index dbc35de9311..6c99507b12b 100644 --- a/apps/desktop/src/platform/services/electron-platform-utils.service.ts +++ b/apps/desktop/src/platform/services/electron-platform-utils.service.ts @@ -9,31 +9,14 @@ import { } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { BiometricMessage, BiometricStorageAction } from "../../types/biometric-message"; -import { isDev, isMacAppStore } from "../../utils"; +import { isMacAppStore } from "../../utils"; import { ClipboardWriteMessage } from "../types/clipboard"; export class ElectronPlatformUtilsService implements PlatformUtilsService { - private deviceCache: DeviceType = null; - constructor(protected i18nService: I18nService, private messagingService: MessagingService) {} getDevice(): DeviceType { - if (!this.deviceCache) { - switch (process.platform) { - case "win32": - this.deviceCache = DeviceType.WindowsDesktop; - break; - case "darwin": - this.deviceCache = DeviceType.MacOsDesktop; - break; - case "linux": - default: - this.deviceCache = DeviceType.LinuxDesktop; - break; - } - } - - return this.deviceCache; + return ipc.platform.deviceType; } getDeviceString(): string { @@ -82,7 +65,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { } getApplicationVersion(): Promise { - return ipcRenderer.invoke("appVersion"); + return ipc.platform.versions.app(); } async getApplicationVersionNumber(): Promise { @@ -92,7 +75,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { // Temporarily restricted to only Windows until https://github.com/electron/electron/pull/28349 // has been merged and an updated electron build is available. supportsWebAuthn(win: Window): boolean { - return process.platform === "win32"; + return this.getDevice() === DeviceType.WindowsDesktop; } supportsDuo(): boolean { @@ -114,7 +97,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { } isDev(): boolean { - return isDev(); + return ipc.platform.isDev; } isSelfHost(): boolean { diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts new file mode 100644 index 00000000000..a6ddfdefca5 --- /dev/null +++ b/apps/desktop/src/preload.ts @@ -0,0 +1,19 @@ +// import { contextBridge } from "electron"; +import platform from "./platform/preload"; + +/** + * Bitwarden Preload script. + * + * This file contains the "glue" between the main process and the renderer process. Please ensure + * that you have read through the following articles before modifying any preload script. + * + * https://www.electronjs.org/docs/latest/tutorial/tutorial-preload + * https://www.electronjs.org/docs/latest/api/context-bridge + */ + +// Each team owns a subspace of the `ipc` global variable in the renderer. +export const ipc = { + platform, +}; + +// contextBridge.exposeInMainWorld("ipc", ipc); diff --git a/apps/desktop/src/scss/list.scss b/apps/desktop/src/scss/list.scss index ec56eaa6c88..39e520f7d89 100644 --- a/apps/desktop/src/scss/list.scss +++ b/apps/desktop/src/scss/list.scss @@ -120,8 +120,12 @@ .item-content { display: block; + overflow-x: hidden; .item-title { display: block; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; .title-badges { @include themify($themes) { color: themed("mutedColor"); diff --git a/apps/desktop/webpack.main.js b/apps/desktop/webpack.main.js index efd1de20d13..59e043fa12e 100644 --- a/apps/desktop/webpack.main.js +++ b/apps/desktop/webpack.main.js @@ -67,7 +67,6 @@ const main = { ], }, plugins: [ - new CleanWebpackPlugin(), new CopyWebpackPlugin({ patterns: [ "./src/package.json", diff --git a/apps/desktop/webpack.preload.js b/apps/desktop/webpack.preload.js new file mode 100644 index 00000000000..721d0567ca4 --- /dev/null +++ b/apps/desktop/webpack.preload.js @@ -0,0 +1,63 @@ +const path = require("path"); +const { merge } = require("webpack-merge"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); +const configurator = require("./config/config"); +const { EnvironmentPlugin } = require("webpack"); + +const NODE_ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV; + +console.log("Preload process config"); +const envConfig = configurator.load(NODE_ENV); +configurator.log(envConfig); + +const common = { + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules\/(?!(@bitwarden)\/).*/, + }, + ], + }, + plugins: [], + resolve: { + extensions: [".tsx", ".ts", ".js"], + plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })], + }, +}; + +const prod = { + output: { + filename: "[name].js", + path: path.resolve(__dirname, "build"), + }, +}; + +const dev = { + output: { + filename: "[name].js", + path: path.resolve(__dirname, "build"), + devtoolModuleFilenameTemplate: "[absolute-resource-path]", + }, + devtool: "cheap-source-map", +}; + +const main = { + mode: NODE_ENV, + target: "electron-preload", + node: { + __dirname: false, + __filename: false, + }, + entry: { + preload: "./src/preload.ts", + }, + optimization: { + minimize: false, + }, +}; + +module.exports = merge(common, NODE_ENV === "development" ? dev : prod, main); diff --git a/apps/desktop/webpack.renderer.js b/apps/desktop/webpack.renderer.js index 64eef5729f6..ea1c350c389 100644 --- a/apps/desktop/webpack.renderer.js +++ b/apps/desktop/webpack.renderer.js @@ -62,6 +62,8 @@ const common = { const renderer = { mode: NODE_ENV, devtool: "source-map", + // TODO: Replace this with web. + // target: "web", target: "electron-renderer", node: { __dirname: false, diff --git a/apps/web/src/app/auth/auth.module.ts b/apps/web/src/app/auth/auth.module.ts index 49be17aa264..056b9f161f9 100644 --- a/apps/web/src/app/auth/auth.module.ts +++ b/apps/web/src/app/auth/auth.module.ts @@ -1,12 +1,11 @@ import { NgModule } from "@angular/core"; -import { CoreAuthModule } from "./core"; -import { SettingsModule } from "./settings/settings.module"; +import { AuthSettingsModule } from "./settings/settings.module"; @NgModule({ - imports: [CoreAuthModule, SettingsModule], + imports: [AuthSettingsModule], declarations: [], providers: [], - exports: [SettingsModule], + exports: [AuthSettingsModule], }) export class AuthModule {} diff --git a/apps/web/src/app/auth/core/core.module.ts b/apps/web/src/app/auth/core/core.module.ts deleted file mode 100644 index e196b1c3d76..00000000000 --- a/apps/web/src/app/auth/core/core.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NgModule, Optional, SkipSelf } from "@angular/core"; - -import { WebauthnLoginApiService } from "./services/webauthn-login/webauthn-login-api.service"; -import { WebauthnLoginService } from "./services/webauthn-login/webauthn-login.service"; - -@NgModule({ - providers: [WebauthnLoginService, WebauthnLoginApiService], -}) -export class CoreAuthModule { - constructor(@Optional() @SkipSelf() parentModule?: CoreAuthModule) { - if (parentModule) { - throw new Error("CoreAuthModule is already loaded. Import it in AuthModule only"); - } - } -} diff --git a/apps/web/src/app/auth/core/index.ts b/apps/web/src/app/auth/core/index.ts index 3d2d739adf9..b2221a94a89 100644 --- a/apps/web/src/app/auth/core/index.ts +++ b/apps/web/src/app/auth/core/index.ts @@ -1,2 +1 @@ export * from "./services"; -export * from "./core.module"; diff --git a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-api.service.ts b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-api.service.ts index 33e1aea369b..6dc61563491 100644 --- a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-api.service.ts +++ b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login-api.service.ts @@ -1,25 +1,20 @@ import { Injectable } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { Verification } from "@bitwarden/common/types/verification"; import { SaveCredentialRequest } from "./request/save-credential.request"; import { WebauthnLoginCredentialCreateOptionsResponse } from "./response/webauthn-login-credential-create-options.response"; import { WebauthnLoginCredentialResponse } from "./response/webauthn-login-credential.response"; -@Injectable() +@Injectable({ providedIn: "root" }) export class WebauthnLoginApiService { - constructor( - private apiService: ApiService, - private userVerificationService: UserVerificationService - ) {} + constructor(private apiService: ApiService) {} async getCredentialCreateOptions( - verification: Verification + request: SecretVerificationRequest ): Promise { - const request = await this.userVerificationService.buildRequest(verification); const response = await this.apiService.send("POST", "/webauthn/options", request, true, true); return new WebauthnLoginCredentialCreateOptionsResponse(response); } @@ -33,8 +28,7 @@ export class WebauthnLoginApiService { return this.apiService.send("GET", "/webauthn", null, true, true); } - async deleteCredential(credentialId: string, verification: Verification): Promise { - const request = await this.userVerificationService.buildRequest(verification); + async deleteCredential(credentialId: string, request: SecretVerificationRequest): Promise { await this.apiService.send("POST", `/webauthn/${credentialId}/delete`, request, true, true); } } diff --git a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login.service.spec.ts b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login.service.spec.ts index 070513f19e8..1e4f1fa7717 100644 --- a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login.service.spec.ts +++ b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login.service.spec.ts @@ -1,5 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; + import { CredentialCreateOptionsView } from "../../views/credential-create-options.view"; import { WebauthnLoginApiService } from "./webauthn-login-api.service"; @@ -7,6 +9,7 @@ import { WebauthnLoginService } from "./webauthn-login.service"; describe("WebauthnService", () => { let apiService!: MockProxy; + let userVerificationService!: MockProxy; let credentials: MockProxy; let webauthnService!: WebauthnLoginService; @@ -15,8 +18,9 @@ describe("WebauthnService", () => { window.PublicKeyCredential = class {} as any; window.AuthenticatorAttestationResponse = class {} as any; apiService = mock(); + userVerificationService = mock(); credentials = mock(); - webauthnService = new WebauthnLoginService(apiService, credentials); + webauthnService = new WebauthnLoginService(apiService, userVerificationService, credentials); }); describe("createCredential", () => { diff --git a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login.service.ts b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login.service.ts index 760214961a7..c5979f08c61 100644 --- a/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login.service.ts +++ b/apps/web/src/app/auth/core/services/webauthn-login/webauthn-login.service.ts @@ -1,6 +1,7 @@ import { Injectable, Optional } from "@angular/core"; import { BehaviorSubject, filter, from, map, Observable, shareReplay, switchMap, tap } from "rxjs"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Verification } from "@bitwarden/common/types/verification"; @@ -11,8 +12,10 @@ import { SaveCredentialRequest } from "./request/save-credential.request"; import { WebauthnLoginAttestationResponseRequest } from "./request/webauthn-login-attestation-response.request"; import { WebauthnLoginApiService } from "./webauthn-login-api.service"; -@Injectable() +@Injectable({ providedIn: "root" }) export class WebauthnLoginService { + static readonly MaxCredentialCount = 5; + private navigatorCredentials: CredentialsContainer; private _refresh$ = new BehaviorSubject(undefined); private _loading$ = new BehaviorSubject(true); @@ -27,6 +30,7 @@ export class WebauthnLoginService { constructor( private apiService: WebauthnLoginApiService, + private userVerificationService: UserVerificationService, @Optional() navigatorCredentials?: CredentialsContainer, @Optional() private logService?: LogService ) { @@ -37,7 +41,8 @@ export class WebauthnLoginService { async getCredentialCreateOptions( verification: Verification ): Promise { - const response = await this.apiService.getCredentialCreateOptions(verification); + const request = await this.userVerificationService.buildRequest(verification); + const response = await this.apiService.getCredentialCreateOptions(request); return new CredentialCreateOptionsView(response.options, response.token); } @@ -95,7 +100,8 @@ export class WebauthnLoginService { } async deleteCredential(credentialId: string, verification: Verification): Promise { - await this.apiService.deleteCredential(credentialId, verification); + const request = await this.userVerificationService.buildRequest(verification); + await this.apiService.deleteCredential(credentialId, request); this.refresh(); } diff --git a/apps/web/src/app/auth/settings/settings.module.ts b/apps/web/src/app/auth/settings/settings.module.ts index 282524d07e4..12ae6bcbf5e 100644 --- a/apps/web/src/app/auth/settings/settings.module.ts +++ b/apps/web/src/app/auth/settings/settings.module.ts @@ -11,6 +11,6 @@ import { WebauthnLoginSettingsModule } from "./webauthn-login-settings"; imports: [SharedModule, WebauthnLoginSettingsModule, PasswordCalloutComponent], declarations: [ChangePasswordComponent], providers: [], - exports: [WebauthnLoginSettingsModule, ChangePasswordComponent], + exports: [ChangePasswordComponent], }) -export class SettingsModule {} +export class AuthSettingsModule {} diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.html b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.html index 57a2c545ca1..aadcf5e5960 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.html +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.html @@ -1,5 +1,5 @@
- + {{ "loginWithPasskey" | i18n }} {{ "newPasskey" | i18n }} diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts index 5c93d6f25e2..12af83cac5c 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts @@ -46,6 +46,7 @@ export class CreateCredentialDialogComponent implements OnInit { protected credentialOptions?: CredentialCreateOptionsView; protected deviceResponse?: PublicKeyCredential; protected hasPasskeys$?: Observable; + protected loading$ = this.webauthnService.loading$; constructor( private formBuilder: FormBuilder, diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.html b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.html index 4cfdbbcf7fe..5e87f6d4adf 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.html +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.html @@ -1,5 +1,5 @@ - + {{ "removePasskey" | i18n }} {{ diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts index 7cb03238392..9ee1337ffb2 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts @@ -27,6 +27,7 @@ export class DeleteCredentialDialogComponent implements OnInit, OnDestroy { masterPassword: ["", [Validators.required]], }); protected credential?: WebauthnCredentialView; + protected loading$ = this.webauthnService.loading$; constructor( @Inject(DIALOG_DATA) private params: DeleteCredentialDialogParams, diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html index 23abe02665c..5896d461bfb 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html @@ -22,7 +22,7 @@

- - - + -
{{ credential.name }} + {{ "supportsEncryption" | i18n }} @@ -31,7 +31,7 @@

{{ "encryptionNotSupported" | i18n }}

+
- - - - - -
- - - {{ o.name }} - - -
- -

-
-
-
+ + {{ "addExistingOrganization" | i18n }} + + + + + + + + + {{ o.name }} + + + + + + + + + + + + diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts index d3eff4bc53c..0d61c264e29 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts @@ -1,4 +1,5 @@ -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -10,20 +11,21 @@ import { DialogService } from "@bitwarden/components"; import { WebProviderService } from "../services/web-provider.service"; +interface AddOrganizationDialogData { + providerId: string; + organizations: Organization[]; +} + @Component({ - selector: "provider-add-organization", templateUrl: "add-organization.component.html", }) export class AddOrganizationComponent implements OnInit { - @Input() providerId: string; - @Input() organizations: Organization[]; - @Output() onAddedOrganization = new EventEmitter(); - - provider: Provider; - formPromise: Promise; - loading = true; + protected provider: Provider; + protected loading = true; constructor( + private dialogRef: DialogRef, + @Inject(DIALOG_DATA) protected data: AddOrganizationDialogData, private providerService: ProviderService, private webProviderService: WebProviderService, private i18nService: I18nService, @@ -37,52 +39,53 @@ export class AddOrganizationComponent implements OnInit { } async load() { - if (this.providerId == null) { + if (this.data.providerId == null) { return; } - this.provider = await this.providerService.get(this.providerId); + this.provider = await this.providerService.get(this.data.providerId); this.loading = false; } - async add(organization: Organization) { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - if (this.formPromise) { - return; - } + add(organization: Organization) { + return async () => { + const confirmed = await this.dialogService.openSimpleDialog({ + title: organization.name, + content: { + key: "addOrganizationConfirmation", + placeholders: [organization.name, this.provider.name], + }, + type: "warning", + }); - const confirmed = await this.dialogService.openSimpleDialog({ - title: organization.name, - content: { - key: "addOrganizationConfirmation", - placeholders: [organization.name, this.provider.name], - }, - type: "warning", - }); + if (!confirmed) { + return false; + } - if (!confirmed) { - return false; - } + try { + await this.webProviderService.addOrganizationToProvider( + this.data.providerId, + organization.id + ); + } catch (e) { + this.validationService.showError(e); + return; + } - try { - this.formPromise = this.webProviderService.addOrganizationToProvider( - this.providerId, - organization.id + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("organizationJoinedProvider") ); - await this.formPromise; - } catch (e) { - this.validationService.showError(e); - return; - } finally { - this.formPromise = null; - } - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("organizationJoinedProvider") - ); - this.onAddedOrganization.emit(); + this.dialogRef.close(true); + }; + } + + static open(dialogService: DialogService, data: AddOrganizationDialogData) { + return dialogService.open(AddOrganizationComponent, { + data, + }); } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html index 59d07f7def8..9e3aaf4d29e 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html @@ -97,5 +97,3 @@

{{ "clients" | i18n }}

- - diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts index 329bf5189e7..758c8120353 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts @@ -1,5 +1,6 @@ -import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom } from "rxjs"; import { first } from "rxjs/operators"; import { ModalService } from "@bitwarden/angular/services/modal.service"; @@ -33,8 +34,6 @@ const DisallowedPlanTypes = [ }) // eslint-disable-next-line rxjs-angular/prefer-takeuntil export class ClientsComponent implements OnInit { - @ViewChild("add", { read: ViewContainerRef, static: true }) addModalRef: ViewContainerRef; - providerId: string; searchText: string; addableOrganizations: Organization[]; @@ -135,23 +134,14 @@ export class ClientsComponent implements OnInit { } async addExistingOrganization() { - const [modal] = await this.modalService.openViewRef( - AddOrganizationComponent, - this.addModalRef, - (comp) => { - comp.providerId = this.providerId; - comp.organizations = this.addableOrganizations; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - comp.onAddedOrganization.subscribe(async () => { - try { - await this.load(); - modal.close(); - } catch (e) { - this.logService.error(`Handled exception: ${e}`); - } - }); - } - ); + const dialogRef = AddOrganizationComponent.open(this.dialogService, { + providerId: this.providerId, + organizations: this.addableOrganizations, + }); + + if (await firstValueFrom(dialogRef.closed)) { + await this.load(); + } } async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index b7f3bf9f382..7995e14825f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -1,9 +1,8 @@ import { CommonModule } from "@angular/common"; -import { ComponentFactoryResolver, NgModule } from "@angular/core"; +import { NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { SearchModule } from "@bitwarden/components"; import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; @@ -56,11 +55,4 @@ import { SetupComponent } from "./setup/setup.component"; ], providers: [WebProviderService, ProviderPermissionsGuard], }) -export class ProvidersModule { - constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) { - modalService.registerComponentFactoryResolver( - AddOrganizationComponent, - componentFactoryResolver - ); - } -} +export class ProvidersModule {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts new file mode 100644 index 00000000000..3ff4d998a3d --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm-org-enabled.guard.ts @@ -0,0 +1,29 @@ +import { inject } from "@angular/core"; +import { ActivatedRouteSnapshot, CanActivateFn, createUrlTreeFromSnapshot } from "@angular/router"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; + +/** + * Redirects from root `/sm` to first organization with access to SM + */ +export const organizationEnabledGuard: CanActivateFn = async (route: ActivatedRouteSnapshot) => { + const syncService = inject(SyncService); + const orgService = inject(OrganizationService); + + /** Workaround to avoid service initialization race condition. */ + if ((await syncService.getLastSync()) == null) { + await syncService.fullSync(false); + } + + const org = orgService.get(route.params.organizationId); + if (org == null || !org.canAccessSecretsManager) { + return createUrlTreeFromSnapshot(route, ["/"]); + } + + if (!org.enabled) { + return createUrlTreeFromSnapshot(route, ["/sm", org.id, "organization-suspended"]); + } + + return true; +}; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/sm.guard.ts b/bitwarden_license/bit-web/src/app/secrets-manager/guards/sm.guard.ts similarity index 100% rename from bitwarden_license/bit-web/src/app/secrets-manager/sm.guard.ts rename to bitwarden_license/bit-web/src/app/secrets-manager/guards/sm.guard.ts diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html index e639c5f126d..d7a404bf1dd 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/org-switcher.component.html @@ -7,6 +7,11 @@ [(open)]="open" [exactMatch]="true" > + + = this.organizationService.organizations$.pipe( - map((orgs) => orgs.filter(this.filter).sort((a, b) => a.name.localeCompare(b.name))) + map((orgs) => + orgs + .filter((org) => this.filter(org)) + .sort((a, b) => a.name.localeCompare(b.name)) + .sort((a, b) => (a.enabled ? -1 : 1)) + ) ); + protected activeOrganization$: Observable = combineLatest([ this.route.paramMap, this.organizations$, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index 86fab25608a..868026a8431 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -70,6 +70,7 @@ export class OverviewComponent implements OnInit, OnDestroy { protected userIsAdmin: boolean; protected showOnboarding = false; protected loading = true; + protected organizationEnabled = false; protected view$: Observable<{ allProjects: ProjectListView[]; @@ -107,6 +108,7 @@ export class OverviewComponent implements OnInit, OnDestroy { this.organizationName = org.name; this.userIsAdmin = org.isAdmin; this.loading = true; + this.organizationEnabled = org.enabled; }); const projects$ = combineLatest([ @@ -208,6 +210,7 @@ export class OverviewComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Edit, + organizationEnabled: this.organizationEnabled, projectId: projectId, }, }); @@ -218,6 +221,7 @@ export class OverviewComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -227,6 +231,7 @@ export class OverviewComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -246,6 +251,7 @@ export class OverviewComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -256,6 +262,7 @@ export class OverviewComponent implements OnInit, OnDestroy { organizationId: this.organizationId, operation: OperationType.Edit, secretId: secretId, + organizationEnabled: this.organizationEnabled, }, }); } @@ -273,6 +280,7 @@ export class OverviewComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts index a6a3c958d09..3fd723c7580 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts @@ -18,6 +18,7 @@ export enum OperationType { export interface ProjectOperation { organizationId: string; operation: OperationType; + organizationEnabled: boolean; projectId?: string; } @@ -63,6 +64,15 @@ export class ProjectDialogComponent implements OnInit { } submit = async () => { + if (!this.data.organizationEnabled) { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("projectsCannotCreate") + ); + return; + } + this.formGroup.markAllAsTouched(); if (this.formGroup.invalid) { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts index 2d1690ef0ec..a952a351537 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts @@ -2,6 +2,7 @@ import { Component } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatest, combineLatestWith, filter, Observable, startWith, switchMap } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.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"; @@ -31,6 +32,7 @@ export class ProjectSecretsComponent { private organizationId: string; private projectId: string; protected project$: Observable; + private organizationEnabled: boolean; constructor( private route: ActivatedRoute, @@ -38,7 +40,8 @@ export class ProjectSecretsComponent { private secretService: SecretService, private dialogService: DialogService, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService + private i18nService: I18nService, + private organizationService: OrganizationService ) {} ngOnInit() { @@ -60,6 +63,7 @@ export class ProjectSecretsComponent { switchMap(async ([_, params]) => { this.organizationId = params.organizationId; this.projectId = params.projectId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; return await this.getSecretsByProject(); }) ); @@ -75,6 +79,7 @@ export class ProjectSecretsComponent { organizationId: this.organizationId, operation: OperationType.Edit, secretId: secretId, + organizationEnabled: this.organizationEnabled, }, }); } @@ -93,6 +98,7 @@ export class ProjectSecretsComponent { organizationId: this.organizationId, operation: OperationType.Add, projectId: this.projectId, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts index c87d238d6a8..148ccc79d26 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts @@ -12,6 +12,7 @@ import { takeUntil, } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.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"; @@ -33,7 +34,7 @@ export class ProjectComponent implements OnInit, OnDestroy { private organizationId: string; private projectId: string; - + private organizationEnabled: boolean; private destroy$ = new Subject(); constructor( @@ -42,7 +43,8 @@ export class ProjectComponent implements OnInit, OnDestroy { private router: Router, private dialogService: DialogService, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService + private i18nService: I18nService, + private organizationService: OrganizationService ) {} ngOnInit(): void { @@ -69,6 +71,7 @@ export class ProjectComponent implements OnInit, OnDestroy { this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { this.organizationId = params.organizationId; this.projectId = params.projectId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; }); } @@ -82,6 +85,7 @@ export class ProjectComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Edit, + organizationEnabled: this.organizationEnabled, projectId: this.projectId, }, }); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts index 7128e26a3d8..1066828f216 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatest, lastValueFrom, Observable, startWith, switchMap } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { DialogService } from "@bitwarden/components"; import { ProjectListView } from "../../models/view/project-list.view"; @@ -32,12 +33,14 @@ export class ProjectsComponent implements OnInit { protected search: string; private organizationId: string; + private organizationEnabled: boolean; constructor( private route: ActivatedRoute, private projectService: ProjectService, private accessPolicyService: AccessPolicyService, - private dialogService: DialogService + private dialogService: DialogService, + private organizationService: OrganizationService ) {} ngOnInit() { @@ -48,6 +51,8 @@ export class ProjectsComponent implements OnInit { ]).pipe( switchMap(async ([params]) => { this.organizationId = params.organizationId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; + return await this.getProjects(); }) ); @@ -62,6 +67,7 @@ export class ProjectsComponent implements OnInit { data: { organizationId: this.organizationId, operation: OperationType.Edit, + organizationEnabled: this.organizationEnabled, projectId: projectId, }, }); @@ -72,6 +78,7 @@ export class ProjectsComponent implements OnInit { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts index 426542823f9..70eca54e3c5 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -29,6 +29,7 @@ export interface SecretOperation { operation: OperationType; projectId?: string; secretId?: string; + organizationEnabled: boolean; } @Component({ @@ -163,6 +164,11 @@ export class SecretDialogComponent implements OnInit { } submit = async () => { + if (!this.data.organizationEnabled) { + this.platformUtilsService.showToast("error", null, this.i18nService.t("secretsCannotCreate")); + return; + } + this.formGroup.markAllAsTouched(); if (this.formGroup.invalid) { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts index 7c05f169a3d..b23393de60a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.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"; @@ -29,13 +30,15 @@ export class SecretsComponent implements OnInit { protected search: string; private organizationId: string; + private organizationEnabled: boolean; constructor( private route: ActivatedRoute, private secretService: SecretService, private dialogService: DialogService, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService + private i18nService: I18nService, + private organizationService: OrganizationService ) {} ngOnInit() { @@ -44,6 +47,8 @@ export class SecretsComponent implements OnInit { combineLatestWith(this.route.params), switchMap(async ([_, params]) => { this.organizationId = params.organizationId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; + return await this.getSecrets(); }) ); @@ -63,6 +68,7 @@ export class SecretsComponent implements OnInit { organizationId: this.organizationId, operation: OperationType.Edit, secretId: secretId, + organizationEnabled: this.organizationEnabled, }, }); } @@ -80,6 +86,7 @@ export class SecretsComponent implements OnInit { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts index 1f42537f956..decd042cc14 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts @@ -18,6 +18,7 @@ export interface ServiceAccountOperation { organizationId: string; serviceAccountId?: string; operation: OperationType; + organizationEnabled: boolean; } @Component({ @@ -62,6 +63,15 @@ export class ServiceAccountDialogComponent { } submit = async () => { + if (!this.data.organizationEnabled) { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("serviceAccountsCannotCreate") + ); + return; + } + this.formGroup.markAllAsTouched(); if (this.formGroup.invalid) { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts index 808073ba810..bebd9ddca62 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatest, Observable, startWith, switchMap } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { DialogService } from "@bitwarden/components"; import { @@ -30,12 +31,14 @@ export class ServiceAccountsComponent implements OnInit { protected search: string; private organizationId: string; + private organizationEnabled: boolean; constructor( private route: ActivatedRoute, private dialogService: DialogService, private accessPolicyService: AccessPolicyService, - private serviceAccountService: ServiceAccountService + private serviceAccountService: ServiceAccountService, + private organizationService: OrganizationService ) {} ngOnInit() { @@ -46,6 +49,8 @@ export class ServiceAccountsComponent implements OnInit { ]).pipe( switchMap(async ([params]) => { this.organizationId = params.organizationId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; + return await this.getServiceAccounts(); }) ); @@ -56,6 +61,7 @@ export class ServiceAccountsComponent implements OnInit { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -66,6 +72,7 @@ export class ServiceAccountsComponent implements OnInit { organizationId: this.organizationId, serviceAccountId: serviceAccountId, operation: OperationType.Edit, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts index 7ecc2f917a4..67a93e8ad87 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts @@ -2,6 +2,7 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { DialogService } from "@bitwarden/components"; import { @@ -24,13 +25,18 @@ import { }) export class NewMenuComponent implements OnInit, OnDestroy { private organizationId: string; + private organizationEnabled: boolean; private destroy$: Subject = new Subject(); - - constructor(private route: ActivatedRoute, private dialogService: DialogService) {} + constructor( + private route: ActivatedRoute, + private dialogService: DialogService, + private organizationService: OrganizationService + ) {} ngOnInit() { this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params: any) => { this.organizationId = params.organizationId; + this.organizationEnabled = this.organizationService.get(params.organizationId)?.enabled; }); } @@ -44,6 +50,7 @@ export class NewMenuComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -53,6 +60,7 @@ export class NewMenuComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } @@ -62,6 +70,7 @@ export class NewMenuComponent implements OnInit, OnDestroy { data: { organizationId: this.organizationId, operation: OperationType.Add, + organizationEnabled: this.organizationEnabled, }, }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.html new file mode 100644 index 00000000000..8de68f65988 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.html @@ -0,0 +1,7 @@ + + + + + {{ "organizationIsDisabled" | i18n }} + {{ "secretsAccessSuspended" | i18n }} + diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts new file mode 100644 index 00000000000..73f89c0826d --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts @@ -0,0 +1,18 @@ +import { Component } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { map } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Icon, Icons } from "@bitwarden/components"; + +@Component({ + templateUrl: "./org-suspended.component.html", +}) +export class OrgSuspendedComponent { + constructor(private organizationService: OrganizationService, private route: ActivatedRoute) {} + + protected NoAccess: Icon = Icons.NoAccess; + protected organizationName$ = this.route.params.pipe( + map((params) => this.organizationService.get(params.organizationId)?.name) + ); +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts index 6d59503b50a..d2990f4c67f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/sm-shared.module.ts @@ -17,6 +17,7 @@ import { BulkConfirmationDialogComponent } from "./dialogs/bulk-confirmation-dia import { BulkStatusDialogComponent } from "./dialogs/bulk-status-dialog.component"; import { HeaderComponent } from "./header.component"; import { NewMenuComponent } from "./new-menu.component"; +import { OrgSuspendedComponent } from "./org-suspended.component"; import { ProjectsListComponent } from "./projects-list.component"; import { SecretsListComponent } from "./secrets-list.component"; @@ -55,6 +56,7 @@ import { SecretsListComponent } from "./secrets-list.component"; ProjectsListComponent, SecretsListComponent, AccessSelectorComponent, + OrgSuspendedComponent, ], providers: [], bootstrap: [], diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts index 5c18bab4e42..0cad3129a40 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/sm-routing.module.ts @@ -2,10 +2,10 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/auth/guards"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { OrganizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard"; import { buildFlaggedRoute } from "@bitwarden/web-vault/app/oss-routing.module"; +import { organizationEnabledGuard } from "./guards/sm-org-enabled.guard"; +import { canActivateSM } from "./guards/sm.guard"; import { LayoutComponent } from "./layout/layout.component"; import { NavigationComponent } from "./layout/navigation.component"; import { OverviewModule } from "./overview/overview.module"; @@ -13,7 +13,7 @@ import { ProjectsModule } from "./projects/projects.module"; import { SecretsModule } from "./secrets/secrets.module"; import { ServiceAccountsModule } from "./service-accounts/service-accounts.module"; import { SettingsModule } from "./settings/settings.module"; -import { canActivateSM } from "./sm.guard"; +import { OrgSuspendedComponent } from "./shared/org-suspended.component"; import { TrashModule } from "./trash/trash.module"; const routes: Routes = [ @@ -29,10 +29,7 @@ const routes: Routes = [ { path: ":organizationId", component: LayoutComponent, - canActivate: [AuthGuard, OrganizationPermissionsGuard], - data: { - organizationPermissions: (org: Organization) => org.canAccessSecretsManager, - }, + canActivate: [AuthGuard], children: [ { path: "", @@ -40,41 +37,51 @@ const routes: Routes = [ outlet: "sidebar", }, { - path: "secrets", - loadChildren: () => SecretsModule, - data: { - titleId: "secrets", - }, - }, - { - path: "projects", - loadChildren: () => ProjectsModule, - data: { - titleId: "projects", - }, - }, - { - path: "service-accounts", - loadChildren: () => ServiceAccountsModule, - data: { - titleId: "serviceAccounts", - }, - }, - { - path: "trash", - loadChildren: () => TrashModule, - data: { - titleId: "trash", - }, - }, - { - path: "settings", - loadChildren: () => SettingsModule, + path: "", + canActivate: [organizationEnabledGuard], + children: [ + { + path: "secrets", + loadChildren: () => SecretsModule, + data: { + titleId: "secrets", + }, + }, + { + path: "projects", + loadChildren: () => ProjectsModule, + data: { + titleId: "projects", + }, + }, + { + path: "service-accounts", + loadChildren: () => ServiceAccountsModule, + data: { + titleId: "serviceAccounts", + }, + }, + { + path: "trash", + loadChildren: () => TrashModule, + data: { + titleId: "trash", + }, + }, + { + path: "settings", + loadChildren: () => SettingsModule, + }, + { + path: "", + loadChildren: () => OverviewModule, + pathMatch: "full", + }, + ], }, { - path: "", - loadChildren: () => OverviewModule, - pathMatch: "full", + path: "organization-suspended", + component: OrgSuspendedComponent, }, ], }, diff --git a/libs/angular/src/directives/fallback-src.directive.ts b/libs/angular/src/directives/fallback-src.directive.ts index 11bce2052b7..082c8534fa3 100644 --- a/libs/angular/src/directives/fallback-src.directive.ts +++ b/libs/angular/src/directives/fallback-src.directive.ts @@ -6,9 +6,15 @@ import { Directive, ElementRef, HostListener, Input } from "@angular/core"; export class FallbackSrcDirective { @Input("appFallbackSrc") appFallbackSrc: string; + /** Only try setting the fallback once. This prevents an infinite loop if the fallback itself is missing. */ + private tryFallback = true; + constructor(private el: ElementRef) {} @HostListener("error") onError() { - this.el.nativeElement.src = this.appFallbackSrc; + if (this.tryFallback) { + this.el.nativeElement.src = this.appFallbackSrc; + this.tryFallback = false; + } } } diff --git a/libs/angular/src/services/modal.service.ts b/libs/angular/src/services/modal.service.ts index ba461764ba8..da47368c2fa 100644 --- a/libs/angular/src/services/modal.service.ts +++ b/libs/angular/src/services/modal.service.ts @@ -88,13 +88,6 @@ export class ModalService { return modalRef; } - registerComponentFactoryResolver( - componentType: Type, - componentFactoryResolver: ComponentFactoryResolver - ): void { - this.factoryResolvers.set(componentType, componentFactoryResolver); - } - resolveComponentFactory(componentType: Type): ComponentFactory { if (this.factoryResolvers.has(componentType)) { return this.factoryResolvers.get(componentType).resolveComponentFactory(componentType); diff --git a/libs/components/src/icon/icons/index.ts b/libs/components/src/icon/icons/index.ts index 03fdb729bc7..02cb975e095 100644 --- a/libs/components/src/icon/icons/index.ts +++ b/libs/components/src/icon/icons/index.ts @@ -1 +1,2 @@ export * from "./search"; +export * from "./no-access"; diff --git a/libs/components/src/icon/icons/no-access.ts b/libs/components/src/icon/icons/no-access.ts new file mode 100644 index 00000000000..f9ad048752a --- /dev/null +++ b/libs/components/src/icon/icons/no-access.ts @@ -0,0 +1,12 @@ +import { svgIcon } from "../icon"; + +export const NoAccess = svgIcon` + + + + + + + + +`; diff --git a/libs/components/src/navigation/nav-group.component.html b/libs/components/src/navigation/nav-group.component.html index ca9a7c3aecf..118f78a1865 100644 --- a/libs/components/src/navigation/nav-group.component.html +++ b/libs/components/src/navigation/nav-group.component.html @@ -17,7 +17,7 @@ [bitIconButton]=" open ? 'bwi-angle-up' : variant === 'tree' ? 'bwi-angle-right' : 'bwi-angle-down' " - [buttonType]="'main'" + [buttonType]="'light'" (click)="toggle($event)" size="small" [title]="'toggleCollapse' | i18n" @@ -32,8 +32,11 @@ - - + + + + + diff --git a/libs/components/src/navigation/nav-item.component.html b/libs/components/src/navigation/nav-item.component.html index 32c8dfbf980..02705e821eb 100644 --- a/libs/components/src/navigation/nav-item.component.html +++ b/libs/components/src/navigation/nav-item.component.html @@ -73,7 +73,7 @@
diff --git a/libs/components/src/navigation/nav-item.stories.ts b/libs/components/src/navigation/nav-item.stories.ts index 7fdbadce31a..c8f90eabcff 100644 --- a/libs/components/src/navigation/nav-item.stories.ts +++ b/libs/components/src/navigation/nav-item.stories.ts @@ -64,7 +64,7 @@ export const WithChildButtons: Story = { slot="start" class="tw-ml-auto" [bitIconButton]="'bwi-clone'" - [buttonType]="'contrast'" + [buttonType]="'light'" size="small" aria-label="option 1" > @@ -72,7 +72,7 @@ export const WithChildButtons: Story = { slot="end" class="tw-ml-auto" [bitIconButton]="'bwi-pencil-square'" - [buttonType]="'contrast'" + [buttonType]="'light'" size="small" aria-label="option 2" > @@ -80,7 +80,7 @@ export const WithChildButtons: Story = { slot="end" class="tw-ml-auto" [bitIconButton]="'bwi-check'" - [buttonType]="'contrast'" + [buttonType]="'light'" size="small" aria-label="option 3" > diff --git a/libs/importer/src/importers/lastpass/access/enums/duo-factor.ts b/libs/importer/src/importers/lastpass/access/enums/duo-factor.ts new file mode 100644 index 00000000000..aa65583935e --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/enums/duo-factor.ts @@ -0,0 +1,6 @@ +export enum DuoFactor { + Push, + Call, + Passcode, + SendPasscodesBySms, +} diff --git a/libs/importer/src/importers/lastpass/access/enums/duo-status.ts b/libs/importer/src/importers/lastpass/access/enums/duo-status.ts new file mode 100644 index 00000000000..6397db5dc91 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/enums/duo-status.ts @@ -0,0 +1,5 @@ +export enum DuoStatus { + Success, + Error, + Info, +} diff --git a/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts b/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts new file mode 100644 index 00000000000..32e74c36ee1 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts @@ -0,0 +1,8 @@ +export enum IdpProvider { + Azure = 0, + OktaAuthServer = 1, + OktaNoAuthServer = 2, + Google = 3, + PingOne = 4, + OneLogin = 5, +} diff --git a/libs/importer/src/importers/lastpass/access/enums/index.ts b/libs/importer/src/importers/lastpass/access/enums/index.ts new file mode 100644 index 00000000000..0059030e0aa --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/enums/index.ts @@ -0,0 +1,6 @@ +export { DuoFactor } from "./duo-factor"; +export { DuoStatus } from "./duo-status"; +export { IdpProvider } from "./idp-provider"; +export { LastpassLoginType } from "./lastpass-login-type"; +export { OtpMethod } from "./otp-method"; +export { Platform } from "./platform"; diff --git a/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts b/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts new file mode 100644 index 00000000000..611dd0b6dab --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts @@ -0,0 +1,5 @@ +export enum LastpassLoginType { + MasterPassword = 0, + // Not sure what Types 1 and 2 are? + Federated = 3, +} diff --git a/libs/importer/src/importers/lastpass/access/otp-method.ts b/libs/importer/src/importers/lastpass/access/enums/otp-method.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/otp-method.ts rename to libs/importer/src/importers/lastpass/access/enums/otp-method.ts diff --git a/libs/importer/src/importers/lastpass/access/platform.ts b/libs/importer/src/importers/lastpass/access/enums/platform.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/platform.ts rename to libs/importer/src/importers/lastpass/access/enums/platform.ts diff --git a/libs/importer/src/importers/lastpass/access/index.ts b/libs/importer/src/importers/lastpass/access/index.ts new file mode 100644 index 00000000000..a124a44b315 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/index.ts @@ -0,0 +1 @@ +export { Vault } from "./vault"; diff --git a/libs/importer/src/importers/lastpass/access/account.ts b/libs/importer/src/importers/lastpass/access/models/account.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/account.ts rename to libs/importer/src/importers/lastpass/access/models/account.ts diff --git a/libs/importer/src/importers/lastpass/access/chunk.ts b/libs/importer/src/importers/lastpass/access/models/chunk.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/chunk.ts rename to libs/importer/src/importers/lastpass/access/models/chunk.ts diff --git a/libs/importer/src/importers/lastpass/access/client-info.ts b/libs/importer/src/importers/lastpass/access/models/client-info.ts similarity index 69% rename from libs/importer/src/importers/lastpass/access/client-info.ts rename to libs/importer/src/importers/lastpass/access/models/client-info.ts index fbe13d57d65..275cdc00d3f 100644 --- a/libs/importer/src/importers/lastpass/access/client-info.ts +++ b/libs/importer/src/importers/lastpass/access/models/client-info.ts @@ -1,4 +1,4 @@ -import { Platform } from "./platform"; +import { Platform } from "../enums"; export class ClientInfo { platform: Platform; diff --git a/libs/importer/src/importers/lastpass/access/federated-user-context.ts b/libs/importer/src/importers/lastpass/access/models/federated-user-context.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/federated-user-context.ts rename to libs/importer/src/importers/lastpass/access/models/federated-user-context.ts diff --git a/libs/importer/src/importers/lastpass/access/models/index.ts b/libs/importer/src/importers/lastpass/access/models/index.ts new file mode 100644 index 00000000000..a0c6121a354 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/models/index.ts @@ -0,0 +1,10 @@ +export { Account } from "./account"; +export { Chunk } from "./chunk"; +export { ClientInfo } from "./client-info"; +export { FederatedUserContext } from "./federated-user-context"; +export { OobResult } from "./oob-result"; +export { OtpResult } from "./otp-result"; +export { ParserOptions } from "./parser-options"; +export { Session } from "./session"; +export { SharedFolder } from "./shared-folder"; +export { UserTypeContext } from "./user-type-context"; diff --git a/libs/importer/src/importers/lastpass/access/oob-result.ts b/libs/importer/src/importers/lastpass/access/models/oob-result.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/oob-result.ts rename to libs/importer/src/importers/lastpass/access/models/oob-result.ts diff --git a/libs/importer/src/importers/lastpass/access/otp-result.ts b/libs/importer/src/importers/lastpass/access/models/otp-result.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/otp-result.ts rename to libs/importer/src/importers/lastpass/access/models/otp-result.ts diff --git a/libs/importer/src/importers/lastpass/access/parser-options.ts b/libs/importer/src/importers/lastpass/access/models/parser-options.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/parser-options.ts rename to libs/importer/src/importers/lastpass/access/models/parser-options.ts diff --git a/libs/importer/src/importers/lastpass/access/session.ts b/libs/importer/src/importers/lastpass/access/models/session.ts similarity index 78% rename from libs/importer/src/importers/lastpass/access/session.ts rename to libs/importer/src/importers/lastpass/access/models/session.ts index 4c712872632..f691968a7a7 100644 --- a/libs/importer/src/importers/lastpass/access/session.ts +++ b/libs/importer/src/importers/lastpass/access/models/session.ts @@ -1,4 +1,4 @@ -import { Platform } from "./platform"; +import { Platform } from "../enums"; export class Session { id: string; diff --git a/libs/importer/src/importers/lastpass/access/shared-folder.ts b/libs/importer/src/importers/lastpass/access/models/shared-folder.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/shared-folder.ts rename to libs/importer/src/importers/lastpass/access/models/shared-folder.ts diff --git a/libs/importer/src/importers/lastpass/access/user-type-context.ts b/libs/importer/src/importers/lastpass/access/models/user-type-context.ts similarity index 63% rename from libs/importer/src/importers/lastpass/access/user-type-context.ts rename to libs/importer/src/importers/lastpass/access/models/user-type-context.ts index f2629d59516..9d849281c2d 100644 --- a/libs/importer/src/importers/lastpass/access/user-type-context.ts +++ b/libs/importer/src/importers/lastpass/access/models/user-type-context.ts @@ -1,17 +1,19 @@ +import { IdpProvider, LastpassLoginType } from "../enums"; + export class UserTypeContext { - type: Type; + type: LastpassLoginType; IdentityProviderGUID: string; IdentityProviderURL: string; OpenIDConnectAuthority: string; OpenIDConnectClientId: string; CompanyId: number; - Provider: Provider; + Provider: IdpProvider; PkceEnabled: boolean; IsPasswordlessEnabled: boolean; isFederated(): boolean { return ( - this.type === Type.Federated && + this.type === LastpassLoginType.Federated && this.hasValue(this.IdentityProviderURL) && this.hasValue(this.OpenIDConnectAuthority) && this.hasValue(this.OpenIDConnectClientId) @@ -22,18 +24,3 @@ export class UserTypeContext { return str != null && str.trim() !== ""; } } - -export enum Provider { - Azure = 0, - OktaAuthServer = 1, - OktaNoAuthServer = 2, - Google = 3, - PingOne = 4, - OneLogin = 5, -} - -export enum Type { - MasterPassword = 0, - // Not sure what Types 1 and 2 are? - Federated = 3, -} diff --git a/libs/importer/src/importers/lastpass/access/binary-reader.ts b/libs/importer/src/importers/lastpass/access/services/binary-reader.ts similarity index 91% rename from libs/importer/src/importers/lastpass/access/binary-reader.ts rename to libs/importer/src/importers/lastpass/access/services/binary-reader.ts index 706afbd9e9b..e7a434e957f 100644 --- a/libs/importer/src/importers/lastpass/access/binary-reader.ts +++ b/libs/importer/src/importers/lastpass/access/services/binary-reader.ts @@ -12,7 +12,7 @@ export class BinaryReader { readBytes(count: number): Uint8Array { if (this.position + count > this.arr.length) { - throw "End of array reached"; + throw new Error("End of array reached"); } const slice = this.arr.subarray(this.position, this.position + count); this.position += count; @@ -62,10 +62,10 @@ export class BinaryReader { seekFromCurrentPosition(offset: number) { const newPosition = this.position + offset; if (newPosition < 0) { - throw "Position cannot be negative"; + throw new Error("Position cannot be negative"); } if (newPosition > this.arr.length) { - throw "Array not large enough to seek to this position"; + throw new Error("Array not large enough to seek to this position"); } this.position = newPosition; } diff --git a/libs/importer/src/importers/lastpass/access/client.ts b/libs/importer/src/importers/lastpass/access/services/client.ts similarity index 94% rename from libs/importer/src/importers/lastpass/access/client.ts rename to libs/importer/src/importers/lastpass/access/services/client.ts index 0a3c8fefe52..2d8b503f01d 100644 --- a/libs/importer/src/importers/lastpass/access/client.ts +++ b/libs/importer/src/importers/lastpass/access/services/client.ts @@ -1,21 +1,23 @@ import { HttpStatusCode } from "@bitwarden/common/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { Account } from "./account"; +import { OtpMethod, Platform } from "../enums"; +import { + Account, + Chunk, + ClientInfo, + OobResult, + OtpResult, + ParserOptions, + Session, + SharedFolder, +} from "../models"; +import { Ui } from "../ui"; + import { BinaryReader } from "./binary-reader"; -import { Chunk } from "./chunk"; -import { ClientInfo } from "./client-info"; import { CryptoUtils } from "./crypto-utils"; -import { OobResult } from "./oob-result"; -import { OtpMethod } from "./otp-method"; -import { OtpResult } from "./otp-result"; import { Parser } from "./parser"; -import { ParserOptions } from "./parser-options"; -import { Platform } from "./platform"; import { RestClient } from "./rest-client"; -import { Session } from "./session"; -import { SharedFolder } from "./shared-folder"; -import { Ui } from "./ui"; const PlatformToUserAgent = new Map([ [Platform.Desktop, "cli"], @@ -68,7 +70,7 @@ export class Client { const reader = new BinaryReader(blob); const chunks = this.parser.extractChunks(reader); if (!this.isComplete(chunks)) { - throw "Blob is truncated or corrupted"; + throw new Error("Blob is truncated or corrupted"); } return await this.parseAccounts(chunks, encryptionKey, privateKey, options); } @@ -236,11 +238,11 @@ export class Client { passcode = ui.provideYubikeyPasscode(); break; default: - throw "Invalid OTP method"; + throw new Error("Invalid OTP method"); } if (passcode == OtpResult.cancel) { - throw "Second factor step is canceled by the user"; + throw new Error("Second factor step is canceled by the user"); } const response = await this.performSingleLoginRequest( @@ -273,7 +275,7 @@ export class Client { ): Promise { const answer = this.approveOob(username, parameters, ui, rest); if (answer == OobResult.cancel) { - throw "Out of band step is canceled by the user"; + throw new Error("Out of band step is canceled by the user"); } const extraParameters = new Map(); @@ -319,7 +321,7 @@ export class Client { private approveOob(username: string, parameters: Map, ui: Ui, rest: RestClient) { const method = parameters.get("outofbandtype"); if (method == null) { - throw "Out of band method is not specified"; + throw new Error("Out of band method is not specified"); } switch (method) { case "lastpassauth": @@ -329,7 +331,7 @@ export class Client { case "salesforcehash": return ui.approveSalesforceAuth(); default: - throw "Out of band method " + method + " is not supported"; + throw new Error("Out of band method " + method + " is not supported"); } } @@ -410,7 +412,7 @@ export class Client { if (attr != null) { return attr; } - throw "Unknown response schema: attribute " + name + " is missing"; + throw new Error("Unknown response schema: attribute " + name + " is missing"); } private getOptionalErrorAttribute(response: Document, name: string): string { @@ -505,7 +507,9 @@ export class Client { private makeError(response: Response) { // TODO: error parsing - throw "HTTP request to " + response.url + " failed with status " + response.status + "."; + throw new Error( + "HTTP request to " + response.url + " failed with status " + response.status + "." + ); } private makeLoginError(response: Document): string { diff --git a/libs/importer/src/importers/lastpass/access/crypto-utils.ts b/libs/importer/src/importers/lastpass/access/services/crypto-utils.ts similarity index 96% rename from libs/importer/src/importers/lastpass/access/crypto-utils.ts rename to libs/importer/src/importers/lastpass/access/services/crypto-utils.ts index c8d9f8a168b..4de046f2aa3 100644 --- a/libs/importer/src/importers/lastpass/access/crypto-utils.ts +++ b/libs/importer/src/importers/lastpass/access/services/crypto-utils.ts @@ -6,7 +6,7 @@ export class CryptoUtils { async deriveKey(username: string, password: string, iterationCount: number) { if (iterationCount < 0) { - throw "Iteration count should be positive"; + throw new Error("Iteration count should be positive"); } if (iterationCount == 1) { return await this.cryptoFunctionService.hash(username + password, "sha256"); @@ -27,7 +27,7 @@ export class CryptoUtils { ExclusiveOr(arr1: Uint8Array, arr2: Uint8Array) { if (arr1.length !== arr2.length) { - throw "Arrays must be the same length."; + throw new Error("Arrays must be the same length."); } const result = new Uint8Array(arr1.length); for (let i = 0; i < arr1.length; i++) { diff --git a/libs/importer/src/importers/lastpass/access/services/index.ts b/libs/importer/src/importers/lastpass/access/services/index.ts new file mode 100644 index 00000000000..2610efdb694 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/services/index.ts @@ -0,0 +1,5 @@ +export { BinaryReader } from "./binary-reader"; +export { Client } from "./client"; +export { CryptoUtils } from "./crypto-utils"; +export { Parser } from "./parser"; +export { RestClient } from "./rest-client"; diff --git a/libs/importer/src/importers/lastpass/access/parser.ts b/libs/importer/src/importers/lastpass/access/services/parser.ts similarity index 97% rename from libs/importer/src/importers/lastpass/access/parser.ts rename to libs/importer/src/importers/lastpass/access/services/parser.ts index fc4b3b4a49a..3d64490be12 100644 --- a/libs/importer/src/importers/lastpass/access/parser.ts +++ b/libs/importer/src/importers/lastpass/access/services/parser.ts @@ -1,12 +1,10 @@ import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { Account } from "./account"; +import { Account, Chunk, ParserOptions, SharedFolder } from "../models"; + import { BinaryReader } from "./binary-reader"; -import { Chunk } from "./chunk"; import { CryptoUtils } from "./crypto-utils"; -import { ParserOptions } from "./parser-options"; -import { SharedFolder } from "./shared-folder"; const AllowedSecureNoteTypes = new Set([ "Server", @@ -285,7 +283,7 @@ export class Parser { const header = "LastPassPrivateKey<"; const footer = ">LastPassPrivateKey"; if (!decrypted.startsWith(header) || !decrypted.endsWith(footer)) { - throw "Failed to decrypt private key"; + throw new Error("Failed to decrypt private key"); } const parsedKey = decrypted.substring(header.length, decrypted.length - footer.length); diff --git a/libs/importer/src/importers/lastpass/access/rest-client.ts b/libs/importer/src/importers/lastpass/access/services/rest-client.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/rest-client.ts rename to libs/importer/src/importers/lastpass/access/services/rest-client.ts diff --git a/libs/importer/src/importers/lastpass/access/duo-ui.ts b/libs/importer/src/importers/lastpass/access/ui/duo-ui.ts similarity index 81% rename from libs/importer/src/importers/lastpass/access/duo-ui.ts rename to libs/importer/src/importers/lastpass/access/ui/duo-ui.ts index 61b52d2582a..60afd0ad9df 100644 --- a/libs/importer/src/importers/lastpass/access/duo-ui.ts +++ b/libs/importer/src/importers/lastpass/access/ui/duo-ui.ts @@ -1,3 +1,5 @@ +import { DuoFactor, DuoStatus } from "../enums"; + // Adds Duo functionality to the module-specific Ui class. export abstract class DuoUi { // To cancel return null @@ -8,19 +10,6 @@ export abstract class DuoUi { updateDuoStatus: (status: DuoStatus, text: string) => void; } -export enum DuoFactor { - Push, - Call, - Passcode, - SendPasscodesBySms, -} - -export enum DuoStatus { - Success, - Error, - Info, -} - export interface DuoChoice { device: DuoDevice; factor: DuoFactor; diff --git a/libs/importer/src/importers/lastpass/access/ui/index.ts b/libs/importer/src/importers/lastpass/access/ui/index.ts new file mode 100644 index 00000000000..e4edc3b6b48 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/ui/index.ts @@ -0,0 +1,2 @@ +export { DuoUi, DuoChoice, DuoDevice } from "./duo-ui"; +export { Ui } from "./ui"; diff --git a/libs/importer/src/importers/lastpass/access/ui.ts b/libs/importer/src/importers/lastpass/access/ui/ui.ts similarity index 93% rename from libs/importer/src/importers/lastpass/access/ui.ts rename to libs/importer/src/importers/lastpass/access/ui/ui.ts index fad86596187..2338e8a291e 100644 --- a/libs/importer/src/importers/lastpass/access/ui.ts +++ b/libs/importer/src/importers/lastpass/access/ui/ui.ts @@ -1,6 +1,6 @@ +import { OobResult, OtpResult } from "../models"; + import { DuoUi } from "./duo-ui"; -import { OobResult } from "./oob-result"; -import { OtpResult } from "./otp-result"; export abstract class Ui extends DuoUi { // To cancel return OtpResult.Cancel, otherwise only valid data is expected. diff --git a/libs/importer/src/importers/lastpass/access/vault.ts b/libs/importer/src/importers/lastpass/access/vault.ts index 157965804c2..a461239eea8 100644 --- a/libs/importer/src/importers/lastpass/access/vault.ts +++ b/libs/importer/src/importers/lastpass/access/vault.ts @@ -3,16 +3,16 @@ import { HttpStatusCode } from "@bitwarden/common/enums"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { Account } from "./account"; -import { Client } from "./client"; -import { ClientInfo } from "./client-info"; -import { CryptoUtils } from "./crypto-utils"; -import { FederatedUserContext } from "./federated-user-context"; -import { Parser } from "./parser"; -import { ParserOptions } from "./parser-options"; -import { RestClient } from "./rest-client"; +import { IdpProvider } from "./enums"; +import { + Account, + ClientInfo, + FederatedUserContext, + ParserOptions, + UserTypeContext, +} from "./models"; +import { Client, CryptoUtils, Parser, RestClient } from "./services"; import { Ui } from "./ui"; -import { Provider, UserTypeContext } from "./user-type-context"; export class Vault { accounts: Account[]; @@ -47,7 +47,7 @@ export class Vault { parserOptions: ParserOptions = ParserOptions.default ): Promise { if (federatedUser == null) { - throw "Federated user context is not set."; + throw new Error("Federated user context is not set."); } const k1 = await this.getK1(federatedUser); const k2 = await this.getK2(federatedUser); @@ -77,32 +77,33 @@ export class Vault { this.userType.PkceEnabled = json.PkceEnabled; this.userType.Provider = json.Provider; this.userType.type = json.type; + return; } - throw "Cannot determine LastPass user type."; + throw new Error("Cannot determine LastPass user type."); } private async getK1(federatedUser: FederatedUserContext): Promise { if (this.userType == null) { - throw "User type is not set."; + throw new Error("User type is not set."); } if (!this.userType.isFederated()) { - throw "Cannot get k1 for LastPass user that is not federated."; + throw new Error("Cannot get k1 for LastPass user that is not federated."); } if (federatedUser == null) { - throw "Federated user is not set."; + throw new Error("Federated user is not set."); } let k1: Uint8Array = null; if (federatedUser.idpUserInfo?.LastPassK1 !== null) { return Utils.fromByteStringToArray(federatedUser.idpUserInfo.LastPassK1); - } else if (this.userType.Provider === Provider.Azure) { + } else if (this.userType.Provider === IdpProvider.Azure) { k1 = await this.getK1Azure(federatedUser); - } else if (this.userType.Provider === Provider.Google) { + } else if (this.userType.Provider === IdpProvider.Google) { k1 = await this.getK1Google(federatedUser); } else { - const b64Encoded = this.userType.Provider === Provider.PingOne; + const b64Encoded = this.userType.Provider === IdpProvider.PingOne; k1 = this.getK1FromAccessToken(federatedUser, b64Encoded); } @@ -110,7 +111,7 @@ export class Vault { return k1; } - throw "Cannot get k1."; + throw new Error("Cannot get k1."); } private async getK1Azure(federatedUser: FederatedUserContext) { @@ -175,11 +176,11 @@ export class Vault { private async getK2(federatedUser: FederatedUserContext): Promise { if (this.userType == null) { - throw "User type is not set."; + throw new Error("User type is not set."); } if (!this.userType.isFederated()) { - throw "Cannot get k2 for LastPass user that is not federated."; + throw new Error("Cannot get k2 for LastPass user that is not federated."); } const rest = new RestClient(); @@ -195,6 +196,6 @@ export class Vault { return Utils.fromB64ToArray(k2); } } - throw "Cannot get k2."; + throw new Error("Cannot get k2."); } } diff --git a/package-lock.json b/package-lock.json index 9ab91abac71..d6436616218 100644 --- a/package-lock.json +++ b/package-lock.json @@ -123,7 +123,7 @@ "cross-env": "7.0.3", "css-loader": "6.8.1", "del": "6.1.1", - "electron": "26.3.0", + "electron": "25.9.1", "electron-builder": "^23.6.0", "electron-log": "4.4.8", "electron-reload": "2.0.0-alpha.1", @@ -229,7 +229,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2023.9.2", + "version": "2023.9.3", "hasInstallScript": true, "license": "GPL-3.0" }, @@ -20179,9 +20179,9 @@ } }, "node_modules/electron": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-26.3.0.tgz", - "integrity": "sha512-7ZpvSHu+jmqialSvywTZnOQZZGLqlyj+yV5HGDrEzFnMiFaXBRpbByHgoUhaExJ/8t/0xKQjKlMRAY65w+zNZQ==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.1.tgz", + "integrity": "sha512-Uo/Fh7igjoUXA/f90iTATZJesQEArVL1uLA672JefNWTLymdKSZkJKiCciu/Xnd0TS6qvdIOUGuJFSTQnKskXQ==", "dev": true, "hasInstallScript": true, "dependencies": { diff --git a/package.json b/package.json index 2cd1718c1e5..763897c388c 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "cross-env": "7.0.3", "css-loader": "6.8.1", "del": "6.1.1", - "electron": "26.3.0", + "electron": "25.9.1", "electron-builder": "^23.6.0", "electron-log": "4.4.8", "electron-reload": "2.0.0-alpha.1",