diff --git a/ghost/extract-api-key/lib/extract-api-key.js b/ghost/extract-api-key/lib/extract-api-key.js index 29c1dc0996..fd04df608a 100644 --- a/ghost/extract-api-key/lib/extract-api-key.js +++ b/ghost/extract-api-key/lib/extract-api-key.js @@ -1,16 +1,57 @@ +const jwt = require('jsonwebtoken'); + /** + * Remove 'Ghost' from raw authorization header and extract the JWT token. + * Eg. Authorization: Ghost ${JWT} + * @param {string} header + */ +const extractTokenFromHeader = (header) => { + const [scheme, token] = header.split(' '); + + if (/^Ghost$/i.test(scheme)) { + return token; + } +}; + +const extractAdminAPIKey = (token) => { + const decoded = jwt.decode(token, {complete: true}); + + if (!decoded || !decoded.header || !decoded.header.kid) { + return null; + } + + return decoded.header.kid; +}; + +/** + * @typedef {object} ApiKey + * @prop {string} key + * @prop {string} type + */ + +/** + * When it's a Content API the function resolves with the value of the key secret. + * When it's an Admin API the function resolves with the value of the key id. * * @param {import('express').Request} req - * @returns {string} + * @returns {ApiKey} */ const extractAPIKey = (req) => { let keyValue = null; + let keyType = null; if (req.query && req.query.key) { keyValue = req.query.key; + keyType = 'content'; + } else if (req.headers && req.headers.authorization) { + keyValue = extractAdminAPIKey(extractTokenFromHeader(req.headers.authorization)); + keyType = 'admin'; } - return keyValue; + return { + key: keyValue, + type: keyType + }; }; module.exports = extractAPIKey; diff --git a/ghost/extract-api-key/package.json b/ghost/extract-api-key/package.json index 86742a27e2..8ea180d53d 100644 --- a/ghost/extract-api-key/package.json +++ b/ghost/extract-api-key/package.json @@ -21,5 +21,7 @@ "access": "public" }, "devDependencies": {}, - "dependencies": {} + "dependencies": { + "jsonwebtoken": "^8.5.1" + } } diff --git a/ghost/extract-api-key/test/extract-api-key.test.js b/ghost/extract-api-key/test/extract-api-key.test.js index dab7583dbe..25d6371341 100644 --- a/ghost/extract-api-key/test/extract-api-key.test.js +++ b/ghost/extract-api-key/test/extract-api-key.test.js @@ -3,22 +3,46 @@ const extractApiKey = require('../index'); describe('Extract API Key', function () { it('Returns nulls for a request without any key', function () { - const key = extractApiKey({ + const {key, type} = extractApiKey({ query: { filter: 'status:active' } }); assert.equal(key, null); + assert.equal(type, null); }); it('Extracts Content API key from the request', function () { - const key = extractApiKey({ + const {key, type} = extractApiKey({ query: { key: '123thekey' } }); assert.equal(key, '123thekey'); + assert.equal(type, 'content'); + }); + + it('Extracts Admin API key from the request', function () { + const {key, type} = extractApiKey({ + headers: { + authorization: 'Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjYyNzM4MjQzNDZiZjUxZjNhYWI5OTA5OSJ9.eyJpYXQiOjE2NTIxNjUyNDQsImV4cCI6MTY1MjE2NTU0NCwiYXVkIjoiL3YyL2FkbWluLyJ9.VdPOZ4XffgYd8qn_46zlJR3jW_rPZTw70COkG5IYIuU' + } + }); + + assert.equal(key, '6273824346bf51f3aab99099'); + assert.equal(type, 'admin'); + }); + + it('Returns null if malformatted Admin API Key', function () { + const {key, type} = extractApiKey({ + headers: { + authorization: 'Ghost incorrectformat' + } + }); + + assert.equal(key, null); + assert.equal(type, 'admin'); }); });