Skip to content

Commit 10f4b82

Browse files
author
James Fuqian
committed
Merge remote-tracking branch 'origin/master' into jfuqian/BB2-896-Responsive-Design-Node-Js-Sample
2 parents f268111 + 7039a75 commit 10f4b82

11 files changed

Lines changed: 123 additions & 42 deletions

File tree

client/src/components/records.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ export default function Records({ }) {
5050
setRecords(records);
5151
}
5252
else {
53-
setMessage({"type": "error", "content": eobData?.message || "Unknown"})
53+
if (eobData.message) {
54+
setMessage({"type": "error", "content": eobData.message || "Unknown"})
55+
}
5456
}
5557
});
5658
}, [])

server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"jet-logger": "^1.0.4",
7171
"jsonfile": "^6.1.0",
7272
"module-alias": "^2.2.2",
73+
"moment": "^2.29.1",
7374
"morgan": "^1.10.0"
7475
},
7576
"devDependencies": {

server/spec/nodemon.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"watch": ["spec"],
33
"ext": "spec.ts",
44
"ignore": ["spec/support"],
5-
"exec": "./node_modules/.bin/ts-node --inspect -r tsconfig-paths/register ./spec"
5+
"exec": "./node_modules/.bin/ts-node -r tsconfig-paths/register ./spec"
66
}

server/src/entities/AuthorizationToken.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import moment from "moment";
2+
13
export interface IAuthorizationToken {
24
access_token: string,
35
expires_in: number,
46
token_type: string,
57
scope: [string],
68
refresh_token: string,
79
patient: string,
8-
expires_at: number
10+
expires_at?: number
911
}
1012

1113
export default class AuthorizationToken implements IAuthorizationToken {
@@ -22,7 +24,7 @@ export default class AuthorizationToken implements IAuthorizationToken {
2224
constructor(authToken: IAuthorizationToken) {
2325
this.access_token = authToken.access_token;
2426
this.expires_in = authToken.expires_in;
25-
this.expires_at = authToken.expires_at;
27+
this.expires_at = authToken.expires_at ? authToken.expires_at : moment().add(this.expires_in).valueOf();
2628
this.patient = authToken.patient;
2729
this.refresh_token = authToken.refresh_token;
2830
this.scope = authToken.scope;

server/src/routes/Authorize.ts

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,22 @@ import Settings from '../entities/Settings';
44
import db from '../utils/db';
55
import { getAccessToken, generateAuthorizeUrl } from '../utils/bb2';
66
import { getBenefitData } from './Data';
7-
import { getLoggedInUser } from 'src/utils/user';
87
import logger from '@shared/Logger';
8+
import { clearBB2Data, getLoggedInUser } from 'src/utils/user';
9+
10+
const BENE_DENIED_ACCESS = 'access_denied';
11+
912

1013
export async function authorizationCallback(req: Request, res: Response) {
1114
try {
15+
16+
if (req.query.error === BENE_DENIED_ACCESS) {
17+
const loggedInUser = getLoggedInUser(db);
18+
// clear all saved claims data since the bene has denied access for the application
19+
clearBB2Data(loggedInUser);
20+
loggedInUser.errors.push(BENE_DENIED_ACCESS);
21+
throw new Error('Beneficiary denied application access to their data');
22+
}
1223

1324
if (!req.query.code) {
1425
throw new Error('Response was missing access code');
@@ -19,31 +30,39 @@ export async function authorizationCallback(req: Request, res: Response) {
1930

2031
// this gets the token from Medicare.gov once the 'user' authenticates their Medicare.gov account
2132
const response = await getAccessToken(req.query.code?.toString(), req.query.state?.toString());
22-
const authToken = new AuthorizationToken(response.data);
23-
24-
/* DEVELOPER NOTES:
25-
* This is where you would most likely place some type of
26-
* persistence service/functionality to store the token along with
27-
* the application user identifiers
28-
*/
29-
30-
// Here we are grabbing the mocked 'user' for our application
31-
// to be able to store the access token for that user
32-
// thereby linking the 'user' of our sample applicaiton with their Medicare.gov account
33-
// providing access to their Medicare data to our sample application
34-
const loggedInUser = getLoggedInUser(db);
35-
loggedInUser.authToken = authToken;
3633

34+
const loggedInUser = getLoggedInUser(db);
3735

38-
/* DEVELOPER NOTES:
39-
* Here we will use the token to get the EoB data for the mocked 'user' of the sample application
40-
* then to save trips to the BB2 API we will store it in the mocked db with the mocked 'user'
41-
*
42-
* You could also request data for the Patient endpoint and/or the Coverage endpoint here
43-
* using similar functionality
44-
*/
45-
const eobData = await getBenefitData( req, res);
46-
loggedInUser.eobData = eobData;
36+
if (response.status === 200) {
37+
const authToken = new AuthorizationToken(response.data);
38+
39+
/* DEVELOPER NOTES:
40+
* This is where you would most likely place some type of
41+
* persistence service/functionality to store the token along with
42+
* the application user identifiers
43+
*/
44+
45+
// Here we are grabbing the mocked 'user' for our application
46+
// to be able to store the access token for that user
47+
// thereby linking the 'user' of our sample applicaiton with their Medicare.gov account
48+
// providing access to their Medicare data to our sample application
49+
loggedInUser.authToken = authToken;
50+
51+
52+
/* DEVELOPER NOTES:
53+
* Here we will use the token to get the EoB data for the mocked 'user' of the sample application
54+
* then to save trips to the BB2 API we will store it in the mocked db with the mocked 'user'
55+
*
56+
* You could also request data for the Patient endpoint and/or the Coverage endpoint here
57+
* using similar functionality
58+
*/
59+
const eobData = await getBenefitData( req, res);
60+
loggedInUser.eobData = eobData;
61+
}
62+
else {
63+
// send generic error message to FE
64+
loggedInUser.eobData = JSON.parse('{"message": "Unable to load EOB Data - authorization failed."}');
65+
}
4766

4867
} catch (e) {
4968
/* DEVELOPER NOTES:

server/src/routes/Data.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import config from '../configs/config';
33
import db from '../utils/db';
44
import { getLoggedInUser } from 'src/utils/user';
55
import { get } from '../utils/request'
6+
import moment from 'moment';
7+
import { refreshAccessToken } from 'src/utils/bb2';
68

79
/* DEVELOPER NOTES:
810
* This is our mocked Data Service layer for both the BB2 API
@@ -15,9 +17,29 @@ import { get } from '../utils/request'
1517
export async function getBenefitData(req: Request, res: Response) {
1618
const loggedInUser = getLoggedInUser(db);
1719
const envConfig = config[db.settings.env];
18-
// get EOB end point
19-
const response = await get(`${envConfig.bb2BaseUrl}/${db.settings.version}/fhir/ExplanationOfBenefit/`, req.query, `${loggedInUser.authToken?.access_token}`);
20-
return (response) ? response.data : null;
20+
const BB2_BENEFIT_URL = envConfig.bb2BaseUrl + '/' + db.settings.version + '/fhir/ExplanationOfBenefit/';
21+
22+
if (!loggedInUser.authToken || !loggedInUser.authToken.access_token) {
23+
return {};
24+
}
25+
26+
/*
27+
* If the access token is expired, use the refresh token to generate a new one
28+
*/
29+
if (moment(loggedInUser.authToken.expires_at).isBefore(moment())) {
30+
const newAuthToken = await refreshAccessToken(loggedInUser.authToken.refresh_token)
31+
loggedInUser.authToken = newAuthToken;
32+
}
33+
34+
const response = await get(BB2_BENEFIT_URL, req.query, `${loggedInUser.authToken?.access_token}`);
35+
36+
if (response.status === 200) {
37+
return response.data;
38+
}
39+
else {
40+
// send generic error to client
41+
return JSON.parse('{"message": "Unable to load EOB Data - fetch FHIR resource error."}');
42+
}
2143
}
2244

2345
/*
@@ -30,12 +52,9 @@ export async function getBenefitData(req: Request, res: Response) {
3052
export async function getBenefitDataEndPoint(req: Request, res: Response) {
3153
const loggedInUser = getLoggedInUser(db);
3254
const data = loggedInUser.eobData;
33-
if ( data && data.entry ) {
55+
if ( data ) {
3456
res.json(data)
3557
}
36-
else {
37-
res.json({message: "Unable to load EOB Data."});
38-
}
3958
}
4059

4160
export async function getPatientData(req: Request, res: Response) {

server/src/utils/bb2.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import FormData from 'form-data';
33
import db from './db';
44
import config from '../configs/config';
55
import { generateCodeChallenge, generateRandomState } from './generatePKCE';
6-
import { post } from './request'
6+
import { post, post_w_config } from './request'
7+
import AuthorizationToken from '@entities/AuthorizationToken';
78

89
export function generateAuthorizeUrl(): string {
910
const envConfig = config[db.settings.env];
@@ -46,6 +47,27 @@ export async function getAccessToken(code: string, state: string | undefined) {
4647
form.append('code_verifier', codeChallenge.verifier);
4748
form.append('code_challenge', codeChallenge.codeChallenge);
4849
}
50+
return await post(BB2_ACCESS_TOKEN_URL, form, form.getHeaders());
51+
}
52+
53+
export async function refreshAccessToken(refreshToken: string) {
54+
const envConfig = config[db.settings.env];
55+
56+
const BB2_ACCESS_TOKEN_URL = envConfig.bb2BaseUrl + '/' + db.settings.version + '/o/token/';
57+
58+
const tokenResponse = await post_w_config({
59+
method: 'post',
60+
url: BB2_ACCESS_TOKEN_URL,
61+
auth: {
62+
username: envConfig.bb2ClientId,
63+
password: envConfig.bb2ClientSecret
64+
},
65+
params: {
66+
'grant_type': 'refresh_token',
67+
'client_id': envConfig.bb2ClientId,
68+
'refresh_token': refreshToken
69+
}
70+
});
4971

50-
return await post(BB2_ACCESS_TOKEN_URL, form, form.getHeaders());
72+
return new AuthorizationToken(tokenResponse.data);
5173
}

server/src/utils/db.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export interface UserInfo {
1616
export interface User {
1717
authToken?: AuthorizationToken,
1818
userInfo: UserInfo,
19-
eobData?: any
19+
eobData?: any,
20+
errors: string[]
2021
}
2122
export interface DB {
2223
patients: object,
@@ -51,7 +52,8 @@ const db: DB = {
5152
userName: 'jdoe29999',
5253
pcp: 'Dr. Hibbert',
5354
primaryFacility: 'Springfield General Hospital'
54-
}
55+
},
56+
errors: []
5557
}],
5658
codeChallenges: {},
5759
codeChallenge: {

server/src/utils/request.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import axios from 'axios';
22
import FormData from 'form-data';
33

4-
export async function post(endpoint_url: string, data: FormData, extra: any) {
4+
export async function post(endpoint_url: string, data: FormData, headers: any) {
55
return await request({
66
method: 'post',
77
url: endpoint_url,
88
data: data,
9-
headers: extra}, true);
9+
headers: headers}, true);
10+
}
11+
12+
export async function post_w_config(config: any) {
13+
return await request(config, false);
1014
}
1115

1216
export async function get(endpointUrl: string, params: any, authToken: string) {

server/src/utils/user.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DB } from "./db";
1+
import { DB, User } from "./db";
22

33
/* DEVELOPER NOTES:
44
* Here we are literally just grabbing the first user
@@ -9,4 +9,9 @@ import { DB } from "./db";
99
*/
1010
export function getLoggedInUser(db : DB) {
1111
return db.users[0];
12+
}
13+
14+
export function clearBB2Data(user: User) {
15+
user.authToken = undefined;
16+
user.eobData = undefined;
1217
}

0 commit comments

Comments
 (0)