@zerotal/core 1.13.3 → 1.13.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/CHANGELOG.md +20 -0
- package/package.json +1 -1
- package/src/gate/GateMiddleware.ts +35 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,26 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.13.4] — 2026-09-01
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **The site gate's staff bypass was a denylist, and failed open.** 1.13.3 admitted any
|
|
16
|
+
authenticated user whose role was not literally `"customer"`. In an app whose roles are
|
|
17
|
+
`user` and `admin` — which is most of them — that is every signed-in visitor, so a
|
|
18
|
+
private preview showed the site to anyone with an account. A gate that fails open is
|
|
19
|
+
worse than no gate, because it reports success while doing nothing.
|
|
20
|
+
|
|
21
|
+
It is now an allowlist: `gate.staffRoles`, defaulting to `["admin"]`. An app whose staff
|
|
22
|
+
role is named something else gets no bypass and notices, which is the safe direction to
|
|
23
|
+
be wrong in. The token path is unchanged.
|
|
24
|
+
|
|
25
|
+
- **The same code broke `tsc` for apps that do not use the gate.** `role !== "customer"`
|
|
26
|
+
is a type error when an app's role union has no such member, and the framework ships TS
|
|
27
|
+
source, so the error landed on a feature the app never touched. The role is read as
|
|
28
|
+
`string` now, because the framework cannot know an app's role names and must not narrow
|
|
29
|
+
to them.
|
|
30
|
+
|
|
11
31
|
## [1.13.3] — 2026-08-31
|
|
12
32
|
|
|
13
33
|
### Added
|
package/package.json
CHANGED
|
@@ -49,6 +49,22 @@ interface GateConfig {
|
|
|
49
49
|
* of your routes a third party calls, so it has to be declared.
|
|
50
50
|
*/
|
|
51
51
|
allow?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* Roles admitted to a preview without a token.
|
|
54
|
+
*
|
|
55
|
+
* An **allowlist**, defaulting to `["admin"]`. An app whose staff role is named
|
|
56
|
+
* something else declares it here:
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* // config/gate.ts
|
|
60
|
+
* export default { staffRoles: ["admin", "editor"] };
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* It is a list rather than a rule because the framework cannot know an app's
|
|
64
|
+
* role names, and the shape it replaced — "anyone who is not a customer" —
|
|
65
|
+
* admitted every signed-in user in an app that has no role called `customer`.
|
|
66
|
+
*/
|
|
67
|
+
staffRoles?: string[];
|
|
52
68
|
/**
|
|
53
69
|
* What the public gets during a **preview** — not maintenance, which is always
|
|
54
70
|
* 503.
|
|
@@ -200,8 +216,25 @@ function _isStaff(ctx: HttpContext): boolean {
|
|
|
200
216
|
if (!("user" in ctx)) return false;
|
|
201
217
|
const user = ctx.user;
|
|
202
218
|
if (typeof user !== "object" || user === null || !("role" in user)) return false;
|
|
203
|
-
|
|
204
|
-
|
|
219
|
+
|
|
220
|
+
// Widened to `string` deliberately. Comparing against an app's own role union
|
|
221
|
+
// is a type error when the app has no such member — 1.13.3 shipped
|
|
222
|
+
// `role !== "customer"` and broke `tsc` for every app whose roles are, say,
|
|
223
|
+
// `"user" | "admin"`, on a feature they were not using. The framework cannot
|
|
224
|
+
// know an app's role names, so it must not narrow to them.
|
|
225
|
+
const role: string = typeof user.role === "string" ? user.role : "";
|
|
226
|
+
if (!role) return false;
|
|
227
|
+
|
|
228
|
+
// An **allowlist**, and this is the load-bearing part. 1.13.3 asked
|
|
229
|
+
// `role !== "customer"`, which reads as "everyone except customers is staff" —
|
|
230
|
+
// so in an app whose roles are `user` and `admin`, every signed-in visitor was
|
|
231
|
+
// staff and the gate let the public straight through. A gate that fails *open*
|
|
232
|
+
// is worse than no gate, because it reports success.
|
|
233
|
+
//
|
|
234
|
+
// Defaulting to `admin` alone rather than to something broad, for the same
|
|
235
|
+
// reason: an app that names its staff role something else gets no bypass and
|
|
236
|
+
// notices, which is the safe direction to be wrong in.
|
|
237
|
+
return config.safe<string[]>("gate.staffRoles", ["admin"]).includes(role);
|
|
205
238
|
}
|
|
206
239
|
|
|
207
240
|
/** HMAC of the token hash under the app key, so the cookie cannot be forged. */
|