Merge remote-tracking branch 'origin/main' into auth/pm-7392/token-service-add-secure-storage-fallback + merge conflict fixes in main.background and cli bw.ts
This commit is contained in:
commit
dfbb6154c7
|
@ -1,7 +1,7 @@
|
|||
import { Location } from "@angular/common";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { Subject, firstValueFrom, map, switchMap, takeUntil } from "rxjs";
|
||||
import { Subject, firstValueFrom, map, of, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
|
||||
|
@ -49,7 +49,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
|
|||
readonly currentAccount$ = this.accountService.activeAccount$.pipe(
|
||||
switchMap((a) =>
|
||||
a == null
|
||||
? null
|
||||
? of(null)
|
||||
: this.authService.activeAccountStatus$.pipe(map((s) => ({ ...a, status: s }))),
|
||||
),
|
||||
);
|
||||
|
@ -106,12 +106,14 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
|
||||
if (confirmed) {
|
||||
this.messagingService.send("logout", { userId });
|
||||
const result = await this.accountSwitcherService.logoutAccount(userId);
|
||||
// unlocked logout responses need to be navigated out of the account switcher.
|
||||
// other responses will be handled by background and app.component
|
||||
if (result?.status === AuthenticationStatus.Unlocked) {
|
||||
this.location.back();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["home"]);
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { CommonModule, Location } from "@angular/common";
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { AvatarModule } from "@bitwarden/components";
|
||||
|
||||
import { AccountSwitcherService, AvailableAccount } from "./services/account-switcher.service";
|
||||
|
@ -21,9 +21,9 @@ export class AccountComponent {
|
|||
|
||||
constructor(
|
||||
private accountSwitcherService: AccountSwitcherService,
|
||||
private router: Router,
|
||||
private location: Location,
|
||||
private i18nService: I18nService,
|
||||
private logService: LogService,
|
||||
) {}
|
||||
|
||||
get specialAccountAddId() {
|
||||
|
@ -32,15 +32,19 @@ export class AccountComponent {
|
|||
|
||||
async selectAccount(id: string) {
|
||||
this.loading.emit(true);
|
||||
await this.accountSwitcherService.selectAccount(id);
|
||||
let result;
|
||||
try {
|
||||
result = await this.accountSwitcherService.selectAccount(id);
|
||||
} catch (e) {
|
||||
this.logService.error("Error selecting account", e);
|
||||
}
|
||||
|
||||
if (id === this.specialAccountAddId) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["home"]);
|
||||
} else {
|
||||
// Navigate out of account switching for unlocked accounts
|
||||
// locked or logged out account statuses are handled by background and app.component
|
||||
if (result?.status === AuthenticationStatus.Unlocked) {
|
||||
this.location.back();
|
||||
}
|
||||
this.loading.emit(false);
|
||||
}
|
||||
|
||||
get status() {
|
||||
|
|
|
@ -186,4 +186,35 @@ describe("AccountSwitcherService", () => {
|
|||
expect(removeListenerSpy).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("logout", () => {
|
||||
const userId1 = "1" as UserId;
|
||||
const userId2 = "2" as UserId;
|
||||
it("initiates logout", async () => {
|
||||
let listener: (
|
||||
message: { command: string; userId: UserId; status: AuthenticationStatus },
|
||||
sender: unknown,
|
||||
sendResponse: unknown,
|
||||
) => void;
|
||||
jest.spyOn(chrome.runtime.onMessage, "addListener").mockImplementation((addedListener) => {
|
||||
listener = addedListener;
|
||||
});
|
||||
|
||||
const removeListenerSpy = jest.spyOn(chrome.runtime.onMessage, "removeListener");
|
||||
|
||||
const logoutPromise = accountSwitcherService.logoutAccount(userId1);
|
||||
|
||||
listener(
|
||||
{ command: "switchAccountFinish", userId: userId2, status: AuthenticationStatus.Unlocked },
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
const result = await logoutPromise;
|
||||
|
||||
expect(messagingService.send).toHaveBeenCalledWith("logout", { userId: userId1 });
|
||||
expect(result).toEqual({ newUserId: userId2, status: AuthenticationStatus.Unlocked });
|
||||
expect(removeListenerSpy).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -41,7 +41,7 @@ export class AccountSwitcherService {
|
|||
SPECIAL_ADD_ACCOUNT_ID = "addAccount";
|
||||
availableAccounts$: Observable<AvailableAccount[]>;
|
||||
|
||||
switchAccountFinished$: Observable<string>;
|
||||
switchAccountFinished$: Observable<{ userId: UserId; status: AuthenticationStatus }>;
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
|
@ -111,11 +111,11 @@ export class AccountSwitcherService {
|
|||
);
|
||||
|
||||
// Create a reusable observable that listens to the switchAccountFinish message and returns the userId from the message
|
||||
this.switchAccountFinished$ = fromChromeEvent<[message: { command: string; userId: string }]>(
|
||||
chrome.runtime.onMessage,
|
||||
).pipe(
|
||||
this.switchAccountFinished$ = fromChromeEvent<
|
||||
[message: { command: string; userId: UserId; status: AuthenticationStatus }]
|
||||
>(chrome.runtime.onMessage).pipe(
|
||||
filter(([message]) => message.command === "switchAccountFinish"),
|
||||
map(([message]) => message.userId),
|
||||
map(([message]) => ({ userId: message.userId, status: message.status })),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -127,12 +127,46 @@ export class AccountSwitcherService {
|
|||
if (id === this.SPECIAL_ADD_ACCOUNT_ID) {
|
||||
id = null;
|
||||
}
|
||||
const userId = id as UserId;
|
||||
|
||||
// Creates a subscription to the switchAccountFinished observable but further
|
||||
// filters it to only care about the current userId.
|
||||
const switchAccountFinishedPromise = firstValueFrom(
|
||||
const switchAccountFinishedPromise = this.listenForSwitchAccountFinish(userId);
|
||||
|
||||
// Initiate the actions required to make account switching happen
|
||||
await this.accountService.switchAccount(userId);
|
||||
this.messagingService.send("switchAccount", { userId }); // This message should cause switchAccountFinish to be sent
|
||||
|
||||
// Wait until we receive the switchAccountFinished message
|
||||
return await switchAccountFinishedPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId the user id to logout
|
||||
* @returns the userId and status of the that has been switch to due to the logout. null on errors.
|
||||
*/
|
||||
async logoutAccount(
|
||||
userId: UserId,
|
||||
): Promise<{ newUserId: UserId; status: AuthenticationStatus } | null> {
|
||||
// logout creates an account switch to the next up user, which may be null
|
||||
const switchPromise = this.listenForSwitchAccountFinish(null);
|
||||
|
||||
await this.messagingService.send("logout", { userId });
|
||||
|
||||
// wait for account switch to happen, the result will be the new user id and status
|
||||
const result = await switchPromise;
|
||||
return { newUserId: result.userId, status: result.status };
|
||||
}
|
||||
|
||||
// Listens for the switchAccountFinish message and returns the userId from the message
|
||||
// Optionally filters switchAccountFinish to an expected userId
|
||||
private listenForSwitchAccountFinish(
|
||||
expectedUserId: UserId | null,
|
||||
): Promise<{ userId: UserId; status: AuthenticationStatus } | null> {
|
||||
return firstValueFrom(
|
||||
this.switchAccountFinished$.pipe(
|
||||
filter((userId) => userId === id),
|
||||
filter(({ userId }) => (expectedUserId ? userId === expectedUserId : true)),
|
||||
timeout({
|
||||
// Much longer than account switching is expected to take for normal accounts
|
||||
// but the account switching process includes a possible full sync so we need to account
|
||||
|
@ -143,20 +177,13 @@ export class AccountSwitcherService {
|
|||
throwError(() => new Error(AccountSwitcherService.incompleteAccountSwitchError)),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// Initiate the actions required to make account switching happen
|
||||
await this.accountService.switchAccount(id as UserId);
|
||||
this.messagingService.send("switchAccount", { userId: id }); // This message should cause switchAccountFinish to be sent
|
||||
|
||||
// Wait until we recieve the switchAccountFinished message
|
||||
await switchAccountFinishedPromise.catch((err) => {
|
||||
).catch((err) => {
|
||||
if (
|
||||
err instanceof Error &&
|
||||
err.message === AccountSwitcherService.incompleteAccountSwitchError
|
||||
) {
|
||||
this.logService.warning("message 'switchAccount' never responded.");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
|
|
|
@ -101,6 +101,7 @@ import { Message, MessageListener, MessageSender } from "@bitwarden/common/platf
|
|||
// eslint-disable-next-line no-restricted-imports -- Used for dependency creation
|
||||
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
||||
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||
import { clearCaches } from "@bitwarden/common/platform/misc/sequentialize";
|
||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
||||
|
@ -829,6 +830,7 @@ export default class MainBackground {
|
|||
logoutCallback,
|
||||
this.billingAccountProfileStateService,
|
||||
this.tokenService,
|
||||
this.authService,
|
||||
);
|
||||
this.eventUploadService = new EventUploadService(
|
||||
this.apiService,
|
||||
|
@ -1194,6 +1196,7 @@ export default class MainBackground {
|
|||
* Switch accounts to indicated userId -- null is no active user
|
||||
*/
|
||||
async switchAccount(userId: UserId) {
|
||||
let nextAccountStatus: AuthenticationStatus;
|
||||
try {
|
||||
const currentlyActiveAccount = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
|
||||
|
@ -1201,6 +1204,8 @@ export default class MainBackground {
|
|||
// can be removed once password generation history is migrated to state providers
|
||||
await this.stateService.clearDecryptedData(currentlyActiveAccount);
|
||||
await this.accountService.switchAccount(userId);
|
||||
// Clear sequentialized caches
|
||||
clearCaches();
|
||||
|
||||
if (userId == null) {
|
||||
this.loginEmailService.setRememberEmail(false);
|
||||
|
@ -1208,11 +1213,12 @@ export default class MainBackground {
|
|||
|
||||
await this.refreshBadge();
|
||||
await this.refreshMenu();
|
||||
await this.overlayBackground.updateOverlayCiphers();
|
||||
await this.overlayBackground?.updateOverlayCiphers(); // null in popup only contexts
|
||||
this.messagingService.send("goHome");
|
||||
return;
|
||||
}
|
||||
|
||||
const status = await this.authService.getAuthStatus(userId);
|
||||
nextAccountStatus = await this.authService.getAuthStatus(userId);
|
||||
const forcePasswordReset =
|
||||
(await firstValueFrom(this.masterPasswordService.forceSetPasswordReason$(userId))) !=
|
||||
ForceSetPasswordReason.None;
|
||||
|
@ -1220,7 +1226,9 @@ export default class MainBackground {
|
|||
await this.systemService.clearPendingClipboard();
|
||||
await this.notificationsService.updateConnection(false);
|
||||
|
||||
if (status === AuthenticationStatus.Locked) {
|
||||
if (nextAccountStatus === AuthenticationStatus.LoggedOut) {
|
||||
this.messagingService.send("goHome");
|
||||
} else if (nextAccountStatus === AuthenticationStatus.Locked) {
|
||||
this.messagingService.send("locked", { userId: userId });
|
||||
} else if (forcePasswordReset) {
|
||||
this.messagingService.send("update-temp-password", { userId: userId });
|
||||
|
@ -1228,11 +1236,14 @@ export default class MainBackground {
|
|||
this.messagingService.send("unlocked", { userId: userId });
|
||||
await this.refreshBadge();
|
||||
await this.refreshMenu();
|
||||
await this.overlayBackground.updateOverlayCiphers();
|
||||
await this.overlayBackground?.updateOverlayCiphers(); // null in popup only contexts
|
||||
await this.syncService.fullSync(false);
|
||||
}
|
||||
} finally {
|
||||
this.messagingService.send("switchAccountFinish", { userId: userId });
|
||||
this.messagingService.send("switchAccountFinish", {
|
||||
userId: userId,
|
||||
status: nextAccountStatus,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1253,6 +1264,13 @@ export default class MainBackground {
|
|||
|
||||
await this.eventUploadService.uploadEvents(userBeingLoggedOut);
|
||||
|
||||
const newActiveUser =
|
||||
userBeingLoggedOut === activeUserId
|
||||
? await firstValueFrom(this.accountService.nextUpAccount$.pipe(map((a) => a?.id)))
|
||||
: null;
|
||||
|
||||
await this.switchAccount(newActiveUser);
|
||||
|
||||
// HACK: We shouldn't wait for the authentication status to change but instead subscribe to the
|
||||
// authentication status to do various actions.
|
||||
const logoutPromise = firstValueFrom(
|
||||
|
@ -1287,11 +1305,6 @@ export default class MainBackground {
|
|||
//Needs to be checked before state is cleaned
|
||||
const needStorageReseed = await this.needsStorageReseed(userId);
|
||||
|
||||
const newActiveUser =
|
||||
userBeingLoggedOut === activeUserId
|
||||
? await firstValueFrom(this.accountService.nextUpAccount$.pipe(map((a) => a?.id)))
|
||||
: null;
|
||||
|
||||
await this.stateService.clean({ userId: userBeingLoggedOut });
|
||||
await this.accountService.clean(userBeingLoggedOut);
|
||||
|
||||
|
@ -1300,16 +1313,10 @@ export default class MainBackground {
|
|||
// HACK: Wait for the user logging outs authentication status to transition to LoggedOut
|
||||
await logoutPromise;
|
||||
|
||||
await this.switchAccount(newActiveUser);
|
||||
if (newActiveUser != null) {
|
||||
// we have a new active user, do not continue tearing down application
|
||||
this.messagingService.send("switchAccountFinish");
|
||||
} else {
|
||||
this.messagingService.send("doneLoggingOut", {
|
||||
logoutReason: logoutReason,
|
||||
userId: userBeingLoggedOut,
|
||||
});
|
||||
}
|
||||
this.messagingService.send("doneLoggingOut", {
|
||||
logoutReason: logoutReason,
|
||||
userId: userBeingLoggedOut,
|
||||
});
|
||||
|
||||
if (needStorageReseed) {
|
||||
await this.reseedStorage();
|
||||
|
@ -1322,9 +1329,7 @@ export default class MainBackground {
|
|||
}
|
||||
await this.refreshBadge();
|
||||
await this.mainContextMenuHandler?.noAccess();
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.notificationsService.updateConnection(false);
|
||||
await this.notificationsService.updateConnection(false);
|
||||
await this.systemService.clearPendingClipboard();
|
||||
await this.systemService.startProcessReload(this.authService);
|
||||
}
|
||||
|
|
|
@ -167,6 +167,11 @@ export class NativeMessagingBackground {
|
|||
cancelButtonText: null,
|
||||
type: "danger",
|
||||
});
|
||||
|
||||
if (this.resolver) {
|
||||
this.resolver(message);
|
||||
}
|
||||
|
||||
break;
|
||||
case "verifyFingerprint": {
|
||||
if (this.sharedSecret == null) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { mergeMap } from "rxjs";
|
||||
import { filter, mergeMap } from "rxjs";
|
||||
|
||||
import {
|
||||
AbstractStorageService,
|
||||
|
@ -34,6 +34,11 @@ export default abstract class AbstractChromeStorageService
|
|||
|
||||
constructor(protected chromeStorageApi: chrome.storage.StorageArea) {
|
||||
this.updates$ = fromChromeEvent(this.chromeStorageApi.onChanged).pipe(
|
||||
filter(([changes]) => {
|
||||
// Our storage services support changing only one key at a time. If more are changed, it's due to
|
||||
// reseeding storage and we should ignore the changes.
|
||||
return Object.keys(changes).length === 1;
|
||||
}),
|
||||
mergeMap(([changes]) => {
|
||||
return Object.entries(changes).map(([key, change]) => {
|
||||
// The `newValue` property isn't on the StorageChange object
|
||||
|
|
|
@ -94,13 +94,9 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
if (msg.logoutReason) {
|
||||
await this.displayLogoutReason(msg.logoutReason);
|
||||
}
|
||||
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["home"]);
|
||||
});
|
||||
this.changeDetectorRef.detectChanges();
|
||||
} else if (msg.command === "authBlocked") {
|
||||
} else if (msg.command === "authBlocked" || msg.command === "goHome") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["home"]);
|
||||
|
@ -140,9 +136,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["/remove-password"]);
|
||||
} else if (msg.command === "switchAccountFinish") {
|
||||
// TODO: unset loading?
|
||||
// this.loading = false;
|
||||
} else if (msg.command == "update-temp-password") {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,795 +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");
|
||||
}
|
||||
|
||||
const logoutCallback = async () => await this.logout();
|
||||
|
||||
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,
|
||||
logoutCallback,
|
||||
);
|
||||
|
||||
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() +
|
||||
")";
|
||||
|
||||
const refreshAccessTokenErrorCallback = () => {
|
||||
throw new Error("Refresh Access token error");
|
||||
};
|
||||
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,
|
||||
refreshAccessTokenErrorCallback,
|
||||
this.logService,
|
||||
logoutCallback,
|
||||
this.vaultTimeoutSettingsService,
|
||||
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,
|
||||
logoutCallback,
|
||||
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,
|
||||
logoutCallback,
|
||||
this.billingAccountProfileStateService,
|
||||
this.tokenService,
|
||||
);
|
||||
|
||||
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();
|
||||
|
|
|
@ -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."));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { ServiceContainer } from "./service-container";
|
||||
|
||||
describe("ServiceContainer", () => {
|
||||
it("instantiates", async () => {
|
||||
expect(() => new ServiceContainer()).not.toThrow();
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -40,6 +40,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||
import { clearCaches } from "@bitwarden/common/platform/misc/sequentialize";
|
||||
import { StateEventRunnerService } from "@bitwarden/common/platform/state";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
@ -399,6 +400,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
this.router.navigate(["/remove-password"]);
|
||||
break;
|
||||
case "switchAccount": {
|
||||
// Clear sequentialized caches
|
||||
clearCaches();
|
||||
if (message.userId != null) {
|
||||
await this.stateService.clearDecryptedData(message.userId);
|
||||
await this.accountService.switchAccount(message.userId);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -273,12 +273,13 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
|
|||
|
||||
// If the current user is not already in the group and cannot add themselves, remove them from the list
|
||||
if (restrictGroupAccess) {
|
||||
const organizationUserId = this.members.find((m) => m.userId === activeAccount.id).id;
|
||||
// organizationUserId may be null if accessing via a provider
|
||||
const organizationUserId = this.members.find((m) => m.userId === activeAccount.id)?.id;
|
||||
const isAlreadyInGroup = this.groupForm.value.members.some(
|
||||
(m) => m.id === organizationUserId,
|
||||
);
|
||||
|
||||
if (!isAlreadyInGroup) {
|
||||
if (organizationUserId != null && !isAlreadyInGroup) {
|
||||
this.members = this.members.filter((m) => m.id !== organizationUserId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, RouterModule } from "@angular/router";
|
||||
|
||||
import { AnonLayoutComponent } from "@bitwarden/auth/angular";
|
||||
|
@ -10,7 +10,7 @@ import { Icon } from "@bitwarden/components";
|
|||
templateUrl: "anon-layout-wrapper.component.html",
|
||||
imports: [AnonLayoutComponent, RouterModule],
|
||||
})
|
||||
export class AnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
||||
export class AnonLayoutWrapperComponent {
|
||||
protected pageTitle: string;
|
||||
protected pageSubtitle: string;
|
||||
protected pageIcon: Icon;
|
||||
|
@ -23,12 +23,4 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy {
|
|||
this.pageSubtitle = this.i18nService.t(this.route.snapshot.firstChild.data["pageSubtitle"]);
|
||||
this.pageIcon = this.route.snapshot.firstChild.data["pageIcon"]; // don't translate
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
document.body.classList.add("layout_frontend");
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
document.body.classList.remove("layout_frontend");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
type="checkbox"
|
||||
bitCheckbox
|
||||
appStopProp
|
||||
*ngIf="canDeleteCollection"
|
||||
*ngIf="showCheckbox"
|
||||
[disabled]="disabled"
|
||||
[checked]="checked"
|
||||
(change)="$event ? this.checkedToggled.next() : null"
|
||||
|
|
|
@ -90,4 +90,12 @@ export class VaultCollectionRowComponent {
|
|||
protected deleteCollection() {
|
||||
this.onEvent.next({ type: "delete", items: [{ collection: this.collection }] });
|
||||
}
|
||||
|
||||
protected get showCheckbox() {
|
||||
if (this.flexibleCollectionsV1Enabled) {
|
||||
return this.collection?.id !== Unassigned;
|
||||
}
|
||||
|
||||
return this.canDeleteCollection;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -245,11 +245,23 @@ export class VaultItemsComponent {
|
|||
const items: VaultItem[] = [].concat(collections).concat(ciphers);
|
||||
|
||||
this.selection.clear();
|
||||
this.editableItems = items.filter(
|
||||
(item) =>
|
||||
item.cipher !== undefined ||
|
||||
(item.collection !== undefined && this.canDeleteCollection(item.collection)),
|
||||
);
|
||||
|
||||
if (this.flexibleCollectionsV1Enabled) {
|
||||
// Every item except for the Unassigned collection is selectable, individual bulk actions check the user's permission
|
||||
this.editableItems = items.filter(
|
||||
(item) =>
|
||||
item.cipher !== undefined ||
|
||||
(item.collection !== undefined && item.collection.id !== Unassigned),
|
||||
);
|
||||
} else {
|
||||
// only collections the user can delete are selectable
|
||||
this.editableItems = items.filter(
|
||||
(item) =>
|
||||
item.cipher !== undefined ||
|
||||
(item.collection !== undefined && this.canDeleteCollection(item.collection)),
|
||||
);
|
||||
}
|
||||
|
||||
this.dataSource.data = items;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,13 +62,23 @@ export class CollectionAdminView extends CollectionView {
|
|||
}
|
||||
|
||||
/**
|
||||
* Whether the current user can edit the collection, including user and group access
|
||||
* Returns true if the user can edit a collection (including user and group access) from the Admin Console.
|
||||
*/
|
||||
override canEdit(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean {
|
||||
return org?.flexibleCollections
|
||||
? org?.canEditAnyCollection(flexibleCollectionsV1Enabled) || this.manage
|
||||
: org?.canEditAnyCollection(flexibleCollectionsV1Enabled) ||
|
||||
(org?.canEditAssignedCollections && this.assigned);
|
||||
return (
|
||||
org?.canEditAnyCollection(flexibleCollectionsV1Enabled) ||
|
||||
super.canEdit(org, flexibleCollectionsV1Enabled)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the user can delete a collection from the Admin Console.
|
||||
*/
|
||||
override canDelete(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean {
|
||||
return (
|
||||
org?.canDeleteAnyCollection(flexibleCollectionsV1Enabled) ||
|
||||
super.canDelete(org, flexibleCollectionsV1Enabled)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -47,7 +47,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
|||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { KdfType, PBKDF2_ITERATIONS } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
|
@ -61,7 +60,7 @@ import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/respon
|
|||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
|
||||
import { DialogService, Icons } from "@bitwarden/components";
|
||||
import { DialogService, Icons, ToastService } from "@bitwarden/components";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import {
|
||||
|
@ -167,7 +166,6 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
private platformUtilsService: PlatformUtilsService,
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private stateService: StateService,
|
||||
private organizationService: OrganizationService,
|
||||
private vaultFilterService: VaultFilterService,
|
||||
private routedVaultFilterService: RoutedVaultFilterService,
|
||||
|
@ -184,6 +182,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
private apiService: ApiService,
|
||||
private userVerificationService: UserVerificationService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private toastService: ToastService,
|
||||
protected kdfConfigService: KdfConfigService,
|
||||
) {}
|
||||
|
||||
|
@ -347,7 +346,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
} else {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
null,
|
||||
this.i18nService.t("unknownCipher"),
|
||||
);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
|
@ -551,6 +550,12 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
async shareCipher(cipher: CipherView) {
|
||||
if ((await this.flexibleCollectionsV1Enabled()) && cipher.organizationId != null) {
|
||||
// You cannot move ciphers between organizations
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cipher?.reprompt !== 0 && !(await this.passwordRepromptService.showPasswordPrompt())) {
|
||||
this.go({ cipherId: null, itemId: null });
|
||||
return;
|
||||
|
@ -700,11 +705,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
const organization = await this.organizationService.get(collection.organizationId);
|
||||
const flexibleCollectionsV1Enabled = await firstValueFrom(this.flexibleCollectionsV1Enabled$);
|
||||
if (!collection.canDelete(organization, flexibleCollectionsV1Enabled)) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("missingPermissions"),
|
||||
);
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
|
@ -755,11 +756,16 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
async restore(c: CipherView): Promise<boolean> {
|
||||
if (!(await this.repromptCipher([c]))) {
|
||||
if (!c.isDeleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c.isDeleted) {
|
||||
if ((await this.flexibleCollectionsV1Enabled()) && !c.edit) {
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this.repromptCipher([c]))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -773,17 +779,18 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
async bulkRestore(ciphers: CipherView[]) {
|
||||
if ((await this.flexibleCollectionsV1Enabled()) && ciphers.some((c) => !c.edit)) {
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this.repromptCipher(ciphers))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedCipherIds = ciphers.map((cipher) => cipher.id);
|
||||
if (selectedCipherIds.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected"),
|
||||
);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -817,6 +824,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
return;
|
||||
}
|
||||
|
||||
if ((await this.flexibleCollectionsV1Enabled()) && !c.edit) {
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
|
||||
const permanent = c.isDeleted;
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
|
@ -852,13 +864,27 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
if (ciphers.length === 0 && collections.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected"),
|
||||
);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
return;
|
||||
}
|
||||
|
||||
const flexibleCollectionsV1Enabled = await this.flexibleCollectionsV1Enabled();
|
||||
|
||||
const canDeleteCollections =
|
||||
collections == null ||
|
||||
collections.every((c) =>
|
||||
c.canDelete(
|
||||
organizations.find((o) => o.id == c.organizationId),
|
||||
flexibleCollectionsV1Enabled,
|
||||
),
|
||||
);
|
||||
const canDeleteCiphers = ciphers == null || ciphers.every((c) => c.edit);
|
||||
|
||||
if (flexibleCollectionsV1Enabled && (!canDeleteCollections || !canDeleteCiphers)) {
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
|
||||
const dialog = openBulkDeleteDialog(this.dialogService, {
|
||||
data: {
|
||||
permanent: this.filter.type === "trash",
|
||||
|
@ -881,11 +907,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
|
||||
const selectedCipherIds = ciphers.map((cipher) => cipher.id);
|
||||
if (selectedCipherIds.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected"),
|
||||
);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -955,12 +977,17 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(await this.flexibleCollectionsV1Enabled()) &&
|
||||
ciphers.some((c) => c.organizationId != null)
|
||||
) {
|
||||
// You cannot move ciphers between organizations
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ciphers.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected"),
|
||||
);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1016,6 +1043,18 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
replaceUrl: true,
|
||||
});
|
||||
}
|
||||
|
||||
private showMissingPermissionsError() {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("missingPermissions"),
|
||||
});
|
||||
}
|
||||
|
||||
private flexibleCollectionsV1Enabled() {
|
||||
return firstValueFrom(this.flexibleCollectionsV1Enabled$);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -70,6 +70,13 @@ export class BulkCollectionAssignmentDialogComponent implements OnDestroy, OnIni
|
|||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
// If no ciphers are passed in, close the dialog
|
||||
if (this.params.ciphers == null || this.params.ciphers.length < 1) {
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
this.dialogRef.close(BulkCollectionAssignmentDialogResult.Canceled);
|
||||
return;
|
||||
}
|
||||
|
||||
const v1FCEnabled = await this.configService.getFeatureFlag(FeatureFlag.FlexibleCollectionsV1);
|
||||
const restrictProviderAccess = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.RestrictProviderAccess,
|
||||
|
@ -86,12 +93,9 @@ export class BulkCollectionAssignmentDialogComponent implements OnDestroy, OnIni
|
|||
|
||||
// If no ciphers are editable, close the dialog
|
||||
if (this.editableItemCount == 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected"),
|
||||
);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("missingPermissions"));
|
||||
this.dialogRef.close(BulkCollectionAssignmentDialogResult.Canceled);
|
||||
return;
|
||||
}
|
||||
|
||||
this.totalItemCount = this.params.ciphers.length;
|
||||
|
|
|
@ -59,7 +59,7 @@ import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
|||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
|
||||
import { DialogService, Icons } from "@bitwarden/components";
|
||||
import { DialogService, Icons, ToastService } from "@bitwarden/components";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { GroupService, GroupView } from "../../admin-console/organizations/core";
|
||||
|
@ -152,7 +152,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
* A list of collections that the user can assign items to and edit those items within.
|
||||
* @protected
|
||||
*/
|
||||
protected editableCollections$: Observable<CollectionView[]>;
|
||||
protected editableCollections$: Observable<CollectionAdminView[]>;
|
||||
protected allCollectionsWithoutUnassigned$: Observable<CollectionAdminView[]>;
|
||||
private _flexibleCollectionsV1FlagEnabled: boolean;
|
||||
|
||||
|
@ -200,6 +200,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
private collectionService: CollectionService,
|
||||
private organizationUserService: OrganizationUserService,
|
||||
protected configService: ConfigService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
|
@ -567,11 +568,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
if (canEditCipher) {
|
||||
await this.editCipherId(cipherId);
|
||||
} else {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("unknownCipher"),
|
||||
);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("unknownCipher"));
|
||||
await this.router.navigate([], {
|
||||
queryParams: { cipherId: null, itemId: null },
|
||||
queryParamsHandling: "merge",
|
||||
|
@ -596,11 +593,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.viewEvents(cipher);
|
||||
} else {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("unknownCipher"),
|
||||
);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("unknownCipher"));
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate([], {
|
||||
|
@ -765,7 +758,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
} else if (event.type === "viewCollectionAccess") {
|
||||
await this.editCollection(event.item, CollectionDialogTabType.Access, event.readonly);
|
||||
} else if (event.type === "bulkEditCollectionAccess") {
|
||||
await this.bulkEditCollectionAccess(event.items);
|
||||
await this.bulkEditCollectionAccess(event.items, this.organization);
|
||||
} else if (event.type === "assignToCollections") {
|
||||
await this.bulkAssignToCollections(event.items);
|
||||
} else if (event.type === "viewEvents") {
|
||||
|
@ -817,7 +810,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
async editCipherCollections(cipher: CipherView) {
|
||||
let collections: CollectionView[] = [];
|
||||
let collections: CollectionAdminView[] = [];
|
||||
|
||||
if (this.flexibleCollectionsV1Enabled) {
|
||||
// V1 limits admins to only adding items to collections they have access to.
|
||||
|
@ -978,11 +971,20 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
async restore(c: CipherView): Promise<boolean> {
|
||||
if (!(await this.repromptCipher([c]))) {
|
||||
if (!c.isDeleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c.isDeleted) {
|
||||
if (
|
||||
this.flexibleCollectionsV1Enabled &&
|
||||
!c.edit &&
|
||||
!this.organization.allowAdminAccessToAllCollectionItems
|
||||
) {
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this.repromptCipher([c]))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -997,17 +999,21 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
async bulkRestore(ciphers: CipherView[]) {
|
||||
if (
|
||||
this.flexibleCollectionsV1Enabled &&
|
||||
ciphers.some((c) => !c.edit && !this.organization.allowAdminAccessToAllCollectionItems)
|
||||
) {
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this.repromptCipher(ciphers))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedCipherIds = ciphers.map((cipher) => cipher.id);
|
||||
if (selectedCipherIds.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected"),
|
||||
);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1017,6 +1023,15 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
async deleteCipher(c: CipherView): Promise<boolean> {
|
||||
if (
|
||||
this.flexibleCollectionsV1Enabled &&
|
||||
!c.edit &&
|
||||
!this.organization.allowAdminAccessToAllCollectionItems
|
||||
) {
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this.repromptCipher([c]))) {
|
||||
return;
|
||||
}
|
||||
|
@ -1048,11 +1063,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
|
||||
async deleteCollection(collection: CollectionView): Promise<void> {
|
||||
if (!collection.canDelete(this.organization, this.flexibleCollectionsV1Enabled)) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("missingPermissions"),
|
||||
);
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
|
@ -1097,13 +1108,23 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
if (ciphers.length === 0 && collections.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected"),
|
||||
);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
return;
|
||||
}
|
||||
|
||||
const canDeleteCollections =
|
||||
collections == null ||
|
||||
collections.every((c) => c.canDelete(organization, this.flexibleCollectionsV1Enabled));
|
||||
const canDeleteCiphers =
|
||||
ciphers == null ||
|
||||
this.organization.allowAdminAccessToAllCollectionItems ||
|
||||
ciphers.every((c) => c.edit);
|
||||
|
||||
if (this.flexibleCollectionsV1Enabled && (!canDeleteCiphers || !canDeleteCollections)) {
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
|
||||
const dialog = openBulkDeleteDialog(this.dialogService, {
|
||||
data: {
|
||||
permanent: this.filter.type === "trash",
|
||||
|
@ -1228,13 +1249,24 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
async bulkEditCollectionAccess(collections: CollectionView[]): Promise<void> {
|
||||
async bulkEditCollectionAccess(
|
||||
collections: CollectionView[],
|
||||
organization: Organization,
|
||||
): Promise<void> {
|
||||
if (collections.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("noCollectionsSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.flexibleCollectionsV1Enabled &&
|
||||
collections.some((c) => !c.canEdit(organization, this.flexibleCollectionsV1Enabled))
|
||||
) {
|
||||
this.showMissingPermissionsError();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1253,11 +1285,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
|
||||
async bulkAssignToCollections(items: CipherView[]) {
|
||||
if (items.length === 0) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("nothingSelected"),
|
||||
);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1338,6 +1366,14 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
protected readonly CollectionDialogTabType = CollectionDialogTabType;
|
||||
|
||||
private showMissingPermissionsError() {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("missingPermissions"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8196,6 +8196,9 @@
|
|||
"viewAccess": {
|
||||
"message": "View access"
|
||||
},
|
||||
"noCollectionsSelected": {
|
||||
"message": "You have not selected any collections."
|
||||
},
|
||||
"updateName": {
|
||||
"message": "Update name"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"env": {
|
||||
"node": true
|
||||
}
|
||||
}
|
|
@ -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>/",
|
||||
}),
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
describe("Jest", () => {
|
||||
it("is set up", () => {
|
||||
expect(true).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -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();
|
|
@ -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) {}
|
|
@ -0,0 +1,7 @@
|
|||
import { ServiceContainer } from "./service-container";
|
||||
|
||||
describe("ServiceContainer", () => {
|
||||
it("instantiates", async () => {
|
||||
expect(() => new ServiceContainer()).not.toThrow();
|
||||
});
|
||||
});
|
|
@ -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 {}
|
|
@ -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"]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"files": ["../../apps/cli/test.setup.ts"]
|
||||
}
|
|
@ -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;
|
|
@ -16,6 +16,10 @@
|
|||
"name": "cli",
|
||||
"path": "apps/cli",
|
||||
},
|
||||
{
|
||||
"name": "cli (bit)",
|
||||
"path": "bitwarden_license/bit-cli",
|
||||
},
|
||||
{
|
||||
"name": "desktop",
|
||||
"path": "apps/desktop",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -652,6 +652,7 @@ const safeProviders: SafeProvider[] = [
|
|||
LOGOUT_CALLBACK,
|
||||
BillingAccountProfileStateService,
|
||||
TokenServiceAbstraction,
|
||||
AuthServiceAbstraction,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<main
|
||||
class="tw-flex tw-min-h-screen tw-w-full tw-mx-auto tw-flex-col tw-gap-9 tw-px-4 tw-pb-4 tw-pt-14 tw-text-main"
|
||||
class="tw-flex tw-min-h-screen tw-w-full tw-mx-auto tw-flex-col tw-gap-9 tw-bg-background-alt tw-px-4 tw-pb-4 tw-pt-14 tw-text-main"
|
||||
>
|
||||
<div class="tw-text-center">
|
||||
<div class="tw-px-8">
|
||||
|
@ -15,7 +15,7 @@
|
|||
</div>
|
||||
<div class="tw-mb-auto tw-mx-auto tw-flex tw-flex-col tw-items-center">
|
||||
<div
|
||||
class="tw-rounded-xl tw-mb-9 tw-mx-auto sm:tw-border sm:tw-border-solid sm:tw-border-secondary-300 sm:tw-p-8"
|
||||
class="tw-rounded-xl tw-mb-9 tw-mx-auto sm:tw-bg-background sm:tw-border sm:tw-border-solid sm:tw-border-secondary-300 sm:tw-p-8"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
|
|
|
@ -119,6 +119,7 @@ export class AccountServiceImplementation implements InternalAccountService {
|
|||
}
|
||||
|
||||
async switchAccount(userId: UserId): Promise<void> {
|
||||
let updateActivity = false;
|
||||
await this.activeAccountIdState.update(
|
||||
(_, accounts) => {
|
||||
if (userId == null) {
|
||||
|
@ -129,6 +130,7 @@ export class AccountServiceImplementation implements InternalAccountService {
|
|||
if (accounts?.[userId] == null) {
|
||||
throw new Error("Account does not exist");
|
||||
}
|
||||
updateActivity = true;
|
||||
return userId;
|
||||
},
|
||||
{
|
||||
|
@ -139,6 +141,10 @@ export class AccountServiceImplementation implements InternalAccountService {
|
|||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (updateActivity) {
|
||||
await this.setAccountActivity(userId, new Date());
|
||||
}
|
||||
}
|
||||
|
||||
async setAccountActivity(userId: UserId, lastActivity: Date): Promise<void> {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { sequentialize } from "./sequentialize";
|
||||
import { clearCaches, sequentialize } from "./sequentialize";
|
||||
|
||||
describe("sequentialize decorator", () => {
|
||||
it("should call the function once", async () => {
|
||||
|
@ -100,6 +100,18 @@ describe("sequentialize decorator", () => {
|
|||
allRes.sort();
|
||||
expect(allRes).toEqual([3, 3, 6, 6, 9, 9]);
|
||||
});
|
||||
|
||||
describe("clearCaches", () => {
|
||||
it("should clear all caches", async () => {
|
||||
const foo = new Foo();
|
||||
const promise = Promise.all([foo.bar(1), foo.bar(1)]);
|
||||
clearCaches();
|
||||
await foo.bar(1);
|
||||
await promise;
|
||||
// one call for the first two, one for the third after the cache was cleared
|
||||
expect(foo.calls).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class Foo {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
const caches = new Map<any, Map<string, Promise<any>>>();
|
||||
|
||||
const getCache = (obj: any) => {
|
||||
let cache = caches.get(obj);
|
||||
if (cache != null) {
|
||||
return cache;
|
||||
}
|
||||
cache = new Map<string, Promise<any>>();
|
||||
caches.set(obj, cache);
|
||||
return cache;
|
||||
};
|
||||
|
||||
export function clearCaches() {
|
||||
caches.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use as a Decorator on async functions, it will prevent multiple 'active' calls as the same time
|
||||
*
|
||||
|
@ -11,17 +27,6 @@
|
|||
export function sequentialize(cacheKey: (args: any[]) => string) {
|
||||
return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
||||
const originalMethod: () => Promise<any> = descriptor.value;
|
||||
const caches = new Map<any, Map<string, Promise<any>>>();
|
||||
|
||||
const getCache = (obj: any) => {
|
||||
let cache = caches.get(obj);
|
||||
if (cache != null) {
|
||||
return cache;
|
||||
}
|
||||
cache = new Map<string, Promise<any>>();
|
||||
caches.set(obj, cache);
|
||||
return cache;
|
||||
};
|
||||
|
||||
return {
|
||||
value: function (...args: any[]) {
|
||||
|
|
|
@ -61,7 +61,10 @@ export class CollectionView implements View, ITreeNodeObject {
|
|||
return org?.canEditAnyCollection(false) || (org?.canEditAssignedCollections && this.assigned);
|
||||
}
|
||||
|
||||
// For editing collection details, not the items within it.
|
||||
/**
|
||||
* Returns true if the user can edit a collection (including user and group access) from the individual vault.
|
||||
* After FCv1, does not include admin permissions - see {@link CollectionAdminView.canEdit}.
|
||||
*/
|
||||
canEdit(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean {
|
||||
if (org != null && org.id !== this.organizationId) {
|
||||
throw new Error(
|
||||
|
@ -69,12 +72,18 @@ export class CollectionView implements View, ITreeNodeObject {
|
|||
);
|
||||
}
|
||||
|
||||
return org?.flexibleCollections
|
||||
? org?.canEditAnyCollection(flexibleCollectionsV1Enabled) || this.manage
|
||||
: org?.canEditAnyCollection(flexibleCollectionsV1Enabled) || org?.canEditAssignedCollections;
|
||||
if (flexibleCollectionsV1Enabled) {
|
||||
// Only use individual permissions, not admin permissions
|
||||
return this.manage;
|
||||
}
|
||||
|
||||
return org?.canEditAnyCollection(flexibleCollectionsV1Enabled) || this.manage;
|
||||
}
|
||||
|
||||
// For deleting a collection, not the items within it.
|
||||
/**
|
||||
* Returns true if the user can delete a collection from the individual vault.
|
||||
* After FCv1, does not include admin permissions - see {@link CollectionAdminView.canDelete}.
|
||||
*/
|
||||
canDelete(org: Organization, flexibleCollectionsV1Enabled: boolean): boolean {
|
||||
if (org != null && org.id !== this.organizationId) {
|
||||
throw new Error(
|
||||
|
@ -83,6 +92,12 @@ export class CollectionView implements View, ITreeNodeObject {
|
|||
}
|
||||
|
||||
const canDeleteManagedCollections = !org?.limitCollectionCreationDeletion || org.isAdmin;
|
||||
|
||||
if (flexibleCollectionsV1Enabled) {
|
||||
// Only use individual permissions, not admin permissions
|
||||
return canDeleteManagedCollections && this.manage;
|
||||
}
|
||||
|
||||
return (
|
||||
org?.canDeleteAnyCollection(flexibleCollectionsV1Enabled) ||
|
||||
(canDeleteManagedCollections && this.manage)
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
DerivedState,
|
||||
StateProvider,
|
||||
} from "../../platform/state";
|
||||
import { CipherId, CollectionId, OrganizationId } from "../../types/guid";
|
||||
import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid";
|
||||
import { UserKey, OrgKey } from "../../types/key";
|
||||
import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service";
|
||||
import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service";
|
||||
|
@ -136,11 +136,12 @@ export class CipherService implements CipherServiceAbstraction {
|
|||
}
|
||||
|
||||
async setDecryptedCipherCache(value: CipherView[]) {
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
// Sometimes we might prematurely decrypt the vault and that will result in no ciphers
|
||||
// if we cache it then we may accidentially return it when it's not right, we'd rather try decryption again.
|
||||
// if we cache it then we may accidentally return it when it's not right, we'd rather try decryption again.
|
||||
// We still want to set null though, that is the indicator that the cache isn't valid and we should do decryption.
|
||||
if (value == null || value.length !== 0) {
|
||||
await this.setDecryptedCiphers(value);
|
||||
await this.setDecryptedCiphers(value, userId);
|
||||
}
|
||||
if (this.searchService != null) {
|
||||
if (value == null) {
|
||||
|
@ -151,15 +152,16 @@ export class CipherService implements CipherServiceAbstraction {
|
|||
}
|
||||
}
|
||||
|
||||
private async setDecryptedCiphers(value: CipherView[]) {
|
||||
private async setDecryptedCiphers(value: CipherView[], userId: UserId) {
|
||||
const cipherViews: { [id: string]: CipherView } = {};
|
||||
value?.forEach((c) => {
|
||||
cipherViews[c.id] = c;
|
||||
});
|
||||
await this.decryptedCiphersState.update(() => cipherViews);
|
||||
await this.stateProvider.setUserState(DECRYPTED_CIPHERS, cipherViews, userId);
|
||||
}
|
||||
|
||||
async clearCache(userId?: string): Promise<void> {
|
||||
async clearCache(userId?: UserId): Promise<void> {
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
await this.clearDecryptedCiphersState(userId);
|
||||
}
|
||||
|
||||
|
@ -524,6 +526,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||
}
|
||||
|
||||
async updateLastUsedDate(id: string): Promise<void> {
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
let ciphersLocalData = await firstValueFrom(this.localData$);
|
||||
|
||||
if (!ciphersLocalData) {
|
||||
|
@ -553,10 +556,11 @@ export class CipherService implements CipherServiceAbstraction {
|
|||
break;
|
||||
}
|
||||
}
|
||||
await this.setDecryptedCiphers(decryptedCipherCache);
|
||||
await this.setDecryptedCiphers(decryptedCipherCache, userId);
|
||||
}
|
||||
|
||||
async updateLastLaunchedDate(id: string): Promise<void> {
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
let ciphersLocalData = await firstValueFrom(this.localData$);
|
||||
|
||||
if (!ciphersLocalData) {
|
||||
|
@ -586,7 +590,7 @@ export class CipherService implements CipherServiceAbstraction {
|
|||
break;
|
||||
}
|
||||
}
|
||||
await this.setDecryptedCiphers(decryptedCipherCache);
|
||||
await this.setDecryptedCiphers(decryptedCipherCache, userId);
|
||||
}
|
||||
|
||||
async saveNeverDomain(domain: string): Promise<void> {
|
||||
|
@ -833,12 +837,18 @@ export class CipherService implements CipherServiceAbstraction {
|
|||
await this.updateEncryptedCipherState(() => ciphers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates ciphers for the currently active user. Inactive users can only clear all ciphers, for now.
|
||||
* @param update update callback for encrypted cipher data
|
||||
* @returns
|
||||
*/
|
||||
private async updateEncryptedCipherState(
|
||||
update: (current: Record<CipherId, CipherData>) => Record<CipherId, CipherData>,
|
||||
): Promise<Record<CipherId, CipherData>> {
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
// Store that we should wait for an update to return any ciphers
|
||||
await this.ciphersExpectingUpdate.forceValue(true);
|
||||
await this.clearDecryptedCiphersState();
|
||||
await this.clearDecryptedCiphersState(userId);
|
||||
const [, updatedCiphers] = await this.encryptedCiphersState.update((current) => {
|
||||
const result = update(current ?? {});
|
||||
return result;
|
||||
|
@ -846,7 +856,8 @@ export class CipherService implements CipherServiceAbstraction {
|
|||
return updatedCiphers;
|
||||
}
|
||||
|
||||
async clear(userId?: string): Promise<any> {
|
||||
async clear(userId?: UserId): Promise<any> {
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
await this.clearEncryptedCiphersState(userId);
|
||||
await this.clearCache(userId);
|
||||
}
|
||||
|
@ -1464,12 +1475,12 @@ export class CipherService implements CipherServiceAbstraction {
|
|||
}
|
||||
}
|
||||
|
||||
private async clearEncryptedCiphersState(userId?: string) {
|
||||
await this.encryptedCiphersState.update(() => ({}));
|
||||
private async clearEncryptedCiphersState(userId: UserId) {
|
||||
await this.stateProvider.setUserState(ENCRYPTED_CIPHERS, {}, userId);
|
||||
}
|
||||
|
||||
private async clearDecryptedCiphersState(userId?: string) {
|
||||
await this.setDecryptedCiphers(null);
|
||||
private async clearDecryptedCiphersState(userId: UserId) {
|
||||
await this.setDecryptedCiphers(null, userId);
|
||||
this.clearSortedCiphers();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { firstValueFrom } from "rxjs";
|
||||
import { firstValueFrom, map, of, switchMap } from "rxjs";
|
||||
|
||||
import { LogoutReason, UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
|
||||
|
@ -12,10 +12,12 @@ import { PolicyData } from "../../../admin-console/models/data/policy.data";
|
|||
import { ProviderData } from "../../../admin-console/models/data/provider.data";
|
||||
import { PolicyResponse } from "../../../admin-console/models/response/policy.response";
|
||||
import { AccountService } from "../../../auth/abstractions/account.service";
|
||||
import { AuthService } from "../../../auth/abstractions/auth.service";
|
||||
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";
|
||||
|
@ -75,6 +77,7 @@ export class SyncService implements SyncServiceAbstraction {
|
|||
private logoutCallback: (logoutReason: LogoutReason, userId?: UserId) => Promise<void>,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private tokenService: TokenService,
|
||||
private authService: AuthService,
|
||||
) {}
|
||||
|
||||
async getLastSync(): Promise<Date> {
|
||||
|
@ -248,7 +251,19 @@ export class SyncService implements SyncServiceAbstraction {
|
|||
|
||||
async syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise<boolean> {
|
||||
this.syncStarted();
|
||||
if (await this.stateService.getIsAuthenticated()) {
|
||||
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 (
|
||||
|
@ -362,15 +377,14 @@ export class SyncService implements SyncServiceAbstraction {
|
|||
private async setForceSetPasswordReasonIfNeeded(profileResponse: ProfileResponse) {
|
||||
// The `forcePasswordReset` flag indicates an admin has reset the user's password and must be updated
|
||||
if (profileResponse.forcePasswordReset) {
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
await this.masterPasswordService.setForceSetPasswordReason(
|
||||
ForceSetPasswordReason.AdminForcePasswordReset,
|
||||
userId,
|
||||
profileResponse.id,
|
||||
);
|
||||
}
|
||||
|
||||
const userDecryptionOptions = await firstValueFrom(
|
||||
this.userDecryptionOptionsService.userDecryptionOptions$,
|
||||
this.userDecryptionOptionsService.userDecryptionOptionsById$(profileResponse.id),
|
||||
);
|
||||
|
||||
if (userDecryptionOptions === null || userDecryptionOptions === undefined) {
|
||||
|
|
Loading…
Reference in New Issue