Merge branch 'release/1.7.1' into production
This commit is contained in:
commit
5a97ceafa8
|
@ -279,11 +279,16 @@ app.enableAuth = function() {
|
||||||
var modelId = modelInstance && modelInstance.id || req.param('id');
|
var modelId = modelInstance && modelInstance.id || req.param('id');
|
||||||
|
|
||||||
if(Model.checkAccess) {
|
if(Model.checkAccess) {
|
||||||
|
// Pause the request before checking access
|
||||||
|
// See https://github.com/strongloop/loopback-storage-service/issues/7
|
||||||
|
req.pause();
|
||||||
Model.checkAccess(
|
Model.checkAccess(
|
||||||
req.accessToken,
|
req.accessToken,
|
||||||
modelId,
|
modelId,
|
||||||
method.name,
|
method.name,
|
||||||
function(err, allowed) {
|
function(err, allowed) {
|
||||||
|
// Emit any cached data events that fired while checking access.
|
||||||
|
req.resume();
|
||||||
if(err) {
|
if(err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
next(err);
|
next(err);
|
||||||
|
|
|
@ -57,7 +57,7 @@ loopback.mime = express.mime;
|
||||||
*/
|
*/
|
||||||
loopback.compat = require('./compat');
|
loopback.compat = require('./compat');
|
||||||
|
|
||||||
/**
|
/*!
|
||||||
* Create an loopback application.
|
* Create an loopback application.
|
||||||
*
|
*
|
||||||
* @return {Function}
|
* @return {Function}
|
||||||
|
@ -305,7 +305,7 @@ loopback.autoAttachModel = function(ModelCtor) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*!
|
||||||
* Built in models / services
|
* Built in models / services
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -194,8 +194,8 @@ Principal.SCOPE = 'SCOPE';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare if two principals are equal
|
* Compare if two principals are equal
|
||||||
* @param p The other principal
|
* Returns true if argument principal is equal to this principal.
|
||||||
* @returns {boolean}
|
* @param {Object} principal The other principal
|
||||||
*/
|
*/
|
||||||
Principal.prototype.equals = function (p) {
|
Principal.prototype.equals = function (p) {
|
||||||
if (p instanceof Principal) {
|
if (p instanceof Principal) {
|
||||||
|
@ -205,11 +205,11 @@ Principal.prototype.equals = function (p) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A request to access protected resources
|
* A request to access protected resources.
|
||||||
* @param {String} model The model name
|
* @param {String} model The model name
|
||||||
* @param {String} property
|
* @param {String} property
|
||||||
* @param {String} accessType The access type
|
* @param {String} accessType The access type
|
||||||
* @param {String} permission The permission
|
* @param {String} permission The requested permission
|
||||||
* @returns {AccessRequest}
|
* @returns {AccessRequest}
|
||||||
* @class
|
* @class
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -50,7 +50,7 @@ var PushNotificationSettingSchema = {
|
||||||
* Data model for Application
|
* Data model for Application
|
||||||
*/
|
*/
|
||||||
var ApplicationSchema = {
|
var ApplicationSchema = {
|
||||||
id: {type: String, id: true, generated: true},
|
id: {type: String, id: true},
|
||||||
// Basic information
|
// Basic information
|
||||||
name: {type: String, required: true}, // The name
|
name: {type: String, required: true}, // The name
|
||||||
description: String, // The description
|
description: String, // The description
|
||||||
|
@ -90,7 +90,7 @@ var ApplicationSchema = {
|
||||||
modified: {type: Date, default: Date}
|
modified: {type: Date, default: Date}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/*!
|
||||||
* Application management functions
|
* Application management functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -189,8 +189,7 @@ Application.resetKeys = function (appId, cb) {
|
||||||
/**
|
/**
|
||||||
* Authenticate the application id and key.
|
* Authenticate the application id and key.
|
||||||
*
|
*
|
||||||
* `matched` will be one of
|
* `matched` parameter is one of:
|
||||||
*
|
|
||||||
* - clientKey
|
* - clientKey
|
||||||
* - javaScriptKey
|
* - javaScriptKey
|
||||||
* - restApiKey
|
* - restApiKey
|
||||||
|
@ -201,7 +200,7 @@ Application.resetKeys = function (appId, cb) {
|
||||||
* @param {String} key
|
* @param {String} key
|
||||||
* @callback {Function} callback
|
* @callback {Function} callback
|
||||||
* @param {Error} err
|
* @param {Error} err
|
||||||
* @param {String} matched - The matching key
|
* @param {String} matched The matching key
|
||||||
*/
|
*/
|
||||||
Application.authenticate = function (appId, key, cb) {
|
Application.authenticate = function (appId, key, cb) {
|
||||||
this.findById(appId, function (err, app) {
|
this.findById(appId, function (err, app) {
|
||||||
|
|
|
@ -18,11 +18,11 @@ var properties = {
|
||||||
*
|
*
|
||||||
* **Properties**
|
* **Properties**
|
||||||
*
|
*
|
||||||
* - `to` - **{ String }** **required**
|
* - `to` - String (required)
|
||||||
* - `from` - **{ String }** **required**
|
* - `from` - String (required)
|
||||||
* - `subject` - **{ String }** **required**
|
* - `subject` - String (required)
|
||||||
* - `text` - **{ String }**
|
* - `text` - String
|
||||||
* - `html` - **{ String }**
|
* - `html` - String
|
||||||
*
|
*
|
||||||
* @class
|
* @class
|
||||||
* @inherits {Model}
|
* @inherits {Model}
|
||||||
|
@ -35,19 +35,24 @@ var Email = module.exports = Model.extend('Email', properties);
|
||||||
*
|
*
|
||||||
* Example Options:
|
* Example Options:
|
||||||
*
|
*
|
||||||
* ```json
|
* ```js
|
||||||
* {
|
* {
|
||||||
* from: "Fred Foo ✔ <foo@blurdybloop.com>", // sender address
|
* from: "Fred Foo <foo@blurdybloop.com>", // sender address
|
||||||
* to: "bar@blurdybloop.com, baz@blurdybloop.com", // list of receivers
|
* to: "bar@blurdybloop.com, baz@blurdybloop.com", // list of receivers
|
||||||
* subject: "Hello ✔", // Subject line
|
* subject: "Hello", // Subject line
|
||||||
* text: "Hello world ✔", // plaintext body
|
* text: "Hello world", // plaintext body
|
||||||
* html: "<b>Hello world ✔</b>" // html body
|
* html: "<b>Hello world</b>" // html body
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* See https://github.com/andris9/Nodemailer for other supported options.
|
* See https://github.com/andris9/Nodemailer for other supported options.
|
||||||
*
|
*
|
||||||
* @param {Object} options
|
* @options {Object} options See below
|
||||||
|
* @prop {String} from Senders's email address
|
||||||
|
* @prop {String} to List of one or more recipient email addresses (comma-delimited)
|
||||||
|
* @prop {String} subject Subject line
|
||||||
|
* @prop {String} text Body text
|
||||||
|
* @prop {String} html Body HTML (optional)
|
||||||
* @param {Function} callback Called after the e-mail is sent or the sending failed
|
* @param {Function} callback Called after the e-mail is sent or the sending failed
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -1,222 +0,0 @@
|
||||||
var loopback = require('../loopback');
|
|
||||||
|
|
||||||
// "OAuth token"
|
|
||||||
var OAuthToken = loopback.createModel({
|
|
||||||
// "access token"
|
|
||||||
accessToken: {
|
|
||||||
type: String,
|
|
||||||
index: {
|
|
||||||
unique: true
|
|
||||||
}
|
|
||||||
}, // key, The string token
|
|
||||||
clientId: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}, // The client id
|
|
||||||
resourceOwner: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}, // The resource owner (user) id
|
|
||||||
realm: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}, // The resource owner realm
|
|
||||||
issuedAt: {
|
|
||||||
type: Date,
|
|
||||||
index: true
|
|
||||||
}, // The timestamp when the token is issued
|
|
||||||
expiresIn: Number, // Expiration time in seconds
|
|
||||||
expiredAt: {
|
|
||||||
type: Date,
|
|
||||||
index: {
|
|
||||||
expires: "1d"
|
|
||||||
}
|
|
||||||
}, // The timestamp when the token is expired
|
|
||||||
scopes: [ String ], // oAuth scopes
|
|
||||||
parameters: [
|
|
||||||
{
|
|
||||||
name: String,
|
|
||||||
value: String
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
authorizationCode: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}, // The corresponding authorization code that is used to request the
|
|
||||||
// access token
|
|
||||||
refreshToken: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}, // The corresponding refresh token if issued
|
|
||||||
|
|
||||||
tokenType: {
|
|
||||||
type: String,
|
|
||||||
enum: [ "Bearer", "MAC" ]
|
|
||||||
}, // The token type, such as Bearer:
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-16
|
|
||||||
// or MAC: http://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05
|
|
||||||
authenticationScheme: String, // HTTP authenticationScheme
|
|
||||||
hash: String // The SHA-1 hash for
|
|
||||||
// client-secret/resource-owner-secret-key
|
|
||||||
});
|
|
||||||
|
|
||||||
// "OAuth authorization code"
|
|
||||||
var OAuthAuthorizationCode = loopback.createModel({
|
|
||||||
code: {
|
|
||||||
type: String,
|
|
||||||
index: {
|
|
||||||
unique: true
|
|
||||||
}
|
|
||||||
}, // key // The string code
|
|
||||||
clientId: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}, // The client id
|
|
||||||
resourceOwner: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}, // The resource owner (user) id
|
|
||||||
realm: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}, // The resource owner realm
|
|
||||||
|
|
||||||
issuedAt: {
|
|
||||||
type: Date,
|
|
||||||
index: true
|
|
||||||
}, // The timestamp when the token is issued
|
|
||||||
expiresIn: Number, // Expiration time in seconds
|
|
||||||
expiredAt: {
|
|
||||||
type: Date,
|
|
||||||
index: {
|
|
||||||
expires: "1d"
|
|
||||||
}
|
|
||||||
}, // The timestamp when the token is expired
|
|
||||||
|
|
||||||
scopes: [ String ], // oAuth scopes
|
|
||||||
parameters: [
|
|
||||||
{
|
|
||||||
name: String,
|
|
||||||
value: String
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
used: Boolean, // Is it ever used
|
|
||||||
redirectURI: String, // The redirectURI from the request, we need to
|
|
||||||
// check if it's identical to the one used for
|
|
||||||
// access token
|
|
||||||
hash: String // The SHA-1 hash for
|
|
||||||
// client-secret/resource-owner-secret-key
|
|
||||||
});
|
|
||||||
|
|
||||||
// "OAuth client registration record"
|
|
||||||
var ClientRegistration = loopback.createModel({
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
index: {
|
|
||||||
unique: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clientId: {
|
|
||||||
type: String,
|
|
||||||
index: {
|
|
||||||
unique: true
|
|
||||||
}
|
|
||||||
}, // key; // The client id
|
|
||||||
clientSecret: String, // The generated client secret
|
|
||||||
|
|
||||||
defaultTokenType: String,
|
|
||||||
accessLevel: Number, // The access level to scopes, -1: disabled, 0:
|
|
||||||
// basic, 1..N
|
|
||||||
disabled: Boolean,
|
|
||||||
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
},
|
|
||||||
email: String,
|
|
||||||
description: String,
|
|
||||||
url: String,
|
|
||||||
iconURL: String,
|
|
||||||
redirectURIs: [ String ],
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
enum: [ "CONFIDENTIAL", "PUBLIC" ]
|
|
||||||
},
|
|
||||||
|
|
||||||
userId: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
} // The registered developer
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// "OAuth permission"
|
|
||||||
var OAuthPermission = loopback.createModel({
|
|
||||||
clientId: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}, // The client id
|
|
||||||
resourceOwner: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}, // The resource owner (user) id
|
|
||||||
realm: {
|
|
||||||
type: String,
|
|
||||||
index: true
|
|
||||||
}, // The resource owner realm
|
|
||||||
|
|
||||||
issuedAt: {
|
|
||||||
type: Date,
|
|
||||||
index: true
|
|
||||||
}, // The timestamp when the permission is issued
|
|
||||||
expiresIn: Number, // Expiration time in seconds
|
|
||||||
expiredAt: {
|
|
||||||
type: Date,
|
|
||||||
index: {
|
|
||||||
expires: "1d"
|
|
||||||
}
|
|
||||||
}, // The timestamp when the permission is expired
|
|
||||||
|
|
||||||
scopes: [ String ]
|
|
||||||
});
|
|
||||||
|
|
||||||
// "OAuth scope"
|
|
||||||
var OAuthScope = loopback.createModel({
|
|
||||||
scope: {
|
|
||||||
type: String,
|
|
||||||
index: {
|
|
||||||
unique: true
|
|
||||||
}
|
|
||||||
}, // key; // The scope name
|
|
||||||
description: String, // Description of the scope
|
|
||||||
iconURL: String, // The icon to be displayed on the "Request Permission"
|
|
||||||
// dialog
|
|
||||||
expiresIn: Number, // The default maximum lifetime of access token that
|
|
||||||
// carries the scope
|
|
||||||
requiredAccessLevel: Number, // The minimum access level required
|
|
||||||
resourceOwnerAuthorizationRequired: Boolean
|
|
||||||
// The scope requires authorization from the resource owner
|
|
||||||
});
|
|
||||||
|
|
||||||
// "OAuth protected resource"
|
|
||||||
var OAuthResource = loopback.createModel({
|
|
||||||
operations: [
|
|
||||||
{
|
|
||||||
type: String,
|
|
||||||
enum: [ "ALL", "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH" ]
|
|
||||||
}
|
|
||||||
], // A list of operations, by default ALL
|
|
||||||
path: String, // The resource URI path
|
|
||||||
scopes: [ String ]
|
|
||||||
// Allowd scopes
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use the schema to register a model
|
|
||||||
exports.OAuthToken = OAuthToken;
|
|
||||||
exports.OAuthAuthorizationCode = OAuthAuthorizationCode;
|
|
||||||
exports.ClientRegistration = ClientRegistration;
|
|
||||||
exports.OAuthPermission = OAuthPermission;
|
|
||||||
exports.OAuthScope = OAuthScope;
|
|
||||||
exports.OAuthResource = OAuthResource;
|
|
|
@ -149,11 +149,14 @@ User.login = function (credentials, include, fn) {
|
||||||
} else if(credentials.username) {
|
} else if(credentials.username) {
|
||||||
query.username = credentials.username;
|
query.username = credentials.username;
|
||||||
} else {
|
} else {
|
||||||
return fn(new Error('must provide username or email'));
|
var err = new Error('username or email is required');
|
||||||
|
err.statusCode = 400;
|
||||||
|
return fn(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.findOne({where: query}, function(err, user) {
|
this.findOne({where: query}, function(err, user) {
|
||||||
var defaultError = new Error('login failed');
|
var defaultError = new Error('login failed');
|
||||||
|
defaultError.statusCode = 401;
|
||||||
|
|
||||||
if(err) {
|
if(err) {
|
||||||
debug('An error is reported from User.findOne: %j', err);
|
debug('An error is reported from User.findOne: %j', err);
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"Platform",
|
"Platform",
|
||||||
"mBaaS"
|
"mBaaS"
|
||||||
],
|
],
|
||||||
"version": "1.7.0",
|
"version": "1.7.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha -R spec"
|
"test": "mocha -R spec"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
var net = require('net');
|
||||||
|
describe('loopback application', function() {
|
||||||
|
it('pauses request stream during authentication', function(done) {
|
||||||
|
// This test reproduces the issue reported in
|
||||||
|
// https://github.com/strongloop/loopback-storage-service/issues/7
|
||||||
|
var app = loopback();
|
||||||
|
setupAppWithStreamingMethod();
|
||||||
|
|
||||||
|
app.listen(0, function() {
|
||||||
|
sendHttpRequestInOnePacket(
|
||||||
|
this.address().port,
|
||||||
|
'POST /streamers/read HTTP/1.0\n' +
|
||||||
|
'Content-Length: 1\n' +
|
||||||
|
'Content-Type: application/x-custom-octet-stream\n' +
|
||||||
|
'\n' +
|
||||||
|
'X',
|
||||||
|
function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(res).to.match(/\nX$/);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupAppWithStreamingMethod() {
|
||||||
|
app.dataSource('db', {
|
||||||
|
connector: loopback.Memory,
|
||||||
|
defaultForType: 'db'
|
||||||
|
});
|
||||||
|
var db = app.datasources.db;
|
||||||
|
|
||||||
|
loopback.User.attachTo(db);
|
||||||
|
loopback.AccessToken.attachTo(db);
|
||||||
|
loopback.Role.attachTo(db);
|
||||||
|
loopback.ACL.attachTo(db);
|
||||||
|
loopback.User.hasMany(loopback.AccessToken, { as: 'accessTokens' });
|
||||||
|
|
||||||
|
var Streamer = app.model('Streamer', { dataSource: 'db' });
|
||||||
|
Streamer.read = function(req, res, cb) {
|
||||||
|
var body = new Buffer(0);
|
||||||
|
req.on('data', function(chunk) {
|
||||||
|
body += chunk;
|
||||||
|
});
|
||||||
|
req.on('end', function() {
|
||||||
|
res.end(body.toString());
|
||||||
|
// we must not call the callback here
|
||||||
|
// because it will attempt to add response headers
|
||||||
|
});
|
||||||
|
req.once('error', function(err) {
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
loopback.remoteMethod(Streamer.read, {
|
||||||
|
http: { method: 'post' },
|
||||||
|
accepts: [
|
||||||
|
{ arg: 'req', type: 'Object', http: { source: 'req' } },
|
||||||
|
{ arg: 'res', type: 'Object', http: { source: 'res' } }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
app.enableAuth();
|
||||||
|
app.use(loopback.token({ model: app.models.accessToken }));
|
||||||
|
app.use(loopback.rest());
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendHttpRequestInOnePacket(port, reqString, cb) {
|
||||||
|
var socket = net.createConnection(port);
|
||||||
|
var response = new Buffer(0);
|
||||||
|
|
||||||
|
socket.on('data', function(chunk) {
|
||||||
|
response += chunk;
|
||||||
|
});
|
||||||
|
socket.on('end', function() {
|
||||||
|
callCb(null, response.toString());
|
||||||
|
});
|
||||||
|
socket.once('error', function(err) {
|
||||||
|
callCb(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.write(reqString.replace(/\n/g, '\r\n'));
|
||||||
|
|
||||||
|
function callCb(err, res) {
|
||||||
|
if (!cb) return;
|
||||||
|
cb(err, res);
|
||||||
|
cb = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -20,6 +20,7 @@ describe('Application', function () {
|
||||||
assert(app.masterKey);
|
assert(app.masterKey);
|
||||||
assert(app.created);
|
assert(app.created);
|
||||||
assert(app.modified);
|
assert(app.modified);
|
||||||
|
assert.equal(typeof app.id, 'string');
|
||||||
done(err, result);
|
done(err, result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,9 @@ 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 invalidCredentials = {email: 'foo1@bar.com', password: 'bar1'};
|
||||||
|
var incompleteCredentials = {password: 'bar1'};
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
User = loopback.User.extend('user');
|
User = loopback.User.extend('user');
|
||||||
User.email = loopback.Email.extend('email');
|
User.email = loopback.Email.extend('email');
|
||||||
|
@ -135,6 +138,40 @@ describe('User', function(){
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Login a user over REST by providing invalid credentials', function(done) {
|
||||||
|
request(app)
|
||||||
|
.post('/users/login')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(401)
|
||||||
|
.send(invalidCredentials)
|
||||||
|
.end(function(err, res){
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Login a user over REST by providing incomplete credentials', function(done) {
|
||||||
|
request(app)
|
||||||
|
.post('/users/login')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(400)
|
||||||
|
.send(incompleteCredentials)
|
||||||
|
.end(function(err, res){
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Login a user over REST with the wrong Content-Type', function(done) {
|
||||||
|
request(app)
|
||||||
|
.post('/users/login')
|
||||||
|
.set('Content-Type', null)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(400)
|
||||||
|
.send(validCredentials)
|
||||||
|
.end(function(err, res){
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Returns current user when `include` is `USER`', function(done) {
|
it('Returns current user when `include` is `USER`', function(done) {
|
||||||
request(app)
|
request(app)
|
||||||
.post('/users/login?include=USER')
|
.post('/users/login?include=USER')
|
||||||
|
|
Loading…
Reference in New Issue