1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-30 00:03:30 +00:00

Merge remote-tracking branch 'origin/main' into playwright

This commit is contained in:
Matt Gibson
2026-01-26 12:57:05 -08:00
1790 changed files with 150488 additions and 32025 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "@bitwarden/cli",
"description": "A secure and free password manager for all of your devices.",
"version": "2025.12.0",
"version": "2026.1.0",
"keywords": [
"bitwarden",
"password",
@@ -69,26 +69,26 @@
"browser-hrtime": "1.1.8",
"chalk": "4.1.2",
"commander": "14.0.0",
"core-js": "3.45.0",
"core-js": "3.47.0",
"form-data": "4.0.4",
"https-proxy-agent": "7.0.6",
"inquirer": "8.2.6",
"jsdom": "26.1.0",
"jszip": "3.10.1",
"koa": "2.16.3",
"koa": "3.1.1",
"koa-bodyparser": "4.4.1",
"koa-json": "2.0.2",
"lowdb": "1.0.0",
"lunr": "2.3.9",
"multer": "2.0.2",
"node-fetch": "2.6.12",
"node-fetch": "2.7.0",
"node-forge": "1.3.2",
"open": "10.1.2",
"open": "11.0.0",
"papaparse": "5.5.3",
"proper-lockfile": "4.1.2",
"rxjs": "7.8.1",
"semver": "7.7.3",
"tldts": "7.0.18",
"tldts": "7.0.19",
"zxcvbn": "4.4.2"
}
}

View File

@@ -9,9 +9,7 @@ import {
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
@@ -28,7 +26,6 @@ export class ConfirmCommand {
private encryptService: EncryptService,
private organizationUserApiService: OrganizationUserApiService,
private accountService: AccountService,
private configService: ConfigService,
private i18nService: I18nService,
) {}
@@ -80,11 +77,7 @@ export class ConfirmCommand {
const key = await this.encryptService.encapsulateKeyUnsigned(orgKey, publicKey);
const req = new OrganizationUserConfirmRequest();
req.key = key.encryptedString;
if (
await firstValueFrom(this.configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation))
) {
req.defaultUserCollectionName = await this.getEncryptedDefaultUserCollectionName(orgKey);
}
req.defaultUserCollectionName = await this.getEncryptedDefaultUserCollectionName(orgKey);
await this.organizationUserApiService.postOrganizationUserConfirm(
options.organizationId,
id,

View File

@@ -1,4 +1,4 @@
import { CollectionView } from "@bitwarden/admin-console/common";
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
import { CollectionWithIdExport } from "@bitwarden/common/models/export/collection-with-id.export";
import { BaseResponse } from "../../../models/response/base.response";

View File

@@ -1,4 +1,4 @@
import { CollectionView } from "@bitwarden/admin-console/common";
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
import { SelectionReadOnly } from "../selection-read-only";

View File

@@ -13,6 +13,7 @@ import {
SsoLoginCredentials,
SsoUrlService,
UserApiLoginCredentials,
UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
@@ -31,6 +32,7 @@ import { TwoFactorService, TwoFactorApiService } from "@bitwarden/common/auth/tw
import { ClientType } from "@bitwarden/common/enums";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { EncryptedMigrator } from "@bitwarden/common/key-management/encrypted-migrator/encrypted-migrator.abstraction";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
@@ -81,6 +83,8 @@ export class LoginCommand {
protected ssoUrlService: SsoUrlService,
protected i18nService: I18nService,
protected masterPasswordService: MasterPasswordServiceAbstraction,
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
protected encryptedMigrator: EncryptedMigrator,
) {}
async run(email: string, password: string, options: OptionValues) {
@@ -111,20 +115,14 @@ export class LoginCommand {
} else if (options.sso != null && this.canInteract) {
// If the optional Org SSO Identifier isn't provided, the option value is `true`.
const orgSsoIdentifier = options.sso === true ? null : options.sso;
const passwordOptions: any = {
type: "password",
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
const state = await this.passwordGenerationService.generatePassword(passwordOptions);
ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256");
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
const ssoPromptData = await this.makeSsoPromptData();
ssoCodeVerifier = ssoPromptData.ssoCodeVerifier;
try {
const ssoParams = await this.openSsoPrompt(codeChallenge, state, orgSsoIdentifier);
const ssoParams = await this.openSsoPrompt(
ssoPromptData.codeChallenge,
ssoPromptData.state,
orgSsoIdentifier,
);
ssoCode = ssoParams.ssoCode;
orgIdentifier = ssoParams.orgIdentifier;
} catch {
@@ -229,9 +227,43 @@ export class LoginCommand {
new PasswordLoginCredentials(email, password, twoFactor),
);
}
// Begin Acting on initial AuthResult
if (response.requiresEncryptionKeyMigration) {
return Response.error(this.i18nService.t("legacyEncryptionUnsupported"));
}
// Opting for not checking feature flag since the server will not respond with
// SsoOrganizationIdentifier if the feature flag is not enabled.
if (response.requiresSso && this.canInteract) {
const ssoPromptData = await this.makeSsoPromptData();
ssoCodeVerifier = ssoPromptData.ssoCodeVerifier;
try {
const ssoParams = await this.openSsoPrompt(
ssoPromptData.codeChallenge,
ssoPromptData.state,
response.ssoOrganizationIdentifier,
);
ssoCode = ssoParams.ssoCode;
orgIdentifier = ssoParams.orgIdentifier;
if (ssoCode != null && ssoCodeVerifier != null) {
response = await this.loginStrategyService.logIn(
new SsoLoginCredentials(
ssoCode,
ssoCodeVerifier,
this.ssoRedirectUri,
orgIdentifier,
undefined, // email to look up 2FA token not required as CLI can't remember 2FA token
twoFactor,
),
);
}
} catch {
return Response.badRequest("Something went wrong. Try again.");
}
}
if (response.requiresTwoFactor) {
const twoFactorProviders = await this.twoFactorService.getSupportedProviders(null);
if (twoFactorProviders.length === 0) {
@@ -277,6 +309,10 @@ export class LoginCommand {
if (twoFactorToken == null && selectedProvider.type === TwoFactorProviderType.Email) {
const emailReq = new TwoFactorEmailRequest();
emailReq.email = await this.loginStrategyService.getEmail();
// if the user was logging in with SSO, we need to include the SSO session token
if (response.ssoEmail2FaSessionToken != null) {
emailReq.ssoEmail2FaSessionToken = response.ssoEmail2FaSessionToken;
}
emailReq.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash();
await this.twoFactorApiService.postTwoFactorEmail(emailReq);
}
@@ -322,15 +358,18 @@ export class LoginCommand {
response = await this.loginStrategyService.logInNewDeviceVerification(newDeviceToken);
}
// We check response two factor again here since MFA could fail based on the logic on ln 226
if (response.requiresTwoFactor) {
return Response.error("Login failed.");
}
if (response.resetMasterPassword) {
return Response.error(
"In order to log in with SSO from the CLI, you must first log in" +
" through the web vault to set your master password.",
);
// If we are in the SSO flow and we got a successful login response (we are past rejection scenarios
// and should always have a userId here), validate that SSO user in MP encryption org has MP set
// This must be done here b/c we have 2 places we try to login with SSO above and neither has a
// common handleSsoAuthnResult method to consoldiate this logic into (1. the normal SSO flow and
// 2. the requiresSso automatic authentication flow)
if (ssoCode != null && ssoCodeVerifier != null && response.userId) {
await this.validateSsoUserInMpEncryptionOrgHasMp(response.userId);
}
// Check if Key Connector domain confirmation is required
@@ -367,6 +406,8 @@ export class LoginCommand {
}
}
await this.encryptedMigrator.runMigrations(response.userId, password);
return await this.handleSuccessResponse(response);
} catch (e) {
if (
@@ -688,6 +729,27 @@ export class LoginCommand {
};
}
/// Generate SSO prompt data: code verifier, code challenge, and state
private async makeSsoPromptData(): Promise<{
ssoCodeVerifier: string;
codeChallenge: string;
state: string;
}> {
const passwordOptions: any = {
type: "password",
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
const state = await this.passwordGenerationService.generatePassword(passwordOptions);
const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256");
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
return { ssoCodeVerifier, codeChallenge, state };
}
private async openSsoPrompt(
codeChallenge: string,
state: string,
@@ -778,4 +840,35 @@ export class LoginCommand {
const checkStateSplit = checkState.split("_identifier=");
return stateSplit[0] === checkStateSplit[0];
}
/**
* Validate that a user logging in with SSO that is in an org using MP encryption
* has a MP set. If not, they cannot set a MP in the CLI and must use another client.
* @param userId
* @returns void
*/
private async validateSsoUserInMpEncryptionOrgHasMp(userId: UserId): Promise<void> {
const userDecryptionOptions = await firstValueFrom(
this.userDecryptionOptionsService.userDecryptionOptionsById$(userId),
);
// device trust isn't supported in the CLI as we don't have persistent device key storage.
const notUsingTrustedDeviceEncryption = !userDecryptionOptions.trustedDeviceOption;
const notUsingKeyConnector = !userDecryptionOptions.keyConnectorOption;
if (
notUsingTrustedDeviceEncryption &&
notUsingKeyConnector &&
!userDecryptionOptions.hasMasterPassword
) {
// If user is in an org that is using MP encryption and they JIT provisioned but
// have not yet set a MP and come to the CLI to login, they won't be able to unlock
// or set a MP in the CLI as it isn't supported.
await this.logoutCallback();
throw Response.error(
"In order to log in with SSO from the CLI, you must first log in" +
" through the web vault, the desktop, or the extension to set your master password.",
);
}
}
}

View File

@@ -172,9 +172,7 @@ export abstract class BaseProgram {
} else {
const command = new UnlockCommand(
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.keyService,
this.serviceContainer.userVerificationService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
@@ -182,8 +180,8 @@ export abstract class BaseProgram {
this.serviceContainer.organizationApiService,
this.serviceContainer.logout,
this.serviceContainer.i18nService,
this.serviceContainer.encryptedMigrator,
this.serviceContainer.masterPasswordUnlockService,
this.serviceContainer.configService,
);
const response = await command.run(null, null);
if (!response.success) {

View File

@@ -138,10 +138,8 @@ export class EditCommand {
);
}
const encCipher = await this.cipherService.encrypt(cipherView, activeUserId);
try {
const updatedCipher = await this.cipherService.updateWithServer(encCipher);
const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId);
const decCipher = await this.cipherService.updateWithServer(cipherView, activeUserId);
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
@@ -186,15 +184,15 @@ export class EditCommand {
return Response.notFound();
}
let folderView = await folder.decrypt();
const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId));
let folderView = await folder.decrypt(userKey);
folderView = FolderExport.toView(req, folderView);
const userKey = await this.keyService.getUserKey(activeUserId);
const encFolder = await this.folderService.encrypt(folderView, userKey);
try {
const folder = await this.folderApiService.save(encFolder, activeUserId);
const updatedFolder = new Folder(folder);
const decFolder = await updatedFolder.decrypt();
const decFolder = await updatedFolder.decrypt(userKey);
const res = new FolderResponse(decFolder);
return Response.success(res);
} catch (e) {

View File

@@ -1,12 +1,11 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { filter, firstValueFrom, map, switchMap } from "rxjs";
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
import { CollectionService } from "@bitwarden/admin-console/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
@@ -417,10 +416,11 @@ export class GetCommand extends DownloadCommand {
private async getFolder(id: string) {
let decFolder: FolderView = null;
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId));
if (Utils.isGuid(id)) {
const folder = await this.folderService.getFromState(id, activeUserId);
if (folder != null) {
decFolder = await folder.decrypt();
decFolder = await folder.decrypt(userKey);
}
} else if (id.trim() !== "") {
let folders = await this.folderService.getAllDecryptedFromState(activeUserId);

View File

@@ -1,16 +1,15 @@
import { firstValueFrom, map } from "rxjs";
import { OrganizationUserApiService, CollectionService } from "@bitwarden/admin-console/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import {
OrganizationUserApiService,
CollectionService,
CollectionData,
Collection,
CollectionDetailsResponse as ApiCollectionDetailsResponse,
CollectionResponse as ApiCollectionResponse,
} from "@bitwarden/admin-console/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
} from "@bitwarden/common/admin-console/models/collections";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EventType } from "@bitwarden/common/enums";

View File

@@ -1,12 +1,12 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { firstValueFrom, map } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { UserId } from "@bitwarden/user-core";
import { Response } from "../models/response";
import { TemplateResponse } from "../models/response/template.response";
@@ -17,16 +17,17 @@ export class StatusCommand {
private syncService: SyncService,
private accountService: AccountService,
private authService: AuthService,
private userAutoUnlockKeyService: UserAutoUnlockKeyService,
) {}
async run(): Promise<Response> {
try {
const baseUrl = await this.baseUrl();
const status = await this.status();
const lastSync = await this.syncService.getLastSync();
const [userId, email] = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])),
);
const status = await this.status(userId);
return Response.success(
new TemplateResponse({
@@ -42,12 +43,18 @@ export class StatusCommand {
}
}
private async baseUrl(): Promise<string> {
private async baseUrl(): Promise<string | undefined> {
const env = await firstValueFrom(this.envService.environment$);
return env.getUrls().base;
}
private async status(): Promise<"unauthenticated" | "locked" | "unlocked"> {
private async status(
userId: UserId | undefined,
): Promise<"unauthenticated" | "locked" | "unlocked"> {
if (userId != null) {
await this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(userId);
}
const authStatus = await this.authService.getAuthStatus();
if (authStatus === AuthenticationStatus.Unlocked) {
return "unlocked";

View File

@@ -3,19 +3,16 @@ import { of } from "rxjs";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { MasterPasswordVerificationResponse } from "@bitwarden/common/auth/types/verification";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptedMigrator } from "@bitwarden/common/key-management/encrypted-migrator/encrypted-migrator.abstraction";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
import { MasterPasswordUnlockService } from "@bitwarden/common/key-management/master-password/abstractions/master-password-unlock.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { mockAccountInfoWith } from "@bitwarden/common/spec";
import { CsprngArray } from "@bitwarden/common/types/csprng";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { UserKey } from "@bitwarden/common/types/key";
import { KeyService } from "@bitwarden/key-management";
import { ConsoleLogService } from "@bitwarden/logging";
import { UserId } from "@bitwarden/user-core";
@@ -30,9 +27,7 @@ describe("UnlockCommand", () => {
let command: UnlockCommand;
const accountService = mock<AccountService>();
const masterPasswordService = mock<InternalMasterPasswordServiceAbstraction>();
const keyService = mock<KeyService>();
const userVerificationService = mock<UserVerificationService>();
const cryptoFunctionService = mock<CryptoFunctionService>();
const logService = mock<ConsoleLogService>();
const keyConnectorService = mock<KeyConnectorService>();
@@ -40,15 +35,16 @@ describe("UnlockCommand", () => {
const organizationApiService = mock<OrganizationApiServiceAbstraction>();
const logout = jest.fn();
const i18nService = mock<I18nService>();
const encryptedMigrator = mock<EncryptedMigrator>();
const masterPasswordUnlockService = mock<MasterPasswordUnlockService>();
const configService = mock<ConfigService>();
const mockMasterPassword = "testExample";
const activeAccount: Account = {
id: "user-id" as UserId,
email: "user@example.com",
emailVerified: true,
name: "User",
...mockAccountInfoWith({
email: "user@example.com",
name: "User",
}),
};
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
const mockSessionKey = new Uint8Array(64) as CsprngArray;
@@ -69,9 +65,6 @@ describe("UnlockCommand", () => {
);
expectedSuccessMessage.raw = b64sessionKey;
// Legacy test data
const mockMasterKey = new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey;
beforeEach(async () => {
jest.clearAllMocks();
@@ -82,9 +75,7 @@ describe("UnlockCommand", () => {
command = new UnlockCommand(
accountService,
masterPasswordService,
keyService,
userVerificationService,
cryptoFunctionService,
logService,
keyConnectorService,
@@ -92,8 +83,8 @@ describe("UnlockCommand", () => {
organizationApiService,
logout,
i18nService,
encryptedMigrator,
masterPasswordUnlockService,
configService,
);
});
@@ -128,116 +119,46 @@ describe("UnlockCommand", () => {
},
);
describe("UnlockWithMasterPasswordUnlockData feature flag enabled", () => {
beforeEach(() => {
configService.getFeatureFlag$.mockReturnValue(of(true));
});
it("calls masterPasswordUnlockService successfully", async () => {
masterPasswordUnlockService.unlockWithMasterPassword.mockResolvedValue(mockUserKey);
it("calls masterPasswordUnlockService successfully", async () => {
masterPasswordUnlockService.unlockWithMasterPassword.mockResolvedValue(mockUserKey);
const response = await command.run(mockMasterPassword, {});
const response = await command.run(mockMasterPassword, {});
expect(response).not.toBeNull();
expect(response.success).toEqual(true);
expect(response.data).toEqual(expectedSuccessMessage);
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
});
it("returns error response if unlockWithMasterPassword fails", async () => {
masterPasswordUnlockService.unlockWithMasterPassword.mockRejectedValue(
new Error("Unlock failed"),
);
const response = await command.run(mockMasterPassword, {});
expect(response).not.toBeNull();
expect(response.success).toEqual(false);
expect(response.message).toEqual("Unlock failed");
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
expect(keyService.setUserKey).not.toHaveBeenCalled();
});
expect(response).not.toBeNull();
expect(response.success).toEqual(true);
expect(response.data).toEqual(expectedSuccessMessage);
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
});
describe("unlock with feature flag off", () => {
beforeEach(() => {
configService.getFeatureFlag$.mockReturnValue(of(false));
});
it("returns error response if unlockWithMasterPassword fails", async () => {
masterPasswordUnlockService.unlockWithMasterPassword.mockRejectedValue(
new Error("Unlock failed"),
);
it("calls decryptUserKeyWithMasterKey successfully", async () => {
userVerificationService.verifyUserByMasterPassword.mockResolvedValue({
masterKey: mockMasterKey,
} as MasterPasswordVerificationResponse);
masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(mockUserKey);
const response = await command.run(mockMasterPassword, {});
const response = await command.run(mockMasterPassword, {});
expect(response).not.toBeNull();
expect(response.success).toEqual(true);
expect(response.data).toEqual(expectedSuccessMessage);
expect(userVerificationService.verifyUserByMasterPassword).toHaveBeenCalledWith(
{
type: VerificationType.MasterPassword,
secret: mockMasterPassword,
},
activeAccount.id,
activeAccount.email,
);
expect(masterPasswordService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(
mockMasterKey,
activeAccount.id,
);
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
});
it("returns error response when verifyUserByMasterPassword throws", async () => {
userVerificationService.verifyUserByMasterPassword.mockRejectedValue(
new Error("Verification failed"),
);
const response = await command.run(mockMasterPassword, {});
expect(response).not.toBeNull();
expect(response.success).toEqual(false);
expect(response.message).toEqual("Verification failed");
expect(userVerificationService.verifyUserByMasterPassword).toHaveBeenCalledWith(
{
type: VerificationType.MasterPassword,
secret: mockMasterPassword,
},
activeAccount.id,
activeAccount.email,
);
expect(masterPasswordService.decryptUserKeyWithMasterKey).not.toHaveBeenCalled();
expect(keyService.setUserKey).not.toHaveBeenCalled();
});
expect(response).not.toBeNull();
expect(response.success).toEqual(false);
expect(response.message).toEqual("Unlock failed");
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
expect(keyService.setUserKey).not.toHaveBeenCalled();
});
describe("calls convertToKeyConnectorCommand if required", () => {
let convertToKeyConnectorSpy: jest.SpyInstance;
beforeEach(() => {
keyConnectorService.convertAccountRequired$ = of(true);
// Feature flag on
masterPasswordUnlockService.unlockWithMasterPassword.mockResolvedValue(mockUserKey);
// Feature flag off
userVerificationService.verifyUserByMasterPassword.mockResolvedValue({
masterKey: mockMasterKey,
} as MasterPasswordVerificationResponse);
masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(mockUserKey);
});
test.each([true, false])("returns failure when feature flag is %s", async (flagValue) => {
configService.getFeatureFlag$.mockReturnValue(of(flagValue));
it("returns error on failure", async () => {
// Mock the ConvertToKeyConnectorCommand
const mockRun = jest.fn().mockResolvedValue({ success: false, message: "convert failed" });
convertToKeyConnectorSpy = jest
@@ -252,67 +173,32 @@ describe("UnlockCommand", () => {
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
expect(convertToKeyConnectorSpy).toHaveBeenCalled();
if (flagValue === true) {
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
} else {
expect(userVerificationService.verifyUserByMasterPassword).toHaveBeenCalledWith(
{
type: VerificationType.MasterPassword,
secret: mockMasterPassword,
},
activeAccount.id,
activeAccount.email,
);
expect(masterPasswordService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(
mockMasterKey,
activeAccount.id,
);
}
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
});
test.each([true, false])(
"returns expected success when feature flag is %s",
async (flagValue) => {
configService.getFeatureFlag$.mockReturnValue(of(flagValue));
it("returns success on successful conversion", async () => {
// Mock the ConvertToKeyConnectorCommand
const mockRun = jest.fn().mockResolvedValue({ success: true });
const convertToKeyConnectorSpy = jest
.spyOn(ConvertToKeyConnectorCommand.prototype, "run")
.mockImplementation(mockRun);
// Mock the ConvertToKeyConnectorCommand
const mockRun = jest.fn().mockResolvedValue({ success: true });
const convertToKeyConnectorSpy = jest
.spyOn(ConvertToKeyConnectorCommand.prototype, "run")
.mockImplementation(mockRun);
const response = await command.run(mockMasterPassword, {});
const response = await command.run(mockMasterPassword, {});
expect(response).not.toBeNull();
expect(response.success).toEqual(true);
expect(response.data).toEqual(expectedSuccessMessage);
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
expect(convertToKeyConnectorSpy).toHaveBeenCalled();
expect(response).not.toBeNull();
expect(response.success).toEqual(true);
expect(response.data).toEqual(expectedSuccessMessage);
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
expect(convertToKeyConnectorSpy).toHaveBeenCalled();
if (flagValue === true) {
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
} else {
expect(userVerificationService.verifyUserByMasterPassword).toHaveBeenCalledWith(
{
type: VerificationType.MasterPassword,
secret: mockMasterPassword,
},
activeAccount.id,
activeAccount.email,
);
expect(masterPasswordService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(
mockMasterKey,
activeAccount.id,
);
}
},
);
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
});
});
});
});

View File

@@ -4,19 +4,13 @@ import { firstValueFrom } from "rxjs";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptedMigrator } from "@bitwarden/common/key-management/encrypted-migrator/encrypted-migrator.abstraction";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
import { MasterPasswordUnlockService } from "@bitwarden/common/key-management/master-password/abstractions/master-password-unlock.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
import { MasterKey } from "@bitwarden/common/types/key";
import { KeyService } from "@bitwarden/key-management";
import { Response } from "../../models/response";
@@ -28,9 +22,7 @@ import { ConvertToKeyConnectorCommand } from "../convert-to-key-connector.comman
export class UnlockCommand {
constructor(
private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private keyService: KeyService,
private userVerificationService: UserVerificationService,
private cryptoFunctionService: CryptoFunctionService,
private logService: ConsoleLogService,
private keyConnectorService: KeyConnectorService,
@@ -38,8 +30,8 @@ export class UnlockCommand {
private organizationApiService: OrganizationApiServiceAbstraction,
private logout: () => Promise<void>,
private i18nService: I18nService,
private encryptedMigrator: EncryptedMigrator,
private masterPasswordUnlockService: MasterPasswordUnlockService,
private configService: ConfigService,
) {}
async run(password: string, cmdOptions: Record<string, any>) {
@@ -59,46 +51,15 @@ export class UnlockCommand {
}
const userId = activeAccount.id;
if (
await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.UnlockWithMasterPasswordUnlockData),
)
) {
try {
const userKey = await this.masterPasswordUnlockService.unlockWithMasterPassword(
password,
userId,
);
await this.keyService.setUserKey(userKey, userId);
} catch (e) {
return Response.error(e.message);
}
} else {
const email = activeAccount.email;
const verification = {
type: VerificationType.MasterPassword,
secret: password,
} as MasterPasswordVerification;
let masterKey: MasterKey;
try {
const response = await this.userVerificationService.verifyUserByMasterPassword(
verification,
userId,
email,
);
masterKey = response.masterKey;
} catch (e) {
// verification failure throws
return Response.error(e.message);
}
const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
masterKey,
try {
const userKey = await this.masterPasswordUnlockService.unlockWithMasterPassword(
password,
userId,
);
await this.keyService.setUserKey(userKey, userId);
} catch (e) {
return Response.error(e.message);
}
if (await firstValueFrom(this.keyConnectorService.convertAccountRequired$)) {
@@ -116,6 +77,8 @@ export class UnlockCommand {
}
}
await this.encryptedMigrator.runMigrations(userId, password);
return this.successResponse();
}

View File

@@ -0,0 +1,15 @@
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
import {
VaultTimeout,
VaultTimeoutStringType,
} from "@bitwarden/common/key-management/vault-timeout";
export class CliSessionTimeoutTypeService implements SessionTimeoutTypeService {
async isAvailable(timeout: VaultTimeout): Promise<boolean> {
return timeout === VaultTimeoutStringType.Never;
}
async getOrPromoteToAvailable(_: VaultTimeout): Promise<VaultTimeout> {
return VaultTimeoutStringType.Never;
}
}

View File

@@ -122,6 +122,7 @@ export class OssServeConfigurator {
this.serviceContainer.syncService,
this.serviceContainer.accountService,
this.serviceContainer.authService,
this.serviceContainer.userAutoUnlockKeyService,
);
this.deleteCommand = new DeleteCommand(
this.serviceContainer.cipherService,
@@ -146,7 +147,6 @@ export class OssServeConfigurator {
this.serviceContainer.encryptService,
this.serviceContainer.organizationUserApiService,
this.serviceContainer.accountService,
this.serviceContainer.configService,
this.serviceContainer.i18nService,
);
this.restoreCommand = new RestoreCommand(
@@ -166,9 +166,7 @@ export class OssServeConfigurator {
);
this.unlockCommand = new UnlockCommand(
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.keyService,
this.serviceContainer.userVerificationService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
@@ -176,8 +174,8 @@ export class OssServeConfigurator {
this.serviceContainer.organizationApiService,
async () => await this.serviceContainer.logout(),
this.serviceContainer.i18nService,
this.serviceContainer.encryptedMigrator,
this.serviceContainer.masterPasswordUnlockService,
this.serviceContainer.configService,
);
this.sendCreateCommand = new SendCreateCommand(

View File

@@ -3,6 +3,8 @@ import * as sdk from "@bitwarden/sdk-internal";
export class CliSdkLoadService extends SdkLoadService {
async load(): Promise<void> {
// CLI uses stdout for user interaction / automations so we cannot log info / debug here.
SdkLoadService.logLevel = sdk.LogLevel.Error;
const module = await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm");
(sdk as any).init(module);
}

View File

@@ -195,6 +195,8 @@ export class Program extends BaseProgram {
this.serviceContainer.ssoUrlService,
this.serviceContainer.i18nService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.userDecryptionOptionsService,
this.serviceContainer.encryptedMigrator,
);
const response = await command.run(email, password, options);
this.processResponse(response, true);
@@ -277,6 +279,11 @@ export class Program extends BaseProgram {
})
.option("--check", "Check lock status.", async () => {
await this.exitIfNotAuthed();
const userId = (await firstValueFrom(this.serviceContainer.accountService.activeAccount$))
?.id;
await this.serviceContainer.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(
userId,
);
const authStatus = await this.serviceContainer.authService.getAuthStatus();
if (authStatus === AuthenticationStatus.Unlocked) {
@@ -296,9 +303,7 @@ export class Program extends BaseProgram {
await this.exitIfNotAuthed();
const command = new UnlockCommand(
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.keyService,
this.serviceContainer.userVerificationService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
@@ -306,8 +311,8 @@ export class Program extends BaseProgram {
this.serviceContainer.organizationApiService,
async () => await this.serviceContainer.logout(),
this.serviceContainer.i18nService,
this.serviceContainer.encryptedMigrator,
this.serviceContainer.masterPasswordUnlockService,
this.serviceContainer.configService,
);
const response = await command.run(password, cmd);
this.processResponse(response);
@@ -517,6 +522,7 @@ export class Program extends BaseProgram {
this.serviceContainer.syncService,
this.serviceContainer.accountService,
this.serviceContainer.authService,
this.serviceContainer.userAutoUnlockKeyService,
);
const response = await command.run();
this.processResponse(response);

View File

@@ -69,6 +69,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service";
import { ClientType } from "@bitwarden/common/enums";
import { DefaultAccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/default-account-cryptographic-state.service";
import {
DefaultKeyGenerationService,
KeyGenerationService,
@@ -76,6 +77,10 @@ import {
import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
import { DeviceTrustService } from "@bitwarden/common/key-management/device-trust/services/device-trust.service.implementation";
import { DefaultEncryptedMigrator } from "@bitwarden/common/key-management/encrypted-migrator/default-encrypted-migrator";
import { EncryptedMigrator } from "@bitwarden/common/key-management/encrypted-migrator/encrypted-migrator.abstraction";
import { DefaultChangeKdfApiService } from "@bitwarden/common/key-management/kdf/change-kdf-api.service";
import { DefaultChangeKdfService } from "@bitwarden/common/key-management/kdf/change-kdf.service";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/services/key-connector.service";
import { MasterPasswordUnlockService } from "@bitwarden/common/key-management/master-password/abstractions/master-password-unlock.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
@@ -99,6 +104,7 @@ import {
EnvironmentService,
RegionConfig,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { LogLevelType } from "@bitwarden/common/platform/enums";
@@ -119,6 +125,7 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r
import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory";
import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service";
import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory";
import { DefaultRegisterSdkService } from "@bitwarden/common/platform/services/sdk/register-sdk.service";
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
import { SyncService } from "@bitwarden/common/platform/sync";
@@ -140,11 +147,13 @@ import { SendService } from "@bitwarden/common/tools/send/services/send.service"
import { UserId } from "@bitwarden/common/types/guid";
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service";
import { CipherSdkService } from "@bitwarden/common/vault/abstractions/cipher-sdk.service";
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import {
CipherAuthorizationService,
DefaultCipherAuthorizationService,
} from "@bitwarden/common/vault/services/cipher-authorization.service";
import { DefaultCipherSdkService } from "@bitwarden/common/vault/services/cipher-sdk.service";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
import { DefaultCipherArchiveService } from "@bitwarden/common/vault/services/default-cipher-archive.service";
import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service";
@@ -207,6 +216,7 @@ import {
import { CliBiometricsService } from "../key-management/cli-biometrics-service";
import { CliProcessReloadService } from "../key-management/cli-process-reload.service";
import { CliSessionTimeoutTypeService } from "../key-management/session-timeout/services/cli-session-timeout-type.service";
import { flagEnabled } from "../platform/flags";
import { CliPlatformUtilsService } from "../platform/services/cli-platform-utils.service";
import { CliSdkLoadService } from "../platform/services/cli-sdk-load.service";
@@ -246,6 +256,7 @@ export class ServiceContainer {
twoFactorApiService: TwoFactorApiService;
hibpApiService: HibpApiService;
environmentService: EnvironmentService;
cipherSdkService: CipherSdkService;
cipherService: CipherService;
folderService: InternalFolderService;
organizationUserApiService: OrganizationUserApiService;
@@ -317,6 +328,7 @@ export class ServiceContainer {
kdfConfigService: KdfConfigService;
taskSchedulerService: TaskSchedulerService;
sdkService: SdkService;
registerSdkService: RegisterSdkService;
sdkLoadService: SdkLoadService;
cipherAuthorizationService: CipherAuthorizationService;
ssoUrlService: SsoUrlService;
@@ -324,10 +336,12 @@ export class ServiceContainer {
cipherEncryptionService: CipherEncryptionService;
restrictedItemTypesService: RestrictedItemTypesService;
cliRestrictedItemTypesService: CliRestrictedItemTypesService;
encryptedMigrator: EncryptedMigrator;
securityStateService: SecurityStateService;
masterPasswordUnlockService: MasterPasswordUnlockService;
cipherArchiveService: CipherArchiveService;
lockService: LockService;
private accountCryptographicStateService: DefaultAccountCryptographicStateService;
constructor() {
let p = null;
@@ -486,10 +500,7 @@ export class ServiceContainer {
const pinStateService = new PinStateService(this.stateProvider);
this.pinService = new PinService(
this.accountService,
this.encryptService,
this.kdfConfigService,
this.keyGenerationService,
this.logService,
this.keyService,
this.sdkService,
@@ -518,7 +529,13 @@ export class ServiceContainer {
this.ssoUrlService = new SsoUrlService();
this.organizationService = new DefaultOrganizationService(this.stateProvider);
this.policyService = new DefaultPolicyService(this.stateProvider, this.organizationService);
this.policyService = new DefaultPolicyService(
this.stateProvider,
this.organizationService,
this.accountService,
);
const sessionTimeoutTypeService = new CliSessionTimeoutTypeService();
this.vaultTimeoutSettingsService = new DefaultVaultTimeoutSettingsService(
this.accountService,
@@ -531,6 +548,7 @@ export class ServiceContainer {
this.stateProvider,
this.logService,
VaultTimeoutStringType.Never, // default vault timeout
sessionTimeoutTypeService,
);
const refreshAccessTokenErrorCallback = () => {
@@ -620,26 +638,10 @@ export class ServiceContainer {
this.accountService,
);
this.keyConnectorService = new KeyConnectorService(
this.accountService,
this.masterPasswordService,
this.keyService,
this.apiService,
this.tokenService,
this.logService,
this.organizationService,
this.keyGenerationService,
logoutCallback,
this.accountCryptographicStateService = new DefaultAccountCryptographicStateService(
this.stateProvider,
);
this.twoFactorService = new DefaultTwoFactorService(
this.i18nService,
this.platformUtilsService,
this.globalStateProvider,
this.twoFactorApiService,
);
const sdkClientFactory = flagEnabled("sdk")
? new DefaultSdkClientFactory()
: new NoopSdkClientFactory();
@@ -658,6 +660,41 @@ export class ServiceContainer {
customUserAgent,
);
this.registerSdkService = new DefaultRegisterSdkService(
sdkClientFactory,
this.environmentService,
this.platformUtilsService,
this.accountService,
this.apiService,
this.stateProvider,
this.configService,
customUserAgent,
);
this.keyConnectorService = new KeyConnectorService(
this.accountService,
this.masterPasswordService,
this.keyService,
this.apiService,
this.tokenService,
this.logService,
this.organizationService,
this.keyGenerationService,
logoutCallback,
this.stateProvider,
this.configService,
this.registerSdkService,
this.securityStateService,
this.accountCryptographicStateService,
);
this.twoFactorService = new DefaultTwoFactorService(
this.i18nService,
this.platformUtilsService,
this.globalStateProvider,
this.twoFactorApiService,
);
this.passwordStrengthService = new PasswordStrengthService();
this.passwordGenerationService = legacyPasswordGenerationServiceFactory(
@@ -734,6 +771,7 @@ export class ServiceContainer {
this.kdfConfigService,
this.taskSchedulerService,
this.configService,
this.accountCryptographicStateService,
);
this.restrictedItemTypesService = new RestrictedItemTypesService(
@@ -759,6 +797,8 @@ export class ServiceContainer {
this.logService,
);
this.cipherSdkService = new DefaultCipherSdkService(this.sdkService, this.logService);
this.cipherService = new CipherService(
this.keyService,
this.domainSettingsService,
@@ -774,6 +814,7 @@ export class ServiceContainer {
this.logService,
this.cipherEncryptionService,
this.messagingService,
this.cipherSdkService,
);
this.cipherArchiveService = new DefaultCipherArchiveService(
@@ -869,6 +910,7 @@ export class ServiceContainer {
this.stateProvider,
this.securityStateService,
this.kdfConfigService,
this.accountCryptographicStateService,
);
this.totpService = new TotpService(this.sdkService);
@@ -895,7 +937,7 @@ export class ServiceContainer {
this.collectionService,
this.keyService,
this.encryptService,
this.pinService,
this.keyGenerationService,
this.accountService,
this.restrictedItemTypesService,
);
@@ -903,7 +945,7 @@ export class ServiceContainer {
this.individualExportService = new IndividualVaultExportService(
this.folderService,
this.cipherService,
this.pinService,
this.keyGenerationService,
this.keyService,
this.encryptService,
this.cryptoFunctionService,
@@ -917,7 +959,7 @@ export class ServiceContainer {
this.organizationExportService = new OrganizationVaultExportService(
this.cipherService,
this.vaultExportApiService,
this.pinService,
this.keyGenerationService,
this.keyService,
this.encryptService,
this.cryptoFunctionService,
@@ -971,6 +1013,21 @@ export class ServiceContainer {
);
this.masterPasswordApiService = new MasterPasswordApiService(this.apiService, this.logService);
const changeKdfApiService = new DefaultChangeKdfApiService(this.apiService);
const changeKdfService = new DefaultChangeKdfService(
changeKdfApiService,
this.sdkService,
this.keyService,
this.masterPasswordService,
);
this.encryptedMigrator = new DefaultEncryptedMigrator(
this.kdfConfigService,
changeKdfService,
this.logService,
this.configService,
this.masterPasswordService,
this.syncService,
);
}
async logout() {
@@ -1007,7 +1064,6 @@ export class ServiceContainer {
this.containerService.attachToGlobal(global);
await this.i18nService.init();
this.twoFactorService.init();
this.encryptService.init(this.configService);
// If a user has a BW_SESSION key stored in their env (not process.env.BW_SESSION),
// this should set the user key to unlock the vault on init.

View File

@@ -9,9 +9,9 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { NodeUtils } from "@bitwarden/node/node-utils";
import { Response } from "../../../models/response";

View File

@@ -5,9 +5,9 @@ import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { Response } from "../../../models/response";
import { CliUtils } from "../../../utils";

View File

@@ -13,11 +13,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendAccess } from "@bitwarden/common/tools/send/models/domain/send-access";
import { SendAccessRequest } from "@bitwarden/common/tools/send/models/request/send-access.request";
import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { KeyService } from "@bitwarden/key-management";
import { NodeUtils } from "@bitwarden/node/node-utils";

View File

@@ -1,4 +1,4 @@
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { Response } from "../../../models/response";
import { TemplateResponse } from "../../../models/response/template.response";

View File

@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { BaseResponse } from "../../../models/response/base.response";

View File

@@ -1,8 +1,8 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { BaseResponse } from "../../../models/response/base.response";

View File

@@ -7,7 +7,7 @@ import * as chalk from "chalk";
import { program, Command, Option, OptionValues } from "commander";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { BaseProgram } from "../../base-program";
import { Response } from "../../models/response";

View File

@@ -1,12 +1,10 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import * as fs from "fs";
import * as path from "path";
import * as inquirer from "inquirer";
import * as JSZip from "jszip";
import { CollectionView } from "@bitwarden/admin-console/common";
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";

View File

@@ -494,7 +494,6 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.encryptService,
this.serviceContainer.organizationUserApiService,
this.serviceContainer.accountService,
this.serviceContainer.configService,
this.serviceContainer.i18nService,
);
const response = await command.run(object, id, cmd);

View File

@@ -103,10 +103,11 @@ export class CreateCommand {
return Response.error("Creating this item type is restricted by organizational policy.");
}
const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId);
const newCipher = await this.cipherService.createWithServer(cipher);
const decCipher = await this.cipherService.decrypt(newCipher, activeUserId);
const res = new CipherResponse(decCipher);
const newCipher = await this.cipherService.createWithServer(
CipherExport.toView(req),
activeUserId,
);
const res = new CipherResponse(newCipher);
return Response.success(res);
} catch (e) {
return Response.error(e);
@@ -181,12 +182,12 @@ export class CreateCommand {
private async createFolder(req: FolderExport) {
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const userKey = await this.keyService.getUserKey(activeUserId);
const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId));
const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey);
try {
const folderData = await this.folderApiService.save(folder, activeUserId);
const newFolder = new Folder(folderData);
const decFolder = await newFolder.decrypt();
const decFolder = await newFolder.decrypt(userKey);
const res = new FolderResponse(decFolder);
return Response.success(res);
} catch (e) {

View File

@@ -10,7 +10,7 @@
<authors>Bitwarden Inc.</authors>
<projectUrl>https://bitwarden.com/</projectUrl>
<iconUrl>https://raw.githubusercontent.com/bitwarden/brand/master/icons/256x256.png</iconUrl>
<copyright>Copyright © 2015-2025 Bitwarden Inc.</copyright>
<copyright>Copyright © 2015-2026 Bitwarden Inc.</copyright>
<projectSourceUrl>https://github.com/bitwarden/clients/</projectSourceUrl>
<docsUrl>https://help.bitwarden.com/article/cli/</docsUrl>
<bugTrackerUrl>https://github.com/bitwarden/clients/issues</bugTrackerUrl>