diff --git a/core/server/data/migrations/versions/2.8/1-add-members-table.js b/core/server/data/migrations/versions/2.8/1-add-members-table.js new file mode 100644 index 0000000000..7ff24e487a --- /dev/null +++ b/core/server/data/migrations/versions/2.8/1-add-members-table.js @@ -0,0 +1,35 @@ +const common = require('../../../../lib/common'); +const commands = require('../../../schema').commands; +const table = 'members'; +const message1 = 'Adding table: ' + table; +const message2 = 'Dropping table: ' + table; + +module.exports.up = (options) => { + const connection = options.connection; + + return connection.schema.hasTable(table) + .then(function (exists) { + if (exists) { + common.logging.warn(message1); + return; + } + + common.logging.info(message1); + return commands.createTable(table, connection); + }); +}; + +module.exports.down = (options) => { + const connection = options.connection; + + return connection.schema.hasTable(table) + .then(function (exists) { + if (!exists) { + common.logging.warn(message2); + return; + } + + common.logging.info(message2); + return commands.deleteTable(table, connection); + }); +}; diff --git a/core/server/data/schema/default-settings.json b/core/server/data/schema/default-settings.json index 2a937bc889..3adab9cb25 100644 --- a/core/server/data/schema/default-settings.json +++ b/core/server/data/schema/default-settings.json @@ -118,5 +118,16 @@ "public_hash": { "defaultValue": null } + }, + "members": { + "members_public_key": { + "defaultValue": null + }, + "members_private_key": { + "defaultValue": null + }, + "members_session_secret": { + "defaultValue": null + } } } diff --git a/core/server/data/schema/schema.js b/core/server/data/schema/schema.js index 143a7fbf33..fa5cfb0b57 100644 --- a/core/server/data/schema/schema.js +++ b/core/server/data/schema/schema.js @@ -142,7 +142,7 @@ module.exports = { maxlength: 50, nullable: false, defaultTo: 'core', - validations: {isIn: [['core', 'blog', 'theme', 'app', 'plugin', 'private']]} + validations: {isIn: [['core', 'blog', 'theme', 'app', 'plugin', 'private', 'members']]} }, created_at: {type: 'dateTime', nullable: false}, created_by: {type: 'string', maxlength: 24, nullable: false}, @@ -368,5 +368,15 @@ module.exports = { mobiledoc: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true}, created_at_ts: {type: 'bigInteger', nullable: false}, created_at: {type: 'dateTime', nullable: false} + }, + members: { + id: {type: 'string', maxlength: 24, nullable: false, primary: true}, + email: {type: 'string', maxlength: 191, nullable: false, unique: true, validations: {isEmail: true}}, + name: {type: 'string', maxlength: 191, nullable: false}, + password: {type: 'string', maxlength: 60, nullable: true}, + created_at: {type: 'dateTime', nullable: false}, + created_by: {type: 'string', maxlength: 24, nullable: false}, + updated_at: {type: 'dateTime', nullable: true}, + updated_by: {type: 'string', maxlength: 24, nullable: true} } }; diff --git a/core/server/models/index.js b/core/server/models/index.js index eabedbaca9..366be4742d 100644 --- a/core/server/models/index.js +++ b/core/server/models/index.js @@ -34,7 +34,8 @@ models = [ 'webhook', 'integration', 'api-key', - 'mobiledoc-revision' + 'mobiledoc-revision', + 'member' ]; function init() { diff --git a/core/server/models/member.js b/core/server/models/member.js new file mode 100644 index 0000000000..5879deb4fa --- /dev/null +++ b/core/server/models/member.js @@ -0,0 +1,40 @@ +const ghostBookshelf = require('./base'); +const security = require('../lib/security'); + +const Member = ghostBookshelf.Model.extend({ + tableName: 'members', + + onSaving() { + ghostBookshelf.Model.prototype.onSaving.apply(this, arguments); + + if (this.hasChanged('password')) { + return security.password.hash(String(this.get('password'))) + .then((hash) => { + this.set('password', hash); + }); + } + }, + + comparePassword(rawPassword) { + return security.password.compare(rawPassword, this.get('password')); + }, + + toJSON(unfilteredOptions) { + var options = Member.filterOptions(unfilteredOptions, 'toJSON'), + attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options); + + // remove password hash and tokens for security reasons + delete attrs.password; + + return attrs; + } +}); + +const Members = ghostBookshelf.Collection.extend({ + model: Member +}); + +module.exports = { + Member: ghostBookshelf.model('Member', Member), + Members: ghostBookshelf.collection('Members', Members) +}; diff --git a/core/server/models/settings.js b/core/server/models/settings.js index 41c3f68ac7..ec5874c2d2 100644 --- a/core/server/models/settings.js +++ b/core/server/models/settings.js @@ -2,6 +2,7 @@ const Promise = require('bluebird'), _ = require('lodash'), uuid = require('uuid'), crypto = require('crypto'), + keypair = require('keypair'), ghostBookshelf = require('./base'), common = require('../lib/common'), validation = require('../data/validation'), @@ -18,9 +19,17 @@ function parseDefaultSettings() { dynamicDefault = { db_hash: uuid.v4(), public_hash: crypto.randomBytes(15).toString('hex'), - session_secret: crypto.randomBytes(32).toString('hex') + session_secret: crypto.randomBytes(32).toString('hex'), + members_session_secret: crypto.randomBytes(32).toString('hex') }; + const membersKeypair = keypair({ + bits: 1024 + }); + + dynamicDefault.members_public_key = membersKeypair.public; + dynamicDefault.members_private_key = membersKeypair.private; + _.each(defaultSettingsInCategories, function each(settings, categoryName) { _.each(settings, function each(setting, settingName) { setting.type = categoryName; diff --git a/core/test/functional/api/v0.1/db_spec.js b/core/test/functional/api/v0.1/db_spec.js index 1d04490f0b..8427e4f93c 100644 --- a/core/test/functional/api/v0.1/db_spec.js +++ b/core/test/functional/api/v0.1/db_spec.js @@ -87,7 +87,7 @@ describe('DB API', function () { var jsonResponse = res.body; should.exist(jsonResponse.db); jsonResponse.db.should.have.length(1); - Object.keys(jsonResponse.db[0].data).length.should.eql(24); + Object.keys(jsonResponse.db[0].data).length.should.eql(25); done(); }); }); @@ -105,7 +105,7 @@ describe('DB API', function () { const jsonResponse = res.body; should.exist(jsonResponse.db); jsonResponse.db.should.have.length(1); - Object.keys(jsonResponse.db[0].data).length.should.eql(26); + Object.keys(jsonResponse.db[0].data).length.should.eql(27); done(); }); }); diff --git a/core/test/unit/data/schema/integrity_spec.js b/core/test/unit/data/schema/integrity_spec.js index 7033e9f7b7..8a499b3bfd 100644 --- a/core/test/unit/data/schema/integrity_spec.js +++ b/core/test/unit/data/schema/integrity_spec.js @@ -19,7 +19,7 @@ var should = require('should'), */ describe('DB version integrity', function () { // Only these variables should need updating - const currentSchemaHash = '92cb4391c426520d2e3e80c46f6ae100'; + const currentSchemaHash = 'b865478398cd2b0a1e5eaffebccdb88c'; const currentFixturesHash = '8b36d1e72c29b7f9073612142b5a8783'; // If this test is failing, then it is likely a change has been made that requires a DB version bump,