Auto-merge for PR #835 via VersionBot

Initial support for api keys in the CLI
This commit is contained in:
resin-io-versionbot[bot] 2018-03-29 10:15:31 +00:00 committed by GitHub
commit 989df9b857
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 72 additions and 61 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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')

View File

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

View File

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

View File

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

View File

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

View File

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