Skip to content

Commit

Permalink
Add bearer option to the lib and cli
Browse files Browse the repository at this point in the history
  • Loading branch information
Juan José committed Aug 29, 2016
1 parent 19fa3b8 commit 7625064
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 11 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dafuq allows you to create an api that executes files on the os command line (vi
* **commands**: Path where to look for commands to run on requests.
* **shebang** (optional): If specified, this will be the command line interpreter to be used when running the file. If it is not specified we will check the file for execution permisions and run it by itself. Defaults to ''.
* **debug** (optional): Show debug info. If true, `console.log` will be used as loggin function. If a function it will used as loggin function instead of the default . Defaults to `false`.
* **bearer** (optional): Add bearer token authorization method to the api. The acces token is provided as the value of this config. Defaults to ''

### Example

Expand Down Expand Up @@ -44,6 +45,7 @@ app.use('/api.cmd/', dafuq({
commands: './commands',
shebang: '/usr/bin/env node', // optional
debug: true // optional
bearer: 'y67x81eg-21od-eewg-ciey-d52f6crtcrqv'
}))
app.listen(3000)
```
Expand All @@ -60,7 +62,12 @@ POST /hello
### CLI
dafuq also allows to be used as cli:
```
$ dafuq --commands="./commands" [--port=3000] [--shebang="/usr/bin/env node"] [--debug]
$ dafuq \
--commands="./commands" \
--port=8080 \ # Defaults to 3000
--shebang="/usr/bin/env node" \ # Defaults to '' (direct terminal execution)
--bearer="y67x81eg-21od-eewg-ciey-d52f6crtcrqv" # API will require bearer access token
--debug
```

## Considerations
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"babel-cli": "^6.14.0",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-istanbul": "^2.0.0",
"babel-polyfill": "^6.13.0",
"babel-preset-es2015": "^6.14.0",
"babel-register": "^6.14.0",
"core-js": "^2.4.1",
Expand Down
3 changes: 3 additions & 0 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ var argv = require('yargs')
// shebang
.describe('shebang', 'the interpreter to use when running the command files')
.default('shebang', '')
// bearer
.describe('bearer', 'an access token that must be provided on the requests to the api')
.default('bearer', '')
// port
.describe('port', 'the port where to listen for api call')
.alias('port', 'p')
Expand Down
60 changes: 53 additions & 7 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,38 @@ function execCommand(command, cb) {
})
}

const HEADER_BEARER_REGEX = /^bearer (.*)$/i

/**
* Returns a middleware that will reject the request with 401 if the
* bearer token at the request does not match the token provided.
*
* @param {String} token The token that the request must provide to continue
* @return {Function} Middleware function
*
* @see {@link https://www.npmjs.com/package/express-bearer-token}
* @see {@link https://tools.ietf.org/html/rfc6750}
*/
function accessMiddleware(token) {
return (req, res, next) => {
let bearer = null
if (req.body && req.body.access_token)
bearer = req.body.access_token
else if (req.query && req.query.access_token)
bearer = req.query.access_token
else if (req.headers && req.headers['authorization']) {
const match = HEADER_BEARER_REGEX.exec(req.headers['authorization'])
if (match)
bearer = match[1]
}

if (bearer === token)
next()
else
res.status(401).send()
}
}

export default function dafuq(config) {

// Allow constructor to be only the commands directory
Expand All @@ -209,7 +241,8 @@ export default function dafuq(config) {
// Assign default values
const opts = Object.assign({
shebang: '',
debug: false
debug: false,
brearer: ''
}, config)

// Options validation
Expand All @@ -222,6 +255,9 @@ export default function dafuq(config) {
if (opts.shebang && (typeof opts.shebang !== 'string' || opts.shebang.length == 0))
throw new TypeError('shebang must be a non empty string')

if (opts.bearer && (typeof opts.bearer !== 'string' || opts.bearer.length == 0))
throw new TypeError('bearer must be a non empty string')

if (opts.debug !== undefined) {
if (opts.debug === true)
opts.debug = IS_TEST ? (() => {}) : console.log
Expand Down Expand Up @@ -261,7 +297,8 @@ export default function dafuq(config) {
* put the result of its execution on response object in the property pointed
* by moduleName.
*
* @param {String} file
* @param {String} file
* @return {Function} Middleware function
*/
function executionMiddleware(file) {
return (req, res, next) => {
Expand Down Expand Up @@ -292,12 +329,21 @@ export default function dafuq(config) {
const filePath = file.relative
const url = '/' + path.dirname(file.relative)
const method = path.basename(filePath, path.extname(filePath)).toLowerCase()
const middleware = executionMiddleware(file.absolute)
const middlewares = []

// If bearer is defined, add an access middleware
if (opts.bearer)
middlewares.push(accessMiddleware(opts.bearer))

// If the method is not any of the "get" methods add the multipart
// upload middleware
if (method !== 'get' && method !== 'head' && method !== 'options')
middlewares.push(upload.any())

middlewares.push(executionMiddleware(file.absolute))

opts.debug(`Adding ${ method } ${ url }`)
if (method === 'get' || method === 'head' || method === 'options')
app[method](url, middleware)
else
app[method](url, upload.any(), middleware)
app[method](url, ...middlewares)
})

// Fallback behaviour, send the result
Expand Down
62 changes: 60 additions & 2 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ describe('Constructor', function() {
}).should.throw(/shebang/);
})

it('should throw if bearer is not a valid string', function() {
build({
path: './commands',
bearer: 1
}).should.throw(/bearer/);
build({
path: './commands',
bearer: {}
}).should.throw(/bearer/);
build({
path: './commands',
bearer: []
}).should.throw(/bearer/);
})

it('should throw if debug is not a boolean nor a function', function() {
build({
path: './commands',
Expand Down Expand Up @@ -103,7 +118,7 @@ describe('Constructor', function() {
})

describe('Invoking a file', () => {
describe('without shebang', () => {
describe('specifing path', () => {
let app;
before(function() {
app = dafuq({ path: './commands' });
Expand Down Expand Up @@ -190,7 +205,50 @@ describe('Invoking a file', () => {
.get('/no-exec')
.expect(404)
.end(done)
})
})
})

describe('specifing bearer', () => {
let app;
const token = 'klr5udmm-qc7g-2ndh-98v2-qjn5039avxqn'

before(function() {
app = dafuq({
path: './commands',
bearer: token
});
})

it('should forbid access if no token on the request', (done) => {
request(app)
.get('/hello')
.expect(401)
.end(done)
})

it('should allow access if token on the query', (done) => {
request(app)
.get('/hello')
.query({ 'access_token': token })
.expect(200)
.end(done)
})

it('should allow access if token on the body', (done) => {
request(app)
.post('/hello')
.send({ 'access_token': token })
.expect(200)
.end(done)
})

it('should allow access if token on the header', (done) => {
request(app)
.get('/hello')
.set('Authorization', 'Bearer ' + token)
.expect(200)
.end(done)
})
})

describe('specifing shebang', () => {
Expand Down

0 comments on commit 7625064

Please sign in to comment.