@pristine-ts/http 4.0.2 → 4.0.4
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/dist/lib/cjs/cors/cors-preflight-response.interface.js +3 -0
- package/dist/lib/cjs/cors/cors-preflight-response.interface.js.map +1 -0
- package/dist/lib/cjs/cors/cors-request-context.interface.js +3 -0
- package/dist/lib/cjs/cors/cors-request-context.interface.js.map +1 -0
- package/dist/lib/cjs/cors/cors-request.handler.js +207 -0
- package/dist/lib/cjs/cors/cors-request.handler.js.map +1 -0
- package/dist/lib/cjs/cors/cors.js +20 -0
- package/dist/lib/cjs/cors/cors.js.map +1 -0
- package/dist/lib/cjs/http.configuration-keys.js +14 -0
- package/dist/lib/cjs/http.configuration-keys.js.map +1 -1
- package/dist/lib/cjs/http.module.js +101 -0
- package/dist/lib/cjs/http.module.js.map +1 -1
- package/dist/lib/cjs/servers/kernel.http-server.js +54 -5
- package/dist/lib/cjs/servers/kernel.http-server.js.map +1 -1
- package/dist/lib/esm/cors/cors-preflight-response.interface.js +2 -0
- package/dist/lib/esm/cors/cors-preflight-response.interface.js.map +1 -0
- package/dist/lib/esm/cors/cors-request-context.interface.js +2 -0
- package/dist/lib/esm/cors/cors-request-context.interface.js.map +1 -0
- package/dist/lib/esm/cors/cors-request.handler.js +204 -0
- package/dist/lib/esm/cors/cors-request.handler.js.map +1 -0
- package/dist/lib/esm/cors/cors.js +4 -0
- package/dist/lib/esm/cors/cors.js.map +1 -0
- package/dist/lib/esm/http.configuration-keys.js +14 -0
- package/dist/lib/esm/http.configuration-keys.js.map +1 -1
- package/dist/lib/esm/http.module.js +101 -0
- package/dist/lib/esm/http.module.js.map +1 -1
- package/dist/lib/esm/servers/kernel.http-server.js +54 -5
- package/dist/lib/esm/servers/kernel.http-server.js.map +1 -1
- package/dist/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/lib/tsconfig.tsbuildinfo +1 -1
- package/dist/types/cors/cors-preflight-response.interface.d.ts +20 -0
- package/dist/types/cors/cors-request-context.interface.d.ts +36 -0
- package/dist/types/cors/cors-request.handler.d.ts +94 -0
- package/dist/types/cors/cors.d.ts +3 -0
- package/dist/types/http.configuration-keys.d.ts +22 -0
- package/dist/types/http.module.d.ts +1 -0
- package/dist/types/servers/kernel.http-server.d.ts +13 -1
- package/package.json +10 -10
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The answer `CorsRequestHandler` produces for a CORS preflight that must be short-circuited
|
|
3
|
+
* before routing. `KernelHttpServer` writes `statusCode` and `headers` straight to the
|
|
4
|
+
* `ServerResponse` without ever invoking `kernel.handle()` — a preflight never corresponds to a
|
|
5
|
+
* real route.
|
|
6
|
+
*/
|
|
7
|
+
export interface CorsPreflightResponseInterface {
|
|
8
|
+
/**
|
|
9
|
+
* The status code to write. Always `204 No Content` for a preflight.
|
|
10
|
+
*/
|
|
11
|
+
statusCode: number;
|
|
12
|
+
/**
|
|
13
|
+
* The response headers to write. Populated with the `Access-Control-*` set when the origin is
|
|
14
|
+
* allow-listed; an empty object when it is not (the browser then blocks for lack of an
|
|
15
|
+
* `Access-Control-Allow-Origin`, and we never reflect an arbitrary origin or emit `"*"`).
|
|
16
|
+
*/
|
|
17
|
+
headers: {
|
|
18
|
+
[name: string]: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The normalized, transport-agnostic view of a request that `CorsRequestHandler` needs to make
|
|
3
|
+
* a CORS decision. Extracted from Node's `IncomingMessage` (via `CorsRequestHandler.buildContext`)
|
|
4
|
+
* so the decision logic can be unit-tested without a live socket or a Node request object.
|
|
5
|
+
*/
|
|
6
|
+
export interface CorsRequestContextInterface {
|
|
7
|
+
/**
|
|
8
|
+
* The upper-cased HTTP method (e.g. `"GET"`, `"OPTIONS"`).
|
|
9
|
+
*/
|
|
10
|
+
method: string;
|
|
11
|
+
/**
|
|
12
|
+
* The `Origin` request header, if present. Absent for same-origin and non-browser callers.
|
|
13
|
+
*/
|
|
14
|
+
origin?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The `Host` request header, if present. Compared against the `allowed-hosts` allowlist as a
|
|
17
|
+
* DNS-rebinding defense for loopback servers.
|
|
18
|
+
*/
|
|
19
|
+
host?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The `Access-Control-Request-Method` preflight header. Its presence on an `OPTIONS` request
|
|
22
|
+
* is what distinguishes a CORS preflight from an ordinary `OPTIONS`.
|
|
23
|
+
*/
|
|
24
|
+
accessControlRequestMethod?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The `Access-Control-Request-Headers` preflight header, listing the headers the actual
|
|
27
|
+
* request intends to send.
|
|
28
|
+
*/
|
|
29
|
+
accessControlRequestHeaders?: string;
|
|
30
|
+
/**
|
|
31
|
+
* `true` when the request carried `Access-Control-Request-Private-Network: true` — the Chrome
|
|
32
|
+
* Private Network Access preflight signal emitted when a public/secure context targets a more
|
|
33
|
+
* private address space (e.g. a loopback daemon).
|
|
34
|
+
*/
|
|
35
|
+
accessControlRequestPrivateNetwork: boolean;
|
|
36
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { CorsRequestContextInterface } from "./cors-request-context.interface";
|
|
2
|
+
import { CorsPreflightResponseInterface } from "./cors-preflight-response.interface";
|
|
3
|
+
/**
|
|
4
|
+
* The config-driven CORS policy engine that `KernelHttpServer` consults inside its request path,
|
|
5
|
+
* BEFORE `kernel.handle()` — the one place that sees the raw request/response and can answer a
|
|
6
|
+
* preflight or reject an origin without the networking `Router` ever running.
|
|
7
|
+
*
|
|
8
|
+
* All decision methods are pure functions of the injected configuration plus a normalized
|
|
9
|
+
* {@link CorsRequestContextInterface}, so the full behavior matrix is unit-testable by
|
|
10
|
+
* constructing this class directly (no live socket, no Node request object required).
|
|
11
|
+
*
|
|
12
|
+
* ## Activation
|
|
13
|
+
* CORS is **inactive unless configured**. Two independent switches:
|
|
14
|
+
* - `cors.allowed-origins` non-empty → origin/preflight/header-echo logic activates.
|
|
15
|
+
* - `cors.allowed-hosts` non-empty → the Host-header allowlist (DNS-rebinding defense) activates.
|
|
16
|
+
* With neither set, every method is a no-op and the server behaves exactly as it did before CORS
|
|
17
|
+
* existed.
|
|
18
|
+
*
|
|
19
|
+
* ## Guarantees
|
|
20
|
+
* - Never emits `Access-Control-Allow-Origin: *` — only an exact echo of an allow-listed Origin.
|
|
21
|
+
* - Never reflects an arbitrary Origin — an Origin absent from the allowlist gets no ACAO headers.
|
|
22
|
+
*/
|
|
23
|
+
export declare class CorsRequestHandler {
|
|
24
|
+
private readonly allowedOrigins;
|
|
25
|
+
private readonly allowedMethods;
|
|
26
|
+
private readonly allowedHeaders;
|
|
27
|
+
private readonly exposedHeaders;
|
|
28
|
+
private readonly maxAge;
|
|
29
|
+
private readonly allowCredentials;
|
|
30
|
+
private readonly allowPrivateNetwork;
|
|
31
|
+
private readonly allowedHosts;
|
|
32
|
+
constructor(allowedOrigins: string | string[], allowedMethods: string | string[], allowedHeaders: string | string[], exposedHeaders: string | string[], maxAge: number, allowCredentials: boolean, allowPrivateNetwork: boolean, allowedHosts: string | string[]);
|
|
33
|
+
/**
|
|
34
|
+
* `true` when an origin allowlist is configured — gates the preflight short-circuit and the
|
|
35
|
+
* `Access-Control-Allow-Origin` echo on actual responses.
|
|
36
|
+
*/
|
|
37
|
+
get isOriginCheckEnabled(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* `true` when a Host allowlist is configured — gates the DNS-rebinding 403.
|
|
40
|
+
*/
|
|
41
|
+
get isHostCheckEnabled(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether any CORS feature is active. `KernelHttpServer` can skip all CORS work when this is
|
|
44
|
+
* `false`, keeping the un-configured request path byte-for-byte identical to before.
|
|
45
|
+
*/
|
|
46
|
+
get isEnabled(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Normalizes a raw Node request into the transport-agnostic {@link CorsRequestContextInterface}.
|
|
49
|
+
* Node lower-cases header names on `IncomingMessage`, and multi-valued headers arrive as arrays;
|
|
50
|
+
* we take the method + the CORS-relevant headers and coerce them to the single-string shape the
|
|
51
|
+
* decision methods expect.
|
|
52
|
+
*/
|
|
53
|
+
buildContext(method: string, headers: {
|
|
54
|
+
[key: string]: string | string[] | undefined;
|
|
55
|
+
}): CorsRequestContextInterface;
|
|
56
|
+
/**
|
|
57
|
+
* Host-header allowlist check (loopback DNS-rebinding defense). Returns `true` (allow) when no
|
|
58
|
+
* allowlist is configured; otherwise the request's `Host` must exactly match a configured entry
|
|
59
|
+
* (case-insensitive, since host names are). A missing `Host` against a configured allowlist is
|
|
60
|
+
* rejected. `KernelHttpServer` turns a `false` here into a `403` before any routing.
|
|
61
|
+
*/
|
|
62
|
+
isHostAllowed(host: string | undefined): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Exact-match origin allowlist check. An absent Origin is never "allowed" (there is nothing to
|
|
65
|
+
* echo). We never wildcard and never reflect an arbitrary Origin.
|
|
66
|
+
*/
|
|
67
|
+
isOriginAllowed(origin: string | undefined): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Decides whether `context` is a CORS preflight that must be answered before routing, and if so
|
|
70
|
+
* returns the `204` response (status + headers) to write. Returns `undefined` when the request is
|
|
71
|
+
* not a preflight we own — the caller then lets it route normally.
|
|
72
|
+
*
|
|
73
|
+
* A CORS preflight is an `OPTIONS` carrying `Access-Control-Request-Method`. We only take over
|
|
74
|
+
* preflight handling when an origin allowlist is configured (same-origin requests never send
|
|
75
|
+
* `Access-Control-Request-Method`, so app-owned `OPTIONS` routes are never stolen). An allow-listed
|
|
76
|
+
* Origin gets the full `Access-Control-*` set; a disallowed Origin still gets a bare `204` with no
|
|
77
|
+
* ACAO — the browser blocks it, and we never leak an arbitrary Origin.
|
|
78
|
+
*/
|
|
79
|
+
buildPreflightResponse(context: CorsRequestContextInterface): CorsPreflightResponseInterface | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* The CORS headers to add to an ACTUAL (non-preflight) response — success OR error. Returns an
|
|
82
|
+
* empty object when origin-checking is off or the Origin is not allow-listed. Applied to 4xx/5xx
|
|
83
|
+
* responses too: without `Access-Control-Allow-Origin` the browser cannot read an error body.
|
|
84
|
+
*/
|
|
85
|
+
buildResponseHeaders(origin: string | undefined): {
|
|
86
|
+
[name: string]: string;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Coerces a list-shaped config value into a trimmed, empty-stripped `string[]`. Accepts both the
|
|
90
|
+
* comma-separated string form (env vars / defaults) and the `string[]` form (programmatic
|
|
91
|
+
* `kernel.start()` config).
|
|
92
|
+
*/
|
|
93
|
+
private normalizeList;
|
|
94
|
+
}
|
|
@@ -17,6 +17,20 @@ export declare const HttpConfigurationKeys: {
|
|
|
17
17
|
readonly KernelServerPort: "pristine.http.kernel-server.port";
|
|
18
18
|
readonly KernelServerTlsKeyPath: "pristine.http.kernel-server.tls.key-path";
|
|
19
19
|
readonly KernelServerTlsCertPath: "pristine.http.kernel-server.tls.cert-path";
|
|
20
|
+
/**
|
|
21
|
+
* CORS keys consumed by `CorsRequestHandler` inside `KernelHttpServer`'s request path.
|
|
22
|
+
* CORS is inactive unless `cors.allowed-origins` (and/or `cors.allowed-hosts`) is set —
|
|
23
|
+
* a server with none of these configured behaves exactly as it did before CORS existed.
|
|
24
|
+
* Each list key accepts a comma-separated string or a `string[]`.
|
|
25
|
+
*/
|
|
26
|
+
readonly CorsAllowedOrigins: "pristine.http.cors.allowed-origins";
|
|
27
|
+
readonly CorsAllowedMethods: "pristine.http.cors.allowed-methods";
|
|
28
|
+
readonly CorsAllowedHeaders: "pristine.http.cors.allowed-headers";
|
|
29
|
+
readonly CorsExposedHeaders: "pristine.http.cors.exposed-headers";
|
|
30
|
+
readonly CorsMaxAge: "pristine.http.cors.max-age";
|
|
31
|
+
readonly CorsAllowCredentials: "pristine.http.cors.allow-credentials";
|
|
32
|
+
readonly CorsAllowPrivateNetwork: "pristine.http.cors.allow-private-network";
|
|
33
|
+
readonly CorsAllowedHosts: "pristine.http.cors.allowed-hosts";
|
|
20
34
|
};
|
|
21
35
|
/**
|
|
22
36
|
* The expected runtime types for each configuration value defined by `@pristine-ts/http`.
|
|
@@ -30,6 +44,14 @@ export interface HttpConfigurationValueMap {
|
|
|
30
44
|
"pristine.http.kernel-server.port": number;
|
|
31
45
|
"pristine.http.kernel-server.tls.key-path": string;
|
|
32
46
|
"pristine.http.kernel-server.tls.cert-path": string;
|
|
47
|
+
"pristine.http.cors.allowed-origins": string;
|
|
48
|
+
"pristine.http.cors.allowed-methods": string;
|
|
49
|
+
"pristine.http.cors.allowed-headers": string;
|
|
50
|
+
"pristine.http.cors.exposed-headers": string;
|
|
51
|
+
"pristine.http.cors.max-age": number;
|
|
52
|
+
"pristine.http.cors.allow-credentials": boolean;
|
|
53
|
+
"pristine.http.cors.allow-private-network": boolean;
|
|
54
|
+
"pristine.http.cors.allowed-hosts": string;
|
|
33
55
|
}
|
|
34
56
|
/**
|
|
35
57
|
* Augments the global `PristineConfigurationValueMap` (defined in `@pristine-ts/common`)
|
|
@@ -2,6 +2,7 @@ import { ModuleInterface } from "@pristine-ts/common";
|
|
|
2
2
|
export * from "./http.module.keyname";
|
|
3
3
|
export * from "./commands/commands";
|
|
4
4
|
export * from "./clients/clients";
|
|
5
|
+
export * from "./cors/cors";
|
|
5
6
|
export * from "./enums/enums";
|
|
6
7
|
export * from "./errors/errors";
|
|
7
8
|
export * from "./interceptors/interceptors";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventIdManager, Kernel, RuntimeServerInterface } from "@pristine-ts/core";
|
|
2
2
|
import { LogHandlerInterface } from "@pristine-ts/logging";
|
|
3
|
+
import { CorsRequestHandler } from "../cors/cors-request.handler";
|
|
3
4
|
/**
|
|
4
5
|
* Per-request override of the start-time bound port/address pair. Both are optional — when
|
|
5
6
|
* absent, the server falls back to the values resolved from `pristine.http.kernel-server.*`
|
|
@@ -37,6 +38,7 @@ export declare class KernelHttpServer implements RuntimeServerInterface {
|
|
|
37
38
|
private readonly logHandler;
|
|
38
39
|
private readonly kernel;
|
|
39
40
|
private readonly eventIdManager;
|
|
41
|
+
private readonly corsRequestHandler;
|
|
40
42
|
/**
|
|
41
43
|
* Identifier reported back via `RuntimeServerInterface.name`. Switches to `"https"` when
|
|
42
44
|
* TLS config is present, mainly so log lines and `pristine info`-style introspection make
|
|
@@ -54,7 +56,7 @@ export declare class KernelHttpServer implements RuntimeServerInterface {
|
|
|
54
56
|
* @private
|
|
55
57
|
*/
|
|
56
58
|
private readonly connections;
|
|
57
|
-
constructor(defaultAddress: string, defaultPort: number, defaultTlsKeyPath: string, defaultTlsCertPath: string, logHandler: LogHandlerInterface, kernel: Kernel, eventIdManager: EventIdManager);
|
|
59
|
+
constructor(defaultAddress: string, defaultPort: number, defaultTlsKeyPath: string, defaultTlsCertPath: string, logHandler: LogHandlerInterface, kernel: Kernel, eventIdManager: EventIdManager, corsRequestHandler: CorsRequestHandler);
|
|
58
60
|
/**
|
|
59
61
|
* Binds the server. Resolves once the underlying server is listening; rejects on bind error
|
|
60
62
|
* (port in use, EACCES, etc).
|
|
@@ -71,6 +73,12 @@ export declare class KernelHttpServer implements RuntimeServerInterface {
|
|
|
71
73
|
stop(options?: {
|
|
72
74
|
drainTimeoutMs?: number;
|
|
73
75
|
}): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* The address the underlying server is bound to, or `null` when not listening. For a TCP socket
|
|
78
|
+
* this is `{address, family, port}` — useful when binding to port `0` (ephemeral port) and the
|
|
79
|
+
* caller needs to discover the assigned port afterwards.
|
|
80
|
+
*/
|
|
81
|
+
getAddress(): import("net").AddressInfo | string | null;
|
|
74
82
|
/**
|
|
75
83
|
* Reads the body, builds a Pristine `Request`, dispatches via the kernel, writes the
|
|
76
84
|
* resulting `Response`. All errors are caught and turned into a 500 response so the
|
|
@@ -97,6 +105,10 @@ export declare class KernelHttpServer implements RuntimeServerInterface {
|
|
|
97
105
|
* Writes a Pristine `Response` (or any object — handlers that return raw JSON serialize as-is)
|
|
98
106
|
* back to the underlying `ServerResponse`. Default status is 200, default content-type is
|
|
99
107
|
* `application/json` when the body is non-string.
|
|
108
|
+
*
|
|
109
|
+
* `corsHeaders` (empty when CORS is off or the Origin isn't allow-listed) is applied AFTER the
|
|
110
|
+
* response's own headers so the `Access-Control-*` set is authoritative and a handler can't
|
|
111
|
+
* accidentally clobber it.
|
|
100
112
|
* @private
|
|
101
113
|
*/
|
|
102
114
|
private writeResponse;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pristine-ts/http",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"module": "dist/lib/esm/http.module.js",
|
|
6
6
|
"main": "dist/lib/cjs/http.module.js",
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@pristine-ts/class-validator": "^2.0.4",
|
|
19
|
-
"@pristine-ts/cli": "^4.0.
|
|
20
|
-
"@pristine-ts/common": "^4.0.
|
|
21
|
-
"@pristine-ts/configuration": "^4.0.
|
|
22
|
-
"@pristine-ts/core": "^4.0.
|
|
23
|
-
"@pristine-ts/data-mapping": "^4.0.
|
|
24
|
-
"@pristine-ts/logging": "^4.0.
|
|
25
|
-
"@pristine-ts/observability": "^4.0.
|
|
26
|
-
"@pristine-ts/validation": "^4.0.
|
|
19
|
+
"@pristine-ts/cli": "^4.0.4",
|
|
20
|
+
"@pristine-ts/common": "^4.0.4",
|
|
21
|
+
"@pristine-ts/configuration": "^4.0.4",
|
|
22
|
+
"@pristine-ts/core": "^4.0.4",
|
|
23
|
+
"@pristine-ts/data-mapping": "^4.0.4",
|
|
24
|
+
"@pristine-ts/logging": "^4.0.4",
|
|
25
|
+
"@pristine-ts/observability": "^4.0.4",
|
|
26
|
+
"@pristine-ts/validation": "^4.0.4",
|
|
27
27
|
"tsyringe": "^4.8.0"
|
|
28
28
|
},
|
|
29
29
|
"author": "",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"src/*.{js,ts}"
|
|
66
66
|
]
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "64a2cc3f0c6e4a3d83b682151bc10def4c7c6cc1",
|
|
69
69
|
"repository": {
|
|
70
70
|
"type": "git",
|
|
71
71
|
"url": "https://github.com/magieno/pristine-ts.git",
|