@stonyx/rest-server 0.2.1-alpha.30 → 0.2.1-alpha.31
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 +72 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ 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. Opt out with `REST_CASE_SENSITIVE_ROUTES=false
|
|
82
|
+
| `caseSensitiveRoutes` | **Boolean** | `true` | Match route paths case-sensitively. Opt out with `restServer.caseSensitiveRoutes: false` (or `REST_CASE_SENSITIVE_ROUTES=false`, devDependencies installs only) — read [Breaking changes](#breaking-changes) before you do |
|
|
83
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. |
|
|
84
84
|
| `statusMap` | **Object** | `{}` | Optional mapping of HTTP status codes to custom messages |
|
|
85
85
|
|
|
@@ -87,26 +87,89 @@ Configuration is read from `stonyx/config` under `restServer`:
|
|
|
87
87
|
|
|
88
88
|
Routes match **case-sensitively**. `GET /Users` does not reach a route mounted at `/users`; it returns 404.
|
|
89
89
|
|
|
90
|
-
This is deliberate and is a security property, not a style choice. Express matches case-insensitively by default, but `request.path`, `request.baseUrl` and `request.originalUrl` all preserve the caller's casing — so an `auth` hook written against the path is case-sensitive while the router that dispatched to it is not. A caller who changes the case of a URL then reaches a handler the canonical URL is denied
|
|
90
|
+
This is deliberate and is a security property, not a style choice. Express matches case-insensitively by default, but `request.path`, `request.baseUrl` and `request.originalUrl` all preserve the caller's casing — so an `auth` hook written against the path is case-sensitive while the router that dispatched to it is not. A caller who changes the case of a URL then reaches a handler the canonical URL is denied.
|
|
91
|
+
|
|
92
|
+
Measured against this repo's own sample requests, **before this change**:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
GET /public/SUCCESS -> 200 the /success handler runs
|
|
96
|
+
GET /PRIVATE/failure -> 505 the auth hook fires on a path it was never written for
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**After this change:**
|
|
91
100
|
|
|
92
101
|
```
|
|
93
|
-
GET /
|
|
94
|
-
GET /
|
|
102
|
+
GET /public/SUCCESS -> 404
|
|
103
|
+
GET /PRIVATE/failure -> 404
|
|
104
|
+
GET /public/success -> 200 canonical paths are untouched
|
|
105
|
+
GET /private/failure -> 505 canonical paths are untouched
|
|
95
106
|
```
|
|
96
107
|
|
|
97
108
|
A router that matches more loosely than every downstream matcher is a fail-open by construction, so the default is the strict one.
|
|
98
109
|
|
|
99
|
-
**
|
|
110
|
+
> **Why not `GET /private/FAILURE` as the probe?** It returns `200` both before and after, because the sample `private.ts` also registers `/:id`, which absorbs the miss. What changes is *which handler ran* — before, the case-varied URL reached the `/failure` handler that `auth` denies with `505`; after, it can only reach the `/:id` handler. Status alone is not a reliable signal that the fix landed; use `GET /public/SUCCESS` for that.
|
|
111
|
+
|
|
112
|
+
### Breaking changes
|
|
113
|
+
|
|
114
|
+
Case-sensitive matching is a **behaviour change**. Requests that previously reached a route now return 404.
|
|
115
|
+
|
|
116
|
+
**Who is affected:**
|
|
117
|
+
|
|
118
|
+
* Any client sending a URL whose case does not exactly match the mounted path — hand-written links, bookmarked or cached URLs, third-party callers, anything that upper-cases path segments.
|
|
119
|
+
* Any app with a capitalised or hyphenated request filename. Mount paths come from filenames, and `camelCaseRoutes` never lower-cases anything: it only upper-cases the letter following a `-`. So `Users.ts` mounts at `/Users` under **both** `camelCaseRoutes` settings, including the default `true`, and `phone-number.ts` mounts at `/phoneNumber` under the default. Clients that hardcode `/users` or `/phonenumber` start 404ing.
|
|
120
|
+
* Anything matching the URL downstream of the router — reverse proxies, WAF path rules, analytics path grouping, `originalUrl`-based routing.
|
|
121
|
+
* `GET /HEALTH` no longer answers. Only `GET /health` does.
|
|
122
|
+
|
|
123
|
+
**The symptom.** Express's default 404, with **no log line and no stack** — `RestServer` registers no 404 handler, so nothing is emitted server-side:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
HTTP/1.1 404 Not Found
|
|
127
|
+
Content-Type: text/html; charset=utf-8
|
|
128
|
+
|
|
129
|
+
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Error</title></head>
|
|
130
|
+
<body><pre>Cannot GET /public/SUCCESS</pre></body></html>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
If routes appear to have vanished after upgrading, `Cannot GET` in the response body is the only string to grep for. Silence in the logs is expected here and is not evidence of a dropped route or a bad build.
|
|
100
134
|
|
|
101
|
-
**
|
|
135
|
+
**Before you upgrade,** list your actual mount paths and compare them against the URLs your clients send:
|
|
102
136
|
|
|
103
137
|
```bash
|
|
138
|
+
ls requests/ # every filename becomes a mount path
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Any filename that is not already all-lowercase — and any hyphenated filename under the default `camelCaseRoutes: true` — produces a mixed-case mount that now requires exact casing from callers.
|
|
142
|
+
|
|
143
|
+
**Opting out (temporary).** Two forms, and they are **not** interchangeable:
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
// config/environment.js in YOUR app — works for every install shape
|
|
147
|
+
export default {
|
|
148
|
+
restServer: { caseSensitiveRoutes: false }
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# environment variable — only effective when @stonyx/rest-server is in your devDependencies
|
|
104
154
|
REST_CASE_SENSITIVE_ROUTES=false
|
|
105
155
|
```
|
|
106
156
|
|
|
107
|
-
|
|
157
|
+
The environment variable is read by this module's own `config/environment.js`, and the Stonyx module loader merges that file only for `@stonyx/*` packages listed in your **`devDependencies`**. If you install `@stonyx/rest-server` into `dependencies`, the file is never loaded and the variable is inert. The config-object form wins in both install shapes, so prefer it.
|
|
158
|
+
|
|
159
|
+
Either form restores Express's default case-insensitive matching. It also re-opens the **case-variant fail-open** described above for any authorization that matches on a URL, so treat it as a temporary measure while you fix client casing, not as a setting to leave on. It has no effect on the other residuals below, which are open either way.
|
|
160
|
+
|
|
161
|
+
**Scope.** This makes *route matching* exact on the **case axis only**. It closes [#47](https://github.com/abofs/stonyx-rest-server/issues/47) and nothing else. The following are known-open members of the same loose-matching family. None is closed by this setting, and this list is the residual risk that is currently *tracked* — not a statement that URL matching is otherwise exact.
|
|
162
|
+
|
|
163
|
+
* **Path parameter values — [#69](https://github.com/abofs/stonyx-rest-server/issues/69).** Route matching is exact, but the router will deliver a `:param` value in any casing, and a hook comparing `request.params.id` is doing its own case-sensitive comparison against it. This is a **bypass**, not a normalisation nicety. Measured against this repo's sample `private.ts`, whose hook is `if (request.params?.id === 'restricted') return 403`:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
GET /private/restricted -> 403 Forbidden
|
|
167
|
+
GET /private/RESTRICTED -> 200 {"data":"param-route"} guarded handler runs
|
|
168
|
+
```
|
|
108
169
|
|
|
109
|
-
|
|
170
|
+
* **Trailing slashes — [#50](https://github.com/abofs/stonyx-rest-server/issues/50).** Express's `strict routing` is a separate setting and is still off, so `GET /private/failure/` still matches `/failure` and still bypasses a path-matching `auth` hook.
|
|
171
|
+
* **Mount-root trailing slash and absolute-form request targets — [#54](https://github.com/abofs/stonyx-rest-server/issues/54).** Both are seen by an `originalUrl`-matching hook as a different string from the canonical URL.
|
|
172
|
+
* **Percent-encoding — [#56](https://github.com/abofs/stonyx-rest-server/issues/56).** `GET /enc/%73ecret` reaches the handler that `GET /enc/secret` is denied, defeating hooks written against `req.path` and against `originalUrl` alike.
|
|
110
173
|
|
|
111
174
|
### Running Behind a Load Balancer
|
|
112
175
|
|
|
@@ -174,7 +237,7 @@ project-root/
|
|
|
174
237
|
* `public.js` — contains public-facing routes without authentication
|
|
175
238
|
* `private.js` — contains routes with authentication via the `auth` hook
|
|
176
239
|
|
|
177
|
-
The `RestServer` will automatically mount these routes using the filenames as paths (`/public` and `/private` by default, or
|
|
240
|
+
The `RestServer` will automatically mount these routes using the filenames as paths (`/public` and `/private` by default, or verbatim if `camelCaseRoutes` is disabled).
|
|
178
241
|
|
|
179
242
|
### Example Requests
|
|
180
243
|
|