@grest-ts/http 0.0.48 → 0.0.49
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 +243 -175
- package/dist/src/_dedupCheck.js +1 -1
- package/dist/src/client/GGContextKeySynchronizer.d.ts +11 -0
- package/dist/src/client/GGContextKeySynchronizer.d.ts.map +1 -0
- package/dist/src/client/GGContextKeySynchronizer.js +26 -0
- package/dist/src/client/GGContextKeySynchronizer.js.map +1 -0
- package/dist/src/index-browser.d.ts +4 -2
- package/dist/src/index-browser.d.ts.map +1 -1
- package/dist/src/index-browser.js +4 -2
- package/dist/src/index-browser.js.map +1 -1
- package/dist/src/index-node.d.ts +8 -3
- package/dist/src/index-node.d.ts.map +1 -1
- package/dist/src/index-node.js +16 -7
- package/dist/src/index-node.js.map +1 -1
- package/dist/src/rpc/RpcRequest/GGRpcRequestBuilder.d.ts +4 -3
- package/dist/src/rpc/RpcRequest/GGRpcRequestBuilder.d.ts.map +1 -1
- package/dist/src/rpc/RpcRequest/GGRpcRequestBuilder.js +9 -2
- package/dist/src/rpc/RpcRequest/GGRpcRequestBuilder.js.map +1 -1
- package/dist/src/rpc/RpcRequest/GGRpcRequestParser.d.ts +3 -3
- package/dist/src/rpc/RpcRequest/GGRpcRequestParser.d.ts.map +1 -1
- package/dist/src/rpc/RpcRequest/GGRpcRequestParser.js +13 -16
- package/dist/src/rpc/RpcRequest/GGRpcRequestParser.js.map +1 -1
- package/dist/src/rpc/RpcResponse/GGRpcResponseBuilder.d.ts +1 -1
- package/dist/src/rpc/RpcResponse/GGRpcResponseBuilder.d.ts.map +1 -1
- package/dist/src/rpc/RpcResponse/GGRpcResponseBuilder.js +3 -3
- package/dist/src/rpc/RpcResponse/GGRpcResponseBuilder.js.map +1 -1
- package/dist/src/schema/GGCookie.d.ts +50 -0
- package/dist/src/schema/GGCookie.d.ts.map +1 -0
- package/dist/src/schema/GGCookie.js +130 -0
- package/dist/src/schema/GGCookie.js.map +1 -0
- package/dist/src/schema/GGHeader.d.ts +23 -0
- package/dist/src/schema/GGHeader.d.ts.map +1 -0
- package/dist/src/schema/GGHeader.js +41 -0
- package/dist/src/schema/GGHeader.js.map +1 -0
- package/dist/src/schema/GGHttpPermissionsChecker.d.ts +9 -0
- package/dist/src/schema/GGHttpPermissionsChecker.d.ts.map +1 -0
- package/dist/src/schema/GGHttpPermissionsChecker.js +29 -0
- package/dist/src/schema/GGHttpPermissionsChecker.js.map +1 -0
- package/dist/src/schema/GGHttpSchema.d.ts +8 -59
- package/dist/src/schema/GGHttpSchema.d.ts.map +1 -1
- package/dist/src/schema/GGHttpSchema.js.map +1 -1
- package/dist/src/schema/GGWireContextKey.d.ts +30 -0
- package/dist/src/schema/GGWireContextKey.d.ts.map +1 -0
- package/dist/src/schema/GGWireContextKey.js +36 -0
- package/dist/src/schema/GGWireContextKey.js.map +1 -0
- package/dist/src/schema/GGWireContextKey.node.d.ts +36 -0
- package/dist/src/schema/GGWireContextKey.node.d.ts.map +1 -0
- package/dist/src/schema/GGWireContextKey.node.js +83 -0
- package/dist/src/schema/GGWireContextKey.node.js.map +1 -0
- package/dist/src/schema/httpSchema.d.ts +2 -2
- package/dist/src/schema/httpSchema.d.ts.map +1 -1
- package/dist/src/schema/httpSchema.js.map +1 -1
- package/dist/src/server/GGHttp.d.ts +2 -26
- package/dist/src/server/GGHttp.d.ts.map +1 -1
- package/dist/src/server/GGHttp.js +0 -15
- package/dist/src/server/GGHttp.js.map +1 -1
- package/dist/src/server/GGHttpSchema.startServer.d.ts +2 -11
- package/dist/src/server/GGHttpSchema.startServer.d.ts.map +1 -1
- package/dist/src/server/GGHttpSchema.startServer.js +11 -28
- package/dist/src/server/GGHttpSchema.startServer.js.map +1 -1
- package/dist/src/server/GGHttpServer.d.ts +16 -14
- package/dist/src/server/GGHttpServer.d.ts.map +1 -1
- package/dist/src/server/GGHttpServer.js +26 -79
- package/dist/src/server/GGHttpServer.js.map +1 -1
- package/dist/src/server/applyRequestMiddleware.d.ts +11 -0
- package/dist/src/server/applyRequestMiddleware.d.ts.map +1 -0
- package/dist/src/server/applyRequestMiddleware.js +38 -0
- package/dist/src/server/applyRequestMiddleware.js.map +1 -0
- package/dist/src/server/applyResponseMiddleware.d.ts +2 -2
- package/dist/src/server/applyResponseMiddleware.d.ts.map +1 -1
- package/dist/src/server/applyResponseMiddleware.js +2 -2
- package/dist/src/server/applyResponseMiddleware.js.map +1 -1
- package/dist/src/server/checkers/checkPermissionsAtStart.d.ts +39 -0
- package/dist/src/server/checkers/checkPermissionsAtStart.d.ts.map +1 -0
- package/dist/src/server/checkers/checkPermissionsAtStart.js +82 -0
- package/dist/src/server/checkers/checkPermissionsAtStart.js.map +1 -0
- package/dist/src/server/checkers/checkWireConflicts.d.ts +18 -0
- package/dist/src/server/checkers/checkWireConflicts.d.ts.map +1 -0
- package/dist/src/server/checkers/checkWireConflicts.js +43 -0
- package/dist/src/server/checkers/checkWireConflicts.js.map +1 -0
- package/dist/src/server/checkers/checkWiresImplemented.d.ts +11 -0
- package/dist/src/server/checkers/checkWiresImplemented.d.ts.map +1 -0
- package/dist/src/server/checkers/checkWiresImplemented.js +27 -0
- package/dist/src/server/checkers/checkWiresImplemented.js.map +1 -0
- package/dist/src/tsconfig.json +2 -1
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +14 -11
- package/src/_dedupCheck.ts +1 -1
- package/src/client/GGContextKeySynchronizer.test.ts +106 -0
- package/src/client/GGContextKeySynchronizer.ts +42 -0
- package/src/index-browser.ts +4 -2
- package/src/index-node.ts +17 -7
- package/src/rpc/RpcRequest/GGRpcRequestBuilder.ts +11 -4
- package/src/rpc/RpcRequest/GGRpcRequestParser.ts +16 -17
- package/src/rpc/RpcResponse/GGRpcResponseBuilder.ts +5 -4
- package/src/schema/GGCookie.ts +151 -0
- package/src/schema/GGHeader.ts +48 -0
- package/src/schema/GGHttpPermissionsChecker.test.ts +125 -0
- package/src/schema/GGHttpPermissionsChecker.ts +35 -0
- package/src/schema/GGHttpSchema.ts +8 -71
- package/src/schema/GGWireContextKey.node.ts +119 -0
- package/src/schema/GGWireContextKey.ts +46 -0
- package/src/schema/httpSchema.ts +3 -3
- package/src/server/GGHttp.ts +3 -34
- package/src/server/GGHttpSchema.startServer.ts +13 -38
- package/src/server/GGHttpServer.ts +33 -89
- package/src/server/applyRequestMiddleware.ts +40 -0
- package/src/server/applyResponseMiddleware.ts +5 -5
- package/src/server/checkers/checkPermissionsAtStart.ts +112 -0
- package/src/server/checkers/checkWireConflicts.ts +65 -0
- package/src/server/checkers/checkWiresImplemented.ts +42 -0
- package/src/tsconfig.json +2 -1
- package/dist/src/schema/cookieMiddleware.d.ts +0 -71
- package/dist/src/schema/cookieMiddleware.d.ts.map +0 -1
- package/dist/src/schema/cookieMiddleware.js +0 -126
- package/dist/src/schema/cookieMiddleware.js.map +0 -1
- package/dist/src/schema/headerBinding.d.ts +0 -35
- package/dist/src/schema/headerBinding.d.ts.map +0 -1
- package/dist/src/schema/headerBinding.js +0 -49
- package/dist/src/schema/headerBinding.js.map +0 -1
- package/dist/src/server/GG_PERMISSIONS.d.ts +0 -15
- package/dist/src/server/GG_PERMISSIONS.d.ts.map +0 -1
- package/dist/src/server/GG_PERMISSIONS.js +0 -15
- package/dist/src/server/GG_PERMISSIONS.js.map +0 -1
- package/src/schema/cookieMiddleware.ts +0 -164
- package/src/schema/headerBinding.ts +0 -65
- package/src/server/GG_PERMISSIONS.ts +0 -15
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { GGContextKey } from "@grest-ts/context";
|
|
2
|
-
import { IsAny, IsString, SERVER_ERROR } from "@grest-ts/schema";
|
|
3
|
-
/**
|
|
4
|
-
* Per-request set of context-key names the current route is permitted to write a cookie
|
|
5
|
-
* for (declared via GGRpc.*(...).updatesCookie(key)). Populated by the server route
|
|
6
|
-
* handler; read by setCookie() to reject undeclared cookie writes.
|
|
7
|
-
*/
|
|
8
|
-
export const GG_COOKIE_WRITES = new GGContextKey("cookie:writes", IsAny);
|
|
9
|
-
const SAFE_DEFAULTS = { httpOnly: true, secure: true, sameSite: "lax", path: "/" };
|
|
10
|
-
/**
|
|
11
|
-
* Per-request Set-Cookie writes scheduled via setCookie()/clearCookie(), keyed by the
|
|
12
|
-
* context key's name. Flushed onto the response by the matching cookie() binding.
|
|
13
|
-
*/
|
|
14
|
-
const GG_COOKIE_PENDING = new GGContextKey("cookie:pending", IsAny);
|
|
15
|
-
// Reject anything that could break out of the Set-Cookie line / inject attributes.
|
|
16
|
-
function assertCookieSafe(label, value) {
|
|
17
|
-
if (/[\x00-\x1f;]/.test(value)) {
|
|
18
|
-
throw new Error(`Cookie ${label} contains illegal characters (control chars or ';'): ${JSON.stringify(value)}`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Read a single named cookie out of a raw `Cookie` header value. Shared by the HTTP
|
|
23
|
-
* binding (parseRequest) and the WebSocket binding (parseHandshake). A malformed
|
|
24
|
-
* percent-encoding (e.g. "sid=%") must not crash — fall back to the raw value.
|
|
25
|
-
*/
|
|
26
|
-
export function readCookie(rawCookieHeader, name) {
|
|
27
|
-
if (typeof rawCookieHeader !== "string")
|
|
28
|
-
return undefined;
|
|
29
|
-
for (const part of rawCookieHeader.split(";")) {
|
|
30
|
-
const eq = part.indexOf("=");
|
|
31
|
-
if (eq === -1)
|
|
32
|
-
continue;
|
|
33
|
-
if (part.slice(0, eq).trim() === name) {
|
|
34
|
-
const rawValue = part.slice(eq + 1).trim();
|
|
35
|
-
try {
|
|
36
|
-
return decodeURIComponent(rawValue);
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return rawValue;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
function serializeSetCookie(name, value, options) {
|
|
46
|
-
const o = { ...SAFE_DEFAULTS, ...options };
|
|
47
|
-
const secure = o.sameSite === "none" ? true : o.secure;
|
|
48
|
-
const sameSite = `${o.sameSite.charAt(0).toUpperCase()}${o.sameSite.slice(1)}`;
|
|
49
|
-
const attrs = `; Path=${o.path}${o.domain ? `; Domain=${o.domain}` : ""}; SameSite=${sameSite}${secure ? "; Secure" : ""}${o.httpOnly ? "; HttpOnly" : ""}`;
|
|
50
|
-
return value
|
|
51
|
-
? `${name}=${encodeURIComponent(value)}${o.maxAgeSec !== undefined ? `; Max-Age=${Math.trunc(o.maxAgeSec)}` : ""}${attrs}`
|
|
52
|
-
: `${name}=; Max-Age=0${attrs}`;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Bind a context key to a cookie, read-only on the wire. Reads ONLY the named cookie —
|
|
56
|
-
* never a header — from the incoming HTTP Cookie (parseRequest) or the real WS upgrade
|
|
57
|
-
* Cookie (parseHandshake), into key.get(). The cookie's wire name defaults to the key's
|
|
58
|
-
* name. To write the cookie, call setCookie(key, …) from a route that declared
|
|
59
|
-
* .updatesCookie(key); the matching binding flushes it as Set-Cookie (HTTP only — a
|
|
60
|
-
* WebSocket has no response to set a cookie on).
|
|
61
|
-
*/
|
|
62
|
-
export function cookie(key, opts) {
|
|
63
|
-
const cookieName = opts?.name ?? key.name;
|
|
64
|
-
assertCookieSafe("name", cookieName);
|
|
65
|
-
return {
|
|
66
|
-
headers: {},
|
|
67
|
-
responseHeaders: {},
|
|
68
|
-
cookieParams: { [cookieName]: opts?.schema ?? IsString.orUndefined },
|
|
69
|
-
parseRequest(req) {
|
|
70
|
-
const raw = req.headers?.["cookie"];
|
|
71
|
-
const value = readCookie(typeof raw === "string" ? raw : undefined, cookieName);
|
|
72
|
-
if (value !== undefined)
|
|
73
|
-
key.set(value);
|
|
74
|
-
},
|
|
75
|
-
updateResponse(res) {
|
|
76
|
-
const pending = GG_COOKIE_PENDING.get()?.get(key.name);
|
|
77
|
-
if (pending === undefined)
|
|
78
|
-
return;
|
|
79
|
-
const line = serializeSetCookie(cookieName, pending.value, pending.options);
|
|
80
|
-
const existing = res.headers["set-cookie"];
|
|
81
|
-
const arr = existing === undefined ? [] : Array.isArray(existing) ? existing : [existing];
|
|
82
|
-
arr.push(line);
|
|
83
|
-
res.headers["set-cookie"] = arr;
|
|
84
|
-
},
|
|
85
|
-
parseHandshake(ctx) {
|
|
86
|
-
const value = readCookie(ctx.upgradeHeaders?.["cookie"], cookieName);
|
|
87
|
-
if (value !== undefined)
|
|
88
|
-
key.set(value);
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Schedule a Set-Cookie on the response (HTTP only). The value never touches key.get() —
|
|
94
|
-
* inbound reads stay set-once. The route must declare .updatesCookie(key); an undeclared
|
|
95
|
-
* call is invalid usage and throws SERVER_ERROR. The matching cookie() binding serializes
|
|
96
|
-
* the pending write with these options (HttpOnly/Secure/SameSite/Path/Domain/Max-Age).
|
|
97
|
-
*/
|
|
98
|
-
export function setCookie(key, value, options) {
|
|
99
|
-
if (options) {
|
|
100
|
-
if (options.maxAgeSec !== undefined && !Number.isFinite(options.maxAgeSec)) {
|
|
101
|
-
throw new Error(`setCookie("${key.name}"): maxAgeSec must be a finite number, got ${options.maxAgeSec}.`);
|
|
102
|
-
}
|
|
103
|
-
if (options.path !== undefined)
|
|
104
|
-
assertCookieSafe("path", options.path);
|
|
105
|
-
if (options.domain !== undefined)
|
|
106
|
-
assertCookieSafe("domain", options.domain);
|
|
107
|
-
}
|
|
108
|
-
if (!GG_COOKIE_WRITES.get()?.has(key.name)) {
|
|
109
|
-
throw new SERVER_ERROR({ debugMessage: `setCookie("${key.name}") was called, but this route did not declare .updatesCookie(<key>). Only routes that declare it may set or clear the cookie.` });
|
|
110
|
-
}
|
|
111
|
-
let pending = GG_COOKIE_PENDING.get();
|
|
112
|
-
if (pending === undefined) {
|
|
113
|
-
pending = new Map();
|
|
114
|
-
GG_COOKIE_PENDING.set(pending);
|
|
115
|
-
}
|
|
116
|
-
pending.set(key.name, { value, options });
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Clear the cookie — schedules a Set-Cookie with Max-Age=0. Pass path/domain to match a
|
|
120
|
-
* scoped cookie (deletion only works when path+domain match what was set). Equivalent to
|
|
121
|
-
* setCookie(key, undefined, options), but reads clearer at logout-style call sites.
|
|
122
|
-
*/
|
|
123
|
-
export function clearCookie(key, options) {
|
|
124
|
-
setCookie(key, undefined, options);
|
|
125
|
-
}
|
|
126
|
-
//# sourceMappingURL=cookieMiddleware.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cookieMiddleware.js","sourceRoot":"","sources":["../../../src/schema/cookieMiddleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { GGContextKey } from "@grest-ts/context";
|
|
2
|
-
import { type GGSchema } from "@grest-ts/schema";
|
|
3
|
-
import type { GGHttpTransportMiddleware } from "./GGHttpSchema";
|
|
4
|
-
/**
|
|
5
|
-
* Local structural copy of GGWebSocketHandshakeContext (defined in @grest-ts/websocket).
|
|
6
|
-
* http must not import websocket; header()'s WS half is typed against this shape and
|
|
7
|
-
* matches GGWebSocketMiddleware structurally at the webSocketSchema.use() call site.
|
|
8
|
-
* `headers` is the in-band handshake bag (client-set, spoofable — same trust level as an
|
|
9
|
-
* HTTP request header).
|
|
10
|
-
*/
|
|
11
|
-
interface WsHandshakeContextLike {
|
|
12
|
-
headers: Record<string, string>;
|
|
13
|
-
upgradeHeaders?: Record<string, string>;
|
|
14
|
-
queryArgs: Record<string, string>;
|
|
15
|
-
}
|
|
16
|
-
export interface GGHeaderBinding extends GGHttpTransportMiddleware {
|
|
17
|
-
parseHandshake(ctx: WsHandshakeContextLike): void;
|
|
18
|
-
updateHandshake(ctx: WsHandshakeContextLike): void;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Bind a context key to a single named header, on both transports. Reads ONLY that header
|
|
22
|
-
* — never a cookie — from the HTTP request (parseRequest) or the in-band WS handshake
|
|
23
|
-
* (parseHandshake) into key.get(), and writes it from key.get() on the client (HTTP
|
|
24
|
-
* updateRequest / WS updateHandshake). `scheme: "bearer"` strips/adds the `Bearer ` prefix;
|
|
25
|
-
* default is verbatim. The wire name defaults to the key's name (lowercased — HTTP headers
|
|
26
|
-
* are case-insensitive). Client-attachable, so it is the service-to-service variant (a
|
|
27
|
-
* Node client can carry it; a browser cookie cannot).
|
|
28
|
-
*/
|
|
29
|
-
export declare function header(key: GGContextKey<string | undefined>, opts?: {
|
|
30
|
-
name?: string;
|
|
31
|
-
scheme?: "bearer";
|
|
32
|
-
schema?: GGSchema<string | undefined>;
|
|
33
|
-
}): GGHeaderBinding;
|
|
34
|
-
export {};
|
|
35
|
-
//# sourceMappingURL=headerBinding.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"headerBinding.d.ts","sourceRoot":"","sources":["../../../src/schema/headerBinding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAW,KAAK,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AACxD,OAAO,KAAK,EAAgB,yBAAyB,EAAC,MAAM,gBAAgB,CAAA;AAE5E;;;;;;GAMG;AACH,UAAU,sBAAsB;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACpC;AAED,MAAM,WAAW,eAAgB,SAAQ,yBAAyB;IAC9D,cAAc,CAAC,GAAG,EAAE,sBAAsB,GAAG,IAAI,CAAA;IACjD,eAAe,CAAC,GAAG,EAAE,sBAAsB,GAAG,IAAI,CAAA;CACrD;AAID;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,YAAY,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,IAAI,CAAC,EAAE;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CAAC,GAAG,eAAe,CA+B/J"}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { IsString } from "@grest-ts/schema";
|
|
2
|
-
const BEARER = "Bearer ";
|
|
3
|
-
/**
|
|
4
|
-
* Bind a context key to a single named header, on both transports. Reads ONLY that header
|
|
5
|
-
* — never a cookie — from the HTTP request (parseRequest) or the in-band WS handshake
|
|
6
|
-
* (parseHandshake) into key.get(), and writes it from key.get() on the client (HTTP
|
|
7
|
-
* updateRequest / WS updateHandshake). `scheme: "bearer"` strips/adds the `Bearer ` prefix;
|
|
8
|
-
* default is verbatim. The wire name defaults to the key's name (lowercased — HTTP headers
|
|
9
|
-
* are case-insensitive). Client-attachable, so it is the service-to-service variant (a
|
|
10
|
-
* Node client can carry it; a browser cookie cannot).
|
|
11
|
-
*/
|
|
12
|
-
export function header(key, opts) {
|
|
13
|
-
const name = (opts?.name ?? key.name).toLowerCase();
|
|
14
|
-
const bearer = opts?.scheme === "bearer";
|
|
15
|
-
const unwrap = (raw) => {
|
|
16
|
-
if (raw === undefined)
|
|
17
|
-
return undefined;
|
|
18
|
-
return bearer && raw.startsWith(BEARER) ? raw.slice(BEARER.length) : raw;
|
|
19
|
-
};
|
|
20
|
-
const wrap = (value) => bearer ? `${BEARER}${value}` : value;
|
|
21
|
-
return {
|
|
22
|
-
headers: { [name]: opts?.schema ?? IsString.orUndefined },
|
|
23
|
-
responseHeaders: {},
|
|
24
|
-
parseRequest(req) {
|
|
25
|
-
const raw = req.headers?.[name];
|
|
26
|
-
const value = unwrap(typeof raw === "string" ? raw : Array.isArray(raw) ? raw[0] : undefined);
|
|
27
|
-
if (value !== undefined)
|
|
28
|
-
key.set(value);
|
|
29
|
-
},
|
|
30
|
-
updateRequest(req) {
|
|
31
|
-
const value = key.get();
|
|
32
|
-
if (value === undefined)
|
|
33
|
-
return;
|
|
34
|
-
req.headers = req.headers ?? {};
|
|
35
|
-
req.headers[name] = wrap(value);
|
|
36
|
-
},
|
|
37
|
-
parseHandshake(ctx) {
|
|
38
|
-
const value = unwrap(ctx.headers?.[name]);
|
|
39
|
-
if (value !== undefined)
|
|
40
|
-
key.set(value);
|
|
41
|
-
},
|
|
42
|
-
updateHandshake(ctx) {
|
|
43
|
-
const value = key.get();
|
|
44
|
-
if (value !== undefined)
|
|
45
|
-
ctx.headers[name] = wrap(value);
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=headerBinding.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"headerBinding.js","sourceRoot":"","sources":["../../../src/schema/headerBinding.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAgB,MAAM,kBAAkB,CAAA;AAqBxD,MAAM,MAAM,GAAG,SAAS,CAAA;AAExB;;;;;;;;GAQG;AACH,MAAM,UAAU,MAAM,CAAC,GAAqC,EAAE,IAAgF;IAC1I,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;IACnD,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAA;IACxC,MAAM,MAAM,GAAG,CAAC,GAAuB,EAAsB,EAAE;QAC3D,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QACvC,OAAO,MAAM,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IAC5E,CAAC,CAAA;IACD,MAAM,IAAI,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;IAC5E,OAAO;QACH,OAAO,EAAE,EAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,IAAI,QAAQ,CAAC,WAAW,EAAC;QACvD,eAAe,EAAE,EAAE;QACnB,YAAY,CAAC,GAAkB;YAC3B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAA;YAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;YAC7F,IAAI,KAAK,KAAK,SAAS;gBAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC3C,CAAC;QACD,aAAa,CAAC,GAAkB;YAC5B,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,CAAA;YACvB,IAAI,KAAK,KAAK,SAAS;gBAAE,OAAM;YAC/B,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAA;YAC/B,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAA;QACnC,CAAC;QACD,cAAc,CAAC,GAA2B;YACtC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;YACzC,IAAI,KAAK,KAAK,SAAS;gBAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC3C,CAAC;QACD,eAAe,CAAC,GAA2B;YACvC,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,CAAA;YACvB,IAAI,KAAK,KAAK,SAAS;gBAAE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAA;QAC5D,CAAC;KACJ,CAAA;AACL,CAAC"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { GGContextKey } from "@grest-ts/context";
|
|
2
|
-
import { GGPermissionChecker } from "@grest-ts/schema";
|
|
3
|
-
/**
|
|
4
|
-
* Per-request permission context populated by the gate when a scope resolver
|
|
5
|
-
* is wired via GGHttp.usePermissions(...). Handler code can read it to make
|
|
6
|
-
* sub-decisions inside an authorized method:
|
|
7
|
-
*
|
|
8
|
-
* const perm = GG_PERMISSIONS.get();
|
|
9
|
-
* if (perm.has(AppPermission.Admin)) { ... }
|
|
10
|
-
*
|
|
11
|
-
* The checker uses the same satisfies() the framework gate uses — no parallel
|
|
12
|
-
* implementations.
|
|
13
|
-
*/
|
|
14
|
-
export declare const GG_PERMISSIONS: GGContextKey<GGPermissionChecker>;
|
|
15
|
-
//# sourceMappingURL=GG_PERMISSIONS.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GG_PERMISSIONS.d.ts","sourceRoot":"","sources":["../../../src/server/GG_PERMISSIONS.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAC,mBAAmB,EAAQ,MAAM,kBAAkB,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,mCAAqE,CAAC"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { GGContextKey } from "@grest-ts/context";
|
|
2
|
-
import { IsAny } from "@grest-ts/schema";
|
|
3
|
-
/**
|
|
4
|
-
* Per-request permission context populated by the gate when a scope resolver
|
|
5
|
-
* is wired via GGHttp.usePermissions(...). Handler code can read it to make
|
|
6
|
-
* sub-decisions inside an authorized method:
|
|
7
|
-
*
|
|
8
|
-
* const perm = GG_PERMISSIONS.get();
|
|
9
|
-
* if (perm.has(AppPermission.Admin)) { ... }
|
|
10
|
-
*
|
|
11
|
-
* The checker uses the same satisfies() the framework gate uses — no parallel
|
|
12
|
-
* implementations.
|
|
13
|
-
*/
|
|
14
|
-
export const GG_PERMISSIONS = new GGContextKey("permissions", IsAny);
|
|
15
|
-
//# sourceMappingURL=GG_PERMISSIONS.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GG_PERMISSIONS.js","sourceRoot":"","sources":["../../../src/server/GG_PERMISSIONS.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAsB,KAAK,EAAC,MAAM,kBAAkB,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,YAAY,CAAsB,aAAa,EAAE,KAAY,CAAC,CAAC"}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import {GGContextKey} from "@grest-ts/context"
|
|
2
|
-
import {IsAny, IsString, SERVER_ERROR, type GGSchema} from "@grest-ts/schema"
|
|
3
|
-
import type {GGHttpRequest, GGHttpResponse, GGHttpTransportMiddleware} from "./GGHttpSchema"
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Per-request set of context-key names the current route is permitted to write a cookie
|
|
7
|
-
* for (declared via GGRpc.*(...).updatesCookie(key)). Populated by the server route
|
|
8
|
-
* handler; read by setCookie() to reject undeclared cookie writes.
|
|
9
|
-
*/
|
|
10
|
-
export const GG_COOKIE_WRITES = new GGContextKey<Set<string>>("cookie:writes", IsAny as unknown as GGSchema<Set<string>>)
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Cookie write rules — passed to setCookie(key, value, options). They live at the write
|
|
14
|
-
* call site (the handler that mints the cookie), never in the shared API.
|
|
15
|
-
*/
|
|
16
|
-
export interface CookieOptions {
|
|
17
|
-
httpOnly?: boolean
|
|
18
|
-
secure?: boolean
|
|
19
|
-
sameSite?: "lax" | "strict" | "none"
|
|
20
|
-
path?: string
|
|
21
|
-
domain?: string
|
|
22
|
-
maxAgeSec?: number
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const SAFE_DEFAULTS = {httpOnly: true, secure: true, sameSite: "lax", path: "/"} as const
|
|
26
|
-
|
|
27
|
-
interface PendingCookie {
|
|
28
|
-
value: string | undefined
|
|
29
|
-
options?: CookieOptions
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Per-request Set-Cookie writes scheduled via setCookie()/clearCookie(), keyed by the
|
|
34
|
-
* context key's name. Flushed onto the response by the matching cookie() binding.
|
|
35
|
-
*/
|
|
36
|
-
const GG_COOKIE_PENDING = new GGContextKey<Map<string, PendingCookie>>("cookie:pending", IsAny as unknown as GGSchema<Map<string, PendingCookie>>)
|
|
37
|
-
|
|
38
|
-
// Reject anything that could break out of the Set-Cookie line / inject attributes.
|
|
39
|
-
function assertCookieSafe(label: string, value: string): void {
|
|
40
|
-
if (/[\x00-\x1f;]/.test(value)) {
|
|
41
|
-
throw new Error(`Cookie ${label} contains illegal characters (control chars or ';'): ${JSON.stringify(value)}`)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Read a single named cookie out of a raw `Cookie` header value. Shared by the HTTP
|
|
47
|
-
* binding (parseRequest) and the WebSocket binding (parseHandshake). A malformed
|
|
48
|
-
* percent-encoding (e.g. "sid=%") must not crash — fall back to the raw value.
|
|
49
|
-
*/
|
|
50
|
-
export function readCookie(rawCookieHeader: string | undefined, name: string): string | undefined {
|
|
51
|
-
if (typeof rawCookieHeader !== "string") return undefined
|
|
52
|
-
for (const part of rawCookieHeader.split(";")) {
|
|
53
|
-
const eq = part.indexOf("=")
|
|
54
|
-
if (eq === -1) continue
|
|
55
|
-
if (part.slice(0, eq).trim() === name) {
|
|
56
|
-
const rawValue = part.slice(eq + 1).trim()
|
|
57
|
-
try {
|
|
58
|
-
return decodeURIComponent(rawValue)
|
|
59
|
-
} catch {
|
|
60
|
-
return rawValue
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return undefined
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function serializeSetCookie(name: string, value: string | undefined, options?: CookieOptions): string {
|
|
68
|
-
const o = {...SAFE_DEFAULTS, ...options}
|
|
69
|
-
const secure = o.sameSite === "none" ? true : o.secure
|
|
70
|
-
const sameSite = `${o.sameSite.charAt(0).toUpperCase()}${o.sameSite.slice(1)}`
|
|
71
|
-
const attrs = `; Path=${o.path}${o.domain ? `; Domain=${o.domain}` : ""}; SameSite=${sameSite}${secure ? "; Secure" : ""}${o.httpOnly ? "; HttpOnly" : ""}`
|
|
72
|
-
return value
|
|
73
|
-
? `${name}=${encodeURIComponent(value)}${o.maxAgeSec !== undefined ? `; Max-Age=${Math.trunc(o.maxAgeSec)}` : ""}${attrs}`
|
|
74
|
-
: `${name}=; Max-Age=0${attrs}`
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Local structural copy of GGWebSocketHandshakeContext (defined in @grest-ts/websocket).
|
|
79
|
-
* http must not import websocket; cookie()'s WS half is typed against this shape and
|
|
80
|
-
* matches GGWebSocketMiddleware structurally at the webSocketSchema.use() call site.
|
|
81
|
-
* `upgradeHeaders` is the real browser upgrade request (carries httpOnly cookies); the
|
|
82
|
-
* in-band `headers` map cannot spoof it.
|
|
83
|
-
*/
|
|
84
|
-
interface WsHandshakeContextLike {
|
|
85
|
-
headers: Record<string, string>
|
|
86
|
-
upgradeHeaders?: Record<string, string>
|
|
87
|
-
queryArgs: Record<string, string>
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface GGCookieBinding extends GGHttpTransportMiddleware {
|
|
91
|
-
/** The cookie this binding reads, mapped to its value schema — emitted as an `in: cookie` doc param. */
|
|
92
|
-
readonly cookieParams: Record<string, GGSchema<string | undefined>>
|
|
93
|
-
parseHandshake(ctx: WsHandshakeContextLike): void
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Bind a context key to a cookie, read-only on the wire. Reads ONLY the named cookie —
|
|
98
|
-
* never a header — from the incoming HTTP Cookie (parseRequest) or the real WS upgrade
|
|
99
|
-
* Cookie (parseHandshake), into key.get(). The cookie's wire name defaults to the key's
|
|
100
|
-
* name. To write the cookie, call setCookie(key, …) from a route that declared
|
|
101
|
-
* .updatesCookie(key); the matching binding flushes it as Set-Cookie (HTTP only — a
|
|
102
|
-
* WebSocket has no response to set a cookie on).
|
|
103
|
-
*/
|
|
104
|
-
export function cookie(key: GGContextKey<string | undefined>, opts?: {name?: string; schema?: GGSchema<string | undefined>}): GGCookieBinding {
|
|
105
|
-
const cookieName = opts?.name ?? key.name
|
|
106
|
-
assertCookieSafe("name", cookieName)
|
|
107
|
-
return {
|
|
108
|
-
headers: {},
|
|
109
|
-
responseHeaders: {},
|
|
110
|
-
cookieParams: {[cookieName]: opts?.schema ?? IsString.orUndefined},
|
|
111
|
-
parseRequest(req: GGHttpRequest): void {
|
|
112
|
-
const raw = req.headers?.["cookie"]
|
|
113
|
-
const value = readCookie(typeof raw === "string" ? raw : undefined, cookieName)
|
|
114
|
-
if (value !== undefined) key.set(value)
|
|
115
|
-
},
|
|
116
|
-
updateResponse(res: GGHttpResponse): void {
|
|
117
|
-
const pending = GG_COOKIE_PENDING.get()?.get(key.name)
|
|
118
|
-
if (pending === undefined) return
|
|
119
|
-
const line = serializeSetCookie(cookieName, pending.value, pending.options)
|
|
120
|
-
const existing = res.headers["set-cookie"]
|
|
121
|
-
const arr = existing === undefined ? [] : Array.isArray(existing) ? existing : [existing]
|
|
122
|
-
arr.push(line)
|
|
123
|
-
res.headers["set-cookie"] = arr
|
|
124
|
-
},
|
|
125
|
-
parseHandshake(ctx: WsHandshakeContextLike): void {
|
|
126
|
-
const value = readCookie(ctx.upgradeHeaders?.["cookie"], cookieName)
|
|
127
|
-
if (value !== undefined) key.set(value)
|
|
128
|
-
},
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Schedule a Set-Cookie on the response (HTTP only). The value never touches key.get() —
|
|
134
|
-
* inbound reads stay set-once. The route must declare .updatesCookie(key); an undeclared
|
|
135
|
-
* call is invalid usage and throws SERVER_ERROR. The matching cookie() binding serializes
|
|
136
|
-
* the pending write with these options (HttpOnly/Secure/SameSite/Path/Domain/Max-Age).
|
|
137
|
-
*/
|
|
138
|
-
export function setCookie(key: GGContextKey<string | undefined>, value: string | undefined, options?: CookieOptions): void {
|
|
139
|
-
if (options) {
|
|
140
|
-
if (options.maxAgeSec !== undefined && !Number.isFinite(options.maxAgeSec)) {
|
|
141
|
-
throw new Error(`setCookie("${key.name}"): maxAgeSec must be a finite number, got ${options.maxAgeSec}.`)
|
|
142
|
-
}
|
|
143
|
-
if (options.path !== undefined) assertCookieSafe("path", options.path)
|
|
144
|
-
if (options.domain !== undefined) assertCookieSafe("domain", options.domain)
|
|
145
|
-
}
|
|
146
|
-
if (!GG_COOKIE_WRITES.get()?.has(key.name)) {
|
|
147
|
-
throw new SERVER_ERROR({debugMessage: `setCookie("${key.name}") was called, but this route did not declare .updatesCookie(<key>). Only routes that declare it may set or clear the cookie.`})
|
|
148
|
-
}
|
|
149
|
-
let pending = GG_COOKIE_PENDING.get()
|
|
150
|
-
if (pending === undefined) {
|
|
151
|
-
pending = new Map()
|
|
152
|
-
GG_COOKIE_PENDING.set(pending)
|
|
153
|
-
}
|
|
154
|
-
pending.set(key.name, {value, options})
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Clear the cookie — schedules a Set-Cookie with Max-Age=0. Pass path/domain to match a
|
|
159
|
-
* scoped cookie (deletion only works when path+domain match what was set). Equivalent to
|
|
160
|
-
* setCookie(key, undefined, options), but reads clearer at logout-style call sites.
|
|
161
|
-
*/
|
|
162
|
-
export function clearCookie(key: GGContextKey<string | undefined>, options?: CookieOptions): void {
|
|
163
|
-
setCookie(key, undefined, options)
|
|
164
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import {GGContextKey} from "@grest-ts/context"
|
|
2
|
-
import {IsString, type GGSchema} from "@grest-ts/schema"
|
|
3
|
-
import type {GGHttpRequest, GGHttpTransportMiddleware} from "./GGHttpSchema"
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Local structural copy of GGWebSocketHandshakeContext (defined in @grest-ts/websocket).
|
|
7
|
-
* http must not import websocket; header()'s WS half is typed against this shape and
|
|
8
|
-
* matches GGWebSocketMiddleware structurally at the webSocketSchema.use() call site.
|
|
9
|
-
* `headers` is the in-band handshake bag (client-set, spoofable — same trust level as an
|
|
10
|
-
* HTTP request header).
|
|
11
|
-
*/
|
|
12
|
-
interface WsHandshakeContextLike {
|
|
13
|
-
headers: Record<string, string>
|
|
14
|
-
upgradeHeaders?: Record<string, string>
|
|
15
|
-
queryArgs: Record<string, string>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface GGHeaderBinding extends GGHttpTransportMiddleware {
|
|
19
|
-
parseHandshake(ctx: WsHandshakeContextLike): void
|
|
20
|
-
updateHandshake(ctx: WsHandshakeContextLike): void
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const BEARER = "Bearer "
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Bind a context key to a single named header, on both transports. Reads ONLY that header
|
|
27
|
-
* — never a cookie — from the HTTP request (parseRequest) or the in-band WS handshake
|
|
28
|
-
* (parseHandshake) into key.get(), and writes it from key.get() on the client (HTTP
|
|
29
|
-
* updateRequest / WS updateHandshake). `scheme: "bearer"` strips/adds the `Bearer ` prefix;
|
|
30
|
-
* default is verbatim. The wire name defaults to the key's name (lowercased — HTTP headers
|
|
31
|
-
* are case-insensitive). Client-attachable, so it is the service-to-service variant (a
|
|
32
|
-
* Node client can carry it; a browser cookie cannot).
|
|
33
|
-
*/
|
|
34
|
-
export function header(key: GGContextKey<string | undefined>, opts?: {name?: string; scheme?: "bearer"; schema?: GGSchema<string | undefined>}): GGHeaderBinding {
|
|
35
|
-
const name = (opts?.name ?? key.name).toLowerCase()
|
|
36
|
-
const bearer = opts?.scheme === "bearer"
|
|
37
|
-
const unwrap = (raw: string | undefined): string | undefined => {
|
|
38
|
-
if (raw === undefined) return undefined
|
|
39
|
-
return bearer && raw.startsWith(BEARER) ? raw.slice(BEARER.length) : raw
|
|
40
|
-
}
|
|
41
|
-
const wrap = (value: string): string => bearer ? `${BEARER}${value}` : value
|
|
42
|
-
return {
|
|
43
|
-
headers: {[name]: opts?.schema ?? IsString.orUndefined},
|
|
44
|
-
responseHeaders: {},
|
|
45
|
-
parseRequest(req: GGHttpRequest): void {
|
|
46
|
-
const raw = req.headers?.[name]
|
|
47
|
-
const value = unwrap(typeof raw === "string" ? raw : Array.isArray(raw) ? raw[0] : undefined)
|
|
48
|
-
if (value !== undefined) key.set(value)
|
|
49
|
-
},
|
|
50
|
-
updateRequest(req: GGHttpRequest): void {
|
|
51
|
-
const value = key.get()
|
|
52
|
-
if (value === undefined) return
|
|
53
|
-
req.headers = req.headers ?? {}
|
|
54
|
-
req.headers[name] = wrap(value)
|
|
55
|
-
},
|
|
56
|
-
parseHandshake(ctx: WsHandshakeContextLike): void {
|
|
57
|
-
const value = unwrap(ctx.headers?.[name])
|
|
58
|
-
if (value !== undefined) key.set(value)
|
|
59
|
-
},
|
|
60
|
-
updateHandshake(ctx: WsHandshakeContextLike): void {
|
|
61
|
-
const value = key.get()
|
|
62
|
-
if (value !== undefined) ctx.headers[name] = wrap(value)
|
|
63
|
-
},
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import {GGContextKey} from "@grest-ts/context";
|
|
2
|
-
import {GGPermissionChecker, IsAny} from "@grest-ts/schema";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Per-request permission context populated by the gate when a scope resolver
|
|
6
|
-
* is wired via GGHttp.usePermissions(...). Handler code can read it to make
|
|
7
|
-
* sub-decisions inside an authorized method:
|
|
8
|
-
*
|
|
9
|
-
* const perm = GG_PERMISSIONS.get();
|
|
10
|
-
* if (perm.has(AppPermission.Admin)) { ... }
|
|
11
|
-
*
|
|
12
|
-
* The checker uses the same satisfies() the framework gate uses — no parallel
|
|
13
|
-
* implementations.
|
|
14
|
-
*/
|
|
15
|
-
export const GG_PERMISSIONS = new GGContextKey<GGPermissionChecker>("permissions", IsAny as any);
|