@rxbenefits/server-utils 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-lint.log +5 -0
- package/README.md +384 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +23 -0
- package/src/api-client.ts +132 -0
- package/src/call-java-handler.ts +369 -0
- package/src/index.ts +13 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
|
|
2
|
+
> @admin-portal/server-utils@0.0.0 lint /Users/dmalone/Documents/GitHub/admin-portal/packages/server-utils
|
|
3
|
+
> eslint . --ext .ts,.tsx
|
|
4
|
+
|
|
5
|
+
Pages directory cannot be found at /Users/dmalone/Documents/GitHub/admin-portal/packages/server-utils/pages or /Users/dmalone/Documents/GitHub/admin-portal/packages/server-utils/src/pages. If using a custom path, please configure with the `no-html-link-for-pages` rule in your eslint config file.
|
package/README.md
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# @rxbenefits/server-utils
|
|
2
|
+
|
|
3
|
+
Shared server-side utilities for Next.js API routes across all microfrontends.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This package centralizes common server-side API functionality to:
|
|
8
|
+
|
|
9
|
+
- **Automatically inject WAF bypass headers** on all backend API requests
|
|
10
|
+
- **Eliminate code duplication** across shell, ben-admin, financials, and future microfrontends
|
|
11
|
+
- **Ensure consistency** in how we handle authentication, tracing, and backend communication
|
|
12
|
+
- **Simplify migration** of new microfrontends by providing pre-built API handlers
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
Choose the implementation pattern that matches your app architecture:
|
|
17
|
+
|
|
18
|
+
- **Pages Router (shell, financials)**: Use [`createCallJavaHandler()`](#pattern-1-pages-router-calljava-handler) for `/pages/api/` routes
|
|
19
|
+
- **App Router (ben-admin)**: Use [`createBackendHeaders()`](#pattern-2-app-router-backend-headers) for `/app/api/` routes
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Implementation Patterns
|
|
24
|
+
|
|
25
|
+
### Pattern 1: Pages Router - CallJava Handler
|
|
26
|
+
|
|
27
|
+
**When to use**: Pages Router apps (shell, financials) that need to proxy requests to backend Java services.
|
|
28
|
+
|
|
29
|
+
**File**: `apps/{your-app}/pages/api/callJava.ts`
|
|
30
|
+
|
|
31
|
+
#### Step 1: Add Dependency
|
|
32
|
+
|
|
33
|
+
Ensure `@rxbenefits/server-utils` is in your `package.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@rxbenefits/server-utils": "workspace:*"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### Step 2: Create callJava Route
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
//************************DO NOT EDIT WITHOUT NOTIFYING Sam or Ken **********************************
|
|
47
|
+
import { createCallJavaHandler } from '@rxbenefits/server-utils';
|
|
48
|
+
|
|
49
|
+
import { getAccessToken, withApiAuthRequired, getWafBypassToken } from '../../lib/auth0';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Service key -> base URL mapping
|
|
53
|
+
* The incoming `route` field may contain one of these keys, which will be replaced with the configured URL.
|
|
54
|
+
*/
|
|
55
|
+
const services = {
|
|
56
|
+
'fms-commission-service': process.env.FMSCOMMISSIONAPIURI || '',
|
|
57
|
+
'fms-invoicing-service': process.env.FMSINVOICEAPIURI || '',
|
|
58
|
+
'eligibility-imports-service': process.env.ELIGIBILITYIMPORTAPIURI || '',
|
|
59
|
+
'audit-service': process.env.AUDITAPIURI || '',
|
|
60
|
+
'billing-service': process.env.FMSBILLINGSERVICEURI || '',
|
|
61
|
+
'bms-service': process.env.BMSAPIURI || '',
|
|
62
|
+
'task-service': process.env.TASKSERVICEAPIURI || '',
|
|
63
|
+
'ben-admin': process.env.BENADMINAPIURI || 'https://ben-admin.dev.rxbenefits.cloud/v1',
|
|
64
|
+
'com-manager-service':
|
|
65
|
+
process.env.COMMANAGERSERVICEURI || 'https://communication-manager-service-qa.rxbenefits.cloud',
|
|
66
|
+
} as const;
|
|
67
|
+
|
|
68
|
+
export const config = {
|
|
69
|
+
api: {
|
|
70
|
+
bodyParser: false,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* CallJava API route handler
|
|
76
|
+
* Uses centralized handler from @rxbenefits/server-utils with automatic:
|
|
77
|
+
* - WAF bypass header injection
|
|
78
|
+
* - Authentication header injection
|
|
79
|
+
* - Tracing header injection
|
|
80
|
+
* - Error handling
|
|
81
|
+
*/
|
|
82
|
+
export default createCallJavaHandler(services, {
|
|
83
|
+
getAccessToken,
|
|
84
|
+
withApiAuthRequired,
|
|
85
|
+
getWafBypassToken,
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### Step 3: Ensure Auth0 WAF Support
|
|
90
|
+
|
|
91
|
+
Make sure your `lib/auth0.ts` exports `getWafBypassToken`:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
export function getWafBypassToken(): string | undefined {
|
|
95
|
+
return auth0Config?.wafBypassToken || process.env.WAF_BYPASS_TOKEN;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**That's it!** Your callJava route now automatically includes WAF bypass headers on all requests.
|
|
100
|
+
|
|
101
|
+
**Reference Examples**:
|
|
102
|
+
|
|
103
|
+
- [`apps/shell/pages/api/callJava.ts`](../../apps/shell/pages/api/callJava.ts)
|
|
104
|
+
- [`apps/financials/pages/api/callJava.ts`](../../apps/financials/pages/api/callJava.ts)
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### Pattern 2: App Router - Backend Headers
|
|
109
|
+
|
|
110
|
+
**When to use**: App Router apps (ben-admin) with custom API handlers that need WAF headers.
|
|
111
|
+
|
|
112
|
+
**Files**:
|
|
113
|
+
|
|
114
|
+
- `apps/{your-app}/app/api/apiHandler.ts`
|
|
115
|
+
- `apps/{your-app}/app/api/graphql/route.ts`
|
|
116
|
+
- Any custom App Router API routes
|
|
117
|
+
|
|
118
|
+
#### Step 1: Add Dependency
|
|
119
|
+
|
|
120
|
+
Ensure `@rxbenefits/server-utils` is in your `package.json`:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"dependencies": {
|
|
125
|
+
"@rxbenefits/server-utils": "workspace:*"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Step 2: Update Your API Handler
|
|
131
|
+
|
|
132
|
+
Import `createBackendHeaders` and replace manual header construction:
|
|
133
|
+
|
|
134
|
+
**Before**:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const headers: Record<string, string> = {
|
|
138
|
+
'Content-Type': contentType ?? 'application/json',
|
|
139
|
+
Authorization: `Bearer ${accessToken}`,
|
|
140
|
+
Application: 'authorization',
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**After**:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { createBackendHeaders } from '@rxbenefits/server-utils';
|
|
148
|
+
import { getWafBypassToken } from '@/lib/auth0';
|
|
149
|
+
|
|
150
|
+
const headers: Record<string, string> = createBackendHeaders({
|
|
151
|
+
accessToken: accessToken || '',
|
|
152
|
+
wafBypassToken: getWafBypassToken(),
|
|
153
|
+
contentType: contentType ?? 'application/json',
|
|
154
|
+
enableTracing: false, // Set true if you want OpenTelemetry tracing
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### Step 3: Example - apiHandler.ts
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { createBackendHeaders } from '@rxbenefits/server-utils';
|
|
162
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
163
|
+
|
|
164
|
+
import { apiConfig } from './apiMaps';
|
|
165
|
+
import type { ApiParams } from './apiMapsHelper';
|
|
166
|
+
|
|
167
|
+
import { getAccessToken, withApiAuthRequired, getWafBypassToken } from '@/lib/auth0';
|
|
168
|
+
|
|
169
|
+
const fetchData = async (
|
|
170
|
+
key: string,
|
|
171
|
+
method: string,
|
|
172
|
+
req: NextRequest,
|
|
173
|
+
params: ApiParams | undefined
|
|
174
|
+
): Promise<unknown> => {
|
|
175
|
+
const res = new NextResponse();
|
|
176
|
+
const { accessToken } = await getAccessToken(req, res);
|
|
177
|
+
|
|
178
|
+
const urlConfig = apiConfig[key];
|
|
179
|
+
let url = typeof urlConfig.url === 'function' ? await urlConfig.url(params) : urlConfig.url;
|
|
180
|
+
|
|
181
|
+
// Use centralized header builder with automatic WAF bypass, auth, and tracing
|
|
182
|
+
const headers: Record<string, string> = createBackendHeaders({
|
|
183
|
+
accessToken: accessToken || '',
|
|
184
|
+
wafBypassToken: getWafBypassToken(),
|
|
185
|
+
contentType: req.headers.get('content-type') ?? 'application/json',
|
|
186
|
+
enableTracing: false,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const response = await fetch(url, {
|
|
190
|
+
method,
|
|
191
|
+
headers,
|
|
192
|
+
body: method !== 'GET' ? await req.text() : undefined,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return response.json();
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### Step 4: Example - GraphQL Route
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { createBackendHeaders } from '@rxbenefits/server-utils';
|
|
203
|
+
|
|
204
|
+
import { getAccessToken, withApiAuthRequired, getWafBypassToken } from '@/lib/auth0';
|
|
205
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
206
|
+
|
|
207
|
+
export const POST = withApiAuthRequired(async (request: NextRequest) => {
|
|
208
|
+
try {
|
|
209
|
+
const res = new NextResponse();
|
|
210
|
+
const { accessToken } = await getAccessToken(request, res);
|
|
211
|
+
|
|
212
|
+
if (!accessToken) {
|
|
213
|
+
return NextResponse.json({ error: 'No access token available' }, { status: 401 });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const body = await request.json();
|
|
217
|
+
const graphqlEndpoint = process.env.NEXT_PUBLIC_GRAPHQL_URL;
|
|
218
|
+
|
|
219
|
+
// Use centralized header builder with automatic WAF bypass, auth, and tracing
|
|
220
|
+
const headers = {
|
|
221
|
+
...createBackendHeaders({
|
|
222
|
+
accessToken,
|
|
223
|
+
wafBypassToken: getWafBypassToken(),
|
|
224
|
+
contentType: 'application/json',
|
|
225
|
+
enableTracing: false,
|
|
226
|
+
}),
|
|
227
|
+
Accept: 'application/json',
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const response = await fetch(graphqlEndpoint, {
|
|
231
|
+
method: 'POST',
|
|
232
|
+
headers,
|
|
233
|
+
body: JSON.stringify(body),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const data = await response.json();
|
|
237
|
+
return NextResponse.json(data);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error('GraphQL API route error:', error);
|
|
240
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Reference Examples**:
|
|
246
|
+
|
|
247
|
+
- [`apps/ben-admin/app/api/apiHandler.ts`](../../apps/ben-admin/app/api/apiHandler.ts)
|
|
248
|
+
- [`apps/ben-admin/app/api/graphql/route.ts`](../../apps/ben-admin/app/api/graphql/route.ts)
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## API Reference
|
|
253
|
+
|
|
254
|
+
### `createBackendHeaders(options)`
|
|
255
|
+
|
|
256
|
+
Creates a headers object with automatic injection of authentication, WAF, and tracing headers.
|
|
257
|
+
|
|
258
|
+
**Parameters**:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
{
|
|
262
|
+
accessToken: string; // Auth0 access token
|
|
263
|
+
wafBypassToken?: string; // WAF bypass token (optional)
|
|
264
|
+
contentType?: string; // Content-Type header (default: 'application/json')
|
|
265
|
+
enableTracing?: boolean; // Enable OpenTelemetry tracing (default: false)
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Returns**: `Record<string, string>` - Headers object ready for fetch/axios
|
|
270
|
+
|
|
271
|
+
**Example**:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
const headers = createBackendHeaders({
|
|
275
|
+
accessToken: token,
|
|
276
|
+
wafBypassToken: getWafBypassToken(),
|
|
277
|
+
contentType: 'application/json',
|
|
278
|
+
enableTracing: true,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
await fetch(url, { headers });
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
### `createCallJavaHandler(services, auth)`
|
|
287
|
+
|
|
288
|
+
Factory function that creates a complete Pages Router API handler for proxying to backend services.
|
|
289
|
+
|
|
290
|
+
**Parameters**:
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
services: Record<string, string>; // Service name -> base URL mapping
|
|
294
|
+
auth: {
|
|
295
|
+
getAccessToken: (req, res) => Promise<{ accessToken?: string }>;
|
|
296
|
+
withApiAuthRequired: (handler: any) => any;
|
|
297
|
+
getWafBypassToken: () => string | undefined;
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Returns**: Next.js API route handler
|
|
302
|
+
|
|
303
|
+
**Example**:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
const services = {
|
|
307
|
+
'fms-commission-service': process.env.FMSCOMMISSIONAPIURI || '',
|
|
308
|
+
'ben-admin': process.env.BENADMINAPIURI || '',
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
export default createCallJavaHandler(services, {
|
|
312
|
+
getAccessToken,
|
|
313
|
+
withApiAuthRequired,
|
|
314
|
+
getWafBypassToken,
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
### `createBackendApiClient(config)`
|
|
321
|
+
|
|
322
|
+
Creates an Axios instance with automatic header injection (advanced use).
|
|
323
|
+
|
|
324
|
+
**Parameters**:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
{
|
|
328
|
+
getWafBypassToken?: () => string | undefined;
|
|
329
|
+
baseURL?: string;
|
|
330
|
+
enableTracing?: boolean;
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Returns**: `{ request, getInstance }`
|
|
335
|
+
|
|
336
|
+
**Note**: Most apps should use `createBackendHeaders()` or `createCallJavaHandler()` instead.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Debugging Upstream Failures
|
|
341
|
+
|
|
342
|
+
When proxy requests fail upstream, the handlers emit structured logs:
|
|
343
|
+
|
|
344
|
+
- `/api/call` (shell) logs `[ApiDebug]` when `API_DEBUG=1` locally or automatically in AWS.
|
|
345
|
+
- `/api/callJava` logs `[ApiDebug]` when `AUTH0_DEBUG=1` locally or automatically in AWS.
|
|
346
|
+
|
|
347
|
+
These logs include the target URL, method, and error code (e.g., `ETIMEDOUT`) to help
|
|
348
|
+
identify network/VPC/DNS issues. Upstream network failures return a 502 response.
|
|
349
|
+
|
|
350
|
+
## Benefits
|
|
351
|
+
|
|
352
|
+
### Before (Duplicated Code)
|
|
353
|
+
|
|
354
|
+
Each app had its own nearly-identical `callJava.ts` with 200+ lines of code. Any bug fix or feature (like WAF headers) required updating 3+ files.
|
|
355
|
+
|
|
356
|
+
### After (Centralized)
|
|
357
|
+
|
|
358
|
+
Each app has a 15-line file that imports the shared handler. Bug fixes and features are applied once in this package.
|
|
359
|
+
|
|
360
|
+
### Migration Impact
|
|
361
|
+
|
|
362
|
+
New microfrontends can use these utilities out-of-the-box without implementing:
|
|
363
|
+
|
|
364
|
+
- WAF bypass header logic
|
|
365
|
+
- Tracing header logic
|
|
366
|
+
- Request/response formatting
|
|
367
|
+
- Error handling patterns
|
|
368
|
+
|
|
369
|
+
## What Gets Automatically Added
|
|
370
|
+
|
|
371
|
+
All utilities in this package automatically add:
|
|
372
|
+
|
|
373
|
+
1. **Authorization Header**: `Authorization: Bearer <token>`
|
|
374
|
+
2. **WAF Bypass Header**: `X-RXB-Bypass: <token>` (if available)
|
|
375
|
+
3. **Application Header**: `Application: authorization`
|
|
376
|
+
4. **Tracing Header**: `traceparent: <trace-context>` (if tracing enabled)
|
|
377
|
+
|
|
378
|
+
## Development
|
|
379
|
+
|
|
380
|
+
This package uses source files directly (no build step required):
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
pnpm --filter @rxbenefits/server-utils lint
|
|
384
|
+
```
|