mirror of https://github.com/TryGhost/Ghost.git
Added API Key auth middleware to v2 content API (#10005)
* Added API Key auth middleware to v2 content API refs #9865 - add `auth.authenticate.authenticateContentApiKey` middleware - accepts `?key=` query param, sets `req.api_key` if it's a known Content API key - add `requiresAuthorizedUserOrApiKey` authorization middleware - passes if either `req.user` or `req.api_key` exists - update `authenticatePublic` middleware stack for v2 content routes * Fixed functional content api tests no-issue This fixes the functional content api tests so they use the content api auth. * Fixed context check and removed skip * Updated cors middleware for content api * Removed client_id from frame.context no-issue The v2 api doesn't have a notion of clients as we do not use oauth for it * Fixed tests for posts input serializer
This commit is contained in:
parent
856af02e08
commit
3db102a776
|
@ -15,8 +15,7 @@ const http = (apiImpl) => {
|
|||
user: req.user,
|
||||
context: {
|
||||
api_key_id: (req.api_key && req.api_key.id) ? req.api_key.id : null,
|
||||
user: ((req.user && req.user.id) || (req.user && models.User.isExternalUser(req.user.id))) ? req.user.id : null,
|
||||
client_id: (req.client && req.client.id) ? req.client.id : null // TODO: @allouis please remove this once Content API auth is in place
|
||||
user: ((req.user && req.user.id) || (req.user && models.User.isExternalUser(req.user.id))) ? req.user.id : null
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ module.exports = {
|
|||
all(apiConfig, frame) {
|
||||
debug('all');
|
||||
|
||||
if (!_.get(frame, 'options.context.user') && _.get(frame, 'options.context.client_id')) {
|
||||
if (!_.get(frame, 'options.context.user') && _.get(frame, 'options.context.api_key_id')) {
|
||||
// CASE: the content api endpoints for posts should only return non page type resources
|
||||
if (frame.options.filter) {
|
||||
if (frame.options.filter.match(/page:\w+\+?/)) {
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
const models = require('../../../models');
|
||||
const common = require('../../../lib/common');
|
||||
|
||||
const authenticateContentApiKey = function authenticateContentApiKey(req, res, next) {
|
||||
// allow fallthrough to other auth methods or final ensureAuthenticated check
|
||||
if (!req.query || !req.query.key) {
|
||||
return next();
|
||||
}
|
||||
|
||||
let key = req.query.key;
|
||||
|
||||
models.ApiKey.findOne({secret: key}).then((apiKey) => {
|
||||
if (!apiKey) {
|
||||
return next(new common.errors.UnauthorizedError({
|
||||
message: common.i18n.t('errors.middleware.auth.unknownContentApiKey'),
|
||||
code: 'UNKNOWN_CONTENT_API_KEY'
|
||||
}));
|
||||
}
|
||||
|
||||
if (apiKey.get('type') !== 'content') {
|
||||
return next(new common.errors.UnauthorizedError({
|
||||
message: common.i18n.t('errors.middleware.auth.invalidApiKeyType'),
|
||||
code: 'INVALID_API_KEY_TYPE'
|
||||
}));
|
||||
}
|
||||
|
||||
// authenticated OK, store the api key on the request for later checks and logging
|
||||
req.api_key = apiKey;
|
||||
next();
|
||||
}).catch((err) => {
|
||||
next(new common.errors.InternalServerError({err}));
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
authenticateContentApiKey
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
get content() {
|
||||
return require('./content');
|
||||
}
|
||||
};
|
|
@ -3,6 +3,7 @@ const authUtils = require('./utils');
|
|||
const models = require('../../models');
|
||||
const common = require('../../lib/common');
|
||||
const session = require('./session');
|
||||
const apiKeyAuth = require('./api-key');
|
||||
|
||||
const authenticate = {
|
||||
// ### Authenticate Client Middleware
|
||||
|
@ -100,7 +101,9 @@ const authenticate = {
|
|||
)(req, res, next);
|
||||
},
|
||||
|
||||
authenticateAdminAPI: [session.safeGetSession, session.getUser]
|
||||
// ### v2 API auth middleware
|
||||
authenticateAdminAPI: [session.safeGetSession, session.getUser],
|
||||
authenticateContentApiKey: apiKeyAuth.content.authenticateContentApiKey
|
||||
};
|
||||
|
||||
module.exports = authenticate;
|
||||
|
|
|
@ -37,7 +37,17 @@ const authorize = {
|
|||
};
|
||||
},
|
||||
|
||||
authorizeAdminAPI: [session.ensureUser]
|
||||
authorizeAdminAPI: [session.ensureUser],
|
||||
// used by API v2 endpoints
|
||||
requiresAuthorizedUserOrApiKey(req, res, next) {
|
||||
const hasUser = req.user && req.user.id;
|
||||
const hasApiKey = req.api_key && req.api_key.id;
|
||||
if (hasUser || hasApiKey) {
|
||||
return next();
|
||||
} else {
|
||||
return next(new common.errors.NoPermissionError({message: common.i18n.t('errors.middleware.auth.pleaseSignInOrAuthenticate')}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = authorize;
|
||||
|
|
|
@ -77,7 +77,11 @@
|
|||
"mismatchedOrigin": "Request made from incorrect origin. Expected '{expected}' received '{actual}'.",
|
||||
"missingUserIDForSession": "Cannot create session without user id.",
|
||||
"accessDenied": "Access denied.",
|
||||
"pleaseSignIn": "Please Sign In"
|
||||
"pleaseSignIn": "Please Sign In",
|
||||
"pleaseSignInOrAuthenticate": "Please sign in or authenticate with an API Key",
|
||||
"unknownAdminApiKey": "Unknown Admin API Key",
|
||||
"unknownContentApiKey": "Unknown Content API Key",
|
||||
"invalidApiKeyType": "Invalid API Key type"
|
||||
},
|
||||
"oauth": {
|
||||
"invalidClient": "Invalid client.",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const cors = require('cors');
|
||||
const auth = require('../../../../services/auth');
|
||||
const shared = require('../../../shared');
|
||||
|
||||
|
@ -6,7 +7,6 @@ const shared = require('../../../shared');
|
|||
*
|
||||
* IMPORTANT
|
||||
* - cors middleware MUST happen before pretty urls, because otherwise cors header can get lost on redirect
|
||||
* - cors middleware MUST happen after authenticateClient, because authenticateClient reads the trusted domains
|
||||
* - url redirects MUST happen after cors, otherwise cors header can get lost on redirect
|
||||
*/
|
||||
|
||||
|
@ -14,11 +14,9 @@ const shared = require('../../../shared');
|
|||
* Authentication for public endpoints
|
||||
*/
|
||||
module.exports.authenticatePublic = [
|
||||
auth.authenticate.authenticateClient,
|
||||
auth.authenticate.authenticateUser,
|
||||
// This is a labs-enabled middleware
|
||||
auth.authorize.requiresAuthorizedUserPublicAPI,
|
||||
shared.middlewares.api.cors,
|
||||
auth.authenticate.authenticateContentApiKey,
|
||||
auth.authorize.requiresAuthorizedUserOrApiKey,
|
||||
cors(),
|
||||
shared.middlewares.urlRedirects.adminRedirect,
|
||||
shared.middlewares.prettyUrls
|
||||
];
|
||||
|
|
|
@ -16,7 +16,7 @@ describe('Pages', function () {
|
|||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'client:trusted-domain');
|
||||
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'api_keys');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -25,13 +25,14 @@ describe('Pages', function () {
|
|||
});
|
||||
|
||||
it('browse pages', function () {
|
||||
request.get(localUtils.API.getApiQuery('pages/?client_id=ghost-admin&client_secret=not_available'))
|
||||
const key = localUtils.getValidKey();
|
||||
return request.get(localUtils.API.getApiQuery(`pages/?key=${key}`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
res.headers.vary.should.eql('Origin, Accept-Encoding');
|
||||
res.headers.vary.should.eql('Accept-Encoding');
|
||||
should.exist(res.headers['access-control-allow-origin']);
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ describe('Posts', function () {
|
|||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'client:trusted-domain');
|
||||
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'api_keys');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -27,8 +27,10 @@ describe('Posts', function () {
|
|||
configUtils.restore();
|
||||
});
|
||||
|
||||
const validKey = localUtils.getValidKey();
|
||||
|
||||
it('browse posts', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available'))
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -38,7 +40,7 @@ describe('Posts', function () {
|
|||
return done(err);
|
||||
}
|
||||
|
||||
res.headers.vary.should.eql('Origin, Accept-Encoding');
|
||||
res.headers.vary.should.eql('Accept-Encoding');
|
||||
should.exist(res.headers['access-control-allow-origin']);
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
|
||||
|
@ -92,7 +94,7 @@ describe('Posts', function () {
|
|||
});
|
||||
|
||||
it('browse posts with basic filters', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=tag:kitchen-sink,featured:true&include=tags'))
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=tag:kitchen-sink,featured:true&include=tags`))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
|
@ -147,7 +149,7 @@ describe('Posts', function () {
|
|||
});
|
||||
|
||||
it('browse posts with basic page filter should not return pages', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=page:true'))
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=page:true`))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
|
@ -171,7 +173,7 @@ describe('Posts', function () {
|
|||
});
|
||||
|
||||
it('browse posts with author filter', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=authors:[joe-bloggs,pat,ghost]&include=authors'))
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=authors:[joe-bloggs,pat,ghost]&include=authors`))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
|
@ -202,7 +204,7 @@ describe('Posts', function () {
|
|||
});
|
||||
|
||||
it('browse posts with published and draft status, should not return drafts', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=status:published,status:draft'))
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=status:published,status:draft`))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
|
@ -223,7 +225,7 @@ describe('Posts', function () {
|
|||
});
|
||||
|
||||
it('[deprecated] browse posts with page non matching filter', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&filter=tag:no-posts'))
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=tag:no-posts`))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
|
@ -249,7 +251,7 @@ describe('Posts', function () {
|
|||
});
|
||||
|
||||
it('browse posts: request to include tags and authors should always contain absolute urls', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&include=tags,authors'))
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&include=tags,authors`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -289,7 +291,7 @@ describe('Posts', function () {
|
|||
});
|
||||
|
||||
it('browse posts from different origin', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-test&client_secret=not_available'))
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}`))
|
||||
.set('Origin', 'https://example.com')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -299,7 +301,7 @@ describe('Posts', function () {
|
|||
return done(err);
|
||||
}
|
||||
|
||||
res.headers.vary.should.eql('Origin, Accept-Encoding');
|
||||
res.headers.vary.should.eql('Accept-Encoding');
|
||||
should.exist(res.headers['access-control-allow-origin']);
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
|
||||
|
@ -319,7 +321,7 @@ describe('Posts', function () {
|
|||
// NOTE: force a redirect to the admin url
|
||||
configUtils.set('admin:url', 'http://localhost:9999');
|
||||
|
||||
request.get(localUtils.API.getApiQuery('posts?client_id=ghost-test&client_secret=not_available'))
|
||||
request.get(localUtils.API.getApiQuery(`posts?key=${validKey}`))
|
||||
.set('Origin', 'https://example.com')
|
||||
// 301 Redirects _should_ be cached
|
||||
.expect('Cache-Control', testUtils.cacheRules.year)
|
||||
|
@ -329,8 +331,8 @@ describe('Posts', function () {
|
|||
return done(err);
|
||||
}
|
||||
|
||||
res.headers.vary.should.eql('Origin, Accept, Accept-Encoding');
|
||||
res.headers.location.should.eql('http://localhost:9999/ghost/api/v2/content/posts/?client_id=ghost-test&client_secret=not_available');
|
||||
res.headers.vary.should.eql('Accept, Accept-Encoding');
|
||||
res.headers.location.should.eql(`http://localhost:9999/ghost/api/v2/content/posts/?key=${validKey}`);
|
||||
should.exist(res.headers['access-control-allow-origin']);
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
done();
|
||||
|
@ -338,7 +340,7 @@ describe('Posts', function () {
|
|||
});
|
||||
|
||||
it('browse posts, ignores staticPages', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available&staticPages=true'))
|
||||
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&staticPages=true`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -361,8 +363,8 @@ describe('Posts', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('denies access with invalid client_secret', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=invalid_secret'))
|
||||
it('denies access with invalid key', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?key=invalid-key'))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/)
|
||||
|
@ -377,47 +379,7 @@ describe('Posts', function () {
|
|||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.errors);
|
||||
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'errorType', 'context']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('denies access with invalid client_id', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=invalid-id&client_secret=not_available'))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(401)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.errors);
|
||||
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'errorType', 'context']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not send CORS headers on an invalid origin', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available'))
|
||||
.set('Origin', 'http://invalid-origin')
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
should.not.exist(res.headers['access-control-allow-origin']);
|
||||
|
||||
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'errorType']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -430,9 +392,7 @@ describe('Posts', function () {
|
|||
}
|
||||
|
||||
request
|
||||
.get(localUtils.API.getApiQuery(
|
||||
'posts/?client_id=ghost-admin&client_secret=not_available&limit=1'
|
||||
))
|
||||
.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&limit=1`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -445,10 +405,7 @@ describe('Posts', function () {
|
|||
post.title.should.eql('Welcome to Ghost');
|
||||
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(
|
||||
'posts/?client_id=ghost-admin&client_secret=not_available&limit=1&filter='
|
||||
+ createFilter(publishedAt, '<')
|
||||
))
|
||||
.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&limit=1&filter=${createFilter(publishedAt, `<`)}`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -462,10 +419,7 @@ describe('Posts', function () {
|
|||
post.title.should.eql('Writing posts with Ghost ✍️');
|
||||
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(
|
||||
'posts/?client_id=ghost-admin&client_secret=not_available&limit=1&filter='
|
||||
+ createFilter(publishedAt, '>')
|
||||
))
|
||||
.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&limit=1&filter=${createFilter(publishedAt, `>`)}`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('Tags Content API V2', function () {
|
|||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'client:trusted-domain');
|
||||
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'api_keys');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -28,8 +28,10 @@ describe('Tags Content API V2', function () {
|
|||
configUtils.restore();
|
||||
});
|
||||
|
||||
const validKey = localUtils.getValidKey();
|
||||
|
||||
it('browse tags without limit defaults to 15', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('tags/?client_id=ghost-admin&client_secret=not_available'))
|
||||
request.get(localUtils.API.getApiQuery(`tags/?key=${validKey}`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -56,7 +58,7 @@ describe('Tags Content API V2', function () {
|
|||
});
|
||||
|
||||
it('browse tags - limit=all should fetch all tags', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('tags/?limit=all&client_id=ghost-admin&client_secret=not_available'))
|
||||
request.get(localUtils.API.getApiQuery(`tags/?limit=all&key=${validKey}`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -77,7 +79,7 @@ describe('Tags Content API V2', function () {
|
|||
});
|
||||
|
||||
it('browse tags without limit=4 fetches 4 tags', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('tags/?limit=4&client_id=ghost-admin&client_secret=not_available'))
|
||||
request.get(localUtils.API.getApiQuery(`tags/?limit=4&key=${validKey}`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -99,7 +101,7 @@ describe('Tags Content API V2', function () {
|
|||
});
|
||||
|
||||
it('browse tags - limit=all should fetch all tags and include count.posts', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('tags/?limit=all&client_id=ghost-admin&client_secret=not_available&include=count.posts'))
|
||||
request.get(localUtils.API.getApiQuery(`tags/?limit=all&key=${validKey}&include=count.posts`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('Users Content API V2', function () {
|
|||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'client:trusted-domain');
|
||||
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'api_keys');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -28,8 +28,10 @@ describe('Users Content API V2', function () {
|
|||
configUtils.restore();
|
||||
});
|
||||
|
||||
const validKey = localUtils.getValidKey();
|
||||
|
||||
it('browse users', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('users/?client_id=ghost-admin&client_secret=not_available'))
|
||||
request.get(localUtils.API.getApiQuery(`users/?key=${validKey}`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -62,7 +64,7 @@ describe('Users Content API V2', function () {
|
|||
});
|
||||
|
||||
it('browse users: ignores fetching roles', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('users/?client_id=ghost-admin&client_secret=not_available&include=roles'))
|
||||
request.get(localUtils.API.getApiQuery(`users/?key=${validKey}&include=roles`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -85,7 +87,7 @@ describe('Users Content API V2', function () {
|
|||
});
|
||||
|
||||
it('browse user by slug: ignores fetching roles', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('users/slug/ghost/?client_id=ghost-admin&client_secret=not_available&include=roles'))
|
||||
request.get(localUtils.API.getApiQuery(`users/slug/ghost/?key=${validKey}&include=roles`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -108,7 +110,7 @@ describe('Users Content API V2', function () {
|
|||
});
|
||||
|
||||
it('browse user by slug: count.posts', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('users/slug/ghost/?client_id=ghost-admin&client_secret=not_available&include=count.posts'))
|
||||
request.get(localUtils.API.getApiQuery(`users/slug/ghost/?key=${validKey}&include=count.posts`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -131,7 +133,7 @@ describe('Users Content API V2', function () {
|
|||
});
|
||||
|
||||
it('browse user by id: count.posts', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('users/1/?client_id=ghost-admin&client_secret=not_available&include=count.posts'))
|
||||
request.get(localUtils.API.getApiQuery(`users/1/?key=${validKey}&include=count.posts`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -154,7 +156,7 @@ describe('Users Content API V2', function () {
|
|||
});
|
||||
|
||||
it('browse user with count.posts', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('users/?client_id=ghost-admin&client_secret=not_available&include=count.posts&order=count.posts ASC'))
|
||||
request.get(localUtils.API.getApiQuery(`users/?key=${validKey}&include=count.posts&order=count.posts ASC`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -199,7 +201,7 @@ describe('Users Content API V2', function () {
|
|||
});
|
||||
|
||||
it('browse user by id: ignores fetching roles', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('users/1/?client_id=ghost-admin&client_secret=not_available&include=roles'))
|
||||
request.get(localUtils.API.getApiQuery(`users/1/?key=${validKey}&include=roles`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
@ -221,7 +223,7 @@ describe('Users Content API V2', function () {
|
|||
});
|
||||
|
||||
it('browse users: post count', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('users/?client_id=ghost-admin&client_secret=not_available&include=count.posts'))
|
||||
request.get(localUtils.API.getApiQuery(`users/?key=${validKey}&include=count.posts`))
|
||||
.set('Origin', testUtils.API.getURL())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
|
|
|
@ -7,5 +7,8 @@ module.exports = {
|
|||
getApiQuery(route) {
|
||||
return url.resolve(API_URL, route);
|
||||
}
|
||||
},
|
||||
getValidKey() {
|
||||
return _.repeat('c', 128);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -46,7 +46,6 @@ describe('Unit: api/shared/http', function () {
|
|||
apiImpl.args[0][0].options.should.eql({
|
||||
context: {
|
||||
api_key_id: null,
|
||||
client_id: null,
|
||||
user: null
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
|||
options: {
|
||||
context: {
|
||||
user: 0,
|
||||
client_id: 1
|
||||
api_key_id: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -34,7 +34,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
|||
options: {
|
||||
context: {
|
||||
user: 0,
|
||||
client_id: 1
|
||||
api_key_id: 1
|
||||
},
|
||||
filter: 'status:published+tag:eins'
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
|||
options: {
|
||||
context: {
|
||||
user: 0,
|
||||
client_id: 1
|
||||
api_key_id: 1
|
||||
},
|
||||
filter: 'page:true+tag:eins'
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
|||
options: {
|
||||
context: {
|
||||
user: 0,
|
||||
client_id: 1
|
||||
api_key_id: 1
|
||||
},
|
||||
filter: 'page:true'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
const common = require('../../../../../server/lib/common');
|
||||
const {authenticateContentApiKey} = require('../../../../../server/services/auth/api-key/content');
|
||||
const models = require('../../../../../server/models');
|
||||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
const testUtils = require('../../../../utils');
|
||||
|
||||
const sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('Content API Key Auth', function () {
|
||||
before(models.init);
|
||||
before(testUtils.teardown);
|
||||
|
||||
this.beforeEach(function () {
|
||||
const fakeApiKey = {
|
||||
id: '1234',
|
||||
type: 'content',
|
||||
secret: Buffer.from('testing').toString('hex'),
|
||||
get(prop) {
|
||||
return this[prop];
|
||||
}
|
||||
};
|
||||
this.fakeApiKey = fakeApiKey;
|
||||
|
||||
this.apiKeyStub = sandbox.stub(models.ApiKey, 'findOne');
|
||||
this.apiKeyStub.returns(new Promise.resolve());
|
||||
this.apiKeyStub.withArgs({secret: fakeApiKey.secret}).returns(new Promise.resolve(fakeApiKey));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should authenticate with known+valid key', function (done) {
|
||||
const req = {
|
||||
query: {
|
||||
key: this.fakeApiKey.secret
|
||||
}
|
||||
};
|
||||
const res = {};
|
||||
|
||||
authenticateContentApiKey(req, res, (arg) => {
|
||||
should.not.exist(arg);
|
||||
req.api_key.should.eql(this.fakeApiKey);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('shouldn\'t authenticate with invalid/unknown key', function (done) {
|
||||
const req = {
|
||||
query: {
|
||||
key: 'unknown'
|
||||
}
|
||||
};
|
||||
const res = {};
|
||||
|
||||
authenticateContentApiKey(req, res, function next(err) {
|
||||
should.exist(err);
|
||||
should.equal(err instanceof common.errors.UnauthorizedError, true);
|
||||
err.code.should.eql('UNKNOWN_CONTENT_API_KEY');
|
||||
should.not.exist(req.api_key);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('shouldn\'t authenticate with a non-content-api key', function (done) {
|
||||
const req = {
|
||||
query: {
|
||||
key: this.fakeApiKey.secret
|
||||
}
|
||||
};
|
||||
const res = {};
|
||||
|
||||
this.fakeApiKey.type = 'admin';
|
||||
|
||||
authenticateContentApiKey(req, res, function next(err) {
|
||||
should.exist(err);
|
||||
should.equal(err instanceof common.errors.UnauthorizedError, true);
|
||||
err.code.should.eql('INVALID_API_KEY_TYPE');
|
||||
should.not.exist(req.api_key);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -385,12 +385,14 @@ DataGenerator.Content = {
|
|||
api_keys: [
|
||||
{
|
||||
id: ObjectId.generate(),
|
||||
type: 'admin'
|
||||
type: 'admin',
|
||||
secret: _.repeat('a', 128)
|
||||
// integration_id: DataGenerator.Content.integrations[0].id
|
||||
},
|
||||
{
|
||||
id: ObjectId.generate(),
|
||||
type: 'content'
|
||||
type: 'content',
|
||||
secret: _.repeat('c', 128)
|
||||
// integration_id: DataGenerator.Content.integrations[0].id
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue