mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-18 21:27:51 +00:00
Inline the entire resin-cli-auth module
This is part of a general push to demodularize any code that isn't realistically reusable outside resin-cli, to make the codebase easier to manage and understand. Once this is done, we'll deprecate the original module itself. Change-Type: patch
This commit is contained in:
parent
f106b95be2
commit
001c8f9601
@ -2,6 +2,8 @@ path = require('path')
|
||||
gulp = require('gulp')
|
||||
coffee = require('gulp-coffee')
|
||||
coffeelint = require('gulp-coffeelint')
|
||||
inlinesource = require('gulp-inline-source')
|
||||
mocha = require('gulp-mocha')
|
||||
shell = require('gulp-shell')
|
||||
packageJSON = require('./package.json')
|
||||
|
||||
@ -10,10 +12,17 @@ OPTIONS =
|
||||
coffeelint: path.join(__dirname, 'coffeelint.json')
|
||||
files:
|
||||
coffee: [ 'lib/**/*.coffee', 'gulpfile.coffee' ]
|
||||
app: [ 'lib/**/*.coffee', '!lib/**/*.spec.coffee' ]
|
||||
app: 'lib/**/*.coffee'
|
||||
tests: 'tests/**/*.spec.coffee'
|
||||
pages: 'lib/auth/pages/*.ejs'
|
||||
directories:
|
||||
build: 'build/'
|
||||
|
||||
gulp.task 'pages', ->
|
||||
gulp.src(OPTIONS.files.pages)
|
||||
.pipe(inlinesource())
|
||||
.pipe(gulp.dest('build/auth/pages'))
|
||||
|
||||
gulp.task 'coffee', [ 'lint' ], ->
|
||||
gulp.src(OPTIONS.files.app)
|
||||
.pipe(coffee(bare: true, header: true))
|
||||
@ -26,5 +35,16 @@ gulp.task 'lint', ->
|
||||
}))
|
||||
.pipe(coffeelint.reporter())
|
||||
|
||||
gulp.task 'watch', [ 'coffee' ], ->
|
||||
gulp.watch([ OPTIONS.files.coffee ], [ 'coffee' ])
|
||||
gulp.task 'test', ->
|
||||
gulp.src(OPTIONS.files.tests, read: false)
|
||||
.pipe(mocha({
|
||||
reporter: 'min'
|
||||
}))
|
||||
|
||||
gulp.task 'build', [
|
||||
'coffee',
|
||||
'pages'
|
||||
]
|
||||
|
||||
gulp.task 'watch', [ 'build' ], ->
|
||||
gulp.watch([ OPTIONS.files.coffee ], [ 'build' ])
|
||||
|
@ -74,7 +74,7 @@ exports.login =
|
||||
_ = require('lodash')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
auth = require('resin-cli-auth')
|
||||
auth = require('../auth')
|
||||
form = require('resin-cli-form')
|
||||
patterns = require('../utils/patterns')
|
||||
messages = require('../utils/messages')
|
||||
|
63
lib/auth/index.coffee
Normal file
63
lib/auth/index.coffee
Normal file
@ -0,0 +1,63 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
|
||||
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.
|
||||
###
|
||||
|
||||
###*
|
||||
# @module auth
|
||||
###
|
||||
|
||||
open = require('open')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
server = require('./server')
|
||||
utils = require('./utils')
|
||||
|
||||
###*
|
||||
# @summary Login to the Resin CLI using the web dashboard
|
||||
# @function
|
||||
# @public
|
||||
#
|
||||
# @description
|
||||
# This function opens the user's default browser and points it
|
||||
# to the Resin.io dashboard where the session token exchange will
|
||||
# take place.
|
||||
#
|
||||
# Once the the token is retrieved, it's automatically persisted.
|
||||
#
|
||||
# @fulfil {String} - session token
|
||||
# @returns {Promise}
|
||||
#
|
||||
# @example
|
||||
# auth.login().then (sessionToken) ->
|
||||
# console.log('I\'m logged in!')
|
||||
# console.log("My session token is: #{sessionToken}")
|
||||
###
|
||||
exports.login = ->
|
||||
options =
|
||||
port: 8989
|
||||
path: '/auth'
|
||||
|
||||
# Needs to be 127.0.0.1 not localhost, because the ip only is whitelisted
|
||||
# from mixed content warnings (as the target of a form in the result page)
|
||||
callbackUrl = "http://127.0.0.1:#{options.port}#{options.path}"
|
||||
return utils.getDashboardLoginURL(callbackUrl).then (loginUrl) ->
|
||||
|
||||
# Leave a bit of time for the
|
||||
# server to get up and runing
|
||||
setTimeout ->
|
||||
open(loginUrl)
|
||||
, 1000
|
||||
|
||||
return server.awaitForToken(options)
|
||||
.tap(resin.auth.loginWithToken)
|
21
lib/auth/pages/error.ejs
Normal file
21
lib/auth/pages/error.ejs
Normal file
@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>Resin CLI - Error</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="./static/style.css" inline>
|
||||
</head>
|
||||
<body>
|
||||
<div class="center">
|
||||
<img class="icon" src="./static/images/sad.png" inline>
|
||||
<h1>Something went wrong</h1>
|
||||
<p>You couldn't login to the Resin CLI for some reason</p>
|
||||
<br>
|
||||
<br>
|
||||
<a href="https://forums.resin.io/" class="button danger">Get help in our forums</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
lib/auth/pages/static/images/happy.png
Normal file
BIN
lib/auth/pages/static/images/happy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
lib/auth/pages/static/images/sad.png
Normal file
BIN
lib/auth/pages/static/images/sad.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
60
lib/auth/pages/static/style.css
Normal file
60
lib/auth/pages/static/style.css
Normal file
@ -0,0 +1,60 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
text-align: center;
|
||||
background-color: #fff;
|
||||
color: rgb(24, 24, 24);
|
||||
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.center {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 45px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
margin: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgb(99, 99, 99);
|
||||
font-size: 1.1rem;
|
||||
margin: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
a.button {
|
||||
padding: 15px 25px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.button.danger {
|
||||
background-color: rgb(235, 110, 111);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
a.button.normal {
|
||||
background-color: rgb(252, 191, 44);
|
||||
color: #fff;
|
||||
}
|
21
lib/auth/pages/success.ejs
Normal file
21
lib/auth/pages/success.ejs
Normal file
@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>Resin CLI - Success</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="./static/style.css" inline>
|
||||
</head>
|
||||
<body>
|
||||
<div class="center">
|
||||
<img class="icon" src="./static/images/happy.png" inline>
|
||||
<h1>Success!</h1>
|
||||
<p>You successfully logged in the Resin CLI</p>
|
||||
<br>
|
||||
<br>
|
||||
<a href="<%= dashboardUrl %>" class="button normal">Go to the dashboard</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
101
lib/auth/server.coffee
Normal file
101
lib/auth/server.coffee
Normal file
@ -0,0 +1,101 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
|
||||
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.
|
||||
###
|
||||
|
||||
express = require('express')
|
||||
path = require('path')
|
||||
bodyParser = require('body-parser')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
utils = require('./utils')
|
||||
|
||||
createServer = ({ port, isDev } = {}) ->
|
||||
app = express()
|
||||
app.use bodyParser.urlencoded
|
||||
extended: true
|
||||
|
||||
app.set('view engine', 'ejs')
|
||||
app.set('views', path.join(__dirname, 'pages'))
|
||||
|
||||
if isDev
|
||||
app.use(express.static(path.join(__dirname, 'pages', 'static')))
|
||||
|
||||
server = app.listen(port)
|
||||
|
||||
return { app, server }
|
||||
|
||||
###*
|
||||
# @summary Await for token
|
||||
# @function
|
||||
# @protected
|
||||
#
|
||||
# @param {Object} options - options
|
||||
# @param {String} options.path - callback path
|
||||
# @param {Number} options.port - http port
|
||||
#
|
||||
# @example
|
||||
# server.awaitForToken
|
||||
# path: '/auth'
|
||||
# port: 9001
|
||||
# .then (token) ->
|
||||
# console.log(token)
|
||||
###
|
||||
exports.awaitForToken = (options) ->
|
||||
{ app, server } = createServer(port: options.port)
|
||||
|
||||
return new Promise (resolve, reject) ->
|
||||
closeServer = (errorMessage, successPayload) ->
|
||||
server.close ->
|
||||
if errorMessage
|
||||
reject(new Error(errorMessage))
|
||||
return
|
||||
|
||||
resolve(successPayload)
|
||||
|
||||
renderAndDone = ({ request, response, viewName, errorMessage, statusCode, token }) ->
|
||||
return getContext(viewName)
|
||||
.then (context) ->
|
||||
response.status(statusCode || 200).render(viewName, context)
|
||||
request.connection.destroy()
|
||||
closeServer(errorMessage, token)
|
||||
|
||||
app.post options.path, (request, response) ->
|
||||
token = request.body.token?.trim()
|
||||
|
||||
Promise.try ->
|
||||
if not token
|
||||
throw new Error('No token')
|
||||
return utils.isTokenValid(token)
|
||||
.tap (isValid) ->
|
||||
if not isValid
|
||||
throw new Error('Invalid token')
|
||||
.then ->
|
||||
renderAndDone({ request, response, viewName: 'success', token })
|
||||
.catch (error) ->
|
||||
renderAndDone({
|
||||
request, response, viewName: 'error',
|
||||
statusCode: 401, errorMessage: error.message
|
||||
})
|
||||
|
||||
app.use (request, response) ->
|
||||
response.status(404).send('Not found')
|
||||
closeServer('Unknown path or verb')
|
||||
|
||||
exports.getContext = getContext = (viewName) ->
|
||||
if viewName is 'success'
|
||||
return Promise.props
|
||||
dashboardUrl: resin.settings.get('dashboardUrl')
|
||||
|
||||
return Promise.resolve({})
|
75
lib/auth/utils.coffee
Normal file
75
lib/auth/utils.coffee
Normal file
@ -0,0 +1,75 @@
|
||||
###
|
||||
Copyright 2016 Resin.io
|
||||
|
||||
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.
|
||||
###
|
||||
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
_ = require('lodash')
|
||||
url = require('url')
|
||||
Promise = require('bluebird')
|
||||
|
||||
###*
|
||||
# @summary Get dashboard CLI login URL
|
||||
# @function
|
||||
# @protected
|
||||
#
|
||||
# @param {String} callbackUrl - callback url
|
||||
# @fulfil {String} - dashboard login url
|
||||
# @returns {Promise}
|
||||
#
|
||||
# @example
|
||||
# utils.getDashboardLoginURL('http://127.0.0.1:3000').then (url) ->
|
||||
# console.log(url)
|
||||
###
|
||||
exports.getDashboardLoginURL = (callbackUrl) ->
|
||||
|
||||
# Encode percentages signs from the escaped url
|
||||
# characters to avoid angular getting confused.
|
||||
callbackUrl = encodeURIComponent(callbackUrl).replace(/%/g, '%25')
|
||||
|
||||
resin.settings.get('dashboardUrl').then (dashboardUrl) ->
|
||||
return url.resolve(dashboardUrl, "/login/cli/#{callbackUrl}")
|
||||
|
||||
###*
|
||||
# @summary Check if a token is valid
|
||||
# @function
|
||||
# @protected
|
||||
#
|
||||
# @description
|
||||
# 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
|
||||
# @returns {Promise}
|
||||
#
|
||||
# utils.isTokenValid('...').then (isValid) ->
|
||||
# if isValid
|
||||
# console.log('Token is valid!')
|
||||
###
|
||||
exports.isTokenValid = (sessionToken) ->
|
||||
if not sessionToken? or _.isEmpty(sessionToken.trim())
|
||||
return Promise.resolve(false)
|
||||
|
||||
return resin.token.get().then (currentToken) ->
|
||||
resin.auth.loginWithToken(sessionToken)
|
||||
.return(sessionToken)
|
||||
.then(resin.auth.isLoggedIn)
|
||||
.tap (isLoggedIn) ->
|
||||
return if isLoggedIn
|
||||
|
||||
if currentToken?
|
||||
return resin.auth.loginWithToken(currentToken)
|
||||
else
|
||||
return resin.auth.logout()
|
18
package.json
18
package.json
@ -19,8 +19,10 @@
|
||||
"resin": "./bin/resin"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp coffee && tsc && npm run doc",
|
||||
"ci": "npm run build && catch-uncommitted",
|
||||
"build": "gulp build && tsc && npm run doc",
|
||||
"pretest": "npm run build",
|
||||
"test": "gulp test",
|
||||
"ci": "npm run test && catch-uncommitted",
|
||||
"doc": "mkdir -p doc/ && coffee extras/capitanodoc/index.coffee > doc/cli.markdown",
|
||||
"watch": "gulp watch",
|
||||
"lint": "gulp lint",
|
||||
@ -42,7 +44,10 @@
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-coffee": "^2.2.0",
|
||||
"gulp-coffeelint": "^0.6.0",
|
||||
"gulp-inline-source": "^2.1.0",
|
||||
"gulp-mocha": "^2.0.0",
|
||||
"gulp-shell": "^0.5.2",
|
||||
"mochainon": "^2.0.0",
|
||||
"require-npm4-to-publish": "^1.0.0",
|
||||
"typescript": "^2.6.1"
|
||||
},
|
||||
@ -52,6 +57,7 @@
|
||||
"any-promise": "^1.3.0",
|
||||
"bash": "0.0.1",
|
||||
"bluebird": "^3.3.3",
|
||||
"body-parser": "^1.14.1",
|
||||
"capitano": "^1.7.0",
|
||||
"chalk": "^1.1.3",
|
||||
"coffee-script": "^1.12.6",
|
||||
@ -62,7 +68,9 @@
|
||||
"dockerode": "^2.5.0",
|
||||
"dockerode-options": "^0.2.1",
|
||||
"drivelist": "^5.0.22",
|
||||
"ejs": "^2.5.7",
|
||||
"etcher-image-write": "^9.0.3",
|
||||
"express": "^4.13.3",
|
||||
"global-tunnel-ng": "github:zvin/global-tunnel#dont-proxy-connections-to-file-sockets",
|
||||
"hasbin": "^1.2.3",
|
||||
"inquirer": "^3.1.1",
|
||||
@ -75,6 +83,7 @@
|
||||
"mz": "^2.6.0",
|
||||
"node-cleanup": "^2.1.2",
|
||||
"nplugm": "^3.0.0",
|
||||
"open": "0.0.5",
|
||||
"president": "^2.0.1",
|
||||
"prettyjson": "^1.1.3",
|
||||
"progress-stream": "^2.0.0",
|
||||
@ -82,7 +91,6 @@
|
||||
"reconfix": "^0.0.3",
|
||||
"request": "^2.81.0",
|
||||
"resin-bundle-resolve": "^0.0.2",
|
||||
"resin-cli-auth": "^1.2.0",
|
||||
"resin-cli-errors": "^1.2.0",
|
||||
"resin-cli-form": "^1.4.1",
|
||||
"resin-cli-visuals": "^1.4.0",
|
||||
@ -105,11 +113,11 @@
|
||||
"stream-to-promise": "^2.2.0",
|
||||
"tmp": "0.0.31",
|
||||
"umount": "^1.1.6",
|
||||
"underscore.string": "^3.1.1",
|
||||
"underscore.string": "^3.2.2",
|
||||
"unzip2": "^0.2.5",
|
||||
"update-notifier": "^2.2.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"removedrive": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
124
tests/auth/server.spec.coffee
Normal file
124
tests/auth/server.spec.coffee
Normal file
@ -0,0 +1,124 @@
|
||||
m = require('mochainon')
|
||||
request = require('request')
|
||||
Promise = require('bluebird')
|
||||
path = require('path')
|
||||
fs = require('fs')
|
||||
ejs = require('ejs')
|
||||
server = require('../../build/auth/server')
|
||||
utils = require('../../build/auth/utils')
|
||||
tokens = require('./tokens.json')
|
||||
|
||||
options =
|
||||
port: 3000
|
||||
path: '/auth'
|
||||
|
||||
getPage = (name) ->
|
||||
pagePath = path.join(__dirname, '..', '..', 'build', 'auth', 'pages', "#{name}.ejs")
|
||||
tpl = fs.readFileSync(pagePath, encoding: 'utf8')
|
||||
compiledTpl = ejs.compile(tpl)
|
||||
return server.getContext(name)
|
||||
.then (context) ->
|
||||
compiledTpl(context)
|
||||
|
||||
describe 'Server:', ->
|
||||
|
||||
it 'should get 404 if posting to an unknown path', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.be.rejectedWith('Unknown path or verb')
|
||||
|
||||
request.post "http://localhost:#{options.port}/foobarbaz",
|
||||
form:
|
||||
token: tokens.johndoe.token
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(404)
|
||||
m.chai.expect(body).to.equal('Not found')
|
||||
done()
|
||||
|
||||
it 'should get 404 if not using the correct verb', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.be.rejectedWith('Unknown path or verb')
|
||||
|
||||
request.get "http://localhost:#{options.port}#{options.path}",
|
||||
form:
|
||||
token: tokens.johndoe.token
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(404)
|
||||
m.chai.expect(body).to.equal('Not found')
|
||||
done()
|
||||
|
||||
describe 'given the token authenticates with the server', ->
|
||||
|
||||
beforeEach ->
|
||||
@utilsIsTokenValidStub = m.sinon.stub(utils, 'isTokenValid')
|
||||
@utilsIsTokenValidStub.returns(Promise.resolve(true))
|
||||
|
||||
afterEach ->
|
||||
@utilsIsTokenValidStub.restore()
|
||||
|
||||
it 'should eventually be the token', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.eventually.equal(tokens.johndoe.token)
|
||||
|
||||
request.post "http://localhost:#{options.port}#{options.path}",
|
||||
form:
|
||||
token: tokens.johndoe.token
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(200)
|
||||
getPage('success').then (expectedBody) ->
|
||||
m.chai.expect(body).to.equal(expectedBody)
|
||||
done()
|
||||
|
||||
describe 'given the token does not authenticate with the server', ->
|
||||
|
||||
beforeEach ->
|
||||
@utilsIsTokenValidStub = m.sinon.stub(utils, 'isTokenValid')
|
||||
@utilsIsTokenValidStub.returns(Promise.resolve(false))
|
||||
|
||||
afterEach ->
|
||||
@utilsIsTokenValidStub.restore()
|
||||
|
||||
it 'should be rejected', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.be.rejectedWith('Invalid token')
|
||||
|
||||
request.post "http://localhost:#{options.port}#{options.path}",
|
||||
form:
|
||||
token: tokens.johndoe.token
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(401)
|
||||
getPage('error').then (expectedBody) ->
|
||||
m.chai.expect(body).to.equal(expectedBody)
|
||||
done()
|
||||
|
||||
it 'should be rejected if no token', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.be.rejectedWith('No token')
|
||||
|
||||
request.post "http://localhost:#{options.port}#{options.path}",
|
||||
form:
|
||||
token: ''
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(401)
|
||||
getPage('error').then (expectedBody) ->
|
||||
m.chai.expect(body).to.equal(expectedBody)
|
||||
done()
|
||||
|
||||
it 'should be rejected if token is malformed', (done) ->
|
||||
promise = server.awaitForToken(options)
|
||||
m.chai.expect(promise).to.be.rejectedWith('Invalid token')
|
||||
|
||||
request.post "http://localhost:#{options.port}#{options.path}",
|
||||
form:
|
||||
token: 'asdf'
|
||||
, (error, response, body) ->
|
||||
m.chai.expect(error).to.not.exist
|
||||
m.chai.expect(response.statusCode).to.equal(401)
|
||||
getPage('error').then (expectedBody) ->
|
||||
m.chai.expect(body).to.equal(expectedBody)
|
||||
done()
|
||||
|
18
tests/auth/tokens.json
Normal file
18
tests/auth/tokens.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"johndoe": {
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImpvaG5kb2UxIiwiZW1haWwiOiJqb2huZG9lQGpvaG5kb2UuY29tIiwiZ2l0bGFiX2lkIjoxMzI1LCJzb2NpYWxfc2VydmljZV9hY2NvdW50IjpudWxsLCJoYXNQYXNzd29yZFNldCI6dHJ1ZSwibmVlZHNQYXNzd29yZFJlc2V0IjpmYWxzZSwicHVibGljX2tleSI6ZmFsc2UsImZlYXR1cmVzIjpbXSwiaWQiOjEzNDQsImludGVyY29tVXNlckhhc2giOiJlMDM3NzhkZDI5ZTE1NzQ0NWYyNzJhY2M5MjExNzBjZjI4MTBiNjJmNTAyNjQ1MjY1Y2MzNDlkNmRlZGEzNTI0IiwicGVybWlzc2lvbnMiOltdLCJpYXQiOjE0MjY3ODMzMTJ9.v5bmh9HwyUZu8zhh1rA79mTL-1jzDOO8eUr_lVaBwhg",
|
||||
"data": {
|
||||
"email": "johndoe@johndoe.com",
|
||||
"username": "johndoe1",
|
||||
"id": 1344
|
||||
}
|
||||
},
|
||||
"janedoe": {
|
||||
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTUyLCJ1c2VybmFtZSI6ImphbmVkb2UiLCJlbWFpbCI6ImphbmVkb2VAYXNkZi5jb20iLCJzb2NpYWxfc2VydmljZV9hY2NvdW50IjpudWxsLCJoYXNfZGlzYWJsZWRfbmV3c2xldHRlciI6dHJ1ZSwiaGFzUGFzc3dvcmRTZXQiOnRydWUsIm5lZWRzUGFzc3dvcmRSZXNldCI6ZmFsc2UsInB1YmxpY19rZXkiOmZhbHNlLCJmZWF0dXJlcyI6W10sImludGVyY29tVXNlckhhc2giOiIwYjRmOWViNDRiMzcxZjBlMzI4ZWY1ZmUwM2FkN2ViMmY1ZjcyZGQ0MThlZjIzMTQ5ZDUyODcwOTY1NThjZTAzIiwicGVybWlzc2lvbnMiOltdLCJpYXQiOjE0MzUzMjAyNjN9.jVzUFu58vzdJFctR8ulyjGL0Em1kjIZSbSxX2SeU03Y",
|
||||
"data": {
|
||||
"email": "janedoe@asdf.com",
|
||||
"username": "janedoe",
|
||||
"id": 152
|
||||
}
|
||||
}
|
||||
}
|
111
tests/auth/utils.spec.coffee
Normal file
111
tests/auth/utils.spec.coffee
Normal file
@ -0,0 +1,111 @@
|
||||
m = require('mochainon')
|
||||
url = require('url')
|
||||
Promise = require('bluebird')
|
||||
resin = require('resin-sdk-preconfigured')
|
||||
utils = require('../../build/auth/utils')
|
||||
tokens = require('./tokens.json')
|
||||
|
||||
describe 'Utils:', ->
|
||||
|
||||
describe '.getDashboardLoginURL()', ->
|
||||
|
||||
it 'should eventually be a valid url', (done) ->
|
||||
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) ->
|
||||
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) ->
|
||||
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) ->
|
||||
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()', ->
|
||||
|
||||
it 'should eventually be false if token is undefined', ->
|
||||
promise = utils.isTokenValid(undefined)
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
it 'should eventually be false if token is null', ->
|
||||
promise = utils.isTokenValid(null)
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
it 'should eventually be false if token is an empty string', ->
|
||||
promise = utils.isTokenValid('')
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
it 'should eventually be false if token is a string containing only spaces', ->
|
||||
promise = utils.isTokenValid(' ')
|
||||
m.chai.expect(promise).to.eventually.be.false
|
||||
|
||||
describe 'given the token does not authenticate with the server', ->
|
||||
|
||||
beforeEach ->
|
||||
@resinAuthIsLoggedInStub = m.sinon.stub(resin.auth, 'isLoggedIn')
|
||||
@resinAuthIsLoggedInStub.returns(Promise.resolve(false))
|
||||
|
||||
afterEach ->
|
||||
@resinAuthIsLoggedInStub.restore()
|
||||
|
||||
it 'should eventually be false', ->
|
||||
promise = utils.isTokenValid(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)
|
||||
|
||||
it 'should preserve the old token', (done) ->
|
||||
resin.auth.getToken().then (originalToken) ->
|
||||
m.chai.expect(originalToken).to.equal(tokens.janedoe.token)
|
||||
return utils.isTokenValid(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)
|
||||
|
||||
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)
|
||||
|
||||
describe 'given the token does authenticate with the server', ->
|
||||
|
||||
beforeEach ->
|
||||
@resinAuthIsLoggedInStub = m.sinon.stub(resin.auth, 'isLoggedIn')
|
||||
@resinAuthIsLoggedInStub.returns(Promise.resolve(true))
|
||||
|
||||
afterEach ->
|
||||
@resinAuthIsLoggedInStub.restore()
|
||||
|
||||
it 'should eventually be true', ->
|
||||
promise = utils.isTokenValid(tokens.johndoe.token)
|
||||
m.chai.expect(promise).to.eventually.be.true
|
Loading…
Reference in New Issue
Block a user