diff --git a/core/client/assets/lib/uploader.js b/core/client/assets/lib/uploader.js index ee8fde8b9e..aecd95134b 100644 --- a/core/client/assets/lib/uploader.js +++ b/core/client/assets/lib/uploader.js @@ -64,6 +64,9 @@ $dropzone.find('.js-fileupload').fileupload().fileupload("option", { url: '/ghost/upload/', + headers: { + 'X-CSRF-Token': $("meta[name='csrf-param']").attr('content') + }, add: function (e, data) { $dropzone.find('.js-fileupload').removeClass('right'); $dropzone.find('.js-url, button.centre').remove(); diff --git a/core/client/init.js b/core/client/init.js index 77ca1043e9..5f1ab7a825 100644 --- a/core/client/init.js +++ b/core/client/init.js @@ -23,6 +23,16 @@ _.extend(Ghost, Backbone.Events); + Backbone.oldsync = Backbone.sync; + // override original sync method to make header request contain csrf token + Backbone.sync = function (method, model, options, error) { + options.beforeSend = function (xhr) { + xhr.setRequestHeader('X-CSRF-Token', $("meta[name='csrf-param']").attr('content')); + }; + /* call the old sync method */ + return Backbone.oldsync(method, model, options, error); + }; + Ghost.init = function () { Ghost.router = new Ghost.Router(); diff --git a/core/client/views/base.js b/core/client/views/base.js index f530000d59..56f9a70a4d 100644 --- a/core/client/views/base.js +++ b/core/client/views/base.js @@ -202,6 +202,9 @@ if (self.className.indexOf('notification-persistent') !== -1) { $.ajax({ type: "DELETE", + headers: { + 'X-CSRF-Token': $("meta[name='csrf-param']").attr('content') + }, url: '/api/v0.1/notifications/' + $(self).find('.close').data('id') }).done(function (result) { bbSelf.$el.slideUp(250, function () { @@ -231,6 +234,9 @@ bbSelf = this; $.ajax({ type: "DELETE", + headers: { + 'X-CSRF-Token': $("meta[name='csrf-param']").attr('content') + }, url: '/api/v0.1/notifications/' + $(self).data('id') }).done(function (result) { var height = bbSelf.$('.js-notification').outerHeight(true), diff --git a/core/client/views/blog.js b/core/client/views/blog.js index 9818ce2ae9..280f05bed0 100644 --- a/core/client/views/blog.js +++ b/core/client/views/blog.js @@ -46,7 +46,7 @@ return Backbone.trigger('blog:activeItem', null); } - var id = this.collection.at(0).id; + var id = this.collection.at(0) ? this.collection.at(0).id : false; if (id) { Backbone.trigger('blog:activeItem', id); } @@ -87,6 +87,8 @@ // Load moar posts! this.isLoading = true; this.collection.fetch({ + update: true, + remove: false, data: { status: 'all', page: (self.collection.currentPage + 1), diff --git a/core/client/views/login.js b/core/client/views/login.js index 9cbdbce012..e16a5531db 100644 --- a/core/client/views/login.js +++ b/core/client/views/login.js @@ -33,6 +33,9 @@ $.ajax({ url: '/ghost/signin/', type: 'POST', + headers: { + 'X-CSRF-Token': $("meta[name='csrf-param']").attr('content') + }, data: { email: email, password: password, @@ -87,6 +90,9 @@ $.ajax({ url: '/ghost/signup/', type: 'POST', + headers: { + 'X-CSRF-Token': $("meta[name='csrf-param']").attr('content') + }, data: { name: name, email: email, @@ -136,6 +142,9 @@ $.ajax({ url: '/ghost/forgotten/', type: 'POST', + headers: { + 'X-CSRF-Token': $("meta[name='csrf-param']").attr('content') + }, data: { email: email }, diff --git a/core/client/views/settings.js b/core/client/views/settings.js index 33e5152fd8..f9040e0bab 100644 --- a/core/client/views/settings.js +++ b/core/client/views/settings.js @@ -204,8 +204,8 @@ var self = this, upload = new Ghost.Models.uploadModal({'key': key, 'src': src, 'id': this.id, 'accept': { func: function () { // The function called on acceptance var data = {}; - if (this.$('#uploadurl').val()) { - data[key] = this.$('#uploadurl').val(); + if (this.$('.js-upload-url').val()) { + data[key] = this.$('.js-upload-url').val(); } else { data[key] = this.$('.js-upload-target').attr('src'); } @@ -266,8 +266,8 @@ var self = this, upload = new Ghost.Models.uploadModal({'key': key, 'src': src, 'id': this.id, 'accept': { func: function () { // The function called on acceptance var data = {}; - if (this.$('#uploadurl').val()) { - data[key] = this.$('#uploadurl').val(); + if (this.$('.js-upload-url').val()) { + data[key] = this.$('.js-upload-url').val(); } else { data[key] = this.$('.js-upload-target').attr('src'); } @@ -379,6 +379,9 @@ $.ajax({ url: '/ghost/changepw/', type: 'POST', + headers: { + 'X-CSRF-Token': $("meta[name='csrf-param']").attr('content') + }, data: { password: oldPassword, newpassword: newPassword, diff --git a/core/server.js b/core/server.js index 620473aa39..d74f56a0b0 100644 --- a/core/server.js +++ b/core/server.js @@ -111,6 +111,7 @@ function ghostLocals(req, res, next) { res.locals = res.locals || {}; res.locals.version = packageInfo.version; res.locals.path = req.path; + res.locals.csrfToken = req.session._csrf; if (res.isAdmin) { _.extend(res.locals, { @@ -120,9 +121,9 @@ function ghostLocals(req, res, next) { api.users.read({id: req.session.user}).then(function (currentUser) { _.extend(res.locals, { currentUser: { - name: currentUser.attributes.name, - email: currentUser.attributes.email, - image: currentUser.attributes.image + name: currentUser.name, + email: currentUser.email, + image: currentUser.image } }); next(); @@ -277,9 +278,16 @@ when(ghost.init()).then(function () { server.use('/ghost/upload/', express.multipart()); server.use('/ghost/upload/', express.multipart({uploadDir: __dirname + '/content/images'})); server.use('/ghost/debug/db/import/', express.multipart()); - server.use(express.cookieParser(ghost.dbHash)); - server.use(express.cookieSession({ cookie: { maxAge: 60000000 }})); + // Session handling + // Pro tip: while in development mode cookieSession can be used + // to keep you logged in while restarting the server + server.use(express.cookieParser(ghost.dbHash)); + server.use(express.cookieSession({ cookie : { maxAge: 12 * 60 * 60 * 1000 }})); + + + //enable express csrf protection + server.use(express.csrf()); // local data server.use(ghostLocals); // So on every request we actually clean out reduntant passive notifications from the server side @@ -296,10 +304,10 @@ when(ghost.init()).then(function () { // ### Error handling // 404 Handler - server.use(errors.render404Page); + server.use(errors.error404); // 500 Handler - server.use(errors.render500Page); + server.use(errors.error500); // ## Routing @@ -349,9 +357,8 @@ when(ghost.init()).then(function () { server.get('/ghost/debug/', auth, admin.debug.index); server.get('/ghost/debug/db/export/', auth, admin.debug['export']); server.post('/ghost/debug/db/import/', auth, admin.debug['import']); - server.get('/ghost/debug/db/reset/', auth, admin.debug.reset); // We don't want to register bodyParser globally b/c of security concerns, so use multipart only here - server.post('/ghost/upload/', admin.uploader); + server.post('/ghost/upload/', auth, admin.uploader); // redirect to /ghost and let that do the authentication to prevent redirects to /ghost//admin etc. server.get(/^\/((ghost-admin|admin|wp-admin|dashboard|signin)\/?)/, function (req, res) { res.redirect('/ghost/'); diff --git a/core/server/api.js b/core/server/api.js index e99eb00d26..502a90c570 100644 --- a/core/server/api.js +++ b/core/server/api.js @@ -18,7 +18,8 @@ var Ghost = require('../ghost'), requestHandler, settingsObject, settingsCollection, - settingsFilter; + settingsFilter, + filteredUserAttributes = ['password', 'created_by', 'updated_by']; // ## Posts posts = { @@ -27,7 +28,17 @@ posts = { // **takes:** filter / pagination parameters browse: function browse(options) { // **returns:** a promise for a page of posts in a json object - return dataProvider.Post.findPage(options); + //return dataProvider.Post.findPage(options); + return dataProvider.Post.findPage(options).then(function (result) { + var i = 0, + omitted = result; + + for (i = 0; i < omitted.posts.length; i = i + 1) { + omitted.posts[i].author = _.omit(omitted.posts[i].author, filteredUserAttributes); + omitted.posts[i].user = _.omit(omitted.posts[i].user, filteredUserAttributes); + } + return omitted; + }); }, // #### Read @@ -35,7 +46,19 @@ posts = { // **takes:** an identifier (id or slug?) read: function read(args) { // **returns:** a promise for a single post in a json object - return dataProvider.Post.findOne(args); + + return dataProvider.Post.findOne(args).then(function (result) { + var omitted; + + if (result) { + omitted = result.toJSON(); + omitted.author = _.omit(omitted.author, filteredUserAttributes); + omitted.user = _.omit(omitted.user, filteredUserAttributes); + return omitted; + } + + return null; + }); }, // #### Edit @@ -83,8 +106,8 @@ posts = { return when(posts.read({id : args.id})).then(function (result) { return dataProvider.Post.destroy(args.id).then(function () { var deletedObj = {}; - deletedObj.id = result.attributes.id; - deletedObj.slug = result.attributes.slug; + deletedObj.id = result.id; + deletedObj.slug = result.slug; return deletedObj; }); }); @@ -101,7 +124,21 @@ users = { // **takes:** options object browse: function browse(options) { // **returns:** a promise for a collection of users in a json object - return dataProvider.User.browse(options); + + return dataProvider.User.browse(options).then(function (result) { + var i = 0, + omitted = {}; + + if (result) { + omitted = result.toJSON(); + } + + for (i = 0; i < omitted.length; i = i + 1) { + omitted[i] = _.omit(omitted[i], filteredUserAttributes); + } + + return omitted; + }); }, // #### Read @@ -113,10 +150,13 @@ users = { args = {id: this.user}; } - var filteredAttributes = ['password', 'created_by', 'updated_by']; + return dataProvider.User.read(args).then(function (result) { + if (result) { + var omitted = _.omit(result.toJSON(), filteredUserAttributes); + return omitted; + } - return dataProvider.User.read(args).then(function omitAttrs(result) { - return _.omit(result, filteredAttributes); + return null; }); }, diff --git a/core/server/controllers/admin.js b/core/server/controllers/admin.js index c2d37368fb..db2e686d37 100644 --- a/core/server/controllers/admin.js +++ b/core/server/controllers/admin.js @@ -107,12 +107,14 @@ adminControllers = { }); } - if (type === 'image/jpeg' || type === 'image/png' || type === 'image/gif') { + //limit uploads to type && extension + if ((type === 'image/jpeg' || type === 'image/png' || type === 'image/gif') + && (ext === '.jpg' || ext === '.jpeg' || ext === '.png' || ext === '.gif')) { getUniqueFileName(dir, basename, ext, null, function (filename) { renameFile(filename); }); } else { - res.send(404, 'Invalid filetype'); + res.send(403, 'Invalid file type'); } }, 'login': function (req, res) { @@ -144,7 +146,6 @@ adminControllers = { } else { res.json(401, {error: 'Slow down, there are way too many login attempts!'}); } - }, changepw: function (req, res) { api.users.changePassword({ @@ -229,7 +230,7 @@ adminControllers = { }).otherwise(errors.logAndThrowError); }, 'logout': function (req, res) { - delete req.session.user; + req.session = null; var notification = { type: 'success', message: 'You were successfully signed out', @@ -376,7 +377,7 @@ adminControllers = { }; return api.notifications.add(notification).then(function () { - delete req.session.user; + req.session = null; res.set({ "X-Cache-Invalidate": "/*" }); @@ -392,37 +393,6 @@ adminControllers = { id: 'per-' + (ghost.notifications.length + 1) }; - return api.notifications.add(notification).then(function () { - res.redirect('/ghost/debug/'); - }); - }); - }, - 'reset': function (req, res) { - // Grab the current version so we can get the migration - dataProvider.reset() - .then(function resetSuccess() { - var notification = { - type: 'success', - message: "Database reset. Create a new user", - status: 'persistent', - id: 'per-' + (ghost.notifications.length + 1) - }; - - return api.notifications.add(notification).then(function () { - delete req.session.user; - res.set({ - "X-Cache-Invalidate": "/*" - }); - res.redirect('/ghost/signup/'); - }); - }, function resetFailure(error) { - var notification = { - type: 'error', - message: error.message || error, - status: 'persistent', - id: 'per-' + (ghost.notifications.length + 1) - }; - return api.notifications.add(notification).then(function () { res.redirect('/ghost/debug/'); }); diff --git a/core/server/controllers/frontend.js b/core/server/controllers/frontend.js index 38bf0e9edb..b09662109a 100644 --- a/core/server/controllers/frontend.js +++ b/core/server/controllers/frontend.js @@ -66,7 +66,7 @@ frontendControllers = { 'single': function (req, res, next) { api.posts.read({'slug': req.params.slug}).then(function (post) { if (post) { - ghost.doFilter('prePostsRender', post.toJSON(), function (post) { + ghost.doFilter('prePostsRender', post, function (post) { res.render('post', {post: post}); }); } else { @@ -88,7 +88,7 @@ frontendControllers = { title: ghost.settings('title'), description: ghost.settings('description'), generator: 'Ghost v' + res.locals.version, - author: user ? user.attributes.name : null, + author: user ? user.name : null, feed_url: url.resolve(siteUrl, '/rss/'), site_url: siteUrl, ttl: '60' diff --git a/core/server/data/export/index.js b/core/server/data/export/index.js index 432e45763c..9c55c0c956 100644 --- a/core/server/data/export/index.js +++ b/core/server/data/export/index.js @@ -2,21 +2,21 @@ var when = require('when'), _ = require('underscore'), migration = require('../migration'), client = require('../../models/base').client, - knex = require('../../models/base').Knex, + knex = require('../../models/base').knex, exporter; function getTablesFromSqlite3() { - return knex.Raw("select * from sqlite_master where type = 'table'").then(function (response) { - return _.reject(_.pluck(response, 'tbl_name'), function (name) { + return knex.raw("select * from sqlite_master where type = 'table'").then(function (response) { + return _.reject(_.pluck(response[0], 'tbl_name'), function (name) { return name === 'sqlite_sequence'; }); }); } function getTablesFromMySQL() { - return knex.Raw('show tables').then(function (response) { - return _.flatten(_.map(response, function (entry) { + return knex.raw("show tables").then(function (response) { + return _.flatten(_.map(response[0], function (entry) { return _.values(entry); })); }); diff --git a/core/server/data/migration/000.js b/core/server/data/migration/000.js index 003e62b3c5..a87e5847d3 100644 --- a/core/server/data/migration/000.js +++ b/core/server/data/migration/000.js @@ -1,5 +1,5 @@ var when = require('when'), - knex = require('../../models/base').Knex, + knex = require('../../models/base').knex, up, down; @@ -7,7 +7,7 @@ up = function () { return when.all([ - knex.Schema.createTable('posts', function (t) { + knex.schema.createTable('posts', function (t) { t.increments().primary(); t.string('uuid', 36).notNull(); t.string('title', 150).notNull(); @@ -30,7 +30,7 @@ up = function () { t.integer('published_by').nullable(); }), - knex.Schema.createTable('users', function (t) { + knex.schema.createTable('users', function (t) { t.increments().primary(); t.string('uuid', 36).notNull(); t.string('name', 150).notNull(); @@ -54,7 +54,7 @@ up = function () { t.integer('updated_by').nullable(); }), - knex.Schema.createTable('roles', function (t) { + knex.schema.createTable('roles', function (t) { t.increments().primary(); t.string('uuid', 36).notNull(); t.string('name', 150).notNull(); @@ -65,13 +65,13 @@ up = function () { t.integer('updated_by').nullable(); }), - knex.Schema.createTable('roles_users', function (t) { + knex.schema.createTable('roles_users', function (t) { t.increments().primary(); t.integer('role_id').notNull(); t.integer('user_id').notNull(); }), - knex.Schema.createTable('permissions', function (t) { + knex.schema.createTable('permissions', function (t) { t.increments().primary(); t.string('uuid', 36).notNull(); t.string('name', 150).notNull(); @@ -84,19 +84,19 @@ up = function () { t.integer('updated_by').nullable(); }), - knex.Schema.createTable('permissions_users', function (t) { + knex.schema.createTable('permissions_users', function (t) { t.increments().primary(); t.integer('user_id').notNull(); t.integer('permission_id').notNull(); }), - knex.Schema.createTable('permissions_roles', function (t) { + knex.schema.createTable('permissions_roles', function (t) { t.increments().primary(); t.integer('role_id').notNull(); t.integer('permission_id').notNull(); }), - knex.Schema.createTable('settings', function (t) { + knex.schema.createTable('settings', function (t) { t.increments().primary(); t.string('uuid', 36).notNull(); t.string('key', 150).notNull().unique(); @@ -107,7 +107,7 @@ up = function () { t.dateTime('updated_at').nullable(); t.integer('updated_by').nullable(); }), - knex.Schema.createTable('tags', function (t) { + knex.schema.createTable('tags', function (t) { t.increments().primary(); t.string('uuid', 36).notNull(); t.string('name', 150).notNull(); @@ -120,30 +120,31 @@ up = function () { t.integer('created_by').notNull(); t.dateTime('updated_at').nullable(); t.integer('updated_by').nullable(); - }), - knex.Schema.createTable('posts_tags', function (t) { + }) + ]).then(function () { + return knex.schema.createTable('posts_tags', function (t) { t.increments().primary(); t.integer('post_id').notNull().unsigned().references('id').inTable('posts'); t.integer('tag_id').notNull().unsigned().references('id').inTable('tags'); - }) - ]); + }); + }); }; down = function () { return when.all([ - knex.Schema.dropTableIfExists('posts_tags'), - knex.Schema.dropTableIfExists('roles_users'), - knex.Schema.dropTableIfExists('permissions_users'), - knex.Schema.dropTableIfExists('permissions_roles'), - knex.Schema.dropTableIfExists('users') + knex.schema.dropTableIfExists('posts_tags'), + knex.schema.dropTableIfExists('roles_users'), + knex.schema.dropTableIfExists('permissions_users'), + knex.schema.dropTableIfExists('permissions_roles'), + knex.schema.dropTableIfExists('users') ]).then(function () { return when.all([ - knex.Schema.dropTableIfExists('roles'), - knex.Schema.dropTableIfExists('settings'), - knex.Schema.dropTableIfExists('permissions'), - knex.Schema.dropTableIfExists('tags'), - knex.Schema.dropTableIfExists('posts') + knex.schema.dropTableIfExists('roles'), + knex.schema.dropTableIfExists('settings'), + knex.schema.dropTableIfExists('permissions'), + knex.schema.dropTableIfExists('tags'), + knex.schema.dropTableIfExists('posts') ]); }); }; diff --git a/core/server/data/migration/index.js b/core/server/data/migration/index.js index 6f11c92257..4dca63f2a3 100644 --- a/core/server/data/migration/index.js +++ b/core/server/data/migration/index.js @@ -3,7 +3,7 @@ var _ = require('underscore'), when = require('when'), series = require('when/sequence'), errors = require('../../errorHandling'), - knex = require('../../models/base').Knex, + knex = require('../../models/base').knex, defaultSettings = require('../default-settings'), Settings = require('../../models/settings').Settings, @@ -30,7 +30,7 @@ function getDefaultDatabaseVersion() { // The migration version number according to the database // This is what the database is currently at and may need to be updated function getDatabaseVersion() { - return knex.Schema.hasTable('settings').then(function (exists) { + return knex.schema.hasTable('settings').then(function (exists) { // Check for the current version from the settings table if (exists) { // Temporary code to deal with old databases with currentVersion settings diff --git a/core/server/errorHandling.js b/core/server/errorHandling.js index 4cc201b2aa..71e8930bdb 100644 --- a/core/server/errorHandling.js +++ b/core/server/errorHandling.js @@ -29,6 +29,26 @@ errors = { throw err; }, + logWarn: function (warn, context, help) { + if ((process.env.NODE_ENV === 'development' || + process.env.NODE_ENV === 'staging' || + process.env.NODE_ENV === 'production')) { + + console.log('\nWarning:'.yellow, warn.yellow); + + if (context) { + console.log(context.white); + } + + if (help) { + console.log(help.green); + } + + // add a new line + console.log(''); + } + }, + logError: function (err, context, help) { var stack = err ? err.stack : null; err = err.message || err || 'Unknown'; @@ -41,7 +61,7 @@ errors = { console.error('\nERROR:'.red, err.red); if (context) { - console.error(context); + console.error(context.white); } if (help) { @@ -133,7 +153,7 @@ errors = { } // Are we admin? If so, don't worry about the user template - if (res.isAdmin || userErrorTemplateExists === true) { + if ((res.isAdmin && req.session.user) || userErrorTemplateExists === true) { return renderErrorInt(); } @@ -150,27 +170,29 @@ errors = { return renderErrorInt(); } - // Message only displays the first time an error is triggered. - errors.logError( - "Theme error template not found", - null, - "Add an error.hbs template to the theme for customised errors." - ); - renderErrorInt(defaultErrorTemplatePath); }); }, - render404Page: function (req, res, next) { - var message = res.isAdmin ? "No Ghost Found" : "Page Not Found"; - this.renderErrorPage(404, message, req, res, next); + error404: function (req, res, next) { + var message = res.isAdmin && req.session.user ? "No Ghost Found" : "Page Not Found"; + + if (req.method === 'GET') { + this.renderErrorPage(404, message, req, res, next); + } else { + res.send(404, message); + } }, - render500Page: function (err, req, res, next) { - if (!err || !(err instanceof Error)) { - next(); + error500: function (err, req, res, next) { + if (req.method === 'GET') { + if (!err || !(err instanceof Error)) { + next(); + } + errors.renderErrorPage(500, err, req, res, next); + } else { + res.send(500, err); } - errors.renderErrorPage(500, err, req, res, next); } }; @@ -182,8 +204,8 @@ _.bindAll( 'logAndThrowError', 'logErrorWithRedirect', 'renderErrorPage', - 'render404Page', - 'render500Page' + 'error404', + 'error500' ); module.exports = errors; \ No newline at end of file diff --git a/core/server/models/base.js b/core/server/models/base.js index 65db8cae4b..0b4276f84d 100644 --- a/core/server/models/base.js +++ b/core/server/models/base.js @@ -1,4 +1,4 @@ -var GhostBookshelf, +var ghostBookshelf, Bookshelf = require('bookshelf'), when = require('when'), moment = require('moment'), @@ -8,16 +8,15 @@ var GhostBookshelf, Validator = require('validator').Validator, sanitize = require('validator').sanitize; -// Initializes Bookshelf as its own instance, so we can modify the Models and not mess up -// others' if they're using the library outside of ghost. -GhostBookshelf = Bookshelf.Initialize('ghost', config[process.env.NODE_ENV || 'development'].database); -GhostBookshelf.client = config[process.env.NODE_ENV].database.client; +// Initializes a new Bookshelf instance, for reference elsewhere in Ghost. +ghostBookshelf = Bookshelf.initialize(config[process.env.NODE_ENV || 'development'].database); +ghostBookshelf.client = config[process.env.NODE_ENV].database.client; -GhostBookshelf.validator = new Validator(); +ghostBookshelf.validator = new Validator(); // The Base Model which other Ghost objects will inherit from, // including some convenience functions as static properties on the model. -GhostBookshelf.Model = GhostBookshelf.Model.extend({ +ghostBookshelf.Model = ghostBookshelf.Model.extend({ hasTimestamps: true, @@ -145,7 +144,7 @@ GhostBookshelf.Model = GhostBookshelf.Model.extend({ */ findAll: function (options) { options = options || {}; - return GhostBookshelf.Collection.forge([], {model: this}).fetch(options); + return ghostBookshelf.Collection.forge([], {model: this}).fetch(options); }, browse: function () { @@ -212,4 +211,4 @@ GhostBookshelf.Model = GhostBookshelf.Model.extend({ }); -module.exports = GhostBookshelf; +module.exports = ghostBookshelf; diff --git a/core/server/models/permission.js b/core/server/models/permission.js index ccd3ffeaf4..98f9c41724 100644 --- a/core/server/models/permission.js +++ b/core/server/models/permission.js @@ -1,10 +1,10 @@ -var GhostBookshelf = require('./base'), +var ghostBookshelf = require('./base'), User = require('./user').User, Role = require('./role').Role, Permission, Permissions; -Permission = GhostBookshelf.Model.extend({ +Permission = ghostBookshelf.Model.extend({ tableName: 'permissions', @@ -14,7 +14,7 @@ Permission = GhostBookshelf.Model.extend({ validate: function () { // TODO: validate object_type, action_type and object_id - GhostBookshelf.validator.check(this.get('name'), "Permission name cannot be blank").notEmpty(); + ghostBookshelf.validator.check(this.get('name'), "Permission name cannot be blank").notEmpty(); }, roles: function () { @@ -26,7 +26,7 @@ Permission = GhostBookshelf.Model.extend({ } }); -Permissions = GhostBookshelf.Collection.extend({ +Permissions = ghostBookshelf.Collection.extend({ model: Permission }); diff --git a/core/server/models/post.js b/core/server/models/post.js index 111c32fff9..c9593d7de0 100644 --- a/core/server/models/post.js +++ b/core/server/models/post.js @@ -11,9 +11,9 @@ var Post, config = require('../../../config'), Tag = require('./tag').Tag, Tags = require('./tag').Tags, - GhostBookshelf = require('./base'); + ghostBookshelf = require('./base'); -Post = GhostBookshelf.Model.extend({ +Post = ghostBookshelf.Model.extend({ tableName: 'posts', @@ -38,7 +38,7 @@ Post = GhostBookshelf.Model.extend({ }, validate: function () { - GhostBookshelf.validator.check(this.get('title'), "Post title cannot be blank").notEmpty(); + ghostBookshelf.validator.check(this.get('title'), "Post title cannot be blank").notEmpty(); return true; }, @@ -61,7 +61,7 @@ Post = GhostBookshelf.Model.extend({ this.set('published_by', 1); } - GhostBookshelf.Model.prototype.saving.call(this); + ghostBookshelf.Model.prototype.saving.call(this); if (this.hasChanged('slug')) { // Pass the new slug through the generator to strip illegal characters, detect duplicates @@ -80,7 +80,7 @@ Post = GhostBookshelf.Model.extend({ this.set('author_id', 1); } - GhostBookshelf.Model.prototype.creating.call(this); + ghostBookshelf.Model.prototype.creating.call(this); if (!this.get('slug')) { // Generating a slug requires a db call to look for conflicting slugs @@ -174,7 +174,7 @@ Post = GhostBookshelf.Model.extend({ findAll: function (options) { options = options || {}; options.withRelated = [ 'author', 'user', 'tags' ]; - return GhostBookshelf.Model.findAll.call(this, options); + return ghostBookshelf.Model.findAll.call(this, options); }, // #### findOne @@ -182,7 +182,7 @@ Post = GhostBookshelf.Model.extend({ findOne: function (args, options) { options = options || {}; options.withRelated = [ 'author', 'user', 'tags' ]; - return GhostBookshelf.Model.findOne.call(this, args, options); + return ghostBookshelf.Model.findOne.call(this, args, options); }, // #### findPage @@ -251,7 +251,7 @@ Post = GhostBookshelf.Model.extend({ // After we're done, we need to figure out what // the limits are for the pagination values. - qb = GhostBookshelf.Knex(_.result(collection, 'tableName')); + qb = ghostBookshelf.knex(_.result(collection, 'tableName')); if (opts.where) { qb.where(opts.where); @@ -328,7 +328,7 @@ Post = GhostBookshelf.Model.extend({ }, add: function (newPostData, options) { - return GhostBookshelf.Model.add.call(this, newPostData, options).tap(function (post) { + return ghostBookshelf.Model.add.call(this, newPostData, options).tap(function (post) { // associated models can't be created until the post has an ID, so run this after return post.updateTags(newPostData.tags); }); @@ -349,7 +349,7 @@ Post = GhostBookshelf.Model.extend({ }); -Posts = GhostBookshelf.Collection.extend({ +Posts = ghostBookshelf.Collection.extend({ model: Post diff --git a/core/server/models/role.js b/core/server/models/role.js index 6d0bd6962c..a0863f03ab 100644 --- a/core/server/models/role.js +++ b/core/server/models/role.js @@ -1,18 +1,18 @@ var User = require('./user').User, Permission = require('./permission').Permission, - GhostBookshelf = require('./base'), + ghostBookshelf = require('./base'), Role, Roles; -Role = GhostBookshelf.Model.extend({ +Role = ghostBookshelf.Model.extend({ tableName: 'roles', permittedAttributes: ['id', 'uuid', 'name', 'description', 'created_at', 'created_by', 'updated_at', 'updated_by'], validate: function () { - GhostBookshelf.validator.check(this.get('name'), "Role name cannot be blank").notEmpty(); - GhostBookshelf.validator.check(this.get('description'), "Role description cannot be blank").notEmpty(); + ghostBookshelf.validator.check(this.get('name'), "Role name cannot be blank").notEmpty(); + ghostBookshelf.validator.check(this.get('description'), "Role description cannot be blank").notEmpty(); }, users: function () { @@ -24,7 +24,7 @@ Role = GhostBookshelf.Model.extend({ } }); -Roles = GhostBookshelf.Collection.extend({ +Roles = ghostBookshelf.Collection.extend({ model: Role }); diff --git a/core/server/models/settings.js b/core/server/models/settings.js index cce389311d..250fefdc71 100644 --- a/core/server/models/settings.js +++ b/core/server/models/settings.js @@ -1,6 +1,6 @@ var Settings, - GhostBookshelf = require('./base'), - validator = GhostBookshelf.validator, + ghostBookshelf = require('./base'), + validator = ghostBookshelf.validator, uuid = require('node-uuid'), _ = require('underscore'), errors = require('../errorHandling'), @@ -29,7 +29,7 @@ defaultSettings = parseDefaultSettings(); // Each setting is saved as a separate row in the database, // but the overlying API treats them as a single key:value mapping -Settings = GhostBookshelf.Model.extend({ +Settings = ghostBookshelf.Model.extend({ tableName: 'settings', @@ -83,7 +83,7 @@ Settings = GhostBookshelf.Model.extend({ this.set('value', this.sanitize('value')); } - return GhostBookshelf.Model.prototype.saving.apply(this, arguments); + return ghostBookshelf.Model.prototype.saving.apply(this, arguments); } }, { @@ -92,7 +92,7 @@ Settings = GhostBookshelf.Model.extend({ if (!_.isObject(_key)) { _key = { key: _key }; } - return GhostBookshelf.Model.read.call(this, _key); + return ghostBookshelf.Model.read.call(this, _key); }, edit: function (_data) { diff --git a/core/server/models/tag.js b/core/server/models/tag.js index a1d972c80b..962b89be4a 100644 --- a/core/server/models/tag.js +++ b/core/server/models/tag.js @@ -1,9 +1,9 @@ var Tag, Tags, Posts = require('./post').Posts, - GhostBookshelf = require('./base'); + ghostBookshelf = require('./base'); -Tag = GhostBookshelf.Model.extend({ +Tag = ghostBookshelf.Model.extend({ tableName: 'tags', @@ -20,7 +20,7 @@ Tag = GhostBookshelf.Model.extend({ creating: function () { var self = this; - GhostBookshelf.Model.prototype.creating.call(this); + ghostBookshelf.Model.prototype.creating.call(this); if (!this.get('slug')) { // Generating a slug requires a db call to look for conflicting slugs @@ -36,7 +36,7 @@ Tag = GhostBookshelf.Model.extend({ } }); -Tags = GhostBookshelf.Collection.extend({ +Tags = ghostBookshelf.Collection.extend({ model: Tag diff --git a/core/server/models/user.js b/core/server/models/user.js index d814a91dea..96a5705fd1 100644 --- a/core/server/models/user.js +++ b/core/server/models/user.js @@ -7,14 +7,14 @@ var User, nodefn = require('when/node/function'), bcrypt = require('bcrypt-nodejs'), Posts = require('./post').Posts, - GhostBookshelf = require('./base'), + ghostBookshelf = require('./base'), Role = require('./role').Role, Permission = require('./permission').Permission; function validatePasswordLength(password) { try { - GhostBookshelf.validator.check(password, "Your must be at least 8 characters long.").len(8); + ghostBookshelf.validator.check(password, "Your must be at least 8 characters long.").len(8); } catch (error) { return when.reject(error); } @@ -22,7 +22,7 @@ function validatePasswordLength(password) { return when.resolve(); } -User = GhostBookshelf.Model.extend({ +User = ghostBookshelf.Model.extend({ tableName: 'users', @@ -33,10 +33,10 @@ User = GhostBookshelf.Model.extend({ ], validate: function () { - GhostBookshelf.validator.check(this.get('email'), "Please enter a valid email address. That one looks a bit dodgy.").isEmail(); - GhostBookshelf.validator.check(this.get('bio'), "We're not writing a novel here! I'm afraid your bio has to stay under 200 characters.").len(0, 200); + ghostBookshelf.validator.check(this.get('email'), "Please enter a valid email address. That one looks a bit dodgy.").isEmail(); + ghostBookshelf.validator.check(this.get('bio'), "We're not writing a novel here! I'm afraid your bio has to stay under 200 characters.").len(0, 200); if (this.get('website') && this.get('website').length > 0) { - GhostBookshelf.validator.check(this.get('website'), "Looks like your website is not actually a website. Try again?").isUrl(); + ghostBookshelf.validator.check(this.get('website'), "Looks like your website is not actually a website. Try again?").isUrl(); } return true; }, @@ -44,7 +44,7 @@ User = GhostBookshelf.Model.extend({ creating: function () { var self = this; - GhostBookshelf.Model.prototype.creating.call(this); + ghostBookshelf.Model.prototype.creating.call(this); if (!this.get('slug')) { // Generating a slug requires a db call to look for conflicting slugs @@ -63,7 +63,7 @@ User = GhostBookshelf.Model.extend({ this.set('website', this.sanitize('website')); this.set('bio', this.sanitize('bio')); - return GhostBookshelf.Model.prototype.saving.apply(this, arguments); + return ghostBookshelf.Model.prototype.saving.apply(this, arguments); }, posts: function () { @@ -111,7 +111,7 @@ User = GhostBookshelf.Model.extend({ // Assign the hashed password userData.password = hash; // Save the user with the hashed password - return GhostBookshelf.Model.add.call(self, userData); + return ghostBookshelf.Model.add.call(self, userData); }).then(function (addedUser) { // Assign the userData to our created user so we can pass it back userData = addedUser; @@ -135,8 +135,8 @@ User = GhostBookshelf.Model.extend({ // } // return nodefn.call(bcrypt.hash, _user.password, null, null).then(function (hash) { // userData.password = hash; - // GhostBookshelf.Model.add.call(UserRole, userRoles); - // return GhostBookshelf.Model.add.call(User, userData); + // ghostBookshelf.Model.add.call(UserRole, userRoles); + // return ghostBookshelf.Model.add.call(User, userData); // }, errors.logAndThrowError); // }, errors.logAndThrowError); @@ -239,7 +239,7 @@ User = GhostBookshelf.Model.extend({ }); -Users = GhostBookshelf.Collection.extend({ +Users = ghostBookshelf.Collection.extend({ model: User }); diff --git a/core/server/views/debug.hbs b/core/server/views/debug.hbs index 841e828aeb..83e50e31f0 100644 --- a/core/server/views/debug.hbs +++ b/core/server/views/debug.hbs @@ -16,6 +16,7 @@
+
@@ -25,11 +26,12 @@
+
- - + +

Import from another Ghost installation. If you import a user, this will replace the current user & log you out.

diff --git a/core/server/views/default.hbs b/core/server/views/default.hbs index b2b76235c1..aa5da10078 100644 --- a/core/server/views/default.hbs +++ b/core/server/views/default.hbs @@ -4,6 +4,7 @@ + Ghost Admin diff --git a/core/test/unit/admin_spec.js b/core/test/unit/admin_spec.js index 397b11d24b..6095ca8daf 100644 --- a/core/test/unit/admin_spec.js +++ b/core/test/unit/admin_spec.js @@ -31,26 +31,26 @@ describe('Admin Controller', function() { }); describe('can not upload invalid file', function() { - it('should return 404 for invalid file type', function() { + it('should return 403 for invalid file type', function() { res.send = sinon.stub(); req.files.uploadimage.name = 'INVALID.FILE'; req.files.uploadimage.type = 'application/octet-stream' admin.uploader(req, res); res.send.calledOnce.should.be.true; - res.send.args[0][0].should.equal(404); - res.send.args[0][1].should.equal('Invalid filetype'); + res.send.args[0][0].should.equal(403); + res.send.args[0][1].should.equal('Invalid file type'); }); }); describe('can not upload file with valid extension but invalid type', function() { - it('should return 404 for invalid file type', function() { + it('should return 403 for invalid file type', function() { res.send = sinon.stub(); req.files.uploadimage.name = 'INVALID.jpg'; req.files.uploadimage.type = 'application/octet-stream' admin.uploader(req, res); res.send.calledOnce.should.be.true; - res.send.args[0][0].should.equal(404); - res.send.args[0][1].should.equal('Invalid filetype'); + res.send.args[0][0].should.equal(403); + res.send.args[0][1].should.equal('Invalid file type'); }); }); diff --git a/core/test/unit/import_spec.js b/core/test/unit/import_spec.js index bd23779a6c..573512f581 100644 --- a/core/test/unit/import_spec.js +++ b/core/test/unit/import_spec.js @@ -7,7 +7,7 @@ var testUtils = require('./testUtils'), errors = require('../../server/errorHandling'), // Stuff we are testing - knex = require("../../server/models/base").Knex, + knex = require("../../server/models/base").knex, migration = require('../../server/data/migration'), exporter = require('../../server/data/export'), importer = require('../../server/data/import'), diff --git a/core/test/unit/model_settings_spec.js b/core/test/unit/model_settings_spec.js index ff8351ce21..5e57b161e5 100644 --- a/core/test/unit/model_settings_spec.js +++ b/core/test/unit/model_settings_spec.js @@ -5,7 +5,7 @@ var testUtils = require('./testUtils'), // Stuff we are testing Models = require('../../server/models'), - knex = require('../../server/models/base').Knex; + knex = require('../../server/models/base').knex; describe('Settings Model', function () { diff --git a/core/test/unit/testUtils.js b/core/test/unit/testUtils.js index 766ae347c9..aed777f939 100644 --- a/core/test/unit/testUtils.js +++ b/core/test/unit/testUtils.js @@ -1,4 +1,4 @@ -var knex = require('../../server/models/base').Knex, +var knex = require('../../server/models/base').knex, when = require('when'), migration = require("../../server/data/migration/"), Settings = require('../../server/models/settings').Settings, diff --git a/package.json b/package.json index 5ba61a6339..b922d16cce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name" : "ghost", - "version" : "0.3.2", + "version" : "0.3.3", "description" : "Just a blogging platform.", "author" : "Ghost Foundation", "homepage" : "http://ghost.org", @@ -30,8 +30,8 @@ "underscore": "1.5.1", "showdown": "0.3.1", "sqlite3": "2.1.16", - "bookshelf": "0.3.1", - "knex": "0.2.7-alpha", + "bookshelf": "0.5.7", + "knex": "0.4.11", "when": "2.2.1", "bcrypt-nodejs": "0.0.3", "node-uuid": "1.4.0",