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:
Fabien O'Carroll 2018-10-15 16:23:34 +07:00 committed by GitHub
parent 856af02e08
commit 3db102a776
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 208 additions and 105 deletions

View File

@ -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
}
});

View File

@ -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+\+?/)) {

View File

@ -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
};

View File

@ -0,0 +1,5 @@
module.exports = {
get content() {
return require('./content');
}
};

View File

@ -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;

View File

@ -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;

View File

@ -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.",

View File

@ -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
];

View File

@ -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']);

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -7,5 +7,8 @@ module.exports = {
getApiQuery(route) {
return url.resolve(API_URL, route);
}
},
getValidKey() {
return _.repeat('c', 128);
}
};

View File

@ -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
}
});

View File

@ -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'
}

View File

@ -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();
});
});
});

View File

@ -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
},
{