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}`); }); // 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); }