mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 22:33:35 +00:00
[PM-23246] CLI unlock with masterPasswordUnlockData (#16217)
* unlock with masterPasswordUnlockData in the CLI
This commit is contained in:
@@ -7,7 +7,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
|
|||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
import { UnlockCommand } from "./auth/commands/unlock.command";
|
import { UnlockCommand } from "./key-management/commands/unlock.command";
|
||||||
import { Response } from "./models/response";
|
import { Response } from "./models/response";
|
||||||
import { ListResponse } from "./models/response/list.response";
|
import { ListResponse } from "./models/response/list.response";
|
||||||
import { MessageResponse } from "./models/response/message.response";
|
import { MessageResponse } from "./models/response/message.response";
|
||||||
@@ -182,6 +182,8 @@ export abstract class BaseProgram {
|
|||||||
this.serviceContainer.organizationApiService,
|
this.serviceContainer.organizationApiService,
|
||||||
this.serviceContainer.logout,
|
this.serviceContainer.logout,
|
||||||
this.serviceContainer.i18nService,
|
this.serviceContainer.i18nService,
|
||||||
|
this.serviceContainer.masterPasswordUnlockService,
|
||||||
|
this.serviceContainer.configService,
|
||||||
);
|
);
|
||||||
const response = await command.run(null, null);
|
const response = await command.run(null, null);
|
||||||
if (!response.success) {
|
if (!response.success) {
|
||||||
|
|||||||
318
apps/cli/src/key-management/commands/unlock.command.spec.ts
Normal file
318
apps/cli/src/key-management/commands/unlock.command.spec.ts
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
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 { 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 { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||||
|
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||||
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
import { ConsoleLogService } from "@bitwarden/logging";
|
||||||
|
import { UserId } from "@bitwarden/user-core";
|
||||||
|
|
||||||
|
import { MessageResponse } from "../../models/response/message.response";
|
||||||
|
import { I18nService } from "../../platform/services/i18n.service";
|
||||||
|
import { ConvertToKeyConnectorCommand } from "../convert-to-key-connector.command";
|
||||||
|
|
||||||
|
import { UnlockCommand } from "./unlock.command";
|
||||||
|
|
||||||
|
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>();
|
||||||
|
const environmentService = mock<EnvironmentService>();
|
||||||
|
const organizationApiService = mock<OrganizationApiServiceAbstraction>();
|
||||||
|
const logout = jest.fn();
|
||||||
|
const i18nService = mock<I18nService>();
|
||||||
|
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",
|
||||||
|
};
|
||||||
|
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
|
||||||
|
const mockSessionKey = new Uint8Array(64) as CsprngArray;
|
||||||
|
const b64sessionKey = Utils.fromBufferToB64(mockSessionKey);
|
||||||
|
const expectedSuccessMessage = new MessageResponse(
|
||||||
|
"Your vault is now unlocked!",
|
||||||
|
"\n" +
|
||||||
|
"To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n" +
|
||||||
|
'$ export BW_SESSION="' +
|
||||||
|
b64sessionKey +
|
||||||
|
'"\n' +
|
||||||
|
'> $env:BW_SESSION="' +
|
||||||
|
b64sessionKey +
|
||||||
|
'"\n\n' +
|
||||||
|
"You can also pass the session key to any command with the `--session` option. ex:\n" +
|
||||||
|
"$ bw list items --session " +
|
||||||
|
b64sessionKey,
|
||||||
|
);
|
||||||
|
expectedSuccessMessage.raw = b64sessionKey;
|
||||||
|
|
||||||
|
// Legacy test data
|
||||||
|
const mockMasterKey = new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
i18nService.t.mockImplementation((key: string) => key);
|
||||||
|
accountService.activeAccount$ = of(activeAccount);
|
||||||
|
keyConnectorService.convertAccountRequired$ = of(false);
|
||||||
|
cryptoFunctionService.randomBytes.mockResolvedValue(mockSessionKey);
|
||||||
|
|
||||||
|
command = new UnlockCommand(
|
||||||
|
accountService,
|
||||||
|
masterPasswordService,
|
||||||
|
keyService,
|
||||||
|
userVerificationService,
|
||||||
|
cryptoFunctionService,
|
||||||
|
logService,
|
||||||
|
keyConnectorService,
|
||||||
|
environmentService,
|
||||||
|
organizationApiService,
|
||||||
|
logout,
|
||||||
|
i18nService,
|
||||||
|
masterPasswordUnlockService,
|
||||||
|
configService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("run", () => {
|
||||||
|
test.each([null as unknown as Account, undefined as unknown as Account])(
|
||||||
|
"returns error response when the active account is %s",
|
||||||
|
async (account) => {
|
||||||
|
accountService.activeAccount$ = of(account);
|
||||||
|
|
||||||
|
const response = await command.run(mockMasterPassword, {});
|
||||||
|
|
||||||
|
expect(response).not.toBeNull();
|
||||||
|
expect(response.success).toEqual(false);
|
||||||
|
expect(response.message).toEqual("No active account found");
|
||||||
|
expect(keyService.setUserKey).not.toHaveBeenCalled();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.each([null as unknown as string, undefined as unknown as string, ""])(
|
||||||
|
"returns error response when the provided password is %s",
|
||||||
|
async (mockMasterPassword) => {
|
||||||
|
process.env.BW_NOINTERACTION = "true";
|
||||||
|
|
||||||
|
const response = await command.run(mockMasterPassword, {});
|
||||||
|
|
||||||
|
expect(response).not.toBeNull();
|
||||||
|
expect(response.success).toEqual(false);
|
||||||
|
expect(response.message).toEqual(
|
||||||
|
"Master password is required. Try again in interactive mode or provide a password file or environment variable.",
|
||||||
|
);
|
||||||
|
expect(keyService.setUserKey).not.toHaveBeenCalled();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("UnlockWithMasterPasswordUnlockData feature flag enabled", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configService.getFeatureFlag$.mockReturnValue(of(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls masterPasswordUnlockService successfully", async () => {
|
||||||
|
masterPasswordUnlockService.unlockWithMasterPassword.mockResolvedValue(mockUserKey);
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("unlock with feature flag off", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configService.getFeatureFlag$.mockReturnValue(of(false));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls decryptUserKeyWithMasterKey successfully", async () => {
|
||||||
|
userVerificationService.verifyUserByMasterPassword.mockResolvedValue({
|
||||||
|
masterKey: mockMasterKey,
|
||||||
|
} as MasterPasswordVerificationResponse);
|
||||||
|
masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(mockUserKey);
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
// Mock the ConvertToKeyConnectorCommand
|
||||||
|
const mockRun = jest.fn().mockResolvedValue({ success: false, message: "convert failed" });
|
||||||
|
convertToKeyConnectorSpy = jest
|
||||||
|
.spyOn(ConvertToKeyConnectorCommand.prototype, "run")
|
||||||
|
.mockImplementation(mockRun);
|
||||||
|
|
||||||
|
const response = await command.run(mockMasterPassword, {});
|
||||||
|
|
||||||
|
expect(response).not.toBeNull();
|
||||||
|
expect(response.success).toEqual(false);
|
||||||
|
expect(response.message).toEqual("convert failed");
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([true, false])(
|
||||||
|
"returns expected success when feature flag is %s",
|
||||||
|
async (flagValue) => {
|
||||||
|
configService.getFeatureFlag$.mockReturnValue(of(flagValue));
|
||||||
|
|
||||||
|
// 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, {});
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,26 +1,29 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { firstValueFrom, map } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||||
import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification";
|
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 { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||||
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
|
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 { 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 { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||||
import { MasterKey } from "@bitwarden/common/types/key";
|
import { MasterKey } from "@bitwarden/common/types/key";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { ConvertToKeyConnectorCommand } from "../../key-management/convert-to-key-connector.command";
|
|
||||||
import { Response } from "../../models/response";
|
import { Response } from "../../models/response";
|
||||||
import { MessageResponse } from "../../models/response/message.response";
|
import { MessageResponse } from "../../models/response/message.response";
|
||||||
import { I18nService } from "../../platform/services/i18n.service";
|
import { I18nService } from "../../platform/services/i18n.service";
|
||||||
import { CliUtils } from "../../utils";
|
import { CliUtils } from "../../utils";
|
||||||
|
import { ConvertToKeyConnectorCommand } from "../convert-to-key-connector.command";
|
||||||
|
|
||||||
export class UnlockCommand {
|
export class UnlockCommand {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -35,6 +38,8 @@ export class UnlockCommand {
|
|||||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||||
private logout: () => Promise<void>,
|
private logout: () => Promise<void>,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
|
private masterPasswordUnlockService: MasterPasswordUnlockService,
|
||||||
|
private configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async run(password: string, cmdOptions: Record<string, any>) {
|
async run(password: string, cmdOptions: Record<string, any>) {
|
||||||
@@ -48,30 +53,53 @@ export class UnlockCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.setNewSessionKey();
|
await this.setNewSessionKey();
|
||||||
const [userId, email] = await firstValueFrom(
|
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||||
this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])),
|
if (activeAccount == null) {
|
||||||
);
|
return Response.error("No active account found");
|
||||||
|
|
||||||
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 userId = activeAccount.id;
|
||||||
|
|
||||||
const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey, userId);
|
if (
|
||||||
await this.keyService.setUserKey(userKey, userId);
|
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,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
|
await this.keyService.setUserKey(userKey, userId);
|
||||||
|
}
|
||||||
|
|
||||||
if (await firstValueFrom(this.keyConnectorService.convertAccountRequired$)) {
|
if (await firstValueFrom(this.keyConnectorService.convertAccountRequired$)) {
|
||||||
const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand(
|
const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand(
|
||||||
@@ -10,12 +10,12 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|||||||
import { ConfirmCommand } from "./admin-console/commands/confirm.command";
|
import { ConfirmCommand } from "./admin-console/commands/confirm.command";
|
||||||
import { ShareCommand } from "./admin-console/commands/share.command";
|
import { ShareCommand } from "./admin-console/commands/share.command";
|
||||||
import { LockCommand } from "./auth/commands/lock.command";
|
import { LockCommand } from "./auth/commands/lock.command";
|
||||||
import { UnlockCommand } from "./auth/commands/unlock.command";
|
|
||||||
import { EditCommand } from "./commands/edit.command";
|
import { EditCommand } from "./commands/edit.command";
|
||||||
import { GetCommand } from "./commands/get.command";
|
import { GetCommand } from "./commands/get.command";
|
||||||
import { ListCommand } from "./commands/list.command";
|
import { ListCommand } from "./commands/list.command";
|
||||||
import { RestoreCommand } from "./commands/restore.command";
|
import { RestoreCommand } from "./commands/restore.command";
|
||||||
import { StatusCommand } from "./commands/status.command";
|
import { StatusCommand } from "./commands/status.command";
|
||||||
|
import { UnlockCommand } from "./key-management/commands/unlock.command";
|
||||||
import { Response } from "./models/response";
|
import { Response } from "./models/response";
|
||||||
import { FileResponse } from "./models/response/file.response";
|
import { FileResponse } from "./models/response/file.response";
|
||||||
import { ServiceContainer } from "./service-container/service-container";
|
import { ServiceContainer } from "./service-container/service-container";
|
||||||
@@ -173,6 +173,8 @@ export class OssServeConfigurator {
|
|||||||
this.serviceContainer.organizationApiService,
|
this.serviceContainer.organizationApiService,
|
||||||
async () => await this.serviceContainer.logout(),
|
async () => await this.serviceContainer.logout(),
|
||||||
this.serviceContainer.i18nService,
|
this.serviceContainer.i18nService,
|
||||||
|
this.serviceContainer.masterPasswordUnlockService,
|
||||||
|
this.serviceContainer.configService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.sendCreateCommand = new SendCreateCommand(
|
this.sendCreateCommand = new SendCreateCommand(
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|||||||
import { LockCommand } from "./auth/commands/lock.command";
|
import { LockCommand } from "./auth/commands/lock.command";
|
||||||
import { LoginCommand } from "./auth/commands/login.command";
|
import { LoginCommand } from "./auth/commands/login.command";
|
||||||
import { LogoutCommand } from "./auth/commands/logout.command";
|
import { LogoutCommand } from "./auth/commands/logout.command";
|
||||||
import { UnlockCommand } from "./auth/commands/unlock.command";
|
|
||||||
import { BaseProgram } from "./base-program";
|
import { BaseProgram } from "./base-program";
|
||||||
import { CompletionCommand } from "./commands/completion.command";
|
import { CompletionCommand } from "./commands/completion.command";
|
||||||
import { EncodeCommand } from "./commands/encode.command";
|
import { EncodeCommand } from "./commands/encode.command";
|
||||||
import { StatusCommand } from "./commands/status.command";
|
import { StatusCommand } from "./commands/status.command";
|
||||||
import { UpdateCommand } from "./commands/update.command";
|
import { UpdateCommand } from "./commands/update.command";
|
||||||
|
import { UnlockCommand } from "./key-management/commands/unlock.command";
|
||||||
import { Response } from "./models/response";
|
import { Response } from "./models/response";
|
||||||
import { MessageResponse } from "./models/response/message.response";
|
import { MessageResponse } from "./models/response/message.response";
|
||||||
import { ConfigCommand } from "./platform/commands/config.command";
|
import { ConfigCommand } from "./platform/commands/config.command";
|
||||||
@@ -303,6 +303,8 @@ export class Program extends BaseProgram {
|
|||||||
this.serviceContainer.organizationApiService,
|
this.serviceContainer.organizationApiService,
|
||||||
async () => await this.serviceContainer.logout(),
|
async () => await this.serviceContainer.logout(),
|
||||||
this.serviceContainer.i18nService,
|
this.serviceContainer.i18nService,
|
||||||
|
this.serviceContainer.masterPasswordUnlockService,
|
||||||
|
this.serviceContainer.configService,
|
||||||
);
|
);
|
||||||
const response = await command.run(password, cmd);
|
const response = await command.run(password, cmd);
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
|
|||||||
@@ -70,7 +70,9 @@ import { EncryptServiceImplementation } from "@bitwarden/common/key-management/c
|
|||||||
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
|
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 { DeviceTrustService } from "@bitwarden/common/key-management/device-trust/services/device-trust.service.implementation";
|
||||||
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/services/key-connector.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";
|
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
|
import { DefaultMasterPasswordUnlockService } from "@bitwarden/common/key-management/master-password/services/default-master-password-unlock.service";
|
||||||
import { MasterPasswordService } from "@bitwarden/common/key-management/master-password/services/master-password.service";
|
import { MasterPasswordService } from "@bitwarden/common/key-management/master-password/services/master-password.service";
|
||||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||||
import { PinService } from "@bitwarden/common/key-management/pin/pin.service.implementation";
|
import { PinService } from "@bitwarden/common/key-management/pin/pin.service.implementation";
|
||||||
@@ -310,6 +312,7 @@ export class ServiceContainer {
|
|||||||
restrictedItemTypesService: RestrictedItemTypesService;
|
restrictedItemTypesService: RestrictedItemTypesService;
|
||||||
cliRestrictedItemTypesService: CliRestrictedItemTypesService;
|
cliRestrictedItemTypesService: CliRestrictedItemTypesService;
|
||||||
securityStateService: SecurityStateService;
|
securityStateService: SecurityStateService;
|
||||||
|
masterPasswordUnlockService: MasterPasswordUnlockService;
|
||||||
cipherArchiveService: CipherArchiveService;
|
cipherArchiveService: CipherArchiveService;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -480,6 +483,11 @@ export class ServiceContainer {
|
|||||||
this.kdfConfigService,
|
this.kdfConfigService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.masterPasswordUnlockService = new DefaultMasterPasswordUnlockService(
|
||||||
|
this.masterPasswordService,
|
||||||
|
this.keyService,
|
||||||
|
);
|
||||||
|
|
||||||
this.appIdService = new AppIdService(this.storageService, this.logService);
|
this.appIdService = new AppIdService(this.storageService, this.logService);
|
||||||
|
|
||||||
const customUserAgent =
|
const customUserAgent =
|
||||||
|
|||||||
Reference in New Issue
Block a user