Merge branch 'main' into autofill/pm-6426-create-alarms-manager-and-update-usage-of-long-lived-timeouts-rework

This commit is contained in:
Cesar Gonzalez 2024-05-15 14:06:52 -05:00 committed by GitHub
commit 1ce5cc3366
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 1667 additions and 1277 deletions

View File

@ -234,6 +234,8 @@ import { ForegroundTaskSchedulerService } from "../platform/services/task-schedu
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider";
import { ForegroundMemoryStorageService } from "../platform/storage/foreground-memory-storage.service";
import { ForegroundSyncService } from "../platform/sync/foreground-sync.service";
import { SyncServiceListener } from "../platform/sync/sync-service.listener";
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
import FilelessImporterBackground from "../tools/background/fileless-importer.background";
@ -344,6 +346,7 @@ export default class MainBackground {
scriptInjectorService: BrowserScriptInjectorService;
kdfConfigService: kdfConfigServiceAbstraction;
offscreenDocumentService: OffscreenDocumentService;
syncServiceListener: SyncServiceListener;
onUpdatedRan: boolean;
onReplacedRan: boolean;
@ -806,32 +809,52 @@ export default class MainBackground {
this.providerService = new ProviderService(this.stateProvider);
this.syncService = new SyncService(
this.masterPasswordService,
this.accountService,
this.apiService,
this.domainSettingsService,
this.folderService,
this.cipherService,
this.cryptoService,
this.collectionService,
this.messagingService,
this.policyService,
this.sendService,
this.logService,
this.keyConnectorService,
this.stateService,
this.providerService,
this.folderApiService,
this.organizationService,
this.sendApiService,
this.userDecryptionOptionsService,
this.avatarService,
logoutCallback,
this.billingAccountProfileStateService,
this.tokenService,
this.authService,
);
if (this.popupOnlyContext) {
this.syncService = new ForegroundSyncService(
this.stateService,
this.folderService,
this.folderApiService,
this.messagingService,
this.logService,
this.cipherService,
this.collectionService,
this.apiService,
this.accountService,
this.authService,
this.sendService,
this.sendApiService,
messageListener,
);
} else {
this.syncService = new SyncService(
this.masterPasswordService,
this.accountService,
this.apiService,
this.domainSettingsService,
this.folderService,
this.cipherService,
this.cryptoService,
this.collectionService,
this.messagingService,
this.policyService,
this.sendService,
this.logService,
this.keyConnectorService,
this.stateService,
this.providerService,
this.folderApiService,
this.organizationService,
this.sendApiService,
this.userDecryptionOptionsService,
this.avatarService,
logoutCallback,
this.billingAccountProfileStateService,
this.tokenService,
this.authService,
);
this.syncServiceListener = new SyncServiceListener(this.syncService, messageListener);
}
this.eventUploadService = new EventUploadService(
this.apiService,
this.stateProvider,
@ -1159,6 +1182,7 @@ export default class MainBackground {
this.contextMenusBackground?.init();
await this.idleBackground.init();
this.webRequestBackground?.startListening();
this.syncServiceListener?.startListening();
return new Promise<void>((resolve) => {
setTimeout(async () => {

View File

@ -167,6 +167,11 @@ export class NativeMessagingBackground {
cancelButtonText: null,
type: "danger",
});
if (this.resolver) {
this.resolver(message);
}
break;
case "verifyFingerprint": {
if (this.sharedSecret == null) {

View File

@ -0,0 +1,79 @@
import { firstValueFrom, timeout } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import {
CommandDefinition,
MessageListener,
MessageSender,
} from "@bitwarden/common/platform/messaging";
import { CoreSyncService } from "@bitwarden/common/platform/sync/internal";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
const SYNC_COMPLETED = new CommandDefinition<{ successfully: boolean }>("syncCompleted");
export const DO_FULL_SYNC = new CommandDefinition<{
forceSync: boolean;
allowThrowOnError: boolean;
}>("doFullSync");
export class ForegroundSyncService extends CoreSyncService {
constructor(
stateService: StateService,
folderService: InternalFolderService,
folderApiService: FolderApiServiceAbstraction,
messageSender: MessageSender,
logService: LogService,
cipherService: CipherService,
collectionService: CollectionService,
apiService: ApiService,
accountService: AccountService,
authService: AuthService,
sendService: InternalSendService,
sendApiService: SendApiService,
private readonly messageListener: MessageListener,
) {
super(
stateService,
folderService,
folderApiService,
messageSender,
logService,
cipherService,
collectionService,
apiService,
accountService,
authService,
sendService,
sendApiService,
);
}
async fullSync(forceSync: boolean, allowThrowOnError: boolean = false): Promise<boolean> {
this.syncInProgress = true;
try {
const syncCompletedPromise = firstValueFrom(
this.messageListener.messages$(SYNC_COMPLETED).pipe(
timeout({
first: 10_000,
with: () => {
throw new Error("Timeout while doing a fullSync call.");
},
}),
),
);
this.messageSender.send(DO_FULL_SYNC, { forceSync, allowThrowOnError });
const result = await syncCompletedPromise;
return result.successfully;
} finally {
this.syncInProgress = false;
}
}
}

View File

@ -0,0 +1,25 @@
import { Subscription, concatMap, filter } from "rxjs";
import { MessageListener, isExternalMessage } from "@bitwarden/common/platform/messaging";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DO_FULL_SYNC } from "./foreground-sync.service";
export class SyncServiceListener {
constructor(
private readonly syncService: SyncService,
private readonly messageListener: MessageListener,
) {}
startListening(): Subscription {
return this.messageListener
.messages$(DO_FULL_SYNC)
.pipe(
filter((message) => isExternalMessage(message)),
concatMap(async ({ forceSync, allowThrowOnError }) => {
await this.syncService.fullSync(forceSync, allowThrowOnError);
}),
)
.subscribe();
}
}

View File

@ -33,6 +33,18 @@
"dist:mac": "npm run build:prod && npm run clean && npm run package:mac",
"dist:lin": "npm run build:prod && npm run clean && npm run package:lin",
"publish:npm": "npm run build:prod && npm publish --access public",
"build:bit": "webpack -c ../../bitwarden_license/bit-cli/webpack.config.js",
"build:bit:debug": "npm run build:bit && node --inspect ./build/bw.js",
"build:bit:watch": "webpack --watch -c ../../bitwarden_license/bit-cli/webpack.config.js",
"build:bit:prod": "cross-env NODE_ENV=production npm run build:bit",
"build:bit:prod:watch": "cross-env NODE_ENV=production npm run build:bit:watch",
"dist:bit": "npm run build:bit:prod && npm run clean && npm run package",
"dist:bit:win": "npm run build:bit:prod && npm run clean && npm run package:bit:win",
"dist:bit:mac": "npm run build:bit:prod && npm run clean && npm run package:bit:mac",
"dist:bit:lin": "npm run build:bit:prod && npm run clean && npm run package:bit:lin",
"package:bit:win": "pkg . --targets win-x64 --output ./dist/bit/windows/bw.exe",
"package:bit:mac": "pkg . --targets macos-x64 --output ./dist/bit/macos/bw",
"package:bit:lin": "pkg . --targets linux-x64 --output ./dist/bit/linux/bw",
"test": "jest",
"test:watch": "jest --watch",
"test:watch:all": "jest --watchAll"

View File

@ -1,788 +1,17 @@
import * as fs from "fs";
import * as path from "path";
import { program } from "commander";
import * as jsdom from "jsdom";
import { firstValueFrom } from "rxjs";
import {
InternalUserDecryptionOptionsServiceAbstraction,
AuthRequestService,
LoginStrategyService,
LoginStrategyServiceAbstraction,
PinService,
PinServiceAbstraction,
UserDecryptionOptionsService,
} from "@bitwarden/auth/common";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
import { OrganizationUserServiceImplementation } from "@bitwarden/common/admin-console/services/organization-user/organization-user.service.implementation";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { KdfConfigService as KdfConfigServiceAbstraction } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { KdfConfigService } from "@bitwarden/common/auth/services/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service";
import { TokenService } from "@bitwarden/common/auth/services/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service";
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import {
DefaultDomainSettingsService,
DomainSettingsService,
} from "@bitwarden/common/autofill/services/domain-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { ClientType } from "@bitwarden/common/enums";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
import {
BiometricStateService,
DefaultBiometricStateService,
} from "@bitwarden/common/platform/biometrics/biometric-state.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";
import { Account } from "@bitwarden/common/platform/models/domain/account";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
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 { 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";
import {
ActiveUserStateProvider,
DerivedStateProvider,
GlobalStateProvider,
SingleUserStateProvider,
StateEventRunnerService,
StateProvider,
} from "@bitwarden/common/platform/state";
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
/* eslint-enable import/no-restricted-paths */
import { AuditService } from "@bitwarden/common/services/audit.service";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { SearchService } from "@bitwarden/common/services/search.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
import {
PasswordGenerationService,
PasswordGenerationServiceAbstraction,
} from "@bitwarden/common/tools/generator/password";
import {
PasswordStrengthService,
PasswordStrengthServiceAbstraction,
} from "@bitwarden/common/tools/password-strength";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service";
import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider";
import { SendService } from "@bitwarden/common/tools/send/services/send.service";
import { UserId } from "@bitwarden/common/types/guid";
import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/services/collection.service";
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service";
import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service";
import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service";
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
import {
ImportApiService,
ImportApiServiceAbstraction,
ImportService,
ImportServiceAbstraction,
} from "@bitwarden/importer/core";
import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service";
import {
IndividualVaultExportService,
IndividualVaultExportServiceAbstraction,
OrganizationVaultExportService,
OrganizationVaultExportServiceAbstraction,
VaultExportService,
VaultExportServiceAbstraction,
} from "@bitwarden/vault-export-core";
import { registerOssPrograms } from "./register-oss-programs";
import { ServiceContainer } from "./service-container";
import { CliPlatformUtilsService } from "./platform/services/cli-platform-utils.service";
import { ConsoleLogService } from "./platform/services/console-log.service";
import { I18nService } from "./platform/services/i18n.service";
import { LowdbStorageService } from "./platform/services/lowdb-storage.service";
import { NodeApiService } from "./platform/services/node-api.service";
import { NodeEnvSecureStorageService } from "./platform/services/node-env-secure-storage.service";
import { Program } from "./program";
import { SendProgram } from "./tools/send/send.program";
import { VaultProgram } from "./vault.program";
async function main() {
const serviceContainer = new ServiceContainer();
await serviceContainer.init();
// Polyfills
global.DOMParser = new jsdom.JSDOM().window.DOMParser;
await registerOssPrograms(serviceContainer);
// eslint-disable-next-line
const packageJson = require("../package.json");
export class Main {
messagingService: MessageSender;
storageService: LowdbStorageService;
secureStorageService: NodeEnvSecureStorageService;
memoryStorageService: MemoryStorageService;
memoryStorageForStateProviders: MemoryStorageServiceForStateProviders;
i18nService: I18nService;
platformUtilsService: CliPlatformUtilsService;
cryptoService: CryptoService;
tokenService: TokenService;
appIdService: AppIdService;
apiService: NodeApiService;
environmentService: EnvironmentService;
cipherService: CipherService;
folderService: InternalFolderService;
organizationUserService: OrganizationUserService;
collectionService: CollectionService;
vaultTimeoutService: VaultTimeoutService;
masterPasswordService: InternalMasterPasswordServiceAbstraction;
vaultTimeoutSettingsService: VaultTimeoutSettingsService;
syncService: SyncService;
eventCollectionService: EventCollectionServiceAbstraction;
eventUploadService: EventUploadServiceAbstraction;
passwordGenerationService: PasswordGenerationServiceAbstraction;
passwordStrengthService: PasswordStrengthServiceAbstraction;
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction;
totpService: TotpService;
containerService: ContainerService;
auditService: AuditService;
importService: ImportServiceAbstraction;
importApiService: ImportApiServiceAbstraction;
exportService: VaultExportServiceAbstraction;
individualExportService: IndividualVaultExportServiceAbstraction;
organizationExportService: OrganizationVaultExportServiceAbstraction;
searchService: SearchService;
keyGenerationService: KeyGenerationServiceAbstraction;
cryptoFunctionService: NodeCryptoFunctionService;
encryptService: EncryptServiceImplementation;
authService: AuthService;
policyService: PolicyService;
policyApiService: PolicyApiServiceAbstraction;
program: Program;
vaultProgram: VaultProgram;
sendProgram: SendProgram;
logService: ConsoleLogService;
sendService: SendService;
sendStateProvider: SendStateProvider;
fileUploadService: FileUploadService;
cipherFileUploadService: CipherFileUploadService;
keyConnectorService: KeyConnectorService;
userVerificationService: UserVerificationService;
pinService: PinServiceAbstraction;
stateService: StateService;
autofillSettingsService: AutofillSettingsServiceAbstraction;
domainSettingsService: DomainSettingsService;
organizationService: OrganizationService;
providerService: ProviderService;
twoFactorService: TwoFactorService;
folderApiService: FolderApiService;
userVerificationApiService: UserVerificationApiService;
organizationApiService: OrganizationApiServiceAbstraction;
syncNotifierService: SyncNotifierService;
sendApiService: SendApiService;
devicesApiService: DevicesApiServiceAbstraction;
deviceTrustService: DeviceTrustServiceAbstraction;
authRequestService: AuthRequestService;
configApiService: ConfigApiServiceAbstraction;
configService: ConfigService;
accountService: AccountService;
globalStateProvider: GlobalStateProvider;
singleUserStateProvider: SingleUserStateProvider;
activeUserStateProvider: ActiveUserStateProvider;
derivedStateProvider: DerivedStateProvider;
stateProvider: StateProvider;
loginStrategyService: LoginStrategyServiceAbstraction;
avatarService: AvatarServiceAbstraction;
stateEventRunnerService: StateEventRunnerService;
biometricStateService: BiometricStateService;
billingAccountProfileStateService: BillingAccountProfileStateService;
providerApiService: ProviderApiServiceAbstraction;
userAutoUnlockKeyService: UserAutoUnlockKeyService;
kdfConfigService: KdfConfigServiceAbstraction;
constructor() {
let p = null;
const relativeDataDir = path.join(path.dirname(process.execPath), "bw-data");
if (fs.existsSync(relativeDataDir)) {
p = relativeDataDir;
} else if (process.env.BITWARDENCLI_APPDATA_DIR) {
p = path.resolve(process.env.BITWARDENCLI_APPDATA_DIR);
} else if (process.platform === "darwin") {
p = path.join(process.env.HOME, "Library/Application Support/Bitwarden CLI");
} else if (process.platform === "win32") {
p = path.join(process.env.APPDATA, "Bitwarden CLI");
} else if (process.env.XDG_CONFIG_HOME) {
p = path.join(process.env.XDG_CONFIG_HOME, "Bitwarden CLI");
} else {
p = path.join(process.env.HOME, ".config/Bitwarden CLI");
}
this.platformUtilsService = new CliPlatformUtilsService(ClientType.Cli, packageJson);
this.logService = new ConsoleLogService(
this.platformUtilsService.isDev(),
(level) => process.env.BITWARDENCLI_DEBUG !== "true" && level <= LogLevelType.Info,
);
this.cryptoFunctionService = new NodeCryptoFunctionService();
this.encryptService = new EncryptServiceImplementation(
this.cryptoFunctionService,
this.logService,
true,
);
this.storageService = new LowdbStorageService(this.logService, null, p, false, true);
this.secureStorageService = new NodeEnvSecureStorageService(
this.storageService,
this.logService,
this.encryptService,
);
this.memoryStorageService = new MemoryStorageService();
this.memoryStorageForStateProviders = new MemoryStorageServiceForStateProviders();
const storageServiceProvider = new StorageServiceProvider(
this.storageService,
this.memoryStorageForStateProviders,
);
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
const stateEventRegistrarService = new StateEventRegistrarService(
this.globalStateProvider,
storageServiceProvider,
);
this.stateEventRunnerService = new StateEventRunnerService(
this.globalStateProvider,
storageServiceProvider,
);
this.i18nService = new I18nService("en", "./locales", this.globalStateProvider);
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
storageServiceProvider,
stateEventRegistrarService,
);
this.messagingService = MessageSender.EMPTY;
this.accountService = new AccountServiceImplementation(
this.messagingService,
this.logService,
this.globalStateProvider,
);
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
this.accountService,
this.singleUserStateProvider,
);
this.derivedStateProvider = new DefaultDerivedStateProvider();
this.stateProvider = new DefaultStateProvider(
this.activeUserStateProvider,
this.singleUserStateProvider,
this.globalStateProvider,
this.derivedStateProvider,
);
this.environmentService = new DefaultEnvironmentService(
this.stateProvider,
this.accountService,
);
this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService);
this.tokenService = new TokenService(
this.singleUserStateProvider,
this.globalStateProvider,
this.platformUtilsService.supportsSecureStorage(),
this.secureStorageService,
this.keyGenerationService,
this.encryptService,
this.logService,
);
const migrationRunner = new MigrationRunner(
this.storageService,
this.logService,
new MigrationBuilderService(),
ClientType.Cli,
);
this.stateService = new StateService(
this.storageService,
this.secureStorageService,
this.memoryStorageService,
this.logService,
new StateFactory(GlobalState, Account),
this.accountService,
this.environmentService,
this.tokenService,
migrationRunner,
);
this.masterPasswordService = new MasterPasswordService(
this.stateProvider,
this.stateService,
this.keyGenerationService,
this.encryptService,
);
this.kdfConfigService = new KdfConfigService(this.stateProvider);
this.pinService = new PinService(
this.accountService,
this.cryptoFunctionService,
this.encryptService,
this.kdfConfigService,
this.keyGenerationService,
this.logService,
this.masterPasswordService,
this.stateProvider,
this.stateService,
);
this.cryptoService = new CryptoService(
this.pinService,
this.masterPasswordService,
this.keyGenerationService,
this.cryptoFunctionService,
this.encryptService,
this.platformUtilsService,
this.logService,
this.stateService,
this.accountService,
this.stateProvider,
this.kdfConfigService,
);
this.appIdService = new AppIdService(this.globalStateProvider);
const customUserAgent =
"Bitwarden_CLI/" +
this.platformUtilsService.getApplicationVersionSync() +
" (" +
this.platformUtilsService.getDeviceString().toUpperCase() +
")";
this.biometricStateService = new DefaultBiometricStateService(this.stateProvider);
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
this.organizationService = new OrganizationService(this.stateProvider);
this.policyService = new PolicyService(this.stateProvider, this.organizationService);
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
this.accountService,
this.pinService,
this.userDecryptionOptionsService,
this.cryptoService,
this.tokenService,
this.policyService,
this.biometricStateService,
this.stateProvider,
this.logService,
VaultTimeoutStringType.Never, // default vault timeout
);
this.apiService = new NodeApiService(
this.tokenService,
this.platformUtilsService,
this.environmentService,
this.appIdService,
this.vaultTimeoutSettingsService,
async (expired: boolean) => await this.logout(),
customUserAgent,
);
this.syncNotifierService = new SyncNotifierService();
this.organizationApiService = new OrganizationApiService(this.apiService, this.syncService);
this.containerService = new ContainerService(this.cryptoService, this.encryptService);
this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider);
this.fileUploadService = new FileUploadService(this.logService);
this.sendStateProvider = new SendStateProvider(this.stateProvider);
this.sendService = new SendService(
this.cryptoService,
this.i18nService,
this.keyGenerationService,
this.sendStateProvider,
this.encryptService,
);
this.cipherFileUploadService = new CipherFileUploadService(
this.apiService,
this.fileUploadService,
);
this.sendApiService = this.sendApiService = new SendApiService(
this.apiService,
this.fileUploadService,
this.sendService,
);
this.searchService = new SearchService(this.logService, this.i18nService, this.stateProvider);
this.collectionService = new CollectionService(
this.cryptoService,
this.i18nService,
this.stateProvider,
);
this.providerService = new ProviderService(this.stateProvider);
this.organizationUserService = new OrganizationUserServiceImplementation(this.apiService);
this.policyApiService = new PolicyApiService(this.policyService, this.apiService);
this.keyConnectorService = new KeyConnectorService(
this.accountService,
this.masterPasswordService,
this.cryptoService,
this.apiService,
this.tokenService,
this.logService,
this.organizationService,
this.keyGenerationService,
async (expired: boolean) => await this.logout(),
this.stateProvider,
);
this.twoFactorService = new TwoFactorService(
this.i18nService,
this.platformUtilsService,
this.globalStateProvider,
);
this.passwordStrengthService = new PasswordStrengthService();
this.passwordGenerationService = new PasswordGenerationService(
this.cryptoService,
this.policyService,
this.stateService,
);
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
this.deviceTrustService = new DeviceTrustService(
this.keyGenerationService,
this.cryptoFunctionService,
this.cryptoService,
this.encryptService,
this.appIdService,
this.devicesApiService,
this.i18nService,
this.platformUtilsService,
this.stateProvider,
this.secureStorageService,
this.userDecryptionOptionsService,
this.logService,
);
this.authRequestService = new AuthRequestService(
this.appIdService,
this.accountService,
this.masterPasswordService,
this.cryptoService,
this.apiService,
this.stateProvider,
);
this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService(
this.stateProvider,
);
this.loginStrategyService = new LoginStrategyService(
this.accountService,
this.masterPasswordService,
this.cryptoService,
this.apiService,
this.tokenService,
this.appIdService,
this.platformUtilsService,
this.messagingService,
this.logService,
this.keyConnectorService,
this.environmentService,
this.stateService,
this.twoFactorService,
this.i18nService,
this.encryptService,
this.passwordStrengthService,
this.policyService,
this.deviceTrustService,
this.authRequestService,
this.userDecryptionOptionsService,
this.globalStateProvider,
this.billingAccountProfileStateService,
this.vaultTimeoutSettingsService,
this.kdfConfigService,
);
this.authService = new AuthService(
this.accountService,
this.messagingService,
this.cryptoService,
this.apiService,
this.stateService,
this.tokenService,
);
this.configApiService = new ConfigApiService(this.apiService, this.tokenService);
this.configService = new DefaultConfigService(
this.configApiService,
this.environmentService,
this.logService,
this.stateProvider,
);
this.cipherService = new CipherService(
this.cryptoService,
this.domainSettingsService,
this.apiService,
this.i18nService,
this.searchService,
this.stateService,
this.autofillSettingsService,
this.encryptService,
this.cipherFileUploadService,
this.configService,
this.stateProvider,
);
this.folderService = new FolderService(
this.cryptoService,
this.i18nService,
this.cipherService,
this.stateProvider,
);
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
const lockedCallback = async (userId?: string) =>
await this.cryptoService.clearStoredUserKey(KeySuffixOptions.Auto);
this.userVerificationService = new UserVerificationService(
this.stateService,
this.cryptoService,
this.accountService,
this.masterPasswordService,
this.i18nService,
this.userVerificationApiService,
this.userDecryptionOptionsService,
this.pinService,
this.logService,
this.vaultTimeoutSettingsService,
this.platformUtilsService,
this.kdfConfigService,
);
this.vaultTimeoutService = new VaultTimeoutService(
this.accountService,
this.masterPasswordService,
this.cipherService,
this.folderService,
this.collectionService,
this.platformUtilsService,
this.messagingService,
this.searchService,
this.stateService,
this.authService,
this.vaultTimeoutSettingsService,
this.stateEventRunnerService,
lockedCallback,
null,
);
this.avatarService = new AvatarService(this.apiService, this.stateProvider);
this.syncService = new SyncService(
this.masterPasswordService,
this.accountService,
this.apiService,
this.domainSettingsService,
this.folderService,
this.cipherService,
this.cryptoService,
this.collectionService,
this.messagingService,
this.policyService,
this.sendService,
this.logService,
this.keyConnectorService,
this.stateService,
this.providerService,
this.folderApiService,
this.organizationService,
this.sendApiService,
this.userDecryptionOptionsService,
this.avatarService,
async (expired: boolean) => await this.logout(),
this.billingAccountProfileStateService,
this.tokenService,
this.authService,
);
this.totpService = new TotpService(this.cryptoFunctionService, this.logService);
this.importApiService = new ImportApiService(this.apiService);
this.importService = new ImportService(
this.cipherService,
this.folderService,
this.importApiService,
this.i18nService,
this.collectionService,
this.cryptoService,
this.pinService,
);
this.individualExportService = new IndividualVaultExportService(
this.folderService,
this.cipherService,
this.pinService,
this.cryptoService,
this.cryptoFunctionService,
this.kdfConfigService,
);
this.organizationExportService = new OrganizationVaultExportService(
this.cipherService,
this.apiService,
this.pinService,
this.cryptoService,
this.cryptoFunctionService,
this.collectionService,
this.kdfConfigService,
);
this.exportService = new VaultExportService(
this.individualExportService,
this.organizationExportService,
);
this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.cryptoService);
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
this.program = new Program(this);
this.vaultProgram = new VaultProgram(this);
this.sendProgram = new SendProgram(this);
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
this.eventUploadService = new EventUploadService(
this.apiService,
this.stateProvider,
this.logService,
this.authService,
);
this.eventCollectionService = new EventCollectionService(
this.cipherService,
this.stateProvider,
this.organizationService,
this.eventUploadService,
this.authService,
);
this.providerApiService = new ProviderApiService(this.apiService);
}
async run() {
await this.init();
await this.program.register();
await this.vaultProgram.register();
await this.sendProgram.register();
program.parse(process.argv);
if (process.argv.slice(2).length === 0) {
program.outputHelp();
}
}
async logout() {
this.authService.logOut(() => {
/* Do nothing */
});
const userId = (await this.stateService.getUserId()) as UserId;
await Promise.all([
this.eventUploadService.uploadEvents(userId as UserId),
this.syncService.setLastSync(new Date(0)),
this.cryptoService.clearKeys(),
this.cipherService.clear(userId),
this.folderService.clear(userId),
this.collectionService.clear(userId as UserId),
this.passwordGenerationService.clear(),
]);
await this.stateEventRunnerService.handleEvent("logout", userId);
await this.stateService.clean();
await this.accountService.clean(userId);
process.env.BW_SESSION = null;
}
private async init() {
await this.storageService.init();
await this.stateService.init();
this.containerService.attachToGlobal(global);
await this.i18nService.init();
this.twoFactorService.init();
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
if (activeAccount) {
await this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(activeAccount.id);
}
}
program.parse(process.argv);
}
const main = new Main();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// Node does not support top-level await statements until ES2022, esnext, etc which we don't use yet
// eslint-disable-next-line @typescript-eslint/no-floating-promises
main.run();
main();

View File

@ -11,9 +11,9 @@ import { ConfirmCommand } from "../admin-console/commands/confirm.command";
import { ShareCommand } from "../admin-console/commands/share.command";
import { LockCommand } from "../auth/commands/lock.command";
import { UnlockCommand } from "../auth/commands/unlock.command";
import { Main } from "../bw";
import { Response } from "../models/response";
import { FileResponse } from "../models/response/file.response";
import { ServiceContainer } from "../service-container";
import { GenerateCommand } from "../tools/generate.command";
import {
SendEditCommand,
@ -55,116 +55,119 @@ export class ServeCommand {
private sendListCommand: SendListCommand;
private sendRemovePasswordCommand: SendRemovePasswordCommand;
constructor(protected main: Main) {
constructor(protected serviceContainer: ServiceContainer) {
this.getCommand = new GetCommand(
this.main.cipherService,
this.main.folderService,
this.main.collectionService,
this.main.totpService,
this.main.auditService,
this.main.cryptoService,
this.main.stateService,
this.main.searchService,
this.main.apiService,
this.main.organizationService,
this.main.eventCollectionService,
this.main.billingAccountProfileStateService,
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.collectionService,
this.serviceContainer.totpService,
this.serviceContainer.auditService,
this.serviceContainer.cryptoService,
this.serviceContainer.stateService,
this.serviceContainer.searchService,
this.serviceContainer.apiService,
this.serviceContainer.organizationService,
this.serviceContainer.eventCollectionService,
this.serviceContainer.billingAccountProfileStateService,
);
this.listCommand = new ListCommand(
this.main.cipherService,
this.main.folderService,
this.main.collectionService,
this.main.organizationService,
this.main.searchService,
this.main.organizationUserService,
this.main.apiService,
this.main.eventCollectionService,
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.collectionService,
this.serviceContainer.organizationService,
this.serviceContainer.searchService,
this.serviceContainer.organizationUserService,
this.serviceContainer.apiService,
this.serviceContainer.eventCollectionService,
);
this.createCommand = new CreateCommand(
this.main.cipherService,
this.main.folderService,
this.main.cryptoService,
this.main.apiService,
this.main.folderApiService,
this.main.billingAccountProfileStateService,
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.cryptoService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
this.serviceContainer.billingAccountProfileStateService,
);
this.editCommand = new EditCommand(
this.main.cipherService,
this.main.folderService,
this.main.cryptoService,
this.main.apiService,
this.main.folderApiService,
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.cryptoService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
);
this.generateCommand = new GenerateCommand(
this.main.passwordGenerationService,
this.main.stateService,
this.serviceContainer.passwordGenerationService,
this.serviceContainer.stateService,
);
this.syncCommand = new SyncCommand(this.main.syncService);
this.syncCommand = new SyncCommand(this.serviceContainer.syncService);
this.statusCommand = new StatusCommand(
this.main.environmentService,
this.main.syncService,
this.main.stateService,
this.main.authService,
this.serviceContainer.environmentService,
this.serviceContainer.syncService,
this.serviceContainer.stateService,
this.serviceContainer.authService,
);
this.deleteCommand = new DeleteCommand(
this.main.cipherService,
this.main.folderService,
this.main.apiService,
this.main.folderApiService,
this.main.billingAccountProfileStateService,
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
this.serviceContainer.billingAccountProfileStateService,
);
this.confirmCommand = new ConfirmCommand(
this.main.apiService,
this.main.cryptoService,
this.main.organizationUserService,
this.serviceContainer.apiService,
this.serviceContainer.cryptoService,
this.serviceContainer.organizationUserService,
);
this.restoreCommand = new RestoreCommand(this.main.cipherService);
this.shareCommand = new ShareCommand(this.main.cipherService);
this.lockCommand = new LockCommand(this.main.vaultTimeoutService);
this.restoreCommand = new RestoreCommand(this.serviceContainer.cipherService);
this.shareCommand = new ShareCommand(this.serviceContainer.cipherService);
this.lockCommand = new LockCommand(this.serviceContainer.vaultTimeoutService);
this.unlockCommand = new UnlockCommand(
this.main.accountService,
this.main.masterPasswordService,
this.main.cryptoService,
this.main.stateService,
this.main.cryptoFunctionService,
this.main.apiService,
this.main.logService,
this.main.keyConnectorService,
this.main.environmentService,
this.main.syncService,
this.main.organizationApiService,
async () => await this.main.logout(),
this.main.kdfConfigService,
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.cryptoService,
this.serviceContainer.stateService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.apiService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
this.serviceContainer.environmentService,
this.serviceContainer.syncService,
this.serviceContainer.organizationApiService,
async () => await this.serviceContainer.logout(),
this.serviceContainer.kdfConfigService,
);
this.sendCreateCommand = new SendCreateCommand(
this.main.sendService,
this.main.environmentService,
this.main.sendApiService,
this.main.billingAccountProfileStateService,
this.serviceContainer.sendService,
this.serviceContainer.environmentService,
this.serviceContainer.sendApiService,
this.serviceContainer.billingAccountProfileStateService,
);
this.sendDeleteCommand = new SendDeleteCommand(
this.serviceContainer.sendService,
this.serviceContainer.sendApiService,
);
this.sendDeleteCommand = new SendDeleteCommand(this.main.sendService, this.main.sendApiService);
this.sendGetCommand = new SendGetCommand(
this.main.sendService,
this.main.environmentService,
this.main.searchService,
this.main.cryptoService,
this.serviceContainer.sendService,
this.serviceContainer.environmentService,
this.serviceContainer.searchService,
this.serviceContainer.cryptoService,
);
this.sendEditCommand = new SendEditCommand(
this.main.sendService,
this.serviceContainer.sendService,
this.sendGetCommand,
this.main.sendApiService,
this.main.billingAccountProfileStateService,
this.serviceContainer.sendApiService,
this.serviceContainer.billingAccountProfileStateService,
);
this.sendListCommand = new SendListCommand(
this.main.sendService,
this.main.environmentService,
this.main.searchService,
this.serviceContainer.sendService,
this.serviceContainer.environmentService,
this.serviceContainer.searchService,
);
this.sendRemovePasswordCommand = new SendRemovePasswordCommand(
this.main.sendService,
this.main.sendApiService,
this.main.environmentService,
this.serviceContainer.sendService,
this.serviceContainer.sendApiService,
this.serviceContainer.environmentService,
);
}
@ -172,7 +175,7 @@ export class ServeCommand {
const protectOrigin = !options.disableOriginProtection;
const port = options.port || 8087;
const hostname = options.hostname || "localhost";
this.main.logService.info(
this.serviceContainer.logService.info(
`Starting server on ${hostname}:${port} with ${
protectOrigin ? "origin protection" : "no origin protection"
}`,
@ -187,7 +190,7 @@ export class ServeCommand {
.use(async (ctx, next) => {
if (protectOrigin && ctx.headers.origin != undefined) {
ctx.status = 403;
this.main.logService.warning(
this.serviceContainer.logService.warning(
`Blocking request from "${
Utils.isNullOrEmpty(ctx.headers.origin)
? "(Origin header value missing)"
@ -407,7 +410,7 @@ export class ServeCommand {
.use(router.routes())
.use(router.allowedMethods())
.listen(port, hostname === "all" ? null : hostname, () => {
this.main.logService.info("Listening on " + hostname + ":" + port);
this.serviceContainer.logService.info("Listening on " + hostname + ":" + port);
});
}
@ -426,12 +429,12 @@ export class ServeCommand {
}
private async errorIfLocked(res: koa.Response) {
const authed = await this.main.stateService.getIsAuthenticated();
const authed = await this.serviceContainer.stateService.getIsAuthenticated();
if (!authed) {
this.processResponse(res, Response.error("You are not logged in."));
return true;
}
if (await this.main.cryptoService.hasUserKey()) {
if (await this.serviceContainer.cryptoService.hasUserKey()) {
return false;
}
this.processResponse(res, Response.error("Vault is locked."));

View File

@ -8,7 +8,6 @@ import { LockCommand } from "./auth/commands/lock.command";
import { LoginCommand } from "./auth/commands/login.command";
import { LogoutCommand } from "./auth/commands/logout.command";
import { UnlockCommand } from "./auth/commands/unlock.command";
import { Main } from "./bw";
import { CompletionCommand } from "./commands/completion.command";
import { ConfigCommand } from "./commands/config.command";
import { EncodeCommand } from "./commands/encode.command";
@ -20,6 +19,7 @@ import { ListResponse } from "./models/response/list.response";
import { MessageResponse } from "./models/response/message.response";
import { StringResponse } from "./models/response/string.response";
import { TemplateResponse } from "./models/response/template.response";
import { ServiceContainer } from "./service-container";
import { GenerateCommand } from "./tools/generate.command";
import { CliUtils } from "./utils";
import { SyncCommand } from "./vault/sync.command";
@ -27,7 +27,7 @@ import { SyncCommand } from "./vault/sync.command";
const writeLn = CliUtils.writeLn;
export class Program {
constructor(protected main: Main) {}
constructor(protected serviceContainer: ServiceContainer) {}
async register() {
program
@ -38,7 +38,10 @@ export class Program {
.option("--quiet", "Don't return anything to stdout.")
.option("--nointeraction", "Do not prompt for interactive user input.")
.option("--session <session>", "Pass session key instead of reading from env.")
.version(await this.main.platformUtilsService.getApplicationVersion(), "-v, --version");
.version(
await this.serviceContainer.platformUtilsService.getApplicationVersion(),
"-v, --version",
);
program.on("option:pretty", () => {
process.env.BW_PRETTY = "true";
@ -68,9 +71,11 @@ export class Program {
process.env.BW_SESSION = key;
// once we have the session key, we can set the user key in memory
const activeAccount = await firstValueFrom(this.main.accountService.activeAccount$);
const activeAccount = await firstValueFrom(
this.serviceContainer.accountService.activeAccount$,
);
if (activeAccount) {
await this.main.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(
await this.serviceContainer.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(
activeAccount.id,
);
}
@ -122,7 +127,7 @@ export class Program {
"Path to a file containing your password as its first line",
)
.option("--check", "Check login status.", async () => {
const authed = await this.main.stateService.getIsAuthenticated();
const authed = await this.serviceContainer.stateService.getIsAuthenticated();
if (authed) {
const res = new MessageResponse("You are logged in!", null);
this.processResponse(Response.success(res), true);
@ -148,24 +153,24 @@ export class Program {
if (!options.check) {
await this.exitIfAuthed();
const command = new LoginCommand(
this.main.loginStrategyService,
this.main.authService,
this.main.apiService,
this.main.cryptoFunctionService,
this.main.environmentService,
this.main.passwordGenerationService,
this.main.passwordStrengthService,
this.main.platformUtilsService,
this.main.stateService,
this.main.cryptoService,
this.main.policyService,
this.main.twoFactorService,
this.main.syncService,
this.main.keyConnectorService,
this.main.policyApiService,
this.main.organizationService,
async () => await this.main.logout(),
this.main.kdfConfigService,
this.serviceContainer.loginStrategyService,
this.serviceContainer.authService,
this.serviceContainer.apiService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.environmentService,
this.serviceContainer.passwordGenerationService,
this.serviceContainer.passwordStrengthService,
this.serviceContainer.platformUtilsService,
this.serviceContainer.stateService,
this.serviceContainer.cryptoService,
this.serviceContainer.policyService,
this.serviceContainer.twoFactorService,
this.serviceContainer.syncService,
this.serviceContainer.keyConnectorService,
this.serviceContainer.policyApiService,
this.serviceContainer.organizationService,
async () => await this.serviceContainer.logout(),
this.serviceContainer.kdfConfigService,
);
const response = await command.run(email, password, options);
this.processResponse(response, true);
@ -184,9 +189,9 @@ export class Program {
.action(async (cmd) => {
await this.exitIfNotAuthed();
const command = new LogoutCommand(
this.main.authService,
this.main.i18nService,
async () => await this.main.logout(),
this.serviceContainer.authService,
this.serviceContainer.i18nService,
async () => await this.serviceContainer.logout(),
);
const response = await command.run();
this.processResponse(response);
@ -204,11 +209,11 @@ export class Program {
.action(async (cmd) => {
await this.exitIfNotAuthed();
if (await this.main.keyConnectorService.getUsesKeyConnector()) {
if (await this.serviceContainer.keyConnectorService.getUsesKeyConnector()) {
const logoutCommand = new LogoutCommand(
this.main.authService,
this.main.i18nService,
async () => await this.main.logout(),
this.serviceContainer.authService,
this.serviceContainer.i18nService,
async () => await this.serviceContainer.logout(),
);
await logoutCommand.run();
this.processResponse(
@ -221,7 +226,7 @@ export class Program {
return;
}
const command = new LockCommand(this.main.vaultTimeoutService);
const command = new LockCommand(this.serviceContainer.vaultTimeoutService);
const response = await command.run();
this.processResponse(response);
});
@ -246,7 +251,7 @@ export class Program {
.option("--check", "Check lock status.", async () => {
await this.exitIfNotAuthed();
const authStatus = await this.main.authService.getAuthStatus();
const authStatus = await this.serviceContainer.authService.getAuthStatus();
if (authStatus === AuthenticationStatus.Unlocked) {
const res = new MessageResponse("Vault is unlocked!", null);
this.processResponse(Response.success(res), true);
@ -263,19 +268,19 @@ export class Program {
if (!cmd.check) {
await this.exitIfNotAuthed();
const command = new UnlockCommand(
this.main.accountService,
this.main.masterPasswordService,
this.main.cryptoService,
this.main.stateService,
this.main.cryptoFunctionService,
this.main.apiService,
this.main.logService,
this.main.keyConnectorService,
this.main.environmentService,
this.main.syncService,
this.main.organizationApiService,
async () => await this.main.logout(),
this.main.kdfConfigService,
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.cryptoService,
this.serviceContainer.stateService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.apiService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
this.serviceContainer.environmentService,
this.serviceContainer.syncService,
this.serviceContainer.organizationApiService,
async () => await this.serviceContainer.logout(),
this.serviceContainer.kdfConfigService,
);
const response = await command.run(password, cmd);
this.processResponse(response);
@ -297,7 +302,7 @@ export class Program {
})
.action(async (cmd) => {
await this.exitIfNotAuthed();
const command = new SyncCommand(this.main.syncService);
const command = new SyncCommand(this.serviceContainer.syncService);
const response = await command.run(cmd);
this.processResponse(response);
});
@ -340,8 +345,8 @@ export class Program {
})
.action(async (options) => {
const command = new GenerateCommand(
this.main.passwordGenerationService,
this.main.stateService,
this.serviceContainer.passwordGenerationService,
this.serviceContainer.stateService,
);
const response = await command.run(options);
this.processResponse(response);
@ -401,7 +406,7 @@ export class Program {
writeLn("", true);
})
.action(async (setting, value, options) => {
const command = new ConfigCommand(this.main.environmentService);
const command = new ConfigCommand(this.serviceContainer.environmentService);
const response = await command.run(setting, value, options);
this.processResponse(response);
});
@ -423,7 +428,7 @@ export class Program {
writeLn("", true);
})
.action(async () => {
const command = new UpdateCommand(this.main.platformUtilsService);
const command = new UpdateCommand(this.serviceContainer.platformUtilsService);
const response = await command.run();
this.processResponse(response);
});
@ -474,10 +479,10 @@ export class Program {
})
.action(async () => {
const command = new StatusCommand(
this.main.environmentService,
this.main.syncService,
this.main.stateService,
this.main.authService,
this.serviceContainer.environmentService,
this.serviceContainer.syncService,
this.serviceContainer.stateService,
this.serviceContainer.authService,
);
const response = await command.run();
this.processResponse(response);
@ -508,7 +513,7 @@ export class Program {
})
.action(async (cmd) => {
await this.exitIfNotAuthed();
const command = new ServeCommand(this.main);
const command = new ServeCommand(this.serviceContainer);
await command.run(cmd);
});
}
@ -598,15 +603,15 @@ export class Program {
}
private async exitIfAuthed() {
const authed = await this.main.stateService.getIsAuthenticated();
const authed = await this.serviceContainer.stateService.getIsAuthenticated();
if (authed) {
const email = await this.main.stateService.getEmail();
const email = await this.serviceContainer.stateService.getEmail();
this.processResponse(Response.error("You are already logged in as " + email + "."), true);
}
}
private async exitIfNotAuthed() {
const authed = await this.main.stateService.getIsAuthenticated();
const authed = await this.serviceContainer.stateService.getIsAuthenticated();
if (!authed) {
this.processResponse(Response.error("You are not logged in."), true);
}
@ -614,11 +619,11 @@ export class Program {
protected async exitIfLocked() {
await this.exitIfNotAuthed();
if (await this.main.cryptoService.hasUserKey()) {
if (await this.serviceContainer.cryptoService.hasUserKey()) {
return;
} else if (process.env.BW_NOINTERACTION !== "true") {
// must unlock
if (await this.main.keyConnectorService.getUsesKeyConnector()) {
if (await this.serviceContainer.keyConnectorService.getUsesKeyConnector()) {
const response = Response.error(
"Your vault is locked. You must unlock your vault using your session key.\n" +
"If you do not have your session key, you can get a new one by logging out and logging in again.",
@ -626,19 +631,19 @@ export class Program {
this.processResponse(response, true);
} else {
const command = new UnlockCommand(
this.main.accountService,
this.main.masterPasswordService,
this.main.cryptoService,
this.main.stateService,
this.main.cryptoFunctionService,
this.main.apiService,
this.main.logService,
this.main.keyConnectorService,
this.main.environmentService,
this.main.syncService,
this.main.organizationApiService,
this.main.logout,
this.main.kdfConfigService,
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.cryptoService,
this.serviceContainer.stateService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.apiService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
this.serviceContainer.environmentService,
this.serviceContainer.syncService,
this.serviceContainer.organizationApiService,
this.serviceContainer.logout,
this.serviceContainer.kdfConfigService,
);
const response = await command.run(null, null);
if (!response.success) {

View File

@ -0,0 +1,22 @@
import { Program } from "./program";
import { ServiceContainer } from "./service-container";
import { SendProgram } from "./tools/send/send.program";
import { VaultProgram } from "./vault.program";
/**
* All OSS licensed programs should be registered here.
* @example
* const myProgram = new myProgram(serviceContainer);
* myProgram.register();
* @param serviceContainer A class that instantiates services and makes them available for dependency injection
*/
export async function registerOssPrograms(serviceContainer: ServiceContainer) {
const program = new Program(serviceContainer);
await program.register();
const vaultProgram = new VaultProgram(serviceContainer);
await vaultProgram.register();
const sendProgram = new SendProgram(serviceContainer);
await sendProgram.register();
}

View File

@ -0,0 +1,7 @@
import { ServiceContainer } from "./service-container";
describe("ServiceContainer", () => {
it("instantiates", async () => {
expect(() => new ServiceContainer()).not.toThrow();
});
});

View File

@ -0,0 +1,772 @@
import * as fs from "fs";
import * as path from "path";
import * as jsdom from "jsdom";
import { firstValueFrom } from "rxjs";
import {
InternalUserDecryptionOptionsServiceAbstraction,
AuthRequestService,
LoginStrategyService,
LoginStrategyServiceAbstraction,
PinService,
PinServiceAbstraction,
UserDecryptionOptionsService,
} from "@bitwarden/auth/common";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
import { OrganizationUserServiceImplementation } from "@bitwarden/common/admin-console/services/organization-user/organization-user.service.implementation";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { KdfConfigService as KdfConfigServiceAbstraction } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DeviceTrustService } from "@bitwarden/common/auth/services/device-trust.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { KdfConfigService } from "@bitwarden/common/auth/services/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service";
import { TokenService } from "@bitwarden/common/auth/services/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service";
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import {
DefaultDomainSettingsService,
DomainSettingsService,
} from "@bitwarden/common/autofill/services/domain-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { ClientType } from "@bitwarden/common/enums";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
import {
BiometricStateService,
DefaultBiometricStateService,
} from "@bitwarden/common/platform/biometrics/biometric-state.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";
import { Account } from "@bitwarden/common/platform/models/domain/account";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation";
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
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 { 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";
import {
ActiveUserStateProvider,
DerivedStateProvider,
GlobalStateProvider,
SingleUserStateProvider,
StateEventRunnerService,
StateProvider,
} from "@bitwarden/common/platform/state";
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
/* eslint-enable import/no-restricted-paths */
import { AuditService } from "@bitwarden/common/services/audit.service";
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { SearchService } from "@bitwarden/common/services/search.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
import {
PasswordGenerationService,
PasswordGenerationServiceAbstraction,
} from "@bitwarden/common/tools/generator/password";
import {
PasswordStrengthService,
PasswordStrengthServiceAbstraction,
} from "@bitwarden/common/tools/password-strength";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service";
import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider";
import { SendService } from "@bitwarden/common/tools/send/services/send.service";
import { UserId } from "@bitwarden/common/types/guid";
import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/services/collection.service";
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service";
import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service";
import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service";
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
import {
ImportApiService,
ImportApiServiceAbstraction,
ImportService,
ImportServiceAbstraction,
} from "@bitwarden/importer/core";
import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service";
import {
IndividualVaultExportService,
IndividualVaultExportServiceAbstraction,
OrganizationVaultExportService,
OrganizationVaultExportServiceAbstraction,
VaultExportService,
VaultExportServiceAbstraction,
} from "@bitwarden/vault-export-core";
import { CliPlatformUtilsService } from "./platform/services/cli-platform-utils.service";
import { ConsoleLogService } from "./platform/services/console-log.service";
import { I18nService } from "./platform/services/i18n.service";
import { LowdbStorageService } from "./platform/services/lowdb-storage.service";
import { NodeApiService } from "./platform/services/node-api.service";
import { NodeEnvSecureStorageService } from "./platform/services/node-env-secure-storage.service";
// Polyfills
global.DOMParser = new jsdom.JSDOM().window.DOMParser;
// eslint-disable-next-line
const packageJson = require("../package.json");
/**
* Instantiates services and makes them available for dependency injection.
* Any Bitwarden-licensed services should be registered here.
*/
export class ServiceContainer {
private inited = false;
messagingService: MessageSender;
storageService: LowdbStorageService;
secureStorageService: NodeEnvSecureStorageService;
memoryStorageService: MemoryStorageService;
memoryStorageForStateProviders: MemoryStorageServiceForStateProviders;
i18nService: I18nService;
platformUtilsService: CliPlatformUtilsService;
cryptoService: CryptoService;
tokenService: TokenService;
appIdService: AppIdService;
apiService: NodeApiService;
environmentService: EnvironmentService;
cipherService: CipherService;
folderService: InternalFolderService;
organizationUserService: OrganizationUserService;
collectionService: CollectionService;
vaultTimeoutService: VaultTimeoutService;
masterPasswordService: InternalMasterPasswordServiceAbstraction;
vaultTimeoutSettingsService: VaultTimeoutSettingsService;
syncService: SyncService;
eventCollectionService: EventCollectionServiceAbstraction;
eventUploadService: EventUploadServiceAbstraction;
passwordGenerationService: PasswordGenerationServiceAbstraction;
passwordStrengthService: PasswordStrengthServiceAbstraction;
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction;
totpService: TotpService;
containerService: ContainerService;
auditService: AuditService;
importService: ImportServiceAbstraction;
importApiService: ImportApiServiceAbstraction;
exportService: VaultExportServiceAbstraction;
individualExportService: IndividualVaultExportServiceAbstraction;
organizationExportService: OrganizationVaultExportServiceAbstraction;
searchService: SearchService;
keyGenerationService: KeyGenerationServiceAbstraction;
cryptoFunctionService: NodeCryptoFunctionService;
encryptService: EncryptServiceImplementation;
authService: AuthService;
policyService: PolicyService;
policyApiService: PolicyApiServiceAbstraction;
logService: ConsoleLogService;
sendService: SendService;
sendStateProvider: SendStateProvider;
fileUploadService: FileUploadService;
cipherFileUploadService: CipherFileUploadService;
keyConnectorService: KeyConnectorService;
userVerificationService: UserVerificationService;
pinService: PinServiceAbstraction;
stateService: StateService;
autofillSettingsService: AutofillSettingsServiceAbstraction;
domainSettingsService: DomainSettingsService;
organizationService: OrganizationService;
providerService: ProviderService;
twoFactorService: TwoFactorService;
folderApiService: FolderApiService;
userVerificationApiService: UserVerificationApiService;
organizationApiService: OrganizationApiServiceAbstraction;
syncNotifierService: SyncNotifierService;
sendApiService: SendApiService;
devicesApiService: DevicesApiServiceAbstraction;
deviceTrustService: DeviceTrustServiceAbstraction;
authRequestService: AuthRequestService;
configApiService: ConfigApiServiceAbstraction;
configService: ConfigService;
accountService: AccountService;
globalStateProvider: GlobalStateProvider;
singleUserStateProvider: SingleUserStateProvider;
activeUserStateProvider: ActiveUserStateProvider;
derivedStateProvider: DerivedStateProvider;
stateProvider: StateProvider;
loginStrategyService: LoginStrategyServiceAbstraction;
avatarService: AvatarServiceAbstraction;
stateEventRunnerService: StateEventRunnerService;
biometricStateService: BiometricStateService;
billingAccountProfileStateService: BillingAccountProfileStateService;
providerApiService: ProviderApiServiceAbstraction;
userAutoUnlockKeyService: UserAutoUnlockKeyService;
kdfConfigService: KdfConfigServiceAbstraction;
constructor() {
let p = null;
const relativeDataDir = path.join(path.dirname(process.execPath), "bw-data");
if (fs.existsSync(relativeDataDir)) {
p = relativeDataDir;
} else if (process.env.BITWARDENCLI_APPDATA_DIR) {
p = path.resolve(process.env.BITWARDENCLI_APPDATA_DIR);
} else if (process.platform === "darwin") {
p = path.join(process.env.HOME, "Library/Application Support/Bitwarden CLI");
} else if (process.platform === "win32") {
p = path.join(process.env.APPDATA, "Bitwarden CLI");
} else if (process.env.XDG_CONFIG_HOME) {
p = path.join(process.env.XDG_CONFIG_HOME, "Bitwarden CLI");
} else {
p = path.join(process.env.HOME, ".config/Bitwarden CLI");
}
this.platformUtilsService = new CliPlatformUtilsService(ClientType.Cli, packageJson);
this.logService = new ConsoleLogService(
this.platformUtilsService.isDev(),
(level) => process.env.BITWARDENCLI_DEBUG !== "true" && level <= LogLevelType.Info,
);
this.cryptoFunctionService = new NodeCryptoFunctionService();
this.encryptService = new EncryptServiceImplementation(
this.cryptoFunctionService,
this.logService,
true,
);
this.storageService = new LowdbStorageService(this.logService, null, p, false, true);
this.secureStorageService = new NodeEnvSecureStorageService(
this.storageService,
this.logService,
this.encryptService,
);
this.memoryStorageService = new MemoryStorageService();
this.memoryStorageForStateProviders = new MemoryStorageServiceForStateProviders();
const storageServiceProvider = new StorageServiceProvider(
this.storageService,
this.memoryStorageForStateProviders,
);
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
const stateEventRegistrarService = new StateEventRegistrarService(
this.globalStateProvider,
storageServiceProvider,
);
this.stateEventRunnerService = new StateEventRunnerService(
this.globalStateProvider,
storageServiceProvider,
);
this.i18nService = new I18nService("en", "./locales", this.globalStateProvider);
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
storageServiceProvider,
stateEventRegistrarService,
);
this.messagingService = MessageSender.EMPTY;
this.accountService = new AccountServiceImplementation(
this.messagingService,
this.logService,
this.globalStateProvider,
);
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
this.accountService,
this.singleUserStateProvider,
);
this.derivedStateProvider = new DefaultDerivedStateProvider();
this.stateProvider = new DefaultStateProvider(
this.activeUserStateProvider,
this.singleUserStateProvider,
this.globalStateProvider,
this.derivedStateProvider,
);
this.environmentService = new DefaultEnvironmentService(
this.stateProvider,
this.accountService,
);
this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService);
this.tokenService = new TokenService(
this.singleUserStateProvider,
this.globalStateProvider,
this.platformUtilsService.supportsSecureStorage(),
this.secureStorageService,
this.keyGenerationService,
this.encryptService,
this.logService,
);
const migrationRunner = new MigrationRunner(
this.storageService,
this.logService,
new MigrationBuilderService(),
ClientType.Cli,
);
this.stateService = new StateService(
this.storageService,
this.secureStorageService,
this.memoryStorageService,
this.logService,
new StateFactory(GlobalState, Account),
this.accountService,
this.environmentService,
this.tokenService,
migrationRunner,
);
this.masterPasswordService = new MasterPasswordService(
this.stateProvider,
this.stateService,
this.keyGenerationService,
this.encryptService,
);
this.kdfConfigService = new KdfConfigService(this.stateProvider);
this.pinService = new PinService(
this.accountService,
this.cryptoFunctionService,
this.encryptService,
this.kdfConfigService,
this.keyGenerationService,
this.logService,
this.masterPasswordService,
this.stateProvider,
this.stateService,
);
this.cryptoService = new CryptoService(
this.pinService,
this.masterPasswordService,
this.keyGenerationService,
this.cryptoFunctionService,
this.encryptService,
this.platformUtilsService,
this.logService,
this.stateService,
this.accountService,
this.stateProvider,
this.kdfConfigService,
);
this.appIdService = new AppIdService(this.globalStateProvider);
const customUserAgent =
"Bitwarden_CLI/" +
this.platformUtilsService.getApplicationVersionSync() +
" (" +
this.platformUtilsService.getDeviceString().toUpperCase() +
")";
this.biometricStateService = new DefaultBiometricStateService(this.stateProvider);
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
this.organizationService = new OrganizationService(this.stateProvider);
this.policyService = new PolicyService(this.stateProvider, this.organizationService);
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
this.accountService,
this.pinService,
this.userDecryptionOptionsService,
this.cryptoService,
this.tokenService,
this.policyService,
this.biometricStateService,
this.stateProvider,
this.logService,
VaultTimeoutStringType.Never, // default vault timeout
);
this.apiService = new NodeApiService(
this.tokenService,
this.platformUtilsService,
this.environmentService,
this.appIdService,
this.vaultTimeoutSettingsService,
async (expired: boolean) => await this.logout(),
customUserAgent,
);
this.syncNotifierService = new SyncNotifierService();
this.organizationApiService = new OrganizationApiService(this.apiService, this.syncService);
this.containerService = new ContainerService(this.cryptoService, this.encryptService);
this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider);
this.fileUploadService = new FileUploadService(this.logService);
this.sendStateProvider = new SendStateProvider(this.stateProvider);
this.sendService = new SendService(
this.cryptoService,
this.i18nService,
this.keyGenerationService,
this.sendStateProvider,
this.encryptService,
);
this.cipherFileUploadService = new CipherFileUploadService(
this.apiService,
this.fileUploadService,
);
this.sendApiService = this.sendApiService = new SendApiService(
this.apiService,
this.fileUploadService,
this.sendService,
);
this.searchService = new SearchService(this.logService, this.i18nService, this.stateProvider);
this.collectionService = new CollectionService(
this.cryptoService,
this.i18nService,
this.stateProvider,
);
this.providerService = new ProviderService(this.stateProvider);
this.organizationUserService = new OrganizationUserServiceImplementation(this.apiService);
this.policyApiService = new PolicyApiService(this.policyService, this.apiService);
this.keyConnectorService = new KeyConnectorService(
this.accountService,
this.masterPasswordService,
this.cryptoService,
this.apiService,
this.tokenService,
this.logService,
this.organizationService,
this.keyGenerationService,
async (expired: boolean) => await this.logout(),
this.stateProvider,
);
this.twoFactorService = new TwoFactorService(
this.i18nService,
this.platformUtilsService,
this.globalStateProvider,
);
this.passwordStrengthService = new PasswordStrengthService();
this.passwordGenerationService = new PasswordGenerationService(
this.cryptoService,
this.policyService,
this.stateService,
);
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
this.deviceTrustService = new DeviceTrustService(
this.keyGenerationService,
this.cryptoFunctionService,
this.cryptoService,
this.encryptService,
this.appIdService,
this.devicesApiService,
this.i18nService,
this.platformUtilsService,
this.stateProvider,
this.secureStorageService,
this.userDecryptionOptionsService,
this.logService,
);
this.authRequestService = new AuthRequestService(
this.appIdService,
this.accountService,
this.masterPasswordService,
this.cryptoService,
this.apiService,
this.stateProvider,
);
this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService(
this.stateProvider,
);
this.loginStrategyService = new LoginStrategyService(
this.accountService,
this.masterPasswordService,
this.cryptoService,
this.apiService,
this.tokenService,
this.appIdService,
this.platformUtilsService,
this.messagingService,
this.logService,
this.keyConnectorService,
this.environmentService,
this.stateService,
this.twoFactorService,
this.i18nService,
this.encryptService,
this.passwordStrengthService,
this.policyService,
this.deviceTrustService,
this.authRequestService,
this.userDecryptionOptionsService,
this.globalStateProvider,
this.billingAccountProfileStateService,
this.vaultTimeoutSettingsService,
this.kdfConfigService,
);
this.authService = new AuthService(
this.accountService,
this.messagingService,
this.cryptoService,
this.apiService,
this.stateService,
this.tokenService,
);
this.configApiService = new ConfigApiService(this.apiService, this.tokenService);
this.configService = new DefaultConfigService(
this.configApiService,
this.environmentService,
this.logService,
this.stateProvider,
);
this.cipherService = new CipherService(
this.cryptoService,
this.domainSettingsService,
this.apiService,
this.i18nService,
this.searchService,
this.stateService,
this.autofillSettingsService,
this.encryptService,
this.cipherFileUploadService,
this.configService,
this.stateProvider,
);
this.folderService = new FolderService(
this.cryptoService,
this.i18nService,
this.cipherService,
this.stateProvider,
);
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
const lockedCallback = async (userId?: string) =>
await this.cryptoService.clearStoredUserKey(KeySuffixOptions.Auto);
this.userVerificationService = new UserVerificationService(
this.stateService,
this.cryptoService,
this.accountService,
this.masterPasswordService,
this.i18nService,
this.userVerificationApiService,
this.userDecryptionOptionsService,
this.pinService,
this.logService,
this.vaultTimeoutSettingsService,
this.platformUtilsService,
this.kdfConfigService,
);
this.vaultTimeoutService = new VaultTimeoutService(
this.accountService,
this.masterPasswordService,
this.cipherService,
this.folderService,
this.collectionService,
this.platformUtilsService,
this.messagingService,
this.searchService,
this.stateService,
this.authService,
this.vaultTimeoutSettingsService,
this.stateEventRunnerService,
lockedCallback,
null,
);
this.avatarService = new AvatarService(this.apiService, this.stateProvider);
this.syncService = new SyncService(
this.masterPasswordService,
this.accountService,
this.apiService,
this.domainSettingsService,
this.folderService,
this.cipherService,
this.cryptoService,
this.collectionService,
this.messagingService,
this.policyService,
this.sendService,
this.logService,
this.keyConnectorService,
this.stateService,
this.providerService,
this.folderApiService,
this.organizationService,
this.sendApiService,
this.userDecryptionOptionsService,
this.avatarService,
async (expired: boolean) => await this.logout(),
this.billingAccountProfileStateService,
this.tokenService,
this.authService,
);
this.totpService = new TotpService(this.cryptoFunctionService, this.logService);
this.importApiService = new ImportApiService(this.apiService);
this.importService = new ImportService(
this.cipherService,
this.folderService,
this.importApiService,
this.i18nService,
this.collectionService,
this.cryptoService,
this.pinService,
);
this.individualExportService = new IndividualVaultExportService(
this.folderService,
this.cipherService,
this.pinService,
this.cryptoService,
this.cryptoFunctionService,
this.kdfConfigService,
);
this.organizationExportService = new OrganizationVaultExportService(
this.cipherService,
this.apiService,
this.pinService,
this.cryptoService,
this.cryptoFunctionService,
this.collectionService,
this.kdfConfigService,
);
this.exportService = new VaultExportService(
this.individualExportService,
this.organizationExportService,
);
this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.cryptoService);
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
this.eventUploadService = new EventUploadService(
this.apiService,
this.stateProvider,
this.logService,
this.authService,
);
this.eventCollectionService = new EventCollectionService(
this.cipherService,
this.stateProvider,
this.organizationService,
this.eventUploadService,
this.authService,
);
this.providerApiService = new ProviderApiService(this.apiService);
}
async logout() {
this.authService.logOut(() => {
/* Do nothing */
});
const userId = (await this.stateService.getUserId()) as UserId;
await Promise.all([
this.eventUploadService.uploadEvents(userId as UserId),
this.syncService.setLastSync(new Date(0)),
this.cryptoService.clearKeys(),
this.cipherService.clear(userId),
this.folderService.clear(userId),
this.collectionService.clear(userId as UserId),
this.passwordGenerationService.clear(),
]);
await this.stateEventRunnerService.handleEvent("logout", userId);
await this.stateService.clean();
await this.accountService.clean(userId);
process.env.BW_SESSION = null;
}
async init() {
if (this.inited) {
this.logService.warning("ServiceContainer.init called more than once");
return;
}
await this.storageService.init();
await this.stateService.init();
this.containerService.attachToGlobal(global);
await this.i18nService.init();
this.twoFactorService.init();
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
if (activeAccount) {
await this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(activeAccount.id);
}
this.inited = true;
}
}

View File

@ -7,7 +7,6 @@ import { program, Command, OptionValues } from "commander";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { Main } from "../../bw";
import { GetCommand } from "../../commands/get.command";
import { Response } from "../../models/response";
import { Program } from "../../program";
@ -29,10 +28,6 @@ import { SendResponse } from "./models/send.response";
const writeLn = CliUtils.writeLn;
export class SendProgram extends Program {
constructor(main: Main) {
super(main);
}
async register() {
program.addCommand(this.sendCommand());
// receive is accessible both at `bw receive` and `bw send receive`
@ -105,12 +100,12 @@ export class SendProgram extends Program {
})
.action(async (url: string, options: OptionValues) => {
const cmd = new SendReceiveCommand(
this.main.apiService,
this.main.cryptoService,
this.main.cryptoFunctionService,
this.main.platformUtilsService,
this.main.environmentService,
this.main.sendApiService,
this.serviceContainer.apiService,
this.serviceContainer.cryptoService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.platformUtilsService,
this.serviceContainer.environmentService,
this.serviceContainer.sendApiService,
);
const response = await cmd.run(url, options);
this.processResponse(response);
@ -127,9 +122,9 @@ export class SendProgram extends Program {
.action(async (options: OptionValues) => {
await this.exitIfLocked();
const cmd = new SendListCommand(
this.main.sendService,
this.main.environmentService,
this.main.searchService,
this.serviceContainer.sendService,
this.serviceContainer.environmentService,
this.serviceContainer.searchService,
);
const response = await cmd.run(options);
this.processResponse(response);
@ -142,18 +137,18 @@ export class SendProgram extends Program {
.description("Get json templates for send objects")
.action(async (object) => {
const cmd = new GetCommand(
this.main.cipherService,
this.main.folderService,
this.main.collectionService,
this.main.totpService,
this.main.auditService,
this.main.cryptoService,
this.main.stateService,
this.main.searchService,
this.main.apiService,
this.main.organizationService,
this.main.eventCollectionService,
this.main.billingAccountProfileStateService,
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.collectionService,
this.serviceContainer.totpService,
this.serviceContainer.auditService,
this.serviceContainer.cryptoService,
this.serviceContainer.stateService,
this.serviceContainer.searchService,
this.serviceContainer.apiService,
this.serviceContainer.organizationService,
this.serviceContainer.eventCollectionService,
this.serviceContainer.billingAccountProfileStateService,
);
const response = await cmd.run("template", object, null);
this.processResponse(response);
@ -188,10 +183,10 @@ export class SendProgram extends Program {
.action(async (id: string, options: OptionValues) => {
await this.exitIfLocked();
const cmd = new SendGetCommand(
this.main.sendService,
this.main.environmentService,
this.main.searchService,
this.main.cryptoService,
this.serviceContainer.sendService,
this.serviceContainer.environmentService,
this.serviceContainer.searchService,
this.serviceContainer.cryptoService,
);
const response = await cmd.run(id, options);
this.processResponse(response);
@ -247,16 +242,16 @@ export class SendProgram extends Program {
.action(async (encodedJson: string, options: OptionValues) => {
await this.exitIfLocked();
const getCmd = new SendGetCommand(
this.main.sendService,
this.main.environmentService,
this.main.searchService,
this.main.cryptoService,
this.serviceContainer.sendService,
this.serviceContainer.environmentService,
this.serviceContainer.searchService,
this.serviceContainer.cryptoService,
);
const cmd = new SendEditCommand(
this.main.sendService,
this.serviceContainer.sendService,
getCmd,
this.main.sendApiService,
this.main.billingAccountProfileStateService,
this.serviceContainer.sendApiService,
this.serviceContainer.billingAccountProfileStateService,
);
const response = await cmd.run(encodedJson, options);
this.processResponse(response);
@ -269,7 +264,10 @@ export class SendProgram extends Program {
.description("delete a Send")
.action(async (id: string) => {
await this.exitIfLocked();
const cmd = new SendDeleteCommand(this.main.sendService, this.main.sendApiService);
const cmd = new SendDeleteCommand(
this.serviceContainer.sendService,
this.serviceContainer.sendApiService,
);
const response = await cmd.run(id);
this.processResponse(response);
});
@ -282,9 +280,9 @@ export class SendProgram extends Program {
.action(async (id: string) => {
await this.exitIfLocked();
const cmd = new SendRemovePasswordCommand(
this.main.sendService,
this.main.sendApiService,
this.main.environmentService,
this.serviceContainer.sendService,
this.serviceContainer.sendApiService,
this.serviceContainer.environmentService,
);
const response = await cmd.run(id);
this.processResponse(response);
@ -323,10 +321,10 @@ export class SendProgram extends Program {
private async runCreate(encodedJson: string, options: OptionValues) {
await this.exitIfLocked();
const cmd = new SendCreateCommand(
this.main.sendService,
this.main.environmentService,
this.main.sendApiService,
this.main.billingAccountProfileStateService,
this.serviceContainer.sendService,
this.serviceContainer.environmentService,
this.serviceContainer.sendApiService,
this.serviceContainer.billingAccountProfileStateService,
);
return await cmd.run(encodedJson, options);
}

View File

@ -2,7 +2,6 @@ import { program, Command } from "commander";
import { ConfirmCommand } from "./admin-console/commands/confirm.command";
import { ShareCommand } from "./admin-console/commands/share.command";
import { Main } from "./bw";
import { EditCommand } from "./commands/edit.command";
import { GetCommand } from "./commands/get.command";
import { ListCommand } from "./commands/list.command";
@ -18,10 +17,6 @@ import { DeleteCommand } from "./vault/delete.command";
const writeLn = CliUtils.writeLn;
export class VaultProgram extends Program {
constructor(protected main: Main) {
super(main);
}
async register() {
program
.addCommand(this.listCommand())
@ -108,14 +103,14 @@ export class VaultProgram extends Program {
await this.exitIfLocked();
const command = new ListCommand(
this.main.cipherService,
this.main.folderService,
this.main.collectionService,
this.main.organizationService,
this.main.searchService,
this.main.organizationUserService,
this.main.apiService,
this.main.eventCollectionService,
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.collectionService,
this.serviceContainer.organizationService,
this.serviceContainer.searchService,
this.serviceContainer.organizationUserService,
this.serviceContainer.apiService,
this.serviceContainer.eventCollectionService,
);
const response = await command.run(object, cmd);
@ -177,18 +172,18 @@ export class VaultProgram extends Program {
await this.exitIfLocked();
const command = new GetCommand(
this.main.cipherService,
this.main.folderService,
this.main.collectionService,
this.main.totpService,
this.main.auditService,
this.main.cryptoService,
this.main.stateService,
this.main.searchService,
this.main.apiService,
this.main.organizationService,
this.main.eventCollectionService,
this.main.billingAccountProfileStateService,
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.collectionService,
this.serviceContainer.totpService,
this.serviceContainer.auditService,
this.serviceContainer.cryptoService,
this.serviceContainer.stateService,
this.serviceContainer.searchService,
this.serviceContainer.apiService,
this.serviceContainer.organizationService,
this.serviceContainer.eventCollectionService,
this.serviceContainer.billingAccountProfileStateService,
);
const response = await command.run(object, id, cmd);
this.processResponse(response);
@ -225,12 +220,12 @@ export class VaultProgram extends Program {
await this.exitIfLocked();
const command = new CreateCommand(
this.main.cipherService,
this.main.folderService,
this.main.cryptoService,
this.main.apiService,
this.main.folderApiService,
this.main.billingAccountProfileStateService,
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.cryptoService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
this.serviceContainer.billingAccountProfileStateService,
);
const response = await command.run(object, encodedJson, cmd);
this.processResponse(response);
@ -271,11 +266,11 @@ export class VaultProgram extends Program {
await this.exitIfLocked();
const command = new EditCommand(
this.main.cipherService,
this.main.folderService,
this.main.cryptoService,
this.main.apiService,
this.main.folderApiService,
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.cryptoService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
);
const response = await command.run(object, id, encodedJson, cmd);
this.processResponse(response);
@ -312,11 +307,11 @@ export class VaultProgram extends Program {
await this.exitIfLocked();
const command = new DeleteCommand(
this.main.cipherService,
this.main.folderService,
this.main.apiService,
this.main.folderApiService,
this.main.billingAccountProfileStateService,
this.serviceContainer.cipherService,
this.serviceContainer.folderService,
this.serviceContainer.apiService,
this.serviceContainer.folderApiService,
this.serviceContainer.billingAccountProfileStateService,
);
const response = await command.run(object, id, cmd);
this.processResponse(response);
@ -341,7 +336,7 @@ export class VaultProgram extends Program {
}
await this.exitIfLocked();
const command = new RestoreCommand(this.main.cipherService);
const command = new RestoreCommand(this.serviceContainer.cipherService);
const response = await command.run(object, id);
this.processResponse(response);
});
@ -379,7 +374,7 @@ export class VaultProgram extends Program {
})
.action(async (id, organizationId, encodedJson, cmd) => {
await this.exitIfLocked();
const command = new ShareCommand(this.main.cipherService);
const command = new ShareCommand(this.serviceContainer.cipherService);
const response = await command.run(id, organizationId, encodedJson);
this.processResponse(response);
});
@ -408,9 +403,9 @@ export class VaultProgram extends Program {
await this.exitIfLocked();
const command = new ConfirmCommand(
this.main.apiService,
this.main.cryptoService,
this.main.organizationUserService,
this.serviceContainer.apiService,
this.serviceContainer.cryptoService,
this.serviceContainer.organizationUserService,
);
const response = await command.run(object, id, cmd);
this.processResponse(response);
@ -437,9 +432,9 @@ export class VaultProgram extends Program {
.action(async (format, filepath, options) => {
await this.exitIfLocked();
const command = new ImportCommand(
this.main.importService,
this.main.organizationService,
this.main.syncService,
this.serviceContainer.importService,
this.serviceContainer.organizationService,
this.serviceContainer.syncService,
);
const response = await command.run(format, filepath, options);
this.processResponse(response);
@ -484,9 +479,9 @@ export class VaultProgram extends Program {
.action(async (options) => {
await this.exitIfLocked();
const command = new ExportCommand(
this.main.exportService,
this.main.policyService,
this.main.eventCollectionService,
this.serviceContainer.exportService,
this.serviceContainer.policyService,
this.serviceContainer.eventCollectionService,
);
const response = await command.run(options);
this.processResponse(response);

View File

@ -92,7 +92,9 @@ export class ElectronCryptoService extends CryptoService {
if (keySuffix === KeySuffixOptions.Biometric) {
await this.migrateBiometricKeyIfNeeded(userId);
const userKey = await this.stateService.getUserKeyBiometric({ userId: userId });
return new SymmetricCryptoKey(Utils.fromB64ToArray(userKey)) as UserKey;
return userKey == null
? null
: (new SymmetricCryptoKey(Utils.fromB64ToArray(userKey)) as UserKey);
}
return await super.getKeyFromStorage(keySuffix, userId);
}
@ -169,7 +171,9 @@ export class ElectronCryptoService extends CryptoService {
// decrypt
const masterKey = new SymmetricCryptoKey(Utils.fromB64ToArray(oldBiometricKey)) as MasterKey;
userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id;
const encUserKeyPrim = await this.stateService.getEncryptedCryptoSymmetricKey();
const encUserKeyPrim = await this.stateService.getEncryptedCryptoSymmetricKey({
userId: userId,
});
const encUserKey =
encUserKeyPrim != null
? new EncString(encUserKeyPrim)
@ -180,6 +184,7 @@ export class ElectronCryptoService extends CryptoService {
const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
masterKey,
encUserKey,
userId,
);
// migrate
await this.storeBiometricKey(userKey, userId);

View File

@ -69,6 +69,13 @@
<span>{{ "readOnlyCollectionAccess" | i18n }}</span>
</ng-container>
<ng-container *ngIf="!dialogReadonly">
<bit-callout
title="{{ 'grantAddAccessCollectionWarningTitle' | i18n }}"
type="warning"
*ngIf="showAddAccessWarning"
>
{{ "grantAddAccessCollectionWarning" | i18n }}
</bit-callout>
<span *ngIf="organization.useGroups">{{ "grantCollectionAccess" | i18n }}</span>
<span *ngIf="!organization.useGroups">{{
"grantCollectionAccessMembersOnly" | i18n
@ -84,7 +91,10 @@
</div>
<div
class="tw-mb-3 tw-text-danger"
*ngIf="formGroup.controls.access.hasError('managePermissionRequired')"
*ngIf="
formGroup.controls.access.hasError('managePermissionRequired') &&
!showAddAccessWarning
"
>
<i class="bwi bwi-error"></i> {{ "managePermissionRequired" | i18n }}
</div>

View File

@ -59,6 +59,7 @@ export interface CollectionDialogParams {
*/
limitNestedCollections?: boolean;
readonly?: boolean;
isAddAccessCollection?: boolean;
}
export interface CollectionDialogResult {
@ -100,6 +101,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
});
protected PermissionMode = PermissionMode;
protected showDeleteButton = false;
protected showAddAccessWarning = false;
constructor(
@Inject(DIALOG_DATA) private params: CollectionDialogParams,
@ -251,6 +253,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
this.handleFormGroupReadonly(this.dialogReadonly);
this.loading = false;
this.showAddAccessWarning = this.handleAddAccessWarning(flexibleCollectionsV1);
},
);
}
@ -362,6 +365,18 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
this.destroy$.complete();
}
private handleAddAccessWarning(flexibleCollectionsV1: boolean): boolean {
if (
flexibleCollectionsV1 &&
!this.organization?.allowAdminAccessToAllCollectionItems &&
this.params.isAddAccessCollection
) {
return true;
}
return false;
}
private handleFormGroupReadonly(readonly: boolean) {
if (readonly) {
this.formGroup.controls.name.disable();

View File

@ -1222,6 +1222,7 @@ export class VaultComponent implements OnInit, OnDestroy {
organizationId: this.organization?.id,
initialTab: tab,
readonly: readonly,
isAddAccessCollection: c.addAccess,
limitNestedCollections: !this.organization.canEditAnyCollection(
this.flexibleCollectionsV1Enabled,
),

View File

@ -7635,6 +7635,12 @@
"readOnlyCollectionAccess": {
"message": "You do not have access to manage this collection."
},
"grantAddAccessCollectionWarningTitle": {
"message": "Missing Can Manage Permissions"
},
"grantAddAccessCollectionWarning": {
"message": "Grant Can manage permissions to allow full collection management including deletion of collection."
},
"grantCollectionAccess": {
"message": "Grant groups or members access to this collection."
},

View File

@ -0,0 +1,5 @@
{
"env": {
"node": true
}
}

View File

@ -0,0 +1,16 @@
const { pathsToModuleNameMapper } = require("ts-jest");
const { compilerOptions } = require("./tsconfig");
const sharedConfig = require("../../libs/shared/jest.config.ts");
/** @type {import('jest').Config} */
module.exports = {
...sharedConfig,
preset: "ts-jest",
testEnvironment: "node",
setupFilesAfterEnv: ["<rootDir>/../../apps/cli/test.setup.ts"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
};

View File

@ -0,0 +1,5 @@
describe("Jest", () => {
it("is set up", () => {
expect(true).toBeTruthy();
});
});

View File

@ -0,0 +1,20 @@
import { program } from "commander";
import { registerOssPrograms } from "@bitwarden/cli/register-oss-programs";
import { registerBitPrograms } from "./register-bit-programs";
import { ServiceContainer } from "./service-container";
async function main() {
const serviceContainer = new ServiceContainer();
await serviceContainer.init();
await registerOssPrograms(serviceContainer);
await registerBitPrograms(serviceContainer);
program.parse(process.argv);
}
// Node does not support top-level await statements until ES2022, esnext, etc which we don't use yet
// eslint-disable-next-line @typescript-eslint/no-floating-promises
main();

View File

@ -0,0 +1,10 @@
import { ServiceContainer } from "./service-container";
/**
* All Bitwarden-licensed programs should be registered here.
* @example
* const myProgram = new myProgram(serviceContainer);
* myProgram.register();
* @param serviceContainer A class that instantiates services and makes them available for dependency injection
*/
export async function registerBitPrograms(serviceContainer: ServiceContainer) {}

View File

@ -0,0 +1,7 @@
import { ServiceContainer } from "./service-container";
describe("ServiceContainer", () => {
it("instantiates", async () => {
expect(() => new ServiceContainer()).not.toThrow();
});
});

View File

@ -0,0 +1,7 @@
import { ServiceContainer as OssServiceContainer } from "@bitwarden/cli/service-container";
/**
* Instantiates services and makes them available for dependency injection.
* Any Bitwarden-licensed services should be registered here.
*/
export class ServiceContainer extends OssServiceContainer {}

View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"pretty": true,
"moduleResolution": "node",
"target": "ES2016",
"module": "es6",
"noImplicitAny": true,
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowJs": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@bitwarden/cli/*": ["../../apps/cli/src/*"],
"@bitwarden/common/spec": ["../../libs/common/spec"],
"@bitwarden/auth/common": ["../../libs/auth/src/common"],
"@bitwarden/auth/angular": ["../../libs/auth/src/angular"],
"@bitwarden/common/*": ["../../libs/common/src/*"],
"@bitwarden/importer/core": ["../../libs/importer/src"],
"@bitwarden/vault-export-core": [
"../../libs/tools/export/vault-export/vault-export-core/src"
],
"@bitwarden/node/*": ["../../libs/node/src/*"]
}
},
"include": ["src", "src/**/*.spec.ts"]
}

View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"files": ["../../apps/cli/test.setup.ts"]
}

View File

@ -0,0 +1,12 @@
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
// Re-use the OSS CLI webpack config
const webpackConfig = require("../../apps/cli/webpack.config");
// Update paths to use the bit-cli entrypoint and tsconfig
webpackConfig.entry = { bw: "../../bitwarden_license/bit-cli/src/bw.ts" };
webpackConfig.resolve.plugins = [
new TsconfigPathsPlugin({ configFile: "../../bitwarden_license/bit-cli/tsconfig.json" }),
];
module.exports = webpackConfig;

View File

@ -16,6 +16,10 @@
"name": "cli",
"path": "apps/cli",
},
{
"name": "cli (bit)",
"path": "bitwarden_license/bit-cli",
},
{
"name": "desktop",
"path": "apps/desktop",

View File

@ -21,6 +21,7 @@ module.exports = {
"<rootDir>/apps/desktop/jest.config.js",
"<rootDir>/apps/web/jest.config.js",
"<rootDir>/bitwarden_license/bit-web/jest.config.js",
"<rootDir>/bitwarden_license/bit-cli/jest.config.js",
"<rootDir>/libs/admin-console/jest.config.js",
"<rootDir>/libs/angular/jest.config.js",

View File

@ -12,8 +12,8 @@ export const getCommand = (commandDefinition: CommandDefinition<object> | string
export const EXTERNAL_SOURCE_TAG = Symbol("externalSource");
export const isExternalMessage = (message: Message<object>) => {
return (message as Record<PropertyKey, unknown>)?.[EXTERNAL_SOURCE_TAG] === true;
export const isExternalMessage = (message: Record<PropertyKey, unknown>) => {
return message?.[EXTERNAL_SOURCE_TAG] === true;
};
export const tagAsExternal: MonoTypeOperatorFunction<Message<object>> = map(

View File

@ -0,0 +1,230 @@
import { firstValueFrom, map, of, switchMap } from "rxjs";
import { ApiService } from "../../abstractions/api.service";
import { AccountService } from "../../auth/abstractions/account.service";
import { AuthService } from "../../auth/abstractions/auth.service";
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
import {
SyncCipherNotification,
SyncFolderNotification,
SyncSendNotification,
} from "../../models/response/notification.response";
import { SendData } from "../../tools/send/models/data/send.data";
import { SendApiService } from "../../tools/send/services/send-api.service.abstraction";
import { InternalSendService } from "../../tools/send/services/send.service.abstraction";
import { CipherService } from "../../vault/abstractions/cipher.service";
import { CollectionService } from "../../vault/abstractions/collection.service";
import { FolderApiServiceAbstraction } from "../../vault/abstractions/folder/folder-api.service.abstraction";
import { InternalFolderService } from "../../vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "../../vault/abstractions/sync/sync.service.abstraction";
import { CipherData } from "../../vault/models/data/cipher.data";
import { FolderData } from "../../vault/models/data/folder.data";
import { LogService } from "../abstractions/log.service";
import { StateService } from "../abstractions/state.service";
import { MessageSender } from "../messaging";
/**
* Core SyncService Logic EXCEPT for fullSync so that implementations can differ.
*/
export abstract class CoreSyncService implements SyncService {
syncInProgress = false;
constructor(
protected readonly stateService: StateService,
protected readonly folderService: InternalFolderService,
protected readonly folderApiService: FolderApiServiceAbstraction,
protected readonly messageSender: MessageSender,
protected readonly logService: LogService,
protected readonly cipherService: CipherService,
protected readonly collectionService: CollectionService,
protected readonly apiService: ApiService,
protected readonly accountService: AccountService,
protected readonly authService: AuthService,
protected readonly sendService: InternalSendService,
protected readonly sendApiService: SendApiService,
) {}
abstract fullSync(forceSync: boolean, allowThrowOnError?: boolean): Promise<boolean>;
async getLastSync(): Promise<Date> {
if ((await this.stateService.getUserId()) == null) {
return null;
}
const lastSync = await this.stateService.getLastSync();
if (lastSync) {
return new Date(lastSync);
}
return null;
}
async setLastSync(date: Date, userId?: string): Promise<any> {
await this.stateService.setLastSync(date.toJSON(), { userId: userId });
}
async syncUpsertFolder(notification: SyncFolderNotification, isEdit: boolean): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
try {
const localFolder = await this.folderService.get(notification.id);
if (
(!isEdit && localFolder == null) ||
(isEdit && localFolder != null && localFolder.revisionDate < notification.revisionDate)
) {
const remoteFolder = await this.folderApiService.get(notification.id);
if (remoteFolder != null) {
await this.folderService.upsert(new FolderData(remoteFolder));
this.messageSender.send("syncedUpsertedFolder", { folderId: notification.id });
return this.syncCompleted(true);
}
}
} catch (e) {
this.logService.error(e);
}
}
return this.syncCompleted(false);
}
async syncDeleteFolder(notification: SyncFolderNotification): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
await this.folderService.delete(notification.id);
this.messageSender.send("syncedDeletedFolder", { folderId: notification.id });
this.syncCompleted(true);
return true;
}
return this.syncCompleted(false);
}
async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
try {
let shouldUpdate = true;
const localCipher = await this.cipherService.get(notification.id);
if (localCipher != null && localCipher.revisionDate >= notification.revisionDate) {
shouldUpdate = false;
}
let checkCollections = false;
if (shouldUpdate) {
if (isEdit) {
shouldUpdate = localCipher != null;
checkCollections = true;
} else {
if (notification.collectionIds == null || notification.organizationId == null) {
shouldUpdate = localCipher == null;
} else {
shouldUpdate = false;
checkCollections = true;
}
}
}
if (
!shouldUpdate &&
checkCollections &&
notification.organizationId != null &&
notification.collectionIds != null &&
notification.collectionIds.length > 0
) {
const collections = await this.collectionService.getAll();
if (collections != null) {
for (let i = 0; i < collections.length; i++) {
if (notification.collectionIds.indexOf(collections[i].id) > -1) {
shouldUpdate = true;
break;
}
}
}
}
if (shouldUpdate) {
const remoteCipher = await this.apiService.getFullCipherDetails(notification.id);
if (remoteCipher != null) {
await this.cipherService.upsert(new CipherData(remoteCipher));
this.messageSender.send("syncedUpsertedCipher", { cipherId: notification.id });
return this.syncCompleted(true);
}
}
} catch (e) {
if (e != null && e.statusCode === 404 && isEdit) {
await this.cipherService.delete(notification.id);
this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id });
return this.syncCompleted(true);
}
}
}
return this.syncCompleted(false);
}
async syncDeleteCipher(notification: SyncCipherNotification): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
await this.cipherService.delete(notification.id);
this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id });
return this.syncCompleted(true);
}
return this.syncCompleted(false);
}
async syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise<boolean> {
this.syncStarted();
const [activeUserId, status] = await firstValueFrom(
this.accountService.activeAccount$.pipe(
switchMap((a) => {
if (a == null) {
of([null, AuthenticationStatus.LoggedOut]);
}
return this.authService.authStatusFor$(a.id).pipe(map((s) => [a.id, s]));
}),
),
);
// Process only notifications for currently active user when user is not logged out
// TODO: once send service allows data manipulation of non-active users, this should process any received notification
if (activeUserId === notification.userId && status !== AuthenticationStatus.LoggedOut) {
try {
const localSend = await firstValueFrom(this.sendService.get$(notification.id));
if (
(!isEdit && localSend == null) ||
(isEdit && localSend != null && localSend.revisionDate < notification.revisionDate)
) {
const remoteSend = await this.sendApiService.getSend(notification.id);
if (remoteSend != null) {
await this.sendService.upsert(new SendData(remoteSend));
this.messageSender.send("syncedUpsertedSend", { sendId: notification.id });
return this.syncCompleted(true);
}
}
} catch (e) {
this.logService.error(e);
}
}
return this.syncCompleted(false);
}
async syncDeleteSend(notification: SyncSendNotification): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
await this.sendService.delete(notification.id);
this.messageSender.send("syncedDeletedSend", { sendId: notification.id });
this.syncCompleted(true);
return true;
}
return this.syncCompleted(false);
}
// Helpers
protected syncStarted() {
this.syncInProgress = true;
this.messageSender.send("syncStarted");
}
protected syncCompleted(successfully: boolean): boolean {
this.syncInProgress = false;
this.messageSender.send("syncCompleted", { successfully: successfully });
return successfully;
}
}

View File

@ -0,0 +1 @@
export { CoreSyncService } from "./core-sync.service";

View File

@ -1,4 +1,4 @@
import { firstValueFrom, map, of, switchMap } from "rxjs";
import { firstValueFrom } from "rxjs";
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
@ -17,22 +17,17 @@ import { AvatarService } from "../../../auth/abstractions/avatar.service";
import { KeyConnectorService } from "../../../auth/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "../../../auth/abstractions/master-password.service.abstraction";
import { TokenService } from "../../../auth/abstractions/token.service";
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
import { DomainSettingsService } from "../../../autofill/services/domain-settings.service";
import { BillingAccountProfileStateService } from "../../../billing/abstractions/account/billing-account-profile-state.service";
import { DomainsResponse } from "../../../models/response/domains.response";
import {
SyncCipherNotification,
SyncFolderNotification,
SyncSendNotification,
} from "../../../models/response/notification.response";
import { ProfileResponse } from "../../../models/response/profile.response";
import { CryptoService } from "../../../platform/abstractions/crypto.service";
import { LogService } from "../../../platform/abstractions/log.service";
import { MessagingService } from "../../../platform/abstractions/messaging.service";
import { StateService } from "../../../platform/abstractions/state.service";
import { MessageSender } from "../../../platform/messaging";
import { sequentialize } from "../../../platform/misc/sequentialize";
import { CoreSyncService } from "../../../platform/sync/core-sync.service";
import { SendData } from "../../../tools/send/models/data/send.data";
import { SendResponse } from "../../../tools/send/models/response/send.response";
import { SendApiService } from "../../../tools/send/services/send-api.service.abstraction";
@ -40,7 +35,6 @@ import { InternalSendService } from "../../../tools/send/services/send.service.a
import { CipherService } from "../../../vault/abstractions/cipher.service";
import { FolderApiServiceAbstraction } from "../../../vault/abstractions/folder/folder-api.service.abstraction";
import { InternalFolderService } from "../../../vault/abstractions/folder/folder.service.abstraction";
import { SyncService as SyncServiceAbstraction } from "../../../vault/abstractions/sync/sync.service.abstraction";
import { CipherData } from "../../../vault/models/data/cipher.data";
import { FolderData } from "../../../vault/models/data/folder.data";
import { CipherResponse } from "../../../vault/models/response/cipher.response";
@ -49,55 +43,51 @@ import { CollectionService } from "../../abstractions/collection.service";
import { CollectionData } from "../../models/data/collection.data";
import { CollectionDetailsResponse } from "../../models/response/collection.response";
export class SyncService implements SyncServiceAbstraction {
syncInProgress = false;
export class SyncService extends CoreSyncService {
constructor(
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private accountService: AccountService,
private apiService: ApiService,
accountService: AccountService,
apiService: ApiService,
private domainSettingsService: DomainSettingsService,
private folderService: InternalFolderService,
private cipherService: CipherService,
folderService: InternalFolderService,
cipherService: CipherService,
private cryptoService: CryptoService,
private collectionService: CollectionService,
private messagingService: MessagingService,
collectionService: CollectionService,
messageSender: MessageSender,
private policyService: InternalPolicyService,
private sendService: InternalSendService,
private logService: LogService,
sendService: InternalSendService,
logService: LogService,
private keyConnectorService: KeyConnectorService,
private stateService: StateService,
stateService: StateService,
private providerService: ProviderService,
private folderApiService: FolderApiServiceAbstraction,
folderApiService: FolderApiServiceAbstraction,
private organizationService: InternalOrganizationServiceAbstraction,
private sendApiService: SendApiService,
sendApiService: SendApiService,
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
private avatarService: AvatarService,
private logoutCallback: (expired: boolean) => Promise<void>,
private billingAccountProfileStateService: BillingAccountProfileStateService,
private tokenService: TokenService,
private authService: AuthService,
) {}
async getLastSync(): Promise<Date> {
if ((await this.stateService.getUserId()) == null) {
return null;
}
const lastSync = await this.stateService.getLastSync();
if (lastSync) {
return new Date(lastSync);
}
return null;
}
async setLastSync(date: Date, userId?: string): Promise<any> {
await this.stateService.setLastSync(date.toJSON(), { userId: userId });
authService: AuthService,
) {
super(
stateService,
folderService,
folderApiService,
messageSender,
logService,
cipherService,
collectionService,
apiService,
accountService,
authService,
sendService,
sendApiService,
);
}
@sequentialize(() => "fullSync")
async fullSync(forceSync: boolean, allowThrowOnError = false): Promise<boolean> {
override async fullSync(forceSync: boolean, allowThrowOnError = false): Promise<boolean> {
this.syncStarted();
const isAuthenticated = await this.stateService.getIsAuthenticated();
if (!isAuthenticated) {
@ -110,6 +100,7 @@ export class SyncService implements SyncServiceAbstraction {
needsSync = await this.needsSyncing(forceSync);
} catch (e) {
if (allowThrowOnError) {
this.syncCompleted(false);
throw e;
}
}
@ -135,6 +126,7 @@ export class SyncService implements SyncServiceAbstraction {
return this.syncCompleted(true);
} catch (e) {
if (allowThrowOnError) {
this.syncCompleted(false);
throw e;
} else {
return this.syncCompleted(false);
@ -142,171 +134,6 @@ export class SyncService implements SyncServiceAbstraction {
}
}
async syncUpsertFolder(notification: SyncFolderNotification, isEdit: boolean): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
try {
const localFolder = await this.folderService.get(notification.id);
if (
(!isEdit && localFolder == null) ||
(isEdit && localFolder != null && localFolder.revisionDate < notification.revisionDate)
) {
const remoteFolder = await this.folderApiService.get(notification.id);
if (remoteFolder != null) {
await this.folderService.upsert(new FolderData(remoteFolder));
this.messagingService.send("syncedUpsertedFolder", { folderId: notification.id });
return this.syncCompleted(true);
}
}
} catch (e) {
this.logService.error(e);
}
}
return this.syncCompleted(false);
}
async syncDeleteFolder(notification: SyncFolderNotification): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
await this.folderService.delete(notification.id);
this.messagingService.send("syncedDeletedFolder", { folderId: notification.id });
this.syncCompleted(true);
return true;
}
return this.syncCompleted(false);
}
async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
try {
let shouldUpdate = true;
const localCipher = await this.cipherService.get(notification.id);
if (localCipher != null && localCipher.revisionDate >= notification.revisionDate) {
shouldUpdate = false;
}
let checkCollections = false;
if (shouldUpdate) {
if (isEdit) {
shouldUpdate = localCipher != null;
checkCollections = true;
} else {
if (notification.collectionIds == null || notification.organizationId == null) {
shouldUpdate = localCipher == null;
} else {
shouldUpdate = false;
checkCollections = true;
}
}
}
if (
!shouldUpdate &&
checkCollections &&
notification.organizationId != null &&
notification.collectionIds != null &&
notification.collectionIds.length > 0
) {
const collections = await this.collectionService.getAll();
if (collections != null) {
for (let i = 0; i < collections.length; i++) {
if (notification.collectionIds.indexOf(collections[i].id) > -1) {
shouldUpdate = true;
break;
}
}
}
}
if (shouldUpdate) {
const remoteCipher = await this.apiService.getFullCipherDetails(notification.id);
if (remoteCipher != null) {
await this.cipherService.upsert(new CipherData(remoteCipher));
this.messagingService.send("syncedUpsertedCipher", { cipherId: notification.id });
return this.syncCompleted(true);
}
}
} catch (e) {
if (e != null && e.statusCode === 404 && isEdit) {
await this.cipherService.delete(notification.id);
this.messagingService.send("syncedDeletedCipher", { cipherId: notification.id });
return this.syncCompleted(true);
}
}
}
return this.syncCompleted(false);
}
async syncDeleteCipher(notification: SyncCipherNotification): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
await this.cipherService.delete(notification.id);
this.messagingService.send("syncedDeletedCipher", { cipherId: notification.id });
return this.syncCompleted(true);
}
return this.syncCompleted(false);
}
async syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise<boolean> {
this.syncStarted();
const [activeUserId, status] = await firstValueFrom(
this.accountService.activeAccount$.pipe(
switchMap((a) => {
if (a == null) {
of([null, AuthenticationStatus.LoggedOut]);
}
return this.authService.authStatusFor$(a.id).pipe(map((s) => [a.id, s]));
}),
),
);
// Process only notifications for currently active user when user is not logged out
// TODO: once send service allows data manipulation of non-active users, this should process any received notification
if (activeUserId === notification.userId && status !== AuthenticationStatus.LoggedOut) {
try {
const localSend = await firstValueFrom(this.sendService.get$(notification.id));
if (
(!isEdit && localSend == null) ||
(isEdit && localSend != null && localSend.revisionDate < notification.revisionDate)
) {
const remoteSend = await this.sendApiService.getSend(notification.id);
if (remoteSend != null) {
await this.sendService.upsert(new SendData(remoteSend));
this.messagingService.send("syncedUpsertedSend", { sendId: notification.id });
return this.syncCompleted(true);
}
}
} catch (e) {
this.logService.error(e);
}
}
return this.syncCompleted(false);
}
async syncDeleteSend(notification: SyncSendNotification): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
await this.sendService.delete(notification.id);
this.messagingService.send("syncedDeletedSend", { sendId: notification.id });
this.syncCompleted(true);
return true;
}
return this.syncCompleted(false);
}
// Helpers
private syncStarted() {
this.syncInProgress = true;
this.messagingService.send("syncStarted");
}
private syncCompleted(successfully: boolean): boolean {
this.syncInProgress = false;
this.messagingService.send("syncCompleted", { successfully: successfully });
return successfully;
}
private async needsSyncing(forceSync: boolean) {
if (forceSync) {
return true;
@ -365,7 +192,7 @@ export class SyncService implements SyncServiceAbstraction {
if (await this.keyConnectorService.userNeedsMigration()) {
await this.keyConnectorService.setConvertAccountRequired(true);
this.messagingService.send("convertAccountToKeyConnector");
this.messageSender.send("convertAccountToKeyConnector");
} else {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises