Merge branch 'release/1.10.0' into production
This commit is contained in:
commit
010cc38ec0
25
README.md
25
README.md
|
@ -35,16 +35,19 @@ as illustrated below:
|
||||||
* [loopback-connector-rest](https://github.com/strongloop/loopback-connector-rest)
|
* [loopback-connector-rest](https://github.com/strongloop/loopback-connector-rest)
|
||||||
* [loopback-connector-soap](https://github.com/strongloop/loopback-connector-soap)
|
* [loopback-connector-soap](https://github.com/strongloop/loopback-connector-soap)
|
||||||
|
|
||||||
* Mobile services
|
* Mobile Components
|
||||||
* [loopback-push-notification](https://github.com/strongloop/loopback-push-notification)
|
* [loopback-component-push](https://github.com/strongloop/loopback-component-push)
|
||||||
* [loopback-storage-service](https://github.com/strongloop/loopback-storage-service)
|
* [loopback-component-storage](https://github.com/strongloop/loopback-component-storage)
|
||||||
|
|
||||||
|
* Security Components
|
||||||
|
* [loopback-component-passport](https://github.com/strongloop/loopback-component-passport)
|
||||||
|
|
||||||
* Clients
|
* Clients
|
||||||
* [loopback-ios](https://github.com/strongloop/loopback-ios)
|
* [loopback-sdk-ios](https://github.com/strongloop/loopback-sdk-ios)
|
||||||
* [strong-remoting-ios](https://github.com/strongloop/strong-remoting-ios)
|
* [loopback-sdk-android](https://github.com/strongloop/loopback-sdk-android)
|
||||||
* [loopback-android](https://github.com/strongloop/loopback-android)
|
* [loopback-sdk-angular](https://github.com/strongloop/loopback-sdk-angular)
|
||||||
* [strong-remoting-android](https://github.com/strongloop/strong-remoting-android)
|
* [loopback-sdk-angular-cli](https://github.com/strongloop/loopback-sdk-angular-cli)
|
||||||
* [loopback-angular](https://github.com/strongloop/loopback-angular)
|
* [grunt-loopback-sdk-angular](https://github.com/strongloop/grunt-loopback-sdk-angular)
|
||||||
|
|
||||||
* Tools
|
* Tools
|
||||||
* [loopback-explorer](https://github.com/strongloop/loopback-explorer)
|
* [loopback-explorer](https://github.com/strongloop/loopback-explorer)
|
||||||
|
@ -52,15 +55,15 @@ as illustrated below:
|
||||||
* [strong-cli](https://github.com/strongloop/strong-cli)
|
* [strong-cli](https://github.com/strongloop/strong-cli)
|
||||||
|
|
||||||
* Examples
|
* Examples
|
||||||
|
* [loopback-example-database](https://github.com/strongloop/loopback-example-database)
|
||||||
|
* [loopback-example-datagraph](https://github.com/strongloop/loopback-example-datagraph)
|
||||||
* [loopback-example-full-stack](https://github.com/strongloop/loopback-example-full-stack)
|
* [loopback-example-full-stack](https://github.com/strongloop/loopback-example-full-stack)
|
||||||
* [loopback-example-office-supplies](https://github.com/strongloop/loopback-example-office-supplies)
|
* [loopback-example-office-supplies](https://github.com/strongloop/loopback-example-office-supplies)
|
||||||
* [loopback-example-todo](https://github.com/strongloop/loopback-example-todo)
|
* [loopback-example-todo](https://github.com/strongloop/loopback-example-todo)
|
||||||
* [loopback-example-access-control](https://github.com/strongloop/loopback-example-access-control)
|
* [loopback-example-access-control](https://github.com/strongloop/loopback-example-access-control)
|
||||||
* [loopback-example-proxy](https://github.com/strongloop/loopback-example-proxy)
|
* [loopback-example-proxy](https://github.com/strongloop/loopback-example-proxy)
|
||||||
* [strongloop-community/loopback-example-datagraph](https://github.com/strongloop-community/loopback-example-datagraph)
|
|
||||||
* [strongloop-community/loopback-example-database](https://github.com/strongloop-community/loopback-example-database)
|
|
||||||
* [strongloop-community/loopback-examples-ios](https://github.com/strongloop-community/loopback-examples-ios)
|
* [strongloop-community/loopback-examples-ios](https://github.com/strongloop-community/loopback-examples-ios)
|
||||||
* [strongloop-community/loopback-example-ssl](https://github.com/strongloop-community/loopback-example-ssl)
|
* [loopback-example-ssl](https://github.com/strongloop/loopback-example-ssl)
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 309 KiB |
|
@ -879,6 +879,13 @@ app.listen = function(cb) {
|
||||||
|
|
||||||
server.on('listening', function() {
|
server.on('listening', function() {
|
||||||
self.set('port', this.address().port);
|
self.set('port', this.address().port);
|
||||||
|
if (!self.get('url')) {
|
||||||
|
// A better default host would be `0.0.0.0`,
|
||||||
|
// but such URL is not supported by Windows
|
||||||
|
var host = self.get('host') || '127.0.0.1';
|
||||||
|
var url = 'http://' + host + ':' + self.get('port') + '/';
|
||||||
|
self.set('url', url);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var useAppConfig =
|
var useAppConfig =
|
||||||
|
|
|
@ -18,7 +18,7 @@ module.exports = MailConnector;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function MailConnector(settings) {
|
function MailConnector(settings) {
|
||||||
assert(typeof settings === 'object', 'cannot initiaze MailConnector without a settings object');
|
assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object');
|
||||||
var transports = settings.transports || [];
|
var transports = settings.transports || [];
|
||||||
this.transportsIndex = {};
|
this.transportsIndex = {};
|
||||||
this.transports = [];
|
this.transports = [];
|
||||||
|
|
|
@ -15,9 +15,9 @@ module.exports = token;
|
||||||
* Check for an access token in cookies, headers, and query string parameters.
|
* Check for an access token in cookies, headers, and query string parameters.
|
||||||
* This function always checks for the following:
|
* This function always checks for the following:
|
||||||
*
|
*
|
||||||
* - `access_token`
|
* - `access_token` (params only)
|
||||||
* - `X-Access-Token`
|
* - `X-Access-Token` (headers only)
|
||||||
* - `authorization`
|
* - `authorization` (headers and cookies)
|
||||||
*
|
*
|
||||||
* It checks for these values in cookies, headers, and query string parameters _in addition_ to the items
|
* It checks for these values in cookies, headers, and query string parameters _in addition_ to the items
|
||||||
* specified in the options parameter.
|
* specified in the options parameter.
|
||||||
|
|
|
@ -209,12 +209,20 @@ function tokenIdForRequest(req, options) {
|
||||||
id = req.header(headers[i]);
|
id = req.header(headers[i]);
|
||||||
|
|
||||||
if(typeof id === 'string') {
|
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');
|
||||||
|
}
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(req.signedCookies) {
|
if(req.signedCookies) {
|
||||||
for(i = 0, length = headers.length; i < length; i++) {
|
for(i = 0, length = cookies.length; i < length; i++) {
|
||||||
id = req.signedCookies[cookies[i]];
|
id = req.signedCookies[cookies[i]];
|
||||||
|
|
||||||
if(typeof id === 'string') {
|
if(typeof id === 'string') {
|
||||||
|
|
|
@ -221,24 +221,42 @@ setRemoting(DataModel.findOne, {
|
||||||
* @param {Function} [cb] - callback called with (err)
|
* @param {Function} [cb] - callback called with (err)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DataModel.remove =
|
|
||||||
DataModel.deleteAll =
|
|
||||||
DataModel.destroyAll = function destroyAll(where, cb) {
|
DataModel.destroyAll = function destroyAll(where, cb) {
|
||||||
throwNotAttached(this.modelName, 'destroyAll');
|
throwNotAttached(this.modelName, 'destroyAll');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for `destroyAll`
|
||||||
|
*/
|
||||||
|
|
||||||
|
DataModel.remove = DataModel.destroyAll;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for `destroyAll`
|
||||||
|
*/
|
||||||
|
DataModel.deleteAll = DataModel.destroyAll;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy a record by id
|
* Destroy a record by id
|
||||||
* @param {*} id The id value
|
* @param {*} id The id value
|
||||||
* @param {Function} cb - callback called with (err)
|
* @param {Function} cb - callback called with (err)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DataModel.removeById =
|
|
||||||
DataModel.deleteById =
|
|
||||||
DataModel.destroyById = function deleteById(id, cb) {
|
DataModel.destroyById = function deleteById(id, cb) {
|
||||||
throwNotAttached(this.modelName, 'deleteById');
|
throwNotAttached(this.modelName, 'deleteById');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for deleteById
|
||||||
|
*/
|
||||||
|
DataModel.deleteById = DataModel.destroyById;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for deleteById
|
||||||
|
*/
|
||||||
|
DataModel.removeById = DataModel.destroyById;
|
||||||
|
|
||||||
// deleteById ~ remoting attributes
|
// deleteById ~ remoting attributes
|
||||||
setRemoting(DataModel.deleteById, {
|
setRemoting(DataModel.deleteById, {
|
||||||
description: 'Delete a model instance by id from the data source',
|
description: 'Delete a model instance by id from the data source',
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
*/
|
*/
|
||||||
var registry = require('../registry');
|
var registry = require('../registry');
|
||||||
var compat = require('../compat');
|
var compat = require('../compat');
|
||||||
var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder;
|
|
||||||
var modeler = new ModelBuilder();
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +12,7 @@ var assert = require('assert');
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Model = module.exports = modeler.define('Model');
|
var Model = module.exports = registry.modelBuilder.define('Model');
|
||||||
|
|
||||||
Model.shared = true;
|
Model.shared = true;
|
||||||
|
|
||||||
|
|
|
@ -26,22 +26,11 @@ var properties = {
|
||||||
realm: {type: String},
|
realm: {type: String},
|
||||||
username: {type: String},
|
username: {type: String},
|
||||||
password: {type: String, required: true},
|
password: {type: String, required: true},
|
||||||
|
credentials: Object, // deprecated, to be removed in 2.x
|
||||||
|
challenges: Object, // deprecated, to be removed in 2.x
|
||||||
email: {type: String, required: true},
|
email: {type: String, required: true},
|
||||||
emailVerified: Boolean,
|
emailVerified: Boolean,
|
||||||
verificationToken: String,
|
verificationToken: String,
|
||||||
|
|
||||||
credentials: [
|
|
||||||
'UserCredential' // User credentials, private or public, such as private/public keys, Kerberos tickets, oAuth tokens, facebook, google, github ids
|
|
||||||
],
|
|
||||||
challenges: [
|
|
||||||
'Challenge' // Security questions/answers
|
|
||||||
],
|
|
||||||
// https://en.wikipedia.org/wiki/Multi-factor_authentication
|
|
||||||
/*
|
|
||||||
factors: [
|
|
||||||
'AuthenticationFactor'
|
|
||||||
],
|
|
||||||
*/
|
|
||||||
status: String,
|
status: String,
|
||||||
created: Date,
|
created: Date,
|
||||||
lastUpdated: Date
|
lastUpdated: Date
|
||||||
|
@ -186,6 +175,15 @@ User.login = function (credentials, include, fn) {
|
||||||
debug('An error is reported from User.findOne: %j', err);
|
debug('An error is reported from User.findOne: %j', err);
|
||||||
fn(defaultError);
|
fn(defaultError);
|
||||||
} else if(user) {
|
} else if(user) {
|
||||||
|
if (self.settings.emailVerificationRequired) {
|
||||||
|
if (!user.emailVerified) {
|
||||||
|
// Fail to log in if email verification is not done yet
|
||||||
|
debug('User email has not been verified');
|
||||||
|
err = new Error('login failed as the email has not been verified');
|
||||||
|
err.statusCode = 401;
|
||||||
|
return fn(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
user.hasPassword(credentials.password, function(err, isMatch) {
|
user.hasPassword(credentials.password, function(err, isMatch) {
|
||||||
if(err) {
|
if(err) {
|
||||||
debug('An error is reported from User.hasPassword: %j', err);
|
debug('An error is reported from User.hasPassword: %j', err);
|
||||||
|
@ -441,6 +439,15 @@ User.setup = function () {
|
||||||
this.$password = bcrypt.hashSync(plain, salt);
|
this.$password = bcrypt.hashSync(plain, salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure emailVerified is not set by creation
|
||||||
|
UserModel.beforeRemote('create', function(ctx, user, next) {
|
||||||
|
var body = ctx.req.body;
|
||||||
|
if (body && body.emailVerified) {
|
||||||
|
body.emailVerified = false;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
loopback.remoteMethod(
|
loopback.remoteMethod(
|
||||||
UserModel.login,
|
UserModel.login,
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,12 +9,16 @@
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var extend = require('util')._extend;
|
var extend = require('util')._extend;
|
||||||
var DataSource = require('loopback-datasource-juggler').DataSource;
|
var juggler = require('loopback-datasource-juggler');
|
||||||
|
var DataSource = juggler.DataSource;
|
||||||
|
var ModelBuilder = juggler.ModelBuilder;
|
||||||
|
|
||||||
var registry = module.exports;
|
var registry = module.exports;
|
||||||
|
|
||||||
registry.defaultDataSources = {};
|
registry.defaultDataSources = {};
|
||||||
|
|
||||||
|
registry.modelBuilder = new ModelBuilder();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a named vanilla JavaScript class constructor with an attached
|
* Create a named vanilla JavaScript class constructor with an attached
|
||||||
* set of properties and options.
|
* set of properties and options.
|
||||||
|
@ -179,7 +183,7 @@ registry.configureModel = function(ModelCtor, config) {
|
||||||
* @header loopback.getModel(modelName)
|
* @header loopback.getModel(modelName)
|
||||||
*/
|
*/
|
||||||
registry.getModel = function(modelName) {
|
registry.getModel = function(modelName) {
|
||||||
return this.Model.modelBuilder.models[modelName];
|
return this.modelBuilder.models[modelName];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -194,7 +198,7 @@ registry.getModel = function(modelName) {
|
||||||
registry.getModelByType = function(modelType) {
|
registry.getModelByType = function(modelType) {
|
||||||
assert(typeof modelType === 'function',
|
assert(typeof modelType === 'function',
|
||||||
'The model type must be a constructor');
|
'The model type must be a constructor');
|
||||||
var models = this.Model.modelBuilder.models;
|
var models = this.modelBuilder.models;
|
||||||
for(var m in models) {
|
for(var m in models) {
|
||||||
if(models[m].prototype instanceof modelType) {
|
if(models[m].prototype instanceof modelType) {
|
||||||
return models[m];
|
return models[m];
|
||||||
|
@ -216,10 +220,10 @@ registry.getModelByType = function(modelType) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
registry.createDataSource = function (name, options) {
|
registry.createDataSource = function (name, options) {
|
||||||
var loopback = this;
|
var self = this;
|
||||||
var ds = new DataSource(name, options, loopback.Model.modelBuilder);
|
var ds = new DataSource(name, options, self.modelBuilder);
|
||||||
ds.createModel = function (name, properties, settings) {
|
ds.createModel = function (name, properties, settings) {
|
||||||
var ModelCtor = loopback.createModel(name, properties, settings);
|
var ModelCtor = self.createModel(name, properties, settings);
|
||||||
ModelCtor.attachTo(ds);
|
ModelCtor.attachTo(ds);
|
||||||
return ModelCtor;
|
return ModelCtor;
|
||||||
};
|
};
|
||||||
|
@ -293,7 +297,7 @@ registry.getDefaultDataSourceForType = function(type) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
registry.autoAttach = function() {
|
registry.autoAttach = function() {
|
||||||
var models = this.Model.modelBuilder.models;
|
var models = this.modelBuilder.models;
|
||||||
assert.equal(typeof models, 'object', 'Cannot autoAttach without a models object');
|
assert.equal(typeof models, 'object', 'Cannot autoAttach without a models object');
|
||||||
|
|
||||||
Object.keys(models).forEach(function(modelName) {
|
Object.keys(models).forEach(function(modelName) {
|
||||||
|
@ -327,3 +331,6 @@ registry.DataSource = DataSource;
|
||||||
|
|
||||||
registry.Model = require('./models/model');
|
registry.Model = require('./models/model');
|
||||||
registry.DataModel = require('./models/data-model');
|
registry.DataModel = require('./models/data-model');
|
||||||
|
|
||||||
|
// Set the default model base class. This is done after the Model class is defined.
|
||||||
|
registry.modelBuilder.defaultModelBaseClass = registry.Model;
|
||||||
|
|
32
package.json
32
package.json
|
@ -26,36 +26,36 @@
|
||||||
"mobile",
|
"mobile",
|
||||||
"mBaaS"
|
"mBaaS"
|
||||||
],
|
],
|
||||||
"version": "1.9.1",
|
"version": "1.10.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha -R spec"
|
"test": "mocha -R spec"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "~1.0.2",
|
"async": "~0.9.0",
|
||||||
"express": "~3.5.0",
|
"bcryptjs": "~2.0.1",
|
||||||
"strong-remoting": "~1.5.0",
|
"debug": "~1.0.3",
|
||||||
"inflection": "~1.3.7",
|
|
||||||
"nodemailer": "~0.7.0",
|
|
||||||
"ejs": "~1.0.0",
|
"ejs": "~1.0.0",
|
||||||
"bcryptjs": "~1.0.3",
|
"express": "3.x",
|
||||||
"underscore.string": "~2.3.3",
|
"inflection": "~1.3.8",
|
||||||
"underscore": "~1.6.0",
|
"nodemailer": "~0.7.1",
|
||||||
|
"strong-remoting": "~1.5.1",
|
||||||
"uid2": "0.0.3",
|
"uid2": "0.0.3",
|
||||||
"async": "~0.9.0"
|
"underscore": "~1.6.0",
|
||||||
|
"underscore.string": "~2.3.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"loopback-datasource-juggler": ">=1.4.0 <1.7.0"
|
"loopback-datasource-juggler": "^1.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"loopback-datasource-juggler": ">=1.4.0 <1.7.0",
|
"loopback-datasource-juggler": "^1.7.0",
|
||||||
"mocha": "~1.20.1",
|
"mocha": "~1.20.1",
|
||||||
"strong-task-emitter": "0.0.x",
|
"strong-task-emitter": "0.0.x",
|
||||||
"supertest": "~0.13.0",
|
"supertest": "~0.13.0",
|
||||||
"chai": "~1.9.1",
|
"chai": "~1.9.1",
|
||||||
"loopback-testing": "~0.2.0",
|
"loopback-testing": "~0.2.0",
|
||||||
"browserify": "~4.1.11",
|
"browserify": "~4.2.1",
|
||||||
"grunt": "~0.4.5",
|
"grunt": "~0.4.5",
|
||||||
"grunt-browserify": "~2.1.0",
|
"grunt-browserify": "~2.1.3",
|
||||||
"grunt-contrib-uglify": "~0.5.0",
|
"grunt-contrib-uglify": "~0.5.0",
|
||||||
"grunt-contrib-jshint": "~0.10.0",
|
"grunt-contrib-jshint": "~0.10.0",
|
||||||
"grunt-contrib-watch": "~0.6.1",
|
"grunt-contrib-watch": "~0.6.1",
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
"karma-firefox-launcher": "~0.1.3",
|
"karma-firefox-launcher": "~0.1.3",
|
||||||
"karma-html2js-preprocessor": "~0.1.0",
|
"karma-html2js-preprocessor": "~0.1.0",
|
||||||
"karma-phantomjs-launcher": "~0.1.4",
|
"karma-phantomjs-launcher": "~0.1.4",
|
||||||
"karma": "~0.12.16",
|
"karma": "~0.12.17",
|
||||||
"karma-browserify": "~0.2.1",
|
"karma-browserify": "~0.2.1",
|
||||||
"karma-mocha": "~0.1.4",
|
"karma-mocha": "~0.1.4",
|
||||||
"grunt-karma": "~0.8.3"
|
"grunt-karma": "~0.8.3"
|
||||||
|
@ -76,8 +76,6 @@
|
||||||
"browser": {
|
"browser": {
|
||||||
"express": "./lib/browser-express.js",
|
"express": "./lib/browser-express.js",
|
||||||
"connect": false,
|
"connect": false,
|
||||||
"passport": false,
|
|
||||||
"passport-local": false,
|
|
||||||
"nodemailer": false
|
"nodemailer": false
|
||||||
},
|
},
|
||||||
"license": {
|
"license": {
|
||||||
|
|
|
@ -12,7 +12,25 @@ describe('loopback.token(options)', function() {
|
||||||
.end(done);
|
.end(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should populate req.token from a header', function (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)
|
createTestAppAndRequest(this.token, done)
|
||||||
.get('/')
|
.get('/')
|
||||||
.set('authorization', this.token.id)
|
.set('authorization', this.token.id)
|
||||||
|
@ -33,6 +51,20 @@ describe('loopback.token(options)', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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) {
|
it('should skip 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) {
|
||||||
|
|
|
@ -481,6 +481,18 @@ describe('app', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('updates "url" on "listening" event', function(done) {
|
||||||
|
var app = loopback();
|
||||||
|
app.set('port', 0);
|
||||||
|
app.set('host', undefined);
|
||||||
|
|
||||||
|
app.listen(function() {
|
||||||
|
expect(app.get('url'), 'url')
|
||||||
|
.to.equal('http://127.0.0.1:' + app.get('port') + '/');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('forwards to http.Server.listen on more than one arg', function(done) {
|
it('forwards to http.Server.listen on more than one arg', function(done) {
|
||||||
var app = loopback();
|
var app = loopback();
|
||||||
app.set('port', 1);
|
app.set('port', 1);
|
||||||
|
|
|
@ -52,5 +52,60 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"physician": {
|
||||||
|
"dataSource": "db",
|
||||||
|
"public": true,
|
||||||
|
"properties": {
|
||||||
|
"name": "string"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"relations": {
|
||||||
|
"patients": {
|
||||||
|
"model": "patient",
|
||||||
|
"type": "hasMany",
|
||||||
|
"through": "appointment",
|
||||||
|
"foreignKey": "patientId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"patient": {
|
||||||
|
"dataSource": "db",
|
||||||
|
"public": true,
|
||||||
|
"properties": {
|
||||||
|
"name": "string"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"relations": {
|
||||||
|
"physicians": {
|
||||||
|
"model": "physician",
|
||||||
|
"type": "hasMany",
|
||||||
|
"through": "appointment",
|
||||||
|
"foreignKey": "physicianId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appointment": {
|
||||||
|
"dataSource": "db",
|
||||||
|
"public": true,
|
||||||
|
"properties": {
|
||||||
|
"date": "date"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"relations": {
|
||||||
|
"physician": {
|
||||||
|
"model": "physician",
|
||||||
|
"type": "belongsTo",
|
||||||
|
"foreignKey": "physicianId"
|
||||||
|
},
|
||||||
|
"patient": {
|
||||||
|
"model": "patient",
|
||||||
|
"type": "belongsTo",
|
||||||
|
"foreignKey": "patientId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ describe('loopback', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loopback.createDataSource(options)', function(){
|
describe('loopback.createDataSource(options)', function() {
|
||||||
it('Create a data source with a connector.', function() {
|
it('Create a data source with a connector.', function() {
|
||||||
var dataSource = loopback.createDataSource({
|
var dataSource = loopback.createDataSource({
|
||||||
connector: loopback.Memory
|
connector: loopback.Memory
|
||||||
|
@ -22,6 +22,23 @@ describe('loopback', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('data source created by loopback', function() {
|
||||||
|
it('should create model extending Model by default', function () {
|
||||||
|
var dataSource = loopback.createDataSource({
|
||||||
|
connector: loopback.Memory
|
||||||
|
});
|
||||||
|
var m1 = dataSource.createModel('m1', {});
|
||||||
|
assert(m1.prototype instanceof loopback.Model);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('model created by loopback', function() {
|
||||||
|
it('should extend from Model by default', function() {
|
||||||
|
var m1 = loopback.createModel('m1', {});
|
||||||
|
assert(m1.prototype instanceof loopback.Model);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('loopback.autoAttach', function () {
|
describe('loopback.autoAttach', function () {
|
||||||
it('doesn\'t overwrite model with datasource configured', function () {
|
it('doesn\'t overwrite model with datasource configured', function () {
|
||||||
var ds1 = loopback.createDataSource('db1', {
|
var ds1 = loopback.createDataSource('db1', {
|
||||||
|
|
|
@ -6,6 +6,7 @@ var app = require(path.join(SIMPLE_APP, 'app.js'));
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var expect = require('chai').expect;
|
var expect = require('chai').expect;
|
||||||
var debug = require('debug')('loopback:test:relations.integration');
|
var debug = require('debug')('loopback:test:relations.integration');
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
describe('relations - integration', function () {
|
describe('relations - integration', function () {
|
||||||
|
|
||||||
|
@ -122,6 +123,218 @@ describe('relations - integration', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('hasMany through', function() {
|
||||||
|
|
||||||
|
function setup(connecting, cb) {
|
||||||
|
var root = {};
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
// Clean up models
|
||||||
|
function (done) {
|
||||||
|
app.models.physician.destroyAll(function (err) {
|
||||||
|
app.models.patient.destroyAll(function (err) {
|
||||||
|
app.models.appointment.destroyAll(function (err) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create a physician
|
||||||
|
function (done) {
|
||||||
|
app.models.physician.create({
|
||||||
|
name: 'ph1'
|
||||||
|
}, function (err, physician) {
|
||||||
|
root.physician = physician;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Create a patient
|
||||||
|
connecting ? function (done) {
|
||||||
|
root.physician.patients.create({
|
||||||
|
name: 'pa1'
|
||||||
|
}, function (err, patient) {
|
||||||
|
root.patient = patient;
|
||||||
|
root.relUrl = '/api/physicians/' + root.physician.id
|
||||||
|
+ '/patients/rel/' + root.patient.id;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
} : function (done) {
|
||||||
|
app.models.patient.create({
|
||||||
|
name: 'pa1'
|
||||||
|
}, function (err, patient) {
|
||||||
|
root.patient = patient;
|
||||||
|
root.relUrl = '/api/physicians/' + root.physician.id
|
||||||
|
+ '/patients/rel/' + root.patient.id;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}], function (err, done) {
|
||||||
|
cb(err, root);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('PUT /physicians/:id/patients/rel/:fk', function () {
|
||||||
|
|
||||||
|
before(function (done) {
|
||||||
|
var self = this;
|
||||||
|
setup(false, function (err, root) {
|
||||||
|
self.url = root.relUrl;
|
||||||
|
self.patient = root.patient;
|
||||||
|
self.physician = root.physician;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lt.describe.whenCalledRemotely('PUT', '/api/physicians/:id/patients/rel/:fk', function () {
|
||||||
|
it('should succeed with statusCode 200', function () {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
assert.equal(this.res.body.patientId, this.patient.id);
|
||||||
|
assert.equal(this.res.body.physicianId, this.physician.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a record in appointment', function (done) {
|
||||||
|
var self = this;
|
||||||
|
app.models.appointment.find(function (err, apps) {
|
||||||
|
assert.equal(apps.length, 1);
|
||||||
|
assert.equal(apps[0].patientId, self.patient.id);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should connect physician to patient', function (done) {
|
||||||
|
var self = this;
|
||||||
|
self.physician.patients(function (err, patients) {
|
||||||
|
assert.equal(patients.length, 1);
|
||||||
|
assert.equal(patients[0].id, self.patient.id);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE /physicians/:id/patients/rel/:fk', function () {
|
||||||
|
|
||||||
|
before(function (done) {
|
||||||
|
var self = this;
|
||||||
|
setup(true, function (err, root) {
|
||||||
|
self.url = root.relUrl;
|
||||||
|
self.patient = root.patient;
|
||||||
|
self.physician = root.physician;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a record in appointment', function (done) {
|
||||||
|
var self = this;
|
||||||
|
app.models.appointment.find(function (err, apps) {
|
||||||
|
assert.equal(apps.length, 1);
|
||||||
|
assert.equal(apps[0].patientId, self.patient.id);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should connect physician to patient', function (done) {
|
||||||
|
var self = this;
|
||||||
|
self.physician.patients(function (err, patients) {
|
||||||
|
assert.equal(patients.length, 1);
|
||||||
|
assert.equal(patients[0].id, self.patient.id);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lt.describe.whenCalledRemotely('DELETE', '/api/physicians/:id/patients/rel/:fk', function () {
|
||||||
|
it('should succeed with statusCode 200', function () {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the record in appointment', function (done) {
|
||||||
|
var self = this;
|
||||||
|
app.models.appointment.find(function (err, apps) {
|
||||||
|
assert.equal(apps.length, 0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the connection between physician and patient', function (done) {
|
||||||
|
var self = this;
|
||||||
|
// Need to refresh the cache
|
||||||
|
self.physician.patients(true, function (err, patients) {
|
||||||
|
assert.equal(patients.length, 0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /physicians/:id/patients/:fk', function () {
|
||||||
|
|
||||||
|
before(function (done) {
|
||||||
|
var self = this;
|
||||||
|
setup(true, function (err, root) {
|
||||||
|
self.url = '/api/physicians/' + root.physician.id
|
||||||
|
+ '/patients/' + root.patient.id;
|
||||||
|
self.patient = root.patient;
|
||||||
|
self.physician = root.physician;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lt.describe.whenCalledRemotely('GET', '/api/physicians/:id/patients/:fk', function () {
|
||||||
|
it('should succeed with statusCode 200', function () {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
assert.equal(this.res.body.id, this.physician.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE /physicians/:id/patients/:fk', function () {
|
||||||
|
|
||||||
|
before(function (done) {
|
||||||
|
var self = this;
|
||||||
|
setup(true, function (err, root) {
|
||||||
|
self.url = '/api/physicians/' + root.physician.id
|
||||||
|
+ '/patients/' + root.patient.id;
|
||||||
|
self.patient = root.patient;
|
||||||
|
self.physician = root.physician;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lt.describe.whenCalledRemotely('DELETE', '/api/physicians/:id/patients/:fk', function () {
|
||||||
|
it('should succeed with statusCode 200', function () {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the record in appointment', function (done) {
|
||||||
|
var self = this;
|
||||||
|
app.models.appointment.find(function (err, apps) {
|
||||||
|
assert.equal(apps.length, 0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the connection between physician and patient', function (done) {
|
||||||
|
var self = this;
|
||||||
|
// Need to refresh the cache
|
||||||
|
self.physician.patients(true, function (err, patients) {
|
||||||
|
assert.equal(patients.length, 0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the record in patient', function (done) {
|
||||||
|
var self = this;
|
||||||
|
app.models.patient.find(function (err, patients) {
|
||||||
|
assert.equal(patients.length, 0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('hasAndBelongsToMany', function() {
|
describe('hasAndBelongsToMany', function() {
|
||||||
beforeEach(function defineProductAndCategoryModels() {
|
beforeEach(function defineProductAndCategoryModels() {
|
||||||
var product = app.model(
|
var product = app.model(
|
||||||
|
|
|
@ -66,4 +66,33 @@ describe('remoting - integration', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Model', function() {
|
||||||
|
it('has expected remote methods', function() {
|
||||||
|
var storeClass = app.handler('rest').adapter
|
||||||
|
.getClasses()
|
||||||
|
.filter(function(c) { return c.name === 'store'; })[0];
|
||||||
|
var methods = storeClass.methods
|
||||||
|
.map(function(m) {
|
||||||
|
return [
|
||||||
|
m.name + '()',
|
||||||
|
m.getHttpMethod(),
|
||||||
|
m.getFullPath()
|
||||||
|
].join(' ');
|
||||||
|
});
|
||||||
|
|
||||||
|
// The list of methods is from docs:
|
||||||
|
// http://docs.strongloop.com/display/LB/Exposing+models+over+a+REST+API
|
||||||
|
expect(methods).to.include.members([
|
||||||
|
'create() POST /stores',
|
||||||
|
'upsert() PUT /stores',
|
||||||
|
'exists() GET /stores/:id/exists',
|
||||||
|
'findById() GET /stores/:id',
|
||||||
|
'find() GET /stores',
|
||||||
|
'findOne() GET /stores/findOne',
|
||||||
|
'deleteById() DELETE /stores/:id',
|
||||||
|
'count() GET /stores/count',
|
||||||
|
'prototype.updateAttributes() PUT /stores/:id',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,6 +8,8 @@ var userMemory = loopback.createDataSource({
|
||||||
|
|
||||||
describe('User', function(){
|
describe('User', function(){
|
||||||
var validCredentials = {email: 'foo@bar.com', password: 'bar'};
|
var validCredentials = {email: 'foo@bar.com', password: 'bar'};
|
||||||
|
var validCredentialsEmailVerified = {email: 'foo1@bar.com', password: 'bar1', emailVerified: true};
|
||||||
|
var validCredentialsEmailVerifiedOverREST = {email: 'foo2@bar.com', password: 'bar2', emailVerified: true};
|
||||||
var validCredentialsWithTTL = {email: 'foo@bar.com', password: 'bar', ttl: 3600};
|
var validCredentialsWithTTL = {email: 'foo@bar.com', password: 'bar', ttl: 3600};
|
||||||
var invalidCredentials = {email: 'foo1@bar.com', password: 'bar1'};
|
var invalidCredentials = {email: 'foo1@bar.com', password: 'bar1'};
|
||||||
var incompleteCredentials = {password: 'bar1'};
|
var incompleteCredentials = {password: 'bar1'};
|
||||||
|
@ -30,7 +32,9 @@ describe('User', function(){
|
||||||
app.use(loopback.rest());
|
app.use(loopback.rest());
|
||||||
app.model(User);
|
app.model(User);
|
||||||
|
|
||||||
User.create(validCredentials, done);
|
User.create(validCredentials, function(err, user) {
|
||||||
|
User.create(validCredentialsEmailVerified, done);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function (done) {
|
afterEach(function (done) {
|
||||||
|
@ -49,6 +53,22 @@ describe('User', function(){
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('credentials/challenges are object types', function (done) {
|
||||||
|
User.create({email: 'f1@b.com', password: 'bar1',
|
||||||
|
credentials: {cert: 'xxxxx', key: '111'},
|
||||||
|
challenges: {x: 'X', a: 1}
|
||||||
|
}, function (err, user) {
|
||||||
|
assert(!err);
|
||||||
|
User.findById(user.id, function (err, user) {
|
||||||
|
assert(user.id);
|
||||||
|
assert(user.email);
|
||||||
|
assert.deepEqual(user.credentials, {cert: 'xxxxx', key: '111'});
|
||||||
|
assert.deepEqual(user.challenges, {x: 'X', a: 1});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Email is required', function (done) {
|
it('Email is required', function (done) {
|
||||||
User.create({password: '123'}, function (err) {
|
User.create({password: '123'}, function (err) {
|
||||||
assert(err);
|
assert(err);
|
||||||
|
@ -57,8 +77,7 @@ describe('User', function(){
|
||||||
assert.equal(err.details.context, "user");
|
assert.equal(err.details.context, "user");
|
||||||
assert.deepEqual(err.details.codes.email, [
|
assert.deepEqual(err.details.codes.email, [
|
||||||
'presence',
|
'presence',
|
||||||
'format.blank',
|
'format.blank'
|
||||||
'uniqueness'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
@ -105,6 +124,18 @@ describe('User', function(){
|
||||||
var u = new User({username: 'foo', password: 'bar'});
|
var u = new User({username: 'foo', password: 'bar'});
|
||||||
assert(u.password !== 'bar');
|
assert(u.password !== 'bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Create a user over REST should remove emailVerified property', function(done) {
|
||||||
|
request(app)
|
||||||
|
.post('/users')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.send(validCredentialsEmailVerifiedOverREST)
|
||||||
|
.end(function(err, res){
|
||||||
|
assert(!res.body.emailVerified);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('User.login', function() {
|
describe('User.login', function() {
|
||||||
|
@ -236,6 +267,63 @@ describe('User', function(){
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('User.login requiring email verification', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
User.settings.emailVerificationRequired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
User.settings.emailVerificationRequired = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Login a user by without email verification', function(done) {
|
||||||
|
User.login(validCredentials, function (err, accessToken) {
|
||||||
|
assert(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Login a user by with email verification', function(done) {
|
||||||
|
User.login(validCredentialsEmailVerified, function (err, accessToken) {
|
||||||
|
assert(accessToken.userId);
|
||||||
|
assert(accessToken.id);
|
||||||
|
assert.equal(accessToken.id.length, 64);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Login a user over REST when email verification is required', function(done) {
|
||||||
|
request(app)
|
||||||
|
.post('/users/login')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.send(validCredentialsEmailVerified)
|
||||||
|
.end(function(err, res){
|
||||||
|
if(err) return done(err);
|
||||||
|
var accessToken = res.body;
|
||||||
|
|
||||||
|
assert(accessToken.userId);
|
||||||
|
assert(accessToken.id);
|
||||||
|
assert.equal(accessToken.id.length, 64);
|
||||||
|
assert(accessToken.user === undefined);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Login a user over REST without email verification when it is required', function(done) {
|
||||||
|
request(app)
|
||||||
|
.post('/users/login')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(401)
|
||||||
|
.send(validCredentials)
|
||||||
|
.end(function(err, res) {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('User.logout', function() {
|
describe('User.logout', function() {
|
||||||
it('Logout a user by providing the current accessToken id (using node)', function(done) {
|
it('Logout a user by providing the current accessToken id (using node)', function(done) {
|
||||||
login(logout);
|
login(logout);
|
||||||
|
|
Loading…
Reference in New Issue