@universis/janitor
Version:
Universis api plugin for handling user authorization and rate limiting
94 lines (84 loc) • 3.48 kB
JavaScript
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
}