const env = require('../helpers/env'), { verifyAsync, decodeAsync } = require('../helpers/jwt_async.js'), utils = require('../helpers/utils.js'), cache = require('../helpers/mem_cache'), subUtil = require('../helpers/subscription_util'), apiErrorHandler = require('error-handler').apiErrorHandler, { Errors, Fields } = require('../helpers/constants'), { SubType, SubFields } = require('../model/subscription'), Vehicle = require('../model/vehicle'), ObjectId = require('mongodb').ObjectId; function getRoutePath(req) { return req && req.baseUrl + req.path; } function isSecuredRoute(routePath) { const nonSecurePaths = ['/login', '/siteVer', '/mailPwdReset', '/resetPassword']; return (routePath && !nonSecurePaths.includes(routePath.substring(routePath.lastIndexOf('/')))); } /** Check on every request and authenticate and authorize users. This middleware must be call first to ensure user authentication and related user session data */ async function checkUser(req, res, next) { // console.log(req.url); if (!isSecuredRoute(getRoutePath(req))) return next(); const token = req.headers.authorization ? req.headers.authorization.split(' ') : null; if (!token || token.length !== 2 || token[0].toLowerCase() !== 'bearer') return apiErrorHandler.handleResErr(res, Errors.NO_ACCESS, 401); const bearer = token[1]; try { const decodedToken = await verifyAsync(bearer, env.TOKEN_SECRET); /* Setup for current logged in user */ req.uid = decodedToken.uid; // Set req User id if (!ObjectId.isValid(req.uid)) return apiErrorHandler.handleResErr(res, Errors.NO_ACCESS, 401); req.ut = decodedToken.ut; // Set req User type // Rebuild the in-memory cache for this user in case of server restarted or the user's cache has expired const userInfo = cache.get(req.uid); if (userInfo && userInfo[Fields.MARKED_DELETE]) { return apiErrorHandler.handleResErr(res, err, 401); } else if (!userInfo || cache.isExpired(req.uid, env.MAX_SESSION_SECS)) { const curUserInfo = await cache.loadUser(req.uid); if (!curUserInfo && curUserInfo[Fields.MARKED_DELETE]) { return apiErrorHandler.handleResErr(res, err, 401); } req.userInfo = curUserInfo; return next(); } else { req.userInfo = userInfo; return next(); } } catch (err) { if (err.name && err.name === 'TokenExpiredError') { const decodedPkg = await decodeAsync(bearer); // Handle the case the user logout when token already expired => should still allow clearing temp data if (req.path.endsWith('/clearTempData')) { req.uid = decodedPkg.uid; if (!ObjectId.isValid(req.uid)) return apiErrorHandler.handleResErr(res, Errors.NO_ACCESS, 401); req.ut = decodedPkg.ut; return next(); } return apiErrorHandler.handleResErr(res, Errors.TOKEN_EXPIRED, 401); } else { return apiErrorHandler.handleResErr(res, Errors.NO_ACCESS, 401); } } } function getUserInfo(req) { if (!req) throw new Error(Errors.INVALID_INPUT); const userInfo = req.userInfo; if (!userInfo || !userInfo.membership || utils.isEmptyArray(userInfo.membership.subscriptions)) throw new Error(Errors.SUBSCRIPTION_NOT_FOUND); return userInfo; } /** Check for require any subscription. It requires to be called after checkUser middleware */ async function checkRqAnySubscription(req, res, next) { const routeUrl = getRoutePath(req); if (!isSecuredRoute(routeUrl)) return next(); try { getUserInfo(req); return next(); } catch (err) { return apiErrorHandler.handleResErr(res, err); } } /** Check for require any subscription. It requires to be called after checkUser middleware */ async function checkRqPkgSubscription(req, res, next) { const routeUrl = getRoutePath(req); if (!isSecuredRoute(routeUrl)) return next(); try { const userInfo = getUserInfo(req); if (!(userInfo.membership.subscriptions.some(sub => sub.type === SubType.PACKAGE))) { return apiErrorHandler.handleResErr(res, Errors.PKG_SUBSCRIPTION_NOT_FOUND); } return next(); } catch (err) { return apiErrorHandler.handleResErr(res, err); } } // async function checkRqTrackingSubscription(req, res, next) { // // TODO: Handle the check for subscription for addon(s) by name. // } async function checkACsLimits(userInfo) { if (!userInfo) throw new Error(Errors.NO_ACCESS); const pkgSub = subUtil.getPkgSubfromUserInfo(userInfo); const numOfAC = await Vehicle.countDocuments({ parent: userInfo.puid, [Fields.MARKED_DELETE]: { $in: [false, null] } }); const maxVehicles = subUtil.getSubMetaField(pkgSub, SubFields.MAX_VEHICLES) || 0; if (numOfAC && numOfAC >= maxVehicles) { throw new Error(Errors.REACHED_VEHICLES_LIMIT); } return true; } async function checkUsageLimits(userInfo) { if (!userInfo) throw new Error(Errors.NO_ACCESS); const pkgSub = subUtil.getPkgSubfromUserInfo(userInfo); const priceMaxAcres = subUtil.getSubMetaField(pkgSub, SubFields.MAX_ACRES) || 0; if (priceMaxAcres) { const ttSprArea = await subUtil.calcTotalAreaByUser(ObjectId(userInfo.puid), pkgSub.periodStart, pkgSub.periodEnd); if (ttSprArea && utils.haToAcre(ttSprArea) >= priceMaxAcres) { throw new Error(Errors.REACHED_AREA_LIMIT); } } return true; } async function checkReqACsLimits(req, res, next) { try { const userInfo = getUserInfo(req); await checkACsLimits(userInfo); return next && (next()); } catch (err) { return apiErrorHandler.handleResErr(res, err); } } async function checkReqUsageLimits(req, res, next) { try { const userInfo = getUserInfo(req); await checkUsageLimits(userInfo); return next && (next()); } catch (err) { return apiErrorHandler.handleResErr(res, err); } } module.exports = { isSecuredRoute, getUserInfo, checkUser, checkACsLimits, checkUsageLimits, checkRqAnySubscription, checkRqPkgSubscription, checkReqACsLimits, checkReqUsageLimits, };