diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index 009a5b8570e0..fa3ef39b1198 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -123,12 +123,7 @@ helpers.notAllowed = async function (req, res, error) { if (req.loggedIn || req.uid === -1) { if (res.locals.isAPI) { - res.status(403).json({ - path: req.path.replace(/^\/api/, ''), - loggedIn: req.loggedIn, - error: data.error, - title: '[[global:403.title]]', - }); + helpers.formatApiResponse(403, res, error); } else { await middleware.buildHeaderAsync(req, res); res.status(403).render('403', { @@ -140,7 +135,7 @@ helpers.notAllowed = async function (req, res, error) { } } else if (res.locals.isAPI) { req.session.returnTo = req.url.replace(/^\/api/, ''); - res.status(401).json('not-authorized'); + helpers.formatApiResponse(401, res, error); } else { req.session.returnTo = req.url; res.redirect(nconf.get('relative_path') + '/login'); @@ -353,9 +348,6 @@ helpers.formatApiResponse = async (statusCode, res, payload) => { }, response: payload || {}, }); - } else if (!payload) { - // Non-2xx statusCode, generate predefined error - res.status(statusCode).json(helpers.generateError(statusCode)); } else if (payload instanceof Error) { if (isLanguageKey.test(payload.message)) { const translated = await translator.translate(payload.message, 'en-GB'); @@ -363,6 +355,9 @@ helpers.formatApiResponse = async (statusCode, res, payload) => { } else { res.status(statusCode).json(helpers.generateError(statusCode, payload.message)); } + } else if (!payload) { + // Non-2xx statusCode, generate predefined error + res.status(statusCode).json(helpers.generateError(statusCode)); } }; @@ -377,34 +372,34 @@ helpers.generateError = (statusCode, message) => { // Need to turn all these into translation strings switch (statusCode) { - case 400: - payload.status.code = 'bad-request'; - payload.status.message = message || 'Something was wrong with the request payload you passed in.'; - break; - - case 401: - payload.status.code = 'not-authorised'; - payload.status.message = 'A valid login session was not found. Please log in and try again.'; - break; - - case 403: - payload.status.code = 'forbidden'; - payload.status.message = 'You are not authorised to make this call'; - break; - - case 404: - payload.status.code = 'not-found'; - payload.status.message = 'Invalid API call'; - break; - - case 426: - payload.status.code = 'upgrade-required'; - payload.status.message = 'HTTPS is required for requests to the write api, please re-send your request via HTTPS'; - break; - - case 500: - payload.status.code = 'internal-server-error'; - payload.status.message = message || payload.status.message; + case 400: + payload.status.code = 'bad-request'; + payload.status.message = message || 'Something was wrong with the request payload you passed in.'; + break; + + case 401: + payload.status.code = 'not-authorised'; + payload.status.message = message || 'A valid login session was not found. Please log in and try again.'; + break; + + case 403: + payload.status.code = 'forbidden'; + payload.status.message = message || 'You are not authorised to make this call'; + break; + + case 404: + payload.status.code = 'not-found'; + payload.status.message = message || 'Invalid API call'; + break; + + case 426: + payload.status.code = 'upgrade-required'; + payload.status.message = message || 'HTTPS is required for requests to the write api, please re-send your request via HTTPS'; + break; + + case 500: + payload.status.code = 'internal-server-error'; + payload.status.message = message || payload.status.message; } return payload; diff --git a/src/middleware/api.js b/src/middleware/api.js deleted file mode 100644 index 8cc1b138ce73..000000000000 --- a/src/middleware/api.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -const passport = require('passport'); -const winston = require('winston'); - -const helpers = require('../controllers/helpers'); -const middleware = module.exports; - -middleware.authenticate = function (req, res, next) { - if (req.headers.hasOwnProperty('authorization')) { - passport.authenticate('bearer', { session: false }, function (err, user) { - if (err) { return next(err); } - if (!user) { return helpers.formatApiResponse(401, res); } - - // If the token received was a master token, a _uid must also be present for all calls - if (user.hasOwnProperty('uid')) { - req.login(user, function (err) { - if (err) { return helpers.formatApiResponse(500, res, err); } - - req.uid = user.uid; - req.loggedIn = req.uid > 0; - next(); - }); - } else if (user.hasOwnProperty('master') && user.master === true) { - if (req.body.hasOwnProperty('_uid') || req.query.hasOwnProperty('_uid')) { - user.uid = req.body._uid || req.query._uid; - delete user.master; - - req.login(user, function (err) { - if (err) { return helpers.formatApiResponse(500, res, err); } - - req.uid = user.uid; - req.loggedIn = req.uid > 0; - next(); - }); - } else { - return helpers.formatApiResponse(400, res, new Error('A master token was received without a corresponding `_uid` in the request body')); - } - } else { - winston.warn('[api/authenticate] Unable to find user after verifying token'); - helpers.formatApiResponse(500, res); - } - })(req, res, next); - } else { - // No bearer token, reject request - helpers.formatApiResponse(401, res); - } -}; diff --git a/src/middleware/user.js b/src/middleware/user.js index db0098920117..de7ae916af80 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -1,6 +1,8 @@ 'use strict'; const nconf = require('nconf'); +const winston = require('winston'); +const passport = require('passport'); const meta = require('../meta'); const user = require('../user'); @@ -11,12 +13,49 @@ const auth = require('../routes/authentication'); const controllers = { helpers: require('../controllers/helpers'), + authentication: require('../controllers/authentication'), }; module.exports = function (middleware) { async function authenticate(req, res) { if (req.loggedIn) { return true; + } else if (req.headers.hasOwnProperty('authorization')) { + passport.authenticate('bearer', { session: false }, function (err, user) { + if (err) { throw new Error(err); } + if (!user) { return false; } + + // If the token received was a master token, a _uid must also be present for all calls + if (user.hasOwnProperty('uid')) { + req.login(user, async function (err) { + if (err) { throw new Error(err); } + + await controllers.authentication.onSuccessfulLogin(req, user.uid); + req.uid = user.uid; + req.loggedIn = req.uid > 0; + return true; + }); + } else if (user.hasOwnProperty('master') && user.master === true) { + if (req.body.hasOwnProperty('_uid') || req.query.hasOwnProperty('_uid')) { + user.uid = req.body._uid || req.query._uid; + delete user.master; + + req.login(user, async function (err) { + if (err) { throw new Error(err); } + + await controllers.authentication.onSuccessfulLogin(req, user.uid); + req.uid = user.uid; + req.loggedIn = req.uid > 0; + return true; + }); + } else { + throw new Error('A master token was received without a corresponding `_uid` in the request body'); + } + } else { + winston.warn('[api/authenticate] Unable to find user after verifying token'); + return false; + } + })(req, res); } await plugins.fireHook('response:middleware.authenticate', { @@ -180,7 +219,7 @@ module.exports = function (middleware) { req.session.returnTo = returnTo; req.session.forceLogin = 1; if (res.locals.isAPI) { - res.status(401).json({}); + controllers.helpers.formatApiResponse(401, res); } else { res.redirect(nconf.get('relative_path') + '/login?local=1'); } diff --git a/src/routes/index.js b/src/routes/index.js index faff537df510..2ead2d683c78 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -112,7 +112,7 @@ module.exports = async function (app, middleware) { await plugins.reloadRoutes({ router: router }); await authRoutes.reloadRoutes({ router: router }); - await writeRoutes.reload({ router: router }); + writeRoutes.reload({ router: router }); addCoreRoutes(app, router, middleware); winston.info('Routes added'); diff --git a/src/routes/write/index.js b/src/routes/write/index.js index a1a12142a0c2..266b4dc5774d 100644 --- a/src/routes/write/index.js +++ b/src/routes/write/index.js @@ -1,6 +1,6 @@ 'use strict'; -const middleware = require('../../middleware/api'); +const middleware = require('../../middleware'); const helpers = require('../../controllers/helpers'); const Write = module.exports; @@ -8,29 +8,32 @@ const Write = module.exports; Write.reload = (params) => { const router = params.router; - // router.use('/api', function (req, res, next) { - // if (req.protocol !== 'https') { - // res.set('Upgrade', 'TLS/1.0, HTTP/1.1'); - // return helpers.formatApiResponse(426, res); - // } else { - // next(); - // } - // }); + router.use('/api/v1', function (req, res, next) { + // if (req.protocol !== 'https') { + // res.set('Upgrade', 'TLS/1.0, HTTP/1.1'); + // return helpers.formatApiResponse(426, res); + // } else { + // next(); + // } + + res.locals.isAPI = true; + next(); + }); - // router.use('/users', require('./users')(coreMiddleware)); + router.use('/api/v1/users', require('./users')()); // router.use('/groups', require('./groups')(coreMiddleware)); // router.use('/posts', require('./posts')(coreMiddleware)); // router.use('/topics', require('./topics')(coreMiddleware)); // router.use('/categories', require('./categories')(coreMiddleware)); // router.use('/util', require('./util')(coreMiddleware)); - router.get('/api/ping', function (req, res) { + router.get('/api/v1/ping', function (req, res) { helpers.formatApiResponse(200, res, { pong: true, }); }); - router.post('/api/ping', middleware.authenticate, function (req, res) { + router.post('/api/v1/ping', middleware.authenticate, function (req, res) { helpers.formatApiResponse(200, res, { uid: req.user.uid, }); diff --git a/src/routes/write/users.js b/src/routes/write/users.js new file mode 100644 index 000000000000..407dbef0e04d --- /dev/null +++ b/src/routes/write/users.js @@ -0,0 +1,177 @@ +'use strict'; + +const users = require('../../user'); + +const middleware = require('../../middleware'); +const helpers = require('../../controllers/helpers'); +// Messaging = require.main.require('./src/messaging'), +// apiMiddleware = require('./middleware'), +// errorHandler = require('../../lib/errorHandler'), +// auth = require('../../lib/auth'), +// utils = require('./utils'), +// async = require.main.require('async'); + + +module.exports = function () { + var app = require('express').Router(); + + app.post('/', middleware.authenticate, middleware.isAdmin, async (req, res) => { + helpers.checkRequired(['username'], req, res); + // if (!utils.checkRequired(['username'], req, res)) { + // return false; + // } + + const uid = await users.create(req.body); + helpers.formatApiResponse(200, res, { + uid: uid, + }); + }); + + // app.route('/:uid') + // .put(apiMiddleware.requireUser, apiMiddleware.exposeAdmin, function(req, res) { + // if (parseInt(req.params.uid, 10) !== parseInt(req.user.uid, 10) && !res.locals.isAdmin) { + // return errorHandler.respond(401, res); + // } + + // // `uid` in `updateProfile` refers to calling user, not target user + // req.body.uid = req.params.uid; + + // Users.updateProfile(req.user.uid, req.body, function(err) { + // return errorHandler.handle(err, res); + // }); + // }) + // .delete(apiMiddleware.requireUser, apiMiddleware.exposeAdmin, function(req, res) { + // if (parseInt(req.params.uid, 10) !== parseInt(req.user.uid, 10) && !res.locals.isAdmin) { + // return errorHandler.respond(401, res); + // } + + // // Clear out any user tokens belonging to the to-be-deleted user + // async.waterfall([ + // async.apply(auth.getTokens, req.params.uid), + // function(tokens, next) { + // async.each(tokens, function(token, next) { + // auth.revokeToken(token, 'user', next); + // }, next); + // }, + // async.apply(Users.delete, req.user.uid, req.params.uid) + // ], function(err) { + // return errorHandler.handle(err, res); + // }); + // }); + + // app.put('/:uid/password', apiMiddleware.requireUser, apiMiddleware.exposeAdmin, function(req, res) { + // if (parseInt(req.params.uid, 10) !== parseInt(req.user.uid, 10) && !res.locals.isAdmin) { + // return errorHandler.respond(401, res); + // } + + // Users.changePassword(req.user.uid, { + // uid: req.params.uid, + // currentPassword: req.body.current || '', + // newPassword: req.body['new'] || '' + // }, function(err) { + // errorHandler.handle(err, res); + // }); + // }); + + // app.put('/:uid/follow', apiMiddleware.requireUser, function(req, res) { + // Users.follow(req.user.uid, req.params.uid, function(err) { + // return errorHandler.handle(err, res); + // }); + // }); + + // app.delete('/:uid/follow', apiMiddleware.requireUser, function(req, res) { + // Users.unfollow(req.user.uid, req.params.uid, function(err) { + // return errorHandler.handle(err, res); + // }); + // }); + + // app.route('/:uid/chats') + // .post(apiMiddleware.requireUser, function(req, res) { + // if (!utils.checkRequired(['message'], req, res)) { + // return false; + // } + + // var timestamp = parseInt(req.body.timestamp, 10) || Date.now(); + + // function addMessage(roomId) { + // Messaging.addMessage({ + // uid: req.user.uid, + // roomId: roomId, + // content: req.body.message, + // timestamp: timestamp, + // }, function(err, message) { + // if (parseInt(req.body.quiet, 10) !== 1) { + // Messaging.notifyUsersInRoom(req.user.uid, roomId, message); + // } + + // return errorHandler.handle(err, res, message); + // }); + // } + + // Messaging.canMessageUser(req.user.uid, req.params.uid, function(err) { + // if (err) { + // return errorHandler.handle(err, res); + // } + + // if (req.body.roomId) { + // addMessage(req.body.roomId); + // } else { + // Messaging.newRoom(req.user.uid, [req.params.uid], function(err, roomId) { + // if (err) { + // return errorHandler.handle(err, res); + // } + + // addMessage(roomId); + // }); + // } + // }); + // }); + + // app.route('/:uid/ban') + // .put(apiMiddleware.requireUser, apiMiddleware.requireAdmin, function(req, res) { + // Users.bans.ban(req.params.uid, req.body.until || 0, req.body.reason || '', function(err) { + // errorHandler.handle(err, res); + // }); + // }) + // .delete(apiMiddleware.requireUser, apiMiddleware.requireAdmin, function(req, res) { + // Users.bans.unban(req.params.uid, function(err) { + // errorHandler.handle(err, res); + // }); + // }); + + // app.route('/:uid/tokens') + // .get(apiMiddleware.requireUser, function(req, res) { + // if (parseInt(req.params.uid, 10) !== parseInt(req.user.uid, 10)) { + // return errorHandler.respond(401, res); + // } + + // auth.getTokens(req.params.uid, function(err, tokens) { + // return errorHandler.handle(err, res, { + // tokens: tokens + // }); + // }); + // }) + // .post(apiMiddleware.requireUser, function(req, res) { + // if (parseInt(req.params.uid, 10) !== parseInt(req.user.uid)) { + // return errorHandler.respond(401, res); + // } + + // auth.generateToken(req.params.uid, function(err, token) { + // return errorHandler.handle(err, res, { + // token: token + // }); + // }); + // }); + + // app.delete('/:uid/tokens/:token', apiMiddleware.requireUser, function(req, res) { + // if (parseInt(req.params.uid, 10) !== req.user.uid) { + // return errorHandler.respond(401, res); + // } + + // auth.revokeToken(req.params.token, 'user', function(err) { + // errorHandler.handle(err, res); + // }); + // }); + + return app; +};