Skip to content

Instantly share code, notes, and snippets.

@nvg
Last active June 6, 2021 16:45
Show Gist options
  • Select an option

  • Save nvg/0b023912d33f8ca5caf7af7fe72e2963 to your computer and use it in GitHub Desktop.

Select an option

Save nvg/0b023912d33f8ca5caf7af7fe72e2963 to your computer and use it in GitHub Desktop.
Credentials Authority
const express = require('express')
const bodyParser = require('body-parser')
const zlib = require('zlib')
const jose = require('node-jose');
const fs = require('fs');
const qrcode = require('qrcode')
const nodemailer = require('nodemailer');
let signingKey;
let keystore = jose.JWK.asKeyStore(JSON.parse(fs.readFileSync('keys.json'))).then(function (result) {
keystore = result;
signingKey = keystore.all()[0];
});
const app = express()
function overrideContentType(req, res, next) {
req.headers['content-type'] = 'application/json';
next();
}
app.use(overrideContentType);
app.use(express.urlencoded());
app.use(express.json());
app.post('/', async (req, res) => {
res.setHeader('Content-Type', 'application/fhir+json')
try {
await issue(req);
res.write('{"resourceType": "Bundle",\n' +
' "type": "message",\n' +
' "entry": []}');
} catch (e) {
console.log(e);
res.status(500).send('Unable to issue SMART card')
}
res.end()
})
const server = app.listen(5000, function () {
const host = server.address()['address'];
const port = server.address()['port'];
console.log("App listening at http://%s:%s", server.address(), host, port)
})
const issue = async function (req) {
const bundle = JSON.stringify(req.body, null, 2);
const credential = toExpandedHealthCard(bundle);
const compressedPayload = zlib.deflateRawSync(credential);
let jws = await createJws(compressedPayload);
let qrCode = await createQr(jws);
await email(req, qrCode);
};
const createJws = async function (payload) {
const fields = {zip: 'DEF'}
let jws = await jose.JWS.createSign({format: 'compact', fields}, signingKey)
.update(Buffer.from(payload))
.final();
return jws;
}
const createQr = async function (jws) {
const numericJWS = jws.split('')
.map((c) => c.charCodeAt(0) - 45)
.flatMap((c) => [Math.floor(c / 10), c % 10]) // Need to maintain leading zeros
.join('')
const qrCodeData = 'shc:/' + numericJWS
const segments = [
{data: 'shc:/', mode: 'byte'},
{data: numericJWS, mode: 'numeric'}
]
let result = await qrcode.toString(segments, {type: 'svg', errorCorrectionLevel: 'low', version: 22})
// qrcode.toFile('./qrcode.png', segments, {width: 800, version: 22})
return result
}
const toExpandedHealthCard = function (bundle) {
const result = {
"iss": "https://smarthealth.cards/examples/issuer",
"nbf": Date.now() / 1000,
"vc": {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"type": [
"VerifiableCredential",
"https://smarthealth.cards#health-card",
"https://smarthealth.cards#immunization",
"https://smarthealth.cards#covid19"
],
"credentialSubject": {
"fhirVersion": "4.0.1",
"fhirBundle": bundle
}
}
}
return JSON.stringify(result);
};
const email = async function (req, qr) {
let message = {
to: req.header('Patient-Email'),
subject: 'Your COVID Immunization SMART Card',
text: '',
html: `<html><style>svg{width: 400px; height: auto;}</style><h1>Hello ${req.header('Patient-Name')}</h1>
<p>Your COVID Immunization SMART Card is ready<br/>
${qr}
</p></html>`,
attachments: [{
filename: 'qrcode.svg',
content: qr,
cid: 'qrcode'
}]
}
let mailConfig = {
host: 'smtp.ethereal.email',
port: 587,
auth: {
user: '[email protected]',
pass: 'hpT6wsba6XUQm1ZQaN'
}
};
let transporter = nodemailer.createTransport(mailConfig);
await transporter.sendMail(message);
transporter.close()
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment