UNPKG

badge

Version:

Stateless GitHub authentication for Hapi.

1,557 lines (1,245 loc) 35.3 kB
"use strict"; var Hapi = require("hapi"); var Lab = require("lab"); var Nipple = require("nipple"); var nock = require("nock"); var plugin = require(".."); var Q = require("q"); var sinon = require("sinon"); var _ = require("lodash"); var after = Lab.after; var before = Lab.before; var describe = Lab.describe; var expect = Lab.expect; var it = Lab.it; var GITHUB_API = "https://api.github.com"; var LOGIN = "octocat"; var OAUTH = "x-oauth-basic"; var ORGANIZATION = "octocats"; var PASSWORD = "password"; var TOKEN = "token"; var USER_AGENT = "Nipple"; var USERNAME = "testy"; var CLIENT_ID = "id"; var CLIENT_SECRET = "secret"; var NOTE = "an app"; var SCOPES = [ "a scope" ]; var URL = "http://example.com"; var BASIC_SCHEME = "Basic"; var CHALLENGE = "WWW-Authenticate"; var REALM = "a realm"; var TOKEN_SCHEME = "token"; function basicAuth (username, password) { return BASIC_SCHEME + " " + (new Buffer(username + ":" + password)).toString("base64"); } function createTestRoute (server, strategy) { server.route( { config : { auth : { mode : "try", strategy : strategy } }, handler : function (request, reply) { reply(request.auth); }, method : "GET", path : "/" } ); } describe("The GitHub basic auth scheme", function () { function assertChallenge (response) { expect(response.result.error, "no error").to.be.an.instanceOf(Error); expect(response.result.error.output.headers, "challenge") .to.have.property(CHALLENGE); expect(response.result.error.output.headers[CHALLENGE], "challenge scheme") .to.contain(BASIC_SCHEME); expect(response.result.error.output.headers[CHALLENGE], "realm") .not.to.contain("realm="); } function assertNoChallenge (response) { expect(response.result.error, "challeng").not.to.exist; } function authenticate (server) { var deferred = Q.defer(); server.inject( { headers : { authorization : basicAuth(USERNAME, PASSWORD) }, method : "GET", url : "/" }, deferred.resolve.bind(deferred) ); return deferred.promise; } function orgRequest () { return nock(GITHUB_API) .matchHeader("Authorization", basicAuth(USERNAME, PASSWORD)) .matchHeader("User-Agent", USER_AGENT) .get("/orgs/" + ORGANIZATION + "/members/" + LOGIN); } function tokenRequest () { return nock(GITHUB_API) .matchHeader("Authorization", basicAuth(USERNAME, PASSWORD)) .matchHeader("User-Agent", USER_AGENT) .put( "/authorizations/clients/" + CLIENT_ID, /* jshint -W106 */ { client_secret : CLIENT_SECRET, note : NOTE, note_url : URL, scopes : SCOPES } /* jshint +W106 */ ); } function userRequest () { return nock(GITHUB_API) .matchHeader("Authorization", basicAuth(USERNAME, PASSWORD)) .matchHeader("User-Agent", USER_AGENT) .get("/user"); } before(function (done) { nock.disableNetConnect(); done(); }); after(function (done) { nock.enableNetConnect(); done(); }); describe("using the default configuration", function () { function createServer () { var server = new Hapi.Server(); return Q.ninvoke(server.pack, "register", plugin) .then(function () { server.auth.strategy("default", "github-basic"); createTestRoute(server, "default"); return server; }); } describe("with valid credentials", function () { var request; var response; before(function (done) { createServer() .then(function (server) { request = userRequest().reply(200, { login : LOGIN }); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("verifies the credentials with GitHub", function (done) { expect(request.isDone(), "no request to GitHub").to.be.true; done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username", LOGIN); done(); }); it("permits the request", function (done) { expect(response.result.isAuthenticated, "not permitted") .to.be.true; done(); }); it("does not present an authentication challenge", function (done) { assertNoChallenge(response); done(); }); }); describe("with invalid credentials", function () { var request; var response; before(function (done) { createServer() .then(function (server) { request = userRequest().reply(401); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); it("verifies the credentials with GitHub", function (done) { expect(request.isDone(), "no GitHub request").to.be.true; done(); }); it("presents an authentication challenge", function (done) { assertChallenge(response); done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username", USERNAME); done(); }); it("prohibits the request", function (done) { expect(response.result.isAuthenticated, "permitted") .to.be.false; done(); }); }); describe("failing to contact GitHub", function () { var response; var getStub; before(function (done) { createServer() .then(function (server) { getStub = sinon.stub( Nipple, "get", function (uri, options, callback) { callback(new Error("boom!")); } ); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { getStub.restore(); done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username", USERNAME); done(); }); it("prohibits the request", function (done) { expect(response.result.isAuthenticated, "permitted") .to.be.false; done(); }); }); describe("without a basic credential", function () { var response; before(function (done) { createServer() .then(function (server) { var deferred = Q.defer(); server.inject( { method : "GET", url : "/" }, deferred.resolve.bind(deferred) ); return deferred.promise; }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); it("presents an authentication challenge", function (done) { assertChallenge(response); done(); }); it("does not return a username", function (done) { expect(response.result.credentials.username, "username") .not.to.exist; done(); }); it("prohibits the request", function (done) { expect(response.result.isAuthenticated, "permitted") .to.be.false; done(); }); }); }); describe("configured with application credentials", function () { function createServer () { var server = new Hapi.Server(); return Q.ninvoke(server.pack, "register", plugin) .then(function () { server.auth.strategy("generate-token", "github-basic", { application : { clientId : CLIENT_ID, clientSecret : CLIENT_SECRET, note : NOTE, scopes : SCOPES, url : URL } }); createTestRoute(server, "generate-token"); return server; }); } describe("with valid credentials", function () { var response; var tokenNock; var userNock; before(function (done) { createServer() .then(function (server) { tokenNock = tokenRequest().reply(200, { token : TOKEN }); userNock = userRequest().reply(200, { login : LOGIN }); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("verifies the request with GitHub", function (done) { expect(userNock.isDone(), "no user request").to.be.true; expect(tokenNock.isDone(), "no token request").to.be.true; done(); }); it("does not present an authentication challenge", function (done) { assertNoChallenge(response); done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username", LOGIN); done(); }); it("returns an API token", function (done) { expect(response.result.artifacts, "no token") .to.have.property("token", TOKEN); done(); }); it("permits the request", function (done) { expect(response.result.isAuthenticated, "not permitted") .to.be.true; done(); }); }); describe("with invalid credentials", function () { var response; var userNock; before(function (done) { createServer() .then(function (server) { userNock = userRequest().reply(401); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("verifies the credentials with GitHub", function (done) { expect(userNock.isDone(), "no GitHub request").to.be.true; done(); }); it("presents an authentication challenge", function (done) { assertChallenge(response); done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username", USERNAME); done(); }); it("does not return and API token", function (done) { expect(response.result.artifacts, "found token") .not.to.have.property("token"); done(); }); it("prohibits the request", function (done) { expect(response.result.isAuthenticated, "permitted") .to.be.false; done(); }); }); describe("failing to retrieve a token", function () { var response; var tokenNock; var userNock; before(function (done) { createServer() .then(function (server) { tokenNock = tokenRequest().reply(500); userNock = userRequest().reply(200, { login : LOGIN }); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("verifies the request with GitHub", function (done) { expect(userNock.isDone(), "no user request").to.be.true; expect(tokenNock.isDone(), "no token request").to.be.true; done(); }); it("does not return and API token", function (done) { expect(response.result.artifacts, "found token") .not.to.have.property("token"); done(); }); it("prohibits the request", function (done) { expect(response.result.isAuthenticated, "permitted") .to.be.false; done(); }); }); }); describe("configured with an organization", function () { function createServer () { var server = new Hapi.Server(); return Q.ninvoke(server.pack, "register", plugin) .then(function () { server.auth.strategy("basic-org", "github-basic", { organization : ORGANIZATION }); createTestRoute(server, "basic-org"); return server; }); } describe("given credentials for a member of the organization", function () { var orgNock; var response; var userNock; before(function (done) { createServer() .then(function (server) { userNock = userRequest().reply(200, { login : LOGIN }); orgNock = orgRequest().reply(204); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("verifies organization membership with GitHub", function (done) { expect(userNock.isDone(), "authentication request").to.be.true; expect(orgNock.isDone(), "membership request").to.be.true; done(); }); it("does not present an authentication challenge", function (done) { assertNoChallenge(response); done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username", LOGIN); done(); }); it("returns the organization", function (done) { expect(response.result.credentials, "no organization") .to.have.property("organization", ORGANIZATION); done(); }); it("permits the request", function (done) { expect(response.result.isAuthenticated, "prohibitted").to.be.true; done(); }); }); describe("given credentials for a non-member", function () { var orgNock; var response; var userNock; before(function (done) { createServer() .then(function (server) { userNock = userRequest().reply(200, { login : LOGIN }); orgNock = orgRequest().reply(404); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("verifies membership with GitHub", function (done) { expect(userNock.isDone(), "authentication request").to.be.true; expect(orgNock.isDone(), "membership request").to.be.true; done(); }); it("presents an authentication challenge", function (done) { assertChallenge(response); done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username", LOGIN); done(); }); it("does not return the organization", function (done) { expect(response.result.credentials, "organization") .not.to.have.property("organization"); done(); }); it("prohibits the request", function (done) { expect(response.result.isAuthenticated, "permitted").to.be.false; done(); }); }); }); describe("configured with application credentials and an organization", function () { function createServer () { var server = new Hapi.Server(); return Q.ninvoke(server.pack, "register", plugin) .then(function () { server.auth.strategy("client-org", "github-basic", { application : { clientId : CLIENT_ID, clientSecret : CLIENT_SECRET, note : NOTE, scopes : SCOPES, url : URL }, organization : ORGANIZATION }); createTestRoute(server, "client-org"); return server; }); } describe("given credentials for a member of the organization", function () { var orgNock; var response; var tokenNock; var userNock; before(function (done) { createServer() .then(function (server) { orgNock = orgRequest().reply(204); tokenNock = tokenRequest().reply(200, { token : TOKEN }); userNock = userRequest().reply(200, { login : LOGIN }); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("verifies membership with GitHub", function (done) { expect(userNock.isDone(), "authentication request").to.be.true; expect(orgNock.isDone(), "membership request").to.be.true; done(); }); it("does not present an authentication challenge", function (done) { assertNoChallenge(response); done(); }); it("requests a token", function (done) { expect(tokenNock.isDone(), "token request").to.be.true; done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username", LOGIN); done(); }); it("returns the organization", function (done) { expect(response.result.credentials, "no organization") .to.have.property("organization", ORGANIZATION); done(); }); it("returns a token", function (done) { expect(response.result.artifacts, "no token") .to.have.property("token", TOKEN); done(); }); it("permits the request", function (done) { expect(response.result.isAuthenticated, "prohibitted").to.be.true; done(); }); }); describe("given credentials for a non-member", function () { var orgNock; var response; var tokenNock; var userNock; before(function (done) { createServer() .then(function (server) { orgNock = orgRequest().reply(404); tokenNock = tokenRequest().reply(500); userNock = userRequest().reply(200, { login : LOGIN }); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("verifies membership with GitHub", function (done) { expect(userNock.isDone(), "authentication request").to.be.true; expect(orgNock.isDone(), "membership request").to.be.true; done(); }); it("presents an authentication challenge", function (done) { assertChallenge(response); done(); }); it("does not request a token", function (done) { expect(tokenNock.isDone(), "token request").to.be.false; done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username", LOGIN); done(); }); it("does not return the organization", function (done) { expect(response.result.credentials, "organization") .not.to.have.property("organization"); done(); }); it("does not return a token", function (done) { expect(response.result.artifacts, "token") .not.to.have.property("token"); done(); }); it("prohibits the request", function (done) { expect(response.result.isAuthenticated, "permitted").to.be.false; done(); }); }); }); describe("configured with a realm", function () { function createServer () { var server = new Hapi.Server(); return Q.ninvoke(server.pack, "register", plugin) .then(function () { server.auth.strategy("basic-realm", "github-basic", { realm : REALM }); createTestRoute(server, "basic-realm"); return server; }); } describe("failing to authenticate a user", function () { var response; var userNock; before(function (done) { createServer() .then(function (server) { userNock = userRequest().reply(401); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("includes a realm in the authentication challenge", function (done) { expect(response.result.error, "no error").to.be.an.instanceOf(Error); expect(response.result.error.output.headers, "challenge") .to.have.property(CHALLENGE); expect(response.result.error.output.headers[CHALLENGE], "no realm") .to.contain("realm=\"" + REALM +"\""); done(); }); }); }); describe("cache", function () { function createServer () { var server = new Hapi.Server(); return Q.ninvoke(server.pack, "register", plugin) .then(function () { server.auth.strategy("caching", "github-basic", { organization : ORGANIZATION }); createTestRoute(server, "caching"); return server; }); } describe("for a authenticated request", function () { var orgNock; var response1; var response2; var userNock; before(function (done) { createServer() .then(function (server) { orgNock = orgRequest().reply(204); userNock = userRequest().reply(200, { login : LOGIN }); return [ server, authenticate(server) ]; }) .spread(function (server, response) { response1 = response; return authenticate(server); }) .then(function (response) { response2 = response; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("verifies the credentials once", function (done) { expect(userNock.isDone(), "user request").to.be.true; expect(orgNock.isDone(), "membership request").to.be.true; done(); }); it("caches the auth result", function (done) { expect(response1.result.isAuthenticated, "prohibitted").to.be.true; expect(response1.result.credentials, "username") .to.have.property("username", LOGIN); expect(response1.result, "results").to.deep.equal(response2.result); done(); }); }); }); describe("configuration", function () { it("cannot have an unsupported option", function (done) { var server = new Hapi.Server(); server.pack.register(plugin, function () { expect(function () { server.auth.strategy("error", "github-basic", { foo : "bar" }); }).to.throw(/not allowed/i); done(); }); }); }); describe("application configuration", function () { var configuration = { clientId : CLIENT_ID, clientSecret : CLIENT_SECRET, note : NOTE, scopes : SCOPES, url : URL }; function testConfiguration (configuration, key, done) { var server = new Hapi.Server(); var options = { application : _.clone(configuration) }; server.pack.register(plugin, function () { delete options.application[key]; expect(function () { server.auth.strategy("error", "github-basic", options); }).to.throw(new RegExp(key, "i")); done(); }); } it("requires a client ID", function (done) { testConfiguration(configuration, "clientId", done); }); it("requires a client secret", function (done) { testConfiguration(configuration, "clientSecret", done); }); it("requires a note", function (done) { testConfiguration(configuration, "note", done); }); it("requires a scope list", function (done) { testConfiguration(configuration, "scopes", done); }); it("requires a URL", function (done) { testConfiguration(configuration, "url", done); }); }); }); describe("The GitHub token auth scheme", function () { function assertChallenge (response) { expect(response.result.error, "no error").to.be.an.instanceOf(Error); expect(response.result.error.output.headers, "challenge") .to.have.property(CHALLENGE); expect(response.result.error.output.headers[CHALLENGE], "challeng scheme") .to.contain(TOKEN_SCHEME); expect(response.result.error.output.headers[CHALLENGE], "realm") .not.to.contain("realm="); } function assertNoChallenge (response) { expect(response.result.error, "challenge").not.to.exist; } function authenticate (server) { var deferred = Q.defer(); server.inject( { headers : { authorization : TOKEN_SCHEME + " " + TOKEN }, method : "GET", url : "/" }, deferred.resolve.bind(deferred) ); return deferred.promise; } function orgRequest () { return nock(GITHUB_API) .matchHeader("Authorization", basicAuth(TOKEN, OAUTH)) .matchHeader("User-Agent", USER_AGENT) .get("/orgs/" + ORGANIZATION + "/members/" + LOGIN); } function tokenRequest () { return nock(GITHUB_API) .matchHeader("Authorization", basicAuth(CLIENT_ID, CLIENT_SECRET)) .matchHeader("User-Agent", USER_AGENT) .get("/applications/" + CLIENT_ID + "/tokens/" + TOKEN); } before(function (done) { nock.disableNetConnect(); done(); }); after(function (done) { nock.enableNetConnect(); done(); }); describe("using the basic configuration", function () { function createServer () { var server = new Hapi.Server(); return Q.ninvoke(server.pack, "register", plugin) .then(function () { server.auth.strategy("token-basic", "github-token", { clientId : CLIENT_ID, clientSecret : CLIENT_SECRET }); createTestRoute(server, "token-basic"); return server; }); } describe("with a valid token", function () { var response; var tokenNock; before(function (done) { createServer() .then(function (server) { tokenNock = tokenRequest().reply( 200, { token : TOKEN, user : { login : LOGIN } } ); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); it("verifies the token with GitHub", function (done) { expect(tokenNock.isDone(), "no GitHub request").to.be.true; done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username", LOGIN); done(); }); it("permits the request", function (done) { expect(response.result.isAuthenticated, "not permitted") .to.be.true; done(); }); it("does not present an authentication challenge", function (done) { assertNoChallenge(response); done(); }); }); describe("with an invalid token", function () { var response; var tokenNock; before(function (done) { createServer() .then(function (server) { tokenNock = tokenRequest().reply(404); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); it("verifies the token with GitHub", function (done) { expect(tokenNock.isDone(), "no GitHub request").to.be.true; done(); }); it("does not return the username", function (done) { expect(response.result.credentials, "found username") .not.to.have.property("username"); done(); }); it("prohibits the request", function (done) { expect(response.result.isAuthenticated, "permitted") .to.be.false; done(); }); it("presents an authentication challenge", function (done) { assertChallenge(response); done(); }); }); describe("failing to contact GitHub", function () { var getStub; var response; before(function (done) { createServer() .then(function (server) { getStub = sinon.stub( Nipple, "get", function (uri, options, callback) { callback(new Error("boom!")); } ); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { getStub.restore(); done(); }); it("verifies the request with GitHub", function (done) { expect(getStub.calledOnce, "no GitHub request").to.be.true; done(); }); it("does not return the username", function (done) { expect(response.result.credentials, "found username") .not.to.have.property("username"); done(); }); it("prohibits the request", function (done) { expect(response.result.isAuthenticated, "permitted") .to.be.false; done(); }); }); describe("without a token", function () { var github; var response; before(function (done) { createServer() .then(function (server) { var deferred = Q.defer(); // Catch-all for unexpected GET requests. github = nock(GITHUB_API) .filteringPath(/.*/, "/") .get("/") .reply(200); server.inject( { method : "GET", url : "/" }, deferred.resolve.bind(deferred) ); return deferred.promise; }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("does not contact GitHub", function (done) { expect(github.isDone(), "GitHub request").to.be.false; done(); }); it("does not return the username", function (done) { expect(response.result.credentials, "found username") .not.to.have.property("username"); done(); }); it("prohibits the request", function (done) { expect(response.result.isAuthenticated, "permitted") .to.be.false; done(); }); it("presents an authentication challenge", function (done) { assertChallenge(response); done(); }); }); }); describe("configured with an organization", function () { function createServer () { var server = new Hapi.Server(); return Q.ninvoke(server.pack, "register", plugin) .then(function () { server.auth.strategy("token-org", "github-token", { clientId : CLIENT_ID, clientSecret : CLIENT_SECRET, organization : ORGANIZATION }); createTestRoute(server, "token-org"); return server; }); } describe("with a token belonging to the organization", function () { var orgNock; var response; var tokenNock; before(function (done) { createServer() .then(function (server) { tokenNock = tokenRequest().reply( 200, { token : TOKEN, user : { login : LOGIN } } ); orgNock = orgRequest().reply(204); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); it("verifies organization membership with GitHub", function (done) { expect(tokenNock.isDone(), "no token request").to.be.true; expect(orgNock.isDone(), "no org request").to.be.true; done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username"); done(); }); it("returns the organization", function (done) { expect(response.result.credentials, "no organization") .to.have.property("organization", ORGANIZATION); done(); }); it("permits the requets", function (done) { expect(response.result.isAuthenticated, "prohibitted") .to.be.true; done(); }); it("does not present an authentication challenge", function (done) { assertNoChallenge(response); done(); }); }); describe("with a token not belonging to the organization", function () { var orgNock; var response; var tokenNock; before(function (done) { createServer() .then(function (server) { tokenNock = tokenRequest().reply( 200, { token : TOKEN, user : { login : LOGIN } } ); orgNock = orgRequest().reply(404); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); it("verifies organization membership with GitHub", function (done) { expect(tokenNock.isDone(), "no token request").to.be.true; expect(orgNock.isDone(), "no org request").to.be.true; done(); }); it("returns the username", function (done) { expect(response.result.credentials, "no username") .to.have.property("username", LOGIN); done(); }); it("does not return the organization", function (done) { expect(response.result.credentials, "found organization") .not.to.have.property("organization"); done(); }); it("prohibits the request", function (done) { expect(response.result.isAuthenticated, "permitted") .to.be.false; done(); }); it("presents an authentication challenge", function (done) { assertChallenge(response); done(); }); }); }); describe("configured with a realm", function () { function createServer () { var server = new Hapi.Server(); return Q.ninvoke(server.pack, "register", plugin) .then(function () { server.auth.strategy("token-realm", "github-token", { clientId : CLIENT_ID, clientSecret : CLIENT_SECRET, realm : REALM }); createTestRoute(server, "token-realm"); return server; }); } describe("failing to validate a token", function () { var response; var tokenNock; before(function (done) { createServer() .then(function (server) { tokenNock = tokenRequest().reply(404); return authenticate(server); }) .then(function (_response_) { response = _response_; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("includes a realm with the authentication challenge", function (done) { expect(response.result.error, "no error").to.be.an.instanceOf(Error); expect(response.result.error.output.headers, "no challenge") .to.have.property(CHALLENGE); expect(response.result.error.output.headers[CHALLENGE], "realm") .to.contain("realm=\"" + REALM + "\""); done(); }); }); }); describe("cache", function () { function createServer () { var server = new Hapi.Server(); return Q.ninvoke(server.pack, "register", plugin) .then(function () { server.auth.strategy("caching", "github-token", { clientId : CLIENT_ID, clientSecret : CLIENT_SECRET, organization : ORGANIZATION }); createTestRoute(server, "caching"); return server; }); } describe("for a authenticated request", function () { var orgNock; var response1; var response2; var tokenNock; before(function (done) { createServer() .then(function (server) { orgNock = orgRequest().reply(204); tokenNock = tokenRequest().reply( 200, { token : TOKEN, user : { login : LOGIN } } ); return [ server, authenticate(server) ]; }) .spread(function (server, response) { response1 = response; return authenticate(server); }) .then(function (response) { response2 = response; }) .nodeify(done); }); after(function (done) { nock.cleanAll(); done(); }); it("verifies the credentials once", function (done) { expect(tokenNock.isDone(), "user request").to.be.true; expect(orgNock.isDone(), "membership request").to.be.true; done(); }); it("caches the auth result", function (done) { expect(response1.result.isAuthenticated, "prohibitted").to.be.true; expect(response1.result.credentials, "username") .to.have.property("username", LOGIN); expect(response1.result, "results").to.deep.equal(response2.result); done(); }); }); }); describe("configuration", function () { function testConfiguration (config, pattern, done) { var server = new Hapi.Server(); server.pack.register(plugin, function (error) { expect(function () { server.auth.strategy("config", "github-token", config); }).to.throw(pattern); done(error); }); } it("must be provided", function (done) { testConfiguration(undefined, /missing/i, done); }); it("requires a client ID", function (done) { testConfiguration({ clientSecret : CLIENT_SECRET }, /clientId/i, done); }); it("requires a client secret", function (done) { testConfiguration({ clientId : CLIENT_ID }, /clientSecret/i, done); }); it("does not allow unknown options", function (done) { var configuration = { clientId : CLIENT_ID, clientSecret : CLIENT_SECRET, foo : "bar" }; testConfiguration(configuration, /not allowed/i, done); }); }); });