Skip to content

Commit

Permalink
Merge pull request #5 from transcend-io/bencmbrook/eshopit
Browse files Browse the repository at this point in the history
eShopIt demo
  • Loading branch information
bencmbrook authored Sep 26, 2020
2 parents c3eeaad + df0b75f commit e9c8237
Show file tree
Hide file tree
Showing 19 changed files with 2,165 additions and 545 deletions.
5 changes: 3 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"venv",
"webhook",
"webhooks"
]
}
],
"editor.formatOnSave": true
}
5 changes: 5 additions & 0 deletions javascript/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all"
}
11 changes: 10 additions & 1 deletion javascript/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Javascript

Internal system integrations in javascript. Check out the parent [README](../README.md) for more context.
Internal system integrations in JavaScript. Check out the parent [README](../README.md) for more context.

## Installation

Expand All @@ -25,3 +25,12 @@ You can test against this example live by adding it to [your datamap](https://ap
```sh
ngrok http -hostname=test-javascript.ngrok.io 4445
```

## Transcend developer notes

- This code is actively used for live demos.
- This is associated with the demo account, [eShopIt](https://e-shop-it.trsnd.co).
- It is hosted on [Render](http://render.com/) at <https://transcend-example.onrender.com>.
- A design choice was made not to put webhook verification on an Express middleware. It's a nice refactor, but it can be esoteric to readers.

- Use ngrok
7 changes: 7 additions & 0 deletions javascript/eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": ["airbnb", "plugin:prettier/recommended"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": ["error"]
}
}
61 changes: 0 additions & 61 deletions javascript/index.js

This file was deleted.

31 changes: 21 additions & 10 deletions javascript/package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
{
"name": "demo",
"name": "eshopit",
"version": "1.0.0",
"description": "",
"main": "index.js",
"description": "Example of an application integrated with Transcend.",
"main": "src/app.js",
"scripts": {
"start": "node index.js",
"start": "node src/app.js",
"dev": "nodemon src/app.js",
"ngrok": "ngrok http 8081",
"lint": "eslint",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"author": "Transcend Inc.",
"license": "ISC",
"dependencies": {
"body-parser": "^1.18.3",
"express": "^4.16.4",
"jws": "^4.0.0",
"request": "^2.88.0"
"body-parser": "^1.19.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-async-handler": "^1.1.4",
"got": "^11.6.2",
"jsonwebtoken": "^8.5.1",
"morgan": "^1.10.0"
},
"devDependencies": {
"ngrok": "^3.1.0"
"eslint": "^7.9.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4",
"nodemon": "^2.0.4",
"prettier": "^2.1.2"
}
}
57 changes: 57 additions & 0 deletions javascript/src/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Developer note: this server is live at https://transcend-example.onrender.com for the eShopIt demo
* https://e-shop-it.trsnd.co
*/

// Load environment variables
require('dotenv').config();

// Libraries
const express = require('express');
const bodyParser = require('body-parser');
const morgan = require('morgan');

// Load webhook handling middlewares
const handleEnrichmentWebhook = require('./handleEnrichmentWebhook');
const handleDSRWebhook = require('./handleDSRWebhook');

// Constants
const {
PORT
} = require('./constants');

// Set up the server
const app = express();
const port = PORT;
app.all('/health', (_, res) => res.sendStatus(200));

// Middlewares
app.use(morgan('tiny'));

app.use(
bodyParser.urlencoded({
extended: false,
}),
);

app.use(bodyParser.json());

/*
* Receive webhook for identity enrichment (optional)
* This path is set by you in the Admin Dashboard.
*/
app.post(
'/transcend/enrichment',
handleEnrichmentWebhook,
);

/*
* Receive webhook (Transcend's notification to this server)
* This path is set by you in the Admin Dashboard.
*/
app.post(
'/transcend/new-dsr',
handleDSRWebhook,
);

app.listen(port, () => console.info(`Example custom data silo listening on port ${port}.`));
11 changes: 11 additions & 0 deletions javascript/src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const {
TRANSCEND_API_KEY,
ENRICHMENT_SIGNING_KEY,
PORT,
} = process.env;

module.exports = {
TRANSCEND_API_KEY,
ENRICHMENT_SIGNING_KEY,
PORT: PORT || 8081,
}
51 changes: 51 additions & 0 deletions javascript/src/handleDSRWebhook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const asyncHandler = require('express-async-handler');

// Helpers
const {
verifyAndExtractWebhook,
} = require('./helpers');

const scheduleAccessRequest = require('./scheduleAccessRequest');

/////////////////////
// WEBHOOK HANDLER //
/////////////////////

module.exports = asyncHandler(async (req, res, next) => {
// Verify the incoming webhook is coming from Transcend, and via the Sombra gateway.
try {
await verifyAndExtractWebhook(req.headers['x-sombra-token']);
} catch (error) {
// If the webhook doesn't pass verification, reject it.
return res.sendStatus(401).send('You are not Transcend!');
}

console.info(`Received DSR webhook - https://app.transcend.io${req.body.extras.request.link}`);

// Extract metadata from the webhook
const userIdentifier = req.body.coreIdentifier.value;
const webhookType = req.body.type; // ACCESS, ERASURE, etc: https://docs.transcend.io/docs/receiving-webhooks#events
const nonce = req.headers['x-transcend-nonce'];

// Depending on the type of webhook, respond accordingly.
switch (webhookType) {
case 'ACCESS':
// Schedule the job to run. Results of the job are sent to Transcend separately (in a different HTTP request, in case the job is slow).
scheduleAccessRequest(userIdentifier, nonce, req.body.extras.request.link);

// Respond OK - webhook received properly.
res.sendStatus(200);
break;

case 'ERASURE':
// Respond with an early "no user found" signal.
res.sendStatus(204);
break;

default:
console.warn(`This type of DSR webhook is unimplemented - https://app.transcend.io${req.body.extras.request.link}`);
return res.status(400).send('This type of privacy request is unimplemented.');
}

console.info(`Successfully responded to DSR webhook - https://app.transcend.io${req.body.extras.request.link}`);
});
69 changes: 69 additions & 0 deletions javascript/src/handleEnrichmentWebhook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const asyncHandler = require('express-async-handler');

// Helpers
const {
createEnricherJwt,
checkIfFraudster,
checkForLegalHold,
verifyAndExtractWebhook,
} = require('./helpers');

/////////////////////
// WEBHOOK HANDLER //
/////////////////////

module.exports = asyncHandler(async (req, res, next) => {
// Verify the incoming webhook is coming from Transcend, and via the Sombra gateway.
let signedBody;
try {
signedBody = await verifyAndExtractWebhook(req.headers['x-sombra-token']);
} catch (error) {
// If the webhook doesn't pass verification, reject it.
return res.status(401).send('You are not Transcend!');
}

console.info(`Received Enrichment webhook - https://app.transcend.io${req.body.extras.request.link}`);

// Add new identifers
const signedRequestIdentifiers = {
email: [
createEnricherJwt({
value: '[email protected]',
}),
createEnricherJwt({
value: '[email protected]',
}),
],
phone: [
createEnricherJwt({
countryCode: 'US',
number: '+18609066012',
}),
],
};

// Check if we should place a hold on this request
const requestIdentifier = signedBody.value;
const isFraudster = await checkIfFraudster(requestIdentifier);
const hasLegalHold = await checkForLegalHold(requestIdentifier);

// In this case, we are automatically cancelling requests from fraudsters.
if (isFraudster)
return res.json({
status: 'CANCELED',
});

// In this case, we are putting a hold on the request so legal can review it.
if (hasLegalHold)
return res.json({
status: 'ON_HOLD',
signedRequestIdentifiers,
});

// Allow the request to proceed
res.json({
signedRequestIdentifiers,
});

console.info(`Successfully responded to Enrichment webhook - https://app.transcend.io${req.body.extras.request.link}`);
});
10 changes: 10 additions & 0 deletions javascript/src/helpers/checkForLegalHold.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Check user against legal hold
* Is there a reason why we can't fulfill this request right now?
*
* Since this is a demo, it just checks for [email protected]
*/
module.exports = async function checkForLegalHold(email) {
const flag = email.split('@')[0].split('+')[1];
return ['legalhold'].includes(flag);
}
10 changes: 10 additions & 0 deletions javascript/src/helpers/checkIfFraudster.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Check user against fraud systems
* Is this user suspected of fraud?
*
* Since this is a demo, it just checks for [email protected]
*/
module.exports = async function checkIfFraudster(email) {
const flag = email.split('@')[0].split('+')[1];
return ['thefraudster', 'fraud', 'fraudster'].includes(flag);
}
28 changes: 28 additions & 0 deletions javascript/src/helpers/createEnricherJwt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Libraries
const jwt = require('jsonwebtoken');

// Constants
const {
ENRICHMENT_SIGNING_KEY,
} = require('../constants');

// In this example, the signing key is stored as a base64-encoded env var
const DECODED_ENRICHMENT_SIGNING_KEY = Buffer.from(ENRICHMENT_SIGNING_KEY, 'base64').toString();

/**
* Sign an identifier with a JWT
* @param {Object} content
*/
module.exports = function createEnricherJwt(content) {
return jwt.sign(
// The content to sign
content,
// The private key for the enricher (you should have uploaded the public key)
DECODED_ENRICHMENT_SIGNING_KEY, {
algorithm: 'ES384',
expiresIn: '1d',
// organization URI from https://app.transcend.io/settings#OrganizationSettings
audience: 'e-shop-it',
}
)
};
Loading

0 comments on commit e9c8237

Please sign in to comment.