mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +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 meta = require('../meta'); | ||||
| const middleware = require('../middleware'); | ||||
| const translator = require('../translator'); | ||||
|  | ||||
| const isLanguageKey = /^\[\[[\w.\-_:]+]]$/; | ||||
| const helpers = module.exports; | ||||
|  | ||||
| helpers.noScriptErrors = async function (req, res, error, httpStatus) { | ||||
| @@ -342,4 +344,70 @@ helpers.getHomePageRoutes = async function (uid) { | ||||
| 	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); | ||||
|   | ||||
							
								
								
									
										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 passport = require('passport'); | ||||
| var passportLocal = require('passport-local').Strategy; | ||||
| const BearerStrategy = require('passport-http-bearer').Strategy; | ||||
| var winston = require('winston'); | ||||
|  | ||||
| const db = require('../database'); | ||||
| var controllers = require('../controllers'); | ||||
| var helpers = require('../controllers/helpers'); | ||||
| var plugins = require('../plugins'); | ||||
| @@ -48,21 +50,45 @@ Auth.getLoginStrategies = function () { | ||||
| 	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) { | ||||
| 	loginStrategies.length = 0; | ||||
| 	const router = params.router; | ||||
|  | ||||
| 	// Local Logins | ||||
| 	if (plugins.hasListeners('action:auth.overrideLogin')) { | ||||
| 		winston.warn('[authentication] Login override detected, skipping local login strategy.'); | ||||
| 		plugins.fireHook('action:auth.overrideLogin'); | ||||
| 	} else { | ||||
| 		passport.use(new passportLocal({ passReqToCallback: true }, controllers.authentication.localLogin)); | ||||
| 	} | ||||
|  | ||||
| 	// HTTP bearer authentication | ||||
| 	passport.use(new BearerStrategy({}, Auth.verifyToken)); | ||||
|  | ||||
| 	// Additional logins via SSO plugins | ||||
| 	try { | ||||
| 		loginStrategies = await plugins.fireHook('filter:auth.init', loginStrategies); | ||||
| 	} catch (err) { | ||||
| 		winston.error('[authentication] ' + err.stack); | ||||
| 	} | ||||
|  | ||||
| 	loginStrategies = loginStrategies || []; | ||||
| 	loginStrategies.forEach(function (strategy) { | ||||
| 		if (strategy.url) { | ||||
|   | ||||
| @@ -15,6 +15,7 @@ var apiRoutes = require('./api'); | ||||
| var adminRoutes = require('./admin'); | ||||
| var feedRoutes = require('./feeds'); | ||||
| var authRoutes = require('./authentication'); | ||||
| const writeRoutes = require('./write'); | ||||
| var helpers = require('./helpers'); | ||||
|  | ||||
| var setupPageRoute = helpers.setupPageRoute; | ||||
| @@ -111,6 +112,7 @@ module.exports = async function (app, middleware) { | ||||
|  | ||||
| 	await plugins.reloadRoutes({ router: router }); | ||||
| 	await authRoutes.reloadRoutes({ router: router }); | ||||
| 	await writeRoutes.reload({ router: router }); | ||||
| 	addCoreRoutes(app, router, middleware); | ||||
|  | ||||
| 	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