mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 22:43:30 +03:00
🎉 Middleware refactor: Give the API its own express App (#7537)
refs #4172 * 🎨 Use bodyParser only where it is needed This is a pretty extreme optimisation, however in the interests of killing middleware/index.js it seemed prudent to move towards not having in there that wasn't strictly necessary 😁 We should reassess how apps do this sort of thing, but it seems pretty sane to declare bodyParsing if and only if it is necessary. * 🎨 Move all API code to API router * 🎨 Refactor API into an App, not just a router - Apps have their own rendering engines, only the frontend & the admin panel need views - The API should be JSON only, with minimal middleware - Individual sections within the API could/should be treated as Routers * 🎨 Flatten API middleware inclusion - get rid of the weird middleware object - move the api-only middleware into the middleware/api folder
This commit is contained in:
parent
0227efb41b
commit
61bf54ec88
240
core/server/api/app.js
Normal file
240
core/server/api/app.js
Normal file
@ -0,0 +1,240 @@
|
||||
// # API routes
|
||||
var express = require('express'),
|
||||
tmpdir = require('os').tmpdir,
|
||||
|
||||
// This essentially provides the controllers for the routes
|
||||
api = require('../api'),
|
||||
|
||||
// Include the middleware
|
||||
|
||||
// API specific
|
||||
auth = require('../auth'),
|
||||
cors = require('../middleware/api/cors'), // routes only?!
|
||||
spamPrevention = require('../middleware/api/spam-prevention'), // routes only
|
||||
versionMatch = require('../middleware/api/version-match'), // global
|
||||
|
||||
// Handling uploads & imports
|
||||
upload = require('multer')({dest: tmpdir()}), // routes only
|
||||
validation = require('../middleware/validation'), // routes only
|
||||
|
||||
// Shared
|
||||
bodyParser = require('body-parser'), // global, shared
|
||||
cacheControl = require('../middleware/cache-control'), // global, shared
|
||||
maintenance = require('../middleware/maintenance'), // global, shared
|
||||
errorHandler = require('../middleware/error-handler'), // global, shared
|
||||
|
||||
// Temporary
|
||||
// @TODO find a more appy way to do this!
|
||||
labs = require('../middleware/labs'),
|
||||
|
||||
// @TODO find a better way to bundle these authentication packages
|
||||
// Authentication for public endpoints
|
||||
authenticatePublic = [
|
||||
auth.authenticate.authenticateClient,
|
||||
auth.authenticate.authenticateUser,
|
||||
auth.authorize.requiresAuthorizedUserPublicAPI,
|
||||
// @TODO do we really need this multiple times or should it be global?
|
||||
cors
|
||||
],
|
||||
// Require user for private endpoints
|
||||
authenticatePrivate = [
|
||||
auth.authenticate.authenticateClient,
|
||||
auth.authenticate.authenticateUser,
|
||||
auth.authorize.requiresAuthorizedUser,
|
||||
// @TODO do we really need this multiple times or should it be global?
|
||||
cors
|
||||
];
|
||||
|
||||
// @TODO refactor/clean this up - how do we want the routing to work long term?
|
||||
function apiRoutes() {
|
||||
var apiRouter = express.Router();
|
||||
|
||||
// alias delete with del
|
||||
apiRouter.del = apiRouter.delete;
|
||||
|
||||
// ## CORS pre-flight check
|
||||
apiRouter.options('*', cors);
|
||||
|
||||
// ## Configuration
|
||||
apiRouter.get('/configuration', authenticatePrivate, api.http(api.configuration.read));
|
||||
apiRouter.get('/configuration/:key', authenticatePrivate, api.http(api.configuration.read));
|
||||
apiRouter.get('/configuration/timezones', authenticatePrivate, api.http(api.configuration.read));
|
||||
|
||||
// ## Posts
|
||||
apiRouter.get('/posts', authenticatePublic, api.http(api.posts.browse));
|
||||
|
||||
apiRouter.post('/posts', authenticatePrivate, api.http(api.posts.add));
|
||||
apiRouter.get('/posts/:id', authenticatePublic, api.http(api.posts.read));
|
||||
apiRouter.get('/posts/slug/:slug', authenticatePublic, api.http(api.posts.read));
|
||||
apiRouter.put('/posts/:id', authenticatePrivate, api.http(api.posts.edit));
|
||||
apiRouter.del('/posts/:id', authenticatePrivate, api.http(api.posts.destroy));
|
||||
|
||||
// ## Schedules
|
||||
apiRouter.put('/schedules/posts/:id', [
|
||||
auth.authenticate.authenticateClient,
|
||||
auth.authenticate.authenticateUser
|
||||
], api.http(api.schedules.publishPost));
|
||||
|
||||
// ## Settings
|
||||
apiRouter.get('/settings', authenticatePrivate, api.http(api.settings.browse));
|
||||
apiRouter.get('/settings/:key', authenticatePrivate, api.http(api.settings.read));
|
||||
apiRouter.put('/settings', authenticatePrivate, api.http(api.settings.edit));
|
||||
|
||||
// ## Users
|
||||
apiRouter.get('/users', authenticatePublic, api.http(api.users.browse));
|
||||
apiRouter.get('/users/:id', authenticatePublic, api.http(api.users.read));
|
||||
apiRouter.get('/users/slug/:slug', authenticatePublic, api.http(api.users.read));
|
||||
apiRouter.get('/users/email/:email', authenticatePublic, api.http(api.users.read));
|
||||
|
||||
apiRouter.put('/users/password', authenticatePrivate, api.http(api.users.changePassword));
|
||||
apiRouter.put('/users/owner', authenticatePrivate, api.http(api.users.transferOwnership));
|
||||
apiRouter.put('/users/:id', authenticatePrivate, api.http(api.users.edit));
|
||||
|
||||
apiRouter.post('/users', authenticatePrivate, api.http(api.users.add));
|
||||
apiRouter.del('/users/:id', authenticatePrivate, api.http(api.users.destroy));
|
||||
|
||||
// ## Tags
|
||||
apiRouter.get('/tags', authenticatePublic, api.http(api.tags.browse));
|
||||
apiRouter.get('/tags/:id', authenticatePublic, api.http(api.tags.read));
|
||||
apiRouter.get('/tags/slug/:slug', authenticatePublic, api.http(api.tags.read));
|
||||
apiRouter.post('/tags', authenticatePrivate, api.http(api.tags.add));
|
||||
apiRouter.put('/tags/:id', authenticatePrivate, api.http(api.tags.edit));
|
||||
apiRouter.del('/tags/:id', authenticatePrivate, api.http(api.tags.destroy));
|
||||
|
||||
// ## Subscribers
|
||||
apiRouter.get('/subscribers', labs.subscribers, authenticatePrivate, api.http(api.subscribers.browse));
|
||||
apiRouter.get('/subscribers/csv', labs.subscribers, authenticatePrivate, api.http(api.subscribers.exportCSV));
|
||||
apiRouter.post('/subscribers/csv',
|
||||
labs.subscribers,
|
||||
authenticatePrivate,
|
||||
upload.single('subscribersfile'),
|
||||
validation.upload({type: 'subscribers'}),
|
||||
api.http(api.subscribers.importCSV)
|
||||
);
|
||||
apiRouter.get('/subscribers/:id', labs.subscribers, authenticatePrivate, api.http(api.subscribers.read));
|
||||
apiRouter.post('/subscribers', labs.subscribers, authenticatePublic, api.http(api.subscribers.add));
|
||||
apiRouter.put('/subscribers/:id', labs.subscribers, authenticatePrivate, api.http(api.subscribers.edit));
|
||||
apiRouter.del('/subscribers/:id', labs.subscribers, authenticatePrivate, api.http(api.subscribers.destroy));
|
||||
|
||||
// ## Roles
|
||||
apiRouter.get('/roles/', authenticatePrivate, api.http(api.roles.browse));
|
||||
|
||||
// ## Clients
|
||||
apiRouter.get('/clients/slug/:slug', api.http(api.clients.read));
|
||||
|
||||
// ## Slugs
|
||||
apiRouter.get('/slugs/:type/:name', authenticatePrivate, api.http(api.slugs.generate));
|
||||
|
||||
// ## Themes
|
||||
apiRouter.get('/themes/:name/download',
|
||||
authenticatePrivate,
|
||||
api.http(api.themes.download)
|
||||
);
|
||||
|
||||
apiRouter.post('/themes/upload',
|
||||
authenticatePrivate,
|
||||
upload.single('theme'),
|
||||
validation.upload({type: 'themes'}),
|
||||
api.http(api.themes.upload)
|
||||
);
|
||||
|
||||
apiRouter.del('/themes/:name',
|
||||
authenticatePrivate,
|
||||
api.http(api.themes.destroy)
|
||||
);
|
||||
|
||||
// ## Notifications
|
||||
apiRouter.get('/notifications', authenticatePrivate, api.http(api.notifications.browse));
|
||||
apiRouter.post('/notifications', authenticatePrivate, api.http(api.notifications.add));
|
||||
apiRouter.del('/notifications/:id', authenticatePrivate, api.http(api.notifications.destroy));
|
||||
|
||||
// ## DB
|
||||
apiRouter.get('/db', authenticatePrivate, api.http(api.db.exportContent));
|
||||
apiRouter.post('/db',
|
||||
authenticatePrivate,
|
||||
upload.single('importfile'),
|
||||
validation.upload({type: 'db'}),
|
||||
api.http(api.db.importContent)
|
||||
);
|
||||
apiRouter.del('/db', authenticatePrivate, api.http(api.db.deleteAllContent));
|
||||
|
||||
// ## Mail
|
||||
apiRouter.post('/mail', authenticatePrivate, api.http(api.mail.send));
|
||||
apiRouter.post('/mail/test', authenticatePrivate, api.http(api.mail.sendTest));
|
||||
|
||||
// ## Slack
|
||||
apiRouter.post('/slack/test', authenticatePrivate, api.http(api.slack.sendTest));
|
||||
|
||||
// ## Authentication
|
||||
apiRouter.post('/authentication/passwordreset',
|
||||
spamPrevention.forgotten,
|
||||
api.http(api.authentication.generateResetToken)
|
||||
);
|
||||
apiRouter.put('/authentication/passwordreset', api.http(api.authentication.resetPassword));
|
||||
apiRouter.post('/authentication/invitation', api.http(api.authentication.acceptInvitation));
|
||||
apiRouter.get('/authentication/invitation', api.http(api.authentication.isInvitation));
|
||||
apiRouter.post('/authentication/setup', api.http(api.authentication.setup));
|
||||
apiRouter.put('/authentication/setup', authenticatePrivate, api.http(api.authentication.updateSetup));
|
||||
apiRouter.get('/authentication/setup', api.http(api.authentication.isSetup));
|
||||
apiRouter.post('/authentication/token',
|
||||
spamPrevention.signin,
|
||||
auth.authenticate.authenticateClient,
|
||||
auth.oauth.generateAccessToken
|
||||
);
|
||||
|
||||
apiRouter.post('/authentication/ghost', [
|
||||
auth.authenticate.authenticateClient,
|
||||
auth.authenticate.authenticateGhostUser,
|
||||
api.http(api.authentication.createTokens)
|
||||
]);
|
||||
|
||||
apiRouter.post('/authentication/revoke', authenticatePrivate, api.http(api.authentication.revoke));
|
||||
|
||||
// ## Uploads
|
||||
// @TODO: rename endpoint to /images/upload (or similar)
|
||||
apiRouter.post('/uploads',
|
||||
authenticatePrivate,
|
||||
upload.single('uploadimage'),
|
||||
validation.upload({type: 'images'}),
|
||||
api.http(api.uploads.add)
|
||||
);
|
||||
|
||||
// ## Invites
|
||||
apiRouter.get('/invites', authenticatePrivate, api.http(api.invites.browse));
|
||||
apiRouter.get('/invites/:id', authenticatePrivate, api.http(api.invites.read));
|
||||
apiRouter.post('/invites', authenticatePrivate, api.http(api.invites.add));
|
||||
apiRouter.del('/invites/:id', authenticatePrivate, api.http(api.invites.destroy));
|
||||
|
||||
return apiRouter;
|
||||
}
|
||||
|
||||
module.exports = function setupApiApp() {
|
||||
var apiApp = express();
|
||||
|
||||
// API middleware
|
||||
|
||||
// Body parsing
|
||||
apiApp.use(bodyParser.json({limit: '1mb'}));
|
||||
apiApp.use(bodyParser.urlencoded({extended: true, limit: '1mb'}));
|
||||
|
||||
// send 503 json response in case of maintenance
|
||||
apiApp.use(maintenance);
|
||||
|
||||
// @TODO check SSL and pretty URLS is needed here too, once we've finished refactoring
|
||||
|
||||
// Check version matches for API requests, depends on res.locals.safeVersion being set
|
||||
// Therefore must come after themeHandler.ghostLocals, for now
|
||||
apiApp.use(versionMatch);
|
||||
|
||||
// API shouldn't be cached
|
||||
apiApp.use(cacheControl('private'));
|
||||
|
||||
// Routing
|
||||
apiApp.use(apiRoutes());
|
||||
|
||||
// API error handling
|
||||
// @TODO: split the API error handling into its own thing?
|
||||
apiApp.use(errorHandler);
|
||||
|
||||
return apiApp;
|
||||
};
|
@ -12,7 +12,7 @@ var path = require('path'),
|
||||
function controller(req, res, next) {
|
||||
var defaultView = path.resolve(__dirname, 'views', 'amp.hbs'),
|
||||
paths = templates.getActiveThemePaths(req.app.get('activeTheme')),
|
||||
data = req.body;
|
||||
data = req.amp;
|
||||
|
||||
if (res.error) {
|
||||
data.error = res.error;
|
||||
@ -33,10 +33,12 @@ function controller(req, res, next) {
|
||||
}
|
||||
|
||||
function getPostData(req, res, next) {
|
||||
// Create a req property where we can store our data
|
||||
req.amp = {};
|
||||
postLookup(res.locals.relativeUrl)
|
||||
.then(function (result) {
|
||||
if (result && result.post) {
|
||||
req.body.post = result.post;
|
||||
req.amp.post = result.post;
|
||||
}
|
||||
|
||||
next();
|
||||
|
@ -37,7 +37,7 @@ describe('AMP Controller', function () {
|
||||
route: {path: '/'},
|
||||
query: {r: ''},
|
||||
params: {},
|
||||
body: {}
|
||||
amp: {}
|
||||
};
|
||||
|
||||
defaultPath = path.join(configUtils.config.get('paths').appRoot, '/core/server/apps/amp/lib/views/amp.hbs');
|
||||
@ -148,7 +148,7 @@ describe('AMP getPostData', function () {
|
||||
};
|
||||
|
||||
req = {
|
||||
body: {
|
||||
amp: {
|
||||
post: {}
|
||||
}
|
||||
};
|
||||
@ -173,7 +173,7 @@ describe('AMP getPostData', function () {
|
||||
ampController.__set__('postLookup', postLookupStub);
|
||||
|
||||
ampController.getPostData(req, res, function () {
|
||||
req.body.post.should.be.eql({
|
||||
req.amp.post.should.be.eql({
|
||||
id: '1',
|
||||
slug: 'welcome-to-ghost',
|
||||
isAmpURL: true
|
||||
@ -197,7 +197,7 @@ describe('AMP getPostData', function () {
|
||||
err.message.should.be.eql('not found');
|
||||
err.statusCode.should.be.eql(404);
|
||||
err.errorType.should.be.eql('NotFoundError');
|
||||
req.body.post.should.be.eql({});
|
||||
req.amp.should.be.eql({});
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -210,7 +210,7 @@ describe('AMP getPostData', function () {
|
||||
ampController.getPostData(req, res, function (err) {
|
||||
should.exist(err);
|
||||
err.should.be.eql('not found');
|
||||
req.body.post.should.be.eql({});
|
||||
req.amp.should.be.eql({});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
var path = require('path'),
|
||||
express = require('express'),
|
||||
middleware = require('./middleware'),
|
||||
bodyParser = require('body-parser'),
|
||||
templates = require('../../../controllers/frontend/templates'),
|
||||
setResponseContext = require('../../../controllers/frontend/context'),
|
||||
privateRouter = express.Router();
|
||||
@ -29,6 +30,7 @@ privateRouter.route('/')
|
||||
controller
|
||||
)
|
||||
.post(
|
||||
bodyParser.urlencoded({extended: true}),
|
||||
middleware.isPrivateSessionAuth,
|
||||
middleware.spamPrevention,
|
||||
middleware.authenticateProtection,
|
||||
|
@ -2,6 +2,7 @@ var path = require('path'),
|
||||
express = require('express'),
|
||||
_ = require('lodash'),
|
||||
subscribeRouter = express.Router(),
|
||||
bodyParser = require('body-parser'),
|
||||
|
||||
// Dirty requires
|
||||
api = require('../../../api'),
|
||||
@ -92,6 +93,7 @@ subscribeRouter.route('/')
|
||||
controller
|
||||
)
|
||||
.post(
|
||||
bodyParser.urlencoded({extended: true}),
|
||||
honeyPot,
|
||||
handleSource,
|
||||
storeSubscriber,
|
||||
|
@ -3,7 +3,7 @@ var oauth2orize = require('oauth2orize'),
|
||||
utils = require('../utils'),
|
||||
errors = require('../errors'),
|
||||
authenticationAPI = require('../api/authentication'),
|
||||
spamPrevention = require('../middleware/spam-prevention'),
|
||||
spamPrevention = require('../middleware/api/spam-prevention'),
|
||||
i18n = require('../i18n'),
|
||||
oauthServer,
|
||||
oauth;
|
||||
|
@ -2,7 +2,7 @@ var cors = require('cors'),
|
||||
_ = require('lodash'),
|
||||
url = require('url'),
|
||||
os = require('os'),
|
||||
config = require('../config'),
|
||||
config = require('../../config'),
|
||||
whitelist = [],
|
||||
ENABLE_CORS = {origin: true, maxAge: 86400},
|
||||
DISABLE_CORS = {origin: false};
|
@ -7,9 +7,9 @@
|
||||
// Helpers to handle spam detection on signin, forgot password, and protected pages.
|
||||
|
||||
var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
config = require('../config'),
|
||||
i18n = require('../i18n'),
|
||||
errors = require('../../errors'),
|
||||
config = require('../../config'),
|
||||
i18n = require('../../i18n'),
|
||||
loginSecurity = [],
|
||||
forgottenSecurity = [],
|
||||
spamPrevention;
|
@ -1,56 +1,39 @@
|
||||
var debug = require('debug')('ghost:middleware'),
|
||||
bodyParser = require('body-parser'),
|
||||
compress = require('compression'),
|
||||
express = require('express'),
|
||||
hbs = require('express-hbs'),
|
||||
path = require('path'),
|
||||
netjet = require('netjet'),
|
||||
multer = require('multer'),
|
||||
tmpdir = require('os').tmpdir,
|
||||
serveStatic = require('express').static,
|
||||
routes = require('../routes'),
|
||||
|
||||
// app requires
|
||||
config = require('../config'),
|
||||
storage = require('../storage'),
|
||||
logging = require('../logging'),
|
||||
errors = require('../errors'),
|
||||
helpers = require('../helpers'),
|
||||
i18n = require('../i18n'),
|
||||
logging = require('../logging'),
|
||||
routes = require('../routes'),
|
||||
storage = require('../storage'),
|
||||
utils = require('../utils'),
|
||||
|
||||
// This should probably be an internal app
|
||||
sitemapHandler = require('../data/xml/sitemap/handler'),
|
||||
cacheControl = require('./cache-control'),
|
||||
checkSSL = require('./check-ssl'),
|
||||
decideIsAdmin = require('./decide-is-admin'),
|
||||
redirectToSetup = require('./redirect-to-setup'),
|
||||
|
||||
// middleware
|
||||
compress = require('compression'),
|
||||
netjet = require('netjet'),
|
||||
serveStatic = require('express').static,
|
||||
// local middleware
|
||||
cacheControl = require('./cache-control'),
|
||||
checkSSL = require('./check-ssl'),
|
||||
decideIsAdmin = require('./decide-is-admin'),
|
||||
errorHandler = require('./error-handler'),
|
||||
ghostLocals = require('./ghost-locals'),
|
||||
maintenance = require('./maintenance'),
|
||||
prettyURLs = require('./pretty-urls'),
|
||||
serveSharedFile = require('./serve-shared-file'),
|
||||
spamPrevention = require('./spam-prevention'),
|
||||
staticTheme = require('./static-theme'),
|
||||
themeHandler = require('./theme-handler'),
|
||||
maintenance = require('./maintenance'),
|
||||
errorHandler = require('./error-handler'),
|
||||
versionMatch = require('./api/version-match'),
|
||||
cors = require('./cors'),
|
||||
validation = require('./validation'),
|
||||
labs = require('./labs'),
|
||||
helpers = require('../helpers'),
|
||||
middleware,
|
||||
setupMiddleware;
|
||||
redirectToSetup = require('./redirect-to-setup'),
|
||||
serveSharedFile = require('./serve-shared-file'),
|
||||
staticTheme = require('./static-theme'),
|
||||
themeHandler = require('./theme-handler');
|
||||
|
||||
middleware = {
|
||||
upload: multer({dest: tmpdir()}),
|
||||
validation: validation,
|
||||
cacheControl: cacheControl,
|
||||
spamPrevention: spamPrevention,
|
||||
api: {
|
||||
errorHandler: errorHandler,
|
||||
cors: cors,
|
||||
labs: labs,
|
||||
versionMatch: versionMatch,
|
||||
maintenance: maintenance
|
||||
}
|
||||
};
|
||||
|
||||
setupMiddleware = function setupMiddleware(blogApp) {
|
||||
module.exports = function setupMiddleware(blogApp) {
|
||||
debug('Middleware start');
|
||||
|
||||
var adminApp = express(),
|
||||
@ -171,23 +154,18 @@ setupMiddleware = function setupMiddleware(blogApp) {
|
||||
// must happen AFTER asset loading and BEFORE routing
|
||||
blogApp.use(prettyURLs);
|
||||
|
||||
// Body parsing
|
||||
blogApp.use(bodyParser.json({limit: '1mb'}));
|
||||
blogApp.use(bodyParser.urlencoded({extended: true, limit: '1mb'}));
|
||||
|
||||
// ### Caching
|
||||
// Blog frontend is cacheable
|
||||
blogApp.use(cacheControl('public'));
|
||||
// Admin shouldn't be cached
|
||||
adminApp.use(cacheControl('private'));
|
||||
// API shouldn't be cached
|
||||
blogApp.use(routes.apiBaseUri, cacheControl('private'));
|
||||
|
||||
debug('General middleware done');
|
||||
|
||||
// ### Routing
|
||||
// Set up API routes
|
||||
blogApp.use(routes.apiBaseUri, routes.api(middleware));
|
||||
// Load the API
|
||||
// @TODO: finish refactoring the API app
|
||||
// @TODO: decide what to do with these paths - config defaults? config overrides?
|
||||
blogApp.use('/ghost/api/v0.1/', require('../api/app')());
|
||||
|
||||
// Mount admin express app to /ghost and set up routes
|
||||
adminApp.use(redirectToSetup);
|
||||
@ -210,7 +188,3 @@ setupMiddleware = function setupMiddleware(blogApp) {
|
||||
blogApp.use(errorHandler);
|
||||
debug('Middleware end');
|
||||
};
|
||||
|
||||
module.exports = setupMiddleware;
|
||||
// Export middleware functions directly
|
||||
module.exports.middleware = middleware;
|
||||
|
@ -1,193 +0,0 @@
|
||||
// # API routes
|
||||
var express = require('express'),
|
||||
api = require('../api'),
|
||||
auth = require('../auth'),
|
||||
apiRoutes;
|
||||
|
||||
apiRoutes = function apiRoutes(middleware) {
|
||||
var router = express.Router(),
|
||||
// Authentication for public endpoints
|
||||
authenticatePublic = [
|
||||
auth.authenticate.authenticateClient,
|
||||
auth.authenticate.authenticateUser,
|
||||
auth.authorize.requiresAuthorizedUserPublicAPI,
|
||||
middleware.api.cors
|
||||
],
|
||||
// Require user for private endpoints
|
||||
authenticatePrivate = [
|
||||
auth.authenticate.authenticateClient,
|
||||
auth.authenticate.authenticateUser,
|
||||
auth.authorize.requiresAuthorizedUser,
|
||||
middleware.api.cors
|
||||
];
|
||||
|
||||
// alias delete with del
|
||||
router.del = router.delete;
|
||||
|
||||
// send 503 json response in case of maintenance
|
||||
router.use(middleware.api.maintenance);
|
||||
|
||||
// Check version matches for API requests, depends on res.locals.safeVersion being set
|
||||
// Therefore must come after themeHandler.ghostLocals, for now
|
||||
router.use(middleware.api.versionMatch);
|
||||
|
||||
// ## CORS pre-flight check
|
||||
router.options('*', middleware.api.cors);
|
||||
|
||||
// ## Configuration
|
||||
router.get('/configuration', authenticatePrivate, api.http(api.configuration.read));
|
||||
router.get('/configuration/:key', authenticatePrivate, api.http(api.configuration.read));
|
||||
router.get('/configuration/timezones', authenticatePrivate, api.http(api.configuration.read));
|
||||
|
||||
// ## Posts
|
||||
router.get('/posts', authenticatePublic, api.http(api.posts.browse));
|
||||
|
||||
router.post('/posts', authenticatePrivate, api.http(api.posts.add));
|
||||
router.get('/posts/:id', authenticatePublic, api.http(api.posts.read));
|
||||
router.get('/posts/slug/:slug', authenticatePublic, api.http(api.posts.read));
|
||||
router.put('/posts/:id', authenticatePrivate, api.http(api.posts.edit));
|
||||
router.del('/posts/:id', authenticatePrivate, api.http(api.posts.destroy));
|
||||
|
||||
// ## Schedules
|
||||
router.put('/schedules/posts/:id', [
|
||||
auth.authenticate.authenticateClient,
|
||||
auth.authenticate.authenticateUser
|
||||
], api.http(api.schedules.publishPost));
|
||||
|
||||
// ## Settings
|
||||
router.get('/settings', authenticatePrivate, api.http(api.settings.browse));
|
||||
router.get('/settings/:key', authenticatePrivate, api.http(api.settings.read));
|
||||
router.put('/settings', authenticatePrivate, api.http(api.settings.edit));
|
||||
|
||||
// ## Users
|
||||
router.get('/users', authenticatePublic, api.http(api.users.browse));
|
||||
router.get('/users/:id', authenticatePublic, api.http(api.users.read));
|
||||
router.get('/users/slug/:slug', authenticatePublic, api.http(api.users.read));
|
||||
router.get('/users/email/:email', authenticatePublic, api.http(api.users.read));
|
||||
|
||||
router.put('/users/password', authenticatePrivate, api.http(api.users.changePassword));
|
||||
router.put('/users/owner', authenticatePrivate, api.http(api.users.transferOwnership));
|
||||
router.put('/users/:id', authenticatePrivate, api.http(api.users.edit));
|
||||
|
||||
router.post('/users', authenticatePrivate, api.http(api.users.add));
|
||||
router.del('/users/:id', authenticatePrivate, api.http(api.users.destroy));
|
||||
|
||||
// ## Tags
|
||||
router.get('/tags', authenticatePublic, api.http(api.tags.browse));
|
||||
router.get('/tags/:id', authenticatePublic, api.http(api.tags.read));
|
||||
router.get('/tags/slug/:slug', authenticatePublic, api.http(api.tags.read));
|
||||
router.post('/tags', authenticatePrivate, api.http(api.tags.add));
|
||||
router.put('/tags/:id', authenticatePrivate, api.http(api.tags.edit));
|
||||
router.del('/tags/:id', authenticatePrivate, api.http(api.tags.destroy));
|
||||
|
||||
// ## Subscribers
|
||||
router.get('/subscribers', middleware.api.labs.subscribers, authenticatePrivate, api.http(api.subscribers.browse));
|
||||
router.get('/subscribers/csv', middleware.api.labs.subscribers, authenticatePrivate, api.http(api.subscribers.exportCSV));
|
||||
router.post('/subscribers/csv',
|
||||
middleware.api.labs.subscribers,
|
||||
authenticatePrivate,
|
||||
middleware.upload.single('subscribersfile'),
|
||||
middleware.validation.upload({type: 'subscribers'}),
|
||||
api.http(api.subscribers.importCSV)
|
||||
);
|
||||
router.get('/subscribers/:id', middleware.api.labs.subscribers, authenticatePrivate, api.http(api.subscribers.read));
|
||||
router.post('/subscribers', middleware.api.labs.subscribers, authenticatePublic, api.http(api.subscribers.add));
|
||||
router.put('/subscribers/:id', middleware.api.labs.subscribers, authenticatePrivate, api.http(api.subscribers.edit));
|
||||
router.del('/subscribers/:id', middleware.api.labs.subscribers, authenticatePrivate, api.http(api.subscribers.destroy));
|
||||
|
||||
// ## Roles
|
||||
router.get('/roles/', authenticatePrivate, api.http(api.roles.browse));
|
||||
|
||||
// ## Clients
|
||||
router.get('/clients/slug/:slug', api.http(api.clients.read));
|
||||
|
||||
// ## Slugs
|
||||
router.get('/slugs/:type/:name', authenticatePrivate, api.http(api.slugs.generate));
|
||||
|
||||
// ## Themes
|
||||
router.get('/themes/:name/download',
|
||||
authenticatePrivate,
|
||||
api.http(api.themes.download)
|
||||
);
|
||||
|
||||
router.post('/themes/upload',
|
||||
authenticatePrivate,
|
||||
middleware.upload.single('theme'),
|
||||
middleware.validation.upload({type: 'themes'}),
|
||||
api.http(api.themes.upload)
|
||||
);
|
||||
|
||||
router.del('/themes/:name',
|
||||
authenticatePrivate,
|
||||
api.http(api.themes.destroy)
|
||||
);
|
||||
|
||||
// ## Notifications
|
||||
router.get('/notifications', authenticatePrivate, api.http(api.notifications.browse));
|
||||
router.post('/notifications', authenticatePrivate, api.http(api.notifications.add));
|
||||
router.del('/notifications/:id', authenticatePrivate, api.http(api.notifications.destroy));
|
||||
|
||||
// ## DB
|
||||
router.get('/db', authenticatePrivate, api.http(api.db.exportContent));
|
||||
router.post('/db',
|
||||
authenticatePrivate,
|
||||
middleware.upload.single('importfile'),
|
||||
middleware.validation.upload({type: 'db'}),
|
||||
api.http(api.db.importContent)
|
||||
);
|
||||
router.del('/db', authenticatePrivate, api.http(api.db.deleteAllContent));
|
||||
|
||||
// ## Mail
|
||||
router.post('/mail', authenticatePrivate, api.http(api.mail.send));
|
||||
router.post('/mail/test', authenticatePrivate, api.http(api.mail.sendTest));
|
||||
|
||||
// ## Slack
|
||||
router.post('/slack/test', authenticatePrivate, api.http(api.slack.sendTest));
|
||||
|
||||
// ## Authentication
|
||||
router.post('/authentication/passwordreset',
|
||||
middleware.spamPrevention.forgotten,
|
||||
api.http(api.authentication.generateResetToken)
|
||||
);
|
||||
router.put('/authentication/passwordreset', api.http(api.authentication.resetPassword));
|
||||
router.post('/authentication/invitation', api.http(api.authentication.acceptInvitation));
|
||||
router.get('/authentication/invitation', api.http(api.authentication.isInvitation));
|
||||
router.post('/authentication/setup', api.http(api.authentication.setup));
|
||||
router.put('/authentication/setup', authenticatePrivate, api.http(api.authentication.updateSetup));
|
||||
router.get('/authentication/setup', api.http(api.authentication.isSetup));
|
||||
router.post('/authentication/token',
|
||||
middleware.spamPrevention.signin,
|
||||
auth.authenticate.authenticateClient,
|
||||
auth.oauth.generateAccessToken
|
||||
);
|
||||
|
||||
router.post('/authentication/ghost', [
|
||||
auth.authenticate.authenticateClient,
|
||||
auth.authenticate.authenticateGhostUser,
|
||||
api.http(api.authentication.createTokens)
|
||||
]);
|
||||
|
||||
router.post('/authentication/revoke', authenticatePrivate, api.http(api.authentication.revoke));
|
||||
|
||||
// ## Uploads
|
||||
// @TODO: rename endpoint to /images/upload (or similar)
|
||||
router.post('/uploads',
|
||||
authenticatePrivate,
|
||||
middleware.upload.single('uploadimage'),
|
||||
middleware.validation.upload({type: 'images'}),
|
||||
api.http(api.uploads.add)
|
||||
);
|
||||
|
||||
// ## Invites
|
||||
router.get('/invites', authenticatePrivate, api.http(api.invites.browse));
|
||||
router.get('/invites/:id', authenticatePrivate, api.http(api.invites.read));
|
||||
router.post('/invites', authenticatePrivate, api.http(api.invites.add));
|
||||
router.del('/invites/:id', authenticatePrivate, api.http(api.invites.destroy));
|
||||
|
||||
// API Router middleware
|
||||
router.use(middleware.api.errorHandler);
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
module.exports = apiRoutes;
|
@ -1,10 +1,4 @@
|
||||
var api = require('./api'),
|
||||
admin = require('./admin'),
|
||||
frontend = require('./frontend');
|
||||
|
||||
module.exports = {
|
||||
apiBaseUri: '/ghost/api/v0.1/',
|
||||
api: api,
|
||||
admin: admin,
|
||||
frontend: frontend
|
||||
admin: require('./admin'),
|
||||
frontend: require('./frontend')
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
var sinon = require('sinon'),
|
||||
should = require('should'),
|
||||
rewire = require('rewire'),
|
||||
configUtils = require('../../utils/configUtils'),
|
||||
cors = rewire('../../../server/middleware/cors');
|
||||
configUtils = require('../../../utils/configUtils'),
|
||||
cors = rewire('../../../../server/middleware/api/cors');
|
||||
|
||||
describe('cors', function () {
|
||||
var res, req, next, sandbox;
|
||||
@ -33,7 +33,7 @@ describe('cors', function () {
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
configUtils.restore();
|
||||
cors = rewire('../../../server/middleware/cors');
|
||||
cors = rewire('../../../../server/middleware/api/cors');
|
||||
});
|
||||
|
||||
it('should not be enabled without a request origin header', function (done) {
|
@ -1,7 +1,7 @@
|
||||
var should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
rewire = require('rewire'),
|
||||
middleware = require('../../../server/middleware').middleware;
|
||||
spamPrevention = require('../../../../server/middleware/api/spam-prevention');
|
||||
|
||||
describe('Middleware: spamPrevention', function () {
|
||||
var sandbox,
|
||||
@ -19,7 +19,7 @@ describe('Middleware: spamPrevention', function () {
|
||||
spyNext = sinon.spy(function (param) {
|
||||
error = param;
|
||||
});
|
||||
middleware.spamPrevention = rewire('../../../server/middleware/spam-prevention');
|
||||
spamPrevention = rewire('../../../../server/middleware/api/spam-prevention');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -41,7 +41,7 @@ describe('Middleware: spamPrevention', function () {
|
||||
|
||||
it('calls next if refreshing the token', function (done) {
|
||||
req.body.grant_type = 'refresh_token';
|
||||
middleware.spamPrevention.signin(req, null, next);
|
||||
spamPrevention.signin(req, null, next);
|
||||
|
||||
next.calledOnce.should.be.true();
|
||||
done();
|
||||
@ -50,7 +50,7 @@ describe('Middleware: spamPrevention', function () {
|
||||
it ('creates a BadRequestError when there\'s no username', function (done) {
|
||||
req.body = {};
|
||||
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
spamPrevention.signin(req, null, spyNext);
|
||||
|
||||
should.exist(error);
|
||||
error.errorType.should.eql('BadRequestError');
|
||||
@ -59,10 +59,10 @@ describe('Middleware: spamPrevention', function () {
|
||||
|
||||
it ('rate limits after 10 attempts', function (done) {
|
||||
for (var ndx = 0; ndx < 10; ndx = ndx + 1) {
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
spamPrevention.signin(req, null, spyNext);
|
||||
}
|
||||
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
spamPrevention.signin(req, null, spyNext);
|
||||
should.exist(error);
|
||||
error.errorType.should.eql('TooManyRequestsError');
|
||||
|
||||
@ -76,10 +76,10 @@ describe('Middleware: spamPrevention', function () {
|
||||
});
|
||||
|
||||
for (ndx = 0; ndx < 10; ndx = ndx + 1) {
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
spamPrevention.signin(req, null, spyNext);
|
||||
}
|
||||
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
spamPrevention.signin(req, null, spyNext);
|
||||
error.errorType.should.eql('TooManyRequestsError');
|
||||
error = null;
|
||||
|
||||
@ -89,7 +89,7 @@ describe('Middleware: spamPrevention', function () {
|
||||
return [3610, 10];
|
||||
});
|
||||
|
||||
middleware.spamPrevention.signin(req, null, spyNext);
|
||||
spamPrevention.signin(req, null, spyNext);
|
||||
should(error).equal(undefined);
|
||||
spyNext.called.should.be.true();
|
||||
|
||||
@ -117,7 +117,7 @@ describe('Middleware: spamPrevention', function () {
|
||||
passwordreset: [{}]
|
||||
};
|
||||
|
||||
middleware.spamPrevention.forgotten(req, null, spyNext);
|
||||
spamPrevention.forgotten(req, null, spyNext);
|
||||
error.errorType.should.eql('BadRequestError');
|
||||
|
||||
done();
|
||||
@ -125,10 +125,10 @@ describe('Middleware: spamPrevention', function () {
|
||||
|
||||
it ('creates an unauthorized error after 5 attempts with same email', function (done) {
|
||||
for (var ndx = 0; ndx < 6; ndx = ndx + 1) {
|
||||
middleware.spamPrevention.forgotten(req, null, spyNext);
|
||||
spamPrevention.forgotten(req, null, spyNext);
|
||||
}
|
||||
|
||||
middleware.spamPrevention.forgotten(req, null, spyNext);
|
||||
spamPrevention.forgotten(req, null, spyNext);
|
||||
error.errorType.should.eql('TooManyRequestsError');
|
||||
|
||||
done();
|
||||
@ -143,10 +143,10 @@ describe('Middleware: spamPrevention', function () {
|
||||
{email: email}
|
||||
];
|
||||
|
||||
middleware.spamPrevention.forgotten(req, null, spyNext);
|
||||
spamPrevention.forgotten(req, null, spyNext);
|
||||
}
|
||||
|
||||
middleware.spamPrevention.forgotten(req, null, spyNext);
|
||||
spamPrevention.forgotten(req, null, spyNext);
|
||||
error.errorType.should.eql('TooManyRequestsError');
|
||||
|
||||
done();
|
@ -1,6 +1,6 @@
|
||||
var should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
middleware = require('../../../server/middleware').middleware;
|
||||
cacheControl = require('../../../server/middleware/cache-control');
|
||||
|
||||
describe('Middleware: cacheControl', function () {
|
||||
var sandbox,
|
||||
@ -19,7 +19,7 @@ describe('Middleware: cacheControl', function () {
|
||||
});
|
||||
|
||||
it('correctly sets the public profile headers', function (done) {
|
||||
middleware.cacheControl('public')(null, res, function (a) {
|
||||
cacheControl('public')(null, res, function (a) {
|
||||
should.not.exist(a);
|
||||
res.set.calledOnce.should.be.true();
|
||||
res.set.calledWith({'Cache-Control': 'public, max-age=0'});
|
||||
@ -28,7 +28,7 @@ describe('Middleware: cacheControl', function () {
|
||||
});
|
||||
|
||||
it('correctly sets the private profile headers', function (done) {
|
||||
middleware.cacheControl('private')(null, res, function (a) {
|
||||
cacheControl('private')(null, res, function (a) {
|
||||
should.not.exist(a);
|
||||
res.set.calledOnce.should.be.true();
|
||||
res.set.calledWith({
|
||||
@ -40,7 +40,7 @@ describe('Middleware: cacheControl', function () {
|
||||
});
|
||||
|
||||
it('will not set headers without a profile', function (done) {
|
||||
middleware.cacheControl()(null, res, function (a) {
|
||||
cacheControl()(null, res, function (a) {
|
||||
should.not.exist(a);
|
||||
res.set.called.should.be.false();
|
||||
done();
|
||||
@ -48,8 +48,8 @@ describe('Middleware: cacheControl', function () {
|
||||
});
|
||||
|
||||
it('will not get confused between serving public and private', function (done) {
|
||||
var publicCC = middleware.cacheControl('public'),
|
||||
privateCC = middleware.cacheControl('private');
|
||||
var publicCC = cacheControl('public'),
|
||||
privateCC = cacheControl('private');
|
||||
|
||||
publicCC(null, res, function () {
|
||||
res.set.calledOnce.should.be.true();
|
||||
@ -79,7 +79,7 @@ describe('Middleware: cacheControl', function () {
|
||||
|
||||
it('will override public with private for private blogs', function (done) {
|
||||
res.isPrivateBlog = true;
|
||||
middleware.cacheControl('public')(null, res, function (a) {
|
||||
cacheControl('public')(null, res, function (a) {
|
||||
should.not.exist(a);
|
||||
res.set.calledOnce.should.be.true();
|
||||
res.set.calledWith({
|
||||
|
Loading…
Reference in New Issue
Block a user