Update AccessToken and User relationship
- Add created default - Default TTLs for user login access tokens - Break out User / AccessToken relationship
This commit is contained in:
parent
efce5039f6
commit
1de2a40e88
|
@ -4,8 +4,10 @@
|
|||
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -14,9 +16,10 @@ var Model = require('../loopback').Model
|
|||
|
||||
var properties = {
|
||||
id: {type: String, generated: true, id: 1},
|
||||
uid: {type: String},
|
||||
ttl: {type: Number, ttl: true}, // time to live in seconds
|
||||
created: {type: Date}
|
||||
ttl: {type: Number, ttl: true, default: DEFAULT_TTL}, // time to live in seconds
|
||||
created: {type: Date, default: function() {
|
||||
return new Date();
|
||||
}}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -71,7 +74,21 @@ AccessToken.findForRequest = function(req, options, cb) {
|
|||
var id = tokenIdForRequest(req, options);
|
||||
|
||||
if(id) {
|
||||
this.findById(id, cb);
|
||||
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();
|
||||
|
@ -79,6 +96,34 @@ AccessToken.findForRequest = function(req, options, 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 || [];
|
||||
|
|
|
@ -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.
|
||||
|
@ -79,7 +82,9 @@ User.login = function (credentials, fn) {
|
|||
if(err) {
|
||||
fn(defaultError);
|
||||
} else if(isMatch) {
|
||||
createAccessToken(user, fn);
|
||||
user.accessTokens.create({
|
||||
ttl: Math.min(credentials.ttl || User.settings.ttl, User.settings.maxTTL)
|
||||
}, fn);
|
||||
} else {
|
||||
fn(defaultError);
|
||||
}
|
||||
|
@ -88,18 +93,6 @@ User.login = function (credentials, fn) {
|
|||
fn(defaultError);
|
||||
}
|
||||
});
|
||||
|
||||
function createAccessToken(user, fn) {
|
||||
var AccessToken = UserCtor.accessToken;
|
||||
|
||||
AccessToken.create({uid: user.id}, function (err, accessToken) {
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else {
|
||||
fn(null, accessToken)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,12 +105,8 @@ User.login = function (credentials, fn) {
|
|||
* @param {String} accessTokenID
|
||||
*/
|
||||
|
||||
User.logout = function (sid, fn) {
|
||||
var UserCtor = this;
|
||||
|
||||
var AccessToken = UserCtor.settings.accessToken || loopback.AccessToken;
|
||||
|
||||
AccessToken.findById(sid, function (err, accessToken) {
|
||||
User.logout = function (tokenId, fn) {
|
||||
this.relations.accessTokens.modelTo.findById(tokenId, function (err, accessToken) {
|
||||
if(err) {
|
||||
fn(err);
|
||||
} else if(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);
|
||||
|
|
|
@ -36,6 +36,27 @@ describe('loopback.token(options)', function() {
|
|||
});
|
||||
});
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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.accessToken.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
|
||||
|
@ -101,7 +102,7 @@ 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, accessToken) {
|
||||
assert(accessToken.uid);
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
|
||||
|
@ -119,7 +120,7 @@ describe('User', function(){
|
|||
if(err) return done(err);
|
||||
var accessToken = res.body;
|
||||
|
||||
assert(accessToken.uid);
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
assert.equal(accessToken.id.length, 64);
|
||||
|
||||
|
@ -164,7 +165,7 @@ describe('User', function(){
|
|||
if(err) return done(err);
|
||||
var accessToken = res.body;
|
||||
|
||||
assert(accessToken.uid);
|
||||
assert(accessToken.userId);
|
||||
assert(accessToken.id);
|
||||
|
||||
fn(null, accessToken.id);
|
||||
|
|
Loading…
Reference in New Issue