-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Alexey Melnikov
committed
Jul 15, 2016
1 parent
f1d3cd0
commit 72e1ded
Showing
6 changed files
with
199 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
var Hapi = require('hapi'); | ||
var async = require('async'); | ||
var chai = require('chai'); | ||
chai.should(); | ||
|
||
describe('HAPI with auth', function () { | ||
var server; | ||
var max = 5; | ||
var duration = 10000; | ||
var user; | ||
|
||
beforeEach(function() { | ||
user = { | ||
name: 'user', | ||
password: 'password', | ||
id: new Date().getTime() | ||
}; | ||
|
||
var users = {}; | ||
users[user.name] = user; | ||
|
||
server = new Hapi.Server(); | ||
server.connection({port: 8000}); | ||
|
||
var options = { | ||
redis: { | ||
host: process.env.REDIS_HOST, | ||
port: 6379 | ||
}, | ||
getKey: function (request, reply, done) { | ||
done(null, request.auth.credentials.id); | ||
}, | ||
getLimit: function (request, reply, done) { | ||
done(null, { | ||
max: max, | ||
duration: duration | ||
}); | ||
} | ||
}; | ||
|
||
server.register([{ | ||
register: require('../'), | ||
options: options | ||
}, { | ||
register: require('./basic-auth') | ||
}], function (err) { | ||
if (err) { | ||
throw err; | ||
} | ||
|
||
// register simple strategy | ||
server.auth.strategy('simple', 'basic', { | ||
validateFunc: function (username, password, callback) { | ||
var u = users[username]; | ||
if (!u) { | ||
return callback(null, false); | ||
} | ||
callback(null, true, { id: u.id, name: u.name }); | ||
} | ||
}); | ||
}); | ||
|
||
server.route({ | ||
method: 'GET', | ||
path: '/admin', | ||
config: { | ||
auth: 'simple', | ||
handler: function (request, reply) { | ||
reply('OK'); | ||
}, | ||
} | ||
}); | ||
}); | ||
|
||
it('should responds 401 with X-RateLimit-* headers for unauthenticated user', function (done) { | ||
var options = { | ||
method: 'GET', | ||
url: '/admin' | ||
}; | ||
|
||
server.inject(options, function (response) { | ||
response.statusCode.should.equal(401); | ||
done(); | ||
}); | ||
}); | ||
|
||
it('should responds 200 with X-RateLimit-* headers for authenticated user', function (done) { | ||
function basicHeader (username, password) { | ||
return 'Basic ' + (new Buffer(username + ':' + password, 'utf8')).toString('base64'); | ||
} | ||
|
||
var options = { | ||
method: 'GET', | ||
url: '/admin', | ||
headers: { | ||
authorization: basicHeader(user.name, user.password) | ||
} | ||
}; | ||
|
||
server.inject(options, function (response) { | ||
response.statusCode.should.equal(200); | ||
|
||
response.headers.should.have.property('x-ratelimit-limit'); | ||
response.headers['x-ratelimit-limit'].should.equal(max); | ||
|
||
response.headers.should.have.property('x-ratelimit-remaining'); | ||
response.headers['x-ratelimit-remaining'].should.equal(max - 1); | ||
|
||
response.headers.should.have.property('x-ratelimit-reset'); | ||
var reset = +response.headers['x-ratelimit-reset']; | ||
(reset - (Date.now() + duration)/1000 <= 0).should.be.true; | ||
|
||
done(); | ||
}); | ||
}); | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// Load modules | ||
|
||
var Boom = require('boom'); | ||
var Hoek = require('hoek'); | ||
|
||
|
||
// Declare internals | ||
|
||
var internals = {}; | ||
|
||
|
||
exports.register = function (plugin, options, next) { | ||
plugin.auth.scheme('basic', internals.implementation); | ||
next(); | ||
}; | ||
|
||
|
||
internals.implementation = function (server, options) { | ||
|
||
Hoek.assert(options, 'Missing basic auth strategy options'); | ||
Hoek.assert(typeof options.validateFunc === 'function', 'options.validateFunc must be a valid function in basic scheme'); | ||
|
||
var settings = Hoek.clone(options); | ||
|
||
var scheme = { | ||
authenticate: function (request, reply) { | ||
var req = request.raw.req; | ||
var authorization = req.headers.authorization; | ||
if (!authorization) { | ||
return reply(Boom.unauthorized(null, 'Basic')); | ||
} | ||
|
||
var parts = authorization.split(/\s+/); | ||
|
||
if (parts[0].toLowerCase() !== 'basic') { | ||
return reply(Boom.unauthorized(null, 'Basic')); | ||
} | ||
|
||
if (parts.length !== 2) { | ||
return reply(Boom.badRequest('Bad HTTP authentication header format', 'Basic')); | ||
} | ||
|
||
var credentialsParts = new Buffer(parts[1], 'base64').toString().split(':'); | ||
if (credentialsParts.length !== 2) { | ||
return reply(Boom.badRequest('Bad header internal syntax', 'Basic')); | ||
} | ||
|
||
var username = credentialsParts[0]; | ||
var password = credentialsParts[1]; | ||
|
||
if (!username && !settings.allowEmptyUsername) { | ||
return reply(Boom.unauthorized('HTTP authentication header missing username', 'Basic')); | ||
} | ||
|
||
settings.validateFunc(username, password, function (err, isValid, credentials) { | ||
credentials = credentials || null; | ||
if (err) { | ||
return reply(err, { credentials: credentials, log: { tags: ['auth', 'basic'], data: err } }); | ||
} | ||
if (!isValid) { | ||
return reply(Boom.unauthorized('Bad username or password', 'Basic'), { credentials: credentials }); | ||
} | ||
if (!credentials || typeof credentials !== 'object') { | ||
return reply(Boom.badImplementation('Bad credentials object received for Basic auth validation'), { log: { tags: 'credentials' } }); | ||
} | ||
|
||
// Authenticated | ||
return reply.continue({credentials: credentials}); | ||
}); | ||
} | ||
}; | ||
|
||
return scheme; | ||
}; | ||
|
||
exports.register.attributes = { | ||
name: 'hapi-basic-auth' | ||
}; |
File renamed without changes.
File renamed without changes.