Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for Promise-based authorizers #12

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ one of the three passed credentials.

Alternatively, you can pass your own `authorizer` function, to check the credentials
however you want. It will be called with a username and password and is expected to
return `true` or `false` to indicate that the credentials were approved or not:
return `true` or `false`, or a `Promise` of `true` or `false` to indicate that the
credentials were approved or not:

```js
app.use(basicAuth( { authorizer: myAuthorizer } ))
Expand All @@ -75,8 +76,18 @@ function myAuthorizer(username, password) {
```

This will authorize all requests with credentials where the username begins with
`'A'` and the password begins with `'secret'`. In an actual application you would
likely look up some data instead ;-)
`'A'` and the password begins with `'secret'`.

Alternatively, you can use an `async` function or return a promise:

```js
app.use(basicAuth( { authorizer: myAuthorizer } ))

async function myAuthorizer(username, password) {
const user = await database.findUser(username)
return user.comparePassword(password)
}
```

### Custom Async Authorization

Expand Down
20 changes: 17 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,20 @@ function buildMiddleware(options) {

if(isAsync)
return authorizer(authentication.name, authentication.pass, authorizerCallback)
else if(!authorizer(authentication.name, authentication.pass))
return unauthorized()
else {
var result = authorizer(authentication.name, authentication.pass)
if (result && typeof result.then === 'function') {
return result.then(function (isAuthorized) {
authorizerCallback(undefined, isAuthorized)
}, function (error) {
authorizerCallback(error)
}).catch(function (e) {
console.log('e', e)
})
} else if (!result) {
return unauthorized()
}
}

return next()

Expand All @@ -69,7 +81,9 @@ function buildMiddleware(options) {
}

function authorizerCallback(err, approved) {
assert.ifError(err)
if (err) {
return next(err)
}

if(approved)
return next()
Expand Down
80 changes: 77 additions & 3 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const express = require('express')
const supertest = require('supertest');

var app = express()
app.set('env', 'test')

//Requires basic auth with username 'Admin' and password 'secret1234'
var staticUserAuth = basicAuth({
Expand All @@ -30,6 +31,11 @@ var asyncAuth = basicAuth({
authorizeAsync: true
})

//Uses a custom promise-based authorizer function
var promiseAuth = basicAuth({
authorizer: myPromiseAuthorizer
})

//Uses a custom response body function
var customBodyAuth = basicAuth({
users: { 'Foo': 'bar' },
Expand Down Expand Up @@ -76,6 +82,10 @@ app.get('/async', asyncAuth, function(req, res) {
res.status(200).send('You passed')
})

app.get('/promise', promiseAuth, function(req, res) {
res.status(200).send('You passed')
})

app.get('/custombody', customBodyAuth, function(req, res) {
res.status(200).send('You passed')
})
Expand All @@ -98,15 +108,34 @@ app.get('/realmfunction', realmFunctionAuth, function(req, res) {

//Custom authorizer checking if the username starts with 'A' and the password with 'secret'
function myAuthorizer(username, password) {
return username.startsWith('A') && password.startsWith('secret')
if(username.startsWith('A') && password.startsWith('secret'))
return true
else if (username.startsWith('error'))
throw new Error('authorizer error')
else
return false
}

//Same but asynchronous
function myAsyncAuthorizer(username, password, cb) {
setTimeout(function () {
if(username.startsWith('A') && password.startsWith('secret'))
return cb(null, true)
else if (username.startsWith('error'))
return cb(new Error('authorizer error'))
else
return cb(null, false)
}, 1)
}

//Same but returns promise
function myPromiseAuthorizer(username, password) {
if(username.startsWith('A') && password.startsWith('secret'))
return cb(null, true)
return Promise.resolve(true)
else if (username.startsWith('error'))
return Promise.reject(new Error('authorizer error'))
else
return cb(null, false)
return Promise.resolve(false)
}

function getUnauthorizedResponse(req) {
Expand Down Expand Up @@ -165,6 +194,13 @@ describe('express-basic-auth', function() {
.expect(401, done)
})

it('should return 500 if authoriser rejects', function(done) {
supertest(app)
.get(endpoint)
.auth('error', 'stuff')
.expect(500, done)
})

it('should accept fitting credentials', function(done) {
supertest(app)
.get(endpoint)
Expand All @@ -189,6 +225,44 @@ describe('express-basic-auth', function() {
.expect(401, done)
})

it('should return 500 if authoriser rejects', function(done) {
supertest(app)
.get(endpoint)
.auth('error', 'stuff')
.expect(500, done)
})

it('should accept fitting credentials', function(done) {
supertest(app)
.get(endpoint)
.auth('Aererer', 'secretiveStuff')
.expect(200, 'You passed', done)
})
})

describe('promise authorizer', function() {
const endpoint = '/promise'

it('should reject on missing header', function(done) {
supertest(app)
.get(endpoint)
.expect(401, done)
})

it('should reject on wrong credentials', function(done) {
supertest(app)
.get(endpoint)
.auth('dude', 'stuff')
.expect(401, done)
})

it('should return 500 if authoriser rejects', function(done) {
supertest(app)
.get(endpoint)
.auth('error', 'stuff')
.expect(500, done)
})

it('should accept fitting credentials', function(done) {
supertest(app)
.get(endpoint)
Expand Down