agmission/Development/server/middlewares/app_validator.js

186 lines
6.2 KiB
JavaScript

'use strict';
const
env = require('../helpers/env.js'),
jwt = require('../helpers/jwt_async.js'),
{ TokenExpiredError } = require('jsonwebtoken'),
utils = require('../helpers/utils.js'),
cache = require('../helpers/mem_cache.js'),
subUtil = require('../helpers/subscription_util.js'),
{ Errors, Fields } = require('../helpers/constants.js'),
{ AppAuthError, AppMembershipError, AppParamError } = require('../helpers/app_error.js'),
{ SubType, SubFields } = require('../model/subscription.js'),
Vehicle = require('../model/vehicle.js'),
ObjectId = require('mongodb').ObjectId;
const USE_SUBSCRIPTION = env.ENABLE_SUBSCRIPTION;
function getRoutePath(req) {
return req && req.baseUrl + req.path;
}
function isSecuredRoute(routePath) {
const nonSecurePaths = ['/login', '/siteVer', '/mailPwdReset', '/resetPassword'];
if (env.INV_IMG_VIR_DIR) {
nonSecurePaths.push(env.INV_IMG_VIR_DIR)
}
return (routePath && !nonSecurePaths.some(p => routePath.includes(p)));
}
/** 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') AppAuthError.throw();
const bearer = token[1];
try {
const decodedToken = await jwt.verifyAsync(bearer, env.TOKEN_SECRET);
/* Setup for current logged in user */
req.uid = decodedToken.uid; // Set req User id
if (!ObjectId.isValid(req.uid)) AppAuthError.throw();
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]) {
AppAuthError.throw();
} else if (!userInfo || cache.isExpired(req.uid, env.MAX_SESSION_SECS)) {
const curUserInfo = await cache.loadUser(req.uid);
if (!curUserInfo && curUserInfo[Fields.MARKED_DELETE]) {
AppAuthError.throw();
}
req.userInfo = curUserInfo;
return next();
} else {
req.userInfo = userInfo;
return next();
}
} catch (err) {
if (err instanceof TokenExpiredError) {
const decodedPkg = await jwt.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)) AppAuthError.throw();
req.ut = decodedPkg.ut;
return next();
}
AppAuthError.throw(Errors.TOKEN_EXPIRED);
} else {
AppAuthError.throw();
}
}
}
function getUserInfo(req) {
if (!req) AppParamError.throw();
const userInfo = req.userInfo;
if (!USE_SUBSCRIPTION) return userInfo; // Temporarily before merging subscription management feature
if (!userInfo || !userInfo.membership || utils.isEmptyArray(userInfo.membership.subscriptions))
AppMembershipError.throw();
return userInfo;
}
/** Check for require any subscription. It requires to be called after checkUser middleware */
async function checkRqAnySubscription(req, res, next) {
if (!USE_SUBSCRIPTION) return next(); // Temporarily before merging subscription management feature
const routeUrl = getRoutePath(req);
if (!isSecuredRoute(routeUrl)) return next();
try {
getUserInfo(req);
return next();
} catch (err) {
return next(err);
}
}
/** Check for require any subscription. It requires to be called after checkUser middleware */
async function checkRqPkgSubscription(req, res, next) {
if (!USE_SUBSCRIPTION) return next(); // Temporarily before merging subscription management feature
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 AppMembershipError.throw(Errors.PKG_SUBSCRIPTION_NOT_FOUND);
}
return next();
} catch (err) {
return next(err);
}
}
// async function checkRqTrackingSubscription(req, res, next) {
// // TODO: Handle the check for subscription for addon(s) by name.
// }
async function checkACsLimits(userInfo) {
if (!USE_SUBSCRIPTION) return true; // Temporarily before merging subscription management feature
if (!userInfo) AppAuthError.throw();
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) {
AppMembershipError.throw(Errors.REACHED_VEHICLES_LIMIT);
}
return true;
}
async function checkUsageLimits(userInfo) {
if (!USE_SUBSCRIPTION) return true; // Temporarily before merging subscription management feature
if (!userInfo) AppAuthError.throw();
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) {
AppMembershipError.throw(Errors.REACHED_AREA_LIMIT);
}
}
return true;
}
async function checkRqACsLimits(req, res, next) {
if (!USE_SUBSCRIPTION) return next(); // Temporarily before merging subscription management feature
const userInfo = getUserInfo(req);
await checkACsLimits(userInfo);
return next && (next());
}
async function checkRqUsageLimits(req, res, next) {
if (!USE_SUBSCRIPTION) return next(); // Temporarily before merging subscription management feature
const userInfo = getUserInfo(req);
await checkUsageLimits(userInfo);
return next && (next());
}
module.exports = {
isSecuredRoute, getUserInfo, checkUser, checkACsLimits, checkUsageLimits,
checkRqAnySubscription, checkRqPkgSubscription, checkRqACsLimits, checkRqUsageLimits,
};