Skip to content

Commit

Permalink
Simolify and clean up #51
Browse files Browse the repository at this point in the history
  • Loading branch information
vlad-ignatov committed Jun 12, 2021
1 parent 07ab7f8 commit a0e4350
Show file tree
Hide file tree
Showing 12 changed files with 2,282 additions and 2,184 deletions.
1,891 changes: 1,119 additions & 772 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 10 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"watch": "DOTENV_CONFIG_PATH=env/development.env node -r dotenv/config ./node_modules/.bin/nodemon --inspect ./src/index.js",
"start": "node ./src/index.js",
"test": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/mocha test/*.js",
"test:cover": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/nyc --reporter=lcov --reporter=text mocha test/*.js",
"test:watch": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/nodemon -w ./test ./node_modules/.bin/nyc --reporter=lcov mocha -R progress test/*.js",
"test:cover": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/nyc --reporter=lcov --reporter=text mocha --exit test/*.js",
"test:watch": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/mocha test/*.js --watch",
"coverage": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/nyc report",
"test:e2e": "NODE_ENV=test node ./selenium-download.js && ./node_modules/nightwatch/bin/nightwatch",
"cert": "openssl genrsa -out private-key.pem 2048 && openssl rsa -in private-key.pem -outform PEM -pubout -out public-key.pem",
Expand All @@ -31,10 +31,11 @@
"concurrently": "^5.2.0",
"cors": "^2.7.1",
"dotenv": "^6.2.0",
"express": "^4.13.3",
"jsonwebtoken": "^5.4.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"jwk-to-pem": "^1.2.6",
"morgan": "^1.10.0",
"node-jose": "^2.0.0",
"pem": "^1.14.4",
"pem-jwk": "^1.5.1",
"replaceall": "^0.1.6",
Expand All @@ -45,20 +46,21 @@
"devDependencies": {
"@types/base64-url": "^2.2.0",
"@types/chai": "^4.2.11",
"@types/express": "^4.17.6",
"@types/jsonwebtoken": "^8.5.0",
"@types/express": "^4.17.12",
"@types/jsonwebtoken": "^8.5.1",
"@types/jwk-to-pem": "^2.0.0",
"@types/mocha": "^7.0.2",
"@types/node": "^14.0.14",
"@types/node-jose": "^1.1.6",
"@types/request": "^2.48.5",
"@types/supertest": "^2.0.9",
"@types/supertest": "^2.0.11",
"chai": "^4.2.0",
"mocha": "^8.0.1",
"nightwatch": "^1.3.6",
"nodemon": "^2.0.4",
"nyc": "^15.1.0",
"selenium-download": "^2.0.15",
"supertest": "^4.0.2",
"supertest": "^6.1.3",
"typescript": "^2.6.1"
}
}
10 changes: 2 additions & 8 deletions src/AuthorizeHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,7 @@ class AuthorizeHandler extends SMARTHandler {
aud : ""
});
redirectUrl.search = null;
redirectUrl.pathname = redirectUrl.pathname.replace(
config.authBaseUrl + "/authorize",
to
);
redirectUrl.pathname = redirectUrl.pathname.replace("/auth/authorize", to);
return this.response.redirect(Url.format(redirectUrl));
}

Expand Down Expand Up @@ -283,10 +280,7 @@ class AuthorizeHandler extends SMARTHandler {

// The "aud" param must match the apiUrl (but can have different protocol)
if (!sim.aud_validated) {
const apiUrl = Lib.buildUrlPath(
config.baseUrl,
req.baseUrl.replace(config.authBaseUrl, config.fhirBaseUrl)
);
const apiUrl = Lib.buildUrlPath(config.baseUrl, req.baseUrl, "fhir");
let a = Lib.normalizeUrl(req.query.aud).replace(/^https?/, "").replace(/^:\/\/localhost/, "://127.0.0.1");
let b = Lib.normalizeUrl(apiUrl ).replace(/^https?/, "").replace(/^:\/\/localhost/, "://127.0.0.1");
if (a != b) {
Expand Down
13 changes: 3 additions & 10 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ const FS = require("fs");
const convert = require('pem-jwk');

const HOST = process.env.HOST || "localhost";
const PORT = process.env.LAUNCHER_PORT ||
process.env.PORT ||
(process.env.NODE_ENV == "test" ? 8444 : 8443);
const PORT = process.env.LAUNCHER_PORT || process.env.PORT || (process.env.NODE_ENV == "test" ? 8444 : 8443);

const PRIVATE_KEY = FS.readFileSync(__dirname + "/../private-key.pem", "utf8");
const PUBLIC_KEY = FS.readFileSync(__dirname + "/../public-key.pem", "utf8");
const JWK = convert.pem2jwk(PRIVATE_KEY);

[
Expand All @@ -24,17 +21,13 @@ const JWK = convert.pem2jwk(PRIVATE_KEY);
});

module.exports = {
port : PORT,
host : HOST,
fhirServerR2 : process.env.FHIR_SERVER_R2,
fhirServerR3 : process.env.FHIR_SERVER_R3,
fhirServerR4 : process.env.FHIR_SERVER_R4,
baseUrl : process.env.BASE_URL || `http://${HOST}:${PORT}`,
sandboxTagSystem : process.env.SANDBOX_TAG_SYSTEM || "https://smarthealthit.org/tags",
authBaseUrl : process.env.AUTH_BASE_URL || "/auth",
fhirBaseUrl : process.env.FHIR_BASE_URL || "/fhir",
protectedSandboxWords: (process.env.PROTECTED_SANDBOX_WORDS || "smart,synthea,pro").split(","),
jwtSecret : process.env.SECRET || "thisisasecret",
port : PORT,
host : HOST,
accessTokenLifetime : process.env.ACCESS_TOKEN_LIFETIME || 60, // minutes
refreshTokenLifeTime : process.env.REFRESH_TOKEN_LIFETIME || 60 * 24 * 365, // minutes
backendServiceAccessTokenLifetime: process.env.BACKEND_ACCESS_TOKEN_LIFETIME || 15, // minutes
Expand Down
86 changes: 86 additions & 0 deletions src/fhir-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* This file defines a router for a virtual server. A virtual server is mostly
* just a pair of FHIR server for specific FHIR version and an auth server.
* These virtual server routers will be mounted on two parallel locations:
* - `{base}/v/{fhirVersion}` - default server
* - `{base}/v/{fhirVersion}/sim/{sim}` - with additional metadata in the {sim}
*/

const express = require("express")
const base64url = require("base64-url")
const wellKnownOIDC = require("./wellKnownOIDCConfiguration")
const wellKnownSmart = require("./wellKnownSmartConfiguration")
const AuthorizeHandler = require("./AuthorizeHandler")
const RegistrationHandler = require("./RegistrationHandler")
const TokenHandler = require("./TokenHandler")
const { introspectionHandler } = require("./introspect")
const lib = require("./lib")
const simpleProxy = require("./simple-proxy")

const fhirServer = module.exports = express.Router({ mergeParams: true })
const urlencoded = express.urlencoded({ extended: false, limit: '64kb' })
const text = express.text({ type: "*/*", limit: 1e6 })

// Authorization endpoints
// -----------------------------------------------------------------------------

fhirServer.get("/auth/authorize", AuthorizeHandler.handleRequest)

fhirServer.post("/auth/token", urlencoded, TokenHandler.handleRequest)

fhirServer.post("/auth/register", urlencoded, RegistrationHandler.handleRequest)

fhirServer.post("/auth/introspect", urlencoded, introspectionHandler)


// Metadata endpoints
// -----------------------------------------------------------------------------

fhirServer.get("/.well-known/smart-configuration", wellKnownSmart)

fhirServer.get("/.well-known/openid-configuration", wellKnownOIDC)


// UI endpoints
// -----------------------------------------------------------------------------

// patient picker
fhirServer.get("/picker", (_, res) => res.sendFile("picker.html", { root: './static' }))

// encounter picker
fhirServer.get("/encounter", (_, res) => res.sendFile("encounter-picker.html", { root: './static' }))

// user picker
fhirServer.get("/login", (_, res) => res.sendFile("login.html", { root: './static' }))

// approve launch dialog
fhirServer.get("/authorize", (_, res) => res.sendFile("authorize.html", { root: './static' }))


// Other endpoints
// -----------------------------------------------------------------------------

// Provide launch_id if the CDS Sandbox asks for it
fhirServer.post("/fhir/_services/smart/launch", express.json(), (req, res) => {
res.json({
launch_id: base64url.encode(JSON.stringify({
context: req.body.parameters || {}
}))
});
});

// Proxy everything else under `/fhir` to the underlying FHIR server
fhirServer.use("/fhir", text, handleParseError, simpleProxy);



function handleParseError(err, req, res, next) {
if (err instanceof SyntaxError && err.status === 400) {
return lib.operationOutcome(
res,
`Failed to parse JSON content, error was: ${err.message}`,
{ httpCode: 400 }
);
}
next(err, req, res);
}
58 changes: 17 additions & 41 deletions src/generator.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,28 @@
const router = require("express").Router({ mergeParams: true });
const ursa = require('ursa-purejs');
const crypto = require("crypto");
const jose = require("node-jose")
const { whitelist } = require("./lib")

module.exports = router;

router.get("/rsa", (req, res) => {

let enc = String(req.query.enc || "");
if (["base64", "binary", "hex", "utf8"].indexOf(enc) == -1) {
enc = undefined;
}

// create a pair of keys (a private key contains both keys...)
const keys = ursa.generatePrivateKey();

// reconstitute the private and public keys from a base64 encoding
// @ts-ignore
const privatePem = keys.toPrivatePem(enc);
const publicPem = keys.toPublicPem(enc);

// make a private key, to be used for encryption
// const privateKey = ursa.createPrivateKey(privatePem, '', enc);
const router = module.exports = require("express").Router({ mergeParams: true });

// make a public key, to be used for decryption
// const publicKey = ursa.createPublicKey(publicPem, enc);

res.json({
privateKey: privatePem,
publicKey : publicPem
});
module.exports = router;

router.get("/rsa", (req, res, next) => {
jose.JWK.createKey("RSA", 2048, { alg: "RS384" }).then(key => {
res.json({
publicKey: key.toPEM(),
privateKey: key.toPEM(true)
})
}, next)
});

router.get("/random", (req, res) => {
const encodings = ["base64", "binary", "hex", "utf8", "ascii"];
let enc = whitelist(encodings, String(req.query.enc), "hex");

/**
* @type { "base64" | "binary" | "hex" | "utf8" }
*/
// @ts-ignore
let enc = String(req.query.enc || "ascii");
if (["base64", "binary", "hex", "utf8"].indexOf(enc) == -1) {
enc = undefined;
}

let len = +req.query.len;
let len = +(req.query.len || 32);
if (isNaN(len) || !isFinite(len) || len < 1 || len > 1024) {
len = 32;
}

res.send(crypto.randomBytes(len).toString(enc));

res.set("content-type", "text/plain")
res.end(jose.util.randomBytes(len).toString(enc))
});
Loading

0 comments on commit a0e4350

Please sign in to comment.