Add support for authentication checking to oclif

Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
Scott Lowe 2020-03-24 09:51:19 +01:00
parent 6ec8bcddaa
commit 8658104647
8 changed files with 68 additions and 21 deletions

View File

@ -105,7 +105,6 @@ export default class EnvAddCmd extends Command {
EnvAddCmd, EnvAddCmd,
); );
const cmd = this; const cmd = this;
const { checkLoggedIn } = await import('../../utils/patterns');
if (!options.application && !options.device) { if (!options.application && !options.device) {
throw new ExpectedError( throw new ExpectedError(
@ -113,7 +112,7 @@ export default class EnvAddCmd extends Command {
); );
} }
await checkLoggedIn(); await Command.checkLoggedIn();
if (params.value == null) { if (params.value == null) {
params.value = process.env[params.name]; params.value = process.env[params.name];

View File

@ -85,9 +85,8 @@ export default class EnvRenameCmd extends Command {
const { args: params, flags: opt } = this.parse<FlagsDef, ArgsDef>( const { args: params, flags: opt } = this.parse<FlagsDef, ArgsDef>(
EnvRenameCmd, EnvRenameCmd,
); );
const { checkLoggedIn } = await import('../../utils/patterns');
await checkLoggedIn(); await Command.checkLoggedIn();
await getBalenaSdk().pine.patch({ await getBalenaSdk().pine.patch({
resource: ec.getVarResourceName(opt.config, opt.device, opt.service), resource: ec.getVarResourceName(opt.config, opt.device, opt.service),

View File

@ -88,9 +88,9 @@ export default class EnvRmCmd extends Command {
EnvRmCmd, EnvRmCmd,
); );
const balena = getBalenaSdk(); const balena = getBalenaSdk();
const { checkLoggedIn, confirm } = await import('../../utils/patterns'); const { confirm } = await import('../../utils/patterns');
await checkLoggedIn(); await Command.checkLoggedIn();
await confirm( await confirm(
opt.yes || false, opt.yes || false,

View File

@ -133,10 +133,9 @@ export default class EnvsCmd extends Command {
public async run() { public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(EnvsCmd); const { flags: options } = this.parse<FlagsDef, {}>(EnvsCmd);
const { checkLoggedIn } = await import('../utils/patterns');
const variables: EnvironmentVariableInfo[] = []; const variables: EnvironmentVariableInfo[] = [];
await checkLoggedIn(); await Command.checkLoggedIn();
if (!options.application && !options.device) { if (!options.application && !options.device) {
throw new ExpectedError('You must specify an application or device'); throw new ExpectedError('You must specify an application or device');

View File

@ -308,8 +308,8 @@ async function validateOptions(options: FlagsDef) {
------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------
`); `);
} }
const { checkLoggedIn } = await import('../../utils/patterns');
await checkLoggedIn(); await Command.checkLoggedIn();
} }
/** /**

View File

@ -16,7 +16,7 @@
*/ */
import Command from '@oclif/command'; import Command from '@oclif/command';
import { ExpectedError } from './errors'; import { InsufficientPrivilegesError } from './errors';
export default abstract class BalenaCommand extends Command { export default abstract class BalenaCommand extends Command {
/** /**
@ -33,19 +33,59 @@ export default abstract class BalenaCommand extends Command {
*/ */
public static root = false; public static root = false;
protected async checkElevatedPrivileges() { /**
const root = (this.constructor as typeof BalenaCommand).root; * Require authentication to run.
if (root) { * 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'))(); const isElevated = await (await import('is-elevated'))();
if (!isElevated) { if (!isElevated) {
throw new ExpectedError( throw new InsufficientPrivilegesError(
'You need admin privileges to run this command', '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() { 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();
}
} }
} }

View File

@ -23,6 +23,8 @@ export class ExpectedError extends TypedError {}
export class NotLoggedInError extends ExpectedError {} export class NotLoggedInError extends ExpectedError {}
export class InsufficientPrivilegesError extends ExpectedError {}
function hasCode(error: any): error is Error & { code: string } { function hasCode(error: any): error is Error & { code: string } {
return error.code != null; return error.code != null;
} }

View File

@ -74,6 +74,10 @@ export function authenticate(options: {}): Bluebird<void> {
}); });
} }
/**
* Check if logged in, and throw `NotLoggedInError` if not.
* Note: `NotLoggedInError` is an `ExpectedError`.
*/
export async function checkLoggedIn(): Promise<void> { export async function checkLoggedIn(): Promise<void> {
const balena = getBalenaSdk(); const balena = getBalenaSdk();
if (!(await balena.auth.isLoggedIn())) { if (!(await balena.auth.isLoggedIn())) {
@ -84,6 +88,10 @@ export async function checkLoggedIn(): Promise<void> {
} }
} }
/**
* Check if logged in, and call `exitWithExpectedError()` if not.
* DEPRECATED: Use checkLoggedIn() instead.
*/
export async function exitIfNotLoggedIn(): Promise<void> { export async function exitIfNotLoggedIn(): Promise<void> {
try { try {
await checkLoggedIn(); await checkLoggedIn();