@stonyx/rest-server 0.2.1-beta.83 → 0.2.1-beta.85
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 +97 -0
- package/config/environment.js +23 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +7 -0
- package/dist/main.js.map +1 -1
- package/dist/request.d.ts.map +1 -1
- package/dist/request.js +6 -0
- package/dist/request.js.map +1 -1
- package/dist/route-matching.d.ts +35 -0
- package/dist/route-matching.d.ts.map +1 -0
- package/dist/route-matching.js +38 -0
- package/dist/route-matching.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,9 +79,106 @@ Configuration is read from `stonyx/config` under `restServer`:
|
|
|
79
79
|
| `origin` | **String \| Array** | `'*'` | CORS origin(s) allowed |
|
|
80
80
|
| `methods` | **String** | `'GET,POST,PATCH,PUT,DELETE'` | CORS allowed methods |
|
|
81
81
|
| `enableHealthCheck` | **Boolean** | `true` | Register `GET /health` endpoint (disable via `REST_HEALTH_CHECK_DISABLE=true`) |
|
|
82
|
+
| `caseSensitiveRoutes` | **Boolean** | `true` | Match route paths case-sensitively. Disable via `REST_CASE_SENSITIVE_ROUTES=false`. See [Case-Sensitive Routing](#case-sensitive-routing) — **disabling this re-opens a security hole**. |
|
|
82
83
|
| `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. |
|
|
83
84
|
| `statusMap` | **Object** | `{}` | Optional mapping of HTTP status codes to custom messages |
|
|
84
85
|
|
|
86
|
+
### Case-Sensitive Routing
|
|
87
|
+
|
|
88
|
+
Routes match **case-sensitively by default**. `GET /users` reaches a route
|
|
89
|
+
mounted at `/users`; `GET /Users` does not reach that mount, and
|
|
90
|
+
`GET /users/Success` does not reach a `/success` handler registered inside it.
|
|
91
|
+
|
|
92
|
+
Read [What this does not do](#what-this-does-not-do) before you rely on that
|
|
93
|
+
sentence. Two things it does not say: "does not reach the handler" is not the
|
|
94
|
+
same as "404", and casing is only one of the two ways express matches more
|
|
95
|
+
loosely than the authorization predicates written against it.
|
|
96
|
+
|
|
97
|
+
This is deliberate and security-relevant. Express matches case-insensitively by
|
|
98
|
+
default, which means any authorization written against the request URL can be
|
|
99
|
+
walked past by changing the case of the request:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
GET /owners/angela -> 404 (correctly filtered)
|
|
103
|
+
GET /OwNeRs/angela -> 200 (full record)
|
|
104
|
+
DELETE /ANIMALS/22 -> 204 (record destroyed)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The consumer's predicate is stricter than the router that dispatched the
|
|
108
|
+
request, so the router hands the handler a request the predicate would have
|
|
109
|
+
rejected. Case-sensitive matching closes the **casing** half of that asymmetry:
|
|
110
|
+
the path a handler sees can only ever be the exact registered casing.
|
|
111
|
+
|
|
112
|
+
It does not close the asymmetry itself. Express exposes `case sensitive
|
|
113
|
+
routing` and `strict routing` as a pair of loose-by-default router settings and
|
|
114
|
+
this change sets only the first, so the identical bypass is still reachable by
|
|
115
|
+
appending a slash. Measured on this release against this repo's own fixture:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
GET /private/failure -> 505 (auth hook fires, request blocked)
|
|
119
|
+
GET /private/failure/ -> 200 (auth hook never fires, handler runs)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
That is the same defect, one character instead of a case shift — translated to
|
|
123
|
+
the example above, `DELETE /animals/22` is filtered and `DELETE /animals/22/`
|
|
124
|
+
destroys the record. It is tracked as
|
|
125
|
+
[#50](https://github.com/abofs/stonyx-rest-server/issues/50) and is not fixed
|
|
126
|
+
here; it is a second consumer-visible behaviour change that needs its own flag
|
|
127
|
+
and its own release note.
|
|
128
|
+
|
|
129
|
+
**So do not drop a URL-normalizing defence you already have on the strength of
|
|
130
|
+
this section.** If your authorization compares `req.path` or `req.originalUrl`,
|
|
131
|
+
keep whatever normalization you have until #50 ships.
|
|
132
|
+
|
|
133
|
+
#### What this does not do
|
|
134
|
+
|
|
135
|
+
**It does not normalize path *parameter values*.** If your `auth()` hook rejects
|
|
136
|
+
`params.id === 'restricted'`, then `GET /private/RESTRICTED` still reaches the
|
|
137
|
+
handler — the router matched the route correctly, and `restricted` and
|
|
138
|
+
`RESTRICTED` are different values. Record ids are legitimately case-sensitive,
|
|
139
|
+
so this is a comparison your application owns. Compare param values with the
|
|
140
|
+
same case-handling you use when you look them up.
|
|
141
|
+
|
|
142
|
+
**A sub-path that misses is not necessarily a 404.** If the route class also
|
|
143
|
+
registers a param route such as `/:id`, a mis-cased sub-path is absorbed by it
|
|
144
|
+
rather than rejected. `GET /private/FAILURE` misses `/failure` and is dispatched
|
|
145
|
+
to `/:id` with `id="FAILURE"` — a different handler, at 200, not a miss; this
|
|
146
|
+
repo's AC5 asserts exactly that. A class exposing `/orders/summary` alongside
|
|
147
|
+
`/orders/:id` will send `GET /orders/SUMMARY` into the `/:id` handler and its
|
|
148
|
+
database lookup. The param route's own `auth()` hook still runs, so this is an
|
|
149
|
+
expectation defect rather than a bypass — but plan for a reroute, not a 404.
|
|
150
|
+
|
|
151
|
+
**It does not cover trailing slashes.** See
|
|
152
|
+
[#50](https://github.com/abofs/stonyx-rest-server/issues/50) above.
|
|
153
|
+
|
|
154
|
+
**It does not redirect or rewrite** mixed-case requests to their canonical
|
|
155
|
+
casing. Whether `/Users` is a typo to forgive or an attack to reject is an
|
|
156
|
+
application policy decision, and encoding it here would mint another variant of
|
|
157
|
+
the bug above.
|
|
158
|
+
|
|
159
|
+
#### Opting out
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
REST_CASE_SENSITIVE_ROUTES=false
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**This restores the vulnerability described above** — any URL-based
|
|
166
|
+
authorization in your application becomes bypassable by changing case. It
|
|
167
|
+
exists as a one-line remediation for an existing deployment, not as a
|
|
168
|
+
configuration to run on.
|
|
169
|
+
|
|
170
|
+
You need it if clients call your endpoints with casing that does not match the
|
|
171
|
+
mount path. Mount paths come from filenames, so this is not hypothetical:
|
|
172
|
+
|
|
173
|
+
- with `camelCaseRoutes` truthy, `phone-number.ts` mounts at `/phoneNumber`, and
|
|
174
|
+
`GET /phonenumber` now returns 404
|
|
175
|
+
- with `camelCaseRoutes` falsy, filenames are used verbatim, so
|
|
176
|
+
`Users.ts` mounts at `/Users` and `GET /users` now returns 404
|
|
177
|
+
|
|
178
|
+
A request that stops matching returns express's default `404 Cannot GET /x` with
|
|
179
|
+
no log line and no stack, so it looks like a deploy that dropped a route. Set
|
|
180
|
+
the flag to restore service, then fix the client's casing and remove the flag.
|
|
181
|
+
|
|
85
182
|
### Running Behind a Load Balancer
|
|
86
183
|
|
|
87
184
|
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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const {
|
|
2
|
+
REST_CASE_SENSITIVE_ROUTES,
|
|
2
3
|
REST_CORS_ORIGIN,
|
|
3
4
|
REST_CORS_METHODS,
|
|
4
5
|
REST_HEALTH_CHECK_DISABLE,
|
|
@@ -8,6 +9,28 @@ const {
|
|
|
8
9
|
} = process.env;
|
|
9
10
|
|
|
10
11
|
const config = {
|
|
12
|
+
// Secure by default: routes match case-sensitively so a consumer's
|
|
13
|
+
// URL-based authorization cannot be walked past by changing case
|
|
14
|
+
// (abofs/stonyx-rest-server#47). Opt out with REST_CASE_SENSITIVE_ROUTES=false
|
|
15
|
+
// only as a temporary remediation for a client that relies on loose casing.
|
|
16
|
+
//
|
|
17
|
+
// DELIBERATELY NOT PINNED in test/config/environment.ts -- do not "fix" this
|
|
18
|
+
// as part of abofs/stonyx-rest-server#43. This line is the only thing the
|
|
19
|
+
// suite still checks about the SHIPPED default. Inverting it to
|
|
20
|
+
// `=== 'true'` turns AC3, AC4 and AC5 red; AC6 stays GREEN, because AC6
|
|
21
|
+
// stubs `caseSensitiveRoutes` to `undefined` and src/route-matching.ts reads
|
|
22
|
+
// `!== false`, so AC6 guards the source's read and not this default.
|
|
23
|
+
// Measured: pin `caseSensitiveRoutes: true` in test/config/environment.ts AND
|
|
24
|
+
// invert this line, and the suite reports 28 pass / 0 fail. A naive pin makes
|
|
25
|
+
// an insecure published default completely invisible to a green suite --
|
|
26
|
+
// quieter and weaker, which is the outcome pinning was supposed to prevent.
|
|
27
|
+
//
|
|
28
|
+
// The cost of leaving it unpinned is that the suite is ambient-sensitive here
|
|
29
|
+
// (`REST_CASE_SENSITIVE_ROUTES=false pnpm test` => 25 pass / 3 fail), but it
|
|
30
|
+
// fails LOUDLY, so there is no false green. Closing #43 for this key needs
|
|
31
|
+
// the subprocess-based env isolation this repo does not yet have; any fix
|
|
32
|
+
// must keep a live assertion on this default.
|
|
33
|
+
caseSensitiveRoutes: REST_CASE_SENSITIVE_ROUTES !== 'false',
|
|
11
34
|
enableHealthCheck: REST_HEALTH_CHECK_DISABLE !== 'true',
|
|
12
35
|
origin: REST_CORS_ORIGIN ?? '*',
|
|
13
36
|
methods: REST_CORS_METHODS ?? 'GET,POST,PATCH,PUT,DELETE',
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAiBA,OAAgB,EAAE,KAAK,OAAO,EAAoE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAiBA,OAAgB,EAAE,KAAK,OAAO,EAAoE,MAAM,SAAS,CAAC;AAKlH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,CAAC,OAAO,OAAO,UAAU;IAC7B,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC;IAE5B,GAAG,EAAG,OAAO,CAAC;IACd,MAAM,EAAG,MAAM,CAAC;;IAgBhB,MAAM,CAAC,KAAK,IAAI,IAAI;IAQd,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAerB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAelC,qBAAqB,IAAI,IAAI;IAW7B,UAAU,CAAC,iBAAiB,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;CAarG"}
|
package/dist/main.js
CHANGED
|
@@ -18,6 +18,7 @@ import express from 'express';
|
|
|
18
18
|
import config from 'stonyx/config';
|
|
19
19
|
import log from 'stonyx/log';
|
|
20
20
|
import { forEachFileImport } from '@stonyx/utils/file';
|
|
21
|
+
import applyRouteMatching from './route-matching.js';
|
|
21
22
|
export { default as Request } from './request.js';
|
|
22
23
|
export default class RestServer {
|
|
23
24
|
static instance;
|
|
@@ -28,6 +29,12 @@ export default class RestServer {
|
|
|
28
29
|
return RestServer.instance;
|
|
29
30
|
RestServer.instance = this;
|
|
30
31
|
this.api = express();
|
|
32
|
+
// Closes the mount segment (/PUBLIC/...) for abofs/stonyx-rest-server#47.
|
|
33
|
+
// Must stay in the constructor: the router is materialized lazily on first
|
|
34
|
+
// route registration, so applying this after setupRouter() is silently
|
|
35
|
+
// ineffective. The matching call in Request's constructor is what closes
|
|
36
|
+
// sub-paths -- see src/route-matching.ts for why both are required.
|
|
37
|
+
applyRouteMatching(this.api);
|
|
31
38
|
}
|
|
32
39
|
static close() {
|
|
33
40
|
if (!RestServer.instance)
|
package/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,OAA2F,MAAM,SAAS,CAAC;AAClH,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,GAAG,MAAM,YAAY,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
|
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,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
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,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAOlH,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;;IAe3B,aAAa,IAAI,IAAI;CAqDtB"}
|
package/dist/request.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import config from 'stonyx/config';
|
|
3
3
|
import { makeArray } from '@stonyx/utils/object';
|
|
4
|
+
import applyRouteMatching from './route-matching.js';
|
|
4
5
|
const METHODS = new Set(['get', 'post', 'put', 'delete', 'patch']);
|
|
5
6
|
export default class Request {
|
|
6
7
|
static stateProp = '__stonyxState';
|
|
@@ -27,6 +28,11 @@ export default class Request {
|
|
|
27
28
|
constructor() {
|
|
28
29
|
const api = express();
|
|
29
30
|
api.disable('x-powered-by');
|
|
31
|
+
// Closes sub-paths (/public/SUCCESS) for abofs/stonyx-rest-server#47.
|
|
32
|
+
// Must stay in the constructor: registerCalls() materializes this router,
|
|
33
|
+
// and a set applied afterwards has no effect. The parent app's setting
|
|
34
|
+
// does not reach here -- see src/route-matching.ts.
|
|
35
|
+
applyRouteMatching(api);
|
|
30
36
|
this.expressInstance = api;
|
|
31
37
|
}
|
|
32
38
|
registerCalls() {
|
package/dist/request.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAO,OAA2F,MAAM,SAAS,CAAC;AAClH,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Express } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Applies this module's route-matching settings to an express app.
|
|
4
|
+
*
|
|
5
|
+
* Called from BOTH express construction sites (abofs/stonyx-rest-server#47):
|
|
6
|
+
* `RestServer`'s constructor closes the mount segment (`/PUBLIC/...`), and
|
|
7
|
+
* `Request`'s constructor closes sub-paths (`/public/SUCCESS`). Neither alone
|
|
8
|
+
* is sufficient -- settings are inherited on mount, but `mountRoute()` calls
|
|
9
|
+
* `registerCalls()` before `api.use()`, so each child router is already built
|
|
10
|
+
* by the time the parent's setting could reach it.
|
|
11
|
+
*
|
|
12
|
+
* Both callers invoke this from a constructor, and must keep doing so: express
|
|
13
|
+
* materializes a router lazily on first route registration, and a setting
|
|
14
|
+
* applied afterwards is silently ineffective -- no throw, no warning.
|
|
15
|
+
*
|
|
16
|
+
* The guard is `!== false`, not a plain truthy check, and that polarity is
|
|
17
|
+
* load-bearing. `trustProxy` and `enableHealthCheck` default to the falsy
|
|
18
|
+
* direction, so a missing key fails safe for them. This flag defaults to the
|
|
19
|
+
* truthy direction, so `if (config.restServer?.caseSensitiveRoutes)` would
|
|
20
|
+
* silently fail OPEN for a consumer whose shipped config predates the key.
|
|
21
|
+
*
|
|
22
|
+
* It lives here, in one place, rather than being written out at each call
|
|
23
|
+
* site, so that a single test can anchor it. The invariant is duplicated the
|
|
24
|
+
* moment the expression is: `test/unit/request-test.ts` AC6 reaches this
|
|
25
|
+
* function through `Request`, which means the same assertion now also covers
|
|
26
|
+
* the `RestServer` half. Two copies of the predicate left the parent's copy
|
|
27
|
+
* free to drift -- inverting it, or dropping the condition entirely, kept the
|
|
28
|
+
* suite green.
|
|
29
|
+
*
|
|
30
|
+
* Note `express({ caseSensitive: true })` does NOT work: express 5's
|
|
31
|
+
* `createApplication()` takes zero arguments and forwards nothing. The app
|
|
32
|
+
* setting is the only mechanism.
|
|
33
|
+
*/
|
|
34
|
+
export default function applyRouteMatching(api: Express): void;
|
|
35
|
+
//# sourceMappingURL=route-matching.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-matching.d.ts","sourceRoot":"","sources":["../src/route-matching.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAE7D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import config from 'stonyx/config';
|
|
2
|
+
/**
|
|
3
|
+
* Applies this module's route-matching settings to an express app.
|
|
4
|
+
*
|
|
5
|
+
* Called from BOTH express construction sites (abofs/stonyx-rest-server#47):
|
|
6
|
+
* `RestServer`'s constructor closes the mount segment (`/PUBLIC/...`), and
|
|
7
|
+
* `Request`'s constructor closes sub-paths (`/public/SUCCESS`). Neither alone
|
|
8
|
+
* is sufficient -- settings are inherited on mount, but `mountRoute()` calls
|
|
9
|
+
* `registerCalls()` before `api.use()`, so each child router is already built
|
|
10
|
+
* by the time the parent's setting could reach it.
|
|
11
|
+
*
|
|
12
|
+
* Both callers invoke this from a constructor, and must keep doing so: express
|
|
13
|
+
* materializes a router lazily on first route registration, and a setting
|
|
14
|
+
* applied afterwards is silently ineffective -- no throw, no warning.
|
|
15
|
+
*
|
|
16
|
+
* The guard is `!== false`, not a plain truthy check, and that polarity is
|
|
17
|
+
* load-bearing. `trustProxy` and `enableHealthCheck` default to the falsy
|
|
18
|
+
* direction, so a missing key fails safe for them. This flag defaults to the
|
|
19
|
+
* truthy direction, so `if (config.restServer?.caseSensitiveRoutes)` would
|
|
20
|
+
* silently fail OPEN for a consumer whose shipped config predates the key.
|
|
21
|
+
*
|
|
22
|
+
* It lives here, in one place, rather than being written out at each call
|
|
23
|
+
* site, so that a single test can anchor it. The invariant is duplicated the
|
|
24
|
+
* moment the expression is: `test/unit/request-test.ts` AC6 reaches this
|
|
25
|
+
* function through `Request`, which means the same assertion now also covers
|
|
26
|
+
* the `RestServer` half. Two copies of the predicate left the parent's copy
|
|
27
|
+
* free to drift -- inverting it, or dropping the condition entirely, kept the
|
|
28
|
+
* suite green.
|
|
29
|
+
*
|
|
30
|
+
* Note `express({ caseSensitive: true })` does NOT work: express 5's
|
|
31
|
+
* `createApplication()` takes zero arguments and forwards nothing. The app
|
|
32
|
+
* setting is the only mechanism.
|
|
33
|
+
*/
|
|
34
|
+
export default function applyRouteMatching(api) {
|
|
35
|
+
if (config.restServer?.caseSensitiveRoutes !== false)
|
|
36
|
+
api.set('case sensitive routing', true);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=route-matching.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-matching.js","sourceRoot":"","sources":["../src/route-matching.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,eAAe,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;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;AAChG,CAAC"}
|