@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.
@@ -0,0 +1,5 @@
1
+
2
+ > @admin-portal/server-utils@0.0.0 build /Users/dmalone/Documents/GitHub/admin-portal/packages/server-utils
3
+ > echo 'Server utils uses source files directly'
4
+
5
+ Server utils uses source files directly
@@ -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
+ ```