mirror of
https://github.com/bitwarden/browser
synced 2026-02-12 22:44:11 +00:00
Merge branch 'main' into km/pm-14445
This commit is contained in:
4
.github/renovate.json
vendored
4
.github/renovate.json
vendored
@@ -73,7 +73,7 @@
|
||||
"reviewers": ["team:team-admin-console-dev"]
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["@types/node-ipc", "node-ipc", "qrious", "regedit"],
|
||||
"matchPackageNames": ["@types/node-ipc", "node-ipc", "qrious"],
|
||||
"description": "Auth owned dependencies",
|
||||
"commitMessagePrefix": "[deps] Auth:",
|
||||
"reviewers": ["team:team-auth-dev"]
|
||||
@@ -258,5 +258,5 @@
|
||||
"reviewers": ["team:team-vault-dev"]
|
||||
}
|
||||
],
|
||||
"ignoreDeps": ["@types/koa-bodyparser", "bootstrap", "node-ipc", "node", "npm", "regedit"]
|
||||
"ignoreDeps": ["@types/koa-bodyparser", "bootstrap", "node-ipc", "node", "npm"]
|
||||
}
|
||||
|
||||
11
.github/workflows/build-browser.yml
vendored
11
.github/workflows/build-browser.yml
vendored
@@ -163,10 +163,6 @@ jobs:
|
||||
run: npm run dist:mv3
|
||||
working-directory: browser-source/apps/browser
|
||||
|
||||
- name: Build Chrome Manifest v3 Beta
|
||||
run: npm run dist:chrome:beta
|
||||
working-directory: browser-source/apps/browser
|
||||
|
||||
- name: Upload Opera artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
@@ -188,13 +184,6 @@ jobs:
|
||||
path: browser-source/apps/browser/dist/dist-chrome-mv3.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Chrome MV3 Beta artifact (DO NOT USE FOR PROD)
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: DO-NOT-USE-FOR-PROD-dist-chrome-MV3-beta-${{ env._BUILD_NUMBER }}.zip
|
||||
path: browser-source/apps/browser/dist/dist-chrome-mv3-beta.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Firefox artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
|
||||
@@ -128,10 +128,10 @@
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "test-storybook:build:production"
|
||||
"buildTarget": "test-storybook:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "test-storybook:build:development"
|
||||
"buildTarget": "test-storybook:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
|
||||
@@ -9,7 +9,6 @@ const replace = require("gulp-replace");
|
||||
|
||||
const manifest = require("./src/manifest.json");
|
||||
const manifestVersion = parseInt(process.env.MANIFEST_VERSION || manifest.version);
|
||||
const betaBuild = process.env.BETA_BUILD === "1";
|
||||
|
||||
const paths = {
|
||||
build: "./build/",
|
||||
@@ -17,27 +16,11 @@ const paths = {
|
||||
safari: "./src/safari/",
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a number to a tuple containing two Uint16's
|
||||
* @param num {number} This number is expected to be a integer style number with no decimals
|
||||
*
|
||||
* @returns {number[]} A tuple containing two elements that are both numbers.
|
||||
*/
|
||||
function numToUint16s(num) {
|
||||
var arr = new ArrayBuffer(4);
|
||||
var view = new DataView(arr);
|
||||
view.setUint32(0, num, false);
|
||||
return [view.getUint16(0), view.getUint16(2)];
|
||||
}
|
||||
|
||||
function buildString() {
|
||||
var build = "";
|
||||
if (process.env.MANIFEST_VERSION) {
|
||||
build = `-mv${process.env.MANIFEST_VERSION}`;
|
||||
}
|
||||
if (betaBuild) {
|
||||
build += "-beta";
|
||||
}
|
||||
if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== "") {
|
||||
build = `-${process.env.BUILD_NUMBER}`;
|
||||
}
|
||||
@@ -71,9 +54,6 @@ function distFirefox() {
|
||||
manifest.optional_permissions = manifest.optional_permissions.filter(
|
||||
(permission) => permission !== "privacy",
|
||||
);
|
||||
if (betaBuild) {
|
||||
manifest = applyBetaLabels(manifest);
|
||||
}
|
||||
return manifest;
|
||||
});
|
||||
}
|
||||
@@ -90,9 +70,6 @@ function distOpera() {
|
||||
delete manifest.commands._execute_sidebar_action;
|
||||
}
|
||||
|
||||
if (betaBuild) {
|
||||
manifest = applyBetaLabels(manifest);
|
||||
}
|
||||
return manifest;
|
||||
});
|
||||
}
|
||||
@@ -102,9 +79,6 @@ function distChrome() {
|
||||
delete manifest.applications;
|
||||
delete manifest.sidebar_action;
|
||||
delete manifest.commands._execute_sidebar_action;
|
||||
if (betaBuild) {
|
||||
manifest = applyBetaLabels(manifest);
|
||||
}
|
||||
return manifest;
|
||||
});
|
||||
}
|
||||
@@ -114,9 +88,6 @@ function distEdge() {
|
||||
delete manifest.applications;
|
||||
delete manifest.sidebar_action;
|
||||
delete manifest.commands._execute_sidebar_action;
|
||||
if (betaBuild) {
|
||||
manifest = applyBetaLabels(manifest);
|
||||
}
|
||||
return manifest;
|
||||
});
|
||||
}
|
||||
@@ -237,9 +208,6 @@ async function safariCopyBuild(source, dest) {
|
||||
delete manifest.commands._execute_sidebar_action;
|
||||
delete manifest.optional_permissions;
|
||||
manifest.permissions.push("nativeMessaging");
|
||||
if (betaBuild) {
|
||||
manifest = applyBetaLabels(manifest);
|
||||
}
|
||||
return manifest;
|
||||
}),
|
||||
),
|
||||
@@ -254,30 +222,6 @@ function stdOutProc(proc) {
|
||||
proc.stderr.on("data", (data) => console.error(data.toString()));
|
||||
}
|
||||
|
||||
function applyBetaLabels(manifest) {
|
||||
manifest.name = "Bitwarden Password Manager BETA";
|
||||
manifest.short_name = "Bitwarden BETA";
|
||||
manifest.description = "THIS EXTENSION IS FOR BETA TESTING BITWARDEN.";
|
||||
if (process.env.GITHUB_RUN_ID) {
|
||||
const existingVersionParts = manifest.version.split("."); // 3 parts expected 2024.4.0
|
||||
|
||||
// GITHUB_RUN_ID is a number like: 8853654662
|
||||
// which will convert to [ 4024, 3206 ]
|
||||
// and a single incremented id of 8853654663 will become [ 4024, 3207 ]
|
||||
const runIdParts = numToUint16s(parseInt(process.env.GITHUB_RUN_ID));
|
||||
|
||||
// Only use the first 2 parts from the given version number and base the other 2 numbers from the GITHUB_RUN_ID
|
||||
// Example: 2024.4.4024.3206
|
||||
const betaVersion = `${existingVersionParts[0]}.${existingVersionParts[1]}.${runIdParts[0]}.${runIdParts[1]}`;
|
||||
|
||||
manifest.version_name = `${betaVersion} beta - ${process.env.GITHUB_SHA.slice(0, 8)}`;
|
||||
manifest.version = betaVersion;
|
||||
} else {
|
||||
manifest.version = `${manifest.version}.0`;
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
exports["dist:firefox"] = distFirefox;
|
||||
exports["dist:chrome"] = distChrome;
|
||||
exports["dist:opera"] = distOpera;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@bitwarden/browser",
|
||||
"version": "2024.10.1",
|
||||
"version": "2024.11.0",
|
||||
"scripts": {
|
||||
"build": "cross-env MANIFEST_VERSION=3 webpack",
|
||||
"build:mv2": "webpack",
|
||||
@@ -10,12 +10,9 @@
|
||||
"build:watch:safari": "cross-env MANIFEST_VERSION=3 BROWSER=safari webpack --watch",
|
||||
"build:watch:mv2": "webpack --watch",
|
||||
"build:prod": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" webpack",
|
||||
"build:prod:beta": "cross-env BETA_BUILD=1 NODE_ENV=production webpack",
|
||||
"build:prod:watch": "cross-env NODE_ENV=production webpack --watch",
|
||||
"dist": "tsc-strict && npm run build:prod && gulp dist",
|
||||
"dist:beta": "npm run build:prod:beta && cross-env BETA_BUILD=1 gulp dist",
|
||||
"dist:mv3": "cross-env MANIFEST_VERSION=3 npm run build:prod && cross-env MANIFEST_VERSION=3 gulp dist",
|
||||
"dist:mv3:beta": "cross-env MANIFEST_VERSION=3 npm run build:prod:beta && cross-env MANIFEST_VERSION=3 BETA_BUILD=1 gulp dist",
|
||||
"dist:chrome": "npm run build:prod && gulp dist:chrome",
|
||||
"dist:chrome:beta": "cross-env MANIFEST_VERSION=3 npm run build:prod:beta && cross-env MANIFEST_VERSION=3 BETA_BUILD=1 gulp dist:chrome",
|
||||
"dist:firefox": "npm run build:prod && gulp dist:firefox",
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from "@bitwarden/common/spec";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -106,6 +107,7 @@ describe("OverlayBackground", () => {
|
||||
let selectedThemeMock$: BehaviorSubject<ThemeType>;
|
||||
let inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
|
||||
let themeStateService: MockProxy<ThemeStateService>;
|
||||
let totpService: MockProxy<TotpService>;
|
||||
let overlayBackground: OverlayBackground;
|
||||
let portKeyForTabSpy: Record<number, string>;
|
||||
let pageDetailsForTabSpy: PageDetailsForTab;
|
||||
@@ -184,6 +186,7 @@ describe("OverlayBackground", () => {
|
||||
inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
||||
themeStateService = mock<ThemeStateService>();
|
||||
themeStateService.selectedTheme$ = selectedThemeMock$;
|
||||
totpService = mock<TotpService>();
|
||||
overlayBackground = new OverlayBackground(
|
||||
logService,
|
||||
cipherService,
|
||||
@@ -198,6 +201,7 @@ describe("OverlayBackground", () => {
|
||||
fido2ActiveRequestManager,
|
||||
inlineMenuFieldQualificationService,
|
||||
themeStateService,
|
||||
totpService,
|
||||
generatedPasswordCallbackMock,
|
||||
addPasswordCallbackMock,
|
||||
);
|
||||
|
||||
@@ -33,6 +33,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { buildCipherIcon } from "@bitwarden/common/vault/icon/build-cipher-icon";
|
||||
@@ -217,6 +218,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
private fido2ActiveRequestManager: Fido2ActiveRequestManager,
|
||||
private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService,
|
||||
private themeStateService: ThemeStateService,
|
||||
private totpService: TotpService,
|
||||
private generatePasswordCallback: () => Promise<string>,
|
||||
private addPasswordCallback: (password: string) => Promise<void>,
|
||||
) {
|
||||
@@ -1058,7 +1060,6 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
}
|
||||
|
||||
const cipher = this.inlineMenuCiphers.get(inlineMenuCipherId);
|
||||
|
||||
if (usePasskey && cipher.login?.hasFido2Credentials) {
|
||||
await this.authenticatePasskeyCredential(
|
||||
sender,
|
||||
@@ -1066,6 +1067,11 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
);
|
||||
this.updateLastUsedInlineMenuCipher(inlineMenuCipherId, cipher);
|
||||
|
||||
if (cipher.login?.totp) {
|
||||
this.platformUtilsService.copyToClipboard(
|
||||
await this.totpService.getCode(cipher.login.totp),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -257,12 +257,9 @@ import { BrowserPlatformUtilsService } from "../platform/services/platform-utils
|
||||
import { PopupViewCacheBackgroundService } from "../platform/services/popup-view-cache-background.service";
|
||||
import { BrowserSdkClientFactory } from "../platform/services/sdk/browser-sdk-client-factory";
|
||||
import { BackgroundTaskSchedulerService } from "../platform/services/task-scheduler/background-task-scheduler.service";
|
||||
import { ForegroundTaskSchedulerService } from "../platform/services/task-scheduler/foreground-task-scheduler.service";
|
||||
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
||||
import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider";
|
||||
import { ForegroundMemoryStorageService } from "../platform/storage/foreground-memory-storage.service";
|
||||
import { OffscreenStorageService } from "../platform/storage/offscreen-storage.service";
|
||||
import { ForegroundSyncService } from "../platform/sync/foreground-sync.service";
|
||||
import { SyncServiceListener } from "../platform/sync/sync-service.listener";
|
||||
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
|
||||
import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
|
||||
@@ -401,7 +398,7 @@ export default class MainBackground {
|
||||
|
||||
private popupViewCacheBackgroundService: PopupViewCacheBackgroundService;
|
||||
|
||||
constructor(public popupOnlyContext: boolean = false) {
|
||||
constructor() {
|
||||
// Services
|
||||
const lockedCallback = async (userId?: string) => {
|
||||
if (this.notificationsService != null) {
|
||||
@@ -460,45 +457,6 @@ export default class MainBackground {
|
||||
this.offscreenDocumentService,
|
||||
);
|
||||
|
||||
// Creates a session key for mv3 storage of large memory items
|
||||
const sessionKey = new Lazy(async () => {
|
||||
// Key already in session storage
|
||||
const sessionStorage = new BrowserMemoryStorageService();
|
||||
const existingKey = await sessionStorage.get<SymmetricCryptoKey>("session-key");
|
||||
if (existingKey) {
|
||||
if (sessionStorage.valuesRequireDeserialization) {
|
||||
return SymmetricCryptoKey.fromJSON(existingKey);
|
||||
}
|
||||
return existingKey;
|
||||
}
|
||||
|
||||
// New key
|
||||
const { derivedKey } = await this.keyGenerationService.createKeyWithPurpose(
|
||||
128,
|
||||
"ephemeral",
|
||||
"bitwarden-ephemeral",
|
||||
);
|
||||
await sessionStorage.save("session-key", derivedKey);
|
||||
return derivedKey;
|
||||
});
|
||||
|
||||
const mv3MemoryStorageCreator = () => {
|
||||
if (this.popupOnlyContext) {
|
||||
return new ForegroundMemoryStorageService();
|
||||
}
|
||||
|
||||
// For local backed session storage, we expect that the encrypted data on disk will persist longer than the encryption key in memory
|
||||
// and failures to decrypt because of that are completely expected. For this reason, we pass in `false` to the `EncryptServiceImplementation`
|
||||
// so that MAC failures are not logged.
|
||||
return new LocalBackedSessionStorageService(
|
||||
sessionKey,
|
||||
this.storageService,
|
||||
new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false),
|
||||
this.platformUtilsService,
|
||||
this.logService,
|
||||
);
|
||||
};
|
||||
|
||||
this.secureStorageService = this.storageService; // secure storage is not supported in browsers, so we use local storage and warn users when it is used
|
||||
|
||||
if (BrowserApi.isManifestVersion(3)) {
|
||||
@@ -506,18 +464,47 @@ export default class MainBackground {
|
||||
this.memoryStorageForStateProviders = new BrowserMemoryStorageService(); // mv3 stores to storage.session
|
||||
this.memoryStorageService = this.memoryStorageForStateProviders;
|
||||
} else {
|
||||
if (popupOnlyContext) {
|
||||
this.memoryStorageForStateProviders = new ForegroundMemoryStorageService();
|
||||
this.memoryStorageService = new ForegroundMemoryStorageService();
|
||||
} else {
|
||||
this.memoryStorageForStateProviders = new BackgroundMemoryStorageService(); // mv2 stores to memory
|
||||
this.memoryStorageService = this.memoryStorageForStateProviders;
|
||||
}
|
||||
this.memoryStorageForStateProviders = new BackgroundMemoryStorageService(); // mv2 stores to memory
|
||||
this.memoryStorageService = this.memoryStorageForStateProviders;
|
||||
}
|
||||
|
||||
this.largeObjectMemoryStorageForStateProviders = BrowserApi.isManifestVersion(3)
|
||||
? mv3MemoryStorageCreator() // mv3 stores to local-backed session storage
|
||||
: this.memoryStorageForStateProviders; // mv2 stores to the same location
|
||||
if (BrowserApi.isManifestVersion(3)) {
|
||||
// Creates a session key for mv3 storage of large memory items
|
||||
const sessionKey = new Lazy(async () => {
|
||||
// Key already in session storage
|
||||
const sessionStorage = new BrowserMemoryStorageService();
|
||||
const existingKey = await sessionStorage.get<SymmetricCryptoKey>("session-key");
|
||||
if (existingKey) {
|
||||
if (sessionStorage.valuesRequireDeserialization) {
|
||||
return SymmetricCryptoKey.fromJSON(existingKey);
|
||||
}
|
||||
return existingKey;
|
||||
}
|
||||
|
||||
// New key
|
||||
const { derivedKey } = await this.keyGenerationService.createKeyWithPurpose(
|
||||
128,
|
||||
"ephemeral",
|
||||
"bitwarden-ephemeral",
|
||||
);
|
||||
await sessionStorage.save("session-key", derivedKey);
|
||||
return derivedKey;
|
||||
});
|
||||
|
||||
this.largeObjectMemoryStorageForStateProviders = new LocalBackedSessionStorageService(
|
||||
sessionKey,
|
||||
this.storageService,
|
||||
// For local backed session storage, we expect that the encrypted data on disk will persist longer than the encryption key in memory
|
||||
// and failures to decrypt because of that are completely expected. For this reason, we pass in `false` to the `EncryptServiceImplementation`
|
||||
// so that MAC failures are not logged.
|
||||
new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false),
|
||||
this.platformUtilsService,
|
||||
this.logService,
|
||||
);
|
||||
} else {
|
||||
// mv2 stores to the same location
|
||||
this.largeObjectMemoryStorageForStateProviders = this.memoryStorageForStateProviders;
|
||||
}
|
||||
|
||||
const localStorageStorageService = BrowserApi.isManifestVersion(3)
|
||||
? new OffscreenStorageService(this.offscreenDocumentService)
|
||||
@@ -575,9 +562,10 @@ export default class MainBackground {
|
||||
this.derivedStateProvider,
|
||||
);
|
||||
|
||||
this.taskSchedulerService = this.popupOnlyContext
|
||||
? new ForegroundTaskSchedulerService(this.logService, this.stateProvider)
|
||||
: new BackgroundTaskSchedulerService(this.logService, this.stateProvider);
|
||||
this.taskSchedulerService = new BackgroundTaskSchedulerService(
|
||||
this.logService,
|
||||
this.stateProvider,
|
||||
);
|
||||
this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.scheduleNextSyncInterval, () =>
|
||||
this.fullSync(),
|
||||
);
|
||||
@@ -873,26 +861,24 @@ export default class MainBackground {
|
||||
|
||||
this.vaultSettingsService = new VaultSettingsService(this.stateProvider);
|
||||
|
||||
if (!this.popupOnlyContext) {
|
||||
this.vaultTimeoutService = new VaultTimeoutService(
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
this.cipherService,
|
||||
this.folderService,
|
||||
this.collectionService,
|
||||
this.platformUtilsService,
|
||||
this.messagingService,
|
||||
this.searchService,
|
||||
this.stateService,
|
||||
this.authService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.stateEventRunnerService,
|
||||
this.taskSchedulerService,
|
||||
this.logService,
|
||||
lockedCallback,
|
||||
logoutCallback,
|
||||
);
|
||||
}
|
||||
this.vaultTimeoutService = new VaultTimeoutService(
|
||||
this.accountService,
|
||||
this.masterPasswordService,
|
||||
this.cipherService,
|
||||
this.folderService,
|
||||
this.collectionService,
|
||||
this.platformUtilsService,
|
||||
this.messagingService,
|
||||
this.searchService,
|
||||
this.stateService,
|
||||
this.authService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.stateEventRunnerService,
|
||||
this.taskSchedulerService,
|
||||
this.logService,
|
||||
lockedCallback,
|
||||
logoutCallback,
|
||||
);
|
||||
this.containerService = new ContainerService(this.keyService, this.encryptService);
|
||||
|
||||
this.sendStateProvider = new SendStateProvider(this.stateProvider);
|
||||
@@ -913,59 +899,41 @@ export default class MainBackground {
|
||||
|
||||
this.providerService = new ProviderService(this.stateProvider);
|
||||
|
||||
if (this.popupOnlyContext) {
|
||||
this.syncService = new ForegroundSyncService(
|
||||
this.stateService,
|
||||
this.folderService,
|
||||
this.folderApiService,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
this.cipherService,
|
||||
this.collectionService,
|
||||
this.apiService,
|
||||
this.accountService,
|
||||
this.authService,
|
||||
this.sendService,
|
||||
this.sendApiService,
|
||||
messageListener,
|
||||
this.stateProvider,
|
||||
);
|
||||
} else {
|
||||
this.syncService = new DefaultSyncService(
|
||||
this.masterPasswordService,
|
||||
this.accountService,
|
||||
this.apiService,
|
||||
this.domainSettingsService,
|
||||
this.folderService,
|
||||
this.cipherService,
|
||||
this.keyService,
|
||||
this.collectionService,
|
||||
this.messagingService,
|
||||
this.policyService,
|
||||
this.sendService,
|
||||
this.logService,
|
||||
this.keyConnectorService,
|
||||
this.stateService,
|
||||
this.providerService,
|
||||
this.folderApiService,
|
||||
this.organizationService,
|
||||
this.sendApiService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.avatarService,
|
||||
logoutCallback,
|
||||
this.billingAccountProfileStateService,
|
||||
this.tokenService,
|
||||
this.authService,
|
||||
this.stateProvider,
|
||||
);
|
||||
this.syncService = new DefaultSyncService(
|
||||
this.masterPasswordService,
|
||||
this.accountService,
|
||||
this.apiService,
|
||||
this.domainSettingsService,
|
||||
this.folderService,
|
||||
this.cipherService,
|
||||
this.keyService,
|
||||
this.collectionService,
|
||||
this.messagingService,
|
||||
this.policyService,
|
||||
this.sendService,
|
||||
this.logService,
|
||||
this.keyConnectorService,
|
||||
this.stateService,
|
||||
this.providerService,
|
||||
this.folderApiService,
|
||||
this.organizationService,
|
||||
this.sendApiService,
|
||||
this.userDecryptionOptionsService,
|
||||
this.avatarService,
|
||||
logoutCallback,
|
||||
this.billingAccountProfileStateService,
|
||||
this.tokenService,
|
||||
this.authService,
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
this.syncServiceListener = new SyncServiceListener(
|
||||
this.syncService,
|
||||
messageListener,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
);
|
||||
|
||||
this.syncServiceListener = new SyncServiceListener(
|
||||
this.syncService,
|
||||
messageListener,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
);
|
||||
}
|
||||
this.eventUploadService = new EventUploadService(
|
||||
this.apiService,
|
||||
this.stateProvider,
|
||||
@@ -1112,122 +1080,128 @@ export default class MainBackground {
|
||||
this.isSafari = this.platformUtilsService.isSafari();
|
||||
|
||||
// Background
|
||||
if (!this.popupOnlyContext) {
|
||||
this.fido2Background = new Fido2Background(
|
||||
this.logService,
|
||||
this.fido2ActiveRequestManager,
|
||||
this.fido2ClientService,
|
||||
this.vaultSettingsService,
|
||||
this.scriptInjectorService,
|
||||
this.configService,
|
||||
this.authService,
|
||||
);
|
||||
|
||||
const lockService = new DefaultLockService(this.accountService, this.vaultTimeoutService);
|
||||
this.fido2Background = new Fido2Background(
|
||||
this.logService,
|
||||
this.fido2ActiveRequestManager,
|
||||
this.fido2ClientService,
|
||||
this.vaultSettingsService,
|
||||
this.scriptInjectorService,
|
||||
this.configService,
|
||||
this.authService,
|
||||
);
|
||||
|
||||
this.runtimeBackground = new RuntimeBackground(
|
||||
this,
|
||||
this.autofillService,
|
||||
this.platformUtilsService as BrowserPlatformUtilsService,
|
||||
this.notificationsService,
|
||||
this.autofillSettingsService,
|
||||
this.processReloadService,
|
||||
this.environmentService,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
this.configService,
|
||||
messageListener,
|
||||
this.accountService,
|
||||
lockService,
|
||||
);
|
||||
this.nativeMessagingBackground = new NativeMessagingBackground(
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
this.cryptoFunctionService,
|
||||
this.runtimeBackground,
|
||||
this.messagingService,
|
||||
this.appIdService,
|
||||
this.platformUtilsService,
|
||||
this.logService,
|
||||
this.authService,
|
||||
this.biometricStateService,
|
||||
this.accountService,
|
||||
);
|
||||
this.commandsBackground = new CommandsBackground(
|
||||
this,
|
||||
this.platformUtilsService,
|
||||
this.vaultTimeoutService,
|
||||
this.authService,
|
||||
() => this.generatePasswordToClipboard(),
|
||||
);
|
||||
this.notificationBackground = new NotificationBackground(
|
||||
this.autofillService,
|
||||
this.cipherService,
|
||||
this.authService,
|
||||
this.policyService,
|
||||
this.folderService,
|
||||
this.userNotificationSettingsService,
|
||||
this.domainSettingsService,
|
||||
this.environmentService,
|
||||
this.logService,
|
||||
this.themeStateService,
|
||||
this.configService,
|
||||
this.accountService,
|
||||
);
|
||||
const lockService = new DefaultLockService(this.accountService, this.vaultTimeoutService);
|
||||
|
||||
this.overlayNotificationsBackground = new OverlayNotificationsBackground(
|
||||
this.logService,
|
||||
this.configService,
|
||||
this.notificationBackground,
|
||||
);
|
||||
this.runtimeBackground = new RuntimeBackground(
|
||||
this,
|
||||
this.autofillService,
|
||||
this.platformUtilsService as BrowserPlatformUtilsService,
|
||||
this.notificationsService,
|
||||
this.autofillSettingsService,
|
||||
this.processReloadService,
|
||||
this.environmentService,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
this.configService,
|
||||
messageListener,
|
||||
this.accountService,
|
||||
lockService,
|
||||
);
|
||||
this.nativeMessagingBackground = new NativeMessagingBackground(
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
this.cryptoFunctionService,
|
||||
this.runtimeBackground,
|
||||
this.messagingService,
|
||||
this.appIdService,
|
||||
this.platformUtilsService,
|
||||
this.logService,
|
||||
this.authService,
|
||||
this.biometricStateService,
|
||||
this.accountService,
|
||||
);
|
||||
this.commandsBackground = new CommandsBackground(
|
||||
this,
|
||||
this.platformUtilsService,
|
||||
this.vaultTimeoutService,
|
||||
this.authService,
|
||||
() => this.generatePasswordToClipboard(),
|
||||
);
|
||||
this.notificationBackground = new NotificationBackground(
|
||||
this.autofillService,
|
||||
this.cipherService,
|
||||
this.authService,
|
||||
this.policyService,
|
||||
this.folderService,
|
||||
this.userNotificationSettingsService,
|
||||
this.domainSettingsService,
|
||||
this.environmentService,
|
||||
this.logService,
|
||||
this.themeStateService,
|
||||
this.configService,
|
||||
this.accountService,
|
||||
);
|
||||
|
||||
this.filelessImporterBackground = new FilelessImporterBackground(
|
||||
this.configService,
|
||||
this.authService,
|
||||
this.policyService,
|
||||
this.notificationBackground,
|
||||
this.importService,
|
||||
this.syncService,
|
||||
this.scriptInjectorService,
|
||||
);
|
||||
this.overlayNotificationsBackground = new OverlayNotificationsBackground(
|
||||
this.logService,
|
||||
this.configService,
|
||||
this.notificationBackground,
|
||||
);
|
||||
|
||||
this.autoSubmitLoginBackground = new AutoSubmitLoginBackground(
|
||||
this.logService,
|
||||
this.autofillService,
|
||||
this.scriptInjectorService,
|
||||
this.authService,
|
||||
this.configService,
|
||||
this.platformUtilsService,
|
||||
this.policyService,
|
||||
);
|
||||
this.filelessImporterBackground = new FilelessImporterBackground(
|
||||
this.configService,
|
||||
this.authService,
|
||||
this.policyService,
|
||||
this.notificationBackground,
|
||||
this.importService,
|
||||
this.syncService,
|
||||
this.scriptInjectorService,
|
||||
);
|
||||
|
||||
const contextMenuClickedHandler = new ContextMenuClickedHandler(
|
||||
(options) => this.platformUtilsService.copyToClipboard(options.text),
|
||||
async () => this.generatePasswordToClipboard(),
|
||||
async (tab, cipher) => {
|
||||
this.loginToAutoFill = cipher;
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
this.autoSubmitLoginBackground = new AutoSubmitLoginBackground(
|
||||
this.logService,
|
||||
this.autofillService,
|
||||
this.scriptInjectorService,
|
||||
this.authService,
|
||||
this.configService,
|
||||
this.platformUtilsService,
|
||||
this.policyService,
|
||||
);
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
BrowserApi.tabSendMessage(tab, {
|
||||
command: "collectPageDetails",
|
||||
tab: tab,
|
||||
sender: "contextMenu",
|
||||
});
|
||||
},
|
||||
this.authService,
|
||||
this.cipherService,
|
||||
this.totpService,
|
||||
this.eventCollectionService,
|
||||
this.userVerificationService,
|
||||
this.accountService,
|
||||
);
|
||||
const contextMenuClickedHandler = new ContextMenuClickedHandler(
|
||||
(options) => this.platformUtilsService.copyToClipboard(options.text),
|
||||
async (_tab) => {
|
||||
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
|
||||
const password = await this.passwordGenerationService.generatePassword(options);
|
||||
this.platformUtilsService.copyToClipboard(password);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.passwordGenerationService.addHistory(password);
|
||||
},
|
||||
async (tab, cipher) => {
|
||||
this.loginToAutoFill = cipher;
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.contextMenusBackground = new ContextMenusBackground(contextMenuClickedHandler);
|
||||
}
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
BrowserApi.tabSendMessage(tab, {
|
||||
command: "collectPageDetails",
|
||||
tab: tab,
|
||||
sender: "contextMenu",
|
||||
});
|
||||
},
|
||||
this.authService,
|
||||
this.cipherService,
|
||||
this.totpService,
|
||||
this.eventCollectionService,
|
||||
this.userVerificationService,
|
||||
this.accountService,
|
||||
);
|
||||
|
||||
this.contextMenusBackground = new ContextMenusBackground(contextMenuClickedHandler);
|
||||
|
||||
this.idleBackground = new IdleBackground(
|
||||
this.vaultTimeoutService,
|
||||
@@ -1246,29 +1220,27 @@ export default class MainBackground {
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
if (!this.popupOnlyContext) {
|
||||
this.mainContextMenuHandler = new MainContextMenuHandler(
|
||||
this.stateService,
|
||||
this.autofillSettingsService,
|
||||
this.i18nService,
|
||||
this.logService,
|
||||
this.billingAccountProfileStateService,
|
||||
);
|
||||
this.mainContextMenuHandler = new MainContextMenuHandler(
|
||||
this.stateService,
|
||||
this.autofillSettingsService,
|
||||
this.i18nService,
|
||||
this.logService,
|
||||
this.billingAccountProfileStateService,
|
||||
);
|
||||
|
||||
this.cipherContextMenuHandler = new CipherContextMenuHandler(
|
||||
this.mainContextMenuHandler,
|
||||
this.authService,
|
||||
this.cipherContextMenuHandler = new CipherContextMenuHandler(
|
||||
this.mainContextMenuHandler,
|
||||
this.authService,
|
||||
this.cipherService,
|
||||
);
|
||||
|
||||
if (chrome.webRequest != null && chrome.webRequest.onAuthRequired != null) {
|
||||
this.webRequestBackground = new WebRequestBackground(
|
||||
this.platformUtilsService,
|
||||
this.cipherService,
|
||||
this.authService,
|
||||
chrome.webRequest,
|
||||
);
|
||||
|
||||
if (chrome.webRequest != null && chrome.webRequest.onAuthRequired != null) {
|
||||
this.webRequestBackground = new WebRequestBackground(
|
||||
this.platformUtilsService,
|
||||
this.cipherService,
|
||||
this.authService,
|
||||
chrome.webRequest,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.keyService);
|
||||
@@ -1283,7 +1255,7 @@ export default class MainBackground {
|
||||
this.containerService.attachToGlobal(self);
|
||||
|
||||
// Only the "true" background should run migrations
|
||||
await this.stateService.init({ runMigrations: !this.popupOnlyContext });
|
||||
await this.stateService.init({ runMigrations: true });
|
||||
|
||||
// This is here instead of in in the InitService b/c we don't plan for
|
||||
// side effects to run in the Browser InitService.
|
||||
@@ -1305,10 +1277,6 @@ export default class MainBackground {
|
||||
|
||||
this.popupViewCacheBackgroundService.startObservingTabChanges();
|
||||
|
||||
if (this.popupOnlyContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.vaultTimeoutService.init(true);
|
||||
this.fido2Background.init();
|
||||
await this.runtimeBackground.init();
|
||||
@@ -1637,7 +1605,6 @@ export default class MainBackground {
|
||||
*/
|
||||
async initOverlayAndTabsBackground() {
|
||||
if (
|
||||
this.popupOnlyContext ||
|
||||
this.overlayBackground ||
|
||||
this.tabsBackground ||
|
||||
(await firstValueFrom(this.authService.activeAccountStatus$)) ===
|
||||
@@ -1678,6 +1645,7 @@ export default class MainBackground {
|
||||
this.fido2ActiveRequestManager,
|
||||
inlineMenuFieldQualificationService,
|
||||
this.themeStateService,
|
||||
this.totpService,
|
||||
() => this.generatePassword(),
|
||||
(password) => this.addPasswordToHistory(password),
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_extName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "2024.10.1",
|
||||
"version": "2024.11.0",
|
||||
"description": "__MSG_extDesc__",
|
||||
"default_locale": "en",
|
||||
"author": "Bitwarden Inc.",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"minimum_chrome_version": "102.0",
|
||||
"name": "__MSG_extName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "2024.10.1",
|
||||
"version": "2024.11.0",
|
||||
"description": "__MSG_extDesc__",
|
||||
"default_locale": "en",
|
||||
"author": "Bitwarden Inc.",
|
||||
|
||||
@@ -2,29 +2,6 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l
|
||||
|
||||
import MainBackground from "../background/main.background";
|
||||
|
||||
import { BrowserApi } from "./browser/browser-api";
|
||||
|
||||
const logService = new ConsoleLogService(false);
|
||||
if (BrowserApi.isManifestVersion(3)) {
|
||||
startHeartbeat().catch((error) => logService.error(error));
|
||||
}
|
||||
const bitwardenMain = ((self as any).bitwardenMain = new MainBackground());
|
||||
bitwardenMain.bootstrap().catch((error) => logService.error(error));
|
||||
|
||||
/**
|
||||
* Tracks when a service worker was last alive and extends the service worker
|
||||
* lifetime by writing the current time to extension storage every 20 seconds.
|
||||
*/
|
||||
async function runHeartbeat() {
|
||||
await chrome.storage.local.set({ "last-heartbeat": new Date().getTime() });
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the heartbeat interval which keeps the service worker alive.
|
||||
*/
|
||||
async function startHeartbeat() {
|
||||
// Run the heartbeat once at service worker startup, then again every 20 seconds.
|
||||
runHeartbeat()
|
||||
.then(() => setInterval(runHeartbeat, 20 * 1000))
|
||||
.catch((error) => logService.error(error));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
'tw-bg-background-alt tw-border-transparent':
|
||||
this.background === 'alt' && !pageContentScrolled(),
|
||||
'tw-bg-background tw-border-secondary-300':
|
||||
(this.background === 'alt' && pageContentScrolled()) || this.background === 'default',
|
||||
(this.background === 'alt' && pageContentScrolled()) || this.background === 'default'
|
||||
}"
|
||||
>
|
||||
<div class="tw-max-w-screen-sm tw-mx-auto tw-flex tw-justify-between tw-w-full">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[ngClass]="{
|
||||
'tw-invisible': loading || nonScrollable.childElementCount === 0,
|
||||
'tw-border-secondary-300': scrolled(),
|
||||
'tw-border-transparent': !scrolled(),
|
||||
'tw-border-transparent': !scrolled()
|
||||
}"
|
||||
>
|
||||
<ng-content select="[slot=above-scroll-area]"></ng-content>
|
||||
|
||||
@@ -24,8 +24,8 @@ import {
|
||||
LockComponentService,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { LockService, LoginEmailService, PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@@ -92,9 +92,15 @@ import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/imp
|
||||
import { PrimarySecondaryStorageService } from "@bitwarden/common/platform/storage/primary-secondary-storage.service";
|
||||
import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service";
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
|
||||
import {
|
||||
FolderService as FolderServiceAbstraction,
|
||||
InternalFolderService,
|
||||
} from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
@@ -107,7 +113,6 @@ import { ExtensionAnonLayoutWrapperDataService } from "../../auth/popup/extensio
|
||||
import { ExtensionLoginComponentService } from "../../auth/popup/login/extension-login-component.service";
|
||||
import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service";
|
||||
import AutofillService from "../../autofill/services/autofill.service";
|
||||
import MainBackground from "../../background/main.background";
|
||||
import { ForegroundBrowserBiometricsService } from "../../key-management/biometrics/foreground-browser-biometrics";
|
||||
import { BrowserKeyService } from "../../key-management/browser-key.service";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
@@ -117,12 +122,12 @@ import { ChromeMessageSender } from "../../platform/messaging/chrome-message.sen
|
||||
/* eslint-enable no-restricted-imports */
|
||||
import { OffscreenDocumentService } from "../../platform/offscreen-document/abstractions/offscreen-document";
|
||||
import { DefaultOffscreenDocumentService } from "../../platform/offscreen-document/offscreen-document.service";
|
||||
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
|
||||
import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service";
|
||||
import { PopupViewCacheService } from "../../platform/popup/view-cache/popup-view-cache.service";
|
||||
import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service";
|
||||
import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service";
|
||||
import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service";
|
||||
import BrowserMemoryStorageService from "../../platform/services/browser-memory-storage.service";
|
||||
import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service";
|
||||
import I18nService from "../../platform/services/i18n.service";
|
||||
import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service";
|
||||
@@ -130,6 +135,7 @@ import { BrowserSdkClientFactory } from "../../platform/services/sdk/browser-sdk
|
||||
import { ForegroundTaskSchedulerService } from "../../platform/services/task-scheduler/foreground-task-scheduler.service";
|
||||
import { BrowserStorageServiceProvider } from "../../platform/storage/browser-storage-service.provider";
|
||||
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
|
||||
import { ForegroundSyncService } from "../../platform/sync/foreground-sync.service";
|
||||
import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging";
|
||||
import { ExtensionLockComponentService } from "../../services/extension-lock-component.service";
|
||||
import { ForegroundVaultTimeoutService } from "../../services/vault-timeout/foreground-vault-timeout.service";
|
||||
@@ -151,26 +157,6 @@ const DISK_BACKUP_LOCAL_STORAGE = new SafeInjectionToken<
|
||||
AbstractStorageService & ObservableStorageService
|
||||
>("DISK_BACKUP_LOCAL_STORAGE");
|
||||
|
||||
const needsBackgroundInit = BrowserPopupUtils.backgroundInitializationRequired();
|
||||
const mainBackground: MainBackground = needsBackgroundInit
|
||||
? createLocalBgService()
|
||||
: BrowserApi.getBackgroundPage().bitwardenMain;
|
||||
|
||||
function createLocalBgService() {
|
||||
const localBgService = new MainBackground(true);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
localBgService.bootstrap();
|
||||
return localBgService;
|
||||
}
|
||||
|
||||
/** @deprecated This method needs to be removed as part of MV3 conversion. Please do not add more and actively try to remove usages */
|
||||
function getBgService<T>(service: keyof MainBackground) {
|
||||
return (): T => {
|
||||
return mainBackground ? (mainBackground[service] as any as T) : null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider definitions used in the ngModule.
|
||||
* Add your provider definition here using the safeProvider function as a wrapper. This will give you type safety.
|
||||
@@ -307,8 +293,23 @@ const safeProviders: SafeProvider[] = [
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SyncService,
|
||||
useFactory: getBgService<SyncService>("syncService"),
|
||||
deps: [],
|
||||
useClass: ForegroundSyncService,
|
||||
deps: [
|
||||
StateService,
|
||||
InternalFolderService,
|
||||
FolderApiServiceAbstraction,
|
||||
MessageSender,
|
||||
LogService,
|
||||
CipherService,
|
||||
CollectionService,
|
||||
ApiService,
|
||||
AccountServiceAbstraction,
|
||||
AuthService,
|
||||
InternalSendService,
|
||||
SendApiService,
|
||||
MessageListener,
|
||||
StateProvider,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: DomainSettingsService,
|
||||
@@ -358,11 +359,6 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: ForegroundVaultTimeoutService,
|
||||
deps: [MessagingServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: NotificationsService,
|
||||
useFactory: getBgService<NotificationsService>("notificationsService"),
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: VaultFilterService,
|
||||
useClass: VaultFilterService,
|
||||
@@ -382,8 +378,8 @@ const safeProviders: SafeProvider[] = [
|
||||
}),
|
||||
safeProvider({
|
||||
provide: MEMORY_STORAGE,
|
||||
useFactory: getBgService<AbstractStorageService>("memoryStorageService"),
|
||||
deps: [],
|
||||
useFactory: (memoryStorage: AbstractStorageService) => memoryStorage,
|
||||
deps: [OBSERVABLE_MEMORY_STORAGE],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: OBSERVABLE_MEMORY_STORAGE,
|
||||
@@ -392,9 +388,7 @@ const safeProviders: SafeProvider[] = [
|
||||
return new ForegroundMemoryStorageService();
|
||||
}
|
||||
|
||||
return getBgService<AbstractStorageService & ObservableStorageService>(
|
||||
"memoryStorageForStateProviders",
|
||||
)();
|
||||
return new BrowserMemoryStorageService();
|
||||
},
|
||||
deps: [],
|
||||
}),
|
||||
@@ -407,9 +401,7 @@ const safeProviders: SafeProvider[] = [
|
||||
return regularMemoryStorageService;
|
||||
}
|
||||
|
||||
return getBgService<AbstractStorageService & ObservableStorageService>(
|
||||
"largeObjectMemoryStorageForStateProviders",
|
||||
)();
|
||||
return new ForegroundMemoryStorageService();
|
||||
},
|
||||
deps: [OBSERVABLE_MEMORY_STORAGE],
|
||||
}),
|
||||
@@ -494,15 +486,7 @@ const safeProviders: SafeProvider[] = [
|
||||
}),
|
||||
safeProvider({
|
||||
provide: INTRAPROCESS_MESSAGING_SUBJECT,
|
||||
useFactory: () => {
|
||||
if (BrowserPopupUtils.backgroundInitializationRequired()) {
|
||||
// There is no persistent main background which means we have one in memory,
|
||||
// we need the same instance that our in memory background is utilizing.
|
||||
return getBgService("intraprocessMessagingSubject")();
|
||||
} else {
|
||||
return new Subject<Message<Record<string, unknown>>>();
|
||||
}
|
||||
},
|
||||
useFactory: () => new Subject<Message<Record<string, unknown>>>(),
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -514,23 +498,6 @@ const safeProviders: SafeProvider[] = [
|
||||
),
|
||||
deps: [INTRAPROCESS_MESSAGING_SUBJECT, LogService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: INTRAPROCESS_MESSAGING_SUBJECT,
|
||||
useFactory: () => {
|
||||
if (needsBackgroundInit) {
|
||||
// We will have created a popup within this context, in that case
|
||||
// we want to make sure we have the same subject as that context so we
|
||||
// can message with it.
|
||||
return getBgService("intraprocessMessagingSubject")();
|
||||
} else {
|
||||
// There isn't a locally created background so we will communicate with
|
||||
// the true background through chrome apis, in that case, we can just create
|
||||
// one for ourself.
|
||||
return new Subject<Message<Record<string, unknown>>>();
|
||||
}
|
||||
},
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: DISK_BACKUP_LOCAL_STORAGE,
|
||||
useFactory: (diskStorage: AbstractStorageService & ObservableStorageService) =>
|
||||
@@ -572,13 +539,7 @@ const safeProviders: SafeProvider[] = [
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ForegroundTaskSchedulerService,
|
||||
useFactory: (logService: LogService, stateProvider: StateProvider) => {
|
||||
if (needsBackgroundInit) {
|
||||
return getBgService<ForegroundTaskSchedulerService>("taskSchedulerService")();
|
||||
}
|
||||
|
||||
return new ForegroundTaskSchedulerService(logService, stateProvider);
|
||||
},
|
||||
useClass: ForegroundTaskSchedulerService,
|
||||
deps: [LogService, StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@bitwarden/cli",
|
||||
"description": "A secure and free password manager for all of your devices.",
|
||||
"version": "2024.10.0",
|
||||
"version": "2024.11.0",
|
||||
"keywords": [
|
||||
"bitwarden",
|
||||
"password",
|
||||
@@ -58,7 +58,7 @@
|
||||
"dependencies": {
|
||||
"@koa/multer": "3.0.2",
|
||||
"@koa/router": "13.1.0",
|
||||
"argon2": "0.40.1",
|
||||
"argon2": "0.41.1",
|
||||
"big-integer": "1.6.52",
|
||||
"browser-hrtime": "1.1.8",
|
||||
"chalk": "4.1.2",
|
||||
@@ -80,7 +80,7 @@
|
||||
"papaparse": "5.4.1",
|
||||
"proper-lockfile": "4.1.2",
|
||||
"rxjs": "7.8.1",
|
||||
"tldts": "6.1.56",
|
||||
"tldts": "6.1.58",
|
||||
"zxcvbn": "4.4.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export class ExportCommand {
|
||||
// format is 'undefined' => Defaults to 'csv'
|
||||
// Any other case => returns the options.format
|
||||
const format =
|
||||
password && options.format == "json" ? "encrypted_json" : (options.format ?? "csv");
|
||||
password && options.format == "json" ? "encrypted_json" : options.format ?? "csv";
|
||||
|
||||
if (!this.isSupportedExportFormat(format)) {
|
||||
return Response.badRequest(
|
||||
|
||||
60
apps/desktop/desktop_native/Cargo.lock
generated
60
apps/desktop/desktop_native/Cargo.lock
generated
@@ -546,6 +546,7 @@ dependencies = [
|
||||
"napi-derive",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1150,13 +1151,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1258,16 +1260,6 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
@@ -1937,26 +1929,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.38.0"
|
||||
version = "1.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
|
||||
checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.3.0"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
|
||||
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2236,7 +2227,7 @@ checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-result",
|
||||
"windows-result 0.1.2",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
@@ -2262,6 +2253,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bafa604f2104cf5ae2cc2db1dee84b7e6a5d11b05f737b60def0ffdc398cbc0a"
|
||||
dependencies = [
|
||||
"windows-result 0.2.0",
|
||||
"windows-strings",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
@@ -2271,6 +2273,24 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978d65aedf914c664c510d9de43c8fd85ca745eaff1ed53edf409b479e441663"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
|
||||
@@ -39,7 +39,7 @@ retry = "=2.0.0"
|
||||
scopeguard = "=1.2.0"
|
||||
sha2 = "=0.10.8"
|
||||
thiserror = "=1.0.61"
|
||||
tokio = { version = "=1.38.0", features = ["io-util", "sync", "macros"] }
|
||||
tokio = { version = "=1.41.0", features = ["io-util", "sync", "macros"] }
|
||||
tokio-util = "=0.7.12"
|
||||
typenum = "=1.17.0"
|
||||
|
||||
|
||||
@@ -21,5 +21,8 @@ napi-derive = "=2.16.12"
|
||||
tokio = { version = "1.38.0" }
|
||||
tokio-util = "0.7.11"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-registry = "=0.3.0"
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "=2.1.3"
|
||||
|
||||
4
apps/desktop/desktop_native/napi/index.d.ts
vendored
4
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -51,6 +51,10 @@ export declare namespace powermonitors {
|
||||
export function onLock(callback: (err: Error | null, ) => any): Promise<void>
|
||||
export function isLockMonitorAvailable(): Promise<boolean>
|
||||
}
|
||||
export declare namespace windows_registry {
|
||||
export function createKey(key: string, subkey: string, value: string): Promise<void>
|
||||
export function deleteKey(key: string, subkey: string): Promise<void>
|
||||
}
|
||||
export declare namespace ipc {
|
||||
export interface IpcMessage {
|
||||
clientId: number
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
||||
mod registry;
|
||||
|
||||
#[napi]
|
||||
pub mod passwords {
|
||||
/// Fetch the stored password from the keychain.
|
||||
@@ -190,6 +193,21 @@ pub mod powermonitors {
|
||||
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub mod windows_registry {
|
||||
#[napi]
|
||||
pub async fn create_key(key: String, subkey: String, value: String) -> napi::Result<()> {
|
||||
crate::registry::create_key(&key, &subkey, &value)
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn delete_key(key: String, subkey: String) -> napi::Result<()> {
|
||||
crate::registry::delete_key(&key, &subkey)
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub mod ipc {
|
||||
use desktop_core::ipc::server::{Message, MessageType};
|
||||
|
||||
9
apps/desktop/desktop_native/napi/src/registry/dummy.rs
Normal file
9
apps/desktop/desktop_native/napi/src/registry/dummy.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
pub fn create_key(_key: &str, _subkey: &str, _value: &str) -> Result<()> {
|
||||
bail!("Not implemented")
|
||||
}
|
||||
|
||||
pub fn delete_key(_key: &str, _subkey: &str) -> Result<()> {
|
||||
bail!("Not implemented")
|
||||
}
|
||||
4
apps/desktop/desktop_native/napi/src/registry/mod.rs
Normal file
4
apps/desktop/desktop_native/napi/src/registry/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[cfg_attr(target_os = "windows", path = "windows.rs")]
|
||||
#[cfg_attr(not(target_os = "windows"), path = "dummy.rs")]
|
||||
mod internal;
|
||||
pub use internal::*;
|
||||
29
apps/desktop/desktop_native/napi/src/registry/windows.rs
Normal file
29
apps/desktop/desktop_native/napi/src/registry/windows.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
fn convert_key(key: &str) -> Result<&'static windows_registry::Key> {
|
||||
Ok(match key.to_uppercase().as_str() {
|
||||
"HKEY_CURRENT_USER" | "HKCU" => windows_registry::CURRENT_USER,
|
||||
"HKEY_LOCAL_MACHINE" | "HKLM" => windows_registry::LOCAL_MACHINE,
|
||||
"HKEY_CLASSES_ROOT" | "HKCR" => windows_registry::CLASSES_ROOT,
|
||||
_ => bail!("Invalid key"),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_key(key: &str, subkey: &str, value: &str) -> Result<()> {
|
||||
let key = convert_key(key)?;
|
||||
|
||||
let subkey = key.create(subkey)?;
|
||||
|
||||
const DEFAULT: &str = "";
|
||||
subkey.set_string(DEFAULT, value)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_key(key: &str, subkey: &str) -> Result<()> {
|
||||
let key = convert_key(key)?;
|
||||
|
||||
key.remove_tree(subkey)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -90,13 +90,6 @@
|
||||
"electronUpdaterCompatibility": ">=0.0.1",
|
||||
"target": ["portable", "nsis-web", "appx"],
|
||||
"sign": "./sign.js",
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "../../node_modules/regedit/vbs",
|
||||
"to": "regedit/vbs",
|
||||
"filter": ["**/*"]
|
||||
}
|
||||
],
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "desktop_native/dist/desktop_proxy.${platform}-${arch}.exe",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@bitwarden/desktop",
|
||||
"description": "A secure and free password manager for all of your devices.",
|
||||
"version": "2024.10.3",
|
||||
"version": "2024.11.0",
|
||||
"keywords": [
|
||||
"bitwarden",
|
||||
"password",
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { existsSync, promises as fs } from "fs";
|
||||
import { homedir, userInfo } from "os";
|
||||
import * as path from "path";
|
||||
import * as util from "util";
|
||||
|
||||
import { ipcMain } from "electron";
|
||||
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { ipc } from "@bitwarden/desktop-napi";
|
||||
import { ipc, windows_registry } from "@bitwarden/desktop-napi";
|
||||
|
||||
import { isDev } from "../utils";
|
||||
|
||||
@@ -142,12 +141,12 @@ export class NativeMessagingMain {
|
||||
await this.writeManifest(path.join(destination, "chrome.json"), chromeJson);
|
||||
|
||||
const nmhs = this.getWindowsNMHS();
|
||||
for (const [key, value] of Object.entries(nmhs)) {
|
||||
for (const [name, [key, subkey]] of Object.entries(nmhs)) {
|
||||
let manifestPath = path.join(destination, "chrome.json");
|
||||
if (key === "Firefox") {
|
||||
if (name === "Firefox") {
|
||||
manifestPath = path.join(destination, "firefox.json");
|
||||
}
|
||||
await this.createWindowsRegistry(value, manifestPath);
|
||||
await windows_registry.createKey(key, subkey, manifestPath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -225,8 +224,8 @@ export class NativeMessagingMain {
|
||||
await this.removeIfExists(path.join(this.userPath, "browsers", "chrome.json"));
|
||||
|
||||
const nmhs = this.getWindowsNMHS();
|
||||
for (const [, value] of Object.entries(nmhs)) {
|
||||
await this.deleteWindowsRegistry(value);
|
||||
for (const [, [key, subkey]] of Object.entries(nmhs)) {
|
||||
await windows_registry.deleteKey(key, subkey);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -274,11 +273,14 @@ export class NativeMessagingMain {
|
||||
|
||||
private getWindowsNMHS() {
|
||||
return {
|
||||
Firefox: "HKCU\\SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden",
|
||||
Chrome: "HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden",
|
||||
Chromium: "HKCU\\SOFTWARE\\Chromium\\NativeMessagingHosts\\com.8bit.bitwarden",
|
||||
Firefox: ["HKCU", "SOFTWARE\\Mozilla\\NativeMessagingHosts\\com.8bit.bitwarden"],
|
||||
Chrome: ["HKCU", "SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\com.8bit.bitwarden"],
|
||||
Chromium: ["HKCU", "SOFTWARE\\Chromium\\NativeMessagingHosts\\com.8bit.bitwarden"],
|
||||
// Edge uses the same registry key as Chrome as a fallback, but it's has its own separate key as well.
|
||||
"Microsoft Edge": "HKCU\\SOFTWARE\\Microsoft\\Edge\\NativeMessagingHosts\\com.8bit.bitwarden",
|
||||
"Microsoft Edge": [
|
||||
"HKCU",
|
||||
"SOFTWARE\\Microsoft\\Edge\\NativeMessagingHosts\\com.8bit.bitwarden",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -419,52 +421,6 @@ export class NativeMessagingMain {
|
||||
return path.join(path.dirname(this.exePath), `desktop_proxy${ext}`);
|
||||
}
|
||||
|
||||
private getRegeditInstance() {
|
||||
// eslint-disable-next-line
|
||||
const regedit = require("regedit");
|
||||
regedit.setExternalVBSLocation(path.join(path.dirname(this.exePath), "resources/regedit/vbs"));
|
||||
|
||||
return regedit;
|
||||
}
|
||||
|
||||
private async createWindowsRegistry(location: string, jsonFile: string) {
|
||||
const regedit = this.getRegeditInstance();
|
||||
|
||||
const createKey = util.promisify(regedit.createKey);
|
||||
const putValue = util.promisify(regedit.putValue);
|
||||
|
||||
this.logService.debug(`Adding registry: ${location}`);
|
||||
|
||||
await createKey(location);
|
||||
|
||||
// Insert path to manifest
|
||||
const obj: any = {};
|
||||
obj[location] = {
|
||||
default: {
|
||||
value: jsonFile,
|
||||
type: "REG_DEFAULT",
|
||||
},
|
||||
};
|
||||
|
||||
return putValue(obj);
|
||||
}
|
||||
|
||||
private async deleteWindowsRegistry(key: string) {
|
||||
const regedit = this.getRegeditInstance();
|
||||
|
||||
const list = util.promisify(regedit.list);
|
||||
const deleteKey = util.promisify(regedit.deleteKey);
|
||||
|
||||
this.logService.debug(`Removing registry: ${key}`);
|
||||
|
||||
try {
|
||||
await list(key);
|
||||
await deleteKey(key);
|
||||
} catch {
|
||||
this.logService.error(`Unable to delete registry key: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
private homedir() {
|
||||
if (process.platform === "darwin") {
|
||||
return userInfo().homedir;
|
||||
|
||||
27
apps/desktop/src/package-lock.json
generated
27
apps/desktop/src/package-lock.json
generated
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@bitwarden/desktop",
|
||||
"version": "2024.10.3",
|
||||
"version": "2024.11.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@bitwarden/desktop",
|
||||
"version": "2024.10.3",
|
||||
"version": "2024.11.0",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@bitwarden/desktop-napi": "file:../desktop_native/napi",
|
||||
"argon2": "0.40.1"
|
||||
"argon2": "0.41.1"
|
||||
}
|
||||
},
|
||||
"../desktop_native/napi": {
|
||||
@@ -35,25 +35,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/argon2": {
|
||||
"version": "0.40.1",
|
||||
"resolved": "https://registry.npmjs.org/argon2/-/argon2-0.40.1.tgz",
|
||||
"integrity": "sha512-DjtHDwd7pm12qeWyfihHoM8Bn5vGcgH6sKwgPqwNYroRmxlrzadHEvMyuvQxN/V8YSyRRKD5x6ito09q1e9OyA==",
|
||||
"version": "0.41.1",
|
||||
"resolved": "https://registry.npmjs.org/argon2/-/argon2-0.41.1.tgz",
|
||||
"integrity": "sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@phc/format": "^1.0.0",
|
||||
"node-addon-api": "^7.1.0",
|
||||
"node-gyp-build": "^4.8.0"
|
||||
"node-addon-api": "^8.1.0",
|
||||
"node-gyp-build": "^4.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"license": "MIT"
|
||||
"version": "8.2.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.2.1.tgz",
|
||||
"integrity": "sha512-vmEOvxwiH8tlOcv4SyE8RH34rI5/nWVaigUeAUPawC6f0+HoDthwI0vkMu4tbtsZrXq6QXFfrkhjofzKEs5tpA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.2",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@bitwarden/desktop",
|
||||
"productName": "Bitwarden",
|
||||
"description": "A secure and free password manager for all of your devices.",
|
||||
"version": "2024.10.3",
|
||||
"version": "2024.11.0",
|
||||
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||
"homepage": "https://bitwarden.com",
|
||||
"license": "GPL-3.0",
|
||||
@@ -13,6 +13,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@bitwarden/desktop-napi": "file:../desktop_native/napi",
|
||||
"argon2": "0.40.1"
|
||||
"argon2": "0.41.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed(collectionsGrouping),
|
||||
'bwi-angle-down': !isCollapsed(collectionsGrouping),
|
||||
'bwi-angle-down': !isCollapsed(collectionsGrouping)
|
||||
}"
|
||||
></i>
|
||||
{{ collectionsGrouping.name | i18n }}
|
||||
@@ -42,7 +42,7 @@
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed(c.node),
|
||||
'bwi-angle-down': !isCollapsed(c.node),
|
||||
'bwi-angle-down': !isCollapsed(c.node)
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed(foldersGrouping),
|
||||
'bwi-angle-down': !isCollapsed(foldersGrouping),
|
||||
'bwi-angle-down': !isCollapsed(foldersGrouping)
|
||||
}"
|
||||
></i>
|
||||
{{ foldersGrouping.name | i18n }}
|
||||
@@ -33,7 +33,7 @@
|
||||
<li
|
||||
*ngFor="let f of folders"
|
||||
[ngClass]="{
|
||||
active: f.node.id === activeFilter.selectedFolderId && activeFilter.selectedFolder,
|
||||
active: f.node.id === activeFilter.selectedFolderId && activeFilter.selectedFolder
|
||||
}"
|
||||
class="filter-option"
|
||||
>
|
||||
@@ -52,7 +52,7 @@
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed(f.node),
|
||||
'bwi-angle-down': !isCollapsed(f.node),
|
||||
'bwi-angle-down': !isCollapsed(f.node)
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed,
|
||||
'bwi-angle-down': !isCollapsed,
|
||||
'bwi-angle-down': !isCollapsed
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
@@ -74,7 +74,7 @@
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed,
|
||||
'bwi-angle-down': !isCollapsed,
|
||||
'bwi-angle-down': !isCollapsed
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed,
|
||||
'bwi-angle-down': !isCollapsed,
|
||||
'bwi-angle-down': !isCollapsed
|
||||
}"
|
||||
></i>
|
||||
{{ typesNode.name | i18n }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@bitwarden/web-vault",
|
||||
"version": "2024.10.5",
|
||||
"version": "2024.11.0",
|
||||
"scripts": {
|
||||
"build:oss": "tsc-strict && webpack",
|
||||
"build:bit": "tsc-strict && webpack -c ../../bitwarden_license/bit-web/webpack.config.js",
|
||||
|
||||
@@ -23,15 +23,14 @@
|
||||
<bit-tab [label]="'role' | i18n">
|
||||
<ng-container *ngIf="!editMode">
|
||||
<p bitTypography="body1">{{ "inviteUserDesc" | i18n }}</p>
|
||||
<bit-form-field *ngIf="remainingSeats$ | async as remainingSeats">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "email" | i18n }}</bit-label>
|
||||
<input id="emails" type="text" appAutoFocus bitInput formControlName="emails" />
|
||||
<bit-hint *ngIf="remainingSeats > 1; else singleSeat">{{
|
||||
"inviteMultipleEmailDesc" | i18n: remainingSeats
|
||||
<bit-hint>{{
|
||||
"inviteMultipleEmailDesc"
|
||||
| i18n
|
||||
: (organization.productTierType === ProductTierType.TeamsStarter ? "10" : "20")
|
||||
}}</bit-hint>
|
||||
<ng-template #singleSeat>
|
||||
<bit-hint>{{ "inviteSingleEmailDesc" | i18n: remainingSeats }}</bit-hint>
|
||||
</ng-template>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
<bit-radio-group formControlName="type">
|
||||
@@ -265,6 +264,16 @@
|
||||
<button
|
||||
*ngIf="editMode"
|
||||
type="button"
|
||||
bitIconButton="bwi-close"
|
||||
buttonType="danger"
|
||||
bitFormButton
|
||||
[appA11yTitle]="'remove' | i18n"
|
||||
[bitAction]="remove"
|
||||
[disabled]="loading"
|
||||
></button>
|
||||
<button
|
||||
*ngIf="editMode && params.managedByOrganization === true"
|
||||
type="button"
|
||||
bitIconButton="bwi-trash"
|
||||
buttonType="danger"
|
||||
bitFormButton
|
||||
|
||||
@@ -65,6 +65,7 @@ export interface MemberDialogParams {
|
||||
isOnSecretsManagerStandalone: boolean;
|
||||
initialTab?: MemberDialogTab;
|
||||
numConfirmedMembers: number;
|
||||
managedByOrganization?: boolean;
|
||||
}
|
||||
|
||||
export enum MemberDialogResult {
|
||||
@@ -89,7 +90,6 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
PermissionMode = PermissionMode;
|
||||
showNoMasterPasswordWarning = false;
|
||||
isOnSecretsManagerStandalone: boolean;
|
||||
remainingSeats$: Observable<number>;
|
||||
|
||||
protected organization$: Observable<Organization>;
|
||||
protected collectionAccessItems: AccessItemView[] = [];
|
||||
@@ -251,10 +251,6 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
|
||||
this.loading = false;
|
||||
});
|
||||
|
||||
this.remainingSeats$ = this.organization$.pipe(
|
||||
map((organization) => organization.seats - this.params.numConfirmedMembers),
|
||||
);
|
||||
}
|
||||
|
||||
private setFormValidators(organization: Organization) {
|
||||
@@ -469,7 +465,7 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
this.close(MemberDialogResult.Saved);
|
||||
};
|
||||
|
||||
delete = async () => {
|
||||
remove = async () => {
|
||||
if (!this.editMode) {
|
||||
return;
|
||||
}
|
||||
@@ -566,6 +562,39 @@ export class MemberDialogComponent implements OnDestroy {
|
||||
this.close(MemberDialogResult.Restored);
|
||||
};
|
||||
|
||||
delete = async () => {
|
||||
if (!this.editMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
key: "deleteOrganizationUser",
|
||||
placeholders: [this.params.name],
|
||||
},
|
||||
content: { key: "deleteOrganizationUserWarning" },
|
||||
type: "warning",
|
||||
acceptButtonText: { key: "delete" },
|
||||
cancelButtonText: { key: "cancel" },
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.organizationUserApiService.deleteOrganizationUser(
|
||||
this.params.organizationId,
|
||||
this.params.organizationUserId,
|
||||
);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("organizationUserDeleted", this.params.name),
|
||||
});
|
||||
this.close(MemberDialogResult.Deleted);
|
||||
};
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
|
||||
@@ -320,6 +320,17 @@
|
||||
<i aria-hidden="true" class="bwi bwi-close"></i> {{ "remove" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="u.managedByOrganization === true"
|
||||
type="button"
|
||||
bitMenuItem
|
||||
(click)="deleteUser(u)"
|
||||
>
|
||||
<span class="tw-text-danger">
|
||||
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>
|
||||
{{ "delete" | i18n }}
|
||||
</span>
|
||||
</button>
|
||||
</bit-menu>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -486,7 +486,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
const enableUpgradePasswordManagerSub = await firstValueFrom(
|
||||
this.enableUpgradePasswordManagerSub$,
|
||||
);
|
||||
if (enableUpgradePasswordManagerSub) {
|
||||
if (enableUpgradePasswordManagerSub && this.organization.canEditSubscription) {
|
||||
const reference = openChangePlanDialog(this.dialogService, {
|
||||
data: {
|
||||
organizationId: this.organization.id,
|
||||
@@ -518,6 +518,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone,
|
||||
initialTab: initialTab,
|
||||
numConfirmedMembers: this.dataSource.confirmedUserCount,
|
||||
managedByOrganization: user?.managedByOrganization,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -725,6 +726,40 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
return true;
|
||||
}
|
||||
|
||||
async deleteUser(user: OrganizationUserView) {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
key: "deleteOrganizationUser",
|
||||
placeholders: [this.userNamePipe.transform(user)],
|
||||
},
|
||||
content: { key: "deleteOrganizationUserWarning" },
|
||||
type: "warning",
|
||||
acceptButtonText: { key: "delete" },
|
||||
cancelButtonText: { key: "cancel" },
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.actionPromise = this.organizationUserApiService.deleteOrganizationUser(
|
||||
this.organization.id,
|
||||
user.id,
|
||||
);
|
||||
try {
|
||||
await this.actionPromise;
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("organizationUserDeleted", this.userNamePipe.transform(user)),
|
||||
});
|
||||
this.dataSource.removeUser(user);
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
}
|
||||
this.actionPromise = null;
|
||||
}
|
||||
|
||||
private async noMasterPasswordConfirmationDialog(user: OrganizationUserView) {
|
||||
return this.dialogService.openSimpleDialog({
|
||||
title: {
|
||||
|
||||
@@ -21,7 +21,13 @@
|
||||
>
|
||||
{{ "purgeVault" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="danger" [bitAction]="deleteAccount">
|
||||
<button
|
||||
*ngIf="showDeleteAccount$ | async"
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="danger"
|
||||
[bitAction]="deleteAccount"
|
||||
>
|
||||
{{ "deleteAccount" | i18n }}
|
||||
</button>
|
||||
</app-danger-zone>
|
||||
|
||||
@@ -23,6 +23,7 @@ export class AccountComponent implements OnInit {
|
||||
|
||||
showChangeEmail$: Observable<boolean>;
|
||||
showPurgeVault$: Observable<boolean>;
|
||||
showDeleteAccount$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private modalService: ModalService,
|
||||
@@ -63,6 +64,16 @@ export class AccountComponent implements OnInit {
|
||||
!isAccountDeprovisioningEnabled || !userIsManagedByOrganization,
|
||||
),
|
||||
);
|
||||
|
||||
this.showDeleteAccount$ = combineLatest([
|
||||
isAccountDeprovisioningEnabled$,
|
||||
userIsManagedByOrganization$,
|
||||
]).pipe(
|
||||
map(
|
||||
([isAccountDeprovisioningEnabled, userIsManagedByOrganization]) =>
|
||||
!isAccountDeprovisioningEnabled || !userIsManagedByOrganization,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async deauthorizeSessions() {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
title="{{ 'customColor' | i18n }}"
|
||||
[ngClass]="{
|
||||
'!tw-outline-[3px] tw-outline-primary-600 hover:tw-outline-[3px] hover:tw-outline-primary-600':
|
||||
customColorSelected,
|
||||
customColorSelected
|
||||
}"
|
||||
class="tw-relative tw-flex tw-h-24 tw-w-24 tw-cursor-pointer tw-place-content-center tw-content-center tw-justify-center tw-rounded-full tw-border tw-border-solid tw-border-secondary-600 tw-outline tw-outline-0 tw-outline-offset-1 hover:tw-outline-1 hover:tw-outline-primary-300 focus:tw-outline-2 focus:tw-outline-primary-600"
|
||||
[style.background-color]="customColor$ | async"
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
[organizationInfo]="{
|
||||
name: orgInfoFormGroup.value.name,
|
||||
email: orgInfoFormGroup.value.billingEmail,
|
||||
type: trialOrganizationType,
|
||||
type: trialOrganizationType
|
||||
}"
|
||||
[subscriptionProduct]="
|
||||
product === ProductType.SecretsManager
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
[organizationInfo]="{
|
||||
name: formGroup.get('name').value,
|
||||
email: formGroup.get('email').value,
|
||||
type: productType,
|
||||
type: productType
|
||||
}"
|
||||
[subscriptionProduct]="SubscriptionProduct.SecretsManager"
|
||||
(steppedBack)="steppedBack()"
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
[organizationInfo]="{
|
||||
name: orgInfoFormGroup.get('name').value,
|
||||
email: orgInfoFormGroup.get('email').value,
|
||||
type: trialOrganizationType,
|
||||
type: trialOrganizationType
|
||||
}"
|
||||
[subscriptionProduct]="SubscriptionProduct.PasswordManager"
|
||||
(steppedBack)="previousStep()"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[disabled]="disabled"
|
||||
class="tw-flex tw-w-full tw-items-center tw-border-none tw-bg-transparent"
|
||||
[ngClass]="{
|
||||
'hover:tw-bg-secondary-100': !disabled && step.editable,
|
||||
'hover:tw-bg-secondary-100': !disabled && step.editable
|
||||
}"
|
||||
[attr.aria-expanded]="selected"
|
||||
>
|
||||
@@ -16,7 +16,7 @@
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-600 tw-text-contrast': selected,
|
||||
'tw-bg-secondary-300 tw-text-main': !selected && !disabled && step.editable,
|
||||
'tw-bg-transparent tw-text-muted': disabled,
|
||||
'tw-bg-transparent tw-text-muted': disabled
|
||||
}"
|
||||
>
|
||||
{{ stepNumber }}
|
||||
@@ -30,13 +30,13 @@
|
||||
<div
|
||||
class="tw-txt-main tw-mt-3.5 tw-h-12 tw-text-left tw-leading-snug"
|
||||
[ngClass]="{
|
||||
'tw-font-bold': selected,
|
||||
'tw-font-bold': selected
|
||||
}"
|
||||
>
|
||||
<p
|
||||
class="main-label text tw-mb-1 tw-text-main"
|
||||
[ngClass]="{
|
||||
'tw-mt-1': !step.subLabel,
|
||||
'tw-mt-1': !step.subLabel
|
||||
}"
|
||||
>
|
||||
{{ step.label }}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
class="tw-inline-block tw-w-11/12 tw-pl-7"
|
||||
[ngClass]="{
|
||||
'tw-border-0 tw-border-l tw-border-solid tw-border-secondary-300': applyBorder,
|
||||
'tw-pt-6': addSubLabelSpacing,
|
||||
'tw-pt-6': addSubLabelSpacing
|
||||
}"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
class="tw-bg-secondary-100 tw-text-center !tw-border-0 tw-text-sm tw-font-bold tw-py-1"
|
||||
[ngClass]="{
|
||||
'tw-bg-primary-700 !tw-text-contrast': selectableProduct === selectedPlan,
|
||||
'tw-bg-secondary-100': !(selectableProduct === selectedPlan),
|
||||
'tw-bg-secondary-100': !(selectableProduct === selectedPlan)
|
||||
}"
|
||||
>
|
||||
{{ "recommended" | i18n }}
|
||||
@@ -82,7 +82,7 @@
|
||||
class="tw-px-2 tw-pb-[4px]"
|
||||
[ngClass]="{
|
||||
'tw-py-1': !(selectableProduct === selectedPlan),
|
||||
'tw-py-0': selectableProduct === selectedPlan,
|
||||
'tw-py-0': selectableProduct === selectedPlan
|
||||
}"
|
||||
>
|
||||
<h3
|
||||
|
||||
@@ -261,7 +261,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
];
|
||||
this.discountPercentageFromSub = this.isSecretsManagerTrial()
|
||||
? 0
|
||||
: (this.sub?.customerDiscount?.percentOff ?? 0);
|
||||
: this.sub?.customerDiscount?.percentOff ?? 0;
|
||||
|
||||
this.setInitialPlanSelection();
|
||||
this.loading = false;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
class="-tw-m-6 tw-mb-3 tw-flex tw-flex-col tw-p-6"
|
||||
[ngClass]="{
|
||||
'tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300 tw-bg-background-alt tw-pb-0':
|
||||
tabsContainer.childElementCount !== 0,
|
||||
tabsContainer.childElementCount !== 0
|
||||
}"
|
||||
>
|
||||
<div class="tw-flex">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<section
|
||||
[ngStyle]="{
|
||||
'--num-products': products.bento.length,
|
||||
'grid-template-columns': 'repeat(min(var(--num-products,1),3),auto)',
|
||||
'grid-template-columns': 'repeat(min(var(--num-products,1),3),auto)'
|
||||
}"
|
||||
class="tw-grid tw-gap-2"
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<i class="bwi bwi-fw !tw-mr-4" [ngClass]="completed ? 'bwi-check tw-text-success' : icon"></i
|
||||
><span
|
||||
[ngClass]="{
|
||||
'tw-text-primary-700 tw-line-through tw-decoration-primary-700 tw-opacity-50': completed,
|
||||
'tw-text-primary-700 tw-line-through tw-decoration-primary-700 tw-opacity-50': completed
|
||||
}"
|
||||
>{{ title }}<i class="bwi bwi-angle-right tw-ml-1"></i
|
||||
></span>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
{{ "refresh" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<bit-tab-group [(selectedIndex)]="tabIndex">
|
||||
<bit-tab-group [(selectedIndex)]="tabIndex" (selectedIndexChange)="onTabChange($event)">
|
||||
<bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
|
||||
<tools-all-applications></tools-all-applications>
|
||||
</bit-tab>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { first } from "rxjs";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components";
|
||||
@@ -18,7 +17,7 @@ import { PasswordHealthComponent } from "./password-health.component";
|
||||
|
||||
export enum AccessIntelligenceTabType {
|
||||
AllApps = 0,
|
||||
PriorityApps = 1,
|
||||
CriticalApps = 1,
|
||||
NotifiedMembers = 2,
|
||||
}
|
||||
|
||||
@@ -58,8 +57,19 @@ export class AccessIntelligenceComponent {
|
||||
);
|
||||
}
|
||||
|
||||
constructor(route: ActivatedRoute) {
|
||||
route.queryParams.pipe(takeUntilDestroyed(), first()).subscribe(({ tabIndex }) => {
|
||||
onTabChange = async (newIndex: number) => {
|
||||
await this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { tabIndex: newIndex },
|
||||
queryParamsHandling: "merge",
|
||||
});
|
||||
};
|
||||
|
||||
constructor(
|
||||
protected route: ActivatedRoute,
|
||||
private router: Router,
|
||||
) {
|
||||
route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => {
|
||||
this.tabIndex = !isNaN(tabIndex) ? tabIndex : AccessIntelligenceTabType.AllApps;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,13 +14,15 @@
|
||||
</h2>
|
||||
</ng-container>
|
||||
<ng-container slot="description">
|
||||
<p class="tw-text-muted">
|
||||
{{ "noAppsInOrgDescription" | i18n }}
|
||||
<div class="tw-flex tw-flex-col tw-mb-2">
|
||||
<span class="tw-text-muted">
|
||||
{{ "noAppsInOrgDescription" | i18n }}
|
||||
</span>
|
||||
<a class="text-primary" routerLink="/login">{{ "learnMore" | i18n }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container slot="button">
|
||||
<button bitButton buttonType="primary" type="button">
|
||||
<button (click)="goToCreateNewLoginItem()" bitButton buttonType="primary" type="button">
|
||||
{{ "createNewLoginItem" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
@@ -50,7 +52,15 @@
|
||||
class="tw-grow"
|
||||
[formControl]="searchControl"
|
||||
></bit-search>
|
||||
<button class="tw-rounded-lg" type="button" buttonType="secondary" bitButton>
|
||||
<button
|
||||
class="tw-rounded-lg"
|
||||
type="button"
|
||||
buttonType="secondary"
|
||||
bitButton
|
||||
[disabled]="!selectedIds.size"
|
||||
[loading]="markingAsCritical"
|
||||
(click)="markAppsAsCritical()"
|
||||
>
|
||||
<i class="bwi bwi-star-f tw-mr-2"></i>
|
||||
{{ "markAppAsCritical" | i18n }}
|
||||
</button>
|
||||
|
||||
@@ -40,6 +40,7 @@ export class AllApplicationsComponent implements OnInit {
|
||||
protected loading = false;
|
||||
protected organization: Organization;
|
||||
noItemsIcon = Icons.Security;
|
||||
protected markingAsCritical = false;
|
||||
|
||||
// MOCK DATA
|
||||
protected mockData = applicationTableMockData;
|
||||
@@ -76,8 +77,18 @@ export class AllApplicationsComponent implements OnInit {
|
||||
.subscribe((v) => (this.dataSource.filter = v));
|
||||
}
|
||||
|
||||
goToCreateNewLoginItem = async () => {
|
||||
// TODO: implement
|
||||
this.toastService.showToast({
|
||||
variant: "warning",
|
||||
title: null,
|
||||
message: "Not yet implemented",
|
||||
});
|
||||
};
|
||||
|
||||
markAppsAsCritical = async () => {
|
||||
// TODO: Send to API once implemented
|
||||
this.markingAsCritical = true;
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
this.selectedIds.clear();
|
||||
@@ -87,6 +98,7 @@ export class AllApplicationsComponent implements OnInit {
|
||||
message: this.i18nService.t("appsMarkedAsCritical"),
|
||||
});
|
||||
resolve(true);
|
||||
this.markingAsCritical = false;
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
</p>
|
||||
</ng-container>
|
||||
<ng-container slot="button">
|
||||
<button bitButton buttonType="primary" type="button">{{ "markCriticalApps" | i18n }}</button>
|
||||
<button (click)="goToAllAppsTab()" bitButton buttonType="primary" type="button">
|
||||
{{ "markCriticalApps" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-no-items>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, DestroyRef, inject, OnInit } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormControl } from "@angular/forms";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { debounceTime, map } from "rxjs";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -12,6 +12,7 @@ import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||
|
||||
import { AccessIntelligenceTabType } from "./access-intelligence.component";
|
||||
import { applicationTableMockData } from "./application-table.mock";
|
||||
|
||||
@Component({
|
||||
@@ -26,8 +27,10 @@ export class CriticalApplicationsComponent implements OnInit {
|
||||
protected searchControl = new FormControl("", { nonNullable: true });
|
||||
private destroyRef = inject(DestroyRef);
|
||||
protected loading = false;
|
||||
protected organizationId: string;
|
||||
noItemsIcon = Icons.Security;
|
||||
// MOCK DATA
|
||||
protected mockData = applicationTableMockData;
|
||||
protected mockAtRiskMembersCount = 0;
|
||||
protected mockAtRiskAppsCount = 0;
|
||||
protected mockTotalMembersCount = 0;
|
||||
@@ -38,18 +41,26 @@ export class CriticalApplicationsComponent implements OnInit {
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map(async (params) => {
|
||||
// const organizationId = params.get("organizationId");
|
||||
this.organizationId = params.get("organizationId");
|
||||
// TODO: use organizationId to fetch data
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
goToAllAppsTab = async () => {
|
||||
await this.router.navigate([`organizations/${this.organizationId}/access-intelligence`], {
|
||||
queryParams: { tabIndex: AccessIntelligenceTabType.AllApps },
|
||||
queryParamsHandling: "merge",
|
||||
});
|
||||
};
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected activatedRoute: ActivatedRoute,
|
||||
protected router: Router,
|
||||
) {
|
||||
this.dataSource.data = applicationTableMockData;
|
||||
this.dataSource.data = []; //applicationTableMockData;
|
||||
this.searchControl.valueChanges
|
||||
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||
.subscribe((v) => (this.dataSource.filter = v));
|
||||
|
||||
@@ -497,7 +497,7 @@
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-eye': !showCardNumber,
|
||||
'bwi-eye-slash': showCardNumber,
|
||||
'bwi-eye-slash': showCardNumber
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
@@ -66,7 +66,7 @@ export class RoutedVaultFilterService implements OnDestroy {
|
||||
collectionId: filter.collectionId ?? null,
|
||||
folderId: filter.folderId ?? null,
|
||||
organizationId:
|
||||
filter.organizationIdParamType === "path" ? null : (filter.organizationId ?? null),
|
||||
filter.organizationIdParamType === "path" ? null : filter.organizationId ?? null,
|
||||
type: filter.type ?? null,
|
||||
},
|
||||
queryParamsHandling: "merge",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
>
|
||||
<h3
|
||||
[ngClass]="{
|
||||
active: isAllVaultsSelected || isNodeSelected(headerNode),
|
||||
active: isAllVaultsSelected || isNodeSelected(headerNode)
|
||||
}"
|
||||
>
|
||||
{{ headerNode.node.name | i18n }}
|
||||
@@ -44,7 +44,7 @@
|
||||
<li
|
||||
*ngFor="let f of filters"
|
||||
[ngClass]="{
|
||||
active: isNodeSelected(f),
|
||||
active: isNodeSelected(f)
|
||||
}"
|
||||
class="filter-option"
|
||||
>
|
||||
@@ -62,7 +62,7 @@
|
||||
class="bwi bwi-fw"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': isCollapsed(f.node),
|
||||
'bwi-angle-down': !isCollapsed(f.node),
|
||||
'bwi-angle-down': !isCollapsed(f.node)
|
||||
}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
|
||||
@@ -3218,9 +3218,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"inviteSingleEmailDesc": {
|
||||
"message": "You have 1 invite remaining."
|
||||
},
|
||||
"userUsingTwoStep": {
|
||||
"message": "This user is using two-step login to protect their account."
|
||||
},
|
||||
@@ -9557,5 +9554,31 @@
|
||||
},
|
||||
"single-org-revoked-user-warning": {
|
||||
"message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations."
|
||||
},
|
||||
"deleteOrganizationUser": {
|
||||
"message": "Delete $NAME$",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"content": "$1",
|
||||
"example": "John Doe"
|
||||
},
|
||||
"description": "Title for the delete organization user dialog"
|
||||
}
|
||||
},
|
||||
"deleteOrganizationUserWarning": {
|
||||
"message": "When a member is deleted, their Bitwarden account and individual vault data will be permanently deleted. Collection data will remain in the organization. To reinstate them they must create an account and be onboarded again.",
|
||||
"description": "Warning for the delete organization user dialog"
|
||||
},
|
||||
"organizationUserDeleted": {
|
||||
"message": "Deleted $NAME$",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"content": "$1",
|
||||
"example": "John Doe"
|
||||
}
|
||||
}
|
||||
},
|
||||
"organizationUserDeletedDesc": {
|
||||
"message": "The user was removed from the organization and all associated user data has been deleted."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import "core-js/stable";
|
||||
require("zone.js/dist/zone");
|
||||
import "zone.js";
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
// Production
|
||||
} else {
|
||||
// Development and test
|
||||
Error["stackTraceLimit"] = Infinity;
|
||||
require("zone.js/dist/long-stack-trace-zone");
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@
|
||||
aria-hidden="true"
|
||||
[ngClass]="{
|
||||
'bwi-angle-right': !showOpenIdCustomizations,
|
||||
'bwi-angle-down': showOpenIdCustomizations,
|
||||
'bwi-angle-down': showOpenIdCustomizations
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
class="tw-text-muted tw-grid tw-grid-flow-col tw-gap-1 tw-grid-cols-1"
|
||||
[ngClass]="{
|
||||
'tw-grid-rows-1': additionalSeatsPurchased <= 0,
|
||||
'tw-grid-rows-2': additionalSeatsPurchased > 0,
|
||||
'tw-grid-rows-2': additionalSeatsPurchased > 0
|
||||
}"
|
||||
>
|
||||
<span class="tw-col-span-1"
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
class="tw-grid tw-grid-flow-col tw-gap-1 tw-grid-cols-1"
|
||||
[ngClass]="{
|
||||
'tw-grid-rows-1': additionalSeatsPurchased === 0,
|
||||
'tw-grid-rows-2': purchasingSeats || sellingSeats,
|
||||
'tw-grid-rows-2': purchasingSeats || sellingSeats
|
||||
}"
|
||||
>
|
||||
<span class="tw-col-span-1">
|
||||
|
||||
@@ -275,4 +275,11 @@ export abstract class OrganizationUserApiService {
|
||||
organizationId: string,
|
||||
ids: string[],
|
||||
): Promise<ListResponse<OrganizationUserBulkResponse>>;
|
||||
|
||||
/**
|
||||
* Remove an organization user's access to the organization and delete their account data
|
||||
* @param organizationId - Identifier for the organization the user belongs to
|
||||
* @param id - Organization user identifier
|
||||
*/
|
||||
abstract deleteOrganizationUser(organizationId: string, id: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -359,4 +359,14 @@ export class DefaultOrganizationUserApiService implements OrganizationUserApiSer
|
||||
);
|
||||
return new ListResponse(r, OrganizationUserBulkResponse);
|
||||
}
|
||||
|
||||
deleteOrganizationUser(organizationId: string, id: string): Promise<void> {
|
||||
return this.apiService.send(
|
||||
"DELETE",
|
||||
"/organizations/" + organizationId + "/users/" + id + "/delete-account",
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ng-container
|
||||
*ngIf="{
|
||||
selectedRegion: selectedRegion$ | async,
|
||||
selectedRegion: selectedRegion$ | async
|
||||
} as data"
|
||||
>
|
||||
<div class="environment-selector-btn">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'tw-pt-0': decreaseTopPadding,
|
||||
'tw-pt-8': !decreaseTopPadding,
|
||||
'tw-min-h-screen': clientType === 'web',
|
||||
'tw-min-h-full': clientType === 'browser' || clientType === 'desktop',
|
||||
'tw-min-h-full': clientType === 'browser' || clientType === 'desktop'
|
||||
}"
|
||||
>
|
||||
<a *ngIf="!hideLogo" [routerLink]="['/']" class="tw-w-[128px] [&>*]:tw-align-top">
|
||||
|
||||
@@ -216,7 +216,7 @@ describe("UserVerificationService", () => {
|
||||
});
|
||||
|
||||
it("returns if verification is successful", async () => {
|
||||
keyService.compareAndUpdateKeyHash.mockResolvedValueOnce(true);
|
||||
keyService.compareKeyHash.mockResolvedValueOnce(true);
|
||||
|
||||
const result = await sut.verifyUserByMasterPassword(
|
||||
{
|
||||
@@ -227,7 +227,7 @@ describe("UserVerificationService", () => {
|
||||
"email",
|
||||
);
|
||||
|
||||
expect(keyService.compareAndUpdateKeyHash).toHaveBeenCalled();
|
||||
expect(keyService.compareKeyHash).toHaveBeenCalled();
|
||||
expect(masterPasswordService.setMasterKeyHash).toHaveBeenCalledWith(
|
||||
"localHash",
|
||||
mockUserId,
|
||||
@@ -240,7 +240,7 @@ describe("UserVerificationService", () => {
|
||||
});
|
||||
|
||||
it("throws if verification fails", async () => {
|
||||
keyService.compareAndUpdateKeyHash.mockResolvedValueOnce(false);
|
||||
keyService.compareKeyHash.mockResolvedValueOnce(false);
|
||||
|
||||
await expect(
|
||||
sut.verifyUserByMasterPassword(
|
||||
@@ -253,7 +253,7 @@ describe("UserVerificationService", () => {
|
||||
),
|
||||
).rejects.toThrow("Invalid master password");
|
||||
|
||||
expect(keyService.compareAndUpdateKeyHash).toHaveBeenCalled();
|
||||
expect(keyService.compareKeyHash).toHaveBeenCalled();
|
||||
expect(masterPasswordService.setMasterKeyHash).not.toHaveBeenCalledWith();
|
||||
expect(masterPasswordService.setMasterKey).not.toHaveBeenCalledWith();
|
||||
});
|
||||
@@ -285,7 +285,7 @@ describe("UserVerificationService", () => {
|
||||
"email",
|
||||
);
|
||||
|
||||
expect(keyService.compareAndUpdateKeyHash).not.toHaveBeenCalled();
|
||||
expect(keyService.compareKeyHash).not.toHaveBeenCalled();
|
||||
expect(masterPasswordService.setMasterKeyHash).toHaveBeenCalledWith(
|
||||
"localHash",
|
||||
mockUserId,
|
||||
@@ -318,7 +318,7 @@ describe("UserVerificationService", () => {
|
||||
),
|
||||
).rejects.toThrow("Invalid master password");
|
||||
|
||||
expect(keyService.compareAndUpdateKeyHash).not.toHaveBeenCalled();
|
||||
expect(keyService.compareKeyHash).not.toHaveBeenCalled();
|
||||
expect(masterPasswordService.setMasterKeyHash).not.toHaveBeenCalledWith();
|
||||
expect(masterPasswordService.setMasterKey).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
@@ -206,9 +206,10 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||
let policyOptions: MasterPasswordPolicyResponse | null;
|
||||
// Client-side verification
|
||||
if (await this.hasMasterPasswordAndMasterKeyHash(userId)) {
|
||||
const passwordValid = await this.keyService.compareAndUpdateKeyHash(
|
||||
const passwordValid = await this.keyService.compareKeyHash(
|
||||
verification.secret,
|
||||
masterKey,
|
||||
userId,
|
||||
);
|
||||
if (!passwordValid) {
|
||||
throw new Error(this.i18nService.t("invalidMasterPassword"));
|
||||
|
||||
@@ -404,11 +404,11 @@ export class StateService<
|
||||
}
|
||||
|
||||
const account = options?.useSecureStorage
|
||||
? ((await this.secureStorageService.get<TAccount>(options.userId, options)) ??
|
||||
? (await this.secureStorageService.get<TAccount>(options.userId, options)) ??
|
||||
(await this.storageService.get<TAccount>(
|
||||
options.userId,
|
||||
this.reconcileOptions(options, { htmlStorageLocation: HtmlStorageLocation.Local }),
|
||||
)))
|
||||
))
|
||||
: await this.storageService.get<TAccount>(options.userId, options);
|
||||
return account;
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ export class DeriveDefinition<TFrom, TTo, TDeps extends DerivedStateDependencies
|
||||
}
|
||||
|
||||
get cleanupDelayMs() {
|
||||
return this.options.cleanupDelayMs < 0 ? 0 : (this.options.cleanupDelayMs ?? 1000);
|
||||
return this.options.cleanupDelayMs < 0 ? 0 : this.options.cleanupDelayMs ?? 1000;
|
||||
}
|
||||
|
||||
get clearOnCleanup() {
|
||||
|
||||
@@ -220,7 +220,8 @@ describe("DefaultActiveUserState", () => {
|
||||
it("should not emit a previous users value if that user is no longer active", async () => {
|
||||
const user1Data: Jsonify<TestState> = {
|
||||
date: "2020-09-21T13:14:17.648Z",
|
||||
array: ["value"],
|
||||
// NOTE: `as any` is here until we migrate to Nx: https://bitwarden.atlassian.net/browse/PM-6493
|
||||
array: ["value"] as any,
|
||||
};
|
||||
const user2Data: Jsonify<TestState> = {
|
||||
date: "2020-09-21T13:14:17.648Z",
|
||||
|
||||
@@ -192,7 +192,8 @@ describe("KeyDefinition", () => {
|
||||
expect(arrayDefinition).toBeTruthy();
|
||||
expect(arrayDefinition.deserializer).toBeTruthy();
|
||||
|
||||
const deserializedValue = arrayDefinition.deserializer([false, true]);
|
||||
// NOTE: `as any` is here until we migrate to Nx: https://bitwarden.atlassian.net/browse/PM-6493
|
||||
const deserializedValue = arrayDefinition.deserializer([false, true] as any);
|
||||
|
||||
expect(deserializedValue).toBeTruthy();
|
||||
expect(deserializedValue).toHaveLength(2);
|
||||
|
||||
@@ -104,7 +104,7 @@ export class KeyDefinition<T> {
|
||||
* Gets the number of milliseconds to wait before cleaning up the state after the last subscriber has unsubscribed.
|
||||
*/
|
||||
get cleanupDelayMs() {
|
||||
return this.options.cleanupDelayMs < 0 ? 0 : (this.options.cleanupDelayMs ?? 1000);
|
||||
return this.options.cleanupDelayMs < 0 ? 0 : this.options.cleanupDelayMs ?? 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,7 +63,7 @@ export class UserKeyDefinition<T> {
|
||||
* Gets the number of milliseconds to wait before cleaning up the state after the last subscriber has unsubscribed.
|
||||
*/
|
||||
get cleanupDelayMs() {
|
||||
return this.options.cleanupDelayMs < 0 ? 0 : (this.options.cleanupDelayMs ?? 1000);
|
||||
return this.options.cleanupDelayMs < 0 ? 0 : this.options.cleanupDelayMs ?? 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,7 +60,7 @@ export class IntegrationContext<Settings extends object> {
|
||||
options: { base64?: boolean; suffix?: string } = null,
|
||||
): Settings extends ApiSettings ? string : never {
|
||||
// normalize `token` then assert it has a value
|
||||
let token = "token" in this.settings ? ((this.settings.token as string) ?? "") : "";
|
||||
let token = "token" in this.settings ? (this.settings.token as string) ?? "" : "";
|
||||
if (token === "") {
|
||||
const error = this.i18n.t("forwaderInvalidToken", this.metadata.name);
|
||||
throw error;
|
||||
|
||||
@@ -91,7 +91,7 @@ export class RestClient {
|
||||
const message = parsed.message?.toString() ?? null;
|
||||
|
||||
// `false` signals no message found
|
||||
const result = error && message ? `${error}: ${message}` : (error ?? message ?? false);
|
||||
const result = error && message ? `${error}: ${message}` : error ?? message ?? false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ describe("Login DTO", () => {
|
||||
password: "myPassword" as EncryptedString,
|
||||
passwordRevisionDate: passwordRevisionDate.toISOString(),
|
||||
totp: "myTotp" as EncryptedString,
|
||||
// NOTE: `as any` is here until we migrate to Nx: https://bitwarden.atlassian.net/browse/PM-6493
|
||||
fido2Credentials: [
|
||||
{
|
||||
credentialId: "keyId" as EncryptedString,
|
||||
@@ -167,7 +168,7 @@ describe("Login DTO", () => {
|
||||
discoverable: "discoverable" as EncryptedString,
|
||||
creationDate: fido2CreationDate.toISOString(),
|
||||
},
|
||||
],
|
||||
] as any,
|
||||
});
|
||||
|
||||
expect(actual).toEqual({
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
? 'tw-bg-text-muted tw-text-contrast tw-gap-1'
|
||||
: 'tw-bg-transparent tw-text-muted tw-gap-1.5',
|
||||
focusVisibleWithin() ? 'tw-ring-2 tw-ring-primary-500 tw-ring-offset-1' : '',
|
||||
fullWidth ? 'tw-w-full' : 'tw-max-w-52',
|
||||
fullWidth ? 'tw-w-full' : 'tw-max-w-52'
|
||||
]"
|
||||
>
|
||||
<!-- Primary button -->
|
||||
@@ -14,7 +14,7 @@
|
||||
type="button"
|
||||
class="fvw-target tw-inline-flex tw-gap-1.5 tw-items-center tw-justify-between tw-bg-transparent hover:tw-bg-transparent tw-border-none tw-outline-none tw-w-full tw-py-1 tw-pl-3 last:tw-pr-3 tw-truncate tw-text-[inherit]"
|
||||
[ngClass]="{
|
||||
'tw-cursor-not-allowed': disabled,
|
||||
'tw-cursor-not-allowed': disabled
|
||||
}"
|
||||
[bitMenuTriggerFor]="menu"
|
||||
[disabled]="disabled"
|
||||
@@ -40,7 +40,7 @@
|
||||
[disabled]="disabled"
|
||||
class="tw-bg-transparent hover:tw-bg-transparent tw-outline-none tw-rounded-full tw-p-1 tw-my-1 tw-mr-1 tw-text-[inherit] tw-border-solid tw-border tw-border-text-muted hover:tw-border-text-contrast hover:disabled:tw-border-transparent tw-aspect-square tw-flex tw-items-center tw-justify-center tw-h-fit focus-visible:tw-ring-2 tw-ring-text-contrast focus-visible:hover:tw-border-transparent"
|
||||
[ngClass]="{
|
||||
'tw-cursor-not-allowed': disabled,
|
||||
'tw-cursor-not-allowed': disabled
|
||||
}"
|
||||
(click)="clear()"
|
||||
>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
'tw-overflow-y-auto': !loading,
|
||||
'tw-invisible tw-overflow-y-hidden': loading,
|
||||
'tw-bg-background': background === 'default',
|
||||
'tw-bg-background-alt': background === 'alt',
|
||||
'tw-bg-background-alt': background === 'alt'
|
||||
}"
|
||||
>
|
||||
<ng-content select="[bitDialogContent]"></ng-content>
|
||||
|
||||
@@ -71,7 +71,7 @@ The content can be a button, anchor, or static container.
|
||||
<bit-item>
|
||||
<button bit-item-content type="button">
|
||||
<bit-avatar slot="start" text="Foo"></bit-avatar>
|
||||
foo@bitwarden.com
|
||||
foo@bitwarden.com
|
||||
<span bitBadge variant="primary" slot="default-trailing">Auto-fill</span>
|
||||
<ng-container slot="secondary">
|
||||
<div>Bitwarden.com</div>
|
||||
|
||||
@@ -94,7 +94,7 @@ export const ContentSlots: Story = {
|
||||
slot="start"
|
||||
[text]="'Foo'"
|
||||
></bit-avatar>
|
||||
foo@bitwarden.com
|
||||
foo@bitwarden.com
|
||||
<ng-container slot="secondary">
|
||||
<div>Bitwarden.com</div>
|
||||
<div><em>locked</em></div>
|
||||
@@ -285,37 +285,37 @@ export const SingleActionList: Story = {
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Foobar
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
</bit-item-group>
|
||||
`,
|
||||
@@ -332,14 +332,14 @@ export const SingleActionWithBadge: Story = {
|
||||
Foobar
|
||||
<span bitBadge variant="primary" slot="default-trailing">Auto-fill</span>
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item>
|
||||
<a bit-item-content href="#">
|
||||
Helloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo!
|
||||
<span bitBadge variant="primary" slot="default-trailing">Auto-fill</span>
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</a>
|
||||
</bit-item>
|
||||
</bit-item-group>
|
||||
`,
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<!-- overlay backdrop for side-nav -->
|
||||
<div
|
||||
*ngIf="{
|
||||
open: sideNavService.open$ | async,
|
||||
open: sideNavService.open$ | async
|
||||
} as data"
|
||||
class="tw-pointer-events-none tw-fixed tw-inset-0 tw-z-10 tw-bg-black tw-bg-opacity-0 motion-safe:tw-transition-colors md:tw-hidden"
|
||||
[ngClass]="[data.open ? 'tw-bg-opacity-30 md:tw-bg-opacity-0' : 'tw-bg-opacity-0']"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ng-container
|
||||
*ngIf="{
|
||||
open: sideNavService.open$ | async,
|
||||
open: sideNavService.open$ | async
|
||||
} as data"
|
||||
>
|
||||
<div
|
||||
@@ -10,12 +10,12 @@
|
||||
showActiveStyles
|
||||
? 'tw-bg-background-alt4'
|
||||
: 'tw-bg-background-alt3 hover:tw-bg-primary-300/60',
|
||||
fvwStyles$ | async,
|
||||
fvwStyles$ | async
|
||||
]"
|
||||
>
|
||||
<div
|
||||
[ngStyle]="{
|
||||
'padding-left': data.open ? (variant === 'tree' ? 2.5 : 1) + treeDepth * 1.5 + 'rem' : '0',
|
||||
'padding-left': data.open ? (variant === 'tree' ? 2.5 : 1) + treeDepth * 1.5 + 'rem' : '0'
|
||||
}"
|
||||
class="tw-relative tw-flex"
|
||||
>
|
||||
@@ -30,7 +30,7 @@
|
||||
<div
|
||||
*ngIf="slotStart.childElementCount === 0"
|
||||
[ngClass]="{
|
||||
'tw-w-0': variant !== 'tree',
|
||||
'tw-w-0': variant !== 'tree'
|
||||
}"
|
||||
>
|
||||
<button
|
||||
@@ -52,7 +52,7 @@
|
||||
class="tw-truncate"
|
||||
[ngClass]="[
|
||||
variant === 'tree' ? 'tw-py-1' : 'tw-py-2',
|
||||
data.open ? 'tw-pr-4' : 'tw-text-center',
|
||||
data.open ? 'tw-pr-4' : 'tw-text-center'
|
||||
]"
|
||||
>
|
||||
<i
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<nav
|
||||
*ngIf="{
|
||||
open: sideNavService.open$ | async,
|
||||
isOverlay: sideNavService.isOverlay$ | async,
|
||||
isOverlay: sideNavService.isOverlay$ | async
|
||||
} as data"
|
||||
id="bit-side-nav"
|
||||
class="tw-fixed md:tw-sticky tw-inset-y-0 tw-left-0 tw-z-30 tw-flex tw-h-screen tw-flex-col tw-overscroll-none tw-overflow-auto tw-bg-background-alt3 tw-outline-none"
|
||||
@@ -10,7 +10,7 @@
|
||||
variant === 'secondary' && {
|
||||
'--color-text-alt2': 'var(--color-text-main)',
|
||||
'--color-background-alt3': 'var(--color-secondary-100)',
|
||||
'--color-background-alt4': 'var(--color-secondary-300)',
|
||||
'--color-background-alt4': 'var(--color-secondary-300)'
|
||||
}
|
||||
"
|
||||
[cdkTrapFocus]="data.isOverlay"
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Component, Input } from "@angular/core";
|
||||
template: `
|
||||
<section
|
||||
[ngClass]="{
|
||||
'tw-mb-6 md:tw-mb-12': !disableMargin,
|
||||
'tw-mb-6 md:tw-mb-12': !disableMargin
|
||||
}"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
|
||||
@@ -223,15 +223,18 @@ export abstract class KeyService {
|
||||
): Promise<string>;
|
||||
|
||||
/**
|
||||
* Compares the provided master password to the stored password hash and server password hash.
|
||||
* Updates the stored hash if outdated.
|
||||
* Compares the provided master password to the stored password hash.
|
||||
* @param masterPassword The user's master password
|
||||
* @param key The user's master key
|
||||
* @param userId The id of the user to do the operation for.
|
||||
* @returns True if the provided master password matches either the stored
|
||||
* key hash or the server key hash
|
||||
*/
|
||||
abstract compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean>;
|
||||
|
||||
abstract compareKeyHash(
|
||||
masterPassword: string,
|
||||
masterKey: MasterKey,
|
||||
userId: UserId,
|
||||
): Promise<boolean>;
|
||||
/**
|
||||
* Stores the encrypted organization keys and clears any decrypted
|
||||
* organization keys currently in memory
|
||||
|
||||
@@ -161,7 +161,7 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||
this.promptCancelledState.state$,
|
||||
]).pipe(
|
||||
map(([userId, record]) => {
|
||||
return userId ? (record?.[userId] ?? false) : false;
|
||||
return userId ? record?.[userId] ?? false : false;
|
||||
}),
|
||||
);
|
||||
this.promptAutomaticallyState = this.stateProvider.getActive(PROMPT_AUTOMATICALLY);
|
||||
|
||||
@@ -739,4 +739,63 @@ describe("keyService", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("compareKeyHash", () => {
|
||||
type TestCase = {
|
||||
masterKey: MasterKey;
|
||||
masterPassword: string | null;
|
||||
storedMasterKeyHash: string;
|
||||
mockReturnedHash: string;
|
||||
expectedToMatch: boolean;
|
||||
};
|
||||
|
||||
const data: TestCase[] = [
|
||||
{
|
||||
masterKey: makeSymmetricCryptoKey(64),
|
||||
masterPassword: "my_master_password",
|
||||
storedMasterKeyHash: "bXlfaGFzaA==",
|
||||
mockReturnedHash: "bXlfaGFzaA==",
|
||||
expectedToMatch: true,
|
||||
},
|
||||
{
|
||||
masterKey: makeSymmetricCryptoKey(64),
|
||||
masterPassword: null,
|
||||
storedMasterKeyHash: "bXlfaGFzaA==",
|
||||
mockReturnedHash: "bXlfaGFzaA==",
|
||||
expectedToMatch: false,
|
||||
},
|
||||
{
|
||||
masterKey: makeSymmetricCryptoKey(64),
|
||||
masterPassword: null,
|
||||
storedMasterKeyHash: null,
|
||||
mockReturnedHash: "bXlfaGFzaA==",
|
||||
expectedToMatch: false,
|
||||
},
|
||||
];
|
||||
|
||||
it.each(data)(
|
||||
"returns expected match value when calculated hash equals stored hash",
|
||||
async ({
|
||||
masterKey,
|
||||
masterPassword,
|
||||
storedMasterKeyHash,
|
||||
mockReturnedHash,
|
||||
expectedToMatch,
|
||||
}) => {
|
||||
masterPasswordService.masterKeyHashSubject.next(storedMasterKeyHash);
|
||||
|
||||
cryptoFunctionService.pbkdf2
|
||||
.calledWith(masterKey.key, masterPassword, "sha256", 2)
|
||||
.mockResolvedValue(Utils.fromB64ToArray(mockReturnedHash));
|
||||
|
||||
const actualDidMatch = await keyService.compareKeyHash(
|
||||
masterPassword,
|
||||
masterKey,
|
||||
mockUserId,
|
||||
);
|
||||
|
||||
expect(actualDidMatch).toBe(expectedToMatch);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -350,38 +350,43 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
}
|
||||
|
||||
// TODO: move to MasterPasswordService
|
||||
async compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean> {
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (!userId) {
|
||||
throw new Error("User ID is required.");
|
||||
async compareKeyHash(
|
||||
masterPassword: string,
|
||||
masterKey: MasterKey,
|
||||
userId: UserId,
|
||||
): Promise<boolean> {
|
||||
if (masterKey == null) {
|
||||
throw new Error("'masterKey' is required to be non-null.");
|
||||
}
|
||||
|
||||
if (masterPassword == null) {
|
||||
// If they don't give us a master password, we can't hash it, and therefore
|
||||
// it will never match what we have stored.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve the current password hash
|
||||
const storedPasswordHash = await firstValueFrom(
|
||||
this.masterPasswordService.masterKeyHash$(userId),
|
||||
);
|
||||
if (masterPassword != null && storedPasswordHash != null) {
|
||||
const localKeyHash = await this.hashMasterKey(
|
||||
masterPassword,
|
||||
masterKey,
|
||||
HashPurpose.LocalAuthorization,
|
||||
);
|
||||
if (localKeyHash != null && storedPasswordHash === localKeyHash) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: remove serverKeyHash check in 1-2 releases after everyone's keyHash has been updated
|
||||
const serverKeyHash = await this.hashMasterKey(
|
||||
masterPassword,
|
||||
masterKey,
|
||||
HashPurpose.ServerAuthorization,
|
||||
);
|
||||
if (serverKeyHash != null && storedPasswordHash === serverKeyHash) {
|
||||
await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId);
|
||||
return true;
|
||||
}
|
||||
if (storedPasswordHash == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
// Hash the key for local use
|
||||
const localKeyHash = await this.hashMasterKey(
|
||||
masterPassword,
|
||||
masterKey,
|
||||
HashPurpose.LocalAuthorization,
|
||||
);
|
||||
|
||||
// Check if the stored hash is already equal to the hash we create locally
|
||||
if (localKeyHash == null || storedPasswordHash !== localKeyHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async setOrgKeys(
|
||||
|
||||
@@ -39,7 +39,7 @@ export class EmailCalculator {
|
||||
* @returns an email address or `null` if the calculation fails.
|
||||
*/
|
||||
concatenate(username: string, domain: string) {
|
||||
const emailDomain = domain?.startsWith("@") ? domain.substring(1, Infinity) : (domain ?? "");
|
||||
const emailDomain = domain?.startsWith("@") ? domain.substring(1, Infinity) : domain ?? "";
|
||||
if (emailDomain.length < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export class EmailRandomizer
|
||||
* is empty, resolves to null instead.
|
||||
*/
|
||||
async randomAsciiCatchall(domain: string, options?: { length?: number }) {
|
||||
const emailDomain = domain?.startsWith("@") ? domain.substring(1, Infinity) : (domain ?? "");
|
||||
const emailDomain = domain?.startsWith("@") ? domain.substring(1, Infinity) : domain ?? "";
|
||||
if (emailDomain.length < 1) {
|
||||
return null;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export class EmailRandomizer
|
||||
domain: string,
|
||||
options?: { numberOfWords?: number; words?: Array<string> },
|
||||
) {
|
||||
const emailDomain = domain?.startsWith("@") ? domain.substring(1, Infinity) : (domain ?? "");
|
||||
const emailDomain = domain?.startsWith("@") ? domain.substring(1, Infinity) : domain ?? "";
|
||||
if (emailDomain.length < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export class ForwarderContext<Settings extends ApiSettings> extends IntegrationC
|
||||
* @remarks the string is thrown for backwards compatibility
|
||||
*/
|
||||
emailDomain(): Settings extends EmailDomainSettings ? string : never {
|
||||
const domain = "domain" in this.settings ? (this.settings.domain ?? "") : "";
|
||||
const domain = "domain" in this.settings ? this.settings.domain ?? "" : "";
|
||||
if (domain === "") {
|
||||
const error = this.i18n.t("forwarderNoDomain", this.configuration.name);
|
||||
throw error;
|
||||
@@ -43,7 +43,7 @@ export class ForwarderContext<Settings extends ApiSettings> extends IntegrationC
|
||||
* @remarks the string is thrown for backwards compatibility
|
||||
*/
|
||||
emailPrefix(): Settings extends EmailPrefixSettings ? string : never {
|
||||
const prefix = "prefix" in this.settings ? (this.settings.prefix ?? "") : "";
|
||||
const prefix = "prefix" in this.settings ? this.settings.prefix ?? "" : "";
|
||||
if (prefix === "") {
|
||||
const error = this.i18n.t("forwarderNoPrefix", this.configuration.name);
|
||||
throw error;
|
||||
|
||||
@@ -42,7 +42,7 @@ export class KeyServiceRandomizer implements Randomizer {
|
||||
throw new Error("items must have at least one entry.");
|
||||
}
|
||||
|
||||
const shuffled = (options?.copy ?? true) ? [...items] : items;
|
||||
const shuffled = options?.copy ?? true ? [...items] : items;
|
||||
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = await this.uniform(0, i);
|
||||
|
||||
@@ -17,7 +17,7 @@ export function availableAlgorithms(policies: Policy[]): CredentialAlgorithm[] {
|
||||
const overridePassword = policies
|
||||
.filter((policy) => policy.type === PolicyType.PasswordGenerator && policy.enabled)
|
||||
.reduce(
|
||||
(type, policy) => (type === "password" ? type : (policy.data.overridePasswordType ?? type)),
|
||||
(type, policy) => (type === "password" ? type : policy.data.overridePasswordType ?? type),
|
||||
null as CredentialAlgorithm,
|
||||
);
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ export class PassphraseGeneratorOptionsEvaluator
|
||||
const wordSeparator =
|
||||
options.wordSeparator === ""
|
||||
? ""
|
||||
: (options.wordSeparator?.[0] ?? DefaultPassphraseGenerationOptions.wordSeparator);
|
||||
: options.wordSeparator?.[0] ?? DefaultPassphraseGenerationOptions.wordSeparator;
|
||||
|
||||
return {
|
||||
...options,
|
||||
|
||||
@@ -32,11 +32,11 @@ export class EffUsernameGeneratorStrategy
|
||||
// algorithm
|
||||
async generate(options: EffUsernameGenerationOptions) {
|
||||
const casing =
|
||||
(options.wordCapitalize ?? DefaultEffUsernameOptions.wordCapitalize)
|
||||
options.wordCapitalize ?? DefaultEffUsernameOptions.wordCapitalize
|
||||
? "TitleCase"
|
||||
: "lowercase";
|
||||
const digits =
|
||||
(options.wordIncludeNumber ?? DefaultEffUsernameOptions.wordIncludeNumber)
|
||||
options.wordIncludeNumber ?? DefaultEffUsernameOptions.wordIncludeNumber
|
||||
? UsernameDigits.enabled
|
||||
: UsernameDigits.disabled;
|
||||
const word = await this.randomizer.randomWords({ numberOfWords: 1, casing, digits });
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
[formGroupName]="i"
|
||||
class="tw-flex tw-p-3 -tw-mx-3 tw-gap-4 tw-bg-background tw-rounded-lg first:-tw-mt-3 last-of-type:tw-mb-0"
|
||||
[ngClass]="{
|
||||
'tw-items-center': field.value.type === FieldType.Boolean,
|
||||
'tw-items-center': field.value.type === FieldType.Boolean
|
||||
}"
|
||||
[attr.data-testid]="field.value.name + '-entry'"
|
||||
cdkDrag
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user