Skip to content
Snippets Groups Projects
index.js 7.33 KiB
Newer Older
  • Learn to ignore specific revisions
  • FORESTIER Fabien's avatar
    FORESTIER Fabien committed
    const http = require('http');
    const httpProxy = require('http-proxy');
    const axios = require('axios');
    const URL = require('url');
    const Redis = require("ioredis");
    
    // CONFIG
    const redisSentinelHost = process.env.REDIS_SENTINEL_HOST;
    const redisSentinelPort = process.env.REDIS_SENTINEL_PORT;
    const redisGroupName = process.env.REDIS_GROUP_NAME;
    const redisAuthorizedTTL = process.env.REDIS_AUTHORIZED_TTL; // In seconds
    const redisUnauthorizedTTL = process.env.REDIS_UNAUTHORIZED_TTL; // In seconds
    const elasticsearchUrl = process.env.ELASTICSEARCH_URL;
    const technicalAccountUsername = process.env.TECHNICAL_ACCOUNT_USERNAME;
    const technicalAccountPassword = process.env.TECHNICAL_ACCOUNT_PASSWORD;
    const proxyHostTarget = process.env.PROXY_HOST_TARGET;
    
    // Configuring the different proxy server
    // Proxy WMS
    var wmsProxy = httpProxy.createProxyServer({
      changeOrigin: true,
      target: proxyHostTarget,
      auth: `${technicalAccountUsername}:${technicalAccountPassword}`,
    });
    
    // Configure and create an HTTP proxy server
    var mvtProxy = httpProxy.createProxyServer({
      changeOrigin: true,
      target: proxyHostTarget,
      auth: `${technicalAccountUsername}:${technicalAccountPassword}`,
    });
    
    var mvtUnauthProxy = httpProxy.createProxyServer({
      changeOrigin: true,
      target: proxyHostTarget,
    });
    
    
    // keep a referece of the proxyRequest in the req object
    wmsProxy.on('proxyReq', function (proxyReq, req) {
      req._proxyReq = proxyReq;
    });
    
    mvtProxy.on('proxyReq', function (proxyReq, req) {
      req._proxyReq = proxyReq;
    });
    
    mvtUnauthProxy.on('proxyReq', function (proxyReq, req) {
      req._proxyReq = proxyReq;
    });
    
    wmsProxy.on('error', function (err, req, res) {
      // If the client cancelled the request, abort the request to the upstream server
      if (req.socket.destroyed && err.code === 'ECONNRESET') {
        req._proxyReq.abort();
      }
      return console.log(`WMS Error, req.socket.destroyed: ${req.socket.destroyed}, ${err}`);
    });
    
    mvtProxy.on('error', function (err, req, res) {
      // If the client cancelled the request, abort the request to the upstream server
      if (req.socket.destroyed && err.code === 'ECONNRESET') {
        req._proxyReq.abort();
      }
      return console.log(`MVT Error, req.socket.destroyed: ${req.socket.destroyed}, ${err}`);
    });
    
    mvtUnauthProxy.on('error', function (err, req, res) {
      // If the client cancelled the request, abort the request to the upstream server
      if (req.socket.destroyed && err.code === 'ECONNRESET') {
        req._proxyReq.abort();
      }
      return console.log(`MVT Unauthenticated Error, req.socket.destroyed: ${req.socket.destroyed}, ${err}`);
    });
    
    
    FORESTIER Fabien's avatar
    FORESTIER Fabien committed
    // Create an HTTP server
    http.createServer(async function (req, res) {
    
      /******************* WMS *****************************/
      if (req.url.includes('/wms')) {
        printLog('Request received', 'WMS');
        wmsProxy.web(req, res, {});
        printLog('WMS request', 'proxified');
        return;
      }
    
      /******************* MVT *****************************/
      if (req.url.includes('/mvt')) {
        printLog('Request received', 'MVT');
    
        // If no cookies then then we can't identify a user and directly proxy the request without credentials
        if (req.headers["x-anonymous-consumer"]) {
          printLog('Request received', `Unauthenticated - proxying the request`);
          mvtUnauthProxy.web(req, res, {});
          return;
        }
    
        // Read the requested layer from the url
        const layer = getParameterValueFromUrl(req.url, 'LAYERS');
    
        if (!layer) {
          printError('Request received', 'No layer provided');
          res.statusCode = 400;
          res.end();
          return;
        }
    
        printLog('Request received', `Layer found: ${layer}`);
    
        const userRightsOnTheLayer = await getRedisValue(`${layer}-${req.headers['x-consumer-username']}`);
    
        if (userRightsOnTheLayer === 'true') {
          printLog('Request received', 'Authorized (value read from Redis)');
          mvtProxy.web(req, res, {});
          return;
        }
    
        if (userRightsOnTheLayer === 'false') {
          printLog('Request received', 'Unauthorized (value read from Redis)');
          res.statusCode = 401;
          res.end();
          return;
        }
    
        const options = {
          method: 'POST',
          url: `${elasticsearchUrl}/_search?&request_cache=false`,
          data: {
            "from": 0,
            "size": 1,
            "_source": [
              "editorial-metadata.isSample",
              "editorial-metadata.isOpenAccess"
            ],
            "query": {
              "term": {
                "metadata-fr.link.name": layer
              }
            }
          },
          headers: {
            cookie: req.headers.cookie,
          }
        };
    
        printLog('Request received', options);
    
        let response;
    
        try {
          response = await axios(options)
        } catch (err) {
          printError('Request to ES failed', err);
          res.statusCode = 500;
          res.end();
          return;
        }
    
    
        // If no results are found it means the specified dataset mvt layer doesn't exists
        if (response.data.hits.hits < 1) {
          printError('Request to ES', 'MVT not found');
          res.statusCode = 404;
          res.end();
          return;
        }
    
        printLog('Request to ES', 'MVT found');
    
        const editorialMetadata = response.data.hits.hits[0]._source['editorial-metadata'];
    
        printLog('Request to ES', editorialMetadata);
    
        if (!editorialMetadata.isOpenAccess && editorialMetadata.isSample) {
          printError('Proxy', 'Unauthorized');
          setRedisValue(`${layer}-${req.headers['x-consumer-username']}`, false, redisUnauthorizedTTL);
          res.statusCode = 401;
          res.end();
          return;
        }
    
        await setRedisValue(`${layer}-${req.headers['x-consumer-username']}`, true, redisAuthorizedTTL);
    
        printLog('Proxy', 'Authorized');
    
        mvtProxy.web(req, res, {});
      }
    
    }).listen(9000);
    
    // HELPERS
    
    function getParameterValueFromUrl(url, paramName) {
    
      const queryParams = URL.parse(url, true).query;
    
      return queryParams && queryParams[paramName] ? queryParams[paramName] : null;
    
    }
    
    async function setRedisValue(key, value, ttl) {
      const redisClient = new Redis({
        sentinels: [{
          host: redisSentinelHost,
          port: redisSentinelPort
        }, ],
        name: redisGroupName,
      });
    
      redisClient.on('error', (error) => {
        printError('Redis client', error);
        redisClient.disconnect();
      });
    
    
      // Set key value with expiration time in seconds
      const res = await redisClient.set(key, value, 'EX', ttl).catch((error) => {
        redisClient.disconnect();
        printError('Redis client', 'Couldn\'t set redis key/value (with ttl).');
        printError('Redis client', error);
        return false;
      });
    
    
      printLog('Redis client', 'Done setting key/value pair');
      redisClient.disconnect();
    
      return res ? true : false;
    }
    
    async function getRedisValue(key) {
      const redisClient = new Redis({
        sentinels: [{
          host: redisSentinelHost,
          port: redisSentinelPort
        }, ],
        name: redisGroupName,
      });
    
      redisClient.on('error', (error) => {
        printError('Redis client', error);
        redisClient.disconnect();
      });
    
      const res = await redisClient.get(key).catch((error) => {
        redisClient.disconnect();
        printError('Redis client', 'Couldn\'t get redis value.');
        printError('Redis client', error);
        return false;
      });
    
      printLog(`Redis client`, `Value found ${res}`);
      redisClient.disconnect();
      return res ? res : null;
    }
    
    function printLog(context, value) {
      console.log(`${new Date().toLocaleString("fr-FR")} [${context}] [log] `, value);
    }
    
    function printError(context, error) {
      console.error(`${new Date().toLocaleString("fr-FR")} [${context}] [error] `, error);
    }