mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 19:15:58 +01:00 
			
		
		
		
	feat: migrating write-api skeleton into core
This commit is contained in:
		| @@ -11,7 +11,9 @@ const categories = require('../categories'); | |||||||
| const plugins = require('../plugins'); | const plugins = require('../plugins'); | ||||||
| const meta = require('../meta'); | const meta = require('../meta'); | ||||||
| const middleware = require('../middleware'); | const middleware = require('../middleware'); | ||||||
|  | const translator = require('../translator'); | ||||||
|  |  | ||||||
|  | const isLanguageKey = /^\[\[[\w.\-_:]+]]$/; | ||||||
| const helpers = module.exports; | const helpers = module.exports; | ||||||
|  |  | ||||||
| helpers.noScriptErrors = async function (req, res, error, httpStatus) { | helpers.noScriptErrors = async function (req, res, error, httpStatus) { | ||||||
| @@ -342,4 +344,70 @@ helpers.getHomePageRoutes = async function (uid) { | |||||||
| 	return data.routes; | 	return data.routes; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | helpers.formatApiResponse = async (statusCode, res, payload) => { | ||||||
|  | 	if (statusCode === 200) { | ||||||
|  | 		res.status(200).json({ | ||||||
|  | 			status: { | ||||||
|  | 				code: 'ok', | ||||||
|  | 				message: 'OK', | ||||||
|  | 			}, | ||||||
|  | 			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'); | ||||||
|  | 			res.status(statusCode).json(helpers.generateError(statusCode, translated)); | ||||||
|  | 		} else { | ||||||
|  | 			res.status(statusCode).json(helpers.generateError(statusCode, payload.message)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | helpers.generateError = (statusCode, message) => { | ||||||
|  | 	var payload = { | ||||||
|  | 		status: { | ||||||
|  | 			code: 'internal-server-error', | ||||||
|  | 			message: 'An unexpected error was encountered while attempting to service your request.', | ||||||
|  | 		}, | ||||||
|  | 		response: {}, | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	// 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; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return payload; | ||||||
|  | }; | ||||||
|  |  | ||||||
| require('../promisify')(helpers); | require('../promisify')(helpers); | ||||||
|   | |||||||
							
								
								
									
										48
									
								
								src/middleware/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/middleware/api.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | '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); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
| @@ -3,8 +3,10 @@ | |||||||
| var async = require('async'); | var async = require('async'); | ||||||
| var passport = require('passport'); | var passport = require('passport'); | ||||||
| var passportLocal = require('passport-local').Strategy; | var passportLocal = require('passport-local').Strategy; | ||||||
|  | const BearerStrategy = require('passport-http-bearer').Strategy; | ||||||
| var winston = require('winston'); | var winston = require('winston'); | ||||||
|  |  | ||||||
|  | const db = require('../database'); | ||||||
| var controllers = require('../controllers'); | var controllers = require('../controllers'); | ||||||
| var helpers = require('../controllers/helpers'); | var helpers = require('../controllers/helpers'); | ||||||
| var plugins = require('../plugins'); | var plugins = require('../plugins'); | ||||||
| @@ -48,21 +50,45 @@ Auth.getLoginStrategies = function () { | |||||||
| 	return loginStrategies; | 	return loginStrategies; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | Auth.verifyToken = async function (token, done) { | ||||||
|  | 	const uid = await db.sortedSetScore('apiTokens', token); | ||||||
|  |  | ||||||
|  | 	if (uid !== null) { | ||||||
|  | 		if (parseInt(uid, 10) > 0) { | ||||||
|  | 			done(null, { | ||||||
|  | 				uid: uid, | ||||||
|  | 			}); | ||||||
|  | 		} else { | ||||||
|  | 			done(null, { | ||||||
|  | 				master: true, | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		done(false); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
| Auth.reloadRoutes = async function (params) { | Auth.reloadRoutes = async function (params) { | ||||||
| 	loginStrategies.length = 0; | 	loginStrategies.length = 0; | ||||||
| 	const router = params.router; | 	const router = params.router; | ||||||
|  |  | ||||||
|  | 	// Local Logins | ||||||
| 	if (plugins.hasListeners('action:auth.overrideLogin')) { | 	if (plugins.hasListeners('action:auth.overrideLogin')) { | ||||||
| 		winston.warn('[authentication] Login override detected, skipping local login strategy.'); | 		winston.warn('[authentication] Login override detected, skipping local login strategy.'); | ||||||
| 		plugins.fireHook('action:auth.overrideLogin'); | 		plugins.fireHook('action:auth.overrideLogin'); | ||||||
| 	} else { | 	} else { | ||||||
| 		passport.use(new passportLocal({ passReqToCallback: true }, controllers.authentication.localLogin)); | 		passport.use(new passportLocal({ passReqToCallback: true }, controllers.authentication.localLogin)); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// HTTP bearer authentication | ||||||
|  | 	passport.use(new BearerStrategy({}, Auth.verifyToken)); | ||||||
|  |  | ||||||
|  | 	// Additional logins via SSO plugins | ||||||
| 	try { | 	try { | ||||||
| 		loginStrategies = await plugins.fireHook('filter:auth.init', loginStrategies); | 		loginStrategies = await plugins.fireHook('filter:auth.init', loginStrategies); | ||||||
| 	} catch (err) { | 	} catch (err) { | ||||||
| 		winston.error('[authentication] ' + err.stack); | 		winston.error('[authentication] ' + err.stack); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	loginStrategies = loginStrategies || []; | 	loginStrategies = loginStrategies || []; | ||||||
| 	loginStrategies.forEach(function (strategy) { | 	loginStrategies.forEach(function (strategy) { | ||||||
| 		if (strategy.url) { | 		if (strategy.url) { | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ var apiRoutes = require('./api'); | |||||||
| var adminRoutes = require('./admin'); | var adminRoutes = require('./admin'); | ||||||
| var feedRoutes = require('./feeds'); | var feedRoutes = require('./feeds'); | ||||||
| var authRoutes = require('./authentication'); | var authRoutes = require('./authentication'); | ||||||
|  | const writeRoutes = require('./write'); | ||||||
| var helpers = require('./helpers'); | var helpers = require('./helpers'); | ||||||
|  |  | ||||||
| var setupPageRoute = helpers.setupPageRoute; | var setupPageRoute = helpers.setupPageRoute; | ||||||
| @@ -111,6 +112,7 @@ module.exports = async function (app, middleware) { | |||||||
|  |  | ||||||
| 	await plugins.reloadRoutes({ router: router }); | 	await plugins.reloadRoutes({ router: router }); | ||||||
| 	await authRoutes.reloadRoutes({ router: router }); | 	await authRoutes.reloadRoutes({ router: router }); | ||||||
|  | 	await writeRoutes.reload({ router: router }); | ||||||
| 	addCoreRoutes(app, router, middleware); | 	addCoreRoutes(app, router, middleware); | ||||||
|  |  | ||||||
| 	winston.info('Routes added'); | 	winston.info('Routes added'); | ||||||
|   | |||||||
							
								
								
									
										54
									
								
								src/routes/write/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/routes/write/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const middleware = require('../../middleware/api'); | ||||||
|  | const helpers = require('../../controllers/helpers'); | ||||||
|  |  | ||||||
|  | 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('/users', require('./users')(coreMiddleware)); | ||||||
|  | 	// 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) { | ||||||
|  | 		helpers.formatApiResponse(200, res, { | ||||||
|  | 			pong: true, | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	router.post('/api/ping', middleware.authenticate, function (req, res) { | ||||||
|  | 		helpers.formatApiResponse(200, res, { | ||||||
|  | 			uid: req.user.uid, | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// This router is reserved exclusively for plugins to add their own routes into the write api plugin. Confused yet? :trollface: | ||||||
|  | 	// var customRouter = require('express').Router(); | ||||||
|  | 	// plugins.fireHook('filter:plugin.write-api.routes', { | ||||||
|  | 	// 	router: customRouter, | ||||||
|  | 	// 	apiMiddleware: apiMiddleware, | ||||||
|  | 	// 	middleware: coreMiddleware, | ||||||
|  | 	// 	errorHandler: errorHandler | ||||||
|  | 	// }, function (err, payload) { | ||||||
|  | 	// 	router.use('/', payload.router); | ||||||
|  |  | ||||||
|  | 	// 	router.use(function(req, res) { | ||||||
|  | 	// 		// Catch-all | ||||||
|  | 	// 		errorHandler.respond(404, res); | ||||||
|  | 	// 	}); | ||||||
|  | 	// }); | ||||||
|  | }; | ||||||
		Reference in New Issue
	
	Block a user