commit
51a38fc0f6
48
docs/api.md
48
docs/api.md
|
@ -210,7 +210,47 @@ app.use(loopback.rest());
|
|||
|
||||
View generated REST documentation by visiting: [http://localhost:3000/_docs](http://localhost:3000/_docs).
|
||||
|
||||
|
||||
### Middleware
|
||||
|
||||
LoopBack comes bundled with several `connect` / `express` style middleware.
|
||||
|
||||
#### loopback.token(options)
|
||||
|
||||
**Options**
|
||||
|
||||
- `cookies` - An `Array` of cookie names
|
||||
- `headers` - An `Array` of header names
|
||||
- `params` - An `Array` of param names
|
||||
|
||||
Each array is used to add additional keys to find an `accessToken` for a `request`.
|
||||
|
||||
The following example illustrates how to check for an `accessToken` in a custom cookie, query string parameter
|
||||
and header called `foo-auth`.
|
||||
|
||||
```js
|
||||
app.use(loopback.token({
|
||||
cookies: ['foo-auth'],
|
||||
headers: ['foo-auth', 'X-Foo-Auth'],
|
||||
cookies: ['foo-auth', 'foo_auth']
|
||||
}));
|
||||
```
|
||||
|
||||
**Defaults**
|
||||
|
||||
By default the following names will be checked. These names are appended to any optional names. They will always
|
||||
be checked, but any names specified will be checked first.
|
||||
|
||||
```js
|
||||
params.push('access_token');
|
||||
headers.push('X-Access-Token');
|
||||
headers.push('authorization');
|
||||
cookies.push('access_token');
|
||||
cookies.push('authorization');
|
||||
```
|
||||
|
||||
> **NOTE:** The `loopback.token()` middleware will only check for [signed cookies](http://expressjs.com/api.html#req.signedCookies).
|
||||
|
||||
### Model
|
||||
|
||||
A Loopback `Model` is a vanilla JavaScript class constructor with an attached set of properties and options. A `Model` instance is created by passing a data object containing properties to the `Model` constructor. A `Model` constructor will clean the object passed to it and only set the values matching the properties you define.
|
||||
|
@ -565,8 +605,8 @@ User.login = function (username, password, fn) {
|
|||
} else if(!user) {
|
||||
fn(failErr);
|
||||
} else if(user.password === passwordHash) {
|
||||
MySessionModel.create({userId: user.id}, function (err, session) {
|
||||
fn(null, session.id);
|
||||
MyAccessTokenModel.create({userId: user.id}, function (err, accessToken) {
|
||||
fn(null, accessToken.id);
|
||||
});
|
||||
} else {
|
||||
fn(failErr);
|
||||
|
@ -585,7 +625,7 @@ loopback.remoteMethod(
|
|||
{arg: 'username', type: 'string', required: true},
|
||||
{arg: 'password', type: 'string', required: true}
|
||||
],
|
||||
returns: {arg: 'sessionId', type: 'any'},
|
||||
returns: {arg: 'accessTokenId', type: 'any'},
|
||||
http: {path: '/sign-in'}
|
||||
}
|
||||
);
|
||||
|
@ -637,7 +677,7 @@ Define an instance method.
|
|||
|
||||
```js
|
||||
User.prototype.logout = function (fn) {
|
||||
MySessionModel.destroyAll({userId: this.id}, fn);
|
||||
MyAccessTokenModel.destroyAll({userId: this.id}, fn);
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -25,16 +25,16 @@ var User = loopback.User.extend('user');
|
|||
// attach to the memory connector
|
||||
User.attachTo(memory);
|
||||
|
||||
// also attach the session model to a data source
|
||||
User.session.attachTo(memory);
|
||||
// also attach the accessToken model to a data source
|
||||
User.accessToken.attachTo(memory);
|
||||
|
||||
// expose over the app's api
|
||||
app.model(User);
|
||||
```
|
||||
|
||||
**Note:** By default the `loopback.User` model uses the `loopback.Session` model to persist sessions. You can change this by setting the `session` property.
|
||||
**Note:** By default the `loopback.User` model uses the `loopback.AccessToken` model to persist access tokens. You can change this by setting the `accessToken` property.
|
||||
|
||||
**Note:** You must attach both the `User` and `User.session` model's to a data source!
|
||||
**Note:** You must attach both the `User` and `User.accessToken` model's to a data source!
|
||||
|
||||
#### User Creation
|
||||
|
||||
|
@ -49,13 +49,13 @@ User.create({email: 'foo@bar.com', password: 'bar'}, function(err, user) {
|
|||
|
||||
#### Login a User
|
||||
|
||||
Create a session for a user using the local auth strategy.
|
||||
Create an `accessToken` for a user using the local auth strategy.
|
||||
|
||||
**Node.js**
|
||||
|
||||
```js
|
||||
User.login({username: 'foo', password: 'bar'}, function(err, session) {
|
||||
console.log(session);
|
||||
User.login({username: 'foo', password: 'bar'}, function(err, accessToken) {
|
||||
console.log(accessToken);
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -88,8 +88,8 @@ POST
|
|||
|
||||
```js
|
||||
// login a user and logout
|
||||
User.login({"email": "foo@bar.com", "password": "bar"}, function(err, session) {
|
||||
User.logout(session.id, function(err) {
|
||||
User.login({"email": "foo@bar.com", "password": "bar"}, function(err, accessToken) {
|
||||
User.logout(accessToken.id, function(err) {
|
||||
// user logged out
|
||||
});
|
||||
});
|
||||
|
@ -106,7 +106,7 @@ User.findOne({email: 'foo@bar.com'}, function(err, user) {
|
|||
POST /users/logout
|
||||
...
|
||||
{
|
||||
"sid": "<session id from user login>"
|
||||
"sid": "<accessToken id from user login>"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -175,23 +175,23 @@ User.confirmReset(token, function(err) {
|
|||
});
|
||||
```
|
||||
|
||||
### Session Model
|
||||
### AccessToken Model
|
||||
|
||||
Identify users by creating sessions when they connect to your loopback app. By default the `loopback.User` model uses the `loopback.Session` model to persist sessions. You can change this by setting the `session` property.
|
||||
Identify users by creating accessTokens when they connect to your loopback app. By default the `loopback.User` model uses the `loopback.AccessToken` model to persist accessTokens. You can change this by setting the `accessToken` property.
|
||||
|
||||
```js
|
||||
// define a custom session model
|
||||
var MySession = loopback.Session.extend('my-session');
|
||||
// define a custom accessToken model
|
||||
var MyAccessToken = loopback.AccessToken.extend('MyAccessToken');
|
||||
|
||||
// define a custom User model
|
||||
var User = loopback.User.extend('user');
|
||||
|
||||
// use the custom session model
|
||||
User.session = MySession;
|
||||
// use the custom accessToken model
|
||||
User.accessToken = MyAccessToken;
|
||||
|
||||
// attach both Session and User to a data source
|
||||
// attach both AccessToken and User to a data source
|
||||
User.attachTo(loopback.memory());
|
||||
MySession.attachTo(loopback.memory());
|
||||
MyAccessToken.attachTo(loopback.memory());
|
||||
```
|
||||
|
||||
### Email Model
|
||||
|
|
|
@ -10,7 +10,7 @@ A LoopBack model consists of:
|
|||
Apps use the model API to display information to the user or trigger actions
|
||||
on the models to interact with backend systems. LoopBack supports both "dynamic" schema-less models and "static", schema-driven models.
|
||||
|
||||
_Dynamic models_ require only a name. The format of the data are specified completely and flexibly by the client application. Well-suited for data that originates on the client, dynamic models enable you to persist data both between sessions and between devices without involving a schema.
|
||||
_Dynamic models_ require only a name. The format of the data are specified completely and flexibly by the client application. Well-suited for data that originates on the client, dynamic models enable you to persist data both between accessTokens and between devices without involving a schema.
|
||||
|
||||
_Static models_ require more code up front, with the format of the data specified completely in JSON. Well-suited to both existing data and large, intricate datasets, static models provide structure and
|
||||
consistency to their data, preventing bugs that can result from unexpected data in the database.
|
||||
|
|
|
@ -79,6 +79,14 @@ app.model = function (Model, config) {
|
|||
return Model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Model by name.
|
||||
*/
|
||||
|
||||
app.getModel = function (modelName) {
|
||||
this.models
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all exposed models.
|
||||
*/
|
||||
|
|
|
@ -172,5 +172,5 @@ loopback.memory = function (name) {
|
|||
loopback.Model = require('./models/model');
|
||||
loopback.Email = require('./models/email');
|
||||
loopback.User = require('./models/user');
|
||||
loopback.Session = require('./models/session');
|
||||
loopback.Application = require('./models/application');
|
||||
loopback.AccessToken = require('./models/access-token');
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var loopback = require('../loopback');
|
||||
var RemoteObjects = require('strong-remoting');
|
||||
|
||||
/**
|
||||
* Export the middleware.
|
||||
*/
|
||||
|
||||
module.exports = token;
|
||||
|
||||
function token(options) {
|
||||
options = options || {};
|
||||
var TokenModel = options.model;
|
||||
assert(TokenModel, 'loopback.token() middleware requires a AccessToken model');
|
||||
|
||||
return function (req, res, next) {
|
||||
TokenModel.findForRequest(req, options, function(err, token) {
|
||||
if(err) return next(err);
|
||||
if(token) {
|
||||
req.accessToken = token;
|
||||
next();
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
/**
|
||||
* Module Dependencies.
|
||||
*/
|
||||
|
||||
var Model = require('../loopback').Model
|
||||
, loopback = require('../loopback')
|
||||
, assert = require('assert')
|
||||
, crypto = require('crypto')
|
||||
, uid = require('uid2')
|
||||
, DEFAULT_TTL = 1209600 // 2 weeks in seconds
|
||||
, DEFAULT_TOKEN_LEN = 64;
|
||||
|
||||
/**
|
||||
* Default AccessToken properties.
|
||||
*/
|
||||
|
||||
var properties = {
|
||||
id: {type: String, generated: true, id: 1},
|
||||
ttl: {type: Number, ttl: true, default: DEFAULT_TTL}, // time to live in seconds
|
||||
created: {type: Date, default: function() {
|
||||
return new Date();
|
||||
}}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extends from the built in `loopback.Model` type.
|
||||
*/
|
||||
|
||||
var AccessToken = module.exports = Model.extend('AccessToken', properties);
|
||||
|
||||
/**
|
||||
* Create a cryptographically random access token id.
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
AccessToken.createAccessTokenId = function (fn) {
|
||||
uid(this.settings.accessTokenIdLength || DEFAULT_TOKEN_LEN, function(err, guid) {
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else {
|
||||
fn(null, guid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*!
|
||||
* Hook to create accessToken id.
|
||||
*/
|
||||
|
||||
AccessToken.beforeCreate = function (next, data) {
|
||||
data = data || {};
|
||||
|
||||
AccessToken.createAccessTokenId(function (err, id) {
|
||||
if(err) {
|
||||
next(err);
|
||||
} else {
|
||||
data.id = id;
|
||||
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a token for the given `ServerRequest`.
|
||||
*
|
||||
* @param {ServerRequest} req
|
||||
* @param {Object} [options] Options for finding the token
|
||||
* @param {Function} callback Calls back with a token if one exists otherwise null or an error.
|
||||
*/
|
||||
|
||||
AccessToken.findForRequest = function(req, options, cb) {
|
||||
var id = tokenIdForRequest(req, options);
|
||||
|
||||
if(id) {
|
||||
this.findById(id, function(err, token) {
|
||||
if(err) {
|
||||
cb(err);
|
||||
} else {
|
||||
token.validate(function(err, isValid) {
|
||||
if(err) {
|
||||
cb(err);
|
||||
} else if(isValid) {
|
||||
cb(null, token);
|
||||
} else {
|
||||
cb(new Error('Invalid Access Token'));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
process.nextTick(function() {
|
||||
cb();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
AccessToken.prototype.validate = function(cb) {
|
||||
try {
|
||||
assert(
|
||||
this.created && typeof this.created.getTime === 'function',
|
||||
'token.created must be a valid Date'
|
||||
);
|
||||
assert(this.ttl !== 0, 'token.ttl must be not be 0');
|
||||
assert(this.ttl, 'token.ttl must exist');
|
||||
assert(this.ttl >= -1, 'token.ttl must be >= -1');
|
||||
|
||||
var now = Date.now();
|
||||
var created = this.created.getTime();
|
||||
var elapsedSeconds = (now - created) / 1000;
|
||||
var secondsToLive = this.ttl;
|
||||
var isValid = elapsedSeconds < secondsToLive;
|
||||
|
||||
if(isValid) {
|
||||
cb(null, isValid);
|
||||
} else {
|
||||
this.destroy(function(err) {
|
||||
cb(err, isValid);
|
||||
});
|
||||
}
|
||||
} catch(e) {
|
||||
cb(e);
|
||||
}
|
||||
}
|
||||
|
||||
function tokenIdForRequest(req, options) {
|
||||
var params = options.params || [];
|
||||
var headers = options.headers || [];
|
||||
var cookies = options.cookies || [];
|
||||
var i = 0;
|
||||
var length;
|
||||
var id;
|
||||
|
||||
params.push('access_token');
|
||||
headers.push('X-Access-Token');
|
||||
headers.push('authorization');
|
||||
cookies.push('access_token');
|
||||
cookies.push('authorization');
|
||||
|
||||
for(length = params.length; i < length; i++) {
|
||||
id = req.param(params[i]);
|
||||
|
||||
if(typeof id === 'string') {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 0, length = headers.length; i < length; i++) {
|
||||
id = req.header(headers[i]);
|
||||
|
||||
if(typeof id === 'string') {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 0, length = headers.length; i < length; i++) {
|
||||
id = req.signedCookies[cookies[i]];
|
||||
|
||||
if(typeof id === 'string') {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
exports.Model = require('./model');
|
||||
exports.Email = require('./email');
|
||||
exports.User = require('./user');
|
||||
exports.Session = require('./session');
|
||||
exports.AccessToken = require('./access-token');
|
||||
|
||||
exports.Application = require('./application');
|
||||
exports.ACL = require('./acl');
|
||||
|
|
|
@ -15,4 +15,4 @@ module.exports = function(dataSource) {
|
|||
dataSource = dataSource || new require('loopback-datasource-juggler').ModelBuilder();
|
||||
var Role = dataSource.define('Role', RoleSchema);
|
||||
return Role;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* Module Dependencies.
|
||||
*/
|
||||
|
||||
var Model = require('../loopback').Model
|
||||
, loopback = require('../loopback')
|
||||
, crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* Default Session properties.
|
||||
*/
|
||||
|
||||
var properties = {
|
||||
id: {type: String, generated: true, id: 1},
|
||||
uid: {type: String},
|
||||
ttl: {type: Number, ttl: true}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extends from the built in `loopback.Model` type.
|
||||
*/
|
||||
|
||||
var Session = module.exports = Model.extend('Session', properties);
|
||||
|
||||
/**
|
||||
* Create a cryptographically random session id.
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
Session.createSessionId = function (fn) {
|
||||
crypto.randomBytes(this.settings.sessionIdLength || 64, function(err, buf) {
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else {
|
||||
fn(null, buf.toString('base64'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*!
|
||||
* Hook to create session id.
|
||||
*/
|
||||
|
||||
Session.beforeCreate = function (next, data) {
|
||||
data = data || {};
|
||||
|
||||
Session.createSessionId(function (err, id) {
|
||||
if(err) {
|
||||
next(err);
|
||||
} else {
|
||||
data.id = id;
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
|
@ -9,7 +9,10 @@ var Model = require('../loopback').Model
|
|||
, crypto = require('crypto')
|
||||
, bcrypt = require('bcryptjs')
|
||||
, passport = require('passport')
|
||||
, LocalStrategy = require('passport-local').Strategy;
|
||||
, LocalStrategy = require('passport-local').Strategy
|
||||
, BaseAccessToken = require('./access-token')
|
||||
, DEFAULT_TTL = 1209600 // 2 weeks in seconds
|
||||
, DEFAULT_MAX_TTL = 31556926; // 1 year in seconds
|
||||
|
||||
/**
|
||||
* Default User properties.
|
||||
|
@ -50,8 +53,8 @@ var User = module.exports = Model.extend('User', properties);
|
|||
/**
|
||||
* Login a user by with the given `credentials`.
|
||||
*
|
||||
* User.login({username: 'foo', password: 'bar'}, function (err, session) {
|
||||
* console.log(session.id);
|
||||
* User.login({username: 'foo', password: 'bar'}, function (err, token) {
|
||||
* console.log(token.id);
|
||||
* });
|
||||
*
|
||||
* @param {Object} credentials
|
||||
|
@ -79,7 +82,9 @@ User.login = function (credentials, fn) {
|
|||
if(err) {
|
||||
fn(defaultError);
|
||||
} else if(isMatch) {
|
||||
createSession(user, fn);
|
||||
user.accessTokens.create({
|
||||
ttl: Math.min(credentials.ttl || User.settings.ttl, User.settings.maxTTL)
|
||||
}, fn);
|
||||
} else {
|
||||
fn(defaultError);
|
||||
}
|
||||
|
@ -88,42 +93,26 @@ User.login = function (credentials, fn) {
|
|||
fn(defaultError);
|
||||
}
|
||||
});
|
||||
|
||||
function createSession(user, fn) {
|
||||
var Session = UserCtor.session;
|
||||
|
||||
Session.create({uid: user.id}, function (err, session) {
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else {
|
||||
fn(null, session)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout a user with the given session id.
|
||||
* Logout a user with the given accessToken id.
|
||||
*
|
||||
* User.logout('asd0a9f8dsj9s0s3223mk', function (err) {
|
||||
* console.log(err || 'Logged out');
|
||||
* });
|
||||
*
|
||||
* @param {String} sessionID
|
||||
* @param {String} accessTokenID
|
||||
*/
|
||||
|
||||
User.logout = function (sid, fn) {
|
||||
var UserCtor = this;
|
||||
|
||||
var Session = UserCtor.settings.session || loopback.Session;
|
||||
|
||||
Session.findById(sid, function (err, session) {
|
||||
User.logout = function (tokenId, fn) {
|
||||
this.relations.accessTokens.modelTo.findById(tokenId, function (err, accessToken) {
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else if(session) {
|
||||
session.destroy(fn);
|
||||
} else if(accessToken) {
|
||||
accessToken.destroy(fn);
|
||||
} else {
|
||||
fn(new Error('could not find session'));
|
||||
fn(new Error('could not find accessToken'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -255,6 +244,10 @@ User.setup = function () {
|
|||
Model.setup.call(this);
|
||||
var UserModel = this;
|
||||
|
||||
// max ttl
|
||||
this.settings.maxTTL = this.settings.maxTTL || DEFAULT_MAX_TTL;
|
||||
this.settings.ttl = DEFAULT_TTL;
|
||||
|
||||
UserModel.setter.password = function (plain) {
|
||||
var salt = bcrypt.genSaltSync(this.constructor.settings.saltWorkFactor || SALT_WORK_FACTOR);
|
||||
this.$password = bcrypt.hashSync(plain, salt);
|
||||
|
@ -266,7 +259,7 @@ User.setup = function () {
|
|||
accepts: [
|
||||
{arg: 'credentials', type: 'object', required: true, http: {source: 'body'}}
|
||||
],
|
||||
returns: {arg: 'session', type: 'object', root: true},
|
||||
returns: {arg: 'accessToken', type: 'object', root: true},
|
||||
http: {verb: 'post'}
|
||||
}
|
||||
);
|
||||
|
@ -305,7 +298,7 @@ User.setup = function () {
|
|||
|
||||
// default models
|
||||
UserModel.email = require('./email');
|
||||
UserModel.session = require('./session');
|
||||
UserModel.accessToken = require('./access-token');
|
||||
|
||||
UserModel.validatesUniquenessOf('email', {message: 'Email already exists'});
|
||||
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
|
|
|
@ -28,7 +28,9 @@
|
|||
"nodemailer": "~0.4.4",
|
||||
"ejs": "~0.8.4",
|
||||
"bcryptjs": "~0.7.10",
|
||||
"underscore.string": "~2.3.3"
|
||||
"underscore.string": "~2.3.3",
|
||||
"underscore": "~1.5.2",
|
||||
"uid2": "0.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"blanket": "~1.1.5",
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
var loopback = require('../');
|
||||
var Token = loopback.AccessToken.extend('MyToken');
|
||||
|
||||
// attach Token to testing memory ds
|
||||
Token.attachTo(loopback.memory());
|
||||
|
||||
describe('loopback.token(options)', function() {
|
||||
beforeEach(createTestingToken);
|
||||
|
||||
it('should populate req.token from the query string', function (done) {
|
||||
createTestAppAndRequest(this.token, done)
|
||||
.get('/?access_token=' + this.token.id)
|
||||
.expect(200)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should populate req.token from a header', function (done) {
|
||||
createTestAppAndRequest(this.token, done)
|
||||
.get('/')
|
||||
.set('authorization', this.token.id)
|
||||
.expect(200)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
it('should populate req.token from a secure cookie', function (done) {
|
||||
var app = createTestApp(this.token, done);
|
||||
|
||||
request(app)
|
||||
.get('/token')
|
||||
.end(function(err, res) {
|
||||
request(app)
|
||||
.get('/')
|
||||
.set('Cookie', res.header['set-cookie'])
|
||||
.end(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('AccessToken', function () {
|
||||
beforeEach(createTestingToken);
|
||||
|
||||
it('should auto-generate id', function () {
|
||||
assert(this.token.id);
|
||||
assert.equal(this.token.id.length, 64);
|
||||
});
|
||||
|
||||
it('should auto-generate created date', function () {
|
||||
assert(this.token.created);
|
||||
assert(Object.prototype.toString.call(this.token.created), '[object Date]');
|
||||
});
|
||||
|
||||
it('should be validateable', function (done) {
|
||||
this.token.validate(function(err, isValid) {
|
||||
assert(isValid);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createTestingToken(done) {
|
||||
var test = this;
|
||||
Token.create({}, function (err, token) {
|
||||
if(err) return done(err);
|
||||
test.token = token;
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
function createTestAppAndRequest(testToken, done) {
|
||||
var app = createTestApp(testToken, done);
|
||||
return request(app);
|
||||
}
|
||||
|
||||
function createTestApp(testToken, done) {
|
||||
var app = loopback();
|
||||
|
||||
app.use(loopback.cookieParser('secret'));
|
||||
app.use(loopback.token({model: Token}));
|
||||
app.get('/token', function(req, res) {
|
||||
res.cookie('authorization', testToken.id, {signed: true});
|
||||
res.end();
|
||||
});
|
||||
app.get('/', function (req, res) {
|
||||
try {
|
||||
assert(req.accessToken, 'req should have accessToken');
|
||||
assert(req.accessToken.id === testToken.id);
|
||||
} catch(e) {
|
||||
return done(e);
|
||||
}
|
||||
res.send('ok');
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
var User = loopback.User.extend('user');
|
||||
var Session = loopback.Session;
|
||||
var AccessToken = loopback.AccessToken;
|
||||
var passport = require('passport');
|
||||
var MailConnector = require('../lib/connectors/mail');
|
||||
|
||||
|
@ -7,7 +7,6 @@ var userMemory = loopback.createDataSource({
|
|||
connector: loopback.Memory
|
||||
});
|
||||
|
||||
|
||||
describe('User', function(){
|
||||
|
||||
var mailDataSource = loopback.createDataSource({
|
||||
|
@ -15,7 +14,9 @@ describe('User', function(){
|
|||
transports: [{type: 'STUB'}]
|
||||
});
|
||||
User.attachTo(userMemory);
|
||||
User.session.attachTo(userMemory);
|
||||
AccessToken.attachTo(userMemory);
|
||||
// TODO(ritch) - this should be a default relationship
|
||||
User.hasMany(AccessToken, {as: 'accessTokens', foreignKey: 'userId'});
|
||||
User.email.attachTo(mailDataSource);
|
||||
|
||||
// allow many User.afterRemote's to be called
|
||||
|
@ -30,7 +31,7 @@ describe('User', function(){
|
|||
|
||||
afterEach(function (done) {
|
||||
User.destroyAll(function (err) {
|
||||
User.session.destroyAll(done);
|
||||
User.accessToken.destroyAll(done);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -84,8 +85,8 @@ describe('User', function(){
|
|||
|
||||
it('Requires a password to login with basic auth', function(done) {
|
||||
User.create({email: 'b@c.com'}, function (err) {
|
||||
User.login({email: 'b@c.com'}, function (err, session) {
|
||||
assert(!session, 'should not create a session without a valid password');
|
||||
User.login({email: 'b@c.com'}, function (err, accessToken) {
|
||||
assert(!accessToken, 'should not create a accessToken without a valid password');
|
||||
assert(err, 'should not login without a password');
|
||||
done();
|
||||
});
|
||||
|
@ -100,10 +101,10 @@ describe('User', function(){
|
|||
|
||||
describe('User.login', function() {
|
||||
it('Login a user by providing credentials', function(done) {
|
||||
User.login({email: 'foo@bar.com', password: 'bar'}, function (err, session) {
|
||||
assert(session.uid);
|
||||
assert(session.id);
|
||||
assert.equal((new Buffer(session.id, 'base64')).length, 64);
|
||||
User.login({email: 'foo@bar.com', password: 'bar'}, function (err, accessToken) {
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
|
||||
done();
|
||||
});
|
||||
|
@ -117,11 +118,11 @@ describe('User', function(){
|
|||
.send({email: 'foo@bar.com', password: 'bar'})
|
||||
.end(function(err, res){
|
||||
if(err) return done(err);
|
||||
var session = res.body;
|
||||
var accessToken = res.body;
|
||||
|
||||
assert(session.uid);
|
||||
assert(session.id);
|
||||
assert.equal((new Buffer(session.id, 'base64')).length, 64);
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
|
||||
done();
|
||||
});
|
||||
|
@ -129,9 +130,9 @@ describe('User', function(){
|
|||
|
||||
it('Login should only allow correct credentials', function(done) {
|
||||
User.create({email: 'foo22@bar.com', password: 'bar'}, function(user, err) {
|
||||
User.login({email: 'foo44@bar.com', password: 'bar'}, function(err, session) {
|
||||
User.login({email: 'foo44@bar.com', password: 'bar'}, function(err, accessToken) {
|
||||
assert(err);
|
||||
assert(!session);
|
||||
assert(!accessToken);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -139,19 +140,19 @@ describe('User', function(){
|
|||
});
|
||||
|
||||
describe('User.logout', function() {
|
||||
it('Logout a user by providing the current session id (using node)', function(done) {
|
||||
it('Logout a user by providing the current accessToken id (using node)', function(done) {
|
||||
login(logout);
|
||||
|
||||
function login(fn) {
|
||||
User.login({email: 'foo@bar.com', password: 'bar'}, fn);
|
||||
}
|
||||
|
||||
function logout(err, session) {
|
||||
User.logout(session.id, verify(session.id, done));
|
||||
function logout(err, accessToken) {
|
||||
User.logout(accessToken.id, verify(accessToken.id, done));
|
||||
}
|
||||
});
|
||||
|
||||
it('Logout a user by providing the current session id (over rest)', function(done) {
|
||||
it('Logout a user by providing the current accessToken id (over rest)', function(done) {
|
||||
login(logout);
|
||||
|
||||
function login(fn) {
|
||||
|
@ -162,12 +163,12 @@ describe('User', function(){
|
|||
.send({email: 'foo@bar.com', password: 'bar'})
|
||||
.end(function(err, res){
|
||||
if(err) return done(err);
|
||||
var session = res.body;
|
||||
var accessToken = res.body;
|
||||
|
||||
assert(session.uid);
|
||||
assert(session.id);
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
|
||||
fn(null, session.id);
|
||||
fn(null, accessToken.id);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -186,8 +187,8 @@ describe('User', function(){
|
|||
return function (err) {
|
||||
if(err) return done(err);
|
||||
|
||||
Session.findById(sid, function (err, session) {
|
||||
assert(!session, 'session should not exist after logging out');
|
||||
AccessToken.findById(sid, function (err, accessToken) {
|
||||
assert(!accessToken, 'accessToken should not exist after logging out');
|
||||
done(err);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue