Initial Commit
This commit is contained in:
parent
a1e6d06669
commit
2743a1c042
88
Global Variables/fhirInterface.js
Normal file
88
Global Variables/fhirInterface.js
Normal file
@ -0,0 +1,88 @@
|
||||
return {
|
||||
getToken() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
getRedisKey = function () {
|
||||
return `fhir-${fhirSettings.jwt.clientId}`;
|
||||
};
|
||||
|
||||
const tokenEntry = await redis.hGetAll(getRedisKey());
|
||||
const currentTimeInSeconds = Math.floor(new Date().getTime() / 1000);
|
||||
|
||||
if (tokenEntry.exp >= currentTimeInSeconds) {
|
||||
resolve(JSON.parse(tokenEntry.token));
|
||||
return;
|
||||
}
|
||||
|
||||
// start the expiry base before we create a new token
|
||||
let expiryInSeconds = Math.floor(new Date().getTime() / 1000);
|
||||
|
||||
// Define headers
|
||||
const headers = {
|
||||
alg: "RS256",
|
||||
typ: "JWT",
|
||||
};
|
||||
|
||||
// Define your payload
|
||||
const payload = {
|
||||
iss: fhirSettings.jwt.client_id,
|
||||
sub: fhirSettings.jwt.client_id,
|
||||
aud: fhirSettings.token_url,
|
||||
};
|
||||
|
||||
// Define token options (optional)
|
||||
const options = {
|
||||
expiresIn: "4m", // Token expires in 1 hour
|
||||
notBefore: "0",
|
||||
algorithm: "RS384",
|
||||
keyid: "iva_id_1234",
|
||||
jwtid: uuidv5("jwtid", fhirSettings.jwt.uuid_namespace),
|
||||
};
|
||||
|
||||
let data = qs.stringify({
|
||||
grant_type: "client_credentials",
|
||||
client_assertion_type:
|
||||
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
client_assertion: jwt.sign(
|
||||
payload,
|
||||
fhirSettings.jwt.privateKey,
|
||||
options
|
||||
),
|
||||
});
|
||||
|
||||
let config = {
|
||||
method: "post",
|
||||
maxBodyLength: Infinity,
|
||||
url: fhirSettings.token_url,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
data: data,
|
||||
};
|
||||
|
||||
const result = await axios(config);
|
||||
|
||||
if (result.status == 200) {
|
||||
expiryInSeconds += result.data.expires_in;
|
||||
console.log(
|
||||
`fhirInterface.getToken: Adding ${getRedisKey()} to redis with expiry=${new Date(
|
||||
expiryInSeconds * 1000
|
||||
)}`
|
||||
);
|
||||
await redis.hSet(getRedisKey(), "exp", expiryInSeconds);
|
||||
await redis.hSet(getRedisKey(), "token", JSON.stringify(result.data));
|
||||
resolve(result.data);
|
||||
} else {
|
||||
reject(result.status);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("fhirInterface.getToken: Error getting Token");
|
||||
console.log(err);
|
||||
if (err.response) {
|
||||
console.log(err.response.data); // => the response payload
|
||||
}
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
10
Global Variables/fhirSettings.json
Normal file
10
Global Variables/fhirSettings.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"base_url": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR",
|
||||
"token_url": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
|
||||
"jwt": {
|
||||
"uuid_namespace": "30e3078d-59fe-4151-8571-304c87580209",
|
||||
"client_id": "66eaeb38-c5f7-41cb-8cd3-791efacf25e5",
|
||||
"privateKey": "*****"
|
||||
},
|
||||
"patient_id": "T81lum-5p6QvDR7l6hv7lfE52bAbA2ylWBnv9CZEzNb0B"
|
||||
}
|
||||
23
Proxy Scripts/fhirPatientRead.js
Normal file
23
Proxy Scripts/fhirPatientRead.js
Normal file
@ -0,0 +1,23 @@
|
||||
(async () => {
|
||||
try {
|
||||
const token = await fhirInterface().getToken();
|
||||
|
||||
let config = {
|
||||
method: "get",
|
||||
url: `${fhirSettings.base_url}/DSTU2/Patient/${fhirSettings.patient_id}`,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `${token.token_type} ${token.access_token}`,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await axios(config);
|
||||
res.json(result.data);
|
||||
} catch (err) {
|
||||
if (err.response) {
|
||||
res.status(err.response.status).json(err.response.data);
|
||||
} else {
|
||||
res.status(400).json(JSON.stringify(err));
|
||||
}
|
||||
}
|
||||
})();
|
||||
86
README.md
86
README.md
@ -1,3 +1,87 @@
|
||||
# fhir-iva-integration
|
||||
|
||||
Example integration to https://fhir.epic.com/
|
||||
Example integration to https://fhir.epic.com/
|
||||
|
||||
## Introduction
|
||||
|
||||
This repository provides and example integration to EPIC using the [Epic on FHIR](https://fhir.epiv.com) Sandbox. To use this example, you will need to create an account and Build an app run the APIs.
|
||||
|
||||
## FHIR App
|
||||
|
||||
This integration example requires you to build an App that allow the [SMART Backend Services (Backend OAUTH 2.0)](https://fhir.epic.com/Documentation?docId=oauth2§ion=BackendOAuth2Guide) workflow to obtain an access token.
|
||||
|
||||
To use this integration, you will need to create a public and private key as described in the section [Creating a Public Private Key Pair for JWT Signature](https://fhir.epic.com/Documentation?docId=oauth2§ion=Creating-Key-Pair) and upload public key file to the FHIR Application and the Private Key to the *fhirSettings* global variable.
|
||||
|
||||
When configuring the FHIR App, please make sure to:
|
||||
|
||||
1. Select *Backend System* as the *Application Audience*
|
||||
2. Select the APIs you wish to use. NOTE: Only DSTU2 APIs have been tested
|
||||
3. Use the *Non-Production Client ID* in the *fhirSettings* Global Variable
|
||||
4. Upload your *publickey509.pem* file to *Sandbox JWT Signing Public Key
|
||||
5. Select *DSTU2* for *SMART on FHIR Version*. Other versions have not been tested
|
||||
6. The rest should be optional for the Sandbox and not required for this example
|
||||
|
||||

|
||||
|
||||
## IVA Studio
|
||||
|
||||
### Global Variables
|
||||
|
||||
Add both *fhirInterface* as a Function and *fhirSettings* as an Object to Global Variables.
|
||||
|
||||
*fhirInterface* currently as one function *getToken* that first checks REDIS for an unexpired token, then create one using the JWT method described by FHIR's documentation.
|
||||
|
||||
> Because this is just an example, limited error handling and no Token refresh protocols have been implemented.
|
||||
|
||||
#### fhirSettings
|
||||
|
||||
Open the file fhirSettings and add your private key.
|
||||
|
||||
> NOTE: Remember, because this is a JSON Object, new lines from the private key will need to be replaced with '\n'.
|
||||
>
|
||||
> This is not recommended for a production environment.
|
||||
|
||||
The file should look something like this when complete.
|
||||
|
||||
```json
|
||||
{
|
||||
"base_url" : "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR",
|
||||
"token_url" : "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
|
||||
"jwt" : {
|
||||
"uuid_namespace" : "30e3078d-59fe-4151-8571-304c87580209",
|
||||
"client_id" : "66eaeb38-c5f7-41cb-8cd3-791efacf25e5",
|
||||
"privateKey" : "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA45nS5ecQak+ewzLOCkWnvwp+cjw1NiZOLV1aX+DGxDkuEX3h\ndQLAYT+bQ+MFojqPQErxnp3kFR+uLQSfQ3tkBL3v4H07hLnV/oZQ5d1CCoCfNVQS\nI3FyN0jr/qPMrqrih+QVouA01GvupShSGrrBYks204lCmHE0VWnvP/+BKDdlEeD5\n1uuDXKpDdE4tQh52y92YhDEoSmw9q6RW2IFKMeaQt3h4nOLVkMNcMyLWaspqmbCE\nTpduwVseY19QV8bNQiy6JaWp3AiVL2+M9VjEr5i2DFMRfQVSPOElCzDlwXZ4LFyk\nOMvPYyECnly7TCiGWcenR/iCjdmgFsrUZKbbqQIDAQABAoIBAHbktQnJ8YZHf1zi\nnkU5a85dMf6EuxtFWVNTT9GD/vEkGY+jnXHddReX/YiyABBl3M0uGRfNzQbH3NnB\nb1z2CSJ9AeDYKo5D8aibC4l4UnZgCEr4Vt1S9uIwYq9La7HWrK1mFXNXAeHxW+HE\ntVcnNbweJE7Ohg5SHI993jAlTZfuk6jqbRFQiOsdSAWcXAYDOeGNHJW/OEmcHa1v\n+vFA8eOjRo8fgxDWgOASdHlWfe4oY3BgKvIlFxtnO7bbqfE9+66lZ+VZMSBivWKZ\nspW1jgJCMZY+s5HNAj74aTV273N2Dd5UA/B3au/8yihHWG1DCuvIJgBhlyuebymA\n0mbnBu0CgYEA+vqR1/6sUbjvIe0Q5hKIzgmQTB5JBhMdVhN9jjkWbzmmHg14X8bx\nMnyomOOOYMs2BL/XnnYELdvLXbNsdfn3z7YLnRRWJYz5BeZKo0cziJ1qAU/GQivl\n+yUQDMWAI+tOCRO2eWU4H5pfWinYgb/Fjnk1e6WU1EEUJI4oE6nC5MsCgYEA6CeF\nKph4579OrjM5aEeOa+v/cHbWen3sw8tYuX5YIUsceJkwnlr7vdgwxfWjWHpQ1vA7\nJTHpTiiU9NBYyIw3RJTCC45+nZB0Wozco6Iimscggceupd8ewbAtk/hAAjWXp7y5\n6+PrqLrML4KGqEJS3CDyOx/NKFW3vXkJjq8FJtsCgYEA5rCQg7fsDkXtUALGqKNa\nqf+yabTgrDu/mFHb83FXxK55mWAKSAblxuE8WyO2yBOhOGZZu6aAmuJPoHX+eMZl\n2L9dF2oM8QEOGDUgX8pffPAr8r6v3jzZbKoZgZO7/8gWd1NuQ1EdcDcF9CtIfaKW\n5SlWVqvRC/QxnpQoFELTCFcCgYEA5HPfi6c3c3bDCpHF8GRaNsGqQRXwweGhWJuG\n2CMIvtqXTeYR/gMysANLG8M51xum6ZzF1zhiilNNIgzVEaVJzedFfPHgj1VT6rer\neCtZOk6yIoRJzVjff2LLt00YUBRFBP+nRgaoJQaNYENmF7YMrCqPtLb6wLJ5ea7e\nRNbejvkCgYEA1nMBDVtFbGyfWtmBB12ip5Xj76DhMvjsTc72cE8aKlDPHMYg7t2B\nSaeks8I20f+yRBWBQW9VdyAflTfZBCSM1fdFGj1M14bTr+ikNT4TNdBmXDbS//3t\nlOoomQDl08DhtkH/wSZrwJKgnNVCSPT+fLZzbTcmrsGkv6FdRthPjFY=\n-----END RSA PRIVATE KEY-----"
|
||||
},
|
||||
"patient_id" : "T81lum-5p6QvDR7l6hv7lfE52bAbA2ylWBnv9CZEzNb0B"
|
||||
}
|
||||
```
|
||||
|
||||
### Proxy Scripts
|
||||
|
||||
An example proxy script has been provided to test our a Patient.READ(DSTU2) API Call. Ideally, you should work this into your Conversation Flows using the *Call API* Widget
|
||||
|
||||
```javascript
|
||||
(async () => {
|
||||
try {
|
||||
const token = await fhirInterface().getToken();
|
||||
|
||||
let config = {
|
||||
method: "get",
|
||||
url: `${fhirSettings.base_url}/DSTU2/Patient/${fhirSettings.patient_id}`,
|
||||
headers: {
|
||||
"Accept" : "application/json",
|
||||
"Authorization" : `${token.token_type} ${token.access_token}`
|
||||
}
|
||||
};
|
||||
|
||||
const result = await axios(config);
|
||||
res.json(result.data);
|
||||
} catch (err) {
|
||||
if( err.response ){
|
||||
res.status(err.response.status).json(err.response.data);
|
||||
} else {
|
||||
res.status(400).json(JSON.stringify(err));
|
||||
}
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
BIN
screenshots/Example FHIR App.png
Normal file
BIN
screenshots/Example FHIR App.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
Loading…
x
Reference in New Issue
Block a user