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,
);
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];

View File

@ -85,9 +85,8 @@ export default class EnvRenameCmd extends Command {
const { args: params, flags: opt } = this.parse<FlagsDef, ArgsDef>(
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),

View File

@ -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,

View File

@ -133,10 +133,9 @@ export default class EnvsCmd extends Command {
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(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');

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 { 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) {
/**
* 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 ExpectedError(
'You need admin privileges to run this command',
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();
}
}
}

View File

@ -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;
}

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> {
const balena = getBalenaSdk();
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> {
try {
await checkLoggedIn();