Refactor access token to make it extensible
1. Make it possible to reuse getIdForRequest() 2. Introduce a flag to control if oAuth2 bearer token should be base64 encoded 3. Promote resolve() to locate/validate access tokens by id
This commit is contained in:
parent
6a4bd6d09f
commit
69df11bb8e
|
@ -79,24 +79,98 @@ module.exports = function(AccessToken) {
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a token for the given `ServerRequest`.
|
* Extract the access token id from the HTTP request
|
||||||
*
|
* @param {Request} req HTTP request object
|
||||||
* @param {ServerRequest} req
|
* @options {Object} [options] Each option array is used to add additional keys to find an `accessToken` for a `request`.
|
||||||
* @param {Object} [options] Options for finding the token
|
* @property {Array} [cookies] Array of cookie names.
|
||||||
* @callback {Function} callback
|
* @property {Array} [headers] Array of header names.
|
||||||
* @param {Error} err
|
* @property {Array} [params] Array of param names.
|
||||||
* @param {AccessToken} token
|
* @property {Boolean} [searchDefaultTokenKeys] Use the default search locations for Token in request
|
||||||
|
* @property {Boolean} [bearerTokenBase64Encoded] Defaults to `true`. For `Bearer` token based `Authorization` headers,
|
||||||
|
* decode the value from `Base64`. If set to `false`, the decoding will be skipped and the token id will be the raw value
|
||||||
|
* parsed from the header.
|
||||||
|
* @return {String} The access token
|
||||||
*/
|
*/
|
||||||
|
AccessToken.getIdForRequest = function(req, options) {
|
||||||
|
options = options || {};
|
||||||
|
var params = options.params || [];
|
||||||
|
var headers = options.headers || [];
|
||||||
|
var cookies = options.cookies || [];
|
||||||
|
var i = 0;
|
||||||
|
var length, id;
|
||||||
|
|
||||||
AccessToken.findForRequest = function(req, options, cb) {
|
// https://github.com/strongloop/loopback/issues/1326
|
||||||
if (cb === undefined && typeof options === 'function') {
|
if (options.searchDefaultTokenKeys !== false) {
|
||||||
cb = options;
|
params = params.concat(['access_token']);
|
||||||
options = {};
|
headers = headers.concat(['X-Access-Token', 'authorization']);
|
||||||
|
cookies = cookies.concat(['access_token', 'authorization']);
|
||||||
}
|
}
|
||||||
|
|
||||||
var id = tokenIdForRequest(req, options);
|
for (length = params.length; i < length; i++) {
|
||||||
|
var param = params[i];
|
||||||
|
// replacement for deprecated req.param()
|
||||||
|
id = req.params && req.params[param] !== undefined ? req.params[param] :
|
||||||
|
req.body && req.body[param] !== undefined ? req.body[param] :
|
||||||
|
req.query && req.query[param] !== undefined ? req.query[param] :
|
||||||
|
undefined;
|
||||||
|
|
||||||
if (id) {
|
if (typeof id === 'string') {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0, length = headers.length; i < length; i++) {
|
||||||
|
id = req.header(headers[i]);
|
||||||
|
|
||||||
|
if (typeof id === 'string') {
|
||||||
|
// Add support for oAuth 2.0 bearer token
|
||||||
|
// http://tools.ietf.org/html/rfc6750
|
||||||
|
if (id.indexOf('Bearer ') === 0) {
|
||||||
|
id = id.substring(7);
|
||||||
|
if (options.bearerTokenBase64Encoded) {
|
||||||
|
// Decode from base64
|
||||||
|
var buf = new Buffer(id, 'base64');
|
||||||
|
id = buf.toString('utf8');
|
||||||
|
}
|
||||||
|
} else if (/^Basic /i.test(id)) {
|
||||||
|
id = id.substring(6);
|
||||||
|
id = (new Buffer(id, 'base64')).toString('utf8');
|
||||||
|
// The spec says the string is user:pass, so if we see both parts
|
||||||
|
// we will assume the longer of the two is the token, so we will
|
||||||
|
// extract "a2b2c3" from:
|
||||||
|
// "a2b2c3"
|
||||||
|
// "a2b2c3:" (curl http://a2b2c3@localhost:3000/)
|
||||||
|
// "token:a2b2c3" (curl http://token:a2b2c3@localhost:3000/)
|
||||||
|
// ":a2b2c3"
|
||||||
|
var parts = /^([^:]*):(.*)$/.exec(id);
|
||||||
|
if (parts) {
|
||||||
|
id = parts[2].length > parts[1].length ? parts[2] : parts[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.signedCookies) {
|
||||||
|
for (i = 0, length = cookies.length; i < length; i++) {
|
||||||
|
id = req.signedCookies[cookies[i]];
|
||||||
|
|
||||||
|
if (typeof id === 'string') {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve and validate the access token by id
|
||||||
|
* @param {String} id Access token
|
||||||
|
* @callback {Function} cb Callback function
|
||||||
|
* @param {Error} err Error information
|
||||||
|
* @param {Object} Resolved access token object
|
||||||
|
*/
|
||||||
|
AccessToken.resolve = function(id, cb) {
|
||||||
this.findById(id, function(err, token) {
|
this.findById(id, function(err, token) {
|
||||||
if (err) {
|
if (err) {
|
||||||
cb(err);
|
cb(err);
|
||||||
|
@ -117,10 +191,29 @@ module.exports = function(AccessToken) {
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a token for the given `ServerRequest`.
|
||||||
|
*
|
||||||
|
* @param {ServerRequest} req
|
||||||
|
* @param {Object} [options] Options for finding the token
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {AccessToken} token
|
||||||
|
*/
|
||||||
|
AccessToken.findForRequest = function(req, options, cb) {
|
||||||
|
if (cb === undefined && typeof options === 'function') {
|
||||||
|
cb = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = this.getIdForRequest(req, options);
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
this.resolve(id, cb);
|
||||||
} else {
|
} else {
|
||||||
process.nextTick(function() {
|
process.nextTick(cb);
|
||||||
cb();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -131,7 +224,6 @@ module.exports = function(AccessToken) {
|
||||||
* @param {Error} err
|
* @param {Error} err
|
||||||
* @param {Boolean} isValid
|
* @param {Boolean} isValid
|
||||||
*/
|
*/
|
||||||
|
|
||||||
AccessToken.prototype.validate = function(cb) {
|
AccessToken.prototype.validate = function(cb) {
|
||||||
try {
|
try {
|
||||||
assert(
|
assert(
|
||||||
|
@ -181,73 +273,4 @@ module.exports = function(AccessToken) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function tokenIdForRequest(req, options) {
|
|
||||||
var params = options.params || [];
|
|
||||||
var headers = options.headers || [];
|
|
||||||
var cookies = options.cookies || [];
|
|
||||||
var i = 0;
|
|
||||||
var length, id;
|
|
||||||
|
|
||||||
// https://github.com/strongloop/loopback/issues/1326
|
|
||||||
if (options.searchDefaultTokenKeys !== false) {
|
|
||||||
params = params.concat(['access_token']);
|
|
||||||
headers = headers.concat(['X-Access-Token', 'authorization']);
|
|
||||||
cookies = cookies.concat(['access_token', 'authorization']);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (length = params.length; i < length; i++) {
|
|
||||||
var param = params[i];
|
|
||||||
// replacement for deprecated req.param()
|
|
||||||
id = req.params && req.params[param] !== undefined ? req.params[param] :
|
|
||||||
req.body && req.body[param] !== undefined ? req.body[param] :
|
|
||||||
req.query && req.query[param] !== undefined ? req.query[param] :
|
|
||||||
undefined;
|
|
||||||
|
|
||||||
if (typeof id === 'string') {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0, length = headers.length; i < length; i++) {
|
|
||||||
id = req.header(headers[i]);
|
|
||||||
|
|
||||||
if (typeof id === 'string') {
|
|
||||||
// Add support for oAuth 2.0 bearer token
|
|
||||||
// http://tools.ietf.org/html/rfc6750
|
|
||||||
if (id.indexOf('Bearer ') === 0) {
|
|
||||||
id = id.substring(7);
|
|
||||||
// Decode from base64
|
|
||||||
var buf = new Buffer(id, 'base64');
|
|
||||||
id = buf.toString('utf8');
|
|
||||||
} else if (/^Basic /i.test(id)) {
|
|
||||||
id = id.substring(6);
|
|
||||||
id = (new Buffer(id, 'base64')).toString('utf8');
|
|
||||||
// The spec says the string is user:pass, so if we see both parts
|
|
||||||
// we will assume the longer of the two is the token, so we will
|
|
||||||
// extract "a2b2c3" from:
|
|
||||||
// "a2b2c3"
|
|
||||||
// "a2b2c3:" (curl http://a2b2c3@localhost:3000/)
|
|
||||||
// "token:a2b2c3" (curl http://token:a2b2c3@localhost:3000/)
|
|
||||||
// ":a2b2c3"
|
|
||||||
var parts = /^([^:]*):(.*)$/.exec(id);
|
|
||||||
if (parts) {
|
|
||||||
id = parts[2].length > parts[1].length ? parts[2] : parts[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.signedCookies) {
|
|
||||||
for (i = 0, length = cookies.length; i < length; i++) {
|
|
||||||
id = req.signedCookies[cookies[i]];
|
|
||||||
|
|
||||||
if (typeof id === 'string') {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -88,6 +88,9 @@ function escapeRegExp(str) {
|
||||||
* @property {Boolean} [overwriteExistingToken] only has effect in combination with `enableDoublecheck`. If truthy, will allow to overwrite an existing accessToken.
|
* @property {Boolean} [overwriteExistingToken] only has effect in combination with `enableDoublecheck`. If truthy, will allow to overwrite an existing accessToken.
|
||||||
* @property {Function|String} [model] AccessToken model name or class to use.
|
* @property {Function|String} [model] AccessToken model name or class to use.
|
||||||
* @property {String} [currentUserLiteral] String literal for the current user.
|
* @property {String} [currentUserLiteral] String literal for the current user.
|
||||||
|
* @property {Boolean} [bearerTokenBase64Encoded] Defaults to `true`. For `Bearer` token based `Authorization` headers,
|
||||||
|
* decode the value from `Base64`. If set to `false`, the decoding will be skipped and the token id will be the raw value
|
||||||
|
* parsed from the header.
|
||||||
* @header loopback.token([options])
|
* @header loopback.token([options])
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -104,6 +107,9 @@ function token(options) {
|
||||||
currentUserLiteral = escapeRegExp(currentUserLiteral);
|
currentUserLiteral = escapeRegExp(currentUserLiteral);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.bearerTokenBase64Encoded === undefined) {
|
||||||
|
options.bearerTokenBase64Encoded = true;
|
||||||
|
}
|
||||||
var enableDoublecheck = !!options.enableDoublecheck;
|
var enableDoublecheck = !!options.enableDoublecheck;
|
||||||
var overwriteExistingToken = !!options.overwriteExistingToken;
|
var overwriteExistingToken = !!options.overwriteExistingToken;
|
||||||
|
|
||||||
|
|
|
@ -115,14 +115,14 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should populate req.token from the query string', function(done) {
|
it('populates req.token from the query string', function(done) {
|
||||||
createTestAppAndRequest(this.token, done)
|
createTestAppAndRequest(this.token, done)
|
||||||
.get('/?access_token=' + this.token.id)
|
.get('/?access_token=' + this.token.id)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(done);
|
.end(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should populate req.token from an authorization header', function(done) {
|
it('populates req.token from an authorization header', function(done) {
|
||||||
createTestAppAndRequest(this.token, done)
|
createTestAppAndRequest(this.token, done)
|
||||||
.get('/')
|
.get('/')
|
||||||
.set('authorization', this.token.id)
|
.set('authorization', this.token.id)
|
||||||
|
@ -130,7 +130,7 @@ describe('loopback.token(options)', function() {
|
||||||
.end(done);
|
.end(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should populate req.token from an X-Access-Token header', function(done) {
|
it('populates req.token from an X-Access-Token header', function(done) {
|
||||||
createTestAppAndRequest(this.token, done)
|
createTestAppAndRequest(this.token, done)
|
||||||
.get('/')
|
.get('/')
|
||||||
.set('X-Access-Token', this.token.id)
|
.set('X-Access-Token', this.token.id)
|
||||||
|
@ -138,7 +138,7 @@ describe('loopback.token(options)', function() {
|
||||||
.end(done);
|
.end(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not search default keys when searchDefaultTokenKeys is false',
|
it('does not search default keys when searchDefaultTokenKeys is false',
|
||||||
function(done) {
|
function(done) {
|
||||||
var tokenId = this.token.id;
|
var tokenId = this.token.id;
|
||||||
var app = createTestApp(
|
var app = createTestApp(
|
||||||
|
@ -162,7 +162,8 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should populate req.token from an authorization header with bearer token', function(done) {
|
it('populates req.token from an authorization header with bearer token with base64',
|
||||||
|
function(done) {
|
||||||
var token = this.token.id;
|
var token = this.token.id;
|
||||||
token = 'Bearer ' + new Buffer(token).toString('base64');
|
token = 'Bearer ' + new Buffer(token).toString('base64');
|
||||||
createTestAppAndRequest(this.token, done)
|
createTestAppAndRequest(this.token, done)
|
||||||
|
@ -172,6 +173,16 @@ describe('loopback.token(options)', function() {
|
||||||
.end(done);
|
.end(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('populates req.token from an authorization header with bearer token', function(done) {
|
||||||
|
var token = this.token.id;
|
||||||
|
token = 'Bearer ' + token;
|
||||||
|
createTestAppAndRequest(this.token, {token: {bearerTokenBase64Encoded: false}}, done)
|
||||||
|
.get('/')
|
||||||
|
.set('authorization', token)
|
||||||
|
.expect(200)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
describe('populating req.token from HTTP Basic Auth formatted authorization header', function() {
|
describe('populating req.token from HTTP Basic Auth formatted authorization header', function() {
|
||||||
it('parses "standalone-token"', function(done) {
|
it('parses "standalone-token"', function(done) {
|
||||||
var token = this.token.id;
|
var token = this.token.id;
|
||||||
|
@ -214,7 +225,7 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should populate req.token from a secure cookie', function(done) {
|
it('populates req.token from a secure cookie', function(done) {
|
||||||
var app = createTestApp(this.token, done);
|
var app = createTestApp(this.token, done);
|
||||||
|
|
||||||
request(app)
|
request(app)
|
||||||
|
@ -227,7 +238,7 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should populate req.token from a header or a secure cookie', function(done) {
|
it('populates req.token from a header or a secure cookie', function(done) {
|
||||||
var app = createTestApp(this.token, done);
|
var app = createTestApp(this.token, done);
|
||||||
var id = this.token.id;
|
var id = this.token.id;
|
||||||
request(app)
|
request(app)
|
||||||
|
@ -241,7 +252,7 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should rewrite url for the current user literal at the end without query',
|
it('rewrites url for the current user literal at the end without query',
|
||||||
function(done) {
|
function(done) {
|
||||||
var app = createTestApp(this.token, done);
|
var app = createTestApp(this.token, done);
|
||||||
var id = this.token.id;
|
var id = this.token.id;
|
||||||
|
@ -257,7 +268,7 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should rewrite url for the current user literal at the end with query',
|
it('rewrites url for the current user literal at the end with query',
|
||||||
function(done) {
|
function(done) {
|
||||||
var app = createTestApp(this.token, done);
|
var app = createTestApp(this.token, done);
|
||||||
var id = this.token.id;
|
var id = this.token.id;
|
||||||
|
@ -273,7 +284,7 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should rewrite url for the current user literal in the middle',
|
it('rewrites url for the current user literal in the middle',
|
||||||
function(done) {
|
function(done) {
|
||||||
var app = createTestApp(this.token, done);
|
var app = createTestApp(this.token, done);
|
||||||
var id = this.token.id;
|
var id = this.token.id;
|
||||||
|
@ -289,7 +300,7 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate a 401 on a current user literal route without an authToken',
|
it('generates a 401 on a current user literal route without an authToken',
|
||||||
function(done) {
|
function(done) {
|
||||||
var app = createTestApp(null, done);
|
var app = createTestApp(null, done);
|
||||||
request(app)
|
request(app)
|
||||||
|
@ -299,7 +310,7 @@ describe('loopback.token(options)', function() {
|
||||||
.end(done);
|
.end(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate a 401 on a current user literal route with invalid authToken',
|
it('generates a 401 on a current user literal route with invalid authToken',
|
||||||
function(done) {
|
function(done) {
|
||||||
var app = createTestApp(this.token, done);
|
var app = createTestApp(this.token, done);
|
||||||
request(app)
|
request(app)
|
||||||
|
@ -309,7 +320,7 @@ describe('loopback.token(options)', function() {
|
||||||
.end(done);
|
.end(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip when req.token is already present', function(done) {
|
it('skips when req.token is already present', function(done) {
|
||||||
var tokenStub = {id: 'stub id'};
|
var tokenStub = {id: 'stub id'};
|
||||||
app.use(function(req, res, next) {
|
app.use(function(req, res, next) {
|
||||||
req.accessToken = tokenStub;
|
req.accessToken = tokenStub;
|
||||||
|
@ -334,7 +345,7 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loading multiple instances of token middleware', function() {
|
describe('loading multiple instances of token middleware', function() {
|
||||||
it('should skip when req.token is already present and no further options are set',
|
it('skips when req.token is already present and no further options are set',
|
||||||
function(done) {
|
function(done) {
|
||||||
var tokenStub = {id: 'stub id'};
|
var tokenStub = {id: 'stub id'};
|
||||||
app.use(function(req, res, next) {
|
app.use(function(req, res, next) {
|
||||||
|
@ -359,7 +370,7 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not overwrite valid existing token (has "id" property) ' +
|
it('does not overwrite valid existing token (has "id" property) ' +
|
||||||
' when overwriteExistingToken is falsy',
|
' when overwriteExistingToken is falsy',
|
||||||
function(done) {
|
function(done) {
|
||||||
var tokenStub = {id: 'stub id'};
|
var tokenStub = {id: 'stub id'};
|
||||||
|
@ -388,7 +399,7 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should overwrite invalid existing token (is !== undefined and has no "id" property) ' +
|
it('overwrites invalid existing token (is !== undefined and has no "id" property) ' +
|
||||||
' when enableDoublecheck is true',
|
' when enableDoublecheck is true',
|
||||||
function(done) {
|
function(done) {
|
||||||
var token = this.token;
|
var token = this.token;
|
||||||
|
@ -421,7 +432,7 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should overwrite existing token when enableDoublecheck ' +
|
it('overwrites existing token when enableDoublecheck ' +
|
||||||
'and overwriteExistingToken options are truthy',
|
'and overwriteExistingToken options are truthy',
|
||||||
function(done) {
|
function(done) {
|
||||||
var token = this.token;
|
var token = this.token;
|
||||||
|
@ -462,12 +473,20 @@ describe('loopback.token(options)', function() {
|
||||||
describe('AccessToken', function() {
|
describe('AccessToken', function() {
|
||||||
beforeEach(createTestingToken);
|
beforeEach(createTestingToken);
|
||||||
|
|
||||||
it('should auto-generate id', function() {
|
it('has getIdForRequest method', function() {
|
||||||
|
expect(typeof Token.getIdForRequest).to.eql('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has resolve method', function() {
|
||||||
|
expect(typeof Token.resolve).to.eql('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates id automatically', function() {
|
||||||
assert(this.token.id);
|
assert(this.token.id);
|
||||||
assert.equal(this.token.id.length, 64);
|
assert.equal(this.token.id.length, 64);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should auto-generate created date', function() {
|
it('generates created date automatically', function() {
|
||||||
assert(this.token.created);
|
assert(this.token.created);
|
||||||
assert(Object.prototype.toString.call(this.token.created), '[object Date]');
|
assert(Object.prototype.toString.call(this.token.created), '[object Date]');
|
||||||
});
|
});
|
||||||
|
@ -525,6 +544,54 @@ describe('AccessToken', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('allows getIdForRequest() to be overridden', function(done) {
|
||||||
|
var expectedTokenId = this.token.id;
|
||||||
|
var current = Token.getIdForRequest;
|
||||||
|
var called = false;
|
||||||
|
Token.getIdForRequest = function(req, options) {
|
||||||
|
called = true;
|
||||||
|
return expectedTokenId;
|
||||||
|
};
|
||||||
|
var req = mockRequest({
|
||||||
|
headers: {'authorization': 'dummy'},
|
||||||
|
});
|
||||||
|
|
||||||
|
Token.findForRequest(req, function(err, token) {
|
||||||
|
Token.getIdForRequest = current;
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
expect(token.id).to.eql(expectedTokenId);
|
||||||
|
expect(called).to.be.true();
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows resolve() to be overridden', function(done) {
|
||||||
|
var expectedTokenId = this.token.id;
|
||||||
|
var current = Token.resolve;
|
||||||
|
var called = false;
|
||||||
|
Token.resolve = function(id, cb) {
|
||||||
|
called = true;
|
||||||
|
process.nextTick(function() {
|
||||||
|
cb(null, {id: expectedTokenId});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var req = mockRequest({
|
||||||
|
headers: {'authorization': expectedTokenId},
|
||||||
|
});
|
||||||
|
|
||||||
|
Token.findForRequest(req, function(err, token) {
|
||||||
|
Token.validate = current;
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
expect(token.id).to.eql(expectedTokenId);
|
||||||
|
expect(called).to.be.true();
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function mockRequest(opts) {
|
function mockRequest(opts) {
|
||||||
return extend(
|
return extend(
|
||||||
{
|
{
|
||||||
|
@ -618,7 +685,7 @@ describe('app.enableAuth()', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prevent remote call if the accessToken is missing and required', function(done) {
|
it('prevents remote call if the accessToken is missing and required', function(done) {
|
||||||
createTestAppAndRequest(null, done)
|
createTestAppAndRequest(null, done)
|
||||||
.del('/tests/123')
|
.del('/tests/123')
|
||||||
.expect(401)
|
.expect(401)
|
||||||
|
|
Loading…
Reference in New Issue