From 9870c16f370a7ba6632227a7800d4fecaf87ea7e Mon Sep 17 00:00:00 2001
From: FORESTIER Fabien <fabien.forestier@soprasteria.com>
Date: Mon, 27 Apr 2020 14:15:22 +0200
Subject: [PATCH] This proxy now has twho root routes /map and /download

---
 .gitlab-ci.yml                      |   4 +-
 Dockerfile                          |   4 +-
 README.md                           |   2 +-
 docker-compose.yml                  |   8 +-
 helpers/elasticsearch.helpers.js    |  76 +++++
 helpers/logs.helpers.js             |   7 +
 helpers/redis.helpers.js            |  61 ++++
 helpers/request-processor.helper.js |  62 ++++
 helpers/userAccesses.helpers.js     |  31 ++
 index.js                            | 299 ++++---------------
 package-lock.json                   | 433 ++++++++++++++++++++++++++--
 package.json                        |  11 +-
 routes/download.routes.js           | 108 +++++++
 routes/index.js                     |   6 +
 routes/map.routes.js                |  76 +++++
 template.env                        |   3 +-
 16 files changed, 915 insertions(+), 276 deletions(-)
 create mode 100644 helpers/elasticsearch.helpers.js
 create mode 100644 helpers/logs.helpers.js
 create mode 100644 helpers/redis.helpers.js
 create mode 100644 helpers/request-processor.helper.js
 create mode 100644 helpers/userAccesses.helpers.js
 create mode 100644 routes/download.routes.js
 create mode 100644 routes/index.js
 create mode 100644 routes/map.routes.js

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 63bbe7b..aafc60f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -29,7 +29,7 @@ build_development:
     - development
   script:
     - export TAG=$CI_COMMIT_SHORT_SHA
-    - export PROXY_MVT_BIND_PORT=9002
+    - export PROXY_QUERY_BIND_PORT=9002
     - docker-compose build 
     - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
     - docker-compose push
@@ -41,7 +41,7 @@ build_release:
     - tags
   script:
     - export TAG=$(echo $CI_COMMIT_TAG | sed 's/v//g')
-    - export PROXY_MVT_BIND_PORT=9002
+    - export PROXY_QUERY_BIND_PORT=9002
     - docker-compose build
     - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
     - docker-compose push
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 8734e31..eabb7c5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,6 +7,8 @@ WORKDIR /app
 # Install npm dependencies
 RUN npm install
 # Copy the project
-COPY . /app
+COPY ./helpers ./helpers
+COPY ./routes ./routes
+COPY ./index.js .
 
 CMD [ "npm", "start" ]
\ No newline at end of file
diff --git a/README.md b/README.md
index 9503003..c97583f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Web mapping services proxy
+# Query proxy
 
 ## Installation
 
diff --git a/docker-compose.yml b/docker-compose.yml
index d307d20..b3138d1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,12 +1,11 @@
 version: "3"
 
 services:
-  proxy-mvt:
-    restart: unless-stopped
+  proxy-query:
     build: .
     image: registry.forge.grandlyon.com/web-et-numerique/web-et-numerique-internet/data.grandlyon.com/web-portal/components/proxies/web-mapping-services:${TAG}
     ports: 
-      - ${PROXY_MVT_BIND_PORT}:9000
+      - ${PROXY_QUERY_BIND_PORT}:9000
     environment:
       - REDIS_SENTINEL_HOST=${REDIS_SENTINEL_HOST}
       - REDIS_SENTINEL_PORT=${REDIS_SENTINEL_PORT}
@@ -14,12 +13,11 @@ services:
       - REDIS_AUTHORIZED_TTL=${REDIS_AUTHORIZED_TTL}
       - REDIS_UNAUTHORIZED_TTL=${REDIS_UNAUTHORIZED_TTL}
       - ELASTICSEARCH_URL=${ELASTICSEARCH_URL}
+      - LEGACY_AUTH_MIDDLEWARE_URL=${LEGACY_AUTH_MIDDLEWARE_URL}
       - TECHNICAL_ACCOUNT_USERNAME=${TECHNICAL_ACCOUNT_USERNAME}
       - TECHNICAL_ACCOUNT_PASSWORD=${TECHNICAL_ACCOUNT_PASSWORD}
       - PROXY_HOST_TARGET=${PROXY_HOST_TARGET}
       - IGN_KEY=${IGN_KEY}
-    depends_on:
-      - redis-sentinel-1
     restart: unless-stopped
 
   redis-master:
diff --git a/helpers/elasticsearch.helpers.js b/helpers/elasticsearch.helpers.js
new file mode 100644
index 0000000..32bb3e6
--- /dev/null
+++ b/helpers/elasticsearch.helpers.js
@@ -0,0 +1,76 @@
+const axios = require('axios');
+
+module.exports.getDatasetInfoFromES = async (elasticsearchUrl, layer, fields, cookies, pathToFile) => {
+  let options = {
+    method: 'POST',
+    url: `${elasticsearchUrl}/_search?request_cache=false`,
+    data: {
+      "from": 0,
+      "size": 1,
+      "_source": fields,
+      "query": {
+        "term": {
+          "metadata-fr.link.name.keyword": layer
+        }
+      }
+    },
+  };
+
+  if (pathToFile) {
+    options = {
+      method: 'POST',
+      url: `${elasticsearchUrl}/_search?request_cache=false`,
+      data: {
+        "from": 0,
+        "size": 1,
+        "_source": fields,
+        "query": {
+          "bool": {
+            "should": [
+              {
+                "term": {
+                  "metadata-fr.link.name.keyword": layer
+                }
+              },
+              {
+                "match_phrase": {
+                  "metadata-fr.link.url": {
+                    "query": pathToFile
+                  }
+                }
+              }
+            ]
+          }
+        }
+      }
+    };
+  }
+
+  if (cookies) {
+    options.headers = {
+      cookie: cookies,
+    };
+  }
+
+  let response;
+
+  try {
+    response = await axios(options)
+  } catch (err) {
+    throw {
+      err,
+      status: 500,
+      message: "Request to Elasticsearch failed",
+    };
+  }
+
+  // If no results are found it means the specified dataset mvt layer doesn't exists
+  if (response.data.hits.hits < 1) {
+    throw {
+      status: 404,
+      message: "Layer not found.",
+    };
+  }
+
+  return response.data.hits.hits[0]._source;
+};
\ No newline at end of file
diff --git a/helpers/logs.helpers.js b/helpers/logs.helpers.js
new file mode 100644
index 0000000..17d5d98
--- /dev/null
+++ b/helpers/logs.helpers.js
@@ -0,0 +1,7 @@
+module.exports.printLog = (context, value) => {
+  console.log(`${new Date().toLocaleString("fr-FR")} [${context}] [log] `, value);
+}
+
+module.exports.printError = (context, error) => {
+  console.error(`${new Date().toLocaleString("fr-FR")} [${context}] [error] `, error);
+}
\ No newline at end of file
diff --git a/helpers/redis.helpers.js b/helpers/redis.helpers.js
new file mode 100644
index 0000000..77986ee
--- /dev/null
+++ b/helpers/redis.helpers.js
@@ -0,0 +1,61 @@
+const Redis = require("ioredis");
+const printError = require('./logs.helpers.js').printError;
+const printLog = require('./logs.helpers.js').printLog;
+
+module.exports.setRedisValue =  async (redisSentinelHost, redisSentinelPort, redisGroupName, 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;
+};
+
+module.exports.getRedisValue = async (redisSentinelHost, redisSentinelPort, redisGroupName, 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;
+};
\ No newline at end of file
diff --git a/helpers/request-processor.helper.js b/helpers/request-processor.helper.js
new file mode 100644
index 0000000..7da0bda
--- /dev/null
+++ b/helpers/request-processor.helper.js
@@ -0,0 +1,62 @@
+const getDatasetInfoFromES = require('./elasticsearch.helpers.js').getDatasetInfoFromES;
+const validateUserAccesses = require('./userAccesses.helpers.js').validateUserAccesses;
+const getRedisValue = require('../helpers/redis.helpers.js').getRedisValue;
+const setRedisValue = require('../helpers/redis.helpers.js').setRedisValue;
+
+module.exports.requestProcessor = async (req, res, layer, service, pathToFile) => {
+    const source = await getDatasetInfoFromES(
+      req.app.locals.config.elasticsearchUrl,
+      layer,
+      [
+        "editorial-metadata.isOpenAccess"
+      ],
+      req.headers.cookie,
+      pathToFile
+    );
+
+    // If dataset is open access proxy the request without adding the technical account credentials
+    if (source['editorial-metadata'].isOpenAccess) {
+      req.app.locals.proxies.unauthenticated.web(req, res, {});
+      return;
+    }
+
+    // If it is a restricted access layer and the user isn't authenticated then directly send a 401 error 
+    if (!source['editorial-metadata'].isOpenAccess && req.headers['x-anonymous-consumer']) {
+      throw {
+        status: 401,
+        message: "Unauthenticated, you need to be authenticated to access this resource."
+      }
+    }
+
+    // Look for an existing value of the user rights for the layer in redis
+    let userAccesses = await getRedisValue(req.app.locals.config.redisSentinelHost, req.app.locals.config.redisSentinelPort, req.app.locals.config.redisGroupName, `download-${layer}-${service}-${req.headers['x-consumer-username']}`);
+
+    // If value found and true, proxy the request adding the technical account credentials
+    if (userAccesses === 'true') {
+      req.app.locals.proxies.authenticated.web(req, res, {});
+      return;
+    }
+
+    // If value found and false, directly send a 403 forbidden error
+    if (userAccesses === 'false') {
+      throw {
+        status: 403,
+        message: "Forbidden access."
+      }
+    }
+
+    // If no pre-existing value for that user, layer and service triple then check the user rights
+    userAccesses = await validateUserAccesses(req.app.locals.config.legacyAuthMiddlewareUrl, req.headers, `${req.params.repo}/${layer}`, service);
+
+    if(!userAccesses) {
+      await setRedisValue(req.app.locals.config.redisSentinelHost, req.app.locals.config.redisSentinelPort, req.app.locals.config.redisGroupName, `download-${layer}-${service}-${req.headers['x-consumer-username']}`, false, req.app.locals.config.redisUnauthorizedTTL);
+      throw {
+        status: 403,
+        message: "Forbidden access."
+      }
+    }
+
+    await setRedisValue(req.app.locals.config.redisSentinelHost, req.app.locals.config.redisSentinelPort, req.app.locals.config.redisGroupName, `download-${layer}-${service}-${req.headers['x-consumer-username']}`, true, req.app.locals.config.redisAuthorizedTTL);
+    req.app.locals.proxies.authenticated.web(req, res, {});
+    return;
+}
\ No newline at end of file
diff --git a/helpers/userAccesses.helpers.js b/helpers/userAccesses.helpers.js
new file mode 100644
index 0000000..61ddfdf
--- /dev/null
+++ b/helpers/userAccesses.helpers.js
@@ -0,0 +1,31 @@
+const axios = require('axios');
+
+module.exports.validateUserAccesses = async (legacyAuthMiddlewareUrl, headers, pathToLayer, service) => {
+  const options = {};
+
+  if (headers.cookie) {
+    options.Cookie = headers.cookie;
+  }
+
+  if(headers["x-xsrf-token"]) {
+    options["x-xsrf-token"] = headers['x-xsrf-token'];
+  }
+
+  const response = await axios.get(`${legacyAuthMiddlewareUrl}/user/resources`, {
+    headers: options,
+  }).catch((err) => {
+    throw {
+      err,
+      status: err.response.status,
+      message: "Request to Legacy Auth Middleware failed",
+    };
+  });
+
+  if (response.data &&
+      response.data.length > 0 &&
+      response.data.find(e => e.urlPattern === pathToLayer && e.serviceName === service && new Date(e.validUntil) > new Date())) {
+    return true;
+  }
+
+  return false;
+}
\ No newline at end of file
diff --git a/index.js b/index.js
index 8d0a144..660fb2a 100644
--- a/index.js
+++ b/index.js
@@ -1,270 +1,89 @@
-const http = require('http');
+const express = require('express');
+const app = express();
+const port = 9000;
 const httpProxy = require('http-proxy');
-const axios = require('axios');
-const URL = require('url');
-const Redis = require("ioredis");
+const cookieParser = require('cookie-parser');
+const printError = require('./helpers/logs.helpers.js').printError;
 
-// 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;
-const ignKey = process.env.IGN_KEY;
+// Parse the request headers in order to populate req.cookies
+app.use(cookieParser());
 
-// Configuring the different proxy server
-// Proxy IGN Ortho
-var ingProxy = httpProxy.createProxyServer({
-  changeOrigin: true,
-  target: 'https://wxs.ign.fr/' + ignKey + '/geoportail/r/wms/',
-});
-
-// Configuring the different proxy server
-// Proxy WMS
-var wmsProxy = httpProxy.createProxyServer({
-  changeOrigin: true,
-  target: proxyHostTarget,
-  auth: `${technicalAccountUsername}:${technicalAccountPassword}`,
+app.use(function(req, res, next) {
+  res.header("Access-Control-Allow-Origin", "*");
+  next();
 });
 
-// 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,
-});
+// CONFIG
 
-ingProxy.on('proxyReq', function (proxyReq, req) {
-  req._proxyReq = proxyReq;
-});
+app.locals.config = {
+  redisSentinelHost: process.env.REDIS_SENTINEL_HOST,
+  redisSentinelPort: process.env.REDIS_SENTINEL_PORT,
+  redisGroupName: process.env.REDIS_GROUP_NAME,
+  redisAuthorizedTTL: process.env.REDIS_AUTHORIZED_TTL, // In seconds
+  redisUnauthorizedTTL: process.env.REDIS_UNAUTHORIZED_TTL, // In seconds
+  elasticsearchUrl: process.env.ELASTICSEARCH_URL,
+  legacyAuthMiddlewareUrl: process.env.LEGACY_AUTH_MIDDLEWARE_URL,
+  technicalAccountUsername: process.env.TECHNICAL_ACCOUNT_USERNAME,
+  technicalAccountPassword: process.env.TECHNICAL_ACCOUNT_PASSWORD,
+  proxyHostTarget: process.env.PROXY_HOST_TARGET,
+  ignKey: process.env.IGN_KEY,
+}
 
-// 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;
-});
+// Proxies configuration
 
-mvtUnauthProxy.on('proxyReq', function (proxyReq, req) {
+// IGN Ortho
+const IGNProxy = httpProxy.createProxyServer({
+  changeOrigin: true,
+  target: 'https://wxs.ign.fr/' + app.locals.config.ignKey + '/geoportail/r/wms/',
+}).on('proxyReq', (proxyReq, req) => {
+  // keep a reference of the proxyRequest in the req object
   req._proxyReq = proxyReq;
-});
-
-ingProxy.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(`ING Error, req.socket.destroyed: ${req.socket.destroyed}, ${err}`);
-});
-
-wmsProxy.on('error', function (err, req, res) {
+}).on('error', (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}`);
+  return console.log(`ING proxy error, req.socket.destroyed: ${req.socket.destroyed}, ${err}`);
 });
 
-mvtProxy.on('error', function (err, req, res) {
+const AuthenticatedProxy = httpProxy.createProxyServer({
+  changeOrigin: true,
+  target: app.locals.config.proxyHostTarget,
+  auth: `${app.locals.config.technicalAccountUsername}:${app.locals.config.technicalAccountPassword}`,
+}).on('proxyReq', (proxyReq, req) => {
+  // keep a reference of the proxyRequest in the req object
+  req._proxyReq = proxyReq;
+}).on('error', (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}`);
+  return printError(`Authenticated proxy error, req.socket.destroyed: ${req.socket.destroyed}, ${err}`);
 });
 
-mvtUnauthProxy.on('error', function (err, req, res) {
+var UnauthenticatedProxy = httpProxy.createProxyServer({
+  changeOrigin: true,
+  target: app.locals.config.proxyHostTarget,
+}).on('proxyReq', (proxyReq, req) => {
+  // keep a reference of the proxyRequest in the req object
+  req._proxyReq = proxyReq;
+}).on('error', (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}`);
+  return printError(`Unauthenticated proxy Error, req.socket.destroyed: ${req.socket.destroyed}, ${err}`);
 });
 
-// Create an HTTP server
-http.createServer(async function (req, res) {
-
-  /******* IGN */
-  if (req.url.includes('/ign')) {
-    req.headers['referer'] = 'grandlyon.com';
-    ingProxy.web(req, res, {});
-    return;
-  }
-
-  /******************* WMS *****************************/
-  if (req.url.includes('/wms')) {
-    wmsProxy.web(req, res, {});
-    return;
-  }
-
-  /******************* MVT *****************************/
-  if (req.url.includes('/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"]) {
-      mvtUnauthProxy.web(req, res, {});
-      return;
-    }
+app.locals.proxies = {
+  ign: IGNProxy,
+  authenticated: AuthenticatedProxy,
+  unauthenticated: UnauthenticatedProxy,
+};
 
-    // Read the requested layer from the url
-    const layer = getParameterValueFromUrl(req.url, 'LAYERS');
-
-    if (!layer) {
-      res.statusCode = 400;
-      res.end();
-      return;
-    }
-
-    const userRightsOnTheLayer = await getRedisValue(`${layer}-${req.headers['x-consumer-username']}`);
-
-    if (userRightsOnTheLayer === 'true') {
-      mvtProxy.web(req, res, {});
-      return;
-    }
-
-    if (userRightsOnTheLayer === 'false') {
-      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,
-      }
-    };
-
-    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;
-    }
-
-    const editorialMetadata = response.data.hits.hits[0]._source['editorial-metadata'];
-
-    if (!editorialMetadata.isOpenAccess && editorialMetadata.isSample) {
-      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);
-
-    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);
-}
+// ROUTES
+app.use(require('./routes/index.js'));
 
-function printError(context, error) {
-  console.error(`${new Date().toLocaleString("fr-FR")} [${context}] [error] `, error);
-}
\ No newline at end of file
+// STARTING SERVER
+app.listen(port, () => printError(`Proxy listening on port: ${port}`));
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 1064f9d..0f390b2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,9 +1,23 @@
 {
-  "name": "proxy-map-services",
-  "version": "1.0.1",
+  "name": "query-proxy",
+  "version": "2.0.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
+    "accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "requires": {
+        "mime-types": "~2.1.24",
+        "negotiator": "0.6.2"
+      }
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
     "axios": {
       "version": "0.19.0",
       "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
@@ -36,16 +50,80 @@
         }
       }
     },
+    "body-parser": {
+      "version": "1.19.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+      "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+      "requires": {
+        "bytes": "3.1.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "on-finished": "~2.3.0",
+        "qs": "6.7.0",
+        "raw-body": "2.4.0",
+        "type-is": "~1.6.17"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
+    "bytes": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+      "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+    },
     "cluster-key-slot": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
       "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw=="
     },
+    "content-disposition": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+      "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+      "requires": {
+        "safe-buffer": "5.1.2"
+      }
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+    },
     "cookie": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
       "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
     },
+    "cookie-parser": {
+      "version": "1.4.5",
+      "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
+      "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
+      "requires": {
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6"
+      }
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
     "debug": {
       "version": "3.2.6",
       "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
@@ -59,11 +137,122 @@
       "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
       "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
     },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+    },
     "eventemitter3": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
       "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg=="
     },
+    "express": {
+      "version": "4.17.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+      "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+      "requires": {
+        "accepts": "~1.3.7",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.19.0",
+        "content-disposition": "0.5.3",
+        "content-type": "~1.0.4",
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.1.2",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.5",
+        "qs": "6.7.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.1.2",
+        "send": "0.17.1",
+        "serve-static": "1.14.1",
+        "setprototypeof": "1.1.1",
+        "statuses": "~1.5.0",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
+    "finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
     "follow-redirects": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz",
@@ -72,10 +261,27 @@
         "debug": "^3.0.0"
       }
     },
-    "http": {
-      "version": "0.0.0",
-      "resolved": "https://registry.npmjs.org/http/-/http-0.0.0.tgz",
-      "integrity": "sha1-huYybSnF0Dnen6xYSkVon5KfT3I="
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+    },
+    "http-errors": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+      "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+      "requires": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.1",
+        "statuses": ">= 1.5.0 < 2",
+        "toidentifier": "1.0.0"
+      }
     },
     "http-proxy": {
       "version": "1.18.0",
@@ -87,6 +293,19 @@
         "requires-port": "^1.0.0"
       }
     },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
     "ioredis": {
       "version": "4.14.1",
       "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.14.1.tgz",
@@ -113,6 +332,11 @@
         }
       }
     },
+    "ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+    },
     "is-buffer": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
@@ -128,20 +352,96 @@
       "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
       "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
     },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+    },
+    "mime-db": {
+      "version": "1.43.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
+      "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
+    },
+    "mime-types": {
+      "version": "2.1.26",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
+      "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
+      "requires": {
+        "mime-db": "1.43.0"
+      }
+    },
     "ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
       "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
     },
-    "punycode": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
-      "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
+    "negotiator": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "proxy-addr": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
+      "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
+      "requires": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.9.1"
+      }
+    },
+    "qs": {
+      "version": "6.7.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+      "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
     },
-    "querystring": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
-      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
+    "range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+    },
+    "raw-body": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+      "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+      "requires": {
+        "bytes": "3.1.0",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      }
     },
     "redis-commands": {
       "version": "1.5.0",
@@ -166,19 +466,112 @@
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
       "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
     },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "send": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+      "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "~1.7.2",
+        "mime": "1.6.0",
+        "ms": "2.1.1",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.1",
+        "statuses": "~1.5.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          },
+          "dependencies": {
+            "ms": {
+              "version": "2.0.0",
+              "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+              "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+            }
+          }
+        },
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+        }
+      }
+    },
+    "serve-static": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+      "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.17.1"
+      }
+    },
+    "setprototypeof": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+      "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+    },
     "standard-as-callback": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.0.1.tgz",
       "integrity": "sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg=="
     },
-    "url": {
-      "version": "0.11.0",
-      "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
-      "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+    "statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+    },
+    "toidentifier": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+      "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
+    },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
       "requires": {
-        "punycode": "1.3.2",
-        "querystring": "0.2.0"
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
       }
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
     }
   }
 }
diff --git a/package.json b/package.json
index 9585de3..03304e2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
-  "name": "proxy-map-services",
-  "version": "1.1.0",
+  "name": "query-proxy",
+  "version": "2.0.0",
   "description": "",
   "main": "index.js",
   "scripts": {
@@ -11,10 +11,9 @@
   "license": "GNU Affero General Public License v3.0",
   "dependencies": {
     "axios": "^0.19.0",
-    "cookie": "^0.4.0",
-    "http": "0.0.0",
+    "cookie-parser": "^1.4.5",
+    "express": "^4.17.1",
     "http-proxy": "^1.18.0",
-    "ioredis": "^4.14.1",
-    "url": "^0.11.0"
+    "ioredis": "^4.14.1"
   }
 }
diff --git a/routes/download.routes.js b/routes/download.routes.js
new file mode 100644
index 0000000..e0e98d2
--- /dev/null
+++ b/routes/download.routes.js
@@ -0,0 +1,108 @@
+var router = require('express').Router();
+const requestProcessor = require('../helpers/request-processor.helper.js').requestProcessor;
+const printError = require('../helpers/logs.helpers.js').printError;
+
+router.get('/wms/:repo*', async (req, res) => {
+
+  const layer = req.query.layers || req.query.LAYERS;
+
+  try {
+
+    return await requestProcessor(req, res, layer, 'wms');
+
+  } catch (err) {
+    printError(`/wms/${req.params.repo}?layers=${layer}`, err);
+    res.status(err.status).send(err);
+    return;
+  }
+});
+
+router.get('/wfs/:repo*', async (req, res) => {
+
+  const layer = req.query.typename || req.query.TYPENAME;
+
+  try {
+
+    return await requestProcessor(req, res, layer, 'wfs');
+
+  } catch (err) {
+    printError(`/wfs/${req.params.repo}?typename=${layer}`, err);
+    res.status(err.status).send(err);
+    return;
+  }
+});
+
+router.get('/ws/:repo/:layer*', async (req, res) => {
+
+  const layer = req.params.layer;
+
+  try {
+  
+    return await requestProcessor(req, res, layer, 'ws');
+
+  } catch (err) {
+    printError(`/ws/${req.params.repo}/${layer}`, err);
+    res.status(err.status).send(err);
+    return;
+  }
+});
+
+router.get('/kml/:repo*', async (req, res) => {
+
+  const layer = req.query.typename || req.query.TYPENAME;
+
+  try {
+
+    return await requestProcessor(req, res, layer, 'kml');
+
+  } catch (err) {
+    printError(`/kml/${req.params.repo}?typename=${layer}`, err);
+    res.status(err.status).send(err);
+    return;
+  }
+});
+
+router.get('/wcs/:repo*', async (req, res) => {
+
+  const layer = req.query.identifiers || req.query.IDENTIFIERS;
+
+  try {
+
+    return await requestProcessor(req, res, layer, 'wcs');
+
+  } catch (err) {
+    printError(`/wcs/${req.params.repo}?identifiers=${layer}`, err);
+    res.status(err.status).send(err);
+    return;
+  }
+});
+
+router.get('/files/:repo/:layer*', async (req, res) => {
+
+  const layer = req.params.layer;
+
+  try {
+  
+    return await requestProcessor(req, res, layer, 'files', req.path);
+
+  } catch (err) {
+    printError(`/files/${req.params.repo}/${layer}`, err);
+    res.status(err.status).send(err);
+    return;
+  }
+});
+
+router.get('/catalogue*', async (req, res) => {
+
+  try {
+  
+    req.app.locals.proxies.unauthenticated.web(req, res, {});
+
+  } catch (err) {
+    printError(`/catalogue`, err);
+    res.status(err.status).send(err);
+    return;
+  }
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/routes/index.js b/routes/index.js
new file mode 100644
index 0000000..80eee0e
--- /dev/null
+++ b/routes/index.js
@@ -0,0 +1,6 @@
+var router = require('express').Router();
+
+router.use('/map', require('./map.routes.js'));
+router.use('/download', require('./download.routes.js'));
+
+module.exports = router;
\ No newline at end of file
diff --git a/routes/map.routes.js b/routes/map.routes.js
new file mode 100644
index 0000000..5b60fae
--- /dev/null
+++ b/routes/map.routes.js
@@ -0,0 +1,76 @@
+const router = require('express').Router();
+const getRedisValue = require('../helpers/redis.helpers.js').getRedisValue;
+const setRedisValue = require('../helpers/redis.helpers.js').setRedisValue;
+const getDatasetInfoFromES = require('../helpers/elasticsearch.helpers.js').getDatasetInfoFromES;
+
+router.get('/ign', (req, res, next) => {
+  req.headers['referer'] = 'grandlyon.com';
+  req.app.locals.proxies.ign.web(req, res, {});
+  return;
+});
+
+router.get('/wms*', (req, res, next) => {
+  req.app.locals.proxies.authenticated.web(req, res, {});
+  return;
+});
+
+router.get('/mvt*', async (req, res, next) => {
+  
+  // If no cookies then then we can't identify a user and directly proxy the request without credentials
+  if (req.headers["x-anonymous-consumer"]) {
+    req.app.locals.proxies.unauthenticated.web(req, res, {});
+    return;
+  }
+
+  // Read the requested layer from the url
+  const layer = req.query.LAYERS;
+
+  if (!layer) {
+    res.status(400).send({err: "Bad request, missing LAYERS parameter."})
+    return;
+  }
+
+  const userRightsOnTheLayer = await getRedisValue(req.app.locals.config.redisSentinelHost, req.app.locals.config.redisSentinelPort, req.app.locals.config.redisGroupName, `map-mvt-${layer}-${req.headers['x-consumer-username']}`);
+
+  if (userRightsOnTheLayer === 'true') {
+    req.app.locals.proxies.authenticated.web(req, res, {});
+    return;
+  }
+
+  if (userRightsOnTheLayer === 'false') {
+    res.status(401).send({err: 'Unauthenticated, you don\'t have access to this layer'});
+    return;
+  }
+
+  let source;
+
+  try {
+    source = await getDatasetInfoFromES(
+      req.app.locals.config.elasticsearchUrl,
+      layer,
+      [
+        "editorial-metadata.isSample",
+        "editorial-metadata.isOpenAccess"
+      ],
+      req.headers.cookie,
+    );
+  } catch(err) {
+    printError('/mvt', err);
+    res.status(err.status).send(err);
+    return;
+  }
+
+  const editorialMetadata = source['editorial-metadata'];
+
+  if (!editorialMetadata.isOpenAccess && editorialMetadata.isSample) {
+    await setRedisValue(req.app.locals.config.redisSentinelHost, req.app.locals.config.redisSentinelPort, req.app.locals.config.redisGroupName, `map-mvt-${layer}-${req.headers['x-consumer-username']}`, false, req.app.locals.config.redisUnauthorizedTTL);
+    res.status(401).send();
+    return;
+  }
+
+  await setRedisValue(req.app.locals.config.redisSentinelHost, req.app.locals.config.redisSentinelPort, req.app.locals.config.redisGroupName, `map-mvt-${layer}-${req.headers['x-consumer-username']}`, true, req.app.locals.config.redisAuthorizedTTL);
+
+  req.app.locals.proxies.authenticated.web(req, res, {});
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/template.env b/template.env
index 01d8d57..746a801 100644
--- a/template.env
+++ b/template.env
@@ -1,9 +1,10 @@
 TAG=<tag of the image to be used>
-PROXY_MAP_SERVICES_BIND_PORT=<proxy port>
+PROXY_QUERY_BIND_PORT=<proxy port>
 TECHNICAL_ACCOUNT_PASSWORD=<username of the technical account which has access to all wms and mvt>
 TECHNICAL_ACCOUNT_USERNAME=<password of the technical account>
 ELASTICSEARCH_URL=<url of the elasticsearch instance>
 PROXY_HOST_TARGET=<url of the map services>
+LEGACY_AUTH_MIDDLEWARE_URL=<url of the legacy auth middleware>
 REDIS_MASTER_HOST=<host of the redis master>
 REDIS_SENTINEL_PORT=<port of the redis sentinel>
 REDIS_SENTINEL_HOST=<host of the redis sentinel>
-- 
GitLab