2018-01-03 04:05:53 +00:00
|
|
|
// Copyright IBM Corp. 2013,2018. All Rights Reserved.
|
2016-05-03 22:50:21 +00:00
|
|
|
// Node module: loopback
|
|
|
|
// This file is licensed under the MIT License.
|
|
|
|
// License text available at https://opensource.org/licenses/MIT
|
|
|
|
|
2016-11-15 21:46:23 +00:00
|
|
|
'use strict';
|
|
|
|
var assert = require('assert');
|
2016-11-22 14:30:04 +00:00
|
|
|
var expect = require('./helpers/expect');
|
2016-05-31 14:50:04 +00:00
|
|
|
var cookieParser = require('cookie-parser');
|
2016-07-28 14:36:23 +00:00
|
|
|
var LoopBackContext = require('loopback-context');
|
|
|
|
var contextMiddleware = require('loopback-context').perRequest;
|
2013-11-13 19:49:08 +00:00
|
|
|
var loopback = require('../');
|
2014-11-14 09:37:22 +00:00
|
|
|
var extend = require('util')._extend;
|
2016-08-16 11:51:42 +00:00
|
|
|
var session = require('express-session');
|
2016-11-15 21:46:23 +00:00
|
|
|
var request = require('supertest');
|
2016-08-16 11:51:42 +00:00
|
|
|
|
2017-03-03 11:35:41 +00:00
|
|
|
var Token, ACL, User, TestModel;
|
2013-11-13 19:49:08 +00:00
|
|
|
|
2013-11-14 23:27:36 +00:00
|
|
|
describe('loopback.token(options)', function() {
|
2016-10-21 15:17:02 +00:00
|
|
|
var app;
|
|
|
|
beforeEach(function(done) {
|
2017-02-24 13:17:12 +00:00
|
|
|
app = loopback({localRegistry: true, loadBuiltinModels: true});
|
|
|
|
app.dataSource('db', {connector: 'memory'});
|
|
|
|
|
2017-03-03 11:35:41 +00:00
|
|
|
ACL = app.registry.getModel('ACL');
|
|
|
|
app.model(ACL, {dataSource: 'db'});
|
|
|
|
|
|
|
|
User = app.registry.getModel('User');
|
|
|
|
app.model(User, {dataSource: 'db'});
|
|
|
|
|
2017-02-24 13:17:12 +00:00
|
|
|
Token = app.registry.createModel({
|
|
|
|
name: 'MyToken',
|
|
|
|
base: 'AccessToken',
|
|
|
|
});
|
|
|
|
app.model(Token, {dataSource: 'db'});
|
|
|
|
|
2017-03-03 11:35:41 +00:00
|
|
|
TestModel = app.registry.createModel({
|
|
|
|
name: 'TestModel',
|
|
|
|
base: 'Model',
|
|
|
|
});
|
|
|
|
TestModel.getToken = function(options, cb) {
|
|
|
|
cb(null, options && options.accessToken || null);
|
|
|
|
};
|
|
|
|
TestModel.remoteMethod('getToken', {
|
|
|
|
accepts: {arg: 'options', type: 'object', http: 'optionsFromRequest'},
|
|
|
|
returns: {arg: 'token', type: 'object'},
|
|
|
|
http: {verb: 'GET', path: '/token'},
|
|
|
|
});
|
|
|
|
app.model(TestModel, {dataSource: 'db'});
|
2017-02-24 13:17:12 +00:00
|
|
|
|
2016-10-21 15:17:02 +00:00
|
|
|
createTestingToken.call(this, done);
|
|
|
|
});
|
2013-11-14 23:27:36 +00:00
|
|
|
|
2017-03-03 11:35:41 +00:00
|
|
|
it('defaults to built-in AccessToken model', function() {
|
|
|
|
var BuiltInToken = app.registry.getModel('AccessToken');
|
|
|
|
app.model(BuiltInToken, {dataSource: 'db'});
|
|
|
|
|
|
|
|
app.enableAuth({dataSource: 'db'});
|
|
|
|
app.use(loopback.token());
|
|
|
|
app.use(loopback.rest());
|
|
|
|
|
|
|
|
return BuiltInToken.create({userId: 123}).then(function(token) {
|
|
|
|
return request(app)
|
|
|
|
.get('/TestModels/token?_format=json')
|
|
|
|
.set('authorization', token.id)
|
|
|
|
.expect(200)
|
|
|
|
.expect('Content-Type', /json/)
|
|
|
|
.then(res => {
|
|
|
|
expect(res.body.token.id).to.eql(token.id);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('uses correct custom AccessToken model from model class param', function() {
|
|
|
|
User.hasMany(Token, {
|
|
|
|
as: 'accessTokens',
|
|
|
|
options: {disableInclude: true},
|
|
|
|
});
|
|
|
|
|
|
|
|
app.enableAuth();
|
|
|
|
app.use(loopback.token({model: Token}));
|
|
|
|
app.use(loopback.rest());
|
|
|
|
|
|
|
|
return Token.create({userId: 123}).then(function(token) {
|
|
|
|
return request(app)
|
|
|
|
.get('/TestModels/token?_format=json')
|
|
|
|
.set('authorization', token.id)
|
|
|
|
.expect(200)
|
|
|
|
.expect('Content-Type', /json/)
|
|
|
|
.then(res => {
|
|
|
|
expect(res.body.token.id).to.eql(token.id);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('uses correct custom AccessToken model from string param', function() {
|
|
|
|
User.hasMany(Token, {
|
|
|
|
as: 'accessTokens',
|
|
|
|
options: {disableInclude: true},
|
|
|
|
});
|
|
|
|
|
|
|
|
app.enableAuth();
|
|
|
|
app.use(loopback.token({model: Token.modelName}));
|
|
|
|
app.use(loopback.rest());
|
|
|
|
|
|
|
|
return Token.create({userId: 123}).then(function(token) {
|
|
|
|
return request(app)
|
|
|
|
.get('/TestModels/token?_format=json')
|
|
|
|
.set('authorization', token.id)
|
|
|
|
.expect(200)
|
|
|
|
.expect('Content-Type', /json/)
|
|
|
|
.then(res => {
|
|
|
|
expect(res.body.token.id).to.eql(token.id);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('populates 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
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('populates req.token from an authorization header', function(done) {
|
2014-07-02 16:02:13 +00:00
|
|
|
createTestAppAndRequest(this.token, done)
|
|
|
|
.get('/')
|
|
|
|
.set('authorization', this.token.id)
|
|
|
|
.expect(200)
|
|
|
|
.end(done);
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('populates req.token from an X-Access-Token header', function(done) {
|
2014-07-02 16:02:13 +00:00
|
|
|
createTestAppAndRequest(this.token, done)
|
|
|
|
.get('/')
|
|
|
|
.set('X-Access-Token', this.token.id)
|
|
|
|
.expect(200)
|
|
|
|
.end(done);
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('does not search default keys when searchDefaultTokenKeys is false',
|
2017-12-12 08:33:15 +00:00
|
|
|
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)
|
2015-05-29 09:29:21 +00:00
|
|
|
// Expect 401 because there is no (non-default) place configured where
|
|
|
|
// the middleware should load the token from
|
2017-12-12 08:33:15 +00:00
|
|
|
.expect(401)
|
|
|
|
.end(done);
|
|
|
|
});
|
2015-05-29 09:29:21 +00:00
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('populates req.token from an authorization header with bearer token with base64',
|
2017-12-12 08:33:15 +00:00
|
|
|
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);
|
|
|
|
});
|
2013-11-14 23:27:36 +00:00
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
|
2016-02-29 15:49:41 +00:00
|
|
|
describe('populating req.token from HTTP Basic Auth formatted authorization header', function() {
|
2015-01-14 22:10:20 +00:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('populates 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-02-04 15:17:32 +00:00
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('populates req.token from a header or a secure cookie', function(done) {
|
2014-07-02 16:02:13 +00:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('rewrites url for the current user literal at the end without query',
|
2015-03-12 15:28:15 +00:00
|
|
|
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);
|
2016-11-15 21:46:23 +00:00
|
|
|
assert.deepEqual(res.body, {userId: userId});
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2015-03-12 15:28:15 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('rewrites url for the current user literal at the end with query',
|
2015-03-12 15:28:15 +00:00
|
|
|
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);
|
2016-11-15 21:46:23 +00:00
|
|
|
assert.deepEqual(res.body, {userId: userId, state: 1});
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2015-03-12 15:28:15 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('rewrites url for the current user literal in the middle',
|
2015-03-12 15:28:15 +00:00
|
|
|
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);
|
2016-11-15 21:46:23 +00:00
|
|
|
assert.deepEqual(res.body, {userId: userId, state: 1});
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2015-03-12 15:28:15 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('generates a 401 on a current user literal route without an authToken',
|
2017-03-18 00:08:17 +00:00
|
|
|
function(done) {
|
|
|
|
var app = createTestApp(null, done);
|
|
|
|
request(app)
|
|
|
|
.get('/users/me')
|
|
|
|
.set('authorization', null)
|
|
|
|
.expect(401)
|
|
|
|
.end(done);
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('generates a 401 on a current user literal route with invalid authToken',
|
2017-03-18 00:08:17 +00:00
|
|
|
function(done) {
|
|
|
|
var app = createTestApp(this.token, done);
|
|
|
|
request(app)
|
|
|
|
.get('/users/me')
|
|
|
|
.set('Authorization', 'invald-token-id')
|
|
|
|
.expect(401)
|
|
|
|
.end(done);
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('skips when req.token is already present', function(done) {
|
2016-11-15 21:46:23 +00:00
|
|
|
var tokenStub = {id: 'stub id'};
|
2014-02-04 15:17:32 +00:00
|
|
|
app.use(function(req, res, next) {
|
|
|
|
req.accessToken = tokenStub;
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-02-04 15:17:32 +00:00
|
|
|
next();
|
|
|
|
});
|
2016-11-15 21:46:23 +00:00
|
|
|
app.use(loopback.token({model: Token}));
|
2014-02-04 15:17:32 +00:00
|
|
|
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);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-02-04 15:17:32 +00:00
|
|
|
expect(res.body).to.eql(tokenStub);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-02-04 15:17:32 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2016-02-29 15:49:41 +00:00
|
|
|
|
|
|
|
describe('loading multiple instances of token middleware', function() {
|
2017-05-02 17:55:51 +00:00
|
|
|
it('skips when req.token is already present and no further options are set',
|
2017-12-12 08:33:15 +00:00
|
|
|
function(done) {
|
|
|
|
var tokenStub = {id: 'stub id'};
|
|
|
|
app.use(function(req, res, next) {
|
|
|
|
req.accessToken = tokenStub;
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2017-12-12 08:33:15 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
app.use(loopback.token({model: Token}));
|
|
|
|
app.get('/', function(req, res, next) {
|
|
|
|
res.send(req.accessToken);
|
|
|
|
});
|
2016-02-29 15:49:41 +00:00
|
|
|
|
2017-12-12 08:33:15 +00:00
|
|
|
request(app).get('/')
|
|
|
|
.set('Authorization', this.token.id)
|
|
|
|
.expect(200)
|
|
|
|
.end(function(err, res) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2017-12-12 08:33:15 +00:00
|
|
|
expect(res.body).to.eql(tokenStub);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2017-12-12 08:33:15 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2016-02-29 15:49:41 +00:00
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('does not overwrite valid existing token (has "id" property) ' +
|
2016-02-29 15:49:41 +00:00
|
|
|
' when overwriteExistingToken is falsy',
|
|
|
|
function(done) {
|
2016-11-15 21:46:23 +00:00
|
|
|
var tokenStub = {id: 'stub id'};
|
2016-02-29 15:49:41 +00:00
|
|
|
app.use(function(req, res, next) {
|
|
|
|
req.accessToken = tokenStub;
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2016-02-29 15:49:41 +00:00
|
|
|
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);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2016-02-29 15:49:41 +00:00
|
|
|
expect(res.body).to.eql(tokenStub);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2016-02-29 15:49:41 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('overwrites invalid existing token (is !== undefined and has no "id" property) ' +
|
2017-03-03 11:35:41 +00:00
|
|
|
' when enableDoublecheck is true',
|
2016-04-13 14:34:41 +00:00
|
|
|
function(done) {
|
|
|
|
var token = this.token;
|
|
|
|
app.use(function(req, res, next) {
|
|
|
|
req.accessToken = null;
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
|
|
|
app.use(loopback.token({
|
|
|
|
model: Token,
|
|
|
|
enableDoublecheck: 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();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('overwrites existing token when enableDoublecheck ' +
|
2016-02-29 15:49:41 +00:00
|
|
|
'and overwriteExistingToken options are truthy',
|
|
|
|
function(done) {
|
|
|
|
var token = this.token;
|
2016-11-15 21:46:23 +00:00
|
|
|
var tokenStub = {id: 'stub id'};
|
2016-02-29 15:49:41 +00:00
|
|
|
app.use(function(req, res, next) {
|
|
|
|
req.accessToken = tokenStub;
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2016-02-29 15:49:41 +00:00
|
|
|
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);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2016-02-29 15:49:41 +00:00
|
|
|
expect(res.body).to.eql({
|
|
|
|
id: token.id,
|
|
|
|
ttl: token.ttl,
|
|
|
|
userId: token.userId,
|
|
|
|
created: token.created.toJSON(),
|
|
|
|
});
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2016-02-29 15:49:41 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2013-11-13 19:49:08 +00:00
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
describe('AccessToken', function() {
|
2013-11-15 02:34:51 +00:00
|
|
|
beforeEach(createTestingToken);
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
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() {
|
2013-11-15 02:34:51 +00:00
|
|
|
assert(this.token.id);
|
|
|
|
assert.equal(this.token.id.length, 64);
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('generates created date automatically', function() {
|
2013-11-15 02:34:51 +00:00
|
|
|
assert(this.token.created);
|
|
|
|
assert(Object.prototype.toString.call(this.token.created), '[object Date]');
|
|
|
|
});
|
|
|
|
|
2016-10-10 11:27:22 +00:00
|
|
|
describe('.validate()', function() {
|
|
|
|
it('accepts valid tokens', function(done) {
|
|
|
|
this.token.validate(function(err, isValid) {
|
|
|
|
assert(isValid);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2016-10-10 11:27:22 +00:00
|
|
|
it('rejects eternal TTL by default', function(done) {
|
|
|
|
this.token.ttl = -1;
|
|
|
|
this.token.validate(function(err, isValid) {
|
|
|
|
if (err) return done(err);
|
|
|
|
expect(isValid, 'isValid').to.equal(false);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('allows eternal tokens when enabled by User.allowEternalTokens',
|
2017-12-12 08:33:15 +00:00
|
|
|
function(done) {
|
|
|
|
var Token = givenLocalTokenModel();
|
2016-10-10 11:27:22 +00:00
|
|
|
|
2017-12-12 08:33:15 +00:00
|
|
|
// Overwrite User settings - enable eternal tokens
|
|
|
|
Token.app.models.User.settings.allowEternalTokens = true;
|
2016-10-10 11:27:22 +00:00
|
|
|
|
2017-12-12 08:33:15 +00:00
|
|
|
Token.create({userId: '123', ttl: -1}, function(err, token) {
|
2016-10-10 11:27:22 +00:00
|
|
|
if (err) return done(err);
|
2017-12-12 08:33:15 +00:00
|
|
|
token.validate(function(err, isValid) {
|
|
|
|
if (err) return done(err);
|
|
|
|
expect(isValid, 'isValid').to.equal(true);
|
|
|
|
done();
|
|
|
|
});
|
2016-10-10 11:27:22 +00:00
|
|
|
});
|
|
|
|
});
|
2013-11-15 02:34:51 +00:00
|
|
|
});
|
2014-11-14 09:37:22 +00:00
|
|
|
|
|
|
|
describe('.findForRequest()', function() {
|
|
|
|
beforeEach(createTestingToken);
|
|
|
|
|
|
|
|
it('supports two-arg variant with no options', function(done) {
|
|
|
|
var expectedTokenId = this.token.id;
|
|
|
|
var req = mockRequest({
|
2016-11-15 21:46:23 +00:00
|
|
|
headers: {'authorization': expectedTokenId},
|
2014-11-14 09:37:22 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
Token.findForRequest(req, function(err, token) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-11-14 09:37:22 +00:00
|
|
|
expect(token.id).to.eql(expectedTokenId);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-11-14 09:37:22 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
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();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-11-14 09:37:22 +00:00
|
|
|
function mockRequest(opts) {
|
|
|
|
return extend(
|
|
|
|
{
|
|
|
|
method: 'GET',
|
|
|
|
url: '/a-test-path',
|
|
|
|
headers: {},
|
|
|
|
_params: {},
|
|
|
|
|
|
|
|
// express helpers
|
|
|
|
param: function(name) { return this._params[name]; },
|
2016-04-01 09:14:26 +00:00
|
|
|
header: function(name) { return this.headers[name]; },
|
2014-11-14 09:37:22 +00:00
|
|
|
},
|
|
|
|
opts);
|
|
|
|
}
|
|
|
|
});
|
2013-11-15 02:34:51 +00:00
|
|
|
});
|
|
|
|
|
2013-11-15 04:19:46 +00:00
|
|
|
describe('app.enableAuth()', function() {
|
2016-10-21 15:17:02 +00:00
|
|
|
var app;
|
2016-07-26 12:10:13 +00:00
|
|
|
beforeEach(function setupAuthWithModels() {
|
2017-02-24 13:17:12 +00:00
|
|
|
app = loopback({localRegistry: true, loadBuiltinModels: true});
|
|
|
|
app.dataSource('db', {connector: 'memory'});
|
|
|
|
|
|
|
|
Token = app.registry.createModel({
|
|
|
|
name: 'MyToken',
|
|
|
|
base: 'AccessToken',
|
|
|
|
});
|
|
|
|
app.model(Token, {dataSource: 'db'});
|
|
|
|
|
|
|
|
ACL = app.registry.getModel('ACL');
|
|
|
|
|
2017-02-24 13:07:41 +00:00
|
|
|
// Fix User's "hasMany accessTokens" relation to use our new MyToken model
|
|
|
|
const User = app.registry.getModel('User');
|
|
|
|
User.settings.relations.accessTokens.model = 'MyToken';
|
|
|
|
|
2017-02-24 13:17:12 +00:00
|
|
|
app.enableAuth({dataSource: 'db'});
|
2016-07-26 12:10:13 +00:00
|
|
|
});
|
2013-11-15 04:19:46 +00:00
|
|
|
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)
|
2014-12-18 20:26:27 +00:00
|
|
|
.end(function(err, res) {
|
|
|
|
if (err) {
|
|
|
|
return done(err);
|
|
|
|
}
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-18 20:26:27 +00:00
|
|
|
var errorResponse = res.body.error;
|
|
|
|
assert(errorResponse);
|
|
|
|
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-18 20:26:27 +00:00
|
|
|
done();
|
|
|
|
});
|
2013-11-15 04:19:46 +00:00
|
|
|
});
|
2014-06-06 01:53:30 +00:00
|
|
|
|
2017-12-12 08:33:15 +00:00
|
|
|
it('denies remote call with app setting status 403', function(done) {
|
2016-11-15 21:46:23 +00:00
|
|
|
createTestAppAndRequest(this.token, {app: {aclErrorStatus: 403}}, done)
|
2014-06-06 01:53:30 +00:00
|
|
|
.del('/tests/123')
|
|
|
|
.expect(403)
|
|
|
|
.set('authorization', this.token.id)
|
2014-12-18 20:26:27 +00:00
|
|
|
.end(function(err, res) {
|
|
|
|
if (err) {
|
|
|
|
return done(err);
|
|
|
|
}
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-18 20:26:27 +00:00
|
|
|
var errorResponse = res.body.error;
|
|
|
|
assert(errorResponse);
|
|
|
|
assert.equal(errorResponse.code, 'ACCESS_DENIED');
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-18 20:26:27 +00:00
|
|
|
done();
|
|
|
|
});
|
2014-06-06 01:53:30 +00:00
|
|
|
});
|
|
|
|
|
2017-12-12 08:33:15 +00:00
|
|
|
it('denies remote call with app setting status 404', function(done) {
|
2016-11-15 21:46:23 +00:00
|
|
|
createTestAppAndRequest(this.token, {model: {aclErrorStatus: 404}}, done)
|
2014-06-06 01:53:30 +00:00
|
|
|
.del('/tests/123')
|
|
|
|
.expect(404)
|
|
|
|
.set('authorization', this.token.id)
|
2014-12-18 20:26:27 +00:00
|
|
|
.end(function(err, res) {
|
|
|
|
if (err) {
|
|
|
|
return done(err);
|
|
|
|
}
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-18 20:26:27 +00:00
|
|
|
var errorResponse = res.body.error;
|
|
|
|
assert(errorResponse);
|
|
|
|
assert.equal(errorResponse.code, 'MODEL_NOT_FOUND');
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-18 20:26:27 +00:00
|
|
|
done();
|
|
|
|
});
|
2014-06-06 01:53:30 +00:00
|
|
|
});
|
|
|
|
|
2017-05-02 17:55:51 +00:00
|
|
|
it('prevents remote call if the accessToken is missing and required', function(done) {
|
2014-06-06 01:53:30 +00:00
|
|
|
createTestAppAndRequest(null, done)
|
|
|
|
.del('/tests/123')
|
|
|
|
.expect(401)
|
|
|
|
.set('authorization', null)
|
2014-12-18 20:26:27 +00:00
|
|
|
.end(function(err, res) {
|
|
|
|
if (err) {
|
|
|
|
return done(err);
|
|
|
|
}
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-18 20:26:27 +00:00
|
|
|
var errorResponse = res.body.error;
|
|
|
|
assert(errorResponse);
|
|
|
|
assert.equal(errorResponse.code, 'AUTHORIZATION_REQUIRED');
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-18 20:26:27 +00:00
|
|
|
done();
|
|
|
|
});
|
2014-06-06 01:53:30 +00:00
|
|
|
});
|
|
|
|
|
2014-11-10 17:37:35 +00:00
|
|
|
it('stores token in the context', function(done) {
|
2017-02-24 13:17:12 +00:00
|
|
|
var TestModel = app.registry.createModel('TestModel', {base: 'Model'});
|
2014-11-10 17:37:35 +00:00
|
|
|
TestModel.getToken = function(cb) {
|
2016-07-28 14:36:23 +00:00
|
|
|
var ctx = LoopBackContext.getCurrentContext();
|
|
|
|
cb(null, ctx && ctx.get('accessToken') || null);
|
2014-11-10 17:37:35 +00:00
|
|
|
};
|
|
|
|
TestModel.remoteMethod('getToken', {
|
2016-11-15 21:46:23 +00:00
|
|
|
returns: {arg: 'token', type: 'object'},
|
|
|
|
http: {verb: 'GET', path: '/token'},
|
2014-11-10 17:37:35 +00:00
|
|
|
});
|
|
|
|
|
2016-11-15 21:46:23 +00:00
|
|
|
app.model(TestModel, {dataSource: null});
|
2014-11-10 17:37:35 +00:00
|
|
|
|
|
|
|
app.enableAuth();
|
2016-07-28 14:36:23 +00:00
|
|
|
app.use(contextMiddleware());
|
2016-11-15 21:46:23 +00:00
|
|
|
app.use(loopback.token({model: Token}));
|
2014-11-10 17:37:35 +00:00
|
|
|
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);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-11-10 17:37:35 +00:00
|
|
|
expect(res.body.token.id).to.eql(token.id);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-11-10 17:37:35 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2016-08-16 11:51:42 +00:00
|
|
|
|
|
|
|
// See https://github.com/strongloop/loopback-context/issues/6
|
|
|
|
it('checks whether context is active', function(done) {
|
|
|
|
app.enableAuth();
|
|
|
|
app.use(contextMiddleware());
|
|
|
|
app.use(session({
|
|
|
|
secret: 'kitty',
|
|
|
|
saveUninitialized: true,
|
|
|
|
resave: true,
|
|
|
|
}));
|
2016-11-15 21:46:23 +00:00
|
|
|
app.use(loopback.token({model: Token}));
|
2016-08-16 11:51:42 +00:00
|
|
|
app.get('/', function(req, res) { res.send('OK'); });
|
|
|
|
app.use(loopback.rest());
|
|
|
|
|
|
|
|
request(app)
|
|
|
|
.get('/')
|
|
|
|
.set('authorization', this.token.id)
|
|
|
|
.set('cookie', 'connect.sid=s%3AFTyno9_MbGTJuOwdh9bxsYCVxlhlulTZ.' +
|
|
|
|
'PZvp85jzLXZBCBkhCsSfuUjhij%2Fb0B1K2RYZdxSQU0c')
|
|
|
|
.expect(200, 'OK')
|
|
|
|
.end(done);
|
|
|
|
});
|
2013-11-15 04:19:46 +00:00
|
|
|
});
|
|
|
|
|
2013-11-13 19:49:08 +00:00
|
|
|
function createTestingToken(done) {
|
|
|
|
var test = this;
|
2016-11-15 21:46:23 +00:00
|
|
|
Token.create({userId: '123'}, function(err, token) {
|
2014-11-21 02:35:36 +00:00
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2013-11-13 19:49:08 +00:00
|
|
|
test.token = token;
|
2016-05-05 04:09:06 +00:00
|
|
|
|
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
|
|
|
|
2014-06-06 01:53:30 +00:00
|
|
|
function createTestAppAndRequest(testToken, settings, done) {
|
|
|
|
var app = createTestApp(testToken, settings, done);
|
2013-11-14 23:27:36 +00:00
|
|
|
return request(app);
|
|
|
|
}
|
|
|
|
|
2014-06-06 01:53:30 +00:00
|
|
|
function createTestApp(testToken, settings, done) {
|
2017-03-03 11:35:41 +00:00
|
|
|
if (!done && typeof settings === 'function') {
|
|
|
|
done = settings;
|
|
|
|
settings = {};
|
|
|
|
}
|
2014-06-06 01:53:30 +00:00
|
|
|
|
|
|
|
var appSettings = settings.app || {};
|
|
|
|
var modelSettings = settings.model || {};
|
2015-05-29 09:29:21 +00:00
|
|
|
var tokenSettings = extend({
|
|
|
|
model: Token,
|
2016-04-01 09:14:26 +00:00
|
|
|
currentUserLiteral: 'me',
|
2015-05-29 09:29:21 +00:00
|
|
|
}, settings.token);
|
2014-06-06 01:53:30 +00:00
|
|
|
|
2017-02-24 13:17:12 +00:00
|
|
|
var app = loopback({localRegistry: true, loadBuiltinModels: true});
|
|
|
|
app.dataSource('db', {connector: 'memory'});
|
2013-11-14 23:27:36 +00:00
|
|
|
|
2016-05-31 14:50:04 +00:00
|
|
|
app.use(cookieParser('secret'));
|
2015-05-29 09:29:21 +00:00
|
|
|
app.use(loopback.token(tokenSettings));
|
2016-11-15 21:46:23 +00:00
|
|
|
app.set('remoting', {errorHandler: {debug: true, log: false}});
|
2013-11-14 23:27:36 +00:00
|
|
|
app.get('/token', function(req, res) {
|
2016-11-15 21:46:23 +00:00
|
|
|
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');
|
|
|
|
});
|
2015-05-29 09:29:21 +00:00
|
|
|
app.get('/check-access', function(req, res) {
|
|
|
|
res.status(req.accessToken ? 200 : 401).end();
|
|
|
|
});
|
2015-03-12 15:28:15 +00:00
|
|
|
app.use('/users/:uid', function(req, res) {
|
2016-11-15 21:46:23 +00:00
|
|
|
var result = {userId: req.params.uid};
|
2015-03-12 15:28:15 +00:00
|
|
|
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());
|
2017-02-24 13:17:12 +00:00
|
|
|
app.enableAuth({dataSource: 'db'});
|
2013-11-15 04:19:46 +00:00
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
Object.keys(appSettings).forEach(function(key) {
|
2014-06-06 01:53:30 +00:00
|
|
|
app.set(key, appSettings[key]);
|
|
|
|
});
|
|
|
|
|
|
|
|
var modelOptions = {
|
2013-11-15 04:19:46 +00:00
|
|
|
acls: [
|
|
|
|
{
|
2014-11-21 01:46:21 +00:00
|
|
|
principalType: 'ROLE',
|
|
|
|
principalId: '$everyone',
|
2013-11-15 04:19:46 +00:00
|
|
|
accessType: ACL.ALL,
|
|
|
|
permission: ACL.DENY,
|
2016-04-01 09:14:26 +00:00
|
|
|
property: 'deleteById',
|
|
|
|
},
|
|
|
|
],
|
2014-06-06 01:53:30 +00:00
|
|
|
};
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
Object.keys(modelSettings).forEach(function(key) {
|
2014-06-06 01:53:30 +00:00
|
|
|
modelOptions[key] = modelSettings[key];
|
2013-11-15 04:19:46 +00:00
|
|
|
});
|
|
|
|
|
2017-02-24 13:17:12 +00:00
|
|
|
var TestModel = app.registry.createModel('test', {}, modelOptions);
|
|
|
|
app.model(TestModel, {dataSource: 'db'});
|
2013-11-14 23:27:36 +00:00
|
|
|
|
|
|
|
return app;
|
|
|
|
}
|
2016-10-10 11:27:22 +00:00
|
|
|
|
|
|
|
function givenLocalTokenModel() {
|
2016-11-15 21:46:23 +00:00
|
|
|
var app = loopback({localRegistry: true, loadBuiltinModels: true});
|
|
|
|
app.dataSource('db', {connector: 'memory'});
|
2016-10-10 11:27:22 +00:00
|
|
|
|
|
|
|
var User = app.registry.getModel('User');
|
2016-11-15 21:46:23 +00:00
|
|
|
app.model(User, {dataSource: 'db'});
|
2016-10-10 11:27:22 +00:00
|
|
|
|
|
|
|
var Token = app.registry.getModel('AccessToken');
|
2016-11-15 21:46:23 +00:00
|
|
|
app.model(Token, {dataSource: 'db'});
|
2016-10-10 11:27:22 +00:00
|
|
|
|
|
|
|
return Token;
|
|
|
|
}
|