diff --git a/core/server/data/schema/schema.js b/core/server/data/schema/schema.js index 6b36349e7c..ced755b46d 100644 --- a/core/server/data/schema/schema.js +++ b/core/server/data/schema/schema.js @@ -355,7 +355,7 @@ module.exports = { maxlength: 191, nullable: false, unique: true, - validations: {isLength: {min: 128, max: 128}} + validations: {isLength: {min: 26, max: 128}} }, role_id: {type: 'string', maxlength: 24, nullable: true}, // integration_id is nullable to allow "internal" API keys that don't show in the UI diff --git a/core/server/models/api-key.js b/core/server/models/api-key.js index 4aef7bbee5..9f88cb51ab 100644 --- a/core/server/models/api-key.js +++ b/core/server/models/api-key.js @@ -2,14 +2,34 @@ const crypto = require('crypto'); const ghostBookshelf = require('./base'); const {Role} = require('./role'); -const createSecret = () => crypto.randomBytes(64).toString('hex'); +/* + * Uses birthday problem estimation to calculate chance of collision + * d = 16^26 // 26 char hex string + * n = 10,000,000 // 10 million + * + * (-n x (n-1)) / 2d + * 1 - e^ + * + * + * 17 + * ~= 4 x 10^ + * + * ref: https://medium.freecodecamp.org/how-long-should-i-make-my-api-key-833ebf2dc26f + * ref: https://en.wikipedia.org/wiki/Birthday_problem#Approximations + * + * 26 char hex string = 13 bytes + * 512 bit JWT secret = 64 bytes + */ +const createSecret = (type) => { + const bytes = type === 'content' ? 13 : 64; + return crypto.randomBytes(bytes).toString('hex'); +}; const ApiKey = ghostBookshelf.Model.extend({ tableName: 'api_keys', defaults() { - // 512bit key for HS256 JWT signing - const secret = createSecret(); + const secret = createSecret(this.get('type')); return { secret diff --git a/core/test/unit/models/api-key_spec.js b/core/test/unit/models/api-key_spec.js index 19246233ac..3005143f49 100644 --- a/core/test/unit/models/api-key_spec.js +++ b/core/test/unit/models/api-key_spec.js @@ -29,6 +29,16 @@ describe('Unit: models/api_key', function () { }); }); + it('sets default secret for content key', function () { + const attrs = { + type: 'content' + }; + + return models.ApiKey.add(attrs).then((api_key) => { + api_key.get('secret').length.should.eql(26); + }); + }); + it('sets hardcoded role for key type', function () { // roles[5] = 'Admin Integration' const role_id = testUtils.DataGenerator.forKnex.roles[5].id;