var loopback = require('../'); var extend = require('util')._extend; var Token = loopback.AccessToken.extend('MyToken'); var ACL = loopback.ACL; describe('loopback.token(options)', function() { beforeEach(createTestingToken); it('should populate req.token from the query string', function(done) { createTestAppAndRequest(this.token, done) .get('/?access_token=' + this.token.id) .expect(200) .end(done); }); 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); }); 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 populate req.token from an authorization header with bearer token', function(done) { var token = this.token.id; token = 'Bearer ' + new Buffer(token).toString('base64'); createTestAppAndRequest(this.token, done) .get('/') .set('authorization', token) .expect(200) .end(done); }); describe('populating req.toen 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); }); }); it('should populate req.token from a secure cookie', function(done) { 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); }); }); 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 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('AccessToken', function() { beforeEach(createTestingToken); it('should auto-generate id', function() { assert(this.token.id); assert.equal(this.token.id.length, 64); }); it('should auto-generate created date', function() { assert(this.token.created); assert(Object.prototype.toString.call(this.token.created), '[object Date]'); }); 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); } }); }); describe('app.enableAuth()', function() { beforeEach(createTestingToken); it('prevents remote call with 401 status on denied ACL', function(done) { 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(); }); }); 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(); }); }); 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(); }); }); 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(); }); }); }); function createTestingToken(done) { var test = this; Token.create({}, function(err, token) { if (err) return done(err); test.token = token; done(); }); } function createTestAppAndRequest(testToken, settings, done) { var app = createTestApp(testToken, settings, done); return request(app); } function createTestApp(testToken, settings, done) { done = arguments[arguments.length - 1]; if (settings == done) settings = {}; settings = settings || {}; var appSettings = settings.app || {}; var modelSettings = settings.model || {}; var app = loopback(); app.use(loopback.cookieParser('secret')); app.use(loopback.token({model: Token})); app.get('/token', function(req, res) { res.cookie('authorization', testToken.id, {signed: true}); res.end(); }); app.get('/', function(req, res) { try { assert(req.accessToken, 'req should have accessToken'); assert(req.accessToken.id === testToken.id); } catch (e) { return done(e); } res.send('ok'); }); app.use(loopback.rest()); app.enableAuth(); Object.keys(appSettings).forEach(function(key) { app.set(key, appSettings[key]); }); var modelOptions = { acls: [ { principalType: 'ROLE', principalId: '$everyone', accessType: ACL.ALL, permission: ACL.DENY, property: 'deleteById' } ] }; Object.keys(modelSettings).forEach(function(key) { modelOptions[key] = modelSettings[key]; }); var TestModel = loopback.PersistedModel.extend('test', {}, modelOptions); TestModel.attachTo(loopback.memory()); app.model(TestModel); return app; }