loopback/test/access-token.test.js

563 lines
15 KiB
JavaScript
Raw Normal View History

2016-05-03 22:50:21 +00:00
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
// Node module: loopback
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var cookieParser = require('cookie-parser');
2013-11-13 19:49:08 +00:00
var loopback = require('../');
var extend = require('util')._extend;
2013-11-13 19:49:08 +00:00
var Token = loopback.AccessToken.extend('MyToken');
var ds = loopback.createDataSource({ connector: loopback.Memory });
Token.attachTo(ds);
2013-11-15 04:19:46 +00:00
var ACL = loopback.ACL;
2013-11-13 19:49:08 +00:00
2013-11-14 23:27:36 +00:00
describe('loopback.token(options)', function() {
2013-11-13 19:49:08 +00:00
beforeEach(createTestingToken);
2013-11-14 23:27:36 +00:00
2014-11-21 02:35:36 +00:00
it('should populate req.token from the query string', function(done) {
2013-11-14 23:27:36 +00:00
createTestAppAndRequest(this.token, done)
2013-11-13 19:49:08 +00:00
.get('/?access_token=' + this.token.id)
.expect(200)
.end(done);
});
2013-11-14 23:27:36 +00:00
2014-11-21 02:35:36 +00:00
it('should populate req.token from an authorization header', function(done) {
createTestAppAndRequest(this.token, done)
.get('/')
.set('authorization', this.token.id)
.expect(200)
.end(done);
});
2014-11-21 02:35:36 +00:00
it('should populate req.token from an X-Access-Token header', function(done) {
createTestAppAndRequest(this.token, done)
.get('/')
.set('X-Access-Token', this.token.id)
.expect(200)
.end(done);
});
it('should not search default keys when searchDefaultTokenKeys is false',
function(done) {
var tokenId = this.token.id;
var app = createTestApp(
this.token,
{ token: { searchDefaultTokenKeys: false }},
done);
var agent = request.agent(app);
// Set the token cookie
agent.get('/token').expect(200).end(function(err, res) {
if (err) return done(err);
// Make a request that sets the token in all places searched by default
agent.get('/check-access?access_token=' + tokenId)
.set('X-Access-Token', tokenId)
.set('authorization', tokenId)
// Expect 401 because there is no (non-default) place configured where
// the middleware should load the token from
.expect(401)
.end(done);
});
});
2014-11-21 02:35:36 +00:00
it('should populate req.token from an authorization header with bearer token', function(done) {
var token = this.token.id;
2014-11-21 02:35:36 +00:00
token = 'Bearer ' + new Buffer(token).toString('base64');
2013-11-14 23:27:36 +00:00
createTestAppAndRequest(this.token, done)
.get('/')
2015-01-14 21:31:30 +00:00
.set('authorization', token)
2013-11-14 23:27:36 +00:00
.expect(200)
.end(done);
});
describe('populating req.token from HTTP Basic Auth formatted authorization header', function() {
it('parses "standalone-token"', function(done) {
var token = this.token.id;
token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done)
.get('/')
.set('authorization', this.token.id)
.expect(200)
.end(done);
});
it('parses "token-and-empty-password:"', function(done) {
var token = this.token.id + ':';
token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done)
.get('/')
.set('authorization', this.token.id)
.expect(200)
.end(done);
});
it('parses "ignored-user:token-is-password"', function(done) {
var token = 'username:' + this.token.id;
token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done)
.get('/')
.set('authorization', this.token.id)
.expect(200)
.end(done);
});
it('parses "token-is-username:ignored-password"', function(done) {
var token = this.token.id + ':password';
token = 'Basic ' + new Buffer(token).toString('base64');
createTestAppAndRequest(this.token, done)
.get('/')
.set('authorization', this.token.id)
.expect(200)
.end(done);
});
});
2014-11-21 02:35:36 +00:00
it('should populate req.token from a secure cookie', function(done) {
2013-11-14 23:27:36 +00:00
var app = createTestApp(this.token, done);
request(app)
.get('/token')
.end(function(err, res) {
request(app)
.get('/')
.set('Cookie', res.header['set-cookie'])
.end(done);
});
});
2014-11-21 02:35:36 +00:00
it('should populate req.token from a header or a secure cookie', function(done) {
var app = createTestApp(this.token, done);
var id = this.token.id;
request(app)
.get('/token')
.end(function(err, res) {
request(app)
.get('/')
.set('authorization', id)
.set('Cookie', res.header['set-cookie'])
.end(done);
});
});
it('should rewrite url for the current user literal at the end without query',
function(done) {
var app = createTestApp(this.token, done);
var id = this.token.id;
var userId = this.token.userId;
request(app)
.get('/users/me')
.set('authorization', id)
.end(function(err, res) {
assert(!err);
assert.deepEqual(res.body, { userId: userId });
done();
});
});
it('should rewrite url for the current user literal at the end with query',
function(done) {
var app = createTestApp(this.token, done);
var id = this.token.id;
var userId = this.token.userId;
request(app)
.get('/users/me?state=1')
.set('authorization', id)
.end(function(err, res) {
assert(!err);
assert.deepEqual(res.body, { userId: userId, state: 1 });
done();
});
});
it('should rewrite url for the current user literal in the middle',
function(done) {
var app = createTestApp(this.token, done);
var id = this.token.id;
var userId = this.token.userId;
request(app)
.get('/users/me/1')
.set('authorization', id)
.end(function(err, res) {
assert(!err);
assert.deepEqual(res.body, { userId: userId, state: 1 });
done();
});
});
it('should skip when req.token is already present', function(done) {
var tokenStub = { id: 'stub id' };
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({ model: Token }));
app.get('/', function(req, res, next) {
res.send(req.accessToken);
});
request(app).get('/')
.set('Authorization', this.token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql(tokenStub);
done();
});
});
describe('loading multiple instances of token middleware', function() {
it('should skip when req.token is already present and no further options are set',
function(done) {
var tokenStub = { id: 'stub id' };
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({ model: Token }));
app.get('/', function(req, res, next) {
res.send(req.accessToken);
});
request(app).get('/')
.set('Authorization', this.token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql(tokenStub);
done();
});
});
it('should not overwrite valid existing token (has "id" property) ' +
' when overwriteExistingToken is falsy',
function(done) {
var tokenStub = { id: 'stub id' };
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({
model: Token,
enableDoublecheck: true,
}));
app.get('/', function(req, res, next) {
res.send(req.accessToken);
});
request(app).get('/')
.set('Authorization', this.token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql(tokenStub);
done();
});
});
it('should overwrite existing token when enableDoublecheck ' +
'and overwriteExistingToken options are truthy',
function(done) {
var token = this.token;
var tokenStub = { id: 'stub id' };
app.use(function(req, res, next) {
req.accessToken = tokenStub;
next();
});
app.use(loopback.token({
model: Token,
enableDoublecheck: true,
overwriteExistingToken: true,
}));
app.get('/', function(req, res, next) {
res.send(req.accessToken);
});
request(app).get('/')
.set('Authorization', token.id)
.expect(200)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql({
id: token.id,
ttl: token.ttl,
userId: token.userId,
created: token.created.toJSON(),
});
done();
});
});
});
2013-11-13 19:49:08 +00:00
});
2014-11-21 02:35:36 +00:00
describe('AccessToken', function() {
beforeEach(createTestingToken);
2014-11-21 02:35:36 +00:00
it('should auto-generate id', function() {
assert(this.token.id);
assert.equal(this.token.id.length, 64);
});
2014-11-21 02:35:36 +00:00
it('should auto-generate created date', function() {
assert(this.token.created);
assert(Object.prototype.toString.call(this.token.created), '[object Date]');
});
2014-11-21 02:35:36 +00:00
it('should be validateable', function(done) {
this.token.validate(function(err, isValid) {
assert(isValid);
done();
});
});
describe('.findForRequest()', function() {
beforeEach(createTestingToken);
it('supports two-arg variant with no options', function(done) {
var expectedTokenId = this.token.id;
var req = mockRequest({
headers: { 'authorization': expectedTokenId },
});
Token.findForRequest(req, function(err, token) {
if (err) return done(err);
expect(token.id).to.eql(expectedTokenId);
done();
});
});
function mockRequest(opts) {
return extend(
{
method: 'GET',
url: '/a-test-path',
headers: {},
_params: {},
// express helpers
param: function(name) { return this._params[name]; },
header: function(name) { return this.headers[name]; },
},
opts);
}
});
});
2013-11-15 04:19:46 +00:00
describe('app.enableAuth()', function() {
beforeEach(createTestingToken);
2014-11-21 02:35:36 +00:00
it('prevents remote call with 401 status on denied ACL', function(done) {
2013-11-15 04:19:46 +00:00
createTestAppAndRequest(this.token, done)
.del('/tests/123')
.expect(401)
.set('authorization', this.token.id)
.end(function(err, res) {
if (err) {
return done(err);
}
var errorResponse = res.body.error;
assert(errorResponse);
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
done();
});
2013-11-15 04:19:46 +00:00
});
2014-11-21 02:35:36 +00:00
it('prevent remote call with app setting status on denied ACL', function(done) {
createTestAppAndRequest(this.token, { app: { aclErrorStatus: 403 }}, done)
.del('/tests/123')
.expect(403)
.set('authorization', this.token.id)
.end(function(err, res) {
if (err) {
return done(err);
}
var errorResponse = res.body.error;
assert(errorResponse);
assert.equal(errorResponse.code, 'ACCESS_DENIED');
done();
});
});
2014-11-21 02:35:36 +00:00
it('prevent remote call with app setting status on denied ACL', function(done) {
createTestAppAndRequest(this.token, { model: { aclErrorStatus: 404 }}, done)
.del('/tests/123')
.expect(404)
.set('authorization', this.token.id)
.end(function(err, res) {
if (err) {
return done(err);
}
var errorResponse = res.body.error;
assert(errorResponse);
assert.equal(errorResponse.code, 'MODEL_NOT_FOUND');
done();
});
});
2014-11-21 02:35:36 +00:00
it('prevent remote call if the accessToken is missing and required', function(done) {
createTestAppAndRequest(null, done)
.del('/tests/123')
.expect(401)
.set('authorization', null)
.end(function(err, res) {
if (err) {
return done(err);
}
var errorResponse = res.body.error;
assert(errorResponse);
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
done();
});
});
it('stores token in the context', function(done) {
var TestModel = loopback.createModel('TestModel', { base: 'Model' });
TestModel.getToken = function(cb) {
cb(null, loopback.getCurrentContext().get('accessToken') || null);
};
TestModel.remoteMethod('getToken', {
returns: { arg: 'token', type: 'object' },
http: { verb: 'GET', path: '/token' },
});
var app = loopback();
app.model(TestModel, { dataSource: null });
app.enableAuth();
app.use(loopback.context());
app.use(loopback.token({ model: Token }));
app.use(loopback.rest());
var token = this.token;
request(app)
.get('/TestModels/token?_format=json')
.set('authorization', token.id)
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) return done(err);
expect(res.body.token.id).to.eql(token.id);
done();
});
});
2013-11-15 04:19:46 +00:00
});
2013-11-13 19:49:08 +00:00
function createTestingToken(done) {
var test = this;
Token.create({ userId: '123' }, function(err, token) {
2014-11-21 02:35:36 +00:00
if (err) return done(err);
2013-11-13 19:49:08 +00:00
test.token = token;
2013-11-13 19:49:08 +00:00
done();
});
2013-11-14 21:01:47 +00:00
}
2013-11-14 23:27:36 +00:00
function createTestAppAndRequest(testToken, settings, done) {
var app = createTestApp(testToken, settings, done);
2013-11-14 23:27:36 +00:00
return request(app);
}
function createTestApp(testToken, settings, done) {
2014-11-21 02:35:36 +00:00
done = arguments[arguments.length - 1];
if (settings == done) settings = {};
settings = settings || {};
var appSettings = settings.app || {};
var modelSettings = settings.model || {};
var tokenSettings = extend({
model: Token,
currentUserLiteral: 'me',
}, settings.token);
2013-11-14 23:27:36 +00:00
var app = loopback();
app.use(cookieParser('secret'));
app.use(loopback.token(tokenSettings));
app.set('remoting', { errorHandler: { debug: true, log: false }});
2013-11-14 23:27:36 +00:00
app.get('/token', function(req, res) {
res.cookie('authorization', testToken.id, { signed: true });
res.cookie('access_token', testToken.id, { signed: true });
2013-11-14 23:27:36 +00:00
res.end();
});
2014-11-21 02:35:36 +00:00
app.get('/', function(req, res) {
2013-11-14 23:27:36 +00:00
try {
assert(req.accessToken, 'req should have accessToken');
assert(req.accessToken.id === testToken.id);
2014-11-21 02:35:36 +00:00
} catch (e) {
2013-11-14 23:27:36 +00:00
return done(e);
}
res.send('ok');
});
app.get('/check-access', function(req, res) {
res.status(req.accessToken ? 200 : 401).end();
});
app.use('/users/:uid', function(req, res) {
var result = { userId: req.params.uid };
if (req.query.state) {
result.state = req.query.state;
} else if (req.url !== '/') {
result.state = req.url.substring(1);
}
res.status(200).send(result);
});
2013-11-15 04:19:46 +00:00
app.use(loopback.rest());
app.enableAuth();
2014-11-21 02:35:36 +00:00
Object.keys(appSettings).forEach(function(key) {
app.set(key, appSettings[key]);
});
var modelOptions = {
2013-11-15 04:19:46 +00:00
acls: [
{
principalType: 'ROLE',
principalId: '$everyone',
2013-11-15 04:19:46 +00:00
accessType: ACL.ALL,
permission: ACL.DENY,
property: 'deleteById',
},
],
};
2014-11-21 02:35:36 +00:00
Object.keys(modelSettings).forEach(function(key) {
modelOptions[key] = modelSettings[key];
2013-11-15 04:19:46 +00:00
});
var TestModel = loopback.PersistedModel.extend('test', {}, modelOptions);
2013-11-15 04:19:46 +00:00
TestModel.attachTo(loopback.memory());
app.model(TestModel);
2013-11-14 23:27:36 +00:00
return app;
}