1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-24 21:41:33 +01:00

Merge branch 'main' into auth/pm-8111/browser-refresh-login-component

This commit is contained in:
Alec Rippberger 2024-10-07 14:31:11 -05:00
commit 0df9474f57
No known key found for this signature in database
GPG Key ID: 9DD8DA583B28154A
86 changed files with 1088 additions and 574 deletions

View File

@ -2,6 +2,7 @@
"devFlags": {},
"flags": {
"showPasswordless": true,
"accountSwitching": false
"accountSwitching": false,
"sdk": true
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@bitwarden/browser",
"version": "2024.10.0",
"version": "2024.10.1",
"scripts": {
"build": "cross-env MANIFEST_VERSION=3 webpack",
"build:mv2": "webpack",

View File

@ -90,6 +90,7 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import {
AbstractStorageService,
@ -122,6 +123,8 @@ import { FileUploadService } from "@bitwarden/common/platform/services/file-uplo
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
import { StateService } from "@bitwarden/common/platform/services/state.service";
import { SystemService } from "@bitwarden/common/platform/services/system.service";
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
@ -228,6 +231,7 @@ import AutofillService from "../autofill/services/autofill.service";
import { SafariApp } from "../browser/safariApp";
import { BackgroundBrowserBiometricsService } from "../key-management/biometrics/background-browser-biometrics.service";
import { BrowserApi } from "../platform/browser/browser-api";
import { flagEnabled } from "../platform/flags";
import { UpdateBadge } from "../platform/listeners/update-badge";
/* eslint-disable no-restricted-imports */
import { ChromeMessageSender } from "../platform/messaging/chrome-message.sender";
@ -245,6 +249,7 @@ import { LocalBackedSessionStorageService } from "../platform/services/local-bac
import { BackgroundPlatformUtilsService } from "../platform/services/platform-utils/background-platform-utils.service";
import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
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";
@ -364,6 +369,7 @@ export default class MainBackground {
syncServiceListener: SyncServiceListener;
themeStateService: DefaultThemeStateService;
autoSubmitLoginBackground: AutoSubmitLoginBackground;
sdkService: SdkService;
onUpdatedRan: boolean;
onReplacedRan: boolean;
@ -719,6 +725,16 @@ export default class MainBackground {
this.stateProvider,
);
const sdkClientFactory = flagEnabled("sdk")
? new BrowserSdkClientFactory()
: new NoopSdkClientFactory();
this.sdkService = new DefaultSdkService(
sdkClientFactory,
this.environmentService,
this.platformUtilsService,
this.apiService,
);
this.passwordStrengthService = new PasswordStrengthService();
this.passwordGenerationService = legacyPasswordGenerationServiceFactory(
@ -1315,6 +1331,20 @@ export default class MainBackground {
await this.initOverlayAndTabsBackground();
if (flagEnabled("sdk")) {
// Warn if the SDK for some reason can't be initialized
let supported = false;
try {
supported = await firstValueFrom(this.sdkService.supported$);
} catch (e) {
// Do nothing.
}
if (!supported) {
this.sdkService.failedToInitialize().catch(this.logService.error);
}
}
return new Promise<void>((resolve) => {
setTimeout(async () => {
await this.refreshBadge();

View File

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
"version": "2024.10.0",
"version": "2024.10.1",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",

View File

@ -3,7 +3,7 @@
"minimum_chrome_version": "102.0",
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
"version": "2024.10.0",
"version": "2024.10.1",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",
@ -39,8 +39,7 @@
}
],
"background": {
"service_worker": "background.js",
"type": "module"
"service_worker": "background.js"
},
"action": {
"default_icon": {

View File

@ -0,0 +1,66 @@
import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory";
import type { BitwardenClient } from "@bitwarden/sdk-internal";
import { BrowserApi } from "../../browser/browser-api";
// https://stackoverflow.com/a/47880734
const supported = (() => {
try {
if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
const module = new WebAssembly.Module(
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00),
);
if (module instanceof WebAssembly.Module) {
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
}
}
} catch (e) {
// ignore
}
return false;
})();
// Manifest v3 does not support dynamic imports in the service worker.
if (BrowserApi.isManifestVersion(3)) {
if (supported) {
// eslint-disable-next-line no-console
console.debug("WebAssembly is supported in this environment");
import("./wasm");
} else {
// eslint-disable-next-line no-console
console.debug("WebAssembly is not supported in this environment");
import("./fallback");
}
}
// Manifest v2 expects dynamic imports to prevent timing issues.
async function load() {
if (BrowserApi.isManifestVersion(3)) {
return;
}
if (supported) {
// eslint-disable-next-line no-console
console.debug("WebAssembly is supported in this environment");
await import("./wasm");
} else {
// eslint-disable-next-line no-console
console.debug("WebAssembly is not supported in this environment");
await import("./fallback");
}
}
/**
* SDK client factory with a js fallback for when WASM is not supported.
*
* Works both in popup and service worker.
*/
export class BrowserSdkClientFactory implements SdkClientFactory {
async createSdkClient(
...args: ConstructorParameters<typeof BitwardenClient>
): Promise<BitwardenClient> {
await load();
return Promise.resolve((globalThis as any).init_sdk(...args));
}
}

View File

@ -0,0 +1,8 @@
import * as sdk from "@bitwarden/sdk-internal";
import * as wasm from "@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm.js";
(globalThis as any).init_sdk = (...args: ConstructorParameters<typeof sdk.BitwardenClient>) => {
(sdk as any).init(wasm);
return new sdk.BitwardenClient(...args);
};

View File

@ -0,0 +1,8 @@
import * as sdk from "@bitwarden/sdk-internal";
import * as wasm from "@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm";
(globalThis as any).init_sdk = (...args: ConstructorParameters<typeof sdk.BitwardenClient>) => {
(sdk as any).init(wasm);
return new sdk.BitwardenClient(...args);
};

View File

@ -1,6 +1,7 @@
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, inject } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap } from "rxjs";
import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap, catchError, of } from "rxjs";
import { LogoutReason } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@ -8,7 +9,9 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { MessageListener } from "@bitwarden/common/platform/messaging";
import { UserId } from "@bitwarden/common/types/guid";
@ -20,6 +23,7 @@ import {
ToastService,
} from "@bitwarden/components";
import { flagEnabled } from "../platform/flags";
import { PopupViewCacheService } from "../platform/popup/view-cache/popup-view-cache.service";
import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service";
import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service";
@ -62,7 +66,28 @@ export class AppComponent implements OnInit, OnDestroy {
private toastService: ToastService,
private accountService: AccountService,
private animationControlService: AnimationControlService,
) {}
private logService: LogService,
private sdkService: SdkService,
) {
if (flagEnabled("sdk")) {
// Warn if the SDK for some reason can't be initialized
this.sdkService.supported$
.pipe(
takeUntilDestroyed(),
catchError(() => {
return of(false);
}),
)
.subscribe((supported) => {
if (!supported) {
this.logService.debug("SDK is not supported");
this.sdkService.failedToInitialize().catch(this.logService.error);
} else {
this.logService.debug("SDK is supported");
}
});
}
}
async ngOnInit() {
initPopupClosedListener();

View File

@ -72,6 +72,7 @@ import {
PlatformUtilsService,
PlatformUtilsService as PlatformUtilsServiceAbstraction,
} from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import {
AbstractStorageService,
@ -80,9 +81,11 @@ import {
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
// eslint-disable-next-line no-restricted-imports -- Used for dependency injection
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
import { flagEnabled } from "@bitwarden/common/platform/misc/flags";
import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling";
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
import {
@ -130,6 +133,7 @@ import BrowserLocalStorageService from "../../platform/services/browser-local-st
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";
import { BrowserSdkClientFactory } from "../../platform/services/sdk/browser-sdk-client-factory";
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";
@ -604,6 +608,11 @@ const safeProviders: SafeProvider[] = [
useClass: LoginEmailService,
deps: [AccountService, AuthService, StateProvider],
}),
safeProvider({
provide: SdkClientFactory,
useClass: flagEnabled("sdk") ? BrowserSdkClientFactory : NoopSdkClientFactory,
deps: [],
}),
];
@NgModule({

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
03100CAF291891F4008E14EF /* encrypt-worker.js in Resources */ = {isa = PBXBuildFile; fileRef = 03100CAE291891F4008E14EF /* encrypt-worker.js */; };
55BC93932CB4268A008CA4C6 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 55BC93922CB4268A008CA4C6 /* assets */; };
55E0374D2577FA6B00979016 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E0374C2577FA6B00979016 /* AppDelegate.swift */; };
55E037502577FA6B00979016 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 55E0374E2577FA6B00979016 /* Main.storyboard */; };
55E037522577FA6B00979016 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E037512577FA6B00979016 /* ViewController.swift */; };
@ -54,6 +55,7 @@
/* Begin PBXFileReference section */
03100CAE291891F4008E14EF /* encrypt-worker.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = "encrypt-worker.js"; path = "../../../build/encrypt-worker.js"; sourceTree = "<group>"; };
5508DD7926051B5900A85C58 /* libswiftAppKit.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftAppKit.tbd; path = usr/lib/swift/libswiftAppKit.tbd; sourceTree = SDKROOT; };
55BC93922CB4268A008CA4C6 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../../../build/assets; sourceTree = "<group>"; };
55E037482577FA6B00979016 /* desktop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktop.app; sourceTree = BUILT_PRODUCTS_DIR; };
55E0374B2577FA6B00979016 /* desktop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = desktop.entitlements; sourceTree = "<group>"; };
55E0374C2577FA6B00979016 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -152,6 +154,7 @@
55E0376F2577FA6F00979016 /* Resources */ = {
isa = PBXGroup;
children = (
55BC93922CB4268A008CA4C6 /* assets */,
03100CAE291891F4008E14EF /* encrypt-worker.js */,
55E037702577FA6F00979016 /* popup */,
55E037712577FA6F00979016 /* background.js */,
@ -270,6 +273,7 @@
55E0377A2577FA6F00979016 /* background.js in Resources */,
55E037792577FA6F00979016 /* popup in Resources */,
03100CAF291891F4008E14EF /* encrypt-worker.js in Resources */,
55BC93932CB4268A008CA4C6 /* assets in Resources */,
55E0377C2577FA6F00979016 /* notification in Resources */,
55E0377E2577FA6F00979016 /* vendor.js in Resources */,
55E0377D2577FA6F00979016 /* content in Resources */,

View File

@ -122,7 +122,7 @@ const moduleRules = [
loader: "@ngtools/webpack",
},
{
test: /\.wasm$/,
test: /argon2(-simd)?\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
@ -316,13 +316,18 @@ const mainConfig = {
},
output: {
filename: "[name].js",
chunkFilename: "assets/[name].js",
webassemblyModuleFilename: "assets/[modulehash].wasm",
path: path.resolve(__dirname, "build"),
clean: true,
},
module: {
noParse: /\.wasm$/,
noParse: /argon2(-simd)?\.wasm$/,
rules: moduleRules,
},
experiments: {
asyncWebAssembly: true,
},
plugins: plugins,
};
@ -395,12 +400,15 @@ if (manifestVersion == 2) {
loader: "ts-loader",
},
{
test: /\.wasm$/,
test: /argon2(-simd)?\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
],
noParse: /\.wasm$/,
noParse: /argon2(-simd)?\.wasm$/,
},
experiments: {
asyncWebAssembly: true,
},
resolve: {
extensions: [".ts", ".js"],

View File

@ -0,0 +1,5 @@
{
"flags": {
"sdk": true
}
}

View File

@ -1,7 +1,27 @@
function load(envName) {
const base = require("./base.json");
const env = loadConfig(envName);
const local = loadConfig("local");
return {
...loadConfig(envName),
...loadConfig("local"),
...base,
...env,
...local,
dev: {
...base.dev,
...env.dev,
...local.dev,
},
flags: {
...base.flags,
...env.flags,
...local.flags,
},
devFlags: {
...base.devFlags,
...env.devFlags,
...local.devFlags,
},
};
}

View File

@ -10,7 +10,11 @@ module.exports = {
preset: "ts-jest",
testEnvironment: "node",
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
moduleNameMapper: {
"@bitwarden/common/platform/services/sdk/default-sdk-client-factory":
"<rootDir>/../../libs/common/spec/jest-sdk-client-factory",
...pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
},
};

View File

@ -1,7 +1,7 @@
{
"name": "@bitwarden/cli",
"description": "A secure and free password manager for all of your devices.",
"version": "2024.9.1",
"version": "2024.10.0",
"keywords": [
"bitwarden",
"password",

View File

@ -64,6 +64,7 @@ import {
RegionConfig,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { MessageSender } from "@bitwarden/common/platform/messaging";
@ -86,6 +87,9 @@ import { KeyGenerationService } from "@bitwarden/common/platform/services/key-ge
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory";
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
import { StateService } from "@bitwarden/common/platform/services/state.service";
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
@ -151,6 +155,7 @@ import {
VaultExportServiceAbstraction,
} from "@bitwarden/vault-export-core";
import { flagEnabled } from "../platform/flags";
import { CliPlatformUtilsService } from "../platform/services/cli-platform-utils.service";
import { ConsoleLogService } from "../platform/services/console-log.service";
import { I18nService } from "../platform/services/i18n.service";
@ -249,6 +254,7 @@ export class ServiceContainer {
userAutoUnlockKeyService: UserAutoUnlockKeyService;
kdfConfigService: KdfConfigServiceAbstraction;
taskSchedulerService: TaskSchedulerService;
sdkService: SdkService;
constructor() {
let p = null;
@ -522,6 +528,17 @@ export class ServiceContainer {
this.globalStateProvider,
);
const sdkClientFactory = flagEnabled("sdk")
? new DefaultSdkClientFactory()
: new NoopSdkClientFactory();
this.sdkService = new DefaultSdkService(
sdkClientFactory,
this.environmentService,
this.platformUtilsService,
this.apiService,
customUserAgent,
);
this.passwordStrengthService = new PasswordStrengthService();
this.passwordGenerationService = legacyPasswordGenerationServiceFactory(
@ -830,5 +847,19 @@ export class ServiceContainer {
}
this.inited = true;
if (flagEnabled("sdk")) {
// Warn if the SDK for some reason can't be initialized
let supported = false;
try {
supported = await firstValueFrom(this.sdkService.supported$);
} catch (e) {
// Do nothing.
}
if (!supported) {
this.sdkService.failedToInitialize().catch(this.logService.error);
}
}
}
}

View File

@ -3,7 +3,7 @@
"pretty": true,
"moduleResolution": "node",
"target": "ES2016",
"module": "es6",
"module": "ES2020",
"noImplicitAny": true,
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,

View File

@ -37,8 +37,10 @@ const plugins = [
contextRegExp: /node-fetch/,
}),
new webpack.EnvironmentPlugin({
ENV: ENV,
BWCLI_ENV: ENV,
FLAGS: envConfig.flags,
DEV_FLAGS: envConfig.devFlags,
}),
new webpack.IgnorePlugin({
resourceRegExp: /canvas/,
@ -79,6 +81,9 @@ const webpackConfig = {
allowlist: [/@bitwarden/],
}),
],
experiments: {
asyncWebAssembly: true,
},
};
module.exports = webpackConfig;

View File

@ -1,4 +1,6 @@
{
"devFlags": {},
"flags": {}
"flags": {
"sdk": true
},
"devFlags": {}
}

View File

@ -1,7 +1,7 @@
{
"name": "@bitwarden/desktop",
"description": "A secure and free password manager for all of your devices.",
"version": "2024.9.2",
"version": "2024.10.0",
"keywords": [
"bitwarden",
"password",

View File

@ -8,8 +8,9 @@ import {
ViewChild,
ViewContainerRef,
} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { Router } from "@angular/router";
import { filter, firstValueFrom, map, Subject, takeUntil, timeout } from "rxjs";
import { catchError, filter, firstValueFrom, map, of, Subject, takeUntil, timeout } from "rxjs";
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@ -21,7 +22,6 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
@ -38,6 +38,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
import { clearCaches } from "@bitwarden/common/platform/misc/sequentialize";
@ -56,6 +57,7 @@ import { BiometricStateService } from "@bitwarden/key-management";
import { DeleteAccountComponent } from "../auth/delete-account.component";
import { LoginApprovalComponent } from "../auth/login/login-approval.component";
import { MenuAccount, MenuUpdateRequest } from "../main/menu/menu.updater";
import { flagEnabled } from "../platform/flags";
import { PremiumComponent } from "../vault/app/accounts/premium.component";
import { FolderAddEditComponent } from "../vault/app/vault/folder-add-edit.component";
@ -150,9 +152,28 @@ export class AppComponent implements OnInit, OnDestroy {
private dialogService: DialogService,
private biometricStateService: BiometricStateService,
private stateEventRunnerService: StateEventRunnerService,
private providerService: ProviderService,
private accountService: AccountService,
) {}
private sdkService: SdkService,
) {
if (flagEnabled("sdk")) {
// Warn if the SDK for some reason can't be initialized
this.sdkService.supported$
.pipe(
takeUntilDestroyed(),
catchError(() => {
return of(false);
}),
)
.subscribe((supported) => {
if (!supported) {
this.logService.debug("SDK is not supported");
this.sdkService.failedToInitialize().catch(this.logService.error);
} else {
this.logService.debug("SDK is supported");
}
});
}
}
ngOnInit() {
this.accountService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((account) => {

View File

@ -65,6 +65,7 @@ import {
} from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service";
@ -73,6 +74,8 @@ import { Message, MessageListener, MessageSender } from "@bitwarden/common/platf
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling";
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory";
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
import { SystemService } from "@bitwarden/common/platform/services/system.service";
import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state";
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
@ -87,6 +90,7 @@ import { BiometricStateService, BiometricsService } from "@bitwarden/key-managem
import { DesktopLoginComponentService } from "../../auth/login/desktop-login-component.service";
import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service";
import { ElectronBiometricsService } from "../../key-management/biometrics/electron-biometrics.service";
import { flagEnabled } from "../../platform/flags";
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
import { ElectronCryptoService } from "../../platform/services/electron-crypto.service";
import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service";
@ -332,6 +336,11 @@ const safeProviders: SafeProvider[] = [
useClass: LoginEmailService,
deps: [AccountService, AuthService, StateProvider],
}),
safeProvider({
provide: SdkClientFactory,
useClass: flagEnabled("sdk") ? DefaultSdkClientFactory : NoopSdkClientFactory,
deps: [],
}),
];
@NgModule({

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; style-src 'self' 'unsafe-inline';
content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline';
img-src 'self' data: *; child-src *; frame-src *; connect-src *;"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />

View File

@ -331,7 +331,7 @@ export class NativeMessagingMain {
const ext = process.platform === "win32" ? ".exe" : "";
if (isDev()) {
return path.join(
const devPath = path.join(
this.appPath,
"..",
"desktop_native",
@ -339,6 +339,12 @@ export class NativeMessagingMain {
"debug",
`desktop_proxy${ext}`,
);
// isDev() returns true when using a production build with ELECTRON_IS_DEV=1,
// so we need to fall back to the prod binary if the dev binary doesn't exist.
if (existsSync(devPath)) {
return devPath;
}
}
return path.join(path.dirname(this.exePath), `desktop_proxy${ext}`);

View File

@ -1,26 +1,18 @@
{
"name": "@bitwarden/desktop",
"version": "2024.9.2",
"version": "2024.10.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@bitwarden/desktop",
"version": "2024.9.2",
"version": "2024.10.0",
"license": "GPL-3.0",
"dependencies": {
"@bitwarden/desktop-napi": "file:../desktop_native/napi",
"argon2": "0.40.1"
}
},
"../desktop_native/napi": {
"name": "@bitwarden/desktop-napi",
"version": "0.1.0",
"license": "GPL-3.0",
"devDependencies": {
"@napi-rs/cli": "2.16.2"
}
},
"../desktop_native/napi": {
"version": "0.1.0",
"license": "GPL-3.0",

View File

@ -2,7 +2,7 @@
"name": "@bitwarden/desktop",
"productName": "Bitwarden",
"description": "A secure and free password manager for all of your devices.",
"version": "2024.9.2",
"version": "2024.10.0",
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
"homepage": "https://bitwarden.com",
"license": "GPL-3.0",

View File

@ -42,7 +42,7 @@ const common = {
type: "asset/resource",
},
{
test: /\.wasm$/,
test: /argon2(-simd)?\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
@ -143,11 +143,15 @@ const renderer = {
parser: { system: true },
},
{
test: /\.wasm$/,
test: /argon2(-simd)?\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
],
noParse: /argon2(-simd)?\.wasm$/,
},
experiments: {
asyncWebAssembly: true,
},
plugins: [
new AngularWebpackPlugin({

View File

@ -11,6 +11,8 @@
"allowedHosts": "auto"
},
"flags": {
"showPasswordless": false
}
"showPasswordless": false,
"sdk": true
},
"devFlags": {}
}

View File

@ -9,11 +9,19 @@ module.exports = {
...sharedConfig,
preset: "jest-preset-angular",
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
moduleNameMapper: pathsToModuleNameMapper(
// lets us use @bitwarden/common/spec in web tests
{ "@bitwarden/common/spec": ["../../libs/common/spec"], ...(compilerOptions?.paths ?? {}) },
{
prefix: "<rootDir>/",
},
),
moduleNameMapper: {
// Replace ESM SDK with Node compatible SDK
"@bitwarden/common/platform/services/sdk/default-sdk-client-factory":
"<rootDir>/../../libs/common/spec/jest-sdk-client-factory",
...pathsToModuleNameMapper(
{
// lets us use @bitwarden/common/spec in web tests
"@bitwarden/common/spec": ["../../libs/common/spec"],
...(compilerOptions?.paths ?? {}),
},
{
prefix: "<rootDir>/",
},
),
},
};

View File

@ -1,6 +1,6 @@
{
"name": "@bitwarden/web-vault",
"version": "2024.10.0",
"version": "2024.10.1",
"scripts": {
"build:oss": "webpack",
"build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js",

View File

@ -1,8 +1,9 @@
import { DOCUMENT } from "@angular/common";
import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { NavigationEnd, Router } from "@angular/router";
import * as jq from "jquery";
import { Subject, filter, firstValueFrom, map, takeUntil, timeout } from "rxjs";
import { Subject, filter, firstValueFrom, map, takeUntil, timeout, catchError, of } from "rxjs";
import { LogoutReason } from "@bitwarden/auth/common";
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
@ -19,7 +20,9 @@ import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broa
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { StateEventRunnerService } from "@bitwarden/common/platform/state";
import { SyncService } from "@bitwarden/common/platform/sync";
@ -30,6 +33,8 @@ import { DialogService, ToastOptions, ToastService } from "@bitwarden/components
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { BiometricStateService } from "@bitwarden/key-management";
import { flagEnabled } from "../utils/flags";
import { PolicyListService } from "./admin-console/core/policy-list.service";
import {
DisableSendPolicy,
@ -85,7 +90,28 @@ export class AppComponent implements OnDestroy, OnInit {
private stateEventRunnerService: StateEventRunnerService,
private organizationService: InternalOrganizationServiceAbstraction,
private accountService: AccountService,
) {}
private logService: LogService,
private sdkService: SdkService,
) {
if (flagEnabled("sdk")) {
// Warn if the SDK for some reason can't be initialized
this.sdkService.supported$
.pipe(
takeUntilDestroyed(),
catchError(() => {
return of(false);
}),
)
.subscribe((supported) => {
if (!supported) {
this.logService.debug("SDK is not supported");
this.sdkService.failedToInitialize().catch(this.logService.error);
} else {
this.logService.debug("SDK is supported");
}
});
}
}
ngOnInit() {
this.i18nService.locale$.pipe(takeUntil(this.destroy$)).subscribe((locale) => {

View File

@ -58,6 +58,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { AppIdService as DefaultAppIdService } from "@bitwarden/common/platform/services/app-id.service";
@ -65,6 +66,7 @@ import { MemoryStorageService } from "@bitwarden/common/platform/services/memory
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
/* eslint-disable import/no-restricted-paths -- Implementation for memory storage */
import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state";
@ -80,6 +82,7 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { BiometricsService } from "@bitwarden/key-management";
import { flagEnabled } from "../../utils/flags";
import { PolicyListService } from "../admin-console/core/policy-list.service";
import {
WebSetPasswordJitService,
@ -93,6 +96,7 @@ import { I18nService } from "../core/i18n.service";
import { WebBiometricsService } from "../key-management/web-biometric.service";
import { WebEnvironmentService } from "../platform/web-environment.service";
import { WebMigrationRunner } from "../platform/web-migration-runner";
import { WebSdkClientFactory } from "../platform/web-sdk-client-factory";
import { WebStorageServiceProvider } from "../platform/web-storage-service.provider";
import { EventService } from "./event.service";
@ -270,6 +274,11 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultCollectionAdminService,
deps: [ApiService, CryptoServiceAbstraction, EncryptService, CollectionService],
}),
safeProvider({
provide: SdkClientFactory,
useClass: flagEnabled("sdk") ? WebSdkClientFactory : NoopSdkClientFactory,
deps: [],
}),
];
@NgModule({

View File

@ -0,0 +1,42 @@
import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory";
import * as sdk from "@bitwarden/sdk-internal";
/**
* SDK client factory with a js fallback for when WASM is not supported.
*/
export class WebSdkClientFactory implements SdkClientFactory {
async createSdkClient(
...args: ConstructorParameters<typeof sdk.BitwardenClient>
): Promise<sdk.BitwardenClient> {
const module = await load();
(sdk as any).init(module);
return Promise.resolve(new sdk.BitwardenClient(...args));
}
}
// https://stackoverflow.com/a/47880734
const supported = (() => {
try {
if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
const module = new WebAssembly.Module(
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00),
);
if (module instanceof WebAssembly.Module) {
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
}
}
} catch (e) {
// ignore
}
return false;
})();
async function load() {
if (supported) {
return await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm");
} else {
return await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm.js");
}
}

View File

@ -65,7 +65,7 @@
>
{{ "cancel" | i18n }}
</button>
<div class="tw-ml-auto">
<div class="tw-ml-auto" *ngIf="showDelete">
<button
bitIconButton="bwi-trash"
type="button"
@ -73,6 +73,7 @@
[appA11yTitle]="'delete' | i18n"
[bitAction]="delete"
[disabled]="!canDelete"
data-testid="delete-cipher-btn"
></button>
</div>
</ng-container>

View File

@ -179,6 +179,15 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
return this.cipher?.edit ?? false;
}
protected get showDelete() {
// Don't show the delete button when cloning a cipher
if (this.params.mode == "form" && this.formConfig.mode === "clone") {
return false;
}
// Never show the delete button for new ciphers
return this.cipher != null;
}
protected get showCipherView() {
return this.cipher != undefined && (this.params.mode === "view" || this.loadingForm);
}
@ -332,8 +341,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy {
};
cancel = async () => {
// We're in View mode, or we don't have a cipher, close the dialog.
if (this.params.mode === "view" || this.cipher == null) {
// We're in View mode, we don't have a cipher, or we were cloning, close the dialog.
if (this.params.mode === "view" || this.cipher == null || this.formConfig.mode === "clone") {
this.dialogRef.close(this._cipherModified ? VaultItemDialogResult.Saved : undefined);
return;
}

View File

@ -0,0 +1,119 @@
import { TestBed } from "@angular/core/testing";
import { BehaviorSubject } from "rxjs";
import { CollectionAdminService } from "@bitwarden/admin-console/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CipherId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service";
import { AdminConsoleCipherFormConfigService } from "./admin-console-cipher-form-config.service";
describe("AdminConsoleCipherFormConfigService", () => {
let adminConsoleConfigService: AdminConsoleCipherFormConfigService;
const cipherId = "333-444-555" as CipherId;
const testOrg = { id: "333-44-55", name: "Test Org", canEditAllCiphers: false };
const organization$ = new BehaviorSubject<Organization>(testOrg as Organization);
const getCipherAdmin = jest.fn().mockResolvedValue(null);
const getCipher = jest.fn().mockResolvedValue(null);
beforeEach(async () => {
getCipherAdmin.mockClear();
getCipher.mockClear();
getCipher.mockResolvedValue({ id: cipherId, name: "Test Cipher - (non-admin)" });
getCipherAdmin.mockResolvedValue({ id: cipherId, name: "Test Cipher - (admin)" });
await TestBed.configureTestingModule({
providers: [
AdminConsoleCipherFormConfigService,
{ provide: OrganizationService, useValue: { get$: () => organization$ } },
{ provide: CipherService, useValue: { get: getCipher } },
{ provide: CollectionAdminService, useValue: { getAll: () => Promise.resolve([]) } },
{
provide: RoutedVaultFilterService,
useValue: { filter$: new BehaviorSubject({ organizationId: testOrg.id }) },
},
{ provide: ApiService, useValue: { getCipherAdmin } },
],
});
});
describe("buildConfig", () => {
it("sets individual attributes", async () => {
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
const { folders, hideIndividualVaultFields } = await adminConsoleConfigService.buildConfig(
"add",
cipherId,
);
expect(folders).toEqual([]);
expect(hideIndividualVaultFields).toBe(true);
});
it("sets mode based on passed mode", async () => {
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
const { mode } = await adminConsoleConfigService.buildConfig("edit", cipherId);
expect(mode).toBe("edit");
});
it("sets admin flag based on `canEditAllCiphers`", async () => {
// Disable edit all ciphers on org
testOrg.canEditAllCiphers = false;
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
let result = await adminConsoleConfigService.buildConfig("add", cipherId);
expect(result.admin).toBe(false);
// Enable edit all ciphers on org
testOrg.canEditAllCiphers = true;
result = await adminConsoleConfigService.buildConfig("add", cipherId);
expect(result.admin).toBe(true);
});
it("sets `allowPersonalOwnership` to false", async () => {
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
const result = await adminConsoleConfigService.buildConfig("clone", cipherId);
expect(result.allowPersonalOwnership).toBe(false);
});
describe("getCipher", () => {
it("retrieves the cipher from the cipher service", async () => {
testOrg.canEditAllCiphers = false;
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
const result = await adminConsoleConfigService.buildConfig("clone", cipherId);
expect(getCipher).toHaveBeenCalledWith(cipherId);
expect(result.originalCipher.name).toBe("Test Cipher - (non-admin)");
// Admin service not needed when cipher service can return the cipher
expect(getCipherAdmin).not.toHaveBeenCalled();
});
it("retrieves the cipher from the admin service", async () => {
getCipher.mockResolvedValueOnce(null);
getCipherAdmin.mockResolvedValue({ id: cipherId, name: "Test Cipher - (admin)" });
adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService);
await adminConsoleConfigService.buildConfig("add", cipherId);
expect(getCipherAdmin).toHaveBeenCalledWith(cipherId);
expect(getCipher).toHaveBeenCalledWith(cipherId);
});
});
});
});

View File

@ -0,0 +1,99 @@
import { inject, Injectable } from "@angular/core";
import { combineLatest, filter, firstValueFrom, map, switchMap } from "rxjs";
import { CollectionAdminService } from "@bitwarden/admin-console/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { CipherId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import {
CipherFormConfig,
CipherFormConfigService,
CipherFormMode,
} from "../../../../../../../libs/vault/src/cipher-form/abstractions/cipher-form-config.service";
import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service";
/** Admin Console implementation of the `CipherFormConfigService`. */
@Injectable()
export class AdminConsoleCipherFormConfigService implements CipherFormConfigService {
private organizationService: OrganizationService = inject(OrganizationService);
private cipherService: CipherService = inject(CipherService);
private routedVaultFilterService: RoutedVaultFilterService = inject(RoutedVaultFilterService);
private collectionAdminService: CollectionAdminService = inject(CollectionAdminService);
private apiService: ApiService = inject(ApiService);
private organizationId$ = this.routedVaultFilterService.filter$.pipe(
map((filter) => filter.organizationId),
filter((filter) => filter !== undefined),
);
private organization$ = this.organizationId$.pipe(
switchMap((organizationId) => this.organizationService.get$(organizationId)),
);
private editableCollections$ = this.organization$.pipe(
switchMap(async (org) => {
const collections = await this.collectionAdminService.getAll(org.id);
// Users that can edit all ciphers can implicitly add to / edit within any collection
if (org.canEditAllCiphers) {
return collections;
}
// The user is only allowed to add/edit items to assigned collections that are not readonly
return collections.filter((c) => c.assigned && !c.readOnly);
}),
);
async buildConfig(
mode: CipherFormMode,
cipherId?: CipherId,
cipherType?: CipherType,
): Promise<CipherFormConfig> {
const [organization, allCollections] = await firstValueFrom(
combineLatest([this.organization$, this.editableCollections$]),
);
const cipher = await this.getCipher(organization, cipherId);
const collections = allCollections.filter(
(c) => c.organizationId === organization.id && c.assigned && !c.readOnly,
);
return {
mode,
cipherType: cipher?.type ?? cipherType ?? CipherType.Login,
admin: organization.canEditAllCiphers ?? false,
allowPersonalOwnership: false,
originalCipher: cipher,
collections,
organizations: [organization], // only a single org is in context at a time
folders: [], // folders not applicable in the admin console
hideIndividualVaultFields: true,
};
}
private async getCipher(organization: Organization, id?: CipherId): Promise<Cipher | null> {
if (id == null) {
return Promise.resolve(null);
}
// Check to see if the user has direct access to the cipher
const cipherFromCipherService = await this.cipherService.get(id);
// If the organization doesn't allow admin/owners to edit all ciphers return the cipher
if (!organization.canEditAllCiphers && cipherFromCipherService != null) {
return cipherFromCipherService;
}
// Retrieve the cipher through the means of an admin
const cipherResponse = await this.apiService.getCipherAdmin(id);
cipherResponse.edit = true;
const cipherData = new CipherData(cipherResponse);
return new Cipher(cipherData);
}
}

View File

@ -1,3 +1,4 @@
import { DialogRef } from "@angular/cdk/dialog";
import {
ChangeDetectorRef,
Component,
@ -42,16 +43,17 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SyncService } from "@bitwarden/common/platform/sync";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
@ -62,7 +64,12 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
import { DialogService, Icons, NoItemsModule, ToastService } from "@bitwarden/components";
import { CollectionAssignmentResult, PasswordRepromptService } from "@bitwarden/vault";
import {
CipherFormConfig,
CipherFormConfigService,
CollectionAssignmentResult,
PasswordRepromptService,
} from "@bitwarden/vault";
import { GroupService, GroupView } from "../../admin-console/organizations/core";
import { openEntityEventsDialog } from "../../admin-console/organizations/manage/entity-events.component";
@ -75,6 +82,11 @@ import {
CollectionDialogTabType,
openCollectionDialog,
} from "../components/collection-dialog";
import {
VaultItemDialogComponent,
VaultItemDialogMode,
VaultItemDialogResult,
} from "../components/vault-item-dialog/vault-item-dialog.component";
import { VaultItemEvent } from "../components/vault-items/vault-item-event";
import { VaultItemsModule } from "../components/vault-items/vault-items.module";
import {
@ -89,12 +101,6 @@ import {
All,
RoutedVaultFilterModel,
} from "../individual-vault/vault-filter/shared/models/routed-vault-filter.model";
import {
openViewCipherDialog,
ViewCipherDialogCloseResult,
ViewCipherDialogResult,
ViewComponent,
} from "../individual-vault/view.component";
import { VaultHeaderComponent } from "../org-vault/vault-header/vault-header.component";
import { getNestedCollectionTree } from "../utils/collection-utils";
@ -106,8 +112,8 @@ import {
} from "./bulk-collections-dialog";
import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component";
import { openOrgVaultCollectionsDialog } from "./collections.component";
import { AdminConsoleCipherFormConfigService } from "./services/admin-console-cipher-form-config.service";
import { VaultFilterModule } from "./vault-filter/vault-filter.module";
const BroadcasterSubscriptionId = "OrgVaultComponent";
const SearchTextDebounceInterval = 200;
@ -127,9 +133,12 @@ enum AddAccessStatusType {
VaultItemsModule,
SharedModule,
NoItemsModule,
ViewComponent,
],
providers: [RoutedVaultFilterService, RoutedVaultFilterBridgeService],
providers: [
RoutedVaultFilterService,
RoutedVaultFilterBridgeService,
{ provide: CipherFormConfigService, useClass: AdminConsoleCipherFormConfigService },
],
})
export class VaultComponent implements OnInit, OnDestroy {
protected Unassigned = Unassigned;
@ -174,6 +183,8 @@ export class VaultComponent implements OnInit, OnDestroy {
private refresh$ = new BehaviorSubject<void>(null);
private destroy$ = new Subject<void>();
protected addAccessStatus$ = new BehaviorSubject<AddAccessStatusType>(0);
private extensionRefreshEnabled: boolean;
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
constructor(
private route: ActivatedRoute,
@ -203,10 +214,15 @@ export class VaultComponent implements OnInit, OnDestroy {
private apiService: ApiService,
private collectionService: CollectionService,
private toastService: ToastService,
private accountService: AccountService,
private configService: ConfigService,
private cipherFormConfigService: CipherFormConfigService,
) {}
async ngOnInit() {
this.extensionRefreshEnabled = await this.configService.getFeatureFlag(
FeatureFlag.ExtensionRefresh,
);
this.trashCleanupWarning = this.i18nService.t(
this.platformUtilsService.isSelfHost()
? "trashCleanupWarningSelfHosted"
@ -466,22 +482,27 @@ export class VaultComponent implements OnInit, OnDestroy {
firstSetup$
.pipe(
switchMap(() => this.route.queryParams),
// Only process the queryParams if the dialog is not open (only when extension refresh is enabled)
filter(() => this.vaultItemDialogRef == undefined || !this.extensionRefreshEnabled),
withLatestFrom(allCipherMap$, allCollections$, organization$),
switchMap(async ([qParams, allCiphersMap, allCollections]) => {
switchMap(async ([qParams, allCiphersMap]) => {
const cipherId = getCipherIdFromParams(qParams);
if (!cipherId) {
return;
}
const cipher = allCiphersMap[cipherId];
const cipherCollections = allCollections.filter((c) =>
cipher.collectionIds.includes(c.id),
);
if (cipher) {
if (qParams.action === "view") {
await this.viewCipher(cipher, cipherCollections);
let action = qParams.action;
// Default to "view" if extension refresh is enabled
if (action == null && this.extensionRefreshEnabled) {
action = "view";
}
if (action === "view") {
await this.viewCipherById(cipher);
} else {
await this.editCipherId(cipherId);
await this.editCipherId(cipher, false);
}
} else {
this.toastService.showToast({
@ -730,12 +751,16 @@ export class VaultComponent implements OnInit, OnDestroy {
}
async addCipher(cipherType?: CipherType) {
if (this.extensionRefreshEnabled) {
return this.addCipherV2(cipherType);
}
let collections: CollectionView[] = [];
// Admins limited to only adding items to collections they have access to.
collections = await firstValueFrom(this.editableCollections$);
await this.editCipher(null, (comp) => {
await this.editCipher(null, false, (comp) => {
comp.type = cipherType || this.activeFilter.cipherType;
comp.collections = collections;
if (this.activeFilter.collectionId) {
@ -744,20 +769,46 @@ export class VaultComponent implements OnInit, OnDestroy {
});
}
/** Opens the Add/Edit Dialog. Only to be used when the BrowserExtension feature flag is active */
async addCipherV2(cipherType?: CipherType) {
const cipherFormConfig = await this.cipherFormConfigService.buildConfig(
"add",
null,
cipherType,
);
const collectionId: CollectionId | undefined = this.activeFilter.collectionId as CollectionId;
cipherFormConfig.initialValues = {
organizationId: this.organization.id as OrganizationId,
collectionIds: collectionId ? [collectionId] : [],
};
await this.openVaultItemDialog("form", cipherFormConfig);
}
/**
* Edit the given cipher
* @param cipherView - The cipher to be edited
* @param cloneCipher - `true` when the cipher should be cloned.
* Used in place of the `additionalComponentParameters`, as
* the `editCipherIdV2` method has a differing implementation.
* @param defaultComponentParameters - A method that takes in an instance of
* the `AddEditComponent` to edit methods directly.
*/
async editCipher(
cipher: CipherView,
cloneCipher: boolean,
additionalComponentParameters?: (comp: AddEditComponent) => void,
) {
return this.editCipherId(cipher?.id, additionalComponentParameters);
return this.editCipherId(cipher, cloneCipher, additionalComponentParameters);
}
async editCipherId(
cipherId: string,
cipher: CipherView,
cloneCipher: boolean,
additionalComponentParameters?: (comp: AddEditComponent) => void,
) {
const cipher = await this.cipherService.get(cipherId);
// if cipher exists (cipher is null when new) and MP reprompt
// is on for this cipher, then show password reprompt
if (
cipher &&
cipher.reprompt !== 0 &&
@ -768,10 +819,15 @@ export class VaultComponent implements OnInit, OnDestroy {
return;
}
if (this.extensionRefreshEnabled) {
await this.editCipherIdV2(cipher, cloneCipher);
return;
}
const defaultComponentParameters = (comp: AddEditComponent) => {
comp.organization = this.organization;
comp.organizationId = this.organization.id;
comp.cipherId = cipherId;
comp.cipherId = cipher.id;
comp.onSavedCipher.pipe(takeUntil(this.destroy$)).subscribe(() => {
modal.close();
this.refresh();
@ -807,46 +863,70 @@ export class VaultComponent implements OnInit, OnDestroy {
}
/**
* Takes a cipher and its assigned collections to opens dialog where it can be viewed.
* @param cipher - the cipher to view
* @param collections - the collections the cipher is assigned to
* Edit a cipher using the new AddEditCipherDialogV2 component.
* Only to be used behind the ExtensionRefresh feature flag.
*/
async viewCipher(cipher: CipherView, collections: CollectionView[] = []) {
private async editCipherIdV2(cipher: CipherView, cloneCipher: boolean) {
const cipherFormConfig = await this.cipherFormConfigService.buildConfig(
cloneCipher ? "clone" : "edit",
cipher.id as CipherId,
);
await this.openVaultItemDialog("form", cipherFormConfig, cipher);
}
/** Opens the view dialog for the given cipher unless password reprompt fails */
async viewCipherById(cipher: CipherView) {
if (!cipher) {
this.go({ cipherId: null, itemId: null });
return;
}
if (cipher.reprompt !== 0 && !(await this.passwordRepromptService.showPasswordPrompt())) {
// didn't pass password prompt, so don't open the dialog
this.go({ cipherId: null, itemId: null });
if (
cipher &&
cipher.reprompt !== 0 &&
!(await this.passwordRepromptService.showPasswordPrompt())
) {
// Didn't pass password prompt, so don't open add / edit modal.
await this.go({ cipherId: null, itemId: null, action: null });
return;
}
const dialogRef = openViewCipherDialog(this.dialogService, {
data: {
cipher: cipher,
collections: collections,
disableEdit: !cipher.edit && !this.organization.canEditAllCiphers,
},
const cipherFormConfig = await this.cipherFormConfigService.buildConfig(
"edit",
cipher.id as CipherId,
cipher.type,
);
await this.openVaultItemDialog("view", cipherFormConfig, cipher);
}
/**
* Open the combined view / edit dialog for a cipher.
*/
async openVaultItemDialog(
mode: VaultItemDialogMode,
formConfig: CipherFormConfig,
cipher?: CipherView,
) {
const disableForm = cipher ? !cipher.edit && !this.organization.canEditAllCiphers : false;
// If the form is disabled, force the mode into `view`
const dialogMode = disableForm ? "view" : mode;
this.vaultItemDialogRef = VaultItemDialogComponent.open(this.dialogService, {
mode: dialogMode,
formConfig,
disableForm,
});
// Wait for the dialog to close.
const result: ViewCipherDialogCloseResult = await lastValueFrom(dialogRef.closed);
// If the dialog was closed by clicking the edit button, navigate to open the edit dialog.
if (result?.action === ViewCipherDialogResult.Edited) {
this.go({ itemId: cipher.id, action: "edit" });
return;
}
const result = await lastValueFrom(this.vaultItemDialogRef.closed);
this.vaultItemDialogRef = undefined;
// If the dialog was closed by deleting the cipher, refresh the vault.
if (result?.action === ViewCipherDialogResult.Deleted) {
if (result === VaultItemDialogResult.Deleted || result === VaultItemDialogResult.Saved) {
this.refresh();
}
// Clear the query params when the view dialog closes
this.go({ cipherId: null, itemId: null, action: null });
// Clear the query params when the dialog closes
await this.go({ cipherId: null, itemId: null, action: null });
}
async cloneCipher(cipher: CipherView) {
@ -867,7 +947,7 @@ export class VaultComponent implements OnInit, OnDestroy {
// Admins limited to only adding items to collections they have access to.
collections = await firstValueFrom(this.editableCollections$);
await this.editCipher(cipher, (comp) => {
await this.editCipher(cipher, true, (comp) => {
comp.cloneMode = true;
comp.collections = collections;
comp.collectionIds = cipher.collectionIds;

View File

@ -78,7 +78,7 @@ const moduleRules = [
loader: "@ngtools/webpack",
},
{
test: /\.wasm$/,
test: /argon2(-simd)?\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
},
@ -324,6 +324,7 @@ const webpackConfig = {
mode: NODE_ENV,
devtool: "source-map",
devServer: devServer,
target: "web",
entry: {
"app/polyfills": "./src/polyfills.ts",
"app/main": "./src/main.ts",
@ -383,9 +384,12 @@ const webpackConfig = {
clean: true,
},
module: {
noParse: /\.wasm$/,
noParse: /argon2(-simd)?\.wasm$/,
rules: moduleRules,
},
experiments: {
asyncWebAssembly: true,
},
plugins: plugins,
};

View File

@ -10,7 +10,11 @@ module.exports = {
preset: "ts-jest",
testEnvironment: "node",
setupFilesAfterEnv: ["<rootDir>/../../apps/cli/test.setup.ts"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
moduleNameMapper: {
"@bitwarden/common/platform/services/sdk/default-sdk-client-factory":
"<rootDir>/../../libs/common/spec/jest-sdk-client-factory",
...pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
},
};

View File

@ -3,7 +3,7 @@
"pretty": true,
"moduleResolution": "node",
"target": "ES2016",
"module": "es6",
"module": "ES2020",
"noImplicitAny": true,
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,

View File

@ -153,6 +153,8 @@ import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwar
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/platform/abstractions/validation.service";
@ -181,6 +183,7 @@ import { KeyGenerationService } from "@bitwarden/common/platform/services/key-ge
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { NoopNotificationsService } from "@bitwarden/common/platform/services/noop-notifications.service";
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
import { StateService } from "@bitwarden/common/platform/services/state.service";
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
@ -1337,6 +1340,16 @@ const safeProviders: SafeProvider[] = [
SsoLoginServiceAbstraction,
],
}),
safeProvider({
provide: SdkService,
useClass: DefaultSdkService,
deps: [
SdkClientFactory,
EnvironmentService,
PlatformUtilsServiceAbstraction,
ApiServiceAbstraction,
],
}),
];
@NgModule({

View File

@ -605,6 +605,10 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.collections = this.writeableCollections?.filter(
(c) => c.organizationId === this.cipher.organizationId,
);
// If there's only one collection, check it by default
if (this.collections.length === 1) {
(this.collections[0] as any).checked = true;
}
const org = await this.organizationService.get(this.cipher.organizationId);
if (org != null) {
this.cipher.organizationUseTotp = org.useTotp;

View File

@ -0,0 +1,9 @@
import { ClientSettings, LogLevel, BitwardenClient } from "@bitwarden/sdk-internal";
import { SdkClientFactory } from "../src/platform/abstractions/sdk/sdk-client-factory";
export class DefaultSdkClientFactory implements SdkClientFactory {
createSdkClient(settings?: ClientSettings, log_level?: LogLevel): Promise<BitwardenClient> {
throw new Error("Method not implemented.");
}
}

View File

@ -0,0 +1,10 @@
import type { BitwardenClient } from "@bitwarden/sdk-internal";
/**
* Factory for creating SDK clients.
*/
export abstract class SdkClientFactory {
abstract createSdkClient(
...args: ConstructorParameters<typeof BitwardenClient>
): Promise<BitwardenClient>;
}

View File

@ -0,0 +1,10 @@
import { Observable } from "rxjs";
import { BitwardenClient } from "@bitwarden/sdk-internal";
export abstract class SdkService {
client$: Observable<BitwardenClient>;
supported$: Observable<boolean>;
abstract failedToInitialize(): Promise<void>;
}

View File

@ -2,6 +2,7 @@
// eslint-disable-next-line @typescript-eslint/ban-types
export type SharedFlags = {
showPasswordless?: boolean;
sdk?: boolean;
};
// required to avoid linting errors when there are no flags
@ -28,7 +29,7 @@ function getFlags<T>(envFlags: string | T): T {
* @returns The value of the flag
*/
export function flagEnabled<Flags extends SharedFlags>(flag: keyof Flags): boolean {
const flags = getFlags<Flags>(process.env.FLAGS);
const flags = getFlags<Flags>(process.env.FLAGS) ?? ({} as Flags);
return flags[flag] == null || !!flags[flag];
}

View File

@ -0,0 +1,19 @@
import * as sdk from "@bitwarden/sdk-internal";
import * as module from "@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm";
import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory";
/**
* Directly imports the Bitwarden SDK and initializes it.
*
* **Warning**: This requires WASM support and will fail if the environment does not support it.
*/
export class DefaultSdkClientFactory implements SdkClientFactory {
async createSdkClient(
...args: ConstructorParameters<typeof sdk.BitwardenClient>
): Promise<sdk.BitwardenClient> {
(sdk as any).init(module);
return Promise.resolve(new sdk.BitwardenClient(...args));
}
}

View File

@ -0,0 +1,111 @@
import { concatMap, firstValueFrom, shareReplay } from "rxjs";
import { LogLevel, DeviceType as SdkDeviceType } from "@bitwarden/sdk-internal";
import { ApiService } from "../../../abstractions/api.service";
import { DeviceType } from "../../../enums/device-type.enum";
import { EnvironmentService } from "../../abstractions/environment.service";
import { PlatformUtilsService } from "../../abstractions/platform-utils.service";
import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory";
import { SdkService } from "../../abstractions/sdk/sdk.service";
export class DefaultSdkService implements SdkService {
client$ = this.environmentService.environment$.pipe(
concatMap(async (env) => {
const settings = {
apiUrl: env.getApiUrl(),
identityUrl: env.getIdentityUrl(),
deviceType: this.toDevice(this.platformUtilsService.getDevice()),
userAgent: this.userAgent ?? navigator.userAgent,
};
return await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info);
}),
shareReplay({ refCount: true, bufferSize: 1 }),
);
supported$ = this.client$.pipe(
concatMap(async (client) => {
return client.echo("bitwarden wasm!") === "bitwarden wasm!";
}),
);
constructor(
private sdkClientFactory: SdkClientFactory,
private environmentService: EnvironmentService,
private platformUtilsService: PlatformUtilsService,
private apiService: ApiService, // Yes we shouldn't import ApiService, but it's temporary
private userAgent: string = null,
) {}
async failedToInitialize(): Promise<void> {
// Only log on cloud instances
if (
this.platformUtilsService.isDev() ||
!(await firstValueFrom(this.environmentService.environment$)).isCloud
) {
return;
}
return this.apiService.send("POST", "/wasm-debug", null, false, false, null, (headers) => {
headers.append("SDK-Version", "1.0.0");
});
}
private toDevice(device: DeviceType): SdkDeviceType {
switch (device) {
case DeviceType.Android:
return "Android";
case DeviceType.iOS:
return "iOS";
case DeviceType.ChromeExtension:
return "ChromeExtension";
case DeviceType.FirefoxExtension:
return "FirefoxExtension";
case DeviceType.OperaExtension:
return "OperaExtension";
case DeviceType.EdgeExtension:
return "EdgeExtension";
case DeviceType.WindowsDesktop:
return "WindowsDesktop";
case DeviceType.MacOsDesktop:
return "MacOsDesktop";
case DeviceType.LinuxDesktop:
return "LinuxDesktop";
case DeviceType.ChromeBrowser:
return "ChromeBrowser";
case DeviceType.FirefoxBrowser:
return "FirefoxBrowser";
case DeviceType.OperaBrowser:
return "OperaBrowser";
case DeviceType.EdgeBrowser:
return "EdgeBrowser";
case DeviceType.IEBrowser:
return "IEBrowser";
case DeviceType.UnknownBrowser:
return "UnknownBrowser";
case DeviceType.AndroidAmazon:
return "AndroidAmazon";
case DeviceType.UWP:
return "UWP";
case DeviceType.SafariBrowser:
return "SafariBrowser";
case DeviceType.VivaldiBrowser:
return "VivaldiBrowser";
case DeviceType.VivaldiExtension:
return "VivaldiExtension";
case DeviceType.SafariExtension:
return "SafariExtension";
case DeviceType.Server:
return "Server";
case DeviceType.WindowsCLI:
return "WindowsCLI";
case DeviceType.MacOsCLI:
return "MacOsCLI";
case DeviceType.LinuxCLI:
return "LinuxCLI";
default:
return "SDK";
}
}
}

View File

@ -0,0 +1,16 @@
import type { BitwardenClient } from "@bitwarden/sdk-internal";
import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory";
/**
* Noop SDK client factory.
*
* Used during SDK rollout to prevent bundling the SDK with some applications.
*/
export class NoopSdkClientFactory implements SdkClientFactory {
createSdkClient(
...args: ConstructorParameters<typeof BitwardenClient>
): Promise<BitwardenClient> {
return Promise.reject(new Error("SDK not available"));
}
}

View File

@ -8,6 +8,7 @@ import { TreeNode } from "../models/domain/tree-node";
import { CollectionView } from "../models/view/collection.view";
export abstract class CollectionService {
encryptedCollections$: Observable<Collection[]>;
decryptedCollections$: Observable<CollectionView[]>;
clearActiveUserCache: () => Promise<void>;

View File

@ -673,6 +673,8 @@ export class CipherService implements CipherServiceAbstraction {
if (orgAdmin && cipher.organizationId != null) {
const request = new CipherCreateRequest(cipher);
response = await this.apiService.postCipherAdmin(request);
const data = new CipherData(response, cipher.collectionIds);
return new Cipher(data);
} else if (cipher.collectionIds != null) {
const request = new CipherCreateRequest(cipher);
response = await this.apiService.postCipherCreate(request);
@ -697,6 +699,8 @@ export class CipherService implements CipherServiceAbstraction {
if (orgAdmin && isNotClone) {
const request = new CipherRequest(cipher);
response = await this.apiService.putCipherAdmin(cipher.id, request);
const data = new CipherData(response, cipher.collectionIds);
return new Cipher(data, cipher.localData);
} else if (cipher.edit) {
const request = new CipherRequest(cipher);
response = await this.apiService.putCipher(cipher.id, request);

View File

@ -56,6 +56,16 @@ describe("1Password 1Pux Importer", () => {
const SecureNoteDataJson = JSON.stringify(SecureNoteData);
const SanitizedExportJson = JSON.stringify(SanitizedExport);
it("should not import items with state 'archived'", async () => {
const importer = new OnePassword1PuxImporter();
const archivedLoginData = LoginData;
archivedLoginData["accounts"][0]["vaults"][0]["items"][0]["state"] = "archived";
const archivedDataJson = JSON.stringify(archivedLoginData);
const result = await importer.parse(archivedDataJson);
expect(result != null).toBe(true);
expect(result.ciphers.length).toBe(0);
});
it("should parse login data", async () => {
const importer = new OnePassword1PuxImporter();
const result = await importer.parse(LoginDataJson);

View File

@ -26,7 +26,7 @@ export const APICredentialsData: ExportData = {
favIndex: 0,
createdAt: 1619465969,
updatedAt: 1619466052,
trashed: false,
state: "active",
categoryUuid: "112",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const APICredentialsData: ExportData = {
value: {
string: "apiuser@nullvalue.test",
},
indexAtSource: 0,
guarded: true,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const APICredentialsData: ExportData = {
value: {
concealed: "apiapiapiapiapiapiappy",
},
indexAtSource: 1,
guarded: true,
multiline: false,
dontGenerate: true,
@ -73,7 +71,6 @@ export const APICredentialsData: ExportData = {
value: {
menu: "jwt",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -89,7 +86,6 @@ export const APICredentialsData: ExportData = {
value: {
string: "filename.jwt",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -105,7 +101,6 @@ export const APICredentialsData: ExportData = {
value: {
date: 1301918460,
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -121,7 +116,6 @@ export const APICredentialsData: ExportData = {
value: {
date: 1932811260,
},
indexAtSource: 5,
guarded: false,
multiline: false,
dontGenerate: false,
@ -137,7 +131,6 @@ export const APICredentialsData: ExportData = {
value: {
string: "not.your.everyday.hostname",
},
indexAtSource: 6,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const BankAccountData: ExportData = {
favIndex: 0,
createdAt: 1619466056,
updatedAt: 1619466187,
trashed: false,
state: "active",
categoryUuid: "101",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const BankAccountData: ExportData = {
value: {
string: "Super Credit Union",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const BankAccountData: ExportData = {
value: {
string: "Cool Guy",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -73,7 +71,6 @@ export const BankAccountData: ExportData = {
value: {
menu: "checking",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -89,7 +86,6 @@ export const BankAccountData: ExportData = {
value: {
string: "111000999",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -105,7 +101,6 @@ export const BankAccountData: ExportData = {
value: {
string: "192837465918273645",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -121,7 +116,6 @@ export const BankAccountData: ExportData = {
value: {
string: "123456",
},
indexAtSource: 5,
guarded: false,
multiline: false,
dontGenerate: false,
@ -137,7 +131,6 @@ export const BankAccountData: ExportData = {
value: {
string: "DE12 123456",
},
indexAtSource: 6,
guarded: false,
multiline: false,
dontGenerate: false,
@ -153,7 +146,6 @@ export const BankAccountData: ExportData = {
value: {
concealed: "5555",
},
indexAtSource: 7,
guarded: false,
multiline: false,
dontGenerate: true,
@ -175,7 +167,6 @@ export const BankAccountData: ExportData = {
value: {
phone: "9399399933",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -191,7 +182,6 @@ export const BankAccountData: ExportData = {
value: {
string: "1 Fifth Avenue",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const CreditCardData: ExportData = {
favIndex: 0,
createdAt: 1619465282,
updatedAt: 1619465447,
trashed: false,
state: "active",
categoryUuid: "002",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const CreditCardData: ExportData = {
value: {
string: "Fred Engels",
},
indexAtSource: 0,
guarded: true,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const CreditCardData: ExportData = {
value: {
creditCardType: "discover",
},
indexAtSource: 1,
guarded: true,
multiline: false,
dontGenerate: false,
@ -73,7 +71,6 @@ export const CreditCardData: ExportData = {
value: {
creditCardNumber: "6011111111111117",
},
indexAtSource: 2,
guarded: true,
clipboardFilter: "0123456789",
multiline: false,
@ -90,7 +87,6 @@ export const CreditCardData: ExportData = {
value: {
concealed: "1312",
},
indexAtSource: 3,
guarded: true,
multiline: false,
dontGenerate: true,
@ -106,7 +102,6 @@ export const CreditCardData: ExportData = {
value: {
monthYear: 209912,
},
indexAtSource: 4,
guarded: true,
multiline: false,
dontGenerate: false,
@ -122,7 +117,6 @@ export const CreditCardData: ExportData = {
value: {
monthYear: 200101,
},
indexAtSource: 5,
guarded: true,
multiline: false,
dontGenerate: false,
@ -138,7 +132,6 @@ export const CreditCardData: ExportData = {
value: {
string: "card",
},
indexAtSource: 6,
guarded: false,
multiline: false,
dontGenerate: false,
@ -160,7 +153,6 @@ export const CreditCardData: ExportData = {
value: {
string: "Some bank",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -176,7 +168,6 @@ export const CreditCardData: ExportData = {
value: {
phone: "123456",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -192,7 +183,6 @@ export const CreditCardData: ExportData = {
value: {
phone: "0800123456",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -208,7 +198,6 @@ export const CreditCardData: ExportData = {
value: {
phone: "+49123456",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -224,7 +213,6 @@ export const CreditCardData: ExportData = {
value: {
url: "somebank.com",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -246,7 +234,6 @@ export const CreditCardData: ExportData = {
value: {
concealed: "1234",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: true,
@ -262,7 +249,6 @@ export const CreditCardData: ExportData = {
value: {
string: "$1312",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -278,7 +264,6 @@ export const CreditCardData: ExportData = {
value: {
string: "$500",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -294,7 +279,6 @@ export const CreditCardData: ExportData = {
value: {
string: "1%",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -310,7 +294,6 @@ export const CreditCardData: ExportData = {
value: {
string: "123456",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const DatabaseData: ExportData = {
favIndex: 0,
createdAt: 1619466193,
updatedAt: 1619466276,
trashed: false,
state: "active",
categoryUuid: "102",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const DatabaseData: ExportData = {
value: {
menu: "postgresql",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const DatabaseData: ExportData = {
value: {
string: "my.secret.db.server",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -73,7 +71,6 @@ export const DatabaseData: ExportData = {
value: {
string: "1337",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -89,7 +86,6 @@ export const DatabaseData: ExportData = {
value: {
string: "user_database",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -105,7 +101,6 @@ export const DatabaseData: ExportData = {
value: {
string: "cooldbuser",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -121,7 +116,6 @@ export const DatabaseData: ExportData = {
value: {
concealed: "^+kTjhLaN7wVPAhGU)*J",
},
indexAtSource: 5,
guarded: false,
multiline: false,
dontGenerate: false,
@ -137,7 +131,6 @@ export const DatabaseData: ExportData = {
value: {
string: "ASDIUFU-283234",
},
indexAtSource: 6,
guarded: false,
multiline: false,
dontGenerate: false,
@ -153,7 +146,6 @@ export const DatabaseData: ExportData = {
value: {
string: "cdbu",
},
indexAtSource: 7,
guarded: false,
multiline: false,
dontGenerate: false,
@ -169,7 +161,6 @@ export const DatabaseData: ExportData = {
value: {
string: "ssh",
},
indexAtSource: 8,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const DriversLicenseData: ExportData = {
favIndex: 0,
createdAt: 1619466279,
updatedAt: 1619466425,
trashed: false,
state: "active",
categoryUuid: "103",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const DriversLicenseData: ExportData = {
value: {
string: "Michael Scarn",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const DriversLicenseData: ExportData = {
value: {
string: "2120 Mifflin Rd.",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -73,7 +71,6 @@ export const DriversLicenseData: ExportData = {
value: {
date: 252504060,
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -89,7 +86,6 @@ export const DriversLicenseData: ExportData = {
value: {
gender: "male",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -105,7 +101,6 @@ export const DriversLicenseData: ExportData = {
value: {
string: "5'11\"",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -121,7 +116,6 @@ export const DriversLicenseData: ExportData = {
value: {
string: "12345678901",
},
indexAtSource: 5,
guarded: false,
multiline: false,
dontGenerate: false,
@ -137,7 +131,6 @@ export const DriversLicenseData: ExportData = {
value: {
string: "C",
},
indexAtSource: 6,
guarded: false,
multiline: false,
dontGenerate: false,
@ -153,7 +146,6 @@ export const DriversLicenseData: ExportData = {
value: {
string: "B",
},
indexAtSource: 7,
guarded: false,
multiline: false,
dontGenerate: false,
@ -169,7 +161,6 @@ export const DriversLicenseData: ExportData = {
value: {
string: "Pennsylvania",
},
indexAtSource: 8,
guarded: false,
multiline: false,
dontGenerate: false,
@ -185,7 +176,6 @@ export const DriversLicenseData: ExportData = {
value: {
string: "United States",
},
indexAtSource: 9,
guarded: false,
multiline: false,
dontGenerate: false,
@ -201,7 +191,6 @@ export const DriversLicenseData: ExportData = {
value: {
monthYear: 203012,
},
indexAtSource: 10,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const EmailAccountData: ExportData = {
favIndex: 0,
createdAt: 1619466428,
updatedAt: 1619466585,
trashed: false,
state: "active",
categoryUuid: "111",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const EmailAccountData: ExportData = {
value: {
menu: "either",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const EmailAccountData: ExportData = {
value: {
string: "someuser@nullvalue.test",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -73,7 +71,6 @@ export const EmailAccountData: ExportData = {
value: {
string: "mailserver.nullvalue.test",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -89,7 +86,6 @@ export const EmailAccountData: ExportData = {
value: {
string: "587",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -105,7 +101,6 @@ export const EmailAccountData: ExportData = {
value: {
concealed: "u1jsf<UI*&YU&^T",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -121,7 +116,6 @@ export const EmailAccountData: ExportData = {
value: {
menu: "TLS",
},
indexAtSource: 5,
guarded: false,
multiline: false,
dontGenerate: false,
@ -137,7 +131,6 @@ export const EmailAccountData: ExportData = {
value: {
menu: "kerberos_v5",
},
indexAtSource: 6,
guarded: false,
multiline: false,
dontGenerate: false,
@ -159,7 +152,6 @@ export const EmailAccountData: ExportData = {
value: {
string: "mailserver.nullvalue.test",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -175,7 +167,6 @@ export const EmailAccountData: ExportData = {
value: {
string: "589",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -191,7 +182,6 @@ export const EmailAccountData: ExportData = {
value: {
string: "someuser@nullvalue.test",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -207,7 +197,6 @@ export const EmailAccountData: ExportData = {
value: {
concealed: "(*1674%^UIUJ*UI(IUI8u98uyy",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -223,7 +212,6 @@ export const EmailAccountData: ExportData = {
value: {
menu: "TLS",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -239,7 +227,6 @@ export const EmailAccountData: ExportData = {
value: {
menu: "password",
},
indexAtSource: 5,
guarded: false,
multiline: false,
dontGenerate: false,
@ -261,7 +248,6 @@ export const EmailAccountData: ExportData = {
value: {
string: "Telum",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -277,7 +263,6 @@ export const EmailAccountData: ExportData = {
value: {
string: "https://telum.nullvalue.test",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -293,7 +278,6 @@ export const EmailAccountData: ExportData = {
value: {
string: "2346666666",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -309,7 +293,6 @@ export const EmailAccountData: ExportData = {
value: {
string: "18005557777",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const EmailFieldOnIdentityData: ExportData = {
favIndex: 0,
createdAt: 1619465450,
updatedAt: 1619465789,
trashed: false,
state: "active",
categoryUuid: "004",
details: {
loginFields: [],
@ -55,7 +55,6 @@ export const EmailFieldOnIdentityData: ExportData = {
provider: "myEmailProvider",
},
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const EmailFieldOnIdentityPrefilledData: ExportData = {
favIndex: 0,
createdAt: 1619465450,
updatedAt: 1619465789,
trashed: false,
state: "active",
categoryUuid: "004",
details: {
loginFields: [],
@ -52,7 +52,6 @@ export const EmailFieldOnIdentityPrefilledData: ExportData = {
value: {
string: "gengels@nullvalue.test",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -71,7 +70,6 @@ export const EmailFieldOnIdentityPrefilledData: ExportData = {
provider: "myEmailProvider",
},
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const EmailFieldData: ExportData = {
favIndex: 1,
createdAt: 1619467985,
updatedAt: 1619468230,
trashed: false,
state: "active",
categoryUuid: "100",
details: {
loginFields: [],
@ -49,7 +49,6 @@ export const EmailFieldData: ExportData = {
provider: "myEmailProvider",
},
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const IdentityData: ExportData = {
favIndex: 0,
createdAt: 1619465450,
updatedAt: 1619465789,
trashed: false,
state: "active",
categoryUuid: "004",
details: {
loginFields: [],
@ -42,7 +42,6 @@ export const IdentityData: ExportData = {
value: {
string: "George",
},
indexAtSource: 0,
guarded: true,
multiline: false,
dontGenerate: false,
@ -58,7 +57,6 @@ export const IdentityData: ExportData = {
value: {
string: "S",
},
indexAtSource: 1,
guarded: true,
multiline: false,
dontGenerate: false,
@ -74,7 +72,6 @@ export const IdentityData: ExportData = {
value: {
string: "Engels",
},
indexAtSource: 2,
guarded: true,
multiline: false,
dontGenerate: false,
@ -90,7 +87,6 @@ export const IdentityData: ExportData = {
value: {
menu: "male",
},
indexAtSource: 3,
guarded: true,
multiline: false,
dontGenerate: false,
@ -106,7 +102,6 @@ export const IdentityData: ExportData = {
value: {
date: 347198460,
},
indexAtSource: 4,
guarded: true,
multiline: false,
dontGenerate: false,
@ -122,7 +117,6 @@ export const IdentityData: ExportData = {
value: {
string: "Steel Worker",
},
indexAtSource: 5,
guarded: true,
multiline: false,
dontGenerate: false,
@ -138,7 +132,6 @@ export const IdentityData: ExportData = {
value: {
string: "Acme Inc.",
},
indexAtSource: 6,
guarded: true,
multiline: false,
dontGenerate: false,
@ -154,7 +147,6 @@ export const IdentityData: ExportData = {
value: {
string: "QA",
},
indexAtSource: 7,
guarded: true,
multiline: false,
dontGenerate: false,
@ -170,7 +162,6 @@ export const IdentityData: ExportData = {
value: {
string: "Quality Assurance Manager",
},
indexAtSource: 8,
guarded: true,
multiline: false,
dontGenerate: false,
@ -198,7 +189,6 @@ export const IdentityData: ExportData = {
state: "California",
},
},
indexAtSource: 0,
guarded: true,
multiline: false,
dontGenerate: false,
@ -214,7 +204,6 @@ export const IdentityData: ExportData = {
value: {
phone: "4565555555",
},
indexAtSource: 1,
guarded: true,
multiline: false,
dontGenerate: false,
@ -230,7 +219,6 @@ export const IdentityData: ExportData = {
value: {
phone: "4575555555",
},
indexAtSource: 2,
guarded: true,
multiline: false,
dontGenerate: false,
@ -246,7 +234,6 @@ export const IdentityData: ExportData = {
value: {
phone: "4585555555",
},
indexAtSource: 3,
guarded: true,
multiline: false,
dontGenerate: false,
@ -262,7 +249,6 @@ export const IdentityData: ExportData = {
value: {
phone: "4595555555",
},
indexAtSource: 4,
guarded: true,
multiline: false,
dontGenerate: false,
@ -284,7 +270,6 @@ export const IdentityData: ExportData = {
value: {
string: "gengels",
},
indexAtSource: 0,
guarded: true,
multiline: false,
dontGenerate: false,
@ -300,7 +285,6 @@ export const IdentityData: ExportData = {
value: {
string: "Who's a super cool guy?",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -316,7 +300,6 @@ export const IdentityData: ExportData = {
value: {
string: "Me, buddy.",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -332,7 +315,6 @@ export const IdentityData: ExportData = {
value: {
string: "gengels@nullvalue.test",
},
indexAtSource: 3,
guarded: true,
multiline: false,
dontGenerate: false,
@ -348,7 +330,6 @@ export const IdentityData: ExportData = {
value: {
string: "cv.gengels.nullvalue.test",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -364,7 +345,6 @@ export const IdentityData: ExportData = {
value: {
string: "12345678",
},
indexAtSource: 5,
guarded: false,
multiline: false,
dontGenerate: false,
@ -380,7 +360,6 @@ export const IdentityData: ExportData = {
value: {
string: "skypeisbad1619",
},
indexAtSource: 6,
guarded: false,
multiline: false,
dontGenerate: false,
@ -396,7 +375,6 @@ export const IdentityData: ExportData = {
value: {
string: "aollol@lololol.aol.com",
},
indexAtSource: 7,
guarded: false,
multiline: false,
dontGenerate: false,
@ -412,7 +390,6 @@ export const IdentityData: ExportData = {
value: {
string: "sk8rboi13@yah00.com",
},
indexAtSource: 8,
guarded: false,
multiline: false,
dontGenerate: false,
@ -428,7 +405,6 @@ export const IdentityData: ExportData = {
value: {
string: "msnothankyou@msn&m&m.com",
},
indexAtSource: 9,
guarded: false,
multiline: false,
dontGenerate: false,
@ -444,7 +420,6 @@ export const IdentityData: ExportData = {
value: {
string: "super cool guy",
},
indexAtSource: 10,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const LoginData: ExportData = {
favIndex: 0,
createdAt: 1635522833,
updatedAt: 1635522872,
trashed: false,
state: "active",
categoryUuid: "001",
details: {
loginFields: [
@ -68,7 +68,6 @@ export const LoginData: ExportData = {
value: {
string: "username123123",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -84,7 +83,6 @@ export const LoginData: ExportData = {
value: {
totp: "otpseed777",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const MedicalRecordData: ExportData = {
favIndex: 0,
createdAt: 1641220207,
updatedAt: 1641220326,
trashed: false,
state: "active",
categoryUuid: "113",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const MedicalRecordData: ExportData = {
value: {
date: 1641038460,
},
indexAtSource: 0,
guarded: true,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const MedicalRecordData: ExportData = {
value: {
string: "some hospital/clinic",
},
indexAtSource: 1,
guarded: true,
multiline: false,
dontGenerate: false,
@ -74,7 +72,6 @@ export const MedicalRecordData: ExportData = {
value: {
string: "Some Doctor",
},
indexAtSource: 2,
guarded: true,
multiline: false,
dontGenerate: false,
@ -91,7 +88,6 @@ export const MedicalRecordData: ExportData = {
value: {
string: "Me",
},
indexAtSource: 3,
guarded: true,
multiline: false,
dontGenerate: false,
@ -108,7 +104,6 @@ export const MedicalRecordData: ExportData = {
value: {
string: "unwell",
},
indexAtSource: 4,
guarded: true,
multiline: true,
dontGenerate: false,
@ -131,7 +126,6 @@ export const MedicalRecordData: ExportData = {
value: {
string: "Insuline",
},
indexAtSource: 0,
guarded: true,
multiline: false,
dontGenerate: false,
@ -148,7 +142,6 @@ export const MedicalRecordData: ExportData = {
value: {
string: "1",
},
indexAtSource: 1,
guarded: true,
multiline: false,
dontGenerate: false,
@ -165,7 +158,6 @@ export const MedicalRecordData: ExportData = {
value: {
string: "multiple times a day",
},
indexAtSource: 2,
guarded: true,
multiline: true,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const MembershipData: ExportData = {
favIndex: 1,
createdAt: 1619467269,
updatedAt: 1619467368,
trashed: false,
state: "active",
categoryUuid: "105",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const MembershipData: ExportData = {
value: {
string: "National Public Library",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const MembershipData: ExportData = {
value: {
url: "https://npl.nullvalue.gov.test",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -73,7 +71,6 @@ export const MembershipData: ExportData = {
value: {
phone: "9995555555",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -89,7 +86,6 @@ export const MembershipData: ExportData = {
value: {
string: "George Engels",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -105,7 +101,6 @@ export const MembershipData: ExportData = {
value: {
monthYear: 199901,
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -121,7 +116,6 @@ export const MembershipData: ExportData = {
value: {
monthYear: 203412,
},
indexAtSource: 5,
guarded: false,
multiline: false,
dontGenerate: false,
@ -137,7 +131,6 @@ export const MembershipData: ExportData = {
value: {
string: "64783862",
},
indexAtSource: 6,
guarded: false,
multiline: false,
dontGenerate: false,
@ -153,7 +146,6 @@ export const MembershipData: ExportData = {
value: {
concealed: "19191",
},
indexAtSource: 7,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const OnePuxExampleFile: ExportData = {
favIndex: 1,
createdAt: 1614298956,
updatedAt: 1635346445,
trashed: false,
state: "active",
categoryUuid: "001",
details: {
loginFields: [
@ -50,7 +50,6 @@ export const OnePuxExampleFile: ExportData = {
value: {
concealed: "12345",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const OutdoorLicenseData: ExportData = {
favIndex: 0,
createdAt: 1619467374,
updatedAt: 1619467492,
trashed: false,
state: "active",
categoryUuid: "104",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const OutdoorLicenseData: ExportData = {
value: {
string: "Cash Bandit",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const OutdoorLicenseData: ExportData = {
value: {
date: 1617278460,
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -73,7 +71,6 @@ export const OutdoorLicenseData: ExportData = {
value: {
date: 2343124860,
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -89,7 +86,6 @@ export const OutdoorLicenseData: ExportData = {
value: {
string: "Bananas,blueberries,corn",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -105,7 +101,6 @@ export const OutdoorLicenseData: ExportData = {
value: {
string: "100/each",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -121,7 +116,6 @@ export const OutdoorLicenseData: ExportData = {
value: {
string: "Washington",
},
indexAtSource: 5,
guarded: false,
multiline: false,
dontGenerate: false,
@ -137,7 +131,6 @@ export const OutdoorLicenseData: ExportData = {
value: {
string: "United States of America",
},
indexAtSource: 6,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const PassportData: ExportData = {
favIndex: 0,
createdAt: 1619467498,
updatedAt: 1619467655,
trashed: false,
state: "active",
categoryUuid: "106",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const PassportData: ExportData = {
value: {
string: "US Passport",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const PassportData: ExportData = {
value: {
string: "United States of America",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -73,7 +71,6 @@ export const PassportData: ExportData = {
value: {
string: "76436847",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -89,7 +86,6 @@ export const PassportData: ExportData = {
value: {
string: "David Global",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -105,7 +101,6 @@ export const PassportData: ExportData = {
value: {
gender: "female",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -121,7 +116,6 @@ export const PassportData: ExportData = {
value: {
string: "International",
},
indexAtSource: 5,
guarded: false,
multiline: false,
dontGenerate: false,
@ -137,7 +131,6 @@ export const PassportData: ExportData = {
value: {
string: "Department of State",
},
indexAtSource: 6,
guarded: false,
multiline: false,
dontGenerate: false,
@ -153,7 +146,6 @@ export const PassportData: ExportData = {
value: {
date: 418046460,
},
indexAtSource: 7,
guarded: false,
multiline: false,
dontGenerate: false,
@ -169,7 +161,6 @@ export const PassportData: ExportData = {
value: {
string: "A cave somewhere in Maine",
},
indexAtSource: 8,
guarded: false,
multiline: false,
dontGenerate: false,
@ -185,7 +176,6 @@ export const PassportData: ExportData = {
value: {
date: 1577880060,
},
indexAtSource: 9,
guarded: false,
multiline: false,
dontGenerate: false,
@ -201,7 +191,6 @@ export const PassportData: ExportData = {
value: {
date: 2524651260,
},
indexAtSource: 10,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const PasswordData: ExportData = {
favIndex: 0,
createdAt: 1619465796,
updatedAt: 1619465869,
trashed: false,
state: "active",
categoryUuid: "005",
details: {
loginFields: [],

View File

@ -26,7 +26,7 @@ export const RewardsProgramData: ExportData = {
favIndex: 0,
createdAt: 1619467659,
updatedAt: 1619467765,
trashed: false,
state: "active",
categoryUuid: "107",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const RewardsProgramData: ExportData = {
value: {
string: "Super Cool Store Co.",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const RewardsProgramData: ExportData = {
value: {
string: "Chef Coldroom",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -73,7 +71,6 @@ export const RewardsProgramData: ExportData = {
value: {
string: "member-29813569",
},
indexAtSource: 2,
guarded: false,
clipboardFilter:
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
@ -91,7 +88,6 @@ export const RewardsProgramData: ExportData = {
value: {
concealed: "99913",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -113,7 +109,6 @@ export const RewardsProgramData: ExportData = {
value: {
string: "additional member id",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -129,7 +124,6 @@ export const RewardsProgramData: ExportData = {
value: {
monthYear: 202101,
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -145,7 +139,6 @@ export const RewardsProgramData: ExportData = {
value: {
phone: "123456",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -161,7 +154,6 @@ export const RewardsProgramData: ExportData = {
value: {
phone: "123456",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -177,7 +169,6 @@ export const RewardsProgramData: ExportData = {
value: {
url: "supercoolstore.com",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const SecureNoteData: ExportData = {
favIndex: 0,
createdAt: 1619465226,
updatedAt: 1619465278,
trashed: false,
state: "active",
categoryUuid: "003",
details: {
loginFields: [],

View File

@ -26,7 +26,7 @@ export const ServerData: ExportData = {
favIndex: 0,
createdAt: 1619467769,
updatedAt: 1619467906,
trashed: false,
state: "active",
categoryUuid: "110",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const ServerData: ExportData = {
value: {
string: "https://coolserver.nullvalue.test",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const ServerData: ExportData = {
value: {
string: "frankly-notsure",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -73,7 +71,6 @@ export const ServerData: ExportData = {
value: {
concealed: "*&YHJI87yjy78u",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -95,7 +92,6 @@ export const ServerData: ExportData = {
value: {
string: "https://coolserver.nullvalue.test/admin",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -111,7 +107,6 @@ export const ServerData: ExportData = {
value: {
string: "frankly-idontknowwhatimdoing",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -127,7 +122,6 @@ export const ServerData: ExportData = {
value: {
concealed: "^%RY&^YUiju8iUYHJI(U",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -149,7 +143,6 @@ export const ServerData: ExportData = {
value: {
string: "Private Hosting Provider Inc.",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -165,7 +158,6 @@ export const ServerData: ExportData = {
value: {
string: "https://phpi.nullvalue.test",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -181,7 +173,6 @@ export const ServerData: ExportData = {
value: {
string: "https://phpi.nullvalue.test/support",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -197,7 +188,6 @@ export const ServerData: ExportData = {
value: {
string: "8882569382",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const SoftwareLicenseData: ExportData = {
favIndex: 1,
createdAt: 1619467985,
updatedAt: 1619468230,
trashed: false,
state: "active",
categoryUuid: "100",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const SoftwareLicenseData: ExportData = {
value: {
string: "5.10.1000",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const SoftwareLicenseData: ExportData = {
value: {
string: "265453-13457355-847327",
},
indexAtSource: 1,
guarded: true,
multiline: true,
dontGenerate: false,
@ -79,7 +77,6 @@ export const SoftwareLicenseData: ExportData = {
value: {
string: "Kay Riddler",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -98,7 +95,6 @@ export const SoftwareLicenseData: ExportData = {
provider: null,
},
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -114,7 +110,6 @@ export const SoftwareLicenseData: ExportData = {
value: {
string: "Riddles and Jigsaw Puzzles GmbH",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -136,7 +131,6 @@ export const SoftwareLicenseData: ExportData = {
value: {
url: "https://limuxcompany.nullvalue.test/5.10.1000/isos",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -152,7 +146,6 @@ export const SoftwareLicenseData: ExportData = {
value: {
string: "Limux Software and Hardware",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -168,7 +161,6 @@ export const SoftwareLicenseData: ExportData = {
value: {
url: "https://limuxcompany.nullvalue.test/",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -184,7 +176,6 @@ export const SoftwareLicenseData: ExportData = {
value: {
string: "$999",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -203,7 +194,6 @@ export const SoftwareLicenseData: ExportData = {
provider: null,
},
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -225,7 +215,6 @@ export const SoftwareLicenseData: ExportData = {
value: {
date: 1617278460,
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -241,7 +230,6 @@ export const SoftwareLicenseData: ExportData = {
value: {
string: "594839",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -257,7 +245,6 @@ export const SoftwareLicenseData: ExportData = {
value: {
string: "$1086.59",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -26,7 +26,7 @@ export const SSNData: ExportData = {
favIndex: 1,
createdAt: 1619467910,
updatedAt: 1619467982,
trashed: false,
state: "active",
categoryUuid: "108",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const SSNData: ExportData = {
value: {
string: "Jack Judd",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const SSNData: ExportData = {
value: {
concealed: "131-216-1900",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: true,

View File

@ -26,7 +26,7 @@ export const WirelessRouterData: ExportData = {
favIndex: 0,
createdAt: 1577652307,
updatedAt: 1577652307,
trashed: false,
state: "active",
categoryUuid: "109",
details: {
loginFields: [],
@ -41,7 +41,6 @@ export const WirelessRouterData: ExportData = {
value: {
string: "pixel 2Xl",
},
indexAtSource: 0,
guarded: false,
multiline: false,
dontGenerate: false,
@ -57,7 +56,6 @@ export const WirelessRouterData: ExportData = {
value: {
concealed: "BqatGTVQ9TCN72tLbjrsHqkb",
},
indexAtSource: 1,
guarded: false,
multiline: false,
dontGenerate: false,
@ -73,7 +71,6 @@ export const WirelessRouterData: ExportData = {
value: {
string: "127.0.0.1",
},
indexAtSource: 2,
guarded: false,
multiline: false,
dontGenerate: false,
@ -89,7 +86,6 @@ export const WirelessRouterData: ExportData = {
value: {
string: "some airportId",
},
indexAtSource: 3,
guarded: false,
multiline: false,
dontGenerate: false,
@ -105,7 +101,6 @@ export const WirelessRouterData: ExportData = {
value: {
string: "some network name",
},
indexAtSource: 4,
guarded: false,
multiline: false,
dontGenerate: false,
@ -121,7 +116,6 @@ export const WirelessRouterData: ExportData = {
value: {
menu: "WPA",
},
indexAtSource: 5,
guarded: false,
multiline: false,
dontGenerate: false,
@ -137,7 +131,6 @@ export const WirelessRouterData: ExportData = {
value: {
concealed: "wifipassword",
},
indexAtSource: 6,
guarded: false,
multiline: false,
dontGenerate: false,
@ -153,7 +146,6 @@ export const WirelessRouterData: ExportData = {
value: {
concealed: "diskpassword",
},
indexAtSource: 7,
guarded: false,
multiline: false,
dontGenerate: false,

View File

@ -22,7 +22,7 @@ export class Parser {
/*
May return null when the chunk does not represent an account.
All secure notes are ACCTs but not all of them store account information.
TODO: Add a test for the folder case!
TODO: Add a test case that covers secure note account!
*/
@ -60,9 +60,17 @@ export class Parser {
// 3: url
step = 3;
let url = Utils.fromBufferToUtf8(
this.decodeHexLoose(Utils.fromBufferToUtf8(this.readItem(reader))),
);
const urlEncoded = this.readItem(reader);
let url =
urlEncoded.length > 0 && urlEncoded[0] === 33 // 33 = '!'
? // URL is encrypted
await this.cryptoUtils.decryptAes256PlainWithDefault(
urlEncoded,
encryptionKey,
placeholder,
)
: // URL is not encrypted
Utils.fromBufferToUtf8(this.decodeHexLoose(Utils.fromBufferToUtf8(urlEncoded)));
// Ignore "group" accounts. They have no credentials.
if (url == "http://group") {

View File

@ -37,7 +37,7 @@ export class OnePassword1PuxImporter extends BaseImporter implements Importer {
// const personalVaults = account.vaults[0].filter((v) => v.attrs.type === VaultAttributeTypeEnum.Personal);
account.vaults.forEach((vault: VaultsEntity) => {
vault.items.forEach((item: Item) => {
if (item.trashed === true) {
if (item.state === "archived") {
return;
}

View File

@ -53,7 +53,7 @@ export interface Item {
favIndex: number;
createdAt: number;
updatedAt: number;
trashed?: boolean;
state: "active" | "archived";
categoryUuid: string;
details: Details;
overview: Overview;
@ -88,12 +88,12 @@ export interface SectionsEntity {
title: string;
name?: string | null;
fields?: FieldsEntity[] | null;
hideAddAnotherField?: boolean | null;
}
export interface FieldsEntity {
title: string;
id: string;
value: Value;
indexAtSource: number;
guarded: boolean;
multiline: boolean;
dontGenerate: boolean;
@ -153,6 +153,8 @@ export interface Overview {
pbe?: number | null;
pgrng?: boolean | null;
tags?: string[] | null;
icons?: string | null;
watchtowerExclusions?: string | null;
}
export interface UrlsEntity {
label: string;

View File

@ -79,6 +79,9 @@ type BaseCipherFormConfig = {
* List of organizations that the user can create ciphers for.
*/
organizations?: Organization[];
/** Hides the fields that are only applicable to individuals, useful in the Admin Console where folders aren't applicable */
hideIndividualVaultFields?: true;
};
/**

View File

@ -2,6 +2,7 @@
<bit-section-header>
<h2 bitTypography="h6">{{ "itemDetails" | i18n }}</h2>
<button
*ngIf="!config.hideIndividualVaultFields"
slot="end"
type="button"
size="small"
@ -18,7 +19,11 @@
<input bitInput formControlName="name" />
</bit-form-field>
<div class="tw-grid tw-grid-cols-2 tw-gap-1">
<bit-form-field *ngIf="showOwnership" [disableMargin]="!showCollectionsControl">
<bit-form-field
*ngIf="showOwnership"
[disableMargin]="!showCollectionsControl"
[class.tw-col-span-2]="config.hideIndividualVaultFields"
>
<bit-label>{{ "owner" | i18n }}</bit-label>
<bit-select formControlName="organizationId">
<bit-option
@ -36,6 +41,7 @@
<bit-form-field
[class.tw-col-span-2]="!showOwnership"
[disableMargin]="!showCollectionsControl"
*ngIf="!config.hideIndividualVaultFields"
>
<bit-label>{{ "folder" | i18n }}</bit-label>
<bit-select formControlName="folderId">

View File

@ -1,5 +1,5 @@
import { inject, Injectable } from "@angular/core";
import { combineLatest, firstValueFrom, map } from "rxjs";
import { combineLatest, filter, firstValueFrom, map, switchMap } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@ -39,9 +39,21 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService {
await firstValueFrom(
combineLatest([
this.organizations$,
this.collectionService.decryptedCollections$,
this.collectionService.encryptedCollections$.pipe(
switchMap((c) =>
this.collectionService.decryptedCollections$.pipe(
filter((d) => d.length === c.length), // Ensure all collections have been decrypted
),
),
),
this.allowPersonalOwnership$,
this.folderService.folderViews$,
this.folderService.folders$.pipe(
switchMap((f) =>
this.folderService.folderViews$.pipe(
filter((d) => d.length - 1 === f.length), // -1 for "No Folder" in folderViews$
),
),
),
this.getCipher(cipherId),
]),
);

14
package-lock.json generated
View File

@ -24,6 +24,7 @@
"@angular/platform-browser": "16.2.12",
"@angular/platform-browser-dynamic": "16.2.12",
"@angular/router": "16.2.12",
"@bitwarden/sdk-internal": "0.1.3",
"@electron/fuses": "1.8.0",
"@koa/multer": "3.0.2",
"@koa/router": "12.0.1",
@ -193,11 +194,11 @@
},
"apps/browser": {
"name": "@bitwarden/browser",
"version": "2024.10.0"
"version": "2024.10.1"
},
"apps/cli": {
"name": "@bitwarden/cli",
"version": "2024.9.1",
"version": "2024.10.0",
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@koa/multer": "3.0.2",
@ -233,7 +234,7 @@
},
"apps/desktop": {
"name": "@bitwarden/desktop",
"version": "2024.9.2",
"version": "2024.10.0",
"hasInstallScript": true,
"license": "GPL-3.0"
},
@ -247,7 +248,7 @@
},
"apps/web": {
"name": "@bitwarden/web-vault",
"version": "2024.10.0"
"version": "2024.10.1"
},
"libs/admin-console": {
"name": "@bitwarden/admin-console",
@ -4625,6 +4626,11 @@
"resolved": "libs/platform",
"link": true
},
"node_modules/@bitwarden/sdk-internal": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.3.tgz",
"integrity": "sha512-zk9DyYMjylVLdljeLn3OLBcD939Hg/qMNJ2FxbyjiSKtcOcgglXgYmbcS01NRFFfM9REbn+j+2fWbQo6N+8SHw=="
},
"node_modules/@bitwarden/vault": {
"resolved": "libs/vault",
"link": true

View File

@ -157,6 +157,7 @@
"@angular/platform-browser": "16.2.12",
"@angular/platform-browser-dynamic": "16.2.12",
"@angular/router": "16.2.12",
"@bitwarden/sdk-internal": "0.1.3",
"@electron/fuses": "1.8.0",
"@koa/multer": "3.0.2",
"@koa/router": "12.0.1",