diff --git a/lib/actions/auth.coffee b/lib/actions/auth.coffee deleted file mode 100644 index b9385c9e..00000000 --- a/lib/actions/auth.coffee +++ /dev/null @@ -1,170 +0,0 @@ -### -Copyright 2016-2017 Balena - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -### - -exports.login = - signature: 'login' - description: 'login to balena' - help: ''' - Use this command to login to your balena account. - - This command will prompt you to login using the following login types: - - - Web authorization: open your web browser and prompt you to authorize the CLI - from the dashboard. - - - Credentials: using email/password and 2FA. - - - Token: using a session token or API key from the preferences page. - - Examples: - - $ balena login - $ balena login --web - $ balena login --token "..." - $ balena login --credentials - $ balena login --credentials --email johndoe@gmail.com --password secret - ''' - options: [ - { - signature: 'token' - description: 'session token or API key' - parameter: 'token' - alias: 't' - } - { - signature: 'web' - description: 'web-based login' - boolean: true - alias: 'w' - } - { - signature: 'credentials' - description: 'credential-based login' - boolean: true - alias: 'c' - } - { - signature: 'email' - parameter: 'email' - description: 'email' - alias: [ 'e', 'u' ] - } - { - signature: 'password' - parameter: 'password' - description: 'password' - alias: 'p' - } - ] - primary: true - action: (params, options, done) -> - _ = require('lodash') - Promise = require('bluebird') - balena = require('balena-sdk').fromSharedOptions() - auth = require('../auth') - form = require('resin-cli-form') - patterns = require('../utils/patterns') - messages = require('../utils/messages') - - login = (options) -> - if options.token? - return Promise.try -> - return options.token if _.isString(options.token) - return form.ask - message: 'Session token or API key from the preferences page' - name: 'token' - type: 'input' - .then(balena.auth.loginWithToken) - .tap -> - balena.auth.whoami() - .then (username) -> - if !username - patterns.exitWithExpectedError('Token authentication failed') - else if options.credentials - return patterns.authenticate(options) - else if options.web - console.info('Connecting to the web dashboard') - return auth.login() - - return patterns.askLoginType().then (loginType) -> - - if loginType is 'register' - signupUrl = 'https://dashboard.balena-cloud.com/signup' - require('open')(signupUrl, { wait: false }) - patterns.exitWithExpectedError("Please sign up at #{signupUrl}") - - options[loginType] = true - return login(options) - - balena.settings.get('balenaUrl').then (balenaUrl) -> - console.log(messages.balenaAsciiArt) - console.log("\nLogging in to #{balenaUrl}") - return login(options) - .then(balena.auth.whoami) - .tap (username) -> - console.info("Successfully logged in as: #{username}") - console.info """ - - Find out about the available commands by running: - - $ balena help - - #{messages.reachingOut} - """ - .nodeify(done) - -exports.logout = - signature: 'logout' - description: 'logout from balena' - help: ''' - Use this command to logout from your balena account. - - Examples: - - $ balena logout - ''' - action: (params, options, done) -> - balena = require('balena-sdk').fromSharedOptions() - balena.auth.logout().nodeify(done) - -exports.whoami = - signature: 'whoami' - description: 'get current username and email address' - help: ''' - Use this command to find out the current logged in username and email address. - - Examples: - - $ balena whoami - ''' - permission: 'user' - action: (params, options, done) -> - Promise = require('bluebird') - balena = require('balena-sdk').fromSharedOptions() - visuals = require('resin-cli-visuals') - - Promise.props - username: balena.auth.whoami() - email: balena.auth.getEmail() - url: balena.settings.get('balenaUrl') - .then (results) -> - console.log visuals.table.vertical results, [ - '$account information$' - 'username' - 'email' - 'url' - ] - .nodeify(done) diff --git a/lib/actions/auth.ts b/lib/actions/auth.ts new file mode 100644 index 00000000..bafe49db --- /dev/null +++ b/lib/actions/auth.ts @@ -0,0 +1,192 @@ +/* +Copyright 2016-2017 Balena + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { CommandDefinition } from 'capitano'; + +export const login: CommandDefinition< + {}, + { + token: string | boolean; + web: boolean; + credentials: boolean; + email: string; + password: string; + } +> = { + signature: 'login', + description: 'login to balena', + help: `\ +Use this command to login to your balena account. + +This command will prompt you to login using the following login types: + +- Web authorization: open your web browser and prompt you to authorize the CLI +from the dashboard. + +- Credentials: using email/password and 2FA. + +- Token: using a session token or API key from the preferences page. + +Examples: + + $ balena login + $ balena login --web + $ balena login --token "..." + $ balena login --credentials + $ balena login --credentials --email johndoe@gmail.com --password secret\ +`, + options: [ + { + signature: 'token', + description: 'session token or API key', + parameter: 'token', + alias: 't', + }, + { + signature: 'web', + description: 'web-based login', + boolean: true, + alias: 'w', + }, + { + signature: 'credentials', + description: 'credential-based login', + boolean: true, + alias: 'c', + }, + { + signature: 'email', + parameter: 'email', + description: 'email', + alias: ['e', 'u'], + }, + { + signature: 'password', + parameter: 'password', + description: 'password', + alias: 'p', + }, + ], + primary: true, + async action(_params, options) { + type Options = typeof options; + const balena = (await import('balena-sdk')).fromSharedOptions(); + const patterns = await import('../utils/patterns'); + const messages = await import('../utils/messages'); + + const doLogin = async (loginOptions: Options): Promise => { + if (loginOptions.token != null) { + let token: string; + if (typeof loginOptions.token === 'string') { + token = loginOptions.token; + } else { + const form = await import('resin-cli-form'); + token = await form.ask({ + message: 'Session token or API key from the preferences page', + name: 'token', + type: 'input', + }); + } + await balena.auth.loginWithToken(token); + if (!(await balena.auth.whoami())) { + patterns.exitWithExpectedError('Token authentication failed'); + } + return; + } else if (loginOptions.credentials) { + return patterns.authenticate(loginOptions); + } else if (loginOptions.web) { + console.info('Connecting to the web dashboard'); + const auth = await import('../auth'); + await auth.login(); + return; + } + + const loginType = await patterns.askLoginType(); + if (loginType === 'register') { + const signupUrl = 'https://dashboard.balena-cloud.com/signup'; + const open = await import('open'); + open(signupUrl, { wait: false }); + return patterns.exitWithExpectedError(`Please sign up at ${signupUrl}`); + } + + loginOptions[loginType] = true; + return doLogin(loginOptions); + }; + + const balenaUrl = await balena.settings.get('balenaUrl'); + + console.log(messages.balenaAsciiArt); + console.log(`\nLogging in to ${balenaUrl}`); + await doLogin(options); + const username = await balena.auth.whoami(); + + console.info(`Successfully logged in as: ${username}`); + console.info(`\ + +Find out about the available commands by running: + + $ balena help + +${messages.reachingOut}`); + }, +}; + +export const logout: CommandDefinition = { + signature: 'logout', + description: 'logout from balena', + help: `\ +Use this command to logout from your balena account. + +Examples: + + $ balena logout\ +`, + async action(_params) { + const balena = (await import('balena-sdk')).fromSharedOptions(); + await balena.auth.logout(); + }, +}; + +export const whoami: CommandDefinition = { + signature: 'whoami', + description: 'get current username and email address', + help: `\ +Use this command to find out the current logged in username and email address. + +Examples: + + $ balena whoami\ +`, + permission: 'user', + async action() { + const balena = (await import('balena-sdk')).fromSharedOptions(); + + const [username, email, url] = await Promise.all([ + balena.auth.whoami(), + balena.auth.getEmail(), + balena.settings.get('balenaUrl'), + ]); + const visuals = await import('resin-cli-visuals'); + console.log( + visuals.table.vertical({ username, email, url }, [ + '$account information$', + 'username', + 'email', + 'url', + ]), + ); + }, +}; diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts index a8e03a1d..67521cb4 100644 --- a/lib/utils/patterns.ts +++ b/lib/utils/patterns.ts @@ -101,7 +101,7 @@ export async function exitIfNotLoggedIn(): Promise { } export function askLoginType() { - return getForm().ask({ + return getForm().ask<'web' | 'credentials' | 'token' | 'register'>({ message: 'How would you like to login?', name: 'loginType', type: 'list', diff --git a/typings/resin-cli-form/index.d.ts b/typings/resin-cli-form/index.d.ts index e8877934..6b72017e 100644 --- a/typings/resin-cli-form/index.d.ts +++ b/typings/resin-cli-form/index.d.ts @@ -24,7 +24,7 @@ declare module 'resin-cli-form' { input: any, ) => TypeOrPromiseLike; - interface AskOptions { + interface AskOptions { message: string; type?: string; name?: string; @@ -44,7 +44,7 @@ declare module 'resin-cli-form' { } const form: { - ask: (options: AskOptions) => Bluebird; + ask: (options: AskOptions) => Bluebird; run: ( questions: RunQuestion[], extraOptions?: { override: object },