@webpieces/http-server 0.4.879 → 0.4.881
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/package.json +5 -5
- package/src/JsonlDuplexExpressCall.js +2 -1
- package/src/JsonlDuplexExpressCall.js.map +1 -1
- package/src/OAuthExpressErrorHandler.d.ts +32 -0
- package/src/OAuthExpressErrorHandler.js +53 -0
- package/src/OAuthExpressErrorHandler.js.map +1 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +4 -1
- package/src/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.881",
|
|
4
4
|
"description": "WebPieces server with filter chain and dependency injection",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@webpieces/core-context": "0.4.
|
|
26
|
-
"@webpieces/core-util": "0.4.
|
|
27
|
-
"@webpieces/gcp-identity": "0.4.
|
|
28
|
-
"@webpieces/http-routing": "0.4.
|
|
25
|
+
"@webpieces/core-context": "0.4.881",
|
|
26
|
+
"@webpieces/core-util": "0.4.881",
|
|
27
|
+
"@webpieces/gcp-identity": "0.4.881",
|
|
28
|
+
"@webpieces/http-routing": "0.4.881",
|
|
29
29
|
"cors": "2.8.5",
|
|
30
30
|
"express": "5.1.0",
|
|
31
31
|
"inversify": "7.10.4"
|
|
@@ -199,7 +199,8 @@ class JsonlDuplexExpressCall {
|
|
|
199
199
|
if (line === undefined)
|
|
200
200
|
break;
|
|
201
201
|
if (line.charCodeAt(0) === 0x1e) {
|
|
202
|
-
|
|
202
|
+
// The SERVER is the receiver of a record its caller wrote mid-upload (#1173).
|
|
203
|
+
const error = new core_util_1.StreamErrorControl().decode(line, core_util_1.ClientRole.SERVER);
|
|
203
204
|
await inbound.cancel(error);
|
|
204
205
|
await outbound.cancel(error);
|
|
205
206
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JsonlDuplexExpressCall.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/JsonlDuplexExpressCall.ts"],"names":[],"mappings":"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","sourcesContent":["import { once } from 'node:events';\nimport { Request, Response } from 'express';\nimport {\n StreamErrorControl,\n DtoValue,\n RequestStream,\n RouteMetadata,\n StreamDirection,\n StreamEventValidator,\n StreamTransportError,\n StreamProtocolError,\n toError,\n} from '@webpieces/core-util';\nimport { RequestContextHeaders, StreamEventContext } from '@webpieces/core-context';\n\nconst MAX_LINE_BYTES = 1024 * 1024;\n\n/** Stateful line reader that preserves bytes received after the initial request record. */\nclass JsonlRequestReader {\n private readonly iterator: AsyncIterator<Buffer | string>;\n private pending = Buffer.alloc(0);\n private done = false;\n\n constructor(req: Request) {\n this.iterator = req[Symbol.asyncIterator]() as AsyncIterator<Buffer | string>;\n }\n\n async nextLine(): Promise<string | undefined> {\n for (;;) {\n const newline = this.pending.indexOf(10);\n if (newline >= 0) {\n if (newline > MAX_LINE_BYTES)\n throw new StreamProtocolError(`JSONL record exceeds ${MAX_LINE_BYTES} bytes.`);\n const line = this.pending.subarray(0, newline).toString('utf8').replace(/\\r$/, '');\n this.pending = this.pending.subarray(newline + 1);\n if (line === '') continue;\n return line;\n }\n if (this.done) {\n if (this.pending.length === 0) return undefined;\n throw new StreamProtocolError('JSONL request ended mid-record.');\n }\n const chunk = await this.iterator.next();\n this.done = chunk.done === true;\n if (chunk.value !== undefined) {\n const bytes = Buffer.isBuffer(chunk.value) ? chunk.value : Buffer.from(chunk.value);\n this.pending = Buffer.concat([this.pending, bytes]);\n }\n if (this.pending.length > MAX_LINE_BYTES && this.pending.indexOf(10) < 0) {\n throw new StreamTransportError(`JSONL record exceeds ${MAX_LINE_BYTES} bytes.`);\n }\n }\n }\n}\n\n/** Ordered raw-JSONL response writer with an atomic initial-response boundary. */\nexport class JsonlResponseWriter {\n private opened = false;\n private ended = false;\n private pendingWrite: Promise<void> = Promise.resolve();\n private readonly queued: string[] = [];\n private queuedBytes = 0;\n private failure?: Error;\n\n constructor(\n private readonly res: Response,\n private readonly route: RouteMetadata,\n private readonly validator: StreamEventValidator,\n ) {\n res.once('close', (): void => {\n if (!res.writableEnded)\n this.failure = new StreamTransportError('Streaming HTTP peer disconnected.');\n });\n }\n\n readonly stream = {\n event: async (value: DtoValue): Promise<void> => {\n if (this.failure) throw this.failure;\n if (this.ended)\n throw new StreamTransportError('Cannot write after stream termination.');\n if (this.route.streaming?.responseSchema) {\n this.validator.validate(this.route.streaming.responseSchema, value, 'response');\n }\n await this.deliver(`${JSON.stringify(value)}\\n`);\n },\n close: async (): Promise<void> => {\n this.ended = true;\n await this.pendingWrite;\n if (this.opened) this.res.end();\n },\n cancel: async (error?: Error): Promise<void> => this.cancel(error),\n };\n\n async open(initial: DtoValue, headers: Map<string, string>): Promise<void> {\n if (this.route.streaming?.initialResponseSchema) {\n this.validator.validate(\n this.route.streaming.initialResponseSchema,\n initial,\n 'response',\n );\n }\n this.res.status(200);\n this.res.setHeader('Content-Type', 'application/x-webpieces-jsonl; charset=utf-8');\n this.res.setHeader('Cache-Control', 'no-cache, no-transform');\n this.res.setHeader('X-Accel-Buffering', 'no');\n for (const entry of headers) this.res.setHeader(entry[0], entry[1]);\n this.res.flushHeaders();\n this.opened = true;\n this.pendingWrite = this.flushOpening(initial);\n await this.pendingWrite;\n }\n\n async cancel(error?: Error): Promise<void> {\n if (this.failure) return;\n if (!error || this.ended) {\n this.failure = error ?? new StreamTransportError('Local stream cancellation.');\n this.res.destroy(this.failure);\n return;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- a failed error-control write must reset rather than send an invented remote DTO\n try {\n await this.deliver(new StreamErrorControl().encode(error));\n this.failure = error;\n this.ended = true;\n if (this.opened) this.res.end();\n } catch (err: unknown) {\n const error = toError(err);\n this.failure = error;\n this.res.destroy(error);\n }\n }\n\n private async flushOpening(initial: DtoValue): Promise<void> {\n await this.write(`${JSON.stringify(initial)}\\n`);\n for (const record of this.queued) await this.write(record);\n this.queued.length = 0;\n if (this.ended) this.res.end();\n }\n\n private async deliver(record: string): Promise<void> {\n if (!this.opened) {\n this.queuedBytes += Buffer.byteLength(record);\n if (this.queuedBytes > 64 * 1024)\n throw new StreamTransportError(\n 'Pre-handshake response buffer exceeds 65536 bytes.',\n );\n this.queued.push(record);\n return;\n }\n const delivery = this.pendingWrite.then((): Promise<void> => this.write(record));\n this.pendingWrite = delivery.catch((): void => undefined);\n await delivery;\n }\n\n private async write(record: string): Promise<void> {\n if (this.res.destroyed || this.res.writableEnded) {\n throw new StreamTransportError('Cannot write to a closed streaming HTTP response.');\n }\n if (this.res.write(record)) return;\n await Promise.race([\n once(this.res, 'drain'),\n once(this.res, 'close').then((): never => {\n throw new StreamTransportError('Streaming HTTP peer disconnected during write.');\n }),\n ]);\n }\n}\n\n/** Handles Node-only REQUEST/FULL application/x-webpieces-jsonl routes. */\nexport class JsonlDuplexExpressCall {\n private readonly validator = new StreamEventValidator();\n\n constructor(\n // webpieces-disable no-any-unknown -- controller boundary has contract-erased DTO types\n private readonly clientMethod: (...args: unknown[]) => Promise<unknown>,\n private readonly route: RouteMetadata,\n private readonly headers: RequestContextHeaders,\n ) {}\n\n async execute(req: Request, res: Response): Promise<void> {\n const reader = new JsonlRequestReader(req);\n const initial = this.parseData(await reader.nextLine(), 'initial request');\n const metadata = this.route.streaming!;\n if (metadata.initialRequestSchema) {\n this.validator.validate(metadata.initialRequestSchema, initial, 'request');\n }\n const outbound = new JsonlResponseWriter(res, this.route, this.validator);\n const returned = await this.invoke(initial, outbound);\n const inbound = this.requestStream(returned);\n const initialResponse = inbound.getInitialResponse();\n const events = new StreamEventContext();\n await outbound.open(initialResponse, this.headers.buildResponseHeaders());\n let cancelled = false;\n const cancel = async (): Promise<void> => {\n if (cancelled) return;\n cancelled = true;\n const error = new StreamTransportError('Streaming HTTP peer disconnected.');\n await inbound.cancel(error);\n };\n req.once('aborted', (): void => void cancel());\n res.once('close', (): void => {\n if (!res.writableEnded) void cancel();\n });\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- post-open failures must be written as sideband controls\n try {\n for (;;) {\n const line = await reader.nextLine();\n if (line === undefined) break;\n if (line.charCodeAt(0) === 0x1e) {\n const error = new StreamErrorControl().decode(line);\n await inbound.cancel(error);\n await outbound.cancel(error);\n return;\n }\n const value = this.parseData(line, 'request event');\n if (metadata.requestSchema) {\n this.validator.validate(metadata.requestSchema, value, 'request');\n }\n await events.run((): Promise<void> => inbound.event(value));\n }\n await inbound.close();\n if (metadata.direction === StreamDirection.REQUEST) await outbound.stream.close();\n } catch (err: unknown) {\n const error = toError(err);\n await outbound.cancel(error);\n await inbound.cancel(error);\n }\n }\n\n // webpieces-disable no-any-unknown -- controller return is validated as a typed RequestStream before opening\n private async invoke(initial: DtoValue, outbound: JsonlResponseWriter): Promise<unknown> {\n if (this.route.streaming?.direction === StreamDirection.FULL) {\n return this.clientMethod(initial, outbound.stream);\n }\n return this.clientMethod(initial);\n }\n\n // webpieces-disable no-any-unknown -- runtime guard narrows controller return value\n private requestStream(value: unknown): RequestStream<DtoValue, DtoValue> {\n if (\n typeof value !== 'object' ||\n value === null ||\n typeof Reflect.get(value, 'getInitialResponse') !== 'function' ||\n typeof Reflect.get(value, 'event') !== 'function' ||\n typeof Reflect.get(value, 'close') !== 'function' ||\n typeof Reflect.get(value, 'cancel') !== 'function'\n ) {\n throw new StreamTransportError('Streaming controller did not return a RequestStream.');\n }\n return value as RequestStream<DtoValue, DtoValue>;\n }\n\n private parseData(line: string | undefined, label: string): DtoValue {\n if (line === undefined) throw new StreamTransportError(`Missing ${label} JSONL record.`);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- JSON.parse is the untrusted JSONL boundary\n try {\n return JSON.parse(line) as DtoValue;\n } catch (err: unknown) {\n const error = toError(err);\n throw new StreamTransportError(\n `Malformed ${label} JSONL record.`,\n undefined,\n undefined,\n error,\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"JsonlDuplexExpressCall.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/JsonlDuplexExpressCall.ts"],"names":[],"mappings":"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","sourcesContent":["import { once } from 'node:events';\nimport { Request, Response } from 'express';\nimport {\n ClientRole,\n StreamErrorControl,\n DtoValue,\n RequestStream,\n RouteMetadata,\n StreamDirection,\n StreamEventValidator,\n StreamTransportError,\n StreamProtocolError,\n toError,\n} from '@webpieces/core-util';\nimport { RequestContextHeaders, StreamEventContext } from '@webpieces/core-context';\n\nconst MAX_LINE_BYTES = 1024 * 1024;\n\n/** Stateful line reader that preserves bytes received after the initial request record. */\nclass JsonlRequestReader {\n private readonly iterator: AsyncIterator<Buffer | string>;\n private pending = Buffer.alloc(0);\n private done = false;\n\n constructor(req: Request) {\n this.iterator = req[Symbol.asyncIterator]() as AsyncIterator<Buffer | string>;\n }\n\n async nextLine(): Promise<string | undefined> {\n for (;;) {\n const newline = this.pending.indexOf(10);\n if (newline >= 0) {\n if (newline > MAX_LINE_BYTES)\n throw new StreamProtocolError(`JSONL record exceeds ${MAX_LINE_BYTES} bytes.`);\n const line = this.pending.subarray(0, newline).toString('utf8').replace(/\\r$/, '');\n this.pending = this.pending.subarray(newline + 1);\n if (line === '') continue;\n return line;\n }\n if (this.done) {\n if (this.pending.length === 0) return undefined;\n throw new StreamProtocolError('JSONL request ended mid-record.');\n }\n const chunk = await this.iterator.next();\n this.done = chunk.done === true;\n if (chunk.value !== undefined) {\n const bytes = Buffer.isBuffer(chunk.value) ? chunk.value : Buffer.from(chunk.value);\n this.pending = Buffer.concat([this.pending, bytes]);\n }\n if (this.pending.length > MAX_LINE_BYTES && this.pending.indexOf(10) < 0) {\n throw new StreamTransportError(`JSONL record exceeds ${MAX_LINE_BYTES} bytes.`);\n }\n }\n }\n}\n\n/** Ordered raw-JSONL response writer with an atomic initial-response boundary. */\nexport class JsonlResponseWriter {\n private opened = false;\n private ended = false;\n private pendingWrite: Promise<void> = Promise.resolve();\n private readonly queued: string[] = [];\n private queuedBytes = 0;\n private failure?: Error;\n\n constructor(\n private readonly res: Response,\n private readonly route: RouteMetadata,\n private readonly validator: StreamEventValidator,\n ) {\n res.once('close', (): void => {\n if (!res.writableEnded)\n this.failure = new StreamTransportError('Streaming HTTP peer disconnected.');\n });\n }\n\n readonly stream = {\n event: async (value: DtoValue): Promise<void> => {\n if (this.failure) throw this.failure;\n if (this.ended)\n throw new StreamTransportError('Cannot write after stream termination.');\n if (this.route.streaming?.responseSchema) {\n this.validator.validate(this.route.streaming.responseSchema, value, 'response');\n }\n await this.deliver(`${JSON.stringify(value)}\\n`);\n },\n close: async (): Promise<void> => {\n this.ended = true;\n await this.pendingWrite;\n if (this.opened) this.res.end();\n },\n cancel: async (error?: Error): Promise<void> => this.cancel(error),\n };\n\n async open(initial: DtoValue, headers: Map<string, string>): Promise<void> {\n if (this.route.streaming?.initialResponseSchema) {\n this.validator.validate(\n this.route.streaming.initialResponseSchema,\n initial,\n 'response',\n );\n }\n this.res.status(200);\n this.res.setHeader('Content-Type', 'application/x-webpieces-jsonl; charset=utf-8');\n this.res.setHeader('Cache-Control', 'no-cache, no-transform');\n this.res.setHeader('X-Accel-Buffering', 'no');\n for (const entry of headers) this.res.setHeader(entry[0], entry[1]);\n this.res.flushHeaders();\n this.opened = true;\n this.pendingWrite = this.flushOpening(initial);\n await this.pendingWrite;\n }\n\n async cancel(error?: Error): Promise<void> {\n if (this.failure) return;\n if (!error || this.ended) {\n this.failure = error ?? new StreamTransportError('Local stream cancellation.');\n this.res.destroy(this.failure);\n return;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- a failed error-control write must reset rather than send an invented remote DTO\n try {\n await this.deliver(new StreamErrorControl().encode(error));\n this.failure = error;\n this.ended = true;\n if (this.opened) this.res.end();\n } catch (err: unknown) {\n const error = toError(err);\n this.failure = error;\n this.res.destroy(error);\n }\n }\n\n private async flushOpening(initial: DtoValue): Promise<void> {\n await this.write(`${JSON.stringify(initial)}\\n`);\n for (const record of this.queued) await this.write(record);\n this.queued.length = 0;\n if (this.ended) this.res.end();\n }\n\n private async deliver(record: string): Promise<void> {\n if (!this.opened) {\n this.queuedBytes += Buffer.byteLength(record);\n if (this.queuedBytes > 64 * 1024)\n throw new StreamTransportError(\n 'Pre-handshake response buffer exceeds 65536 bytes.',\n );\n this.queued.push(record);\n return;\n }\n const delivery = this.pendingWrite.then((): Promise<void> => this.write(record));\n this.pendingWrite = delivery.catch((): void => undefined);\n await delivery;\n }\n\n private async write(record: string): Promise<void> {\n if (this.res.destroyed || this.res.writableEnded) {\n throw new StreamTransportError('Cannot write to a closed streaming HTTP response.');\n }\n if (this.res.write(record)) return;\n await Promise.race([\n once(this.res, 'drain'),\n once(this.res, 'close').then((): never => {\n throw new StreamTransportError('Streaming HTTP peer disconnected during write.');\n }),\n ]);\n }\n}\n\n/** Handles Node-only REQUEST/FULL application/x-webpieces-jsonl routes. */\nexport class JsonlDuplexExpressCall {\n private readonly validator = new StreamEventValidator();\n\n constructor(\n // webpieces-disable no-any-unknown -- controller boundary has contract-erased DTO types\n private readonly clientMethod: (...args: unknown[]) => Promise<unknown>,\n private readonly route: RouteMetadata,\n private readonly headers: RequestContextHeaders,\n ) {}\n\n async execute(req: Request, res: Response): Promise<void> {\n const reader = new JsonlRequestReader(req);\n const initial = this.parseData(await reader.nextLine(), 'initial request');\n const metadata = this.route.streaming!;\n if (metadata.initialRequestSchema) {\n this.validator.validate(metadata.initialRequestSchema, initial, 'request');\n }\n const outbound = new JsonlResponseWriter(res, this.route, this.validator);\n const returned = await this.invoke(initial, outbound);\n const inbound = this.requestStream(returned);\n const initialResponse = inbound.getInitialResponse();\n const events = new StreamEventContext();\n await outbound.open(initialResponse, this.headers.buildResponseHeaders());\n let cancelled = false;\n const cancel = async (): Promise<void> => {\n if (cancelled) return;\n cancelled = true;\n const error = new StreamTransportError('Streaming HTTP peer disconnected.');\n await inbound.cancel(error);\n };\n req.once('aborted', (): void => void cancel());\n res.once('close', (): void => {\n if (!res.writableEnded) void cancel();\n });\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- post-open failures must be written as sideband controls\n try {\n for (;;) {\n const line = await reader.nextLine();\n if (line === undefined) break;\n if (line.charCodeAt(0) === 0x1e) {\n // The SERVER is the receiver of a record its caller wrote mid-upload (#1173).\n const error = new StreamErrorControl().decode(line, ClientRole.SERVER);\n await inbound.cancel(error);\n await outbound.cancel(error);\n return;\n }\n const value = this.parseData(line, 'request event');\n if (metadata.requestSchema) {\n this.validator.validate(metadata.requestSchema, value, 'request');\n }\n await events.run((): Promise<void> => inbound.event(value));\n }\n await inbound.close();\n if (metadata.direction === StreamDirection.REQUEST) await outbound.stream.close();\n } catch (err: unknown) {\n const error = toError(err);\n await outbound.cancel(error);\n await inbound.cancel(error);\n }\n }\n\n // webpieces-disable no-any-unknown -- controller return is validated as a typed RequestStream before opening\n private async invoke(initial: DtoValue, outbound: JsonlResponseWriter): Promise<unknown> {\n if (this.route.streaming?.direction === StreamDirection.FULL) {\n return this.clientMethod(initial, outbound.stream);\n }\n return this.clientMethod(initial);\n }\n\n // webpieces-disable no-any-unknown -- runtime guard narrows controller return value\n private requestStream(value: unknown): RequestStream<DtoValue, DtoValue> {\n if (\n typeof value !== 'object' ||\n value === null ||\n typeof Reflect.get(value, 'getInitialResponse') !== 'function' ||\n typeof Reflect.get(value, 'event') !== 'function' ||\n typeof Reflect.get(value, 'close') !== 'function' ||\n typeof Reflect.get(value, 'cancel') !== 'function'\n ) {\n throw new StreamTransportError('Streaming controller did not return a RequestStream.');\n }\n return value as RequestStream<DtoValue, DtoValue>;\n }\n\n private parseData(line: string | undefined, label: string): DtoValue {\n if (line === undefined) throw new StreamTransportError(`Missing ${label} JSONL record.`);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- JSON.parse is the untrusted JSONL boundary\n try {\n return JSON.parse(line) as DtoValue;\n } catch (err: unknown) {\n const error = toError(err);\n throw new StreamTransportError(\n `Malformed ${label} JSONL record.`,\n undefined,\n undefined,\n error,\n );\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from 'express';
|
|
2
|
+
import { OAuthProtocolError } from '@webpieces/core-util';
|
|
3
|
+
/**
|
|
4
|
+
* The express boundary for an app's OWN OAuth routes — `/oauth/token`, `/oauth/register`,
|
|
5
|
+
* `/oauth/authorize` — which an app mounts directly on express beside `WpMcpServer.bind(...)`
|
|
6
|
+
* (webpieces leaves token issuance pluggable). It answers an {@link OAuthProtocolError} in the RFC
|
|
7
|
+
* 6749/6750 shape and logs it at INFO as an EXPECTED refusal (#1176); anything else is passed to
|
|
8
|
+
* the next express error handler untouched, so a real bug is still a fault.
|
|
9
|
+
*
|
|
10
|
+
* Two ways to use it, for the two shapes a raw route boundary takes:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const oauthErrors = new OAuthExpressErrorHandler();
|
|
14
|
+
* // 1. as express error middleware (express 5 forwards an async handler's rejection to it)
|
|
15
|
+
* app.post('/oauth/token', urlencoded({ extended: false }), tokenHandler, oauthErrors.middleware);
|
|
16
|
+
* // 2. from a route's own try/catch
|
|
17
|
+
* if (err instanceof OAuthProtocolError) oauthErrors.write(res, err);
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* A webpieces `@Endpoint` that throws an `OAuthProtocolError` needs neither: the default
|
|
21
|
+
* `ErrorTranslator` renders it with the same `OAuthErrorResponse`.
|
|
22
|
+
*/
|
|
23
|
+
export declare class OAuthExpressErrorHandler {
|
|
24
|
+
private readonly responseWriter;
|
|
25
|
+
/**
|
|
26
|
+
* Express error middleware (four parameters, which is how express recognises one). An arrow
|
|
27
|
+
* property so it can be handed to `app.use(...)` without losing `this`.
|
|
28
|
+
*/
|
|
29
|
+
readonly middleware: (failure: unknown, _req: Request, res: Response, next: NextFunction) => void;
|
|
30
|
+
/** Write `error` in the RFC shape, with its status and headers, and log it at INFO. */
|
|
31
|
+
write(res: Response, error: OAuthProtocolError): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OAuthExpressErrorHandler = void 0;
|
|
4
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
5
|
+
const ExpressResponseWriter_1 = require("./ExpressResponseWriter");
|
|
6
|
+
const log = core_util_1.LogManager.getLogger('OAuthExpressErrorHandler');
|
|
7
|
+
/**
|
|
8
|
+
* The express boundary for an app's OWN OAuth routes — `/oauth/token`, `/oauth/register`,
|
|
9
|
+
* `/oauth/authorize` — which an app mounts directly on express beside `WpMcpServer.bind(...)`
|
|
10
|
+
* (webpieces leaves token issuance pluggable). It answers an {@link OAuthProtocolError} in the RFC
|
|
11
|
+
* 6749/6750 shape and logs it at INFO as an EXPECTED refusal (#1176); anything else is passed to
|
|
12
|
+
* the next express error handler untouched, so a real bug is still a fault.
|
|
13
|
+
*
|
|
14
|
+
* Two ways to use it, for the two shapes a raw route boundary takes:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* const oauthErrors = new OAuthExpressErrorHandler();
|
|
18
|
+
* // 1. as express error middleware (express 5 forwards an async handler's rejection to it)
|
|
19
|
+
* app.post('/oauth/token', urlencoded({ extended: false }), tokenHandler, oauthErrors.middleware);
|
|
20
|
+
* // 2. from a route's own try/catch
|
|
21
|
+
* if (err instanceof OAuthProtocolError) oauthErrors.write(res, err);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* A webpieces `@Endpoint` that throws an `OAuthProtocolError` needs neither: the default
|
|
25
|
+
* `ErrorTranslator` renders it with the same `OAuthErrorResponse`.
|
|
26
|
+
*/
|
|
27
|
+
class OAuthExpressErrorHandler {
|
|
28
|
+
responseWriter = new ExpressResponseWriter_1.ExpressResponseWriter();
|
|
29
|
+
/**
|
|
30
|
+
* Express error middleware (four parameters, which is how express recognises one). An arrow
|
|
31
|
+
* property so it can be handed to `app.use(...)` without losing `this`.
|
|
32
|
+
*/
|
|
33
|
+
// webpieces-disable no-any-unknown -- express hands an error handler whatever was thrown
|
|
34
|
+
middleware = (failure, _req, res, next) => {
|
|
35
|
+
if (!(failure instanceof core_util_1.OAuthProtocolError)) {
|
|
36
|
+
next(failure);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.write(res, failure);
|
|
40
|
+
};
|
|
41
|
+
/** Write `error` in the RFC shape, with its status and headers, and log it at INFO. */
|
|
42
|
+
write(res, error) {
|
|
43
|
+
const level = error.isCallerError() ? 'refused' : 'unavailable';
|
|
44
|
+
log.info(`OAuth request ${level}: status=${error.status} error=${error.error} message=${error.message}`);
|
|
45
|
+
if (res.headersSent) {
|
|
46
|
+
res.end();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.responseWriter.write(res, core_util_1.OAuthErrorResponse.toResponse(error, undefined));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.OAuthExpressErrorHandler = OAuthExpressErrorHandler;
|
|
53
|
+
//# sourceMappingURL=OAuthExpressErrorHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OAuthExpressErrorHandler.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/OAuthExpressErrorHandler.ts"],"names":[],"mappings":";;;AACA,oDAA0F;AAC1F,mEAAgE;AAEhE,MAAM,GAAG,GAAG,sBAAU,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAa,wBAAwB;IAChB,cAAc,GAAG,IAAI,6CAAqB,EAAE,CAAC;IAE9D;;;OAGG;IACH,yFAAyF;IAChF,UAAU,GAAG,CAClB,OAAgB,EAChB,IAAa,EACb,GAAa,EACb,IAAkB,EACd,EAAE;QACN,IAAI,CAAC,CAAC,OAAO,YAAY,8BAAkB,CAAC,EAAE,CAAC;YAC3C,IAAI,CAAC,OAAO,CAAC,CAAC;YACd,OAAO;QACX,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC7B,CAAC,CAAC;IAEF,uFAAuF;IACvF,KAAK,CAAC,GAAa,EAAE,KAAyB;QAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC;QAChE,GAAG,CAAC,IAAI,CACJ,iBAAiB,KAAK,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,OAAO,EAAE,CACjG,CAAC;QACF,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YAClB,GAAG,CAAC,GAAG,EAAE,CAAC;YACV,OAAO;QACX,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,EAAE,8BAAkB,CAAC,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;IACpF,CAAC;CACJ;AAjCD,4DAiCC","sourcesContent":["import { NextFunction, Request, Response } from 'express';\nimport { LogManager, OAuthErrorResponse, OAuthProtocolError } from '@webpieces/core-util';\nimport { ExpressResponseWriter } from './ExpressResponseWriter';\n\nconst log = LogManager.getLogger('OAuthExpressErrorHandler');\n\n/**\n * The express boundary for an app's OWN OAuth routes — `/oauth/token`, `/oauth/register`,\n * `/oauth/authorize` — which an app mounts directly on express beside `WpMcpServer.bind(...)`\n * (webpieces leaves token issuance pluggable). It answers an {@link OAuthProtocolError} in the RFC\n * 6749/6750 shape and logs it at INFO as an EXPECTED refusal (#1176); anything else is passed to\n * the next express error handler untouched, so a real bug is still a fault.\n *\n * Two ways to use it, for the two shapes a raw route boundary takes:\n *\n * ```ts\n * const oauthErrors = new OAuthExpressErrorHandler();\n * // 1. as express error middleware (express 5 forwards an async handler's rejection to it)\n * app.post('/oauth/token', urlencoded({ extended: false }), tokenHandler, oauthErrors.middleware);\n * // 2. from a route's own try/catch\n * if (err instanceof OAuthProtocolError) oauthErrors.write(res, err);\n * ```\n *\n * A webpieces `@Endpoint` that throws an `OAuthProtocolError` needs neither: the default\n * `ErrorTranslator` renders it with the same `OAuthErrorResponse`.\n */\nexport class OAuthExpressErrorHandler {\n private readonly responseWriter = new ExpressResponseWriter();\n\n /**\n * Express error middleware (four parameters, which is how express recognises one). An arrow\n * property so it can be handed to `app.use(...)` without losing `this`.\n */\n // webpieces-disable no-any-unknown -- express hands an error handler whatever was thrown\n readonly middleware = (\n failure: unknown,\n _req: Request,\n res: Response,\n next: NextFunction,\n ): void => {\n if (!(failure instanceof OAuthProtocolError)) {\n next(failure);\n return;\n }\n this.write(res, failure);\n };\n\n /** Write `error` in the RFC shape, with its status and headers, and log it at INFO. */\n write(res: Response, error: OAuthProtocolError): void {\n const level = error.isCallerError() ? 'refused' : 'unavailable';\n log.info(\n `OAuth request ${level}: status=${error.status} error=${error.error} message=${error.message}`,\n );\n if (res.headersSent) {\n res.end();\n return;\n }\n this.responseWriter.write(res, OAuthErrorResponse.toResponse(error, undefined));\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { StreamBodyReader } from './body/StreamBodyReader';
|
|
|
5
5
|
export { PreConsumedBodyReader } from './body/PreConsumedBodyReader';
|
|
6
6
|
export { RecordingFilter } from './filters/RecordingFilter';
|
|
7
7
|
export { ExpressResponseWriter } from './ExpressResponseWriter';
|
|
8
|
+
export { OAuthExpressErrorHandler } from './OAuthExpressErrorHandler';
|
|
8
9
|
export { TestCaseRecorderImpl } from './recorder/TestCaseRecorderImpl';
|
|
9
10
|
export { SpecGenerator } from './recorder/SpecGenerator';
|
|
10
11
|
export { recordable } from './recorder/recordable';
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SharedSecrets = exports.AuthenticatedCaller = exports.AuthConfig = exports.HttpRequest = exports.ApiClient = exports.FilterDefinition = exports.FilterMatcher = exports.MethodMeta = exports.RouteHandler = exports.HeaderRegistry = exports.WebpiecesCoreHeaders = exports.recordable = exports.SpecGenerator = exports.TestCaseRecorderImpl = exports.ExpressResponseWriter = exports.RecordingFilter = exports.PreConsumedBodyReader = exports.StreamBodyReader = exports.WebpiecesMiddleware = exports.WebpiecesExpressRouter = void 0;
|
|
3
|
+
exports.SharedSecrets = exports.AuthenticatedCaller = exports.AuthConfig = exports.HttpRequest = exports.ApiClient = exports.FilterDefinition = exports.FilterMatcher = exports.MethodMeta = exports.RouteHandler = exports.HeaderRegistry = exports.WebpiecesCoreHeaders = exports.recordable = exports.SpecGenerator = exports.TestCaseRecorderImpl = exports.OAuthExpressErrorHandler = exports.ExpressResponseWriter = exports.RecordingFilter = exports.PreConsumedBodyReader = exports.StreamBodyReader = exports.WebpiecesMiddleware = exports.WebpiecesExpressRouter = void 0;
|
|
4
4
|
// Express adapter (the only place express lifecycle lives) over the node-only ApiFactory
|
|
5
5
|
var WebpiecesExpressRouter_1 = require("./WebpiecesExpressRouter");
|
|
6
6
|
Object.defineProperty(exports, "WebpiecesExpressRouter", { enumerable: true, get: function () { return WebpiecesExpressRouter_1.WebpiecesExpressRouter; } });
|
|
@@ -20,6 +20,9 @@ Object.defineProperty(exports, "RecordingFilter", { enumerable: true, get: funct
|
|
|
20
20
|
// plus one header") rather than copying its status-to-message table.
|
|
21
21
|
var ExpressResponseWriter_1 = require("./ExpressResponseWriter");
|
|
22
22
|
Object.defineProperty(exports, "ExpressResponseWriter", { enumerable: true, get: function () { return ExpressResponseWriter_1.ExpressResponseWriter; } });
|
|
23
|
+
// RFC 6749/6750 rendering + INFO logging of an OAuthProtocolError on an app's raw express OAuth routes.
|
|
24
|
+
var OAuthExpressErrorHandler_1 = require("./OAuthExpressErrorHandler");
|
|
25
|
+
Object.defineProperty(exports, "OAuthExpressErrorHandler", { enumerable: true, get: function () { return OAuthExpressErrorHandler_1.OAuthExpressErrorHandler; } });
|
|
23
26
|
// How ApiEndUserError is answered: 'gui' (266, default) or 'edge' (its edgeHttpStatus, else 400).
|
|
24
27
|
// Test-case recording (contract lives in @webpieces/core-util)
|
|
25
28
|
var TestCaseRecorderImpl_1 = require("./recorder/TestCaseRecorderImpl");
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/index.ts"],"names":[],"mappings":";;;AAAA,yFAAyF;AACzF,mEAAkE;AAAzD,gIAAA,sBAAsB,OAAA;AAC/B,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAI5B,4DAA2D;AAAlD,oHAAA,gBAAgB,OAAA;AACzB,sEAAqE;AAA5D,8HAAA,qBAAqB,OAAA;AAC9B,gGAAgG;AAChG,+FAA+F;AAC/F,+FAA+F;AAC/F,6DAA4D;AAAnD,kHAAA,eAAe,OAAA;AAExB,4FAA4F;AAC5F,mGAAmG;AACnG,qEAAqE;AACrE,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,kGAAkG;AAElG,+DAA+D;AAC/D,wEAAuE;AAA9D,4HAAA,oBAAoB,OAAA;AAC7B,0DAAyD;AAAhD,8GAAA,aAAa,OAAA;AACtB,oDAAmD;AAA1C,wGAAA,UAAU,OAAA;AAEnB,mEAAmE;AACnE,uEAAsE;AAA7D,4HAAA,oBAAoB,OAAA;AAC7B,kDAAsD;AAA7C,2GAAA,cAAc,OAAA;AAEvB,gEAAgE;AAChE,wDAYiC;AAX7B,4GAAA,YAAY,OAAA;AACZ,0GAAA,UAAU,OAAA;AAEV,6GAAA,aAAa,OAAA;AACb,gHAAA,gBAAgB,OAAA;AAEhB,yGAAA,SAAS,OAAA;AACT,2GAAA,WAAW,OAAA;AACX,0GAAA,UAAU,OAAA;AACV,mHAAA,mBAAmB,OAAA;AACnB,6GAAA,aAAa,OAAA","sourcesContent":["// Express adapter (the only place express lifecycle lives) over the node-only ApiFactory\nexport { WebpiecesExpressRouter } from './WebpiecesExpressRouter';\nexport { WebpiecesMiddleware } from './WebpiecesMiddleware';\n// How routes read the body bytes: stream (default) or a host's pre-consumed req.rawBody (issue #937).\n// Chosen via WebpiecesExpressRouter.setBodyReader(...) before bindExpress(app).\nexport { RequestBodyReader } from './body/RequestBodyReader';\nexport { StreamBodyReader } from './body/StreamBodyReader';\nexport { PreConsumedBodyReader } from './body/PreConsumedBodyReader';\n// LogApiFilter is now a FIXED framework filter auto-installed by WebpiecesRouter (outermost, at\n// priority 1,000,000, above AuthFilter). Apps must NOT install it themselves — it is no longer\n// exported here. Remove any `router.addFilter(new FilterDefinition(1800, LogApiFilter, '*'))`.\nexport { RecordingFilter } from './filters/RecordingFilter';\n\n// The webpieces DEFAULT error response, exported as a DELEGABLE object: it returns the same\n// HttpResponseDto an app's ErrorTranslator.toWire returns, so an app wraps it (\"webpieces' answer,\n// plus one header\") rather than copying its status-to-message table.\nexport { ExpressResponseWriter } from './ExpressResponseWriter';\n// How ApiEndUserError is answered: 'gui' (266, default) or 'edge' (its edgeHttpStatus, else 400).\n\n// Test-case recording (contract lives in @webpieces/core-util)\nexport { TestCaseRecorderImpl } from './recorder/TestCaseRecorderImpl';\nexport { SpecGenerator } from './recorder/SpecGenerator';\nexport { recordable } from './recorder/recordable';\n\n// Context keys + registry (the global magic-context header system)\nexport { WebpiecesCoreHeaders } from './headers/WebpiecesCoreHeaders';\nexport { HeaderRegistry } from '@webpieces/core-util';\n\n// Re-export from http-routing for one-import adapter ergonomics\nexport {\n RouteHandler,\n MethodMeta,\n HttpFilter,\n FilterMatcher,\n FilterDefinition,\n ApiFactory,\n ApiClient,\n HttpRequest,\n AuthConfig,\n AuthenticatedCaller,\n SharedSecrets,\n} from '@webpieces/http-routing';\n// ExpressRouteHandler now lives in http-server (express adapter), not node-only http-routing\nexport { ExpressRouteHandler } from './WebpiecesMiddleware';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-server/src/index.ts"],"names":[],"mappings":";;;AAAA,yFAAyF;AACzF,mEAAkE;AAAzD,gIAAA,sBAAsB,OAAA;AAC/B,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAI5B,4DAA2D;AAAlD,oHAAA,gBAAgB,OAAA;AACzB,sEAAqE;AAA5D,8HAAA,qBAAqB,OAAA;AAC9B,gGAAgG;AAChG,+FAA+F;AAC/F,+FAA+F;AAC/F,6DAA4D;AAAnD,kHAAA,eAAe,OAAA;AAExB,4FAA4F;AAC5F,mGAAmG;AACnG,qEAAqE;AACrE,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,wGAAwG;AACxG,uEAAsE;AAA7D,oIAAA,wBAAwB,OAAA;AACjC,kGAAkG;AAElG,+DAA+D;AAC/D,wEAAuE;AAA9D,4HAAA,oBAAoB,OAAA;AAC7B,0DAAyD;AAAhD,8GAAA,aAAa,OAAA;AACtB,oDAAmD;AAA1C,wGAAA,UAAU,OAAA;AAEnB,mEAAmE;AACnE,uEAAsE;AAA7D,4HAAA,oBAAoB,OAAA;AAC7B,kDAAsD;AAA7C,2GAAA,cAAc,OAAA;AAEvB,gEAAgE;AAChE,wDAYiC;AAX7B,4GAAA,YAAY,OAAA;AACZ,0GAAA,UAAU,OAAA;AAEV,6GAAA,aAAa,OAAA;AACb,gHAAA,gBAAgB,OAAA;AAEhB,yGAAA,SAAS,OAAA;AACT,2GAAA,WAAW,OAAA;AACX,0GAAA,UAAU,OAAA;AACV,mHAAA,mBAAmB,OAAA;AACnB,6GAAA,aAAa,OAAA","sourcesContent":["// Express adapter (the only place express lifecycle lives) over the node-only ApiFactory\nexport { WebpiecesExpressRouter } from './WebpiecesExpressRouter';\nexport { WebpiecesMiddleware } from './WebpiecesMiddleware';\n// How routes read the body bytes: stream (default) or a host's pre-consumed req.rawBody (issue #937).\n// Chosen via WebpiecesExpressRouter.setBodyReader(...) before bindExpress(app).\nexport { RequestBodyReader } from './body/RequestBodyReader';\nexport { StreamBodyReader } from './body/StreamBodyReader';\nexport { PreConsumedBodyReader } from './body/PreConsumedBodyReader';\n// LogApiFilter is now a FIXED framework filter auto-installed by WebpiecesRouter (outermost, at\n// priority 1,000,000, above AuthFilter). Apps must NOT install it themselves — it is no longer\n// exported here. Remove any `router.addFilter(new FilterDefinition(1800, LogApiFilter, '*'))`.\nexport { RecordingFilter } from './filters/RecordingFilter';\n\n// The webpieces DEFAULT error response, exported as a DELEGABLE object: it returns the same\n// HttpResponseDto an app's ErrorTranslator.toWire returns, so an app wraps it (\"webpieces' answer,\n// plus one header\") rather than copying its status-to-message table.\nexport { ExpressResponseWriter } from './ExpressResponseWriter';\n// RFC 6749/6750 rendering + INFO logging of an OAuthProtocolError on an app's raw express OAuth routes.\nexport { OAuthExpressErrorHandler } from './OAuthExpressErrorHandler';\n// How ApiEndUserError is answered: 'gui' (266, default) or 'edge' (its edgeHttpStatus, else 400).\n\n// Test-case recording (contract lives in @webpieces/core-util)\nexport { TestCaseRecorderImpl } from './recorder/TestCaseRecorderImpl';\nexport { SpecGenerator } from './recorder/SpecGenerator';\nexport { recordable } from './recorder/recordable';\n\n// Context keys + registry (the global magic-context header system)\nexport { WebpiecesCoreHeaders } from './headers/WebpiecesCoreHeaders';\nexport { HeaderRegistry } from '@webpieces/core-util';\n\n// Re-export from http-routing for one-import adapter ergonomics\nexport {\n RouteHandler,\n MethodMeta,\n HttpFilter,\n FilterMatcher,\n FilterDefinition,\n ApiFactory,\n ApiClient,\n HttpRequest,\n AuthConfig,\n AuthenticatedCaller,\n SharedSecrets,\n} from '@webpieces/http-routing';\n// ExpressRouteHandler now lives in http-server (express adapter), not node-only http-routing\nexport { ExpressRouteHandler } from './WebpiecesMiddleware';\n"]}
|