Skip to content

Instantly share code, notes, and snippets.

@kenmoini
Last active October 9, 2019 20:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kenmoini/291fe518608324ab93c70d6928ffe7fd to your computer and use it in GitHub Desktop.
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 …
/*
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