UNPKG

@universis/janitor

Version:

Universis api plugin for handling user authorization and rate limiting

94 lines (84 loc) 3.48 kB
import { ApplicationService, HttpForbiddenError, TraceUtils } from '@themost/common'; import express from 'express'; import jwt from 'jsonwebtoken'; class HttpRemoteAddrForbiddenError extends HttpForbiddenError { constructor() { super('Access is denied due to remote address conflict. The client network has been changed or cannot be determined.'); this.statusCode = 403.6; } } class RemoteAddressValidator extends ApplicationService { constructor(app) { super(app); // get proxy address forwarding option let proxyAddressForwarding = app.getConfiguration().getSourceAt('settings/universis/api/proxyAddressForwarding'); if (typeof proxyAddressForwarding !== 'boolean') { proxyAddressForwarding = false; } this.proxyAddressForwarding = proxyAddressForwarding; // get token claim name this.claim = app.getConfiguration().getSourceAt('settings/universis/janitor/remoteAddress/claim') || 'remoteAddress'; app.serviceRouter.subscribe((serviceRouter) => { if (serviceRouter == null) { return; } const addRouter = express.Router(); addRouter.use((req, res, next) => { void this.validateRemoteAddress(req).then((value) => { if (value === false) { return next(new HttpRemoteAddrForbiddenError()); } return next(); }).catch((err) => { return next(err); }); }); // insert router at the beginning of serviceRouter.stack serviceRouter.stack.unshift.apply(serviceRouter.stack, addRouter.stack); }); } /** * Gets remote address from request * @param {import('express').Request} req * @returns */ getRemoteAddress(req) { let remoteAddress; if (this.proxyAddressForwarding) { // get proxy headers or remote address remoteAddress = req.headers['x-real-ip'] || req.headers['x-forwarded-for'] || (req.connection ? req.connection.remoteAddress : req.socket.remoteAddress); } else { remoteAddress = req.connection ? req.connection.remoteAddress : req.socket.remoteAddress; } return remoteAddress; } /** * Validates token remote address with request remote address * @param {import('express').Request} req * @returns {Promise<boolean>} */ async validateRemoteAddress(req) { const authenticationToken = req.context?.user?.authenticationToken; if (authenticationToken != null) { const access_token = jwt.decode(authenticationToken); const remoteAddress = access_token[this.claim]; if (remoteAddress == null) { TraceUtils.warn(`Remote address validation failed. Expected a valid remote address claimed by using "${this.claim}" attribute but got none.`); return false; } // get context remote address const requestRemoteAddress = this.getRemoteAddress(req); if (remoteAddress !== requestRemoteAddress) { TraceUtils.warn(`Remote address validation failed. Expected remote address is ${remoteAddress || 'Uknown'} but request remote address is ${requestRemoteAddress}`); return false; } return true; } TraceUtils.warn('Remote address validation cannot be completed because authentication token is not available.'); return false; } } export { HttpRemoteAddrForbiddenError, RemoteAddressValidator }