-
Notifications
You must be signed in to change notification settings - Fork 1
Ncpdp integration #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Ncpdp integration #146
Conversation
| const order = await doctorOrder.findById(req.params.id).exec(); | ||
| console.log('Found doctor order by id! --- ', order); | ||
|
|
||
| // Non-REMS drugs auto-approve | ||
| if (!isRemsDrug(order)) { | ||
| const newOrder = await doctorOrder.findOneAndUpdate( | ||
| { _id: req.params.id }, | ||
| { dispenseStatus: 'Approved' }, | ||
| { new: true } | ||
| ); | ||
| res.send(newOrder); | ||
| console.log('Non-REMS drug - auto-approved'); | ||
| return; | ||
| } | ||
|
|
||
| // REMS drugs - send NCPDP REMSRequest per spec | ||
| console.log('REMS drug - sending REMSRequest for authorization per NCPDP workflow'); | ||
| const ncpdpResponse = await sendREMSRequest(order); | ||
|
|
||
| if (!ncpdpResponse) { | ||
| res.send(order); | ||
| console.log('NCPDP REMSRequest failed'); | ||
| return; | ||
| } | ||
|
|
||
| // Update based on NCPDP response | ||
| const updateData = { | ||
| dispenseStatus: getDispenseStatus(order, ncpdpResponse), | ||
| metRequirements: ncpdpResponse.metRequirements || order.metRequirements | ||
| }; | ||
|
|
||
| if (ncpdpResponse.status === 'APPROVED') { | ||
| updateData.authorizationNumber = ncpdpResponse.authorizationNumber; | ||
| updateData.authorizationExpiration = ncpdpResponse.authorizationExpiration; | ||
| updateData.caseNumber = ncpdpResponse.caseId; | ||
|
|
||
| // Format approval note with ETASU summary | ||
| let approvalNote = `APPROVED - Authorization: ${ncpdpResponse.authorizationNumber}, Expires: ${ncpdpResponse.authorizationExpiration}`; | ||
| if (ncpdpResponse.etasuSummary) { | ||
| approvalNote += `\n\nETASU Requirements Met:\n${ncpdpResponse.etasuSummary}`; | ||
| } | ||
| updateData.remsNote = approvalNote; | ||
| updateData.denialReasonCode = null; | ||
| console.log('APPROVED:', ncpdpResponse.authorizationNumber); | ||
| } else if (ncpdpResponse.status === 'DENIED') { | ||
| updateData.denialReasonCode = ncpdpResponse.reasonCode; | ||
| updateData.remsNote = ncpdpResponse.remsNote; | ||
| updateData.caseNumber = ncpdpResponse.caseId; | ||
| console.log('DENIED:', ncpdpResponse.reasonCode); | ||
| } | ||
|
|
||
| const newOrder = await doctorOrder.findOneAndUpdate( | ||
| { _id: req.params.id }, | ||
| updateData, | ||
| { new: true } | ||
| ); | ||
|
|
||
| res.send(newOrder); | ||
| console.log('Updated order with NCPDP response'); | ||
| } catch (error) { | ||
| console.log('Error', error); | ||
| return error; | ||
| } | ||
| }); | ||
|
|
||
| /** |
Check failure
Code scanning / CodeQL
Missing rate limiting High
a database access
This route handler performs
a database access
This route handler performs
a database access
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
In general, to fix missing rate limiting for expensive handlers in an Express app, you add a rate-limiting middleware (for example from express-rate-limit) and apply it either globally or to specific sensitive routes. This middleware counts requests per client (typically by IP) within a time window and rejects or delays requests that exceed a configured threshold, mitigating DoS via request floods.
For this file, the least intrusive, functionality-preserving fix is to introduce a small, dedicated rate limiter and apply it to the /api/updateRx/:id route (and optionally to the other DB-heavy routes if desired). We'll import express-rate-limit, define a limiter instance near the top of doctorOrders.js, and then pass it as middleware to router.patch('/api/updateRx/:id', ...). This only affects how many times the endpoint can be hit per client in a given window; it does not change the DB logic, request/response shapes, or route signatures.
Concretely:
- At the top of
backend/src/routes/doctorOrders.js, addimport rateLimit from 'express-rate-limit';. - Define a limiter, e.g.
const updateRxLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 });(values can be tuned; we just need reasonable protection). - Update the route definition on line 154 from
router.patch('/api/updateRx/:id', async (req, res) => { ...torouter.patch('/api/updateRx/:id', updateRxLimiter, async (req, res) => { ...so all accesses to this DB-intensive handler are throttled.
This single limiter instance addresses all three alert variants, since they all refer to DB calls within the same handler.
-
Copy modified line R19 -
Copy modified lines R21-R26 -
Copy modified line R160
| @@ -16,7 +16,14 @@ | ||
| } from '../ncpdpScriptBuilder/buildScript.v2017071.js'; | ||
| import { NewRx } from '../database/schemas/newRx.js'; | ||
| import { medicationRequestToRemsAdmins } from '../database/data.js'; | ||
| import rateLimit from 'express-rate-limit'; | ||
|
|
||
| // Rate limiter for updateRx route to protect DB and external integrations | ||
| const updateRxLimiter = rateLimit({ | ||
| windowMs: 15 * 60 * 1000, // 15 minutes | ||
| max: 100, // limit each IP to 100 update requests per window | ||
| }); | ||
|
|
||
| bpx(bodyParser); | ||
| router.use( | ||
| bodyParser.xml({ | ||
| @@ -151,7 +157,7 @@ | ||
| * Route: 'doctorOrders/api/updateRx/:id' | ||
| * Description : 'Updates prescription based on mongo id, sends NCPDP REMSRequest for authorization' | ||
| */ | ||
| router.patch('/api/updateRx/:id', async (req, res) => { | ||
| router.patch('/api/updateRx/:id', updateRxLimiter, async (req, res) => { | ||
| try { | ||
| const order = await doctorOrder.findById(req.params.id).exec(); | ||
| console.log('Found doctor order by id! --- ', order); |
-
Copy modified lines R16-R17
| @@ -13,7 +13,8 @@ | ||
| "mongoose": "^8.9.5", | ||
| "var": "^0.4.0", | ||
| "web-vitals": "^2.1.4", | ||
| "xml2js": "^0.6.0" | ||
| "xml2js": "^0.6.0", | ||
| "express-rate-limit": "^8.2.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/chai": "^4.3.4", |
| Package | Version | Security advisories |
| express-rate-limit (npm) | 8.2.1 | None |
| res.send(newOrder); | ||
| console.log('Non-REMS drug - auto-approved'); | ||
| return; | ||
| } | ||
|
|
||
| // REMS drugs - send NCPDP REMSRequest per spec | ||
| console.log('REMS drug - sending REMSRequest for authorization per NCPDP workflow'); | ||
| const ncpdpResponse = await sendREMSRequest(order); | ||
|
|
||
| if (!ncpdpResponse) { | ||
| res.send(order); | ||
| console.log('NCPDP REMSRequest failed'); | ||
| return; | ||
| } | ||
|
|
||
| // Update based on NCPDP response | ||
| const updateData = { | ||
| dispenseStatus: getDispenseStatus(order, ncpdpResponse), | ||
| metRequirements: ncpdpResponse.metRequirements || order.metRequirements | ||
| }; | ||
|
|
||
| if (ncpdpResponse.status === 'APPROVED') { | ||
| updateData.authorizationNumber = ncpdpResponse.authorizationNumber; | ||
| updateData.authorizationExpiration = ncpdpResponse.authorizationExpiration; | ||
| updateData.caseNumber = ncpdpResponse.caseId; | ||
|
|
||
| // Format approval note with ETASU summary | ||
| let approvalNote = `APPROVED - Authorization: ${ncpdpResponse.authorizationNumber}, Expires: ${ncpdpResponse.authorizationExpiration}`; | ||
| if (ncpdpResponse.etasuSummary) { | ||
| approvalNote += `\n\nETASU Requirements Met:\n${ncpdpResponse.etasuSummary}`; | ||
| } | ||
| updateData.remsNote = approvalNote; | ||
| updateData.denialReasonCode = null; | ||
| console.log('APPROVED:', ncpdpResponse.authorizationNumber); | ||
| } else if (ncpdpResponse.status === 'DENIED') { | ||
| updateData.denialReasonCode = ncpdpResponse.reasonCode; | ||
| updateData.remsNote = ncpdpResponse.remsNote; | ||
| updateData.caseNumber = ncpdpResponse.caseId; | ||
| console.log('DENIED:', ncpdpResponse.reasonCode); | ||
| } | ||
|
|
||
| const newOrder = await doctorOrder.findOneAndUpdate( | ||
| { _id: req.params.id }, |
Check failure
Code scanning / CodeQL
Missing rate limiting High
a database access
This route handler performs
a database access
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
In general, the problem is fixed by adding a rate-limiting middleware to the route (or router) so that repeated requests from a client within a short time window are rejected or delayed. For Express, a common approach is to use the express-rate-limit package and apply a limiter specifically to sensitive routes that perform expensive DB operations or external HTTP calls.
The best fix here, without changing existing behavior beyond limiting abuse, is to import express-rate-limit, define a limiter tailored for doctor order–related operations, and apply it to the PATCH /api/updateRx/:id/pickedUp route. This keeps the current route logic intact while ensuring that a single client cannot flood this endpoint. Concretely, in backend/src/routes/doctorOrders.js we should: (1) add an import of express-rate-limit near the top, (2) define a limiter (e.g., window of 15 minutes and a reasonable max number of requests, such as 100) after the other middleware setup, and (3) modify the route definition on line 250 so that the limiter is passed as a middleware before the async handler: router.patch('/api/updateRx/:id/pickedUp', pickedUpLimiter, async (req, res) => { ... });. No other functionality inside the handler needs to change.
-
Copy modified line R19 -
Copy modified lines R21-R25 -
Copy modified line R255
| @@ -16,7 +16,13 @@ | ||
| } from '../ncpdpScriptBuilder/buildScript.v2017071.js'; | ||
| import { NewRx } from '../database/schemas/newRx.js'; | ||
| import { medicationRequestToRemsAdmins } from '../database/data.js'; | ||
| import rateLimit from 'express-rate-limit'; | ||
|
|
||
| const pickedUpLimiter = rateLimit({ | ||
| windowMs: 15 * 60 * 1000, // 15 minutes | ||
| max: 100, // limit each IP to 100 requests per windowMs for this route | ||
| }); | ||
|
|
||
| bpx(bodyParser); | ||
| router.use( | ||
| bodyParser.xml({ | ||
| @@ -247,7 +252,7 @@ | ||
| * Route: 'doctorOrders/api/updateRx/:id/pickedUp' | ||
| * Description : 'Updates prescription dispense status to picked up and sends RxFill per NCPDP spec' | ||
| */ | ||
| router.patch('/api/updateRx/:id/pickedUp', async (req, res) => { | ||
| router.patch('/api/updateRx/:id/pickedUp', pickedUpLimiter, async (req, res) => { | ||
| let prescriberOrderNumber = null; | ||
| try { | ||
| const newOrder = await doctorOrder.findOneAndUpdate( |
-
Copy modified lines R16-R17
| @@ -13,7 +13,8 @@ | ||
| "mongoose": "^8.9.5", | ||
| "var": "^0.4.0", | ||
| "web-vitals": "^2.1.4", | ||
| "xml2js": "^0.6.0" | ||
| "xml2js": "^0.6.0", | ||
| "express-rate-limit": "^8.2.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/chai": "^4.3.4", |
| Package | Version | Security advisories |
| express-rate-limit (npm) | 8.2.1 | None |
Describe your changes
Please include a summary of the changes and the related issue/task. Please also include relevant motivation and context. List any dependencies that are required for this change, including links to other pull requests/branches in other repositories if applicable.
Issue ticket number and Jira link
Please include the Jira Ticket Number and Link for this issue/task.
Checklist before requesting a review
devnot main (the only exception to this is releases fromdevand hotfix branches)Checklist for conducting a review
Workflow
Owner of the Pull Request will be responsible for merge after all requirements are met, including approval from at least one reviewer. Additional changes made after a review will dismiss any approvals and require re-review of the additional updates. Auto merging can be enabled below if additional changes are likely not to be needed. The bot will auto assign reviewers to your Pull Request for you.