diff --git a/lib/actions-oclif/env/add.ts b/lib/actions-oclif/env/add.ts index 1e19c827..d0f33084 100644 --- a/lib/actions-oclif/env/add.ts +++ b/lib/actions-oclif/env/add.ts @@ -105,7 +105,6 @@ export default class EnvAddCmd extends Command { EnvAddCmd, ); const cmd = this; - const { checkLoggedIn } = await import('../../utils/patterns'); if (!options.application && !options.device) { throw new ExpectedError( @@ -113,7 +112,7 @@ export default class EnvAddCmd extends Command { ); } - await checkLoggedIn(); + await Command.checkLoggedIn(); if (params.value == null) { params.value = process.env[params.name]; diff --git a/lib/actions-oclif/env/rename.ts b/lib/actions-oclif/env/rename.ts index 13078c18..2681dc8a 100644 --- a/lib/actions-oclif/env/rename.ts +++ b/lib/actions-oclif/env/rename.ts @@ -85,9 +85,8 @@ export default class EnvRenameCmd extends Command { const { args: params, flags: opt } = this.parse( EnvRenameCmd, ); - const { checkLoggedIn } = await import('../../utils/patterns'); - await checkLoggedIn(); + await Command.checkLoggedIn(); await getBalenaSdk().pine.patch({ resource: ec.getVarResourceName(opt.config, opt.device, opt.service), diff --git a/lib/actions-oclif/env/rm.ts b/lib/actions-oclif/env/rm.ts index 6a142e7d..2eaa4b0b 100644 --- a/lib/actions-oclif/env/rm.ts +++ b/lib/actions-oclif/env/rm.ts @@ -88,9 +88,9 @@ export default class EnvRmCmd extends Command { EnvRmCmd, ); const balena = getBalenaSdk(); - const { checkLoggedIn, confirm } = await import('../../utils/patterns'); + const { confirm } = await import('../../utils/patterns'); - await checkLoggedIn(); + await Command.checkLoggedIn(); await confirm( opt.yes || false, diff --git a/lib/actions-oclif/envs.ts b/lib/actions-oclif/envs.ts index 44d60586..5af1225c 100644 --- a/lib/actions-oclif/envs.ts +++ b/lib/actions-oclif/envs.ts @@ -133,10 +133,9 @@ export default class EnvsCmd extends Command { public async run() { const { flags: options } = this.parse(EnvsCmd); - const { checkLoggedIn } = await import('../utils/patterns'); const variables: EnvironmentVariableInfo[] = []; - await checkLoggedIn(); + await Command.checkLoggedIn(); if (!options.application && !options.device) { throw new ExpectedError('You must specify an application or device'); diff --git a/lib/actions-oclif/os/configure.ts b/lib/actions-oclif/os/configure.ts index 4339f82c..02a2417c 100644 --- a/lib/actions-oclif/os/configure.ts +++ b/lib/actions-oclif/os/configure.ts @@ -308,8 +308,8 @@ async function validateOptions(options: FlagsDef) { ------------------------------------------------------------------------------------------- `); } - const { checkLoggedIn } = await import('../../utils/patterns'); - await checkLoggedIn(); + + await Command.checkLoggedIn(); } /** diff --git a/lib/command.ts b/lib/command.ts index 37ddf3b7..0f428395 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -16,7 +16,7 @@ */ import Command from '@oclif/command'; -import { ExpectedError } from './errors'; +import { InsufficientPrivilegesError } from './errors'; export default abstract class BalenaCommand extends Command { /** @@ -33,19 +33,59 @@ export default abstract class BalenaCommand extends Command { */ public static root = false; - protected async checkElevatedPrivileges() { - const root = (this.constructor as typeof BalenaCommand).root; - if (root) { - const isElevated = await (await import('is-elevated'))(); - if (!isElevated) { - throw new ExpectedError( - 'You need admin privileges to run this command', - ); - } + /** + * Require authentication to run. + * When set to true, command will exit with an error + * if user is not already logged in. + */ + public static authenticated = false; + + /** + * Throw InsufficientPrivilegesError if not root on Mac/Linux + * or non-Administrator on Windows. + * + * Called automatically if `root=true`. + * Can be called explicitly by command implementation, if e.g.: + * - check should only be done conditionally + * - other code needs to execute before check + */ + protected static async checkElevatedPrivileges() { + const isElevated = await (await import('is-elevated'))(); + if (!isElevated) { + throw new InsufficientPrivilegesError( + 'You need root/admin privileges to run this command', + ); } } + /** + * Throw NotLoggedInError if not logged in. + * + * Called automatically if `authenticated=true`. + * Can be called explicitly by command implementation, if e.g.: + * - check should only be done conditionally + * - other code needs to execute before check + * + * Note, currently public to allow use outside of derived commands + * (as some command implementations require this. Can be made protected + * if this changes). + */ + public static async checkLoggedIn() { + await (await import('./utils/patterns')).checkLoggedIn(); + } + protected async init() { - await this.checkElevatedPrivileges(); + const requireElevatedPrivileges = (this.constructor as typeof BalenaCommand) + .root; + const requireAuthentication = (this.constructor as typeof BalenaCommand) + .authenticated; + + if (requireElevatedPrivileges) { + await BalenaCommand.checkElevatedPrivileges(); + } + + if (requireAuthentication) { + await BalenaCommand.checkLoggedIn(); + } } } diff --git a/lib/errors.ts b/lib/errors.ts index 49af2d2c..383273bf 100644 --- a/lib/errors.ts +++ b/lib/errors.ts @@ -23,6 +23,8 @@ export class ExpectedError extends TypedError {} export class NotLoggedInError extends ExpectedError {} +export class InsufficientPrivilegesError extends ExpectedError {} + function hasCode(error: any): error is Error & { code: string } { return error.code != null; } diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts index f430a029..f5730ea2 100644 --- a/lib/utils/patterns.ts +++ b/lib/utils/patterns.ts @@ -74,6 +74,10 @@ export function authenticate(options: {}): Bluebird { }); } +/** + * Check if logged in, and throw `NotLoggedInError` if not. + * Note: `NotLoggedInError` is an `ExpectedError`. + */ export async function checkLoggedIn(): Promise { const balena = getBalenaSdk(); if (!(await balena.auth.isLoggedIn())) { @@ -84,6 +88,10 @@ export async function checkLoggedIn(): Promise { } } +/** + * Check if logged in, and call `exitWithExpectedError()` if not. + * DEPRECATED: Use checkLoggedIn() instead. + */ export async function exitIfNotLoggedIn(): Promise { try { await checkLoggedIn();