mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-29 13:52:10 +03:00
Server side cleanup
- remove sessions - remove all references to csrf - create a shared base model for the 2 types of token
This commit is contained in:
parent
4c6b324494
commit
3ff9146d9e
@ -1,104 +0,0 @@
|
||||
var Store = require('express-session').Store,
|
||||
models = require('./models'),
|
||||
time12h = 12 * 60 * 60 * 1000,
|
||||
|
||||
BSStore;
|
||||
|
||||
// Initialize store and clean old sessions
|
||||
BSStore = function BSStore(options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
Store.call(this, options);
|
||||
|
||||
models.Session.findAll()
|
||||
.then(function (model) {
|
||||
var i,
|
||||
now = new Date().getTime();
|
||||
for (i = 0; i < model.length; i = i + 1) {
|
||||
if (now > model.at(i).get('expires')) {
|
||||
self.destroy(model.at(i).get('id'));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
BSStore.prototype = new Store();
|
||||
|
||||
// store a given session
|
||||
BSStore.prototype.set = function (sid, sessData, callback) {
|
||||
var maxAge = sessData.cookie.maxAge,
|
||||
now = new Date().getTime(),
|
||||
expires = maxAge ? now + maxAge : now + time12h,
|
||||
sessionModel = models.Session;
|
||||
|
||||
sessData = JSON.stringify(sessData);
|
||||
|
||||
//necessary since bookshelf updates models if id is set
|
||||
sessionModel.forge({id: sid}).fetch()
|
||||
.then(function (model) {
|
||||
if (model) {
|
||||
return sessionModel.forge({id: sid, expires: expires, sess: sessData }).save();
|
||||
}
|
||||
return sessionModel.forge({id: sid, expires: expires, sess: sessData })
|
||||
.save(null, {method: 'insert'});
|
||||
}).then(function () {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
// fetch a session, if session is expired delete it
|
||||
BSStore.prototype.get = function (sid, callback) {
|
||||
var now = new Date().getTime(),
|
||||
self = this,
|
||||
sess,
|
||||
expires;
|
||||
|
||||
models.Session.forge({id: sid})
|
||||
.fetch()
|
||||
.then(function (model) {
|
||||
if (model) {
|
||||
sess = JSON.parse(model.get('sess'));
|
||||
expires = model.get('expires');
|
||||
if (now < expires) {
|
||||
callback(null, sess);
|
||||
} else {
|
||||
self.destroy(sid, callback);
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// delete a given sessions
|
||||
BSStore.prototype.destroy = function (sid, callback) {
|
||||
models.Session.forge({id: sid})
|
||||
.destroy()
|
||||
.then(function () {
|
||||
// check if callback is null
|
||||
// session.regenerate doesn't provide callback
|
||||
// cleanup at startup does neither
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// get the count of all stored sessions
|
||||
BSStore.prototype.length = function (callback) {
|
||||
models.Session.findAll()
|
||||
.then(function (model) {
|
||||
callback(null, model.length);
|
||||
});
|
||||
};
|
||||
|
||||
// delete all sessions
|
||||
BSStore.prototype.clear = function (callback) {
|
||||
models.Session.destroyAll()
|
||||
.then(function () {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports = BSStore;
|
@ -4,7 +4,7 @@ var _ = require('lodash'),
|
||||
config = require('../../config'),
|
||||
utils = require('../utils'),
|
||||
|
||||
excludedTables = ['sessions'],
|
||||
excludedTables = [],
|
||||
exporter;
|
||||
|
||||
exporter = function () {
|
||||
|
@ -25,6 +25,8 @@ var api = require('../api'),
|
||||
authStrategies = require('./authStrategies'),
|
||||
|
||||
expressServer,
|
||||
setupMiddleware,
|
||||
|
||||
ONE_HOUR_S = 60 * 60,
|
||||
ONE_YEAR_S = 365 * 24 * ONE_HOUR_S,
|
||||
ONE_HOUR_MS = ONE_HOUR_S * 1000,
|
||||
@ -221,7 +223,7 @@ function robots() {
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = function (server) {
|
||||
setupMiddleware = function (server) {
|
||||
var logging = config().logging,
|
||||
subdir = config().paths.subdir,
|
||||
corePath = config().paths.corePath,
|
||||
@ -319,6 +321,7 @@ module.exports = function (server) {
|
||||
expressServer.use(errors.error500);
|
||||
};
|
||||
|
||||
module.exports = setupMiddleware;
|
||||
// Export middleware functions directly
|
||||
module.exports.middleware = middleware;
|
||||
// Expose middleware functions in this file as well
|
||||
|
@ -3,7 +3,6 @@
|
||||
// middleware_spec.js
|
||||
|
||||
var _ = require('lodash'),
|
||||
csrf = require('csurf'),
|
||||
express = require('express'),
|
||||
busboy = require('./ghost-busboy'),
|
||||
config = require('../config'),
|
||||
@ -78,16 +77,6 @@ var middleware = {
|
||||
next();
|
||||
},
|
||||
|
||||
// Check if we're logged in, and if so, redirect people back to dashboard
|
||||
// Login and signup forms in particular
|
||||
redirectToDashboard: function (req, res, next) {
|
||||
if (req.user && req.user.id) {
|
||||
return res.redirect(config().paths.subdir + '/ghost/');
|
||||
}
|
||||
|
||||
next();
|
||||
},
|
||||
|
||||
// While we're here, let's clean up on aisle 5
|
||||
// That being ghost.notifications, and let's remove the passives from there
|
||||
// plus the local messages, as they have already been added at this point
|
||||
@ -160,15 +149,6 @@ var middleware = {
|
||||
});
|
||||
},
|
||||
|
||||
conditionalCSRF: function (req, res, next) {
|
||||
// CSRF is needed for admin only
|
||||
if (res.isAdmin) {
|
||||
csrf()(req, res, next);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
},
|
||||
|
||||
// work around to handle missing client_secret
|
||||
// oauth2orize needs it, but untrusted clients don't have it
|
||||
addClientSecret: function (req, res, next) {
|
||||
|
@ -1,44 +1,11 @@
|
||||
var ghostBookshelf = require('./base'),
|
||||
Basetoken = require('./basetoken'),
|
||||
|
||||
Accesstoken,
|
||||
Accesstokens;
|
||||
|
||||
Accesstoken = ghostBookshelf.Model.extend({
|
||||
|
||||
tableName: 'accesstokens',
|
||||
|
||||
user: function () {
|
||||
return this.belongsTo('User');
|
||||
},
|
||||
|
||||
client: function () {
|
||||
return this.belongsTo('Client');
|
||||
},
|
||||
|
||||
// override for base function since we don't have
|
||||
// a created_by field for sessions
|
||||
creating: function (newObj, attr, options) {
|
||||
/*jshint unused:false*/
|
||||
},
|
||||
|
||||
// override for base function since we don't have
|
||||
// a updated_by field for sessions
|
||||
saving: function (newObj, attr, options) {
|
||||
/*jshint unused:false*/
|
||||
// Remove any properties which don't belong on the model
|
||||
this.attributes = this.pick(this.permittedAttributes());
|
||||
}
|
||||
|
||||
}, {
|
||||
destroyAllExpired: function (options) {
|
||||
options = this.filterOptions(options, 'destroyAll');
|
||||
return ghostBookshelf.Collection.forge([], {model: this})
|
||||
.query('where', 'expires', '<', Date.now())
|
||||
.fetch()
|
||||
.then(function (collection) {
|
||||
collection.invokeThen('destroy', options);
|
||||
});
|
||||
}
|
||||
Accesstoken = Basetoken.extend({
|
||||
tableName: 'accesstokens'
|
||||
});
|
||||
|
||||
Accesstokens = ghostBookshelf.Collection.extend({
|
||||
|
@ -1,11 +1,16 @@
|
||||
var ghostBookshelf = require('./base'),
|
||||
|
||||
Session,
|
||||
Sessions;
|
||||
Basetoken;
|
||||
|
||||
Session = ghostBookshelf.Model.extend({
|
||||
Basetoken = ghostBookshelf.Model.extend({
|
||||
|
||||
tableName: 'sessions',
|
||||
user: function () {
|
||||
return this.belongsTo('User');
|
||||
},
|
||||
|
||||
client: function () {
|
||||
return this.belongsTo('Client');
|
||||
},
|
||||
|
||||
// override for base function since we don't have
|
||||
// a created_by field for sessions
|
||||
@ -22,20 +27,15 @@ Session = ghostBookshelf.Model.extend({
|
||||
}
|
||||
|
||||
}, {
|
||||
destroyAll: function (options) {
|
||||
destroyAllExpired: function (options) {
|
||||
options = this.filterOptions(options, 'destroyAll');
|
||||
return ghostBookshelf.Collection.forge([], {model: this}).fetch()
|
||||
return ghostBookshelf.Collection.forge([], {model: this})
|
||||
.query('where', 'expires', '<', Date.now())
|
||||
.fetch()
|
||||
.then(function (collection) {
|
||||
collection.invokeThen('destroy', options);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Sessions = ghostBookshelf.Collection.extend({
|
||||
model: Session
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
Session: ghostBookshelf.model('Session', Session),
|
||||
Sessions: ghostBookshelf.collection('Sessions', Sessions)
|
||||
};
|
||||
module.exports = Basetoken;
|
@ -11,7 +11,6 @@ models = {
|
||||
Settings: require('./settings').Settings,
|
||||
Tag: require('./tag').Tag,
|
||||
Base: require('./base'),
|
||||
Session: require('./session').Session,
|
||||
App: require('./app').App,
|
||||
AppField: require('./appField').AppField,
|
||||
AppSetting: require('./appSetting').AppSetting,
|
||||
|
@ -1,44 +1,11 @@
|
||||
var ghostBookshelf = require('./base'),
|
||||
Basetoken = require('./basetoken'),
|
||||
|
||||
Refreshtoken,
|
||||
Refreshtokens;
|
||||
|
||||
Refreshtoken = ghostBookshelf.Model.extend({
|
||||
|
||||
tableName: 'refreshtokens',
|
||||
|
||||
user: function () {
|
||||
return this.belongsTo('User');
|
||||
},
|
||||
|
||||
client: function () {
|
||||
return this.belongsTo('Client');
|
||||
},
|
||||
|
||||
// override for base function since we don't have
|
||||
// a created_by field for sessions
|
||||
creating: function (newObj, attr, options) {
|
||||
/*jshint unused:false*/
|
||||
},
|
||||
|
||||
// override for base function since we don't have
|
||||
// a updated_by field for sessions
|
||||
saving: function (newObj, attr, options) {
|
||||
/*jshint unused:false*/
|
||||
// Remove any properties which don't belong on the model
|
||||
this.attributes = this.pick(this.permittedAttributes());
|
||||
}
|
||||
|
||||
}, {
|
||||
destroyAllExpired: function (options) {
|
||||
options = this.filterOptions(options, 'destroyAll');
|
||||
return ghostBookshelf.Collection.forge([], {model: this})
|
||||
.query('where', 'expires', '<', Date.now())
|
||||
.fetch()
|
||||
.then(function (collection) {
|
||||
collection.invokeThen('destroy', options);
|
||||
});
|
||||
}
|
||||
Refreshtoken = Basetoken.extend({
|
||||
tableName: 'refreshtokens'
|
||||
});
|
||||
|
||||
Refreshtokens = ghostBookshelf.Collection.extend({
|
||||
|
@ -12,22 +12,12 @@ adminRoutes = function (middleware) {
|
||||
subdir = config().paths.subdir;
|
||||
|
||||
// ### Admin routes
|
||||
router.get('^/logout/', function redirect(req, res) {
|
||||
router.get(/^\/(logout|signout)\/$/, function redirect(req, res) {
|
||||
/*jslint unparam:true*/
|
||||
res.set({'Cache-Control': 'public, max-age=' + ONE_YEAR_S});
|
||||
res.redirect(301, subdir + '/ghost/signout/');
|
||||
});
|
||||
router.get('^/signout/', function redirect(req, res) {
|
||||
/*jslint unparam:true*/
|
||||
res.set({'Cache-Control': 'public, max-age=' + ONE_YEAR_S});
|
||||
res.redirect(301, subdir + '/ghost/signout/');
|
||||
});
|
||||
router.get('^/signin/', function redirect(req, res) {
|
||||
/*jslint unparam:true*/
|
||||
res.set({'Cache-Control': 'public, max-age=' + ONE_YEAR_S});
|
||||
res.redirect(301, subdir + '/ghost/signin/');
|
||||
});
|
||||
router.get('^/signup/', function redirect(req, res) {
|
||||
router.get(/^\/signup\/$/, function redirect(req, res) {
|
||||
/*jslint unparam:true*/
|
||||
res.set({'Cache-Control': 'public, max-age=' + ONE_YEAR_S});
|
||||
res.redirect(301, subdir + '/ghost/signup/');
|
||||
@ -36,7 +26,7 @@ adminRoutes = function (middleware) {
|
||||
router.post('/ghost/upload/', middleware.busboy, admin.upload);
|
||||
|
||||
// redirect to /ghost and let that do the authentication to prevent redirects to /ghost//admin etc.
|
||||
router.get(/^\/((ghost-admin|admin|wp-admin|dashboard|signin)\/?)$/, function (req, res) {
|
||||
router.get(/^\/((ghost-admin|admin|wp-admin|dashboard|signin|login)\/?)$/, function (req, res) {
|
||||
/*jslint unparam:true*/
|
||||
res.redirect(subdir + '/ghost/');
|
||||
});
|
||||
|
@ -1,3 +1,6 @@
|
||||
var utils,
|
||||
getRandomInt;
|
||||
|
||||
/**
|
||||
* Return a random int, used by `utils.uid()`
|
||||
*
|
||||
@ -6,11 +9,11 @@
|
||||
* @return {Number}
|
||||
* @api private
|
||||
*/
|
||||
function getRandomInt(min, max) {
|
||||
getRandomInt = function (min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
};
|
||||
|
||||
var utils = {
|
||||
utils = {
|
||||
/**
|
||||
* Return a unique identifier with the given `len`.
|
||||
*
|
||||
|
@ -4,7 +4,6 @@
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html" charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="csrf-param" content="{{csrfToken}}" />
|
||||
|
||||
<title>Ghost Admin</title>
|
||||
|
||||
|
@ -92,14 +92,6 @@ describe('Admin Routing', function () {
|
||||
.end(doEndNoAuth(done));
|
||||
});
|
||||
|
||||
it('should redirect /signin/ to /ghost/signin/', function (done) {
|
||||
request.get('/signin/')
|
||||
.expect('Location', '/ghost/signin/')
|
||||
.expect('Cache-Control', cacheRules.year)
|
||||
.expect(301)
|
||||
.end(doEndNoAuth(done));
|
||||
});
|
||||
|
||||
it('should redirect /signup/ to /ghost/signup/', function (done) {
|
||||
request.get('/signup/')
|
||||
.expect('Location', '/ghost/signup/')
|
||||
@ -107,6 +99,24 @@ describe('Admin Routing', function () {
|
||||
.expect(301)
|
||||
.end(doEndNoAuth(done));
|
||||
});
|
||||
|
||||
// Admin aliases
|
||||
it('should redirect /signin/ to /ghost/', function (done) {
|
||||
request.get('/signin/')
|
||||
.expect('Location', '/ghost/')
|
||||
.expect('Cache-Control', cacheRules.public)
|
||||
.expect(302)
|
||||
.end(doEndNoAuth(done));
|
||||
});
|
||||
|
||||
it('should redirect /admin/ to /ghost/', function (done) {
|
||||
request.get('/admin/')
|
||||
.expect('Location', '/ghost/')
|
||||
.expect('Cache-Control', cacheRules.public)
|
||||
.expect(302)
|
||||
.end(doEndNoAuth(done));
|
||||
});
|
||||
// there are more of these... but we get the point
|
||||
});
|
||||
|
||||
// we'll use X-Forwarded-Proto: https to simulate an 'https://' request behind a proxy
|
||||
@ -184,7 +194,6 @@ describe('Admin Routing', function () {
|
||||
});
|
||||
|
||||
describe('Ghost Admin Setup', function () {
|
||||
|
||||
it('should redirect from /ghost/ to /ghost/setup/ when no user/not installed yet', function (done) {
|
||||
request.get('/ghost/')
|
||||
.expect('Location', /ghost\/setup/)
|
||||
@ -230,9 +239,22 @@ describe('Admin Routing', function () {
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
// TODO: new test for Ember where user is added
|
||||
//
|
||||
// describe('Ghost Admin Forgot Password', function () {
|
||||
// before(function (done) {
|
||||
// // Create a user / do setup etc
|
||||
// testUtils.clearData()
|
||||
// .then(function () {
|
||||
// return testUtils.initData();
|
||||
// })
|
||||
// .then(function () {
|
||||
// return testUtils.insertDefaultFixtures();
|
||||
// }).then(function () {
|
||||
// done();
|
||||
// })
|
||||
// .catch(done);
|
||||
// });
|
||||
//
|
||||
// it('should respond with html for /ghost/forgotten/', function (done) {
|
||||
// request.get('/ghost/forgotten/')
|
||||
// .expect('Content-Type', /html/)
|
||||
@ -240,7 +262,7 @@ describe('Admin Routing', function () {
|
||||
// .expect(200)
|
||||
// .end(doEnd(done));
|
||||
// });
|
||||
|
||||
//
|
||||
// it('should respond 404 for /ghost/reset/', function (done) {
|
||||
// request.get('/ghost/reset/')
|
||||
// .expect('Cache-Control', cacheRules['private'])
|
||||
@ -248,7 +270,7 @@ describe('Admin Routing', function () {
|
||||
// .expect(/Page Not Found/)
|
||||
// .end(doEnd(done));
|
||||
// });
|
||||
|
||||
//
|
||||
// it('should redirect /ghost/reset/*/', function (done) {
|
||||
// request.get('/ghost/reset/athing/')
|
||||
// .expect('Location', /ghost\/forgotten/)
|
||||
@ -261,8 +283,7 @@ describe('Admin Routing', function () {
|
||||
|
||||
// TODO: not working anymore, needs new test for Ember
|
||||
// describe('Authenticated Admin Routing', function () {
|
||||
// var user = testUtils.DataGenerator.forModel.users[0],
|
||||
// csrfToken = '';
|
||||
// var user = testUtils.DataGenerator.forModel.users[0];
|
||||
|
||||
// before(function (done) {
|
||||
// var app = express();
|
||||
@ -287,13 +308,9 @@ describe('Admin Routing', function () {
|
||||
// return done(err);
|
||||
// }
|
||||
|
||||
// var pattern_meta = /<meta.*?name="csrf-param".*?content="(.*?)".*?>/i;
|
||||
// pattern_meta.should.exist;
|
||||
// csrfToken = res.text.match(pattern_meta)[1];
|
||||
|
||||
// process.nextTick(function() {
|
||||
// request.post('/ghost/signin/')
|
||||
// .set('X-CSRF-Token', csrfToken)
|
||||
// .send({email: user.email, password: user.password})
|
||||
// .expect(200)
|
||||
// .end(function (err, res) {
|
||||
@ -309,7 +326,6 @@ describe('Admin Routing', function () {
|
||||
// return done(err);
|
||||
// }
|
||||
|
||||
// csrfToken = res.text.match(pattern_meta)[1];
|
||||
// done();
|
||||
// });
|
||||
// });
|
||||
|
@ -40,7 +40,6 @@
|
||||
"cookie-parser": "1.0.1",
|
||||
"connect": "3.0.0-rc.1",
|
||||
"connect-slashes": "1.2.0",
|
||||
"csurf": "1.1.0",
|
||||
"downsize": "0.0.5",
|
||||
"express": "4.1.1",
|
||||
"express-hbs": "0.7.10",
|
||||
|
Loading…
Reference in New Issue
Block a user