mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-04-07 19:34:13 +00:00
Auto-merge for PR #835 via VersionBot
Initial support for api keys in the CLI
This commit is contained in:
commit
989df9b857
@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file
|
||||
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## v7.2.0 - 2018-03-29
|
||||
|
||||
* Do not require a login for builds #835 [Tim Perry]
|
||||
* Allow (experimental!) login with API keys #835 [Tim Perry]
|
||||
|
||||
## v7.1.6 - 2018-03-29
|
||||
|
||||
* Fix build emulation for multi-stage builds #838 [Tim Perry]
|
||||
|
@ -262,7 +262,7 @@ from the dashboard.
|
||||
|
||||
- Credentials: using email/password and 2FA.
|
||||
|
||||
- Token: using the authentication token from the preferences page.
|
||||
- Token: using a session token or API key (experimental) from the preferences page.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -276,7 +276,7 @@ Examples:
|
||||
|
||||
#### --token, -t <token>
|
||||
|
||||
auth token
|
||||
session token or API key (experimental)
|
||||
|
||||
#### --web, -w
|
||||
|
||||
|
@ -28,7 +28,7 @@ gulp.task 'coffee', ->
|
||||
gulp.task 'test', ->
|
||||
gulp.src(OPTIONS.files.tests, read: false)
|
||||
.pipe(mocha({
|
||||
reporter: 'min'
|
||||
reporter: 'spec'
|
||||
}))
|
||||
|
||||
gulp.task 'build', [
|
||||
|
@ -27,7 +27,7 @@ exports.login =
|
||||
|
||||
- Credentials: using email/password and 2FA.
|
||||
|
||||
- Token: using the authentication token from the preferences page.
|
||||
- Token: using a session token or API key (experimental) from the preferences page.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -40,7 +40,7 @@ exports.login =
|
||||
options: [
|
||||
{
|
||||
signature: 'token'
|
||||
description: 'auth token'
|
||||
description: 'session token or API key (experimental)'
|
||||
parameter: 'token'
|
||||
alias: 't'
|
||||
}
|
||||
@ -73,7 +73,7 @@ exports.login =
|
||||
action: (params, options, done) ->
|
||||
_ = require('lodash')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
auth = require('../auth')
|
||||
form = require('resin-cli-form')
|
||||
patterns = require('../utils/patterns')
|
||||
@ -84,7 +84,7 @@ exports.login =
|
||||
return Promise.try ->
|
||||
return options.token if _.isString(options.token)
|
||||
return form.ask
|
||||
message: 'Token (from the preferences page)'
|
||||
message: 'Session token or API key (experimental) from the preferences page'
|
||||
name: 'token'
|
||||
type: 'input'
|
||||
.then(resin.auth.loginWithToken)
|
||||
@ -188,7 +188,7 @@ exports.whoami =
|
||||
permission: 'user'
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
visuals = require('resin-cli-visuals')
|
||||
|
||||
Promise.props
|
||||
|
@ -47,7 +47,6 @@ buildProject = (docker, logger, composeOpts, opts) ->
|
||||
module.exports =
|
||||
signature: 'build [source]'
|
||||
description: 'Build a single image or a multicontainer project locally'
|
||||
permission: 'user'
|
||||
primary: true
|
||||
help: '''
|
||||
Use this command to build an image or a complete multicontainer project
|
||||
|
@ -62,14 +62,15 @@ capitanoExecuteAsync = Promise.promisify(capitano.execute)
|
||||
|
||||
# We don't yet use resin-sdk directly everywhere, but we set up shared
|
||||
# options correctly so we can do safely in submodules
|
||||
require('resin-sdk').setSharedOptions(
|
||||
ResinSdk = require('resin-sdk')
|
||||
ResinSdk.setSharedOptions(
|
||||
apiUrl: settings.get('apiUrl')
|
||||
imageMakerUrl: settings.get('imageMakerUrl')
|
||||
dataDirectory: settings.get('dataDirectory')
|
||||
retries: 2
|
||||
)
|
||||
# Keep using sdk-preconfigured for now, but only temporarily
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
|
||||
resin = ResinSdk.fromSharedOptions()
|
||||
|
||||
actions = require('./actions')
|
||||
errors = require('./errors')
|
||||
|
@ -77,9 +77,9 @@ exports.awaitForToken = (options) ->
|
||||
Promise.try ->
|
||||
if not token
|
||||
throw new Error('No token')
|
||||
return utils.isTokenValid(token)
|
||||
.tap (isValid) ->
|
||||
if not isValid
|
||||
return utils.loginIfTokenValid(token)
|
||||
.tap (loggedIn) ->
|
||||
if not loggedIn
|
||||
throw new Error('Invalid token')
|
||||
.then ->
|
||||
renderAndDone({ request, response, viewName: 'success', token })
|
||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
###
|
||||
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
resin = require('resin-sdk').fromSharedOptions()
|
||||
_ = require('lodash')
|
||||
url = require('url')
|
||||
Promise = require('bluebird')
|
||||
@ -42,7 +42,7 @@ exports.getDashboardLoginURL = (callbackUrl) ->
|
||||
return url.resolve(dashboardUrl, "/login/cli/#{callbackUrl}")
|
||||
|
||||
###*
|
||||
# @summary Check if a token is valid
|
||||
# @summary Log in using a token, but only if the token is valid
|
||||
# @function
|
||||
# @protected
|
||||
#
|
||||
@ -50,21 +50,26 @@ exports.getDashboardLoginURL = (callbackUrl) ->
|
||||
# This function checks that the token is not only well-structured
|
||||
# but that it also authenticates with the server successfully.
|
||||
#
|
||||
# @param {String} sessionToken - token
|
||||
# @fulfil {Boolean} - whether is valid or not
|
||||
# If authenticated, the token is persisted, if not then the previous
|
||||
# login state is restored.
|
||||
#
|
||||
# @param {String} token - session token or api key
|
||||
# @fulfil {Boolean} - whether the login was successful or not
|
||||
# @returns {Promise}
|
||||
#
|
||||
# utils.isTokenValid('...').then (isValid) ->
|
||||
# if isValid
|
||||
# utils.loginIfTokenValid('...').then (loggedIn) ->
|
||||
# if loggedIn
|
||||
# console.log('Token is valid!')
|
||||
###
|
||||
exports.isTokenValid = (sessionToken) ->
|
||||
if not sessionToken? or _.isEmpty(sessionToken.trim())
|
||||
exports.loginIfTokenValid = (token) ->
|
||||
if not token? or _.isEmpty(token.trim())
|
||||
return Promise.resolve(false)
|
||||
|
||||
return resin.token.get().then (currentToken) ->
|
||||
resin.auth.loginWithToken(sessionToken)
|
||||
.return(sessionToken)
|
||||
return resin.auth.getToken()
|
||||
.catchReturn(undefined)
|
||||
.then (currentToken) ->
|
||||
resin.auth.loginWithToken(token)
|
||||
.return(token)
|
||||
.then(resin.auth.isLoggedIn)
|
||||
.tap (isLoggedIn) ->
|
||||
return if isLoggedIn
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "resin-cli",
|
||||
"version": "7.1.6",
|
||||
"version": "7.2.0",
|
||||
"description": "The official resin.io CLI tool",
|
||||
"main": "./build/actions/index.js",
|
||||
"homepage": "https://github.com/resin-io/resin-cli",
|
||||
@ -78,6 +78,7 @@
|
||||
"publish-release": "^1.3.3",
|
||||
"require-npm4-to-publish": "^1.0.0",
|
||||
"resin-lint": "^1.5.0",
|
||||
"rewire": "^3.0.2",
|
||||
"ts-node": "^4.0.1",
|
||||
"typescript": "2.4.0"
|
||||
},
|
||||
|
@ -51,11 +51,11 @@ describe 'Server:', ->
|
||||
describe 'given the token authenticates with the server', ->
|
||||
|
||||
beforeEach ->
|
||||
@utilsIsTokenValidStub = m.sinon.stub(utils, 'isTokenValid')
|
||||
@utilsIsTokenValidStub.returns(Promise.resolve(true))
|
||||
@loginIfTokenValidStub = m.sinon.stub(utils, 'loginIfTokenValid')
|
||||
@loginIfTokenValidStub.returns(Promise.resolve(true))
|
||||
|
||||
afterEach ->
|
||||
@utilsIsTokenValidStub.restore()
|
||||
@loginIfTokenValidStub.restore()
|
||||
|
||||
it 'should eventually be the token', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
@ -74,11 +74,11 @@ describe 'Server:', ->
|
||||
describe 'given the token does not authenticate with the server', ->
|
||||
|
||||
beforeEach ->
|
||||
@utilsIsTokenValidStub = m.sinon.stub(utils, 'isTokenValid')
|
||||
@utilsIsTokenValidStub.returns(Promise.resolve(false))
|
||||
@loginIfTokenValidStub = m.sinon.stub(utils, 'loginIfTokenValid')
|
||||
@loginIfTokenValidStub.returns(Promise.resolve(false))
|
||||
|
||||
afterEach ->
|
||||
@utilsIsTokenValidStub.restore()
|
||||
@loginIfTokenValidStub.restore()
|
||||
|
||||
it 'should be rejected', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
|
@ -1,64 +1,64 @@
|
||||
m = require('mochainon')
|
||||
url = require('url')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
utils = require('../../build/auth/utils')
|
||||
|
||||
tokens = require('./tokens.json')
|
||||
|
||||
rewire = require('rewire')
|
||||
utils = rewire('../../build/auth/utils')
|
||||
resin = utils.__get__('resin')
|
||||
|
||||
describe 'Utils:', ->
|
||||
|
||||
describe '.getDashboardLoginURL()', ->
|
||||
|
||||
it 'should eventually be a valid url', (done) ->
|
||||
it 'should eventually be a valid url', ->
|
||||
utils.getDashboardLoginURL('https://127.0.0.1:3000/callback').then (loginUrl) ->
|
||||
m.chai.expect ->
|
||||
url.parse(loginUrl)
|
||||
.to.not.throw(Error)
|
||||
.nodeify(done)
|
||||
|
||||
it 'should eventually contain an https protocol', (done) ->
|
||||
|
||||
it 'should eventually contain an https protocol', ->
|
||||
Promise.props
|
||||
dashboardUrl: resin.settings.get('dashboardUrl')
|
||||
loginUrl: utils.getDashboardLoginURL('https://127.0.0.1:3000/callback')
|
||||
.then ({ dashboardUrl, loginUrl }) ->
|
||||
protocol = url.parse(loginUrl).protocol
|
||||
m.chai.expect(protocol).to.equal(url.parse(dashboardUrl).protocol)
|
||||
.nodeify(done)
|
||||
|
||||
it 'should correctly escape a callback url without a path', (done) ->
|
||||
it 'should correctly escape a callback url without a path', ->
|
||||
Promise.props
|
||||
dashboardUrl: resin.settings.get('dashboardUrl')
|
||||
loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000')
|
||||
.then ({ dashboardUrl, loginUrl }) ->
|
||||
expectedUrl = "#{dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000"
|
||||
m.chai.expect(loginUrl).to.equal(expectedUrl)
|
||||
.nodeify(done)
|
||||
|
||||
it 'should correctly escape a callback url with a path', (done) ->
|
||||
it 'should correctly escape a callback url with a path', ->
|
||||
Promise.props
|
||||
dashboardUrl: resin.settings.get('dashboardUrl')
|
||||
loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000/callback')
|
||||
.then ({ dashboardUrl, loginUrl }) ->
|
||||
expectedUrl = "#{dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000%252Fcallback"
|
||||
m.chai.expect(loginUrl).to.equal(expectedUrl)
|
||||
.nodeify(done)
|
||||
|
||||
describe '.isTokenValid()', ->
|
||||
describe '.loginIfTokenValid()', ->
|
||||
|
||||
it 'should eventually be false if token is undefined', ->
|
||||
promise = utils.isTokenValid(undefined)
|
||||
promise = utils.loginIfTokenValid(undefined)
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
it 'should eventually be false if token is null', ->
|
||||
promise = utils.isTokenValid(null)
|
||||
promise = utils.loginIfTokenValid(null)
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
it 'should eventually be false if token is an empty string', ->
|
||||
promise = utils.isTokenValid('')
|
||||
promise = utils.loginIfTokenValid('')
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
it 'should eventually be false if token is a string containing only spaces', ->
|
||||
promise = utils.isTokenValid(' ')
|
||||
promise = utils.loginIfTokenValid(' ')
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
describe 'given the token does not authenticate with the server', ->
|
||||
@ -71,31 +71,31 @@ describe 'Utils:', ->
|
||||
@resinAuthIsLoggedInStub.restore()
|
||||
|
||||
it 'should eventually be false', ->
|
||||
promise = utils.isTokenValid(tokens.johndoe.token)
|
||||
promise = utils.loginIfTokenValid(tokens.johndoe.token)
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
describe 'given there was a token already', ->
|
||||
|
||||
beforeEach (done) ->
|
||||
resin.auth.loginWithToken(tokens.janedoe.token).nodeify(done)
|
||||
beforeEach ->
|
||||
resin.auth.loginWithToken(tokens.janedoe.token)
|
||||
|
||||
it 'should preserve the old token', (done) ->
|
||||
it 'should preserve the old token', ->
|
||||
resin.auth.getToken().then (originalToken) ->
|
||||
m.chai.expect(originalToken).to.equal(tokens.janedoe.token)
|
||||
return utils.isTokenValid(tokens.johndoe.token)
|
||||
return utils.loginIfTokenValid(tokens.johndoe.token)
|
||||
.then(resin.auth.getToken).then (currentToken) ->
|
||||
m.chai.expect(currentToken).to.equal(tokens.janedoe.token)
|
||||
.nodeify(done)
|
||||
|
||||
describe 'given there was no token', ->
|
||||
|
||||
beforeEach (done) ->
|
||||
resin.auth.logout().nodeify(done)
|
||||
beforeEach ->
|
||||
resin.auth.logout()
|
||||
|
||||
it 'should stay without a token', (done) ->
|
||||
utils.isTokenValid(tokens.johndoe.token).then ->
|
||||
m.chai.expect(resin.token.get()).to.eventually.not.exist
|
||||
.nodeify(done)
|
||||
it 'should stay without a token', ->
|
||||
utils.loginIfTokenValid(tokens.johndoe.token).then ->
|
||||
resin.auth.isLoggedIn()
|
||||
.then (isLoggedIn) ->
|
||||
m.chai.expect(isLoggedIn).to.equal(false)
|
||||
|
||||
describe 'given the token does authenticate with the server', ->
|
||||
|
||||
@ -107,5 +107,5 @@ describe 'Utils:', ->
|
||||
@resinAuthIsLoggedInStub.restore()
|
||||
|
||||
it 'should eventually be true', ->
|
||||
promise = utils.isTokenValid(tokens.johndoe.token)
|
||||
promise = utils.loginIfTokenValid(tokens.johndoe.token)
|
||||
m.chai.expect(promise).to.eventually.be.true
|
||||
|
Loading…
x
Reference in New Issue
Block a user