@stonyx/rest-server 0.2.1-alpha.24 → 0.2.1-alpha.25
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/README.md +55 -13
- package/config/environment.js +27 -11
- package/dist/request.d.ts.map +1 -1
- package/dist/request.js +22 -6
- package/dist/request.js.map +1 -1
- package/dist/route-matching.d.ts +3 -1
- package/dist/route-matching.d.ts.map +1 -1
- package/dist/route-matching.js +11 -1
- package/dist/route-matching.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ Configuration is read from `stonyx/config` under `restServer`:
|
|
|
81
81
|
| `enableHealthCheck` | **Boolean** | `true` | Register `GET /health` endpoint (disable via `REST_HEALTH_CHECK_DISABLE=true`) |
|
|
82
82
|
| `caseSensitiveRoutes` | **Boolean** | `true` | Match route paths case-sensitively. Disable via `REST_CASE_SENSITIVE_ROUTES=false`. See [Route Matching Strictness](#route-matching-strictness) — **disabling this re-opens a security hole**. |
|
|
83
83
|
| `strictRoutes` | **Boolean** | `true` | Match route paths strictly, so a trailing slash does not match a route registered without one. Disable via `REST_STRICT_ROUTES=false`. See [Route Matching Strictness](#route-matching-strictness) — **disabling this re-opens a security hole**, and note `GET /health/` now 404s. |
|
|
84
|
-
| `canonicalRoutes` | **Boolean** | `true` | Reject a request whose raw target is not the canonical path express matched, before your `auth` hook runs. Disable via `REST_CANONICAL_ROUTES=false`. See [Route Matching Strictness](#route-matching-strictness) — **disabling this re-opens a security hole**, and note `GET /route/` at a mount root and
|
|
84
|
+
| `canonicalRoutes` | **Boolean** | `true` | Reject a request whose raw target is not the canonical path express matched, before your `auth` hook runs. Disable via `REST_CANONICAL_ROUTES=false`. See [Route Matching Strictness](#route-matching-strictness) — **disabling this re-opens a security hole**, and note `GET /route/` at a mount root and every absolute-form request target now 404 on the routes this module registers (see the scope limit under [Upgrading](#upgrading-behaviour-changes)). |
|
|
85
85
|
| `trustProxy` | **Boolean** | `false` | Trust reverse proxy headers (e.g. `X-Forwarded-Proto`). Enable via `REST_TRUST_PROXY=true` when running behind a load balancer such as AWS ALB/ELB to ensure correct protocol detection. |
|
|
86
86
|
| `statusMap` | **Object** | `{}` | Optional mapping of HTTP status codes to custom messages |
|
|
87
87
|
|
|
@@ -165,26 +165,45 @@ against `req.originalUrl` — the field express does not normalize:
|
|
|
165
165
|
GET /admin 401 401 (hook fires, request blocked)
|
|
166
166
|
GET /admin/ 200 404 (hook never fired; now a miss)
|
|
167
167
|
GET http://host/admin 200 404 (absolute-form; hook never fired)
|
|
168
|
-
GET http://host/admin/settings 200 404 (absolute-form
|
|
168
|
+
GET http://host/admin/settings 200 404 (absolute-form; every route from a request class)
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
The second vector is the one to check first. [RFC 9112
|
|
172
172
|
§3.2.2](https://www.rfc-editor.org/rfc/rfc9112#section-3.2.2) permits an
|
|
173
173
|
**absolute-form** request target, express routes it, and it hands your hook the
|
|
174
174
|
whole URI — `req.originalUrl === "http://host/admin"`. Unlike the mount-root
|
|
175
|
-
slash, that affects **every route**, not one edge.
|
|
175
|
+
slash, that affects **every route mounted from a request class**, not one edge.
|
|
176
|
+
The qualifier is a registration-site limit, not a special case for one URL: the
|
|
177
|
+
check runs inside the handlers this module registers, so anything you register
|
|
178
|
+
directly on `RestServer.instance.api` is outside it. In this repo that is
|
|
179
|
+
`/health` alone, and `GET http://host/health` still returns 200 — measured.
|
|
176
180
|
|
|
177
181
|
The target is compared **raw**. It is not parsed, normalized or resolved first:
|
|
178
182
|
normalizing it would launder exactly the string your hook is exposed to and
|
|
179
183
|
re-open the absolute-form vector by construction. Only the query string is
|
|
180
184
|
removed before comparison, so `GET /admin?x=1` still reaches your hook — **strip
|
|
181
185
|
the query yourself** if your hook compares `req.originalUrl` against a fixed
|
|
182
|
-
path, or it will not match
|
|
183
|
-
|
|
186
|
+
path, or it will not match (see [Consumer
|
|
187
|
+
Contracts](#consumer-contracts)). Rejections are indistinguishable from a
|
|
188
|
+
genuine miss by design; see [Upgrading](#upgrading-behaviour-changes).
|
|
184
189
|
|
|
185
190
|
Routes registered *with* a literal trailing slash are unaffected — their
|
|
186
|
-
canonical target carries the slash. So are
|
|
187
|
-
|
|
191
|
+
canonical target carries the slash. So are index-mounted route classes and
|
|
192
|
+
query strings on canonical paths.
|
|
193
|
+
|
|
194
|
+
**Param routes: "unaffected" means "no regression", not "no residual".**
|
|
195
|
+
`/resource/:id` keeps matching exactly as it did. But percent-encoding is not
|
|
196
|
+
normalized on either side of the comparison — express decodes only
|
|
197
|
+
`req.params`, not `req.path` and not `req.originalUrl` — so `target` and
|
|
198
|
+
`canonical` are *both* the encoded string and this check passes the request
|
|
199
|
+
through by construction. Measured on a hook that authorizes on
|
|
200
|
+
`req.originalUrl` with the query correctly stripped, on a `/:id` route:
|
|
201
|
+
`GET /enc/secret` → 401, `GET /enc/%73ecret` → **200 with the guarded handler
|
|
202
|
+
running unauthenticated**, identically before and after this change. It is a
|
|
203
|
+
residual, not something `canonicalRoutes` introduced, and it is **wider** than
|
|
204
|
+
the two vectors above: `%73` defeats a `req.path` hook as well. Tracked as
|
|
205
|
+
[#56](https://github.com/abofs/stonyx-rest-server/issues/56) — until it ships,
|
|
206
|
+
do not read a param-segment route class as covered on this axis.
|
|
188
207
|
|
|
189
208
|
#### What this does not do
|
|
190
209
|
|
|
@@ -219,12 +238,19 @@ All three controls change which requests match, so all three are
|
|
|
219
238
|
consumer-visible.
|
|
220
239
|
|
|
221
240
|
**Clients or forward proxies sending absolute-form request targets now get 404
|
|
222
|
-
on every route.** `GET http://host/admin HTTP/1.1`
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
241
|
+
on every route mounted from a request class.** `GET http://host/admin HTTP/1.1`
|
|
242
|
+
is a legal request target
|
|
243
|
+
([RFC 9112 §3.2.2](https://www.rfc-editor.org/rfc/rfc9112#section-3.2.2)), and
|
|
244
|
+
express used to route it. It is now rejected on every route this module
|
|
245
|
+
registers — for a client that emits it, this is a total outage, not a partial
|
|
246
|
+
one, and it is the largest blast radius in this change. The one carve-out is a
|
|
247
|
+
**registration site**, not a route: the check lives in the handlers mounted from
|
|
248
|
+
your request classes, so anything registered directly on
|
|
249
|
+
`RestServer.instance.api` never reaches it. `GET /health` is the only such route
|
|
250
|
+
in this repo, and `GET http://host/health` still returns 200 — so do not use it
|
|
251
|
+
to confirm the new rejection is live, and do not assume an authorized route you
|
|
252
|
+
registered on `api` yourself is covered. Reverse proxies in normal use (nginx,
|
|
253
|
+
HAProxy, AWS ALB) send origin-form and are unaffected; **forward** proxies and
|
|
228
254
|
hand-rolled HTTP clients are the exposure. Remediation is
|
|
229
255
|
`REST_CANONICAL_ROUTES=false`, or fix the client.
|
|
230
256
|
|
|
@@ -295,6 +321,22 @@ again. They exist as one-line remediations for an existing deployment, not as a
|
|
|
295
321
|
configuration to run on. Set the flag to restore service, then fix the client
|
|
296
322
|
and remove the flag.
|
|
297
323
|
|
|
324
|
+
### Consumer Contracts
|
|
325
|
+
|
|
326
|
+
Three things this module deliberately does **not** do for you. Each is a state
|
|
327
|
+
the framework permits, only your own discipline prevents, and that produces **no
|
|
328
|
+
error and no log** when that discipline lapses — so they are collected here
|
|
329
|
+
rather than left implied by the sections above.
|
|
330
|
+
|
|
331
|
+
| you must | because | symptom if you don't |
|
|
332
|
+
|---|---|---|
|
|
333
|
+
| **Strip the query string** before comparing `req.originalUrl` to a fixed path in an `auth` hook | `canonicalRoutes` compares the query-*stripped* target — a query string is a legitimately variable part of a request target, and rejecting on it would 404 every `?`-carrying request | `GET /admin?x=1` reaches your guarded handler **unauthenticated**, 200, no error, no log. Measured identical before and after `canonicalRoutes` |
|
|
334
|
+
| **Compare param values with the same decoding and casing you look them up with** | express decodes only `req.params`; `req.path` and `req.originalUrl` both stay percent-encoded, and param *values* are never case-normalized | `GET /orders/%73ecret` runs the handler with `id === "secret"` while your hook compared `%73ecret` and did not match — unauthenticated 200. Tracked as [#56](https://github.com/abofs/stonyx-rest-server/issues/56) |
|
|
335
|
+
| **Return `undefined` from `auth()` to mean "authorized"** — never `0` | any integer return is sent as the HTTP status | returning `0` sends a `0` status rather than allowing the request |
|
|
336
|
+
|
|
337
|
+
`test/sample/requests/admin.ts` in this repo is the worked example of a
|
|
338
|
+
correctly-written `originalUrl` hook for the first row.
|
|
339
|
+
|
|
298
340
|
### Running Behind a Load Balancer
|
|
299
341
|
|
|
300
342
|
When your application runs behind a reverse proxy or load balancer (e.g. AWS ALB/ELB), the load balancer terminates SSL and forwards requests to your server over HTTP internally. This means Express sees `http` as the protocol even though the original client request used `https`.
|
package/config/environment.js
CHANGED
|
@@ -23,13 +23,20 @@ const config = {
|
|
|
23
23
|
// stubs `caseSensitiveRoutes` to `undefined` and src/route-matching.ts reads
|
|
24
24
|
// `!== false`, so AC6 guards the source's read and not this default.
|
|
25
25
|
// Measured: pin `caseSensitiveRoutes: true` in test/config/environment.ts AND
|
|
26
|
-
// invert this line, and the suite reports
|
|
26
|
+
// invert this line, and the suite reports 34 pass / 0 fail. A naive pin makes
|
|
27
27
|
// an insecure published default completely invisible to a green suite --
|
|
28
28
|
// quieter and weaker, which is the outcome pinning was supposed to prevent.
|
|
29
|
+
// Inverting this line ALONE, unpinned, reports 31 pass / 3 fail (#47's AC3,
|
|
30
|
+
// AC4 and AC5; AC6 green).
|
|
29
31
|
//
|
|
30
32
|
// The cost of leaving it unpinned is that the suite is ambient-sensitive here
|
|
31
|
-
// (`REST_CASE_SENSITIVE_ROUTES=false pnpm test` =>
|
|
32
|
-
// fails LOUDLY, so there is no false green.
|
|
33
|
+
// (`REST_CASE_SENSITIVE_ROUTES=false pnpm test` => 31 pass / 3 fail), but it
|
|
34
|
+
// fails LOUDLY, so there is no false green.
|
|
35
|
+
//
|
|
36
|
+
// Every count in this block was re-measured against the 34-test suite at
|
|
37
|
+
// #54's head. PASS totals here move whenever a test is ADDED anywhere in the
|
|
38
|
+
// repo -- the FAIL counts are the load-bearing half. Re-measure, do not
|
|
39
|
+
// adjust by arithmetic, when this block next looks stale. Closing #43 for this key needs
|
|
33
40
|
// the subprocess-based env isolation this repo does not yet have; any fix
|
|
34
41
|
// must keep a live assertion on this default.
|
|
35
42
|
caseSensitiveRoutes: REST_CASE_SENSITIVE_ROUTES !== 'false',
|
|
@@ -47,19 +54,21 @@ const config = {
|
|
|
47
54
|
// DELIBERATELY NOT PINNED in test/config/environment.ts -- do not "fix" this
|
|
48
55
|
// as part of abofs/stonyx-rest-server#43. Same trap as the key above, and now
|
|
49
56
|
// measured for both: pin `strictRoutes: true` in test/config/environment.ts
|
|
50
|
-
// AND invert this line to `=== 'true'`, and the suite reports
|
|
57
|
+
// AND invert this line to `=== 'true'`, and the suite reports 34 pass /
|
|
51
58
|
// 0 fail. A naive pin makes an insecure published default completely
|
|
52
59
|
// invisible to a green suite.
|
|
53
60
|
//
|
|
54
|
-
// Unpinned, inverting this line alone turns #50's AC1 and AC2 red (
|
|
61
|
+
// Unpinned, inverting this line alone turns #50's AC1 and AC2 red (32/2).
|
|
55
62
|
// AC3 stays GREEN under that mutation, because AC3 sets `strictRoutes` on the
|
|
56
63
|
// config object directly and so guards src/route-matching.ts's READ rather
|
|
57
64
|
// than this default -- the two assertions cover different halves and neither
|
|
58
65
|
// subsumes the other.
|
|
59
66
|
//
|
|
60
67
|
// The cost is that the suite is ambient-sensitive here
|
|
61
|
-
// (`REST_STRICT_ROUTES=false pnpm test` =>
|
|
62
|
-
// LOUDLY, so there is no false green.
|
|
68
|
+
// (`REST_STRICT_ROUTES=false pnpm test` => 32 pass / 2 fail), but it fails
|
|
69
|
+
// LOUDLY, so there is no false green. All counts in this block re-measured
|
|
70
|
+
// against the 34-test suite at #54's head; the FAIL count is the load-bearing
|
|
71
|
+
// half, since PASS totals move whenever a test is added anywhere. Closing #43 for either key needs
|
|
63
72
|
// subprocess-based env isolation this repo does not have; any fix must keep a
|
|
64
73
|
// live assertion on this default.
|
|
65
74
|
strictRoutes: REST_STRICT_ROUTES !== 'false',
|
|
@@ -75,8 +84,13 @@ const config = {
|
|
|
75
84
|
// 1. `GET /route/` at a mounted route class's ROOT now returns 404.
|
|
76
85
|
// 2. Clients or forward proxies emitting an ABSOLUTE-FORM request target
|
|
77
86
|
// (`GET http://host/admin HTTP/1.1`, RFC 9112 3.2.2) now receive 404 on
|
|
78
|
-
//
|
|
79
|
-
//
|
|
87
|
+
// every route registered through Request.registerCalls(). That is a
|
|
88
|
+
// REGISTRATION-SITE limit, not a carve-out for one URL: /health is
|
|
89
|
+
// registered directly on the parent app (src/main.ts) and still answers
|
|
90
|
+
// 200 to an absolute-form target -- and so would any route a consumer
|
|
91
|
+
// registers on the public RestServer.instance.api itself. This is the
|
|
92
|
+
// larger blast radius: for such a client it is a total outage, not a
|
|
93
|
+
// partial one. Reverse proxies in normal use
|
|
80
94
|
// (nginx, HAProxy, ALB) send origin-form and are unaffected.
|
|
81
95
|
// This module emits no request log, so both look like a dropped route.
|
|
82
96
|
//
|
|
@@ -105,8 +119,10 @@ const config = {
|
|
|
105
119
|
// guards src/route-matching.ts's READ rather than this default -- the two
|
|
106
120
|
// assertions cover different halves and neither subsumes the other.
|
|
107
121
|
// Conversely, weakening the READ to `=== true` reports 33 pass / 1 fail with
|
|
108
|
-
// AC2 as the only failure and AC1 fully green.
|
|
109
|
-
// #54 branch head.
|
|
122
|
+
// AC2 as the only failure and AC1 fully green. All four counts in this block
|
|
123
|
+
// were re-measured on the #54 branch head after the AC1.11/AC1.12 assertions
|
|
124
|
+
// were added; the suite is 34 tests, and the FAIL count is the load-bearing
|
|
125
|
+
// half.
|
|
110
126
|
//
|
|
111
127
|
// The cost is that the suite is ambient-sensitive here
|
|
112
128
|
// (`REST_CANONICAL_ROUTES=false pnpm test` => 32 pass / 2 fail), but it fails
|
package/dist/request.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,KAAK,OAAO,IAAI,cAAc,EAAE,KAAK,QAAQ,IAAI,eAAe,EAAqB,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAOrI,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACnD,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AACtG,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;AAC3F,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,EAAE,CAAC,CAAC,CAAC;AAE9F,MAAM,CAAC,OAAO,OAAO,OAAO;IAC1B,MAAM,CAAC,SAAS,SAAmB;IAEnC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY;IASlD,MAAM,CAAC,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAWrE,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAG,aAAa,CAAC;IACjB,IAAI,CAAC,EAAE,WAAW,CAAC;;IA4B3B,aAAa,IAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,KAAK,OAAO,IAAI,cAAc,EAAE,KAAK,QAAQ,IAAI,eAAe,EAAqB,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAOrI,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACnD,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AACtG,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;AAC3F,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,EAAE,CAAC,CAAC,CAAC;AAE9F,MAAM,CAAC,OAAO,OAAO,OAAO;IAC1B,MAAM,CAAC,SAAS,SAAmB;IAEnC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY;IASlD,MAAM,CAAC,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAWrE,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAG,aAAa,CAAC;IACjB,IAAI,CAAC,EAAE,WAAW,CAAC;;IA4B3B,aAAa,IAAI,IAAI;CA4FtB"}
|
package/dist/request.js
CHANGED
|
@@ -63,13 +63,24 @@ export default class Request {
|
|
|
63
63
|
// bypasses against hooks that authorize on `req.originalUrl`: the
|
|
64
64
|
// mount-root trailing slash and the absolute-form request target.
|
|
65
65
|
//
|
|
66
|
-
//
|
|
67
|
-
// red
|
|
66
|
+
// Three properties of this line are load-bearing. Each is named with
|
|
67
|
+
// the ONE assertion that turns red when it is removed -- assertion
|
|
68
|
+
// numbers in `test/integration/rest-server-test.ts` AC1, so the claim
|
|
69
|
+
// is checkable rather than a promise that coverage exists somewhere:
|
|
68
70
|
//
|
|
69
|
-
// 1. It runs
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
71
|
+
// 1. It runs OUTSIDE `if (this.auth)`. Gating it on the hook would
|
|
72
|
+
// leave `GET /public/` at 200 and make a security control depend
|
|
73
|
+
// on an unrelated consumer choice. Killed by AC1.6, which probes
|
|
74
|
+
// a route class with no hook.
|
|
75
|
+
// 2. It runs BEFORE that block, not merely outside it. This is a
|
|
76
|
+
// SEPARATE property from 1 and needs its own probe: AC1.6 is on
|
|
77
|
+
// a hookless class, so it stays green if this line is merely
|
|
78
|
+
// moved BELOW the block. Measured with it moved: the suite was
|
|
79
|
+
// 34 pass / 0 fail while `GET http://HOST/private/failure`
|
|
80
|
+
// answered 505 -- the consumer's hook status, which is the same
|
|
81
|
+
// oracle class as 3, and the hook itself ran on a request this
|
|
82
|
+
// module was about to reject. Killed by AC1.11.
|
|
83
|
+
// 3. It rejects with `next('router')`, NOT sendStatusResponse() or
|
|
73
84
|
// res.sendStatus(404). Measured: sendStatus returns
|
|
74
85
|
// `text/plain "Not Found"` while a genuine miss returns
|
|
75
86
|
// `text/html <pre>Cannot GET ...</pre>` -- a working ORACLE
|
|
@@ -79,6 +90,11 @@ export default class Request {
|
|
|
79
90
|
// Content-Type, same CSP header). Routing it through
|
|
80
91
|
// sendStatusResponse() would additionally re-introduce the
|
|
81
92
|
// oracle for any consumer who sets a 404 in `statusMap`.
|
|
93
|
+
// Killed by AC1.5.
|
|
94
|
+
//
|
|
95
|
+
// Properties 1 and 2 were previously stated here as a single item
|
|
96
|
+
// asserted to have "its own red-able assertion". It did not: only
|
|
97
|
+
// half of it was covered. Do not re-merge them.
|
|
82
98
|
if (shouldRejectTarget(req))
|
|
83
99
|
return next('router');
|
|
84
100
|
// Run auth after route matching so request.params is populated
|
package/dist/request.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAO,OAA8G,MAAM,SAAS,CAAC;AACrI,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,kBAAkB,EAAE,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE7E,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAOnE,MAAM,CAAC,OAAO,OAAO,OAAO;IAC1B,MAAM,CAAC,SAAS,GAAG,eAAe,CAAC;IAEnC,MAAM,CAAC,QAAQ,CAAC,GAAmB;QACjC,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;QAC9B,MAAM,MAAM,GAAG,GAAyC,CAAC;QACzD,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC,SAAS,CAAiB,CAAC;QAE9E,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;QACvB,OAAO,MAAM,CAAC,SAAS,CAAiB,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,kBAAkB,CAAC,GAAoB,EAAE,MAAc;QAC5D,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,EAAE,SAAS,IAAI,EAAE,CAAC;QACrD,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAExC,IAAI,OAAO,EAAE,CAAC;YACZ,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,eAAe,CAAU;IACzB,QAAQ,CAAiB;IAGzB;QACE,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAE5B,+DAA+D;QAC/D,uEAAuE;QACvE,2EAA2E;QAC3E,oEAAoE;QACpE,yEAAyE;QACzE,wEAAwE;QACxE,uEAAuE;QACvE,4DAA4D;QAC5D,EAAE;QACF,4EAA4E;QAC5E,yEAAyE;QACzE,uEAAuE;QACvE,2DAA2D;QAC3D,uEAAuE;QACvE,yEAAyE;QACzE,wEAAwE;QACxE,UAAU;QACV,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAExB,IAAI,CAAC,eAAe,GAAG,GAAG,CAAC;IAC7B,CAAC;IAED,aAAa;QACX,MAAM,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC;QACjC,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE,GAAG,OAAO,CAAC;QAEjD,KAAK,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzB,OAAO,CAAC,IAAI,CAAC,WAAW,MAAM,2CAA2C,CAAC,CAAC;gBAC3E,SAAS;YACX,CAAC;YAED,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACvD,eAAiK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,GAAmB,EAAE,GAAoB,EAAE,IAAkB,EAAE,EAAE;oBACxP,sEAAsE;oBACtE,oEAAoE;oBACpE,kEAAkE;oBAClE,kEAAkE;oBAClE,EAAE;oBACF,qEAAqE;oBACrE,
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"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"}
|
package/dist/route-matching.d.ts
CHANGED
|
@@ -122,7 +122,9 @@ export default function applyRouteMatching(api: Express): void;
|
|
|
122
122
|
* hazard here, so do not carry that constraint across.
|
|
123
123
|
*
|
|
124
124
|
* The caller must reject with `next('router')`, NOT `res.sendStatus(404)`, and
|
|
125
|
-
* must run this
|
|
125
|
+
* must run this BEFORE `this.auth` as well as outside `if (this.auth)` -- those
|
|
126
|
+
* are two separate properties with two separate assertions (AC1.11 and AC1.6);
|
|
127
|
+
* see src/request.ts.
|
|
126
128
|
*
|
|
127
129
|
* Guard polarity is `!== false`, matching both siblings, for the same measured
|
|
128
130
|
* reason: the secure value is the TRUTHY one, so a plain truthy check fails
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-matching.d.ts","sourceRoot":"","sources":["../src/route-matching.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,SAAS,CAAC;AAGlE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAG7D;AAED
|
|
1
|
+
{"version":3,"file":"route-matching.d.ts","sourceRoot":"","sources":["../src/route-matching.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,SAAS,CAAC;AAGlE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAG7D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAuB/D"}
|
package/dist/route-matching.js
CHANGED
|
@@ -127,7 +127,9 @@ export default function applyRouteMatching(api) {
|
|
|
127
127
|
* hazard here, so do not carry that constraint across.
|
|
128
128
|
*
|
|
129
129
|
* The caller must reject with `next('router')`, NOT `res.sendStatus(404)`, and
|
|
130
|
-
* must run this
|
|
130
|
+
* must run this BEFORE `this.auth` as well as outside `if (this.auth)` -- those
|
|
131
|
+
* are two separate properties with two separate assertions (AC1.11 and AC1.6);
|
|
132
|
+
* see src/request.ts.
|
|
131
133
|
*
|
|
132
134
|
* Guard polarity is `!== false`, matching both siblings, for the same measured
|
|
133
135
|
* reason: the secure value is the TRUTHY one, so a plain truthy check fails
|
|
@@ -155,6 +157,14 @@ export function shouldRejectTarget(req) {
|
|
|
155
157
|
const target = req.originalUrl.split('?')[0];
|
|
156
158
|
// At a mount root express reports `req.path === '/'` while the canonical
|
|
157
159
|
// target is the bare mount segment, so the two are not simply concatenated.
|
|
160
|
+
//
|
|
161
|
+
// `&& req.baseUrl` is load-bearing and is NOT a redundant truthiness guard.
|
|
162
|
+
// A route class named `index` mounts at '/' (src/main.ts `mountRoute()`),
|
|
163
|
+
// and that is the one mount shape where `req.baseUrl` is ''. Without the
|
|
164
|
+
// conjunct, `GET /` compares the raw target '/' against a canonical of '' and
|
|
165
|
+
// the APPLICATION ROOT is rejected. Measured before it had a guard: shipped
|
|
166
|
+
// `GET /` -> 200, conjunct dropped -> 404, suite 34 pass / 0 fail BOTH ways.
|
|
167
|
+
// Killed now by AC1.12, against `test/sample/requests/index.ts`.
|
|
158
168
|
const canonical = req.path === '/' && req.baseUrl ? req.baseUrl : req.baseUrl + req.path;
|
|
159
169
|
return target !== canonical;
|
|
160
170
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-matching.js","sourceRoot":"","sources":["../src/route-matching.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,eAAe,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,GAAY;IACrD,IAAI,MAAM,CAAC,UAAU,EAAE,mBAAmB,KAAK,KAAK;QAAE,GAAG,CAAC,GAAG,CAAC,wBAAwB,EAAE,IAAI,CAAC,CAAC;IAC9F,IAAI,MAAM,CAAC,UAAU,EAAE,YAAY,KAAK,KAAK;QAAE,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;AACjF,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"route-matching.js","sourceRoot":"","sources":["../src/route-matching.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,eAAe,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,GAAY;IACrD,IAAI,MAAM,CAAC,UAAU,EAAE,mBAAmB,KAAK,KAAK;QAAE,GAAG,CAAC,GAAG,CAAC,wBAAwB,EAAE,IAAI,CAAC,CAAC;IAC9F,IAAI,MAAM,CAAC,UAAU,EAAE,YAAY,KAAK,KAAK;QAAE,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;AACjF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAmB;IACpD,6EAA6E;IAC7E,4EAA4E;IAC5E,8BAA8B;IAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,EAAE,eAAe,KAAK,KAAK,CAAC;IAC9D,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAE5B,oEAAoE;IACpE,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7C,yEAAyE;IACzE,4EAA4E;IAC5E,EAAE;IACF,4EAA4E;IAC5E,0EAA0E;IAC1E,yEAAyE;IACzE,8EAA8E;IAC9E,4EAA4E;IAC5E,6EAA6E;IAC7E,iEAAiE;IACjE,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC;IAEzF,OAAO,MAAM,KAAK,SAAS,CAAC;AAC9B,CAAC"}
|