@c9up/transit 0.1.1 → 0.1.3
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/TransitProvider.d.ts +3 -0
- package/dist/TransitProvider.d.ts.map +1 -1
- package/dist/TransitProvider.js +17 -2
- package/dist/TransitProvider.js.map +1 -1
- package/dist/augmentations.d.ts +24 -0
- package/dist/augmentations.d.ts.map +1 -0
- package/dist/augmentations.js +18 -0
- package/dist/augmentations.js.map +1 -0
- package/dist/ber.d.ts +9 -0
- package/dist/ber.d.ts.map +1 -1
- package/dist/ber.js +16 -1
- package/dist/ber.js.map +1 -1
- package/dist/drivers/OidcDriver.js +1 -1
- package/dist/drivers/OidcDriver.js.map +1 -1
- package/dist/drivers/SamlDriver.d.ts.map +1 -1
- package/dist/drivers/SamlDriver.js +6 -1
- package/dist/drivers/SamlDriver.js.map +1 -1
- package/dist/dsig.d.ts.map +1 -1
- package/dist/dsig.js +17 -11
- package/dist/dsig.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +18 -2
- package/dist/jwt.js.map +1 -1
- package/dist/ldap.d.ts.map +1 -1
- package/dist/ldap.js +13 -1
- package/dist/ldap.js.map +1 -1
- package/dist/quasar.d.ts +7 -8
- package/dist/quasar.d.ts.map +1 -1
- package/dist/quasar.js +15 -35
- package/dist/quasar.js.map +1 -1
- package/dist/services/main.d.ts +10 -1
- package/dist/services/main.d.ts.map +1 -1
- package/dist/services/main.js +12 -1
- package/dist/services/main.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +19 -1
- package/dist/types.js.map +1 -1
- package/dist/vendor/nodeEnv.d.ts +5 -0
- package/dist/vendor/nodeEnv.d.ts.map +1 -0
- package/dist/{nodeEnv.js → vendor/nodeEnv.js} +13 -8
- package/dist/vendor/nodeEnv.js.map +1 -0
- package/dist/vendor/quasarConnection.d.ts +35 -0
- package/dist/vendor/quasarConnection.d.ts.map +1 -0
- package/dist/vendor/quasarConnection.js +62 -0
- package/dist/vendor/quasarConnection.js.map +1 -0
- package/dist/xml.d.ts +20 -1
- package/dist/xml.d.ts.map +1 -1
- package/dist/xml.js +43 -5
- package/dist/xml.js.map +1 -1
- package/package.json +9 -4
- package/src/TransitProvider.ts +20 -4
- package/src/augmentations.ts +28 -0
- package/src/ber.ts +19 -1
- package/src/drivers/OidcDriver.ts +2 -2
- package/src/drivers/SamlDriver.ts +6 -1
- package/src/dsig.ts +26 -15
- package/src/index.ts +2 -0
- package/src/jwt.ts +21 -3
- package/src/ldap.ts +17 -0
- package/src/quasar.ts +15 -52
- package/src/services/main.ts +13 -1
- package/src/types.ts +20 -1
- package/src/{nodeEnv.ts → vendor/nodeEnv.ts} +14 -8
- package/src/vendor/quasarConnection.ts +129 -0
- package/src/xml.ts +74 -5
- package/dist/nodeEnv.d.ts +0 -17
- package/dist/nodeEnv.d.ts.map +0 -1
- package/dist/nodeEnv.js.map +0 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Generated from scripts/vendor/quasarConnection.ts — do not edit.
|
|
2
|
+
//
|
|
3
|
+
// This package is published and built from its own repository, so the file
|
|
4
|
+
// has to exist here rather than be imported. `pnpm vendor:sync` rewrites it,
|
|
5
|
+
// and `pnpm vendor:check` fails if this copy has drifted from the original.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolving a Redis connection by name, from `@c9up/quasar`.
|
|
9
|
+
*
|
|
10
|
+
* No package here depends on quasar: it is an optional peer, and this module
|
|
11
|
+
* never imports it statically — the specifier is built at runtime so the
|
|
12
|
+
* TypeScript build stays free of it too. A host that never names a connection
|
|
13
|
+
* neither installs it nor pays for it.
|
|
14
|
+
*
|
|
15
|
+
* What varies between callers is passed in rather than copied: which commands
|
|
16
|
+
* they will issue, and what they are doing with them. Both end up in the
|
|
17
|
+
* message, because "the connection is missing lpop" is only useful next to
|
|
18
|
+
* "which the queue's atomic pop issues".
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** The slice of quasar's manager this needs: a connection, by name. */
|
|
22
|
+
interface ConnectionSource {
|
|
23
|
+
connection(name?: string): unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read a member without assuming how the object answers.
|
|
28
|
+
*
|
|
29
|
+
* Two shapes have to work, and they disagree about `in`. Quasar's accessor is
|
|
30
|
+
* a Proxy over an empty null-prototype object with only a `get` trap, so
|
|
31
|
+
* `"connection" in manager` is FALSE while reading it returns the function —
|
|
32
|
+
* gating on `in` rejected the real manager. And a module namespace under a
|
|
33
|
+
* test double RAISES for an export it does not have, rather than answering
|
|
34
|
+
* undefined, so a bare read fails on the mock. Reading through a catch is what
|
|
35
|
+
* satisfies both.
|
|
36
|
+
*/
|
|
37
|
+
function readMember(value: unknown, name: string): unknown {
|
|
38
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
39
|
+
try {
|
|
40
|
+
return Reflect.get(value, name);
|
|
41
|
+
} catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isConnectionSource(value: unknown): value is ConnectionSource {
|
|
47
|
+
return typeof readMember(value, "connection") === "function";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface QuasarConnectionRequest {
|
|
51
|
+
/** The package asking. It prefixes every message this can raise. */
|
|
52
|
+
pkg: string;
|
|
53
|
+
/**
|
|
54
|
+
* How this package builds an error.
|
|
55
|
+
*
|
|
56
|
+
* Not optional, and not `new Error` by default: a package with its own
|
|
57
|
+
* error type carries codes (`E_<PKG>_<REASON>`) that a caller catches on,
|
|
58
|
+
* and a shared helper throwing a bare Error would silently drop them. The
|
|
59
|
+
* two reasons are distinguished so the codes can be too.
|
|
60
|
+
*/
|
|
61
|
+
raise: (
|
|
62
|
+
reason: "quasar-missing" | "incomplete-connection",
|
|
63
|
+
message: string,
|
|
64
|
+
cause?: unknown,
|
|
65
|
+
) => Error;
|
|
66
|
+
/** Connection name, or `undefined` for quasar's default. */
|
|
67
|
+
name?: string;
|
|
68
|
+
/** The commands the caller will actually issue. */
|
|
69
|
+
required: readonly string[];
|
|
70
|
+
/** What the caller does with them, for the message that names a gap. */
|
|
71
|
+
what: string;
|
|
72
|
+
/**
|
|
73
|
+
* A second way out, when this package has one.
|
|
74
|
+
*
|
|
75
|
+
* Several of them accept a client object directly, so "pass a client
|
|
76
|
+
* instead of a connection name" is actionable — and it was in each
|
|
77
|
+
* package's own message before this was shared. Dropping it to unify the
|
|
78
|
+
* wording would have made the error strictly less useful.
|
|
79
|
+
*/
|
|
80
|
+
alternative?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The named connection, checked for the commands the caller needs BEFORE it is
|
|
85
|
+
* handed over — a connection missing one would otherwise fail on the first
|
|
86
|
+
* command, far from the line that asked for it.
|
|
87
|
+
*/
|
|
88
|
+
export async function quasarConnection<T>(
|
|
89
|
+
request: QuasarConnectionRequest,
|
|
90
|
+
): Promise<T> {
|
|
91
|
+
const { pkg, name, required, what, raise, alternative } = request;
|
|
92
|
+
const specifier = "@c9up/quasar/services/main";
|
|
93
|
+
let loaded: unknown;
|
|
94
|
+
try {
|
|
95
|
+
loaded = await import(/* @vite-ignore */ specifier);
|
|
96
|
+
} catch (cause) {
|
|
97
|
+
throw raise(
|
|
98
|
+
"quasar-missing",
|
|
99
|
+
`[${pkg}] ${what} asks for the quasar connection "${name ?? "default"}", but @c9up/quasar is not installed.\n` +
|
|
100
|
+
` pnpm add @c9up/quasar${alternative ? `, ${alternative}` : ""}`,
|
|
101
|
+
cause,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const manager = isConnectionSource(loaded)
|
|
106
|
+
? loaded
|
|
107
|
+
: Reflect.get(Object(loaded), "default");
|
|
108
|
+
if (!isConnectionSource(manager)) {
|
|
109
|
+
throw raise(
|
|
110
|
+
"quasar-missing",
|
|
111
|
+
`[${pkg}] @c9up/quasar/services/main did not expose a connection() manager`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const connection = manager.connection(name);
|
|
116
|
+
const missing = required.filter(
|
|
117
|
+
(command) => typeof readMember(connection, command) !== "function",
|
|
118
|
+
);
|
|
119
|
+
if (missing.length > 0) {
|
|
120
|
+
throw raise(
|
|
121
|
+
"incomplete-connection",
|
|
122
|
+
`[${pkg}] the quasar connection${name ? ` '${name}'` : ""} is missing ${missing.join(", ")}, which ${what} issues`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
// Load-bearing: every member of `required` has just been checked to be a
|
|
126
|
+
// function on this object. The caller names the type it needs because only
|
|
127
|
+
// the caller knows which commands it asked for.
|
|
128
|
+
return connection as T;
|
|
129
|
+
}
|
package/src/xml.ts
CHANGED
|
@@ -74,13 +74,48 @@ class XmlError extends Error {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* What a document is allowed to cost.
|
|
79
|
+
*
|
|
80
|
+
* A SAML response is parsed BEFORE its signature is checked — it has to be,
|
|
81
|
+
* since the signature is inside it — so every one of these bounds applies to
|
|
82
|
+
* bytes an unauthenticated caller chose. Without them a single POST could
|
|
83
|
+
* exhaust the heap, or nest deeply enough to overflow the stack in `#element`,
|
|
84
|
+
* which recurses once per level.
|
|
85
|
+
*/
|
|
86
|
+
export interface XmlLimits {
|
|
87
|
+
/** Characters in the document. Default 1 MiB — a large SAML response is tens of KiB. */
|
|
88
|
+
maxLength?: number;
|
|
89
|
+
/** Nesting levels. Default 100; the recursion is one frame per level. */
|
|
90
|
+
maxDepth?: number;
|
|
91
|
+
/** Elements, text runs, comments and instructions, together. Default 50 000. */
|
|
92
|
+
maxNodes?: number;
|
|
93
|
+
/** Attributes on one element. Default 256. */
|
|
94
|
+
maxAttributes?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const DEFAULT_LIMITS: Required<XmlLimits> = {
|
|
98
|
+
maxLength: 1024 * 1024,
|
|
99
|
+
maxDepth: 100,
|
|
100
|
+
maxNodes: 50_000,
|
|
101
|
+
maxAttributes: 256,
|
|
102
|
+
};
|
|
103
|
+
|
|
77
104
|
/** Parse a document and return its root element. */
|
|
78
|
-
export function parseXml(source: string): XmlElement {
|
|
105
|
+
export function parseXml(source: string, limits?: XmlLimits): XmlElement {
|
|
106
|
+
const resolved = { ...DEFAULT_LIMITS, ...limits };
|
|
107
|
+
// Checked before the copy below, not after: normalising duplicates the whole
|
|
108
|
+
// string, so a 300 MB body cost 600 MB before anything looked at its size.
|
|
109
|
+
if (source.length > resolved.maxLength) {
|
|
110
|
+
throw new XmlError(
|
|
111
|
+
`the document is ${source.length} characters, over the ${resolved.maxLength} allowed`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
79
114
|
// Line endings are normalised before parsing, as the canonicalization
|
|
80
115
|
// specification requires — otherwise the same document signed on one
|
|
81
116
|
// platform digests differently on another.
|
|
82
117
|
const input = source.replace(/\r\n?/g, "\n");
|
|
83
|
-
const parser = new Parser(input);
|
|
118
|
+
const parser = new Parser(input, resolved);
|
|
84
119
|
return parser.document();
|
|
85
120
|
}
|
|
86
121
|
|
|
@@ -151,7 +186,22 @@ export function textOf(element: XmlElement): string {
|
|
|
151
186
|
|
|
152
187
|
class Parser {
|
|
153
188
|
#at = 0;
|
|
154
|
-
|
|
189
|
+
/** Nodes built so far, across the whole document. */
|
|
190
|
+
#nodes = 0;
|
|
191
|
+
constructor(
|
|
192
|
+
private readonly source: string,
|
|
193
|
+
private readonly limits: Required<XmlLimits>,
|
|
194
|
+
) {}
|
|
195
|
+
|
|
196
|
+
/** Count one node, and refuse a document that keeps producing them. */
|
|
197
|
+
#node(): void {
|
|
198
|
+
this.#nodes += 1;
|
|
199
|
+
if (this.#nodes > this.limits.maxNodes) {
|
|
200
|
+
throw new XmlError(
|
|
201
|
+
`the document has more than ${this.limits.maxNodes} nodes`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
155
205
|
|
|
156
206
|
document(): XmlElement {
|
|
157
207
|
let root: XmlElement | undefined;
|
|
@@ -184,7 +234,17 @@ class Parser {
|
|
|
184
234
|
return root;
|
|
185
235
|
}
|
|
186
236
|
|
|
187
|
-
#element(parent: XmlElement | undefined): XmlElement {
|
|
237
|
+
#element(parent: XmlElement | undefined, depth = 0): XmlElement {
|
|
238
|
+
// Checked on the way IN: `#element` recurses once per level, so a
|
|
239
|
+
// document nested deeply enough overflowed the stack before any of this
|
|
240
|
+
// ran — and the stack it overflows is the one holding the signature check
|
|
241
|
+
// that has not happened yet.
|
|
242
|
+
if (depth > this.limits.maxDepth) {
|
|
243
|
+
throw new XmlError(
|
|
244
|
+
`the document nests deeper than ${this.limits.maxDepth}`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
this.#node();
|
|
188
248
|
this.#expect("<");
|
|
189
249
|
const qname = this.#name();
|
|
190
250
|
const element: XmlElement = {
|
|
@@ -210,6 +270,11 @@ class Parser {
|
|
|
210
270
|
this.#expect("=");
|
|
211
271
|
this.#skipSpace();
|
|
212
272
|
raw.push({ qname: name, value: this.#attributeValue() });
|
|
273
|
+
if (raw.length > this.limits.maxAttributes) {
|
|
274
|
+
throw new XmlError(
|
|
275
|
+
`element <${qname}> carries more than ${this.limits.maxAttributes} attributes`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
213
278
|
}
|
|
214
279
|
|
|
215
280
|
for (const { qname: name, value } of raw) {
|
|
@@ -289,6 +354,7 @@ class Parser {
|
|
|
289
354
|
if (this.#peek("<!--")) {
|
|
290
355
|
const end = this.source.indexOf("-->", this.#at);
|
|
291
356
|
if (end === -1) throw new XmlError("a comment is never closed");
|
|
357
|
+
this.#node();
|
|
292
358
|
element.children.push({
|
|
293
359
|
type: "comment",
|
|
294
360
|
value: this.source.slice(this.#at + 4, end),
|
|
@@ -299,6 +365,7 @@ class Parser {
|
|
|
299
365
|
if (this.#peek("<![CDATA[")) {
|
|
300
366
|
const end = this.source.indexOf("]]>", this.#at);
|
|
301
367
|
if (end === -1) throw new XmlError("a CDATA section is never closed");
|
|
368
|
+
this.#node();
|
|
302
369
|
element.children.push({
|
|
303
370
|
type: "text",
|
|
304
371
|
value: this.source.slice(this.#at + 9, end),
|
|
@@ -315,6 +382,7 @@ class Parser {
|
|
|
315
382
|
throw new XmlError("a processing instruction is never closed");
|
|
316
383
|
const body = this.source.slice(this.#at + 2, end);
|
|
317
384
|
const space = body.search(/\s/);
|
|
385
|
+
this.#node();
|
|
318
386
|
element.children.push({
|
|
319
387
|
type: "instruction",
|
|
320
388
|
target: space === -1 ? body : body.slice(0, space),
|
|
@@ -324,11 +392,12 @@ class Parser {
|
|
|
324
392
|
continue;
|
|
325
393
|
}
|
|
326
394
|
if (this.#peek("<")) {
|
|
327
|
-
element.children.push(this.#element(element));
|
|
395
|
+
element.children.push(this.#element(element, depth + 1));
|
|
328
396
|
continue;
|
|
329
397
|
}
|
|
330
398
|
const next = this.source.indexOf("<", this.#at);
|
|
331
399
|
const end = next === -1 ? this.source.length : next;
|
|
400
|
+
this.#node();
|
|
332
401
|
element.children.push({
|
|
333
402
|
type: "text",
|
|
334
403
|
value: resolveEntities(this.source.slice(this.#at, end)),
|
package/dist/nodeEnv.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reading `NODE_ENV`, with the aliases people actually set.
|
|
3
|
-
*
|
|
4
|
-
* `NODE_ENV=prod` is ordinary in a Dockerfile or a platform dashboard. Read
|
|
5
|
-
* verbatim it answers "not production" — and here that means the in-process
|
|
6
|
-
* SAML replay store is accepted on a production deployment, where it bounds
|
|
7
|
-
* replay within one replica and not beyond it.
|
|
8
|
-
*
|
|
9
|
-
* Duplicated rather than imported: transit depends on no other package in this
|
|
10
|
-
* workspace, and a security decision that only holds when an optional peer is
|
|
11
|
-
* installed is not a decision.
|
|
12
|
-
*/
|
|
13
|
-
/** The canonical name for whatever `NODE_ENV` holds. */
|
|
14
|
-
export declare function normalizeNodeEnv(value: string | undefined): string;
|
|
15
|
-
/** Whether this process is running in production, under any spelling. */
|
|
16
|
-
export declare function inProduction(): boolean;
|
|
17
|
-
//# sourceMappingURL=nodeEnv.d.ts.map
|
package/dist/nodeEnv.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nodeEnv.d.ts","sourceRoot":"","sources":["../src/nodeEnv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAOlE;AAED,yEAAyE;AACzE,wBAAgB,YAAY,IAAI,OAAO,CAEtC"}
|
package/dist/nodeEnv.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"nodeEnv.js","sourceRoot":"","sources":["../src/nodeEnv.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;AACnD,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AACzC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAEtC,wDAAwD;AACxD,MAAM,UAAU,gBAAgB,CAAC,KAAyB;IACzD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC1D,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAChC,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,aAAa,CAAC;IACjD,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,YAAY,CAAC;IACjD,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAC3C,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY;IAC3B,OAAO,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,YAAY,CAAC;AAChE,CAAC"}
|