import * as chalk from "chalk"; import { firstValueFrom, map } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { UserId } from "@bitwarden/common/types/guid"; import { UnlockCommand } from "./auth/commands/unlock.command"; import { Response } from "./models/response"; import { ListResponse } from "./models/response/list.response"; import { MessageResponse } from "./models/response/message.response"; import { StringResponse } from "./models/response/string.response"; import { TemplateResponse } from "./models/response/template.response"; import { ServiceContainer } from "./service-container/service-container"; import { CliUtils } from "./utils"; const writeLn = CliUtils.writeLn; export abstract class BaseProgram { constructor(protected serviceContainer: ServiceContainer) {} protected processResponse(response: Response, exitImmediately = false) { if (!response.success) { if (process.env.BW_QUIET !== "true") { if (process.env.BW_RESPONSE === "true") { writeLn(this.getJson(response), true, false); } else { writeLn(chalk.redBright(response.message), true, true); } } const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; if (exitImmediately) { process.exit(exitCode); } else { process.exitCode = exitCode; } return; } if (process.env.BW_RESPONSE === "true") { writeLn(this.getJson(response), true, false); } else if (response.data != null) { let out: string = null; if (response.data.object === "template") { out = this.getJson((response.data as TemplateResponse).template); } if (out == null) { if (response.data.object === "string") { const data = (response.data as StringResponse).data; if (data != null) { out = data; } } else if (response.data.object === "list") { out = this.getJson((response.data as ListResponse).data); } else if (response.data.object === "message") { out = this.getMessage(response); } else { out = this.getJson(response.data); } } if (out != null && process.env.BW_QUIET !== "true") { writeLn(out, true, false); } } if (exitImmediately) { process.exit(0); } else { process.exitCode = 0; } } private getJson(obj: any): string { if (process.env.BW_PRETTY === "true") { return JSON.stringify(obj, null, " "); } else { return JSON.stringify(obj); } } protected getMessage(response: Response): string { const message = response.data as MessageResponse; if (process.env.BW_RAW === "true") { return message.raw; } let out = ""; if (message.title != null) { if (message.noColor) { out = message.title; } else { out = chalk.greenBright(message.title); } } if (message.message != null) { if (message.title != null) { out += "\n"; } out += message.message; } return out.trim() === "" ? null : out; } protected async exitIfAuthed() { const authed = await firstValueFrom( this.serviceContainer.authService.activeAccountStatus$.pipe( map((status) => status > AuthenticationStatus.LoggedOut), ), ); if (authed) { const email = await firstValueFrom( this.serviceContainer.accountService.activeAccount$.pipe(map((a) => a?.email)), ); this.processResponse(Response.error("You are already logged in as " + email + "."), true); } } /** * Exist if no user is authenticated * @returns the userId of the active account */ protected async exitIfNotAuthed() { const fail = () => this.processResponse(Response.error("You are not logged in."), true); const userId = (await firstValueFrom(this.serviceContainer.accountService.activeAccount$))?.id; if (!userId) { fail(); } const authed = await this.serviceContainer.stateService.getIsAuthenticated({ userId }); if (!authed) { fail(); } return userId; } protected async exitIfLocked() { const userId = await this.exitIfNotAuthed(); // If the process.env does not have a BW_SESSION key, then we will never be able to retrieve // the auto user key from secure storage. This is because the auto user key is encrypted with // the session key. const hasUserKey = await this.serviceContainer.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet( userId, ); if (hasUserKey) { // User is unlocked return; } // User is locked await this.handleLockedUser(userId); } private async handleLockedUser(userId: UserId) { if (process.env.BW_NOINTERACTION === "true") { this.processResponse(Response.error("Vault is locked."), true); return; } // must unlock with interaction allowed if (await this.serviceContainer.keyConnectorService.getUsesKeyConnector(userId)) { const response = Response.error( "Your vault is locked. You must unlock your vault using your session key.\n" + "If you do not have your session key, you can get a new one by logging out and logging in again.", ); this.processResponse(response, true); } else { const command = new UnlockCommand( this.serviceContainer.accountService, this.serviceContainer.masterPasswordService, this.serviceContainer.cryptoService, this.serviceContainer.userVerificationService, this.serviceContainer.cryptoFunctionService, this.serviceContainer.logService, this.serviceContainer.keyConnectorService, this.serviceContainer.environmentService, this.serviceContainer.syncService, this.serviceContainer.organizationApiService, this.serviceContainer.logout, ); const response = await command.run(null, null); if (!response.success) { this.processResponse(response, true); } } } protected async exitIfFeatureFlagDisabled(featureFlag: FeatureFlag) { const enabled = await firstValueFrom( this.serviceContainer.configService.getFeatureFlag$(featureFlag), ); if (!enabled) { this.processResponse(Response.error("This command is temporarily unavailable."), true); } } }