From 19869e59a7839adcfcb51599a8cbdb0a97dd51e1 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Fri, 11 Apr 2025 18:17:52 +0100 Subject: [PATCH] confirm key connector domain command for cli --- apps/cli/src/auth/commands/login.command.ts | 22 ++++++ .../confirm-key-connector-domain.command.ts | 76 +++++++++++++++++++ apps/cli/src/locales/en/messages.json | 15 ++++ apps/cli/src/program.ts | 1 + 4 files changed, 114 insertions(+) create mode 100644 apps/cli/src/key-management/confirm-key-connector-domain.command.ts diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 9af6e1f0613..249270e4047 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -34,6 +34,7 @@ import { KeyConnectorService } from "@bitwarden/common/key-management/key-connec import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -44,6 +45,7 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legac import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { NodeUtils } from "@bitwarden/node/node-utils"; +import { ConfirmKeyConnectorDomainCommand } from "../../key-management/confirm-key-connector-domain.command"; import { Response } from "../../models/response"; import { MessageResponse } from "../../models/response/message.response"; @@ -76,6 +78,7 @@ export class LoginCommand { protected logoutCallback: () => Promise, protected kdfConfigService: KdfConfigService, protected ssoUrlService: SsoUrlService, + protected i18nService: I18nService, ) {} async run(email: string, password: string, options: OptionValues) { @@ -356,6 +359,25 @@ export class LoginCommand { ); } + if (response.requiresKeyConnectorDomainConfirmation != null) { + const command = new ConfirmKeyConnectorDomainCommand( + response.userId, + response.requiresKeyConnectorDomainConfirmation.organizationId, + response.requiresKeyConnectorDomainConfirmation.keyConnectorUrl, + response.requiresKeyConnectorDomainConfirmation.kdf, + response.requiresKeyConnectorDomainConfirmation.kdfIterations, + response.requiresKeyConnectorDomainConfirmation.kdfMemory, + response.requiresKeyConnectorDomainConfirmation.kdfParallelism, + this.keyConnectorService, + this.logoutCallback, + this.i18nService, + ); + const confirmResponse = await command.run(); + if (!confirmResponse.success) { + return confirmResponse; + } + } + // Run full sync before handling success response or password reset flows (to get Master Password Policies) await this.syncService.fullSync(true); diff --git a/apps/cli/src/key-management/confirm-key-connector-domain.command.ts b/apps/cli/src/key-management/confirm-key-connector-domain.command.ts new file mode 100644 index 00000000000..9acc5f3fda5 --- /dev/null +++ b/apps/cli/src/key-management/confirm-key-connector-domain.command.ts @@ -0,0 +1,76 @@ +import * as inquirer from "inquirer"; + +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { KdfType } from "@bitwarden/key-management"; + +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; + +export class ConfirmKeyConnectorDomainCommand { + constructor( + private readonly userId: UserId, + private readonly organizationId: string, + private readonly keyConnectorUrl: string, + private readonly kdf: KdfType, + private readonly kdfIterations: number, + private readonly kdfMemory: number, + private readonly kdfParallelism: number, + private keyConnectorService: KeyConnectorService, + private logout: () => Promise, + private i18nService: I18nService, + ) {} + + async run(): Promise { + // If no interaction available, alert user to use web vault + const canInteract = process.env.BW_NOINTERACTION !== "true"; + if (!canInteract) { + await this.logout(); + return Response.error( + new MessageResponse( + this.i18nService.t("organizationUsingKeyConnectorConfirmLoggedOut"), + null, + ), + ); + } + + const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ + type: "list", + name: "confirm", + message: this.i18nService.t("confirmKeyConnectorDomain", this.keyConnectorUrl), + choices: [ + { + name: this.i18nService.t("confirm"), + value: "confirmed", + }, + { + name: this.i18nService.t("logOut"), + value: "cancel", + }, + ], + }); + + if (answer.confirm === "confirmed") { + try { + await this.keyConnectorService.convertNewSsoUserToKeyConnector( + this.organizationId, + this.userId, + this.keyConnectorUrl, + this.kdf, + this.kdfIterations, + this.kdfMemory, + this.kdfParallelism, + ); + } catch (e) { + await this.logout(); + throw e; + } + + return Response.success(); + } else { + await this.logout(); + return Response.error(this.i18nService.t("youHaveBeenLoggedOut")); + } + } +} diff --git a/apps/cli/src/locales/en/messages.json b/apps/cli/src/locales/en/messages.json index 9149e25c5bc..398c3f0d239 100644 --- a/apps/cli/src/locales/en/messages.json +++ b/apps/cli/src/locales/en/messages.json @@ -212,5 +212,20 @@ }, "youHaveBeenLoggedOut": { "message": "You have been logged out." + }, + "organizationUsingKeyConnectorConfirmLoggedOut": { + "message": "An organization you are a member of is using Key Connector. In order to access the vault, you must confirm the Key Connector domain now via the web vault. You have been logged out." + }, + "confirmKeyConnectorDomain": { + "message": "Please confirm the domain below with your organization administrator. Key Connector domain: $KEYCONNECTORDOMAIN$", + "placeholders": { + "keyConnectorDomain": { + "content": "$1", + "example": "Key Connector domain" + } + } + }, + "confirm": { + "message": "Confirm" } } diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index a0603d9f796..ddfa8d1a8f3 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -172,6 +172,7 @@ export class Program extends BaseProgram { async () => await this.serviceContainer.logout(), this.serviceContainer.kdfConfigService, this.serviceContainer.ssoUrlService, + this.serviceContainer.i18nService, ); const response = await command.run(email, password, options); this.processResponse(response, true);