@chidchanun/bcp 0.1.16 → 0.1.18
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/docs/README.md +325 -0
- package/docs/auth-route-guards.md +216 -0
- package/docs/middleware.md +486 -24
- package/docs/releases/0.1.17.md +36 -0
- package/docs/releases/0.1.18.md +119 -0
- package/package.json +1 -1
- package/packages/client/src/auth.ts +15 -0
- package/packages/server/src/auth-guard.ts +403 -0
- package/packages/server/src/middleware-loader.ts +34 -7
- package/packages/server/src/middleware-proxy.ts +325 -159
- package/packages/server/src/middleware.ts +544 -104
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import * as http from "node:http";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
executeMiddleware,
|
|
7
|
-
sendMiddlewareRedirect,
|
|
4
|
+
executeMiddlewarePipeline,
|
|
5
|
+
getMiddlewareRedirectTarget,
|
|
8
6
|
sendMiddlewareResponse,
|
|
7
|
+
type MiddlewareExecutionInput,
|
|
9
8
|
type MiddlewareModule,
|
|
10
9
|
} from "./middleware.js";
|
|
11
10
|
|
|
@@ -81,8 +80,8 @@ export function createMiddlewareProxy(
|
|
|
81
80
|
await options
|
|
82
81
|
.getMiddleware();
|
|
83
82
|
|
|
84
|
-
const
|
|
85
|
-
await
|
|
83
|
+
const response =
|
|
84
|
+
await executeMiddlewarePipeline(
|
|
86
85
|
module,
|
|
87
86
|
{
|
|
88
87
|
url:
|
|
@@ -94,91 +93,53 @@ export function createMiddlewareProxy(
|
|
|
94
93
|
incomingHeadersToHeaders(
|
|
95
94
|
req.headers
|
|
96
95
|
),
|
|
97
|
-
}
|
|
96
|
+
},
|
|
97
|
+
(
|
|
98
|
+
pipelineInput
|
|
99
|
+
) =>
|
|
100
|
+
requestUpstreamAsResponse(
|
|
101
|
+
req,
|
|
102
|
+
pipelineInput,
|
|
103
|
+
upstreamHostname,
|
|
104
|
+
options.upstreamPort,
|
|
105
|
+
host,
|
|
106
|
+
incomingUrl,
|
|
107
|
+
navigationTarget,
|
|
108
|
+
targetUrl
|
|
109
|
+
)
|
|
98
110
|
);
|
|
99
111
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
) {
|
|
104
|
-
if (
|
|
105
|
-
navigationTarget
|
|
106
|
-
) {
|
|
107
|
-
applyMiddlewareResponseHeaders(
|
|
108
|
-
res,
|
|
109
|
-
result.headers
|
|
110
|
-
);
|
|
111
|
-
res.statusCode =
|
|
112
|
-
204;
|
|
113
|
-
res.setHeader(
|
|
114
|
-
NAVIGATION_REDIRECT_HEADER,
|
|
115
|
-
result.url.href
|
|
116
|
-
);
|
|
117
|
-
res.setHeader(
|
|
118
|
-
"Cache-Control",
|
|
119
|
-
"no-store"
|
|
120
|
-
);
|
|
121
|
-
res.end();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
sendMiddlewareRedirect(
|
|
126
|
-
req,
|
|
127
|
-
res,
|
|
128
|
-
result
|
|
112
|
+
const middlewareRedirect =
|
|
113
|
+
getMiddlewareRedirectTarget(
|
|
114
|
+
response
|
|
129
115
|
);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
116
|
|
|
133
117
|
if (
|
|
134
|
-
|
|
135
|
-
|
|
118
|
+
navigationTarget &&
|
|
119
|
+
middlewareRedirect
|
|
136
120
|
) {
|
|
137
|
-
|
|
138
|
-
req,
|
|
121
|
+
applyNavigationRedirectHeaders(
|
|
139
122
|
res,
|
|
140
|
-
|
|
123
|
+
response.headers
|
|
141
124
|
);
|
|
125
|
+
res.statusCode =
|
|
126
|
+
204;
|
|
127
|
+
res.setHeader(
|
|
128
|
+
NAVIGATION_REDIRECT_HEADER,
|
|
129
|
+
middlewareRedirect.href
|
|
130
|
+
);
|
|
131
|
+
res.setHeader(
|
|
132
|
+
"Cache-Control",
|
|
133
|
+
"no-store"
|
|
134
|
+
);
|
|
135
|
+
res.end();
|
|
142
136
|
return;
|
|
143
137
|
}
|
|
144
138
|
|
|
145
|
-
|
|
146
|
-
req,
|
|
147
|
-
result.requestHeaders
|
|
148
|
-
);
|
|
149
|
-
applyMiddlewareResponseHeaders(
|
|
150
|
-
res,
|
|
151
|
-
result.responseHeaders
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
result.type ===
|
|
156
|
-
"rewrite"
|
|
157
|
-
) {
|
|
158
|
-
if (
|
|
159
|
-
navigationTarget
|
|
160
|
-
) {
|
|
161
|
-
incomingUrl
|
|
162
|
-
.searchParams
|
|
163
|
-
.set(
|
|
164
|
-
"url",
|
|
165
|
-
`${result.url.pathname}${result.url.search}${result.url.hash}`
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
req.url =
|
|
169
|
-
`${incomingUrl.pathname}${incomingUrl.search}`;
|
|
170
|
-
} else {
|
|
171
|
-
req.url =
|
|
172
|
-
`${result.url.pathname}${result.url.search}`;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
proxyRequest(
|
|
139
|
+
await sendMiddlewareResponse(
|
|
177
140
|
req,
|
|
178
141
|
res,
|
|
179
|
-
|
|
180
|
-
options.upstreamPort,
|
|
181
|
-
host
|
|
142
|
+
response
|
|
182
143
|
);
|
|
183
144
|
} catch (error) {
|
|
184
145
|
console.error(
|
|
@@ -282,114 +243,319 @@ function resolveNavigationTarget(
|
|
|
282
243
|
}
|
|
283
244
|
}
|
|
284
245
|
|
|
285
|
-
function
|
|
246
|
+
function requestUpstreamAsResponse(
|
|
286
247
|
req: http.IncomingMessage,
|
|
287
|
-
|
|
248
|
+
input: MiddlewareExecutionInput,
|
|
288
249
|
upstreamHostname: string,
|
|
289
250
|
upstreamPort: number,
|
|
290
|
-
originalHost: string
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
originalHost,
|
|
309
|
-
"x-forwarded-host":
|
|
310
|
-
originalHost,
|
|
311
|
-
},
|
|
312
|
-
}
|
|
251
|
+
originalHost: string,
|
|
252
|
+
incomingUrl: URL,
|
|
253
|
+
navigationTarget: URL | null,
|
|
254
|
+
originalTargetUrl: URL
|
|
255
|
+
): Promise<Response> {
|
|
256
|
+
const target =
|
|
257
|
+
input.url instanceof URL
|
|
258
|
+
? new URL(
|
|
259
|
+
input.url.href
|
|
260
|
+
)
|
|
261
|
+
: new URL(
|
|
262
|
+
input.url
|
|
263
|
+
);
|
|
264
|
+
const requestHeaders =
|
|
265
|
+
headersToNodeHeaders(
|
|
266
|
+
new Headers(
|
|
267
|
+
input.headers
|
|
268
|
+
)
|
|
313
269
|
);
|
|
314
270
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
271
|
+
requestHeaders.host =
|
|
272
|
+
originalHost;
|
|
273
|
+
requestHeaders[
|
|
274
|
+
"x-forwarded-host"
|
|
275
|
+
] = originalHost;
|
|
276
|
+
|
|
277
|
+
const path =
|
|
278
|
+
resolveUpstreamPath(
|
|
279
|
+
target,
|
|
280
|
+
incomingUrl,
|
|
281
|
+
navigationTarget,
|
|
282
|
+
originalTargetUrl
|
|
283
|
+
);
|
|
321
284
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
285
|
+
return new Promise<Response>(
|
|
286
|
+
(
|
|
287
|
+
resolve,
|
|
288
|
+
reject
|
|
289
|
+
) => {
|
|
290
|
+
const upstream =
|
|
291
|
+
http.request(
|
|
292
|
+
{
|
|
293
|
+
hostname:
|
|
294
|
+
upstreamHostname,
|
|
295
|
+
port:
|
|
296
|
+
upstreamPort,
|
|
297
|
+
method:
|
|
298
|
+
input.method ??
|
|
299
|
+
req.method ??
|
|
300
|
+
"GET",
|
|
301
|
+
path,
|
|
302
|
+
headers:
|
|
303
|
+
requestHeaders,
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
upstream.once(
|
|
308
|
+
"response",
|
|
309
|
+
(
|
|
310
|
+
upstreamResponse
|
|
311
|
+
) => {
|
|
312
|
+
const chunks:
|
|
313
|
+
Buffer[] = [];
|
|
314
|
+
|
|
315
|
+
upstreamResponse.on(
|
|
316
|
+
"data",
|
|
317
|
+
(
|
|
318
|
+
chunk:
|
|
319
|
+
Buffer |
|
|
320
|
+
string
|
|
321
|
+
) => {
|
|
322
|
+
chunks.push(
|
|
323
|
+
Buffer.isBuffer(
|
|
324
|
+
chunk
|
|
325
|
+
)
|
|
326
|
+
? chunk
|
|
327
|
+
: Buffer.from(
|
|
328
|
+
chunk
|
|
329
|
+
)
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
);
|
|
333
|
+
upstreamResponse.once(
|
|
334
|
+
"error",
|
|
335
|
+
reject
|
|
336
|
+
);
|
|
337
|
+
upstreamResponse.once(
|
|
338
|
+
"end",
|
|
339
|
+
() => {
|
|
340
|
+
const headers =
|
|
341
|
+
responseHeadersFromNode(
|
|
342
|
+
upstreamResponse.headers
|
|
343
|
+
);
|
|
344
|
+
const status =
|
|
345
|
+
upstreamResponse.statusCode ??
|
|
346
|
+
502;
|
|
347
|
+
const body =
|
|
348
|
+
req.method ===
|
|
349
|
+
"HEAD" ||
|
|
350
|
+
status === 204 ||
|
|
351
|
+
status === 304
|
|
352
|
+
? null
|
|
353
|
+
: Buffer.concat(
|
|
354
|
+
chunks
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
resolve(
|
|
358
|
+
new Response(
|
|
359
|
+
body,
|
|
360
|
+
{
|
|
361
|
+
status,
|
|
362
|
+
statusText:
|
|
363
|
+
upstreamResponse.statusMessage ??
|
|
364
|
+
"",
|
|
365
|
+
headers,
|
|
366
|
+
}
|
|
367
|
+
)
|
|
368
|
+
);
|
|
369
|
+
}
|
|
337
370
|
);
|
|
338
371
|
}
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
upstream.once(
|
|
375
|
+
"error",
|
|
376
|
+
reject
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
req.pipe(
|
|
380
|
+
upstream
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function resolveUpstreamPath(
|
|
387
|
+
target: URL,
|
|
388
|
+
incomingUrl: URL,
|
|
389
|
+
navigationTarget: URL | null,
|
|
390
|
+
originalTargetUrl: URL
|
|
391
|
+
): string {
|
|
392
|
+
if (!navigationTarget) {
|
|
393
|
+
return `${target.pathname}${target.search}`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const envelope =
|
|
397
|
+
new URL(
|
|
398
|
+
incomingUrl.href
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
if (
|
|
402
|
+
target.href !==
|
|
403
|
+
originalTargetUrl.href
|
|
404
|
+
) {
|
|
405
|
+
envelope.searchParams.set(
|
|
406
|
+
"url",
|
|
407
|
+
`${target.pathname}${target.search}${target.hash}`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return `${envelope.pathname}${envelope.search}`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function responseHeadersFromNode(
|
|
415
|
+
input: http.IncomingHttpHeaders
|
|
416
|
+
): Headers {
|
|
417
|
+
const headers =
|
|
418
|
+
new Headers();
|
|
419
|
+
|
|
420
|
+
for (
|
|
421
|
+
const [
|
|
422
|
+
key,
|
|
423
|
+
value,
|
|
424
|
+
]
|
|
425
|
+
of Object.entries(
|
|
426
|
+
input
|
|
427
|
+
)
|
|
428
|
+
) {
|
|
429
|
+
if (
|
|
430
|
+
value === undefined ||
|
|
431
|
+
isHopByHopHeader(
|
|
432
|
+
key
|
|
433
|
+
)
|
|
434
|
+
) {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (
|
|
439
|
+
Array.isArray(
|
|
440
|
+
value
|
|
441
|
+
)
|
|
442
|
+
) {
|
|
443
|
+
for (
|
|
444
|
+
const item
|
|
445
|
+
of value
|
|
446
|
+
) {
|
|
447
|
+
headers.append(
|
|
448
|
+
key,
|
|
449
|
+
item
|
|
450
|
+
);
|
|
339
451
|
}
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
340
454
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
455
|
+
headers.set(
|
|
456
|
+
key,
|
|
457
|
+
value
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return headers;
|
|
462
|
+
}
|
|
344
463
|
|
|
464
|
+
function headersToNodeHeaders(
|
|
465
|
+
input: Headers
|
|
466
|
+
): http.OutgoingHttpHeaders {
|
|
467
|
+
const headers:
|
|
468
|
+
http.OutgoingHttpHeaders = {};
|
|
469
|
+
|
|
470
|
+
input.forEach(
|
|
471
|
+
(value, key) => {
|
|
345
472
|
if (
|
|
346
|
-
|
|
473
|
+
!isHopByHopHeader(
|
|
474
|
+
key
|
|
475
|
+
)
|
|
347
476
|
) {
|
|
348
|
-
|
|
349
|
-
|
|
477
|
+
headers[key] =
|
|
478
|
+
value;
|
|
350
479
|
}
|
|
351
|
-
|
|
352
|
-
upstreamResponse.pipe(
|
|
353
|
-
res
|
|
354
|
-
);
|
|
355
480
|
}
|
|
356
481
|
);
|
|
357
482
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
483
|
+
return headers;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function applyNavigationRedirectHeaders(
|
|
487
|
+
res: http.ServerResponse,
|
|
488
|
+
headers: Headers
|
|
489
|
+
): void {
|
|
490
|
+
headers.forEach(
|
|
491
|
+
(value, key) => {
|
|
492
|
+
const normalized =
|
|
493
|
+
key.toLowerCase();
|
|
365
494
|
|
|
366
495
|
if (
|
|
367
|
-
|
|
496
|
+
normalized ===
|
|
497
|
+
"location" ||
|
|
498
|
+
normalized ===
|
|
499
|
+
"content-length" ||
|
|
500
|
+
isHopByHopHeader(
|
|
501
|
+
normalized
|
|
502
|
+
)
|
|
368
503
|
) {
|
|
369
|
-
res.destroy(
|
|
370
|
-
error
|
|
371
|
-
);
|
|
372
504
|
return;
|
|
373
505
|
}
|
|
374
506
|
|
|
375
|
-
res.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
"Content-Type":
|
|
379
|
-
"text/plain; charset=utf-8",
|
|
380
|
-
"Cache-Control":
|
|
381
|
-
"no-store",
|
|
382
|
-
}
|
|
383
|
-
);
|
|
384
|
-
res.end(
|
|
385
|
-
"Bad Gateway"
|
|
507
|
+
res.setHeader(
|
|
508
|
+
key,
|
|
509
|
+
value
|
|
386
510
|
);
|
|
387
511
|
}
|
|
388
512
|
);
|
|
389
513
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
514
|
+
const getSetCookie =
|
|
515
|
+
(
|
|
516
|
+
headers as Headers & {
|
|
517
|
+
getSetCookie?: () => string[];
|
|
518
|
+
}
|
|
519
|
+
).getSetCookie;
|
|
520
|
+
|
|
521
|
+
if (
|
|
522
|
+
typeof getSetCookie ===
|
|
523
|
+
"function"
|
|
524
|
+
) {
|
|
525
|
+
const cookies =
|
|
526
|
+
getSetCookie.call(
|
|
527
|
+
headers
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
if (
|
|
531
|
+
cookies.length > 0
|
|
532
|
+
) {
|
|
533
|
+
res.setHeader(
|
|
534
|
+
"Set-Cookie",
|
|
535
|
+
cookies
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function isHopByHopHeader(
|
|
542
|
+
name: string
|
|
543
|
+
): boolean {
|
|
544
|
+
switch (
|
|
545
|
+
name.toLowerCase()
|
|
546
|
+
) {
|
|
547
|
+
case "connection":
|
|
548
|
+
case "keep-alive":
|
|
549
|
+
case "proxy-authenticate":
|
|
550
|
+
case "proxy-authorization":
|
|
551
|
+
case "te":
|
|
552
|
+
case "trailer":
|
|
553
|
+
case "transfer-encoding":
|
|
554
|
+
case "upgrade":
|
|
555
|
+
return true;
|
|
556
|
+
default:
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
393
559
|
}
|
|
394
560
|
|
|
395
561
|
function incomingHeadersToHeaders(
|