Last active
October 9, 2019 20:31
-
-
Save kenmoini/291fe518608324ab93c70d6928ffe7fd to your computer and use it in GitHub Desktop.
RAWR Partner Portal API Server Excerpt - A demonstration of one of the Node.js Express based API microservices used in RAWR. A number of routes have been truncated for privacy and security reason, and proper operation will still require the Dockerfile, Jenkinsfile, package.json and .env files, other routes for this service, as well as the Auth0 …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
NOTE: This file has been truncated | |
RAWR Partner Portal API Server Excerpt - A demonstration of one of the Node.js Express based API microservices used in RAWR. | |
A number of routes have been truncated for privacy and security reason, and proper operation will still require the Dockerfile, Jenkinsfile, package.json and .env files, other routes for this service, as well as the Auth0 application stack configured as needed, and other microservices used in the K8s/OCP cluster. | |
Fierce Software - RAWR Partner Portal API Server (rawr-partner-fe-api) | |
The Rapid Analytic Web Resource (RAWR) extends some functionality to our vendor partners | |
This service relies on Auth0, AWS SQS, and the following supplimentary Laravel Lumen services: | |
- rawr-partner-be-get-analytics | |
- rawr-partner-be-get-authentication | |
- rawr-partner-be-get-contact | |
- rawr-partner-be-get-contacts | |
- rawr-partner-be-get-mail-campaign | |
- rawr-partner-be-get-mail-campaigns | |
- rawr-partner-be-get-page | |
- rawr-partner-be-get-pages | |
- rawr-partner-be-get-realm | |
- rawr-partner-be-get-realms | |
- rawr-partner-be-get-user | |
- rawr-partner-be-get-users | |
- rawr-partner-be-set-contact | |
- rawr-partner-be-set-realm | |
- rawr-partner-be-queue-processor | |
- rawr-partner-fe-application | |
- rawr-partner-fe-authentication | |
*/ | |
console.log('RAWR Partner Portal API Server starting...'); | |
const express = require('express'); | |
const app = express(); | |
const AWS = require('aws-sdk'); | |
const cors = require('cors'); | |
const jwt = require('express-jwt'); | |
const jwtAuthz = require('express-jwt-authz'); | |
const jwksRsa = require('jwks-rsa'); | |
const request = require('request'); | |
//============================================================================== | |
// Load global configuration for interation with Auth0, SQS Queue, and Kubernetes Service Account Secret | |
require('dotenv').config(); | |
//============================================================================== | |
// Config checks... | |
if (!process.env.AUTH0_DOMAIN || !process.env.AUTH0_AUDIENCE) { | |
throw 'Make sure you have AUTH0_DOMAIN, and AUTH0_AUDIENCE in your .env file'; | |
} | |
if (!process.env.AWS_SECRET || !process.env.AWS_KEY || !process.env.AWS_REGION || !process.env.AWS_SQS_URL) { | |
throw 'Make sure you have AWS_SECRET, AWS_KEY, AWS_REGION, and AWS_SQS_URL in your .env file'; | |
} | |
if (!process.env.K8S_SVC_ACCT || !process.env.K8S_SECRET) { | |
throw 'Make sure you have K8S_SVC_ACCT, and K8S_SECRET in your .env file'; | |
} | |
//============================================================================== | |
// Cross Origin Request Stuff (CORS) /s | |
const corsOptions = { | |
origin: 'http://rawr-partner-fe-api:8080' | |
}; | |
app.use(cors(corsOptions)); | |
//============================================================================== | |
// Auth0 JWT Setup | |
const checkJWT = jwt({ | |
// Dynamically provide a signing key based on the [Key ID](https://tools.ietf.org/html/rfc7515#section-4.1.4) header parameter ("kid") and the signing keys provided by the JWKS endpoint. | |
secret: jwksRsa.expressJwtSecret({ | |
cache: true, | |
rateLimit: true, | |
jwksRequestsPerMinute: 5, | |
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json` | |
}), | |
// Validate the audience and the issuer. | |
audience: process.env.AUTH0_AUDIENCE, | |
issuer: `https://${process.env.AUTH0_DOMAIN}/`, | |
// Do NOT use HS256 - single pre-shared key increases attack blast radius | |
algorithms: ['RS256'] | |
}); | |
// Permission Scopes | |
// Do NOT set as list/dict/array/etc - the single lines are atomic and are easier to manage with Auth0 and the supplimentary services | |
// GET/Viewer Role Permissions | |
const scopes_get_analytics = jwtAuthz(['read:analytics']); | |
const scopes_get_contacts = jwtAuthz(['read:contacts']); | |
const scopes_get_mail_campaigns = jwtAuthz(['read:mail_campaigns']); | |
const scopes_get_pages = jwtAuthz(['read:pages']); | |
const scopes_get_realms = jwtAuthz(['read:realms']); | |
// POST/Set Moderator/Admin Role Permissions | |
// Users scoped with WRITE also have READ | |
const scopes_set_contacts = jwtAuthz(['read:contacts', 'write:contacts']); | |
const scopes_set_realms = jwtAuthz(['read:realms', 'write:realms']); | |
// AWS SQS | |
// Create an SQS service object | |
var aws_sqs = new AWS.SQS({apiVersion: '2012-11-05'}); | |
//============================================================================== | |
// Public Routes | |
// Kubernetes Health Check Endpoint | |
app.get('/healthz', function(req, res) { | |
// Non-JSON response sent - all K8s needs is a HTTP status really | |
var r = req.headers.referer || '/' | |
res.status(200).send('<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=' + r + '"></head><body>ok</body></html>'); | |
}); | |
//============================================================================== | |
// Private Routes | |
// Get Homescreen page | |
app.get('/api/v2/page/partner-home', checkJWT, scopes_get_pages, function(req, res) { | |
// Create request to the rawr-partner-be-get-pages service for the home page associated to this user. | |
// Uses: rawr-partner-be-get-pages, rawr-partner-be-get-realm, rawr-partner-be-get-user | |
const request_options = { | |
url: 'http://rawr-partner-be-get-page/', | |
headers: { | |
'RAWR_UID': req.rawr_uid, | |
'TARGET_PAGE': 'partner-home' | |
} | |
}; | |
function callback(error, response, body) { | |
if (!error && response.statusCode == 200) { | |
const body = JSON.parse(body); | |
res.json({ | |
req_status: 'success', | |
body: body | |
}); | |
} | |
else { | |
res.json({ | |
req_status: 'failed', | |
error: error, | |
status_code: response.statusCode | |
}); | |
} | |
} | |
request(request_options, request_callback); | |
}); | |
//============================================================================== | |
// Additional routes truncated for privacy & security... | |
//============================================================================== | |
// Request a click-stream report | |
app.get('/api/v2/analytics/click-stream', checkJWT, scopes_get_analytics, function(req, res) { | |
// Create request to the AWS SQS Service to begin a ClickStream Job for a campaign or contact | |
// Uses: rawr-partner-be-get-pages, rawr-partner-be-get-realm, rawr-partner-be-get-user, rawr-partner-be-analytics, rawr-partner-be-get-user, rawr-partner-be-get-mail-campaign, rawr-partner-be-queue-processor | |
var params = { | |
DelaySeconds: 1, | |
MessageAttributes: { | |
"RAWR_UID": { | |
DataType: "Binary", | |
StringValue: req.rawr_uid | |
}, | |
// Is this a campaign or contact? | |
"RAWR_ClickstreamType": { | |
DataType: "String", | |
StringValue: req.rawr_clickstream_type | |
}, | |
"RAWR_ClickstreamTargetID": { | |
DataType: "Number", | |
StringValue: req.rawr_clickstream_target_id | |
} | |
}, | |
MessageBody: "Clickstream request filed by " + req.rawr_uid, | |
// Use FIFO queues for Clickstream reports, scales better on demand | |
// IDs are set with UID, Type, and Target as Targets can overlap between Types | |
MessageDeduplicationId: "CS_" + req.rawr_uid + "_" + req.rawr_clickstream_type + "_" + req.rawr_clickstream_target_id, // Required for FIFO queues | |
MessageId: "CS_" + req.rawr_uid + "_" + req.rawr_clickstream_type + "_" + req.rawr_clickstream_target_id, // Required for FIFO queues | |
QueueUrl: process.env.AWS_SQS_URL | |
}; | |
//Send SQS Message, process response back to frontend application | |
aws_sqs.sendMessage(params, function(err, data) { | |
if (err) { | |
res.json({ | |
req_status: 'failed', | |
error: err, | |
//SQS doesn't give HTTP status codes of course, so we just kinda make our own... | |
status_code: '500' | |
}); | |
} | |
else { | |
res.json({ | |
req_status: 'success', | |
body: data.MessageId, | |
//SQS doesn't give HTTP status codes of course, so we just kinda make our own... | |
status_code: '200' | |
}); | |
} | |
}); | |
}); | |
app.use(function(err, req, res, next){ | |
console.error(err.stack); | |
return res.status(err.status).json({ message: err.message }); | |
}); | |
//============================================================================== | |
// Launch server | |
app.listen(8080); | |
console.log('Listening on http://:8080'); | |
console.log('RAWR Partner Portal API Server launched.'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment