Last active
June 6, 2021 16:45
-
-
Save nvg/0b023912d33f8ca5caf7af7fe72e2963 to your computer and use it in GitHub Desktop.
Credentials Authority
This file contains hidden or 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
| 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