@c9up/ream 0.2.27 → 0.2.29
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 +1 -0
- package/dist/Codemods.d.ts +16 -0
- package/dist/Codemods.d.ts.map +1 -1
- package/dist/Codemods.js +182 -0
- package/dist/Codemods.js.map +1 -1
- package/dist/Ignitor.d.ts +11 -1
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +27 -21
- package/dist/Ignitor.js.map +1 -1
- package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -1
- package/dist/bodyparser/BodyParserMiddleware.js +3 -0
- package/dist/bodyparser/BodyParserMiddleware.js.map +1 -1
- package/dist/bodyparser/qsParse.d.ts +10 -1
- package/dist/bodyparser/qsParse.d.ts.map +1 -1
- package/dist/bodyparser/qsParse.js +53 -15
- package/dist/bodyparser/qsParse.js.map +1 -1
- package/dist/codemods/source.d.ts +57 -0
- package/dist/codemods/source.d.ts.map +1 -0
- package/dist/codemods/source.js +282 -0
- package/dist/codemods/source.js.map +1 -0
- package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
- package/dist/graphql/GraphQLEngine.js +3 -2
- package/dist/graphql/GraphQLEngine.js.map +1 -1
- package/dist/http/Exception.d.ts +14 -1
- package/dist/http/Exception.d.ts.map +1 -1
- package/dist/http/Exception.js +10 -2
- package/dist/http/Exception.js.map +1 -1
- package/dist/http/HttpContext.d.ts +11 -0
- package/dist/http/HttpContext.d.ts.map +1 -1
- package/dist/http/HttpContext.js +2 -1
- package/dist/http/HttpContext.js.map +1 -1
- package/dist/http/RedirectBuilder.d.ts +9 -3
- package/dist/http/RedirectBuilder.d.ts.map +1 -1
- package/dist/http/RedirectBuilder.js +39 -37
- package/dist/http/RedirectBuilder.js.map +1 -1
- package/dist/http/Request.d.ts +42 -17
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +65 -29
- package/dist/http/Request.js.map +1 -1
- package/dist/http/SseStream.js +6 -7
- package/dist/http/SseStream.js.map +1 -1
- package/dist/http/permissions.d.ts +19 -0
- package/dist/http/permissions.d.ts.map +1 -0
- package/dist/http/permissions.js +28 -0
- package/dist/http/permissions.js.map +1 -0
- package/dist/http-client/HttpClient.d.ts +111 -0
- package/dist/http-client/HttpClient.d.ts.map +1 -0
- package/dist/http-client/HttpClient.js +386 -0
- package/dist/http-client/HttpClient.js.map +1 -0
- package/dist/http-client/addresses.d.ts +14 -0
- package/dist/http-client/addresses.d.ts.map +1 -0
- package/dist/http-client/addresses.js +57 -0
- package/dist/http-client/addresses.js.map +1 -0
- package/dist/http-client/fake.d.ts +50 -0
- package/dist/http-client/fake.d.ts.map +1 -0
- package/dist/http-client/fake.js +95 -0
- package/dist/http-client/fake.js.map +1 -0
- package/dist/http-client/index.d.ts +7 -0
- package/dist/http-client/index.d.ts.map +1 -0
- package/dist/http-client/index.js +7 -0
- package/dist/http-client/index.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware/Pipeline.d.ts.map +1 -1
- package/dist/middleware/Pipeline.js +6 -3
- package/dist/middleware/Pipeline.js.map +1 -1
- package/dist/native/generated.d.ts +108 -3
- package/dist/native/generated.d.ts.map +1 -1
- package/dist/rpc/RpcProvider.d.ts +6 -0
- package/dist/rpc/RpcProvider.d.ts.map +1 -1
- package/dist/rpc/RpcProvider.js +4 -1
- package/dist/rpc/RpcProvider.js.map +1 -1
- package/dist/rpc/RpcRouter.d.ts +27 -14
- package/dist/rpc/RpcRouter.d.ts.map +1 -1
- package/dist/rpc/RpcRouter.js +95 -38
- package/dist/rpc/RpcRouter.js.map +1 -1
- package/dist/scheduler/locks/RedisLockBackend.d.ts.map +1 -1
- package/dist/scheduler/locks/RedisLockBackend.js +13 -2
- package/dist/scheduler/locks/RedisLockBackend.js.map +1 -1
- package/dist/security/CookieSigner.d.ts.map +1 -1
- package/dist/security/CookieSigner.js +17 -3
- package/dist/security/CookieSigner.js.map +1 -1
- package/dist/security/SignedUrl.d.ts.map +1 -1
- package/dist/security/SignedUrl.js +6 -1
- package/dist/security/SignedUrl.js.map +1 -1
- package/dist/server/Server.d.ts +75 -9
- package/dist/server/Server.d.ts.map +1 -1
- package/dist/server/Server.js +60 -9
- package/dist/server/Server.js.map +1 -1
- package/dist/server/nativeGrpc.d.ts +27 -0
- package/dist/server/nativeGrpc.d.ts.map +1 -0
- package/dist/server/nativeGrpc.js +47 -0
- package/dist/server/nativeGrpc.js.map +1 -0
- package/dist/server/nativeLimits.d.ts +49 -0
- package/dist/server/nativeLimits.d.ts.map +1 -0
- package/dist/server/nativeLimits.js +67 -0
- package/dist/server/nativeLimits.js.map +1 -0
- package/dist/session/Session.d.ts +45 -0
- package/dist/session/Session.d.ts.map +1 -1
- package/dist/session/Session.js +31 -2
- package/dist/session/Session.js.map +1 -1
- package/dist/session/SessionMiddleware.d.ts.map +1 -1
- package/dist/session/SessionMiddleware.js +52 -13
- package/dist/session/SessionMiddleware.js.map +1 -1
- package/dist/session/config.d.ts +5 -0
- package/dist/session/config.d.ts.map +1 -1
- package/dist/session/config.js +8 -0
- package/dist/session/config.js.map +1 -1
- package/dist/session/drivers/CookieDriver.d.ts +12 -3
- package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
- package/dist/session/drivers/CookieDriver.js +20 -6
- package/dist/session/drivers/CookieDriver.js.map +1 -1
- package/dist/session/drivers/DatabaseDriver.d.ts +2 -0
- package/dist/session/drivers/DatabaseDriver.d.ts.map +1 -1
- package/dist/session/drivers/DatabaseDriver.js +6 -0
- package/dist/session/drivers/DatabaseDriver.js.map +1 -1
- package/dist/session/drivers/FileDriver.d.ts +8 -0
- package/dist/session/drivers/FileDriver.d.ts.map +1 -1
- package/dist/session/drivers/FileDriver.js +85 -11
- package/dist/session/drivers/FileDriver.js.map +1 -1
- package/dist/session/drivers/MemoryDriver.d.ts +2 -0
- package/dist/session/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/session/drivers/MemoryDriver.js +21 -5
- package/dist/session/drivers/MemoryDriver.js.map +1 -1
- package/dist/session/drivers/RedisDriver.d.ts +2 -0
- package/dist/session/drivers/RedisDriver.d.ts.map +1 -1
- package/dist/session/drivers/RedisDriver.js +6 -0
- package/dist/session/drivers/RedisDriver.js.map +1 -1
- package/dist/session/errors.d.ts +27 -0
- package/dist/session/errors.d.ts.map +1 -1
- package/dist/session/errors.js +33 -0
- package/dist/session/errors.js.map +1 -1
- package/dist/session/templateTags.d.ts +14 -0
- package/dist/session/templateTags.d.ts.map +1 -1
- package/dist/session/templateTags.js +45 -17
- package/dist/session/templateTags.js.map +1 -1
- package/dist/storage/StaticMiddleware.d.ts.map +1 -1
- package/dist/storage/StaticMiddleware.js +29 -18
- package/dist/storage/StaticMiddleware.js.map +1 -1
- package/events.darwin-arm64.node +0 -0
- package/events.darwin-x64.node +0 -0
- package/events.linux-arm64-gnu.node +0 -0
- package/events.linux-x64-gnu.node +0 -0
- package/events.win32-x64-msvc.node +0 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +11 -2
- package/scheduler.darwin-arm64.node +0 -0
- package/scheduler.darwin-x64.node +0 -0
- package/scheduler.linux-arm64-gnu.node +0 -0
- package/scheduler.linux-x64-gnu.node +0 -0
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/Codemods.ts +265 -0
- package/src/Ignitor.ts +48 -20
- package/src/bodyparser/BodyParserMiddleware.ts +3 -0
- package/src/bodyparser/qsParse.ts +69 -14
- package/src/codemods/source.ts +303 -0
- package/src/graphql/GraphQLEngine.ts +3 -2
- package/src/http/Exception.ts +17 -2
- package/src/http/HttpContext.ts +14 -1
- package/src/http/RedirectBuilder.ts +48 -37
- package/src/http/Request.ts +87 -29
- package/src/http/SseStream.ts +6 -7
- package/src/http/permissions.ts +31 -0
- package/src/http-client/HttpClient.ts +505 -0
- package/src/http-client/addresses.ts +58 -0
- package/src/http-client/fake.ts +141 -0
- package/src/http-client/index.ts +21 -0
- package/src/index.ts +9 -1
- package/src/middleware/Pipeline.ts +7 -3
- package/src/native/generated.ts +123 -3
- package/src/rpc/RpcProvider.ts +10 -1
- package/src/rpc/RpcRouter.ts +126 -42
- package/src/scheduler/locks/RedisLockBackend.ts +12 -4
- package/src/security/CookieSigner.ts +17 -3
- package/src/security/SignedUrl.ts +6 -1
- package/src/server/Server.ts +103 -9
- package/src/server/nativeGrpc.ts +59 -0
- package/src/server/nativeLimits.ts +94 -0
- package/src/session/Session.ts +72 -2
- package/src/session/SessionMiddleware.ts +53 -14
- package/src/session/config.ts +7 -0
- package/src/session/drivers/CookieDriver.ts +21 -6
- package/src/session/drivers/DatabaseDriver.ts +10 -0
- package/src/session/drivers/FileDriver.ts +91 -11
- package/src/session/drivers/MemoryDriver.ts +20 -5
- package/src/session/drivers/RedisDriver.ts +7 -0
- package/src/session/errors.ts +48 -0
- package/src/session/templateTags.ts +57 -0
- package/src/storage/StaticMiddleware.ts +28 -18
package/src/http/Request.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { SignedUrl } from '../security/SignedUrl.js'
|
|
|
13
13
|
import type { Dict } from '../types/helpers.js'
|
|
14
14
|
import { Macroable } from '../utils/Macroable.js'
|
|
15
15
|
import { getPath, omitPaths, pickPaths } from '../utils/objectPath.js'
|
|
16
|
+
import { Exception } from './Exception.js'
|
|
16
17
|
import type {
|
|
17
18
|
RequestAwareValidator,
|
|
18
19
|
RequestValidationOptions,
|
|
@@ -37,6 +38,26 @@ export interface RawRequest {
|
|
|
37
38
|
* `ip()` accessor falls back to legacy header parsing.
|
|
38
39
|
*/
|
|
39
40
|
ip?: string
|
|
41
|
+
/**
|
|
42
|
+
* The trusted address chain, closest hop first — the socket peer, then each
|
|
43
|
+
* `X-Forwarded-For` entry the Rust layer walked right to left while the hop
|
|
44
|
+
* before it was a trusted proxy. Its last entry is {@link ip}. Absent in
|
|
45
|
+
* hand-built fixtures, where the chain is the peer alone.
|
|
46
|
+
*/
|
|
47
|
+
ips?: string[]
|
|
48
|
+
/**
|
|
49
|
+
* Whether the socket peer is one of the trusted proxies configured with
|
|
50
|
+
* `server.trustedProxies()`, as the Rust layer decided against the same
|
|
51
|
+
* list that resolves {@link ip}. Absent in hand-built fixtures: untrusted.
|
|
52
|
+
*/
|
|
53
|
+
peerTrusted?: boolean
|
|
54
|
+
/**
|
|
55
|
+
* The body was over its content type's limit (`config/bodyparser.ts`): the
|
|
56
|
+
* Rust layer did not keep it, and `body` is empty. Reading the body then
|
|
57
|
+
* throws `E_REQUEST_ENTITY_TOO_LARGE` instead of handing back nothing as if
|
|
58
|
+
* nothing had been sent.
|
|
59
|
+
*/
|
|
60
|
+
bodyTooLarge?: boolean
|
|
40
61
|
/**
|
|
41
62
|
* Connection scheme as seen by the server (`http`/`https`), when the Rust
|
|
42
63
|
* layer ships it. Defaults to `http` in `protocol()` when absent; the
|
|
@@ -72,7 +93,6 @@ export class Request extends Macroable {
|
|
|
72
93
|
#cookieSigner?: CookieSigner
|
|
73
94
|
#signedUrl?: SignedUrl
|
|
74
95
|
#allowMethodSpoofing = false
|
|
75
|
-
#trustProxy = false
|
|
76
96
|
#routeInfo?: { name?: string; pattern: string; reference?: string }
|
|
77
97
|
#response?: { fresh(): boolean }
|
|
78
98
|
#parsedBody: Dict<unknown> | undefined
|
|
@@ -245,13 +265,17 @@ export class Request extends Macroable {
|
|
|
245
265
|
}
|
|
246
266
|
|
|
247
267
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
268
|
+
* Whether the socket peer is one of the trusted proxies configured with
|
|
269
|
+
* `server.trustedProxies()` — the condition under which `X-Forwarded-Proto`
|
|
270
|
+
* and `X-Forwarded-Host` are believed by {@link protocol} and {@link host}.
|
|
271
|
+
*
|
|
272
|
+
* The decision is the Rust layer's (`peerTrusted`), made once per request
|
|
273
|
+
* against the same list that resolves {@link ip}, so a trusted proxy that
|
|
274
|
+
* forwards the scheme or host without an `X-Forwarded-For` is believed too.
|
|
275
|
+
* A request without the flag — a hand-built fixture — is untrusted.
|
|
252
276
|
*/
|
|
253
|
-
|
|
254
|
-
this.#
|
|
277
|
+
#peerIsTrustedProxy(): boolean {
|
|
278
|
+
return this.#raw.peerTrusted === true
|
|
255
279
|
}
|
|
256
280
|
|
|
257
281
|
/**
|
|
@@ -272,11 +296,12 @@ export class Request extends Macroable {
|
|
|
272
296
|
|
|
273
297
|
/**
|
|
274
298
|
* The request host including port (AdonisJS `host`). Honours
|
|
275
|
-
* `X-Forwarded-Host` ONLY when
|
|
276
|
-
* header. Returns null when
|
|
299
|
+
* `X-Forwarded-Host` ONLY when the request came through a trusted proxy (see
|
|
300
|
+
* `server.trustedProxies()`); otherwise the `Host` header. Returns null when
|
|
301
|
+
* neither is present.
|
|
277
302
|
*/
|
|
278
303
|
host(): string | null {
|
|
279
|
-
if (this.#
|
|
304
|
+
if (this.#peerIsTrustedProxy()) {
|
|
280
305
|
const forwarded = this.#raw.headers['x-forwarded-host']
|
|
281
306
|
// The first hop in the list, named rather than indexed: `split` always
|
|
282
307
|
// yields one element, and saying so beats being told it might not.
|
|
@@ -287,12 +312,15 @@ export class Request extends Macroable {
|
|
|
287
312
|
}
|
|
288
313
|
|
|
289
314
|
/**
|
|
290
|
-
* The request protocol — `http` or `https` (AdonisJS `protocol`).
|
|
291
|
-
* `
|
|
292
|
-
*
|
|
315
|
+
* The request protocol — `http` or `https` (AdonisJS `protocol`). A TLS
|
|
316
|
+
* connection is `https` whatever the headers say. Otherwise
|
|
317
|
+
* `X-Forwarded-Proto` is honoured only when the request came through a
|
|
318
|
+
* trusted proxy (see `server.trustedProxies()`), and the connection scheme
|
|
319
|
+
* the Rust layer reported (defaulting to `http`) is used when it is not.
|
|
293
320
|
*/
|
|
294
321
|
protocol(): string {
|
|
295
|
-
if (this.#
|
|
322
|
+
if (this.#raw.scheme === 'https') return 'https'
|
|
323
|
+
if (this.#peerIsTrustedProxy()) {
|
|
296
324
|
const forwarded = this.#raw.headers['x-forwarded-proto']
|
|
297
325
|
const [first] = forwarded?.split(',') ?? []
|
|
298
326
|
if (first !== undefined) return first.trim().toLowerCase()
|
|
@@ -306,20 +334,15 @@ export class Request extends Macroable {
|
|
|
306
334
|
}
|
|
307
335
|
|
|
308
336
|
/**
|
|
309
|
-
* The
|
|
310
|
-
* `X-Forwarded-For`
|
|
311
|
-
*
|
|
337
|
+
* The trusted address chain, closest hop first (AdonisJS `ips`): the socket
|
|
338
|
+
* peer, then each `X-Forwarded-For` hop a trusted proxy vouched for. The
|
|
339
|
+
* last entry is {@link ip}. Hops added by an untrusted peer are not in it —
|
|
340
|
+
* the Rust layer stops at the first address it does not trust, so the chain
|
|
341
|
+
* goes past the peer only when {@link peerTrusted} holds.
|
|
312
342
|
*/
|
|
313
343
|
ips(): string[] {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (forwarded) {
|
|
317
|
-
return forwarded
|
|
318
|
-
.split(',')
|
|
319
|
-
.map((value) => value.trim())
|
|
320
|
-
.filter((value) => value.length > 0)
|
|
321
|
-
}
|
|
322
|
-
}
|
|
344
|
+
const chain = this.#raw.ips
|
|
345
|
+
if (chain && chain.length > 0 && this.#peerIsTrustedProxy()) return [...chain]
|
|
323
346
|
return [this.ip()]
|
|
324
347
|
}
|
|
325
348
|
|
|
@@ -477,17 +500,26 @@ export class Request extends Macroable {
|
|
|
477
500
|
return this.#signedUrl.verify(this.url(true), purpose)
|
|
478
501
|
}
|
|
479
502
|
|
|
480
|
-
/**
|
|
503
|
+
/**
|
|
504
|
+
* Get the raw body as a string (decoded from base64 if binary). Throws
|
|
505
|
+
* `E_REQUEST_ENTITY_TOO_LARGE` (413) when the body was over its limit: what
|
|
506
|
+
* the server kept of it is nothing, and nothing is not the body.
|
|
507
|
+
*/
|
|
481
508
|
raw(): string {
|
|
482
509
|
if (this.#rawBodyOverride !== undefined) return this.#rawBodyOverride
|
|
510
|
+
this.#assertBodyKept()
|
|
483
511
|
if (this.#raw.bodyEncoding === 'base64') {
|
|
484
512
|
return Buffer.from(this.#raw.body, 'base64').toString('utf8')
|
|
485
513
|
}
|
|
486
514
|
return this.#raw.body
|
|
487
515
|
}
|
|
488
516
|
|
|
489
|
-
/**
|
|
517
|
+
/**
|
|
518
|
+
* Get the raw body as a Buffer (preserves binary integrity for file uploads).
|
|
519
|
+
* Throws `E_REQUEST_ENTITY_TOO_LARGE` (413) when the body was over its limit.
|
|
520
|
+
*/
|
|
490
521
|
rawBuffer(): Buffer {
|
|
522
|
+
this.#assertBodyKept()
|
|
491
523
|
if (this.#raw.bodyEncoding === 'base64') {
|
|
492
524
|
return Buffer.from(this.#raw.body, 'base64')
|
|
493
525
|
}
|
|
@@ -716,7 +748,8 @@ export class Request extends Macroable {
|
|
|
716
748
|
id: this.id(),
|
|
717
749
|
url: this.url(true),
|
|
718
750
|
query: this.parsedUrl().search?.slice(1) ?? '',
|
|
719
|
-
|
|
751
|
+
// A dump must not throw: an over-limit body is reported as absent.
|
|
752
|
+
body: this.#bodyWasDropped() ? undefined : this.body(),
|
|
720
753
|
params: this.params(),
|
|
721
754
|
headers: this.headers(),
|
|
722
755
|
method: this.method(),
|
|
@@ -1062,8 +1095,33 @@ export class Request extends Macroable {
|
|
|
1062
1095
|
return this.#validated
|
|
1063
1096
|
}
|
|
1064
1097
|
|
|
1098
|
+
/** The body was over its limit and nothing has replaced it since. */
|
|
1099
|
+
#bodyWasDropped(): boolean {
|
|
1100
|
+
return (
|
|
1101
|
+
this.#raw.bodyTooLarge === true &&
|
|
1102
|
+
this.#parsedBody === undefined &&
|
|
1103
|
+
this.#rawBodyOverride === undefined
|
|
1104
|
+
)
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Refuse to read a body the server did not keep. The same 413 the body
|
|
1109
|
+
* parser raises, so a route without it still answers the client properly
|
|
1110
|
+
* the moment it reaches for the body — and a route that never does is not
|
|
1111
|
+
* charged for it.
|
|
1112
|
+
*/
|
|
1113
|
+
#assertBodyKept(): void {
|
|
1114
|
+
if (this.#raw.bodyTooLarge === true) {
|
|
1115
|
+
throw new Exception('request entity too large', {
|
|
1116
|
+
status: 413,
|
|
1117
|
+
code: 'E_REQUEST_ENTITY_TOO_LARGE',
|
|
1118
|
+
})
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1065
1122
|
#ensureParsedBody(): void {
|
|
1066
1123
|
if (this.#parsedBody !== undefined) return
|
|
1124
|
+
this.#assertBodyKept()
|
|
1067
1125
|
const raw = this.#raw.body
|
|
1068
1126
|
if (!raw || raw.length === 0) {
|
|
1069
1127
|
this.#parsedBody = {}
|
package/src/http/SseStream.ts
CHANGED
|
@@ -253,14 +253,13 @@ function encodeFrame(event: string, data: string, eventId?: string): string {
|
|
|
253
253
|
const lines: string[] = []
|
|
254
254
|
// `event:` and `id:` are single-line tokens — strip CR/LF so a
|
|
255
255
|
// caller-supplied event name or id can't splice extra SSE frames
|
|
256
|
-
// (the same injection `comment()` guards against).
|
|
257
|
-
// it's split on `\n` and each line re-prefixed, so newlines there
|
|
258
|
-
// stay inside one logical event.
|
|
256
|
+
// (the same injection `comment()` guards against).
|
|
259
257
|
if (event) lines.push(`event: ${event.replace(/[\r\n]+/g, ' ')}`)
|
|
260
|
-
// Multi-line data is allowed
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
|
|
258
|
+
// Multi-line data is allowed but each line must repeat the `data: `
|
|
259
|
+
// prefix. The event-stream parser ends a line on CRLF, LF AND a lone
|
|
260
|
+
// CR, so split on all three: a payload CR left in place would end the
|
|
261
|
+
// line and let the rest of the payload forge its own fields.
|
|
262
|
+
for (const line of data.split(/\r\n|\r|\n/)) {
|
|
264
263
|
lines.push(`data: ${line}`)
|
|
265
264
|
}
|
|
266
265
|
if (eventId) lines.push(`id: ${eventId.replace(/[\r\n]+/g, ' ')}`)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The permission check shared by the route, RPC and GraphQL gates, so the
|
|
3
|
+
* three cannot drift apart.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AuthState } from './HttpContext.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Whether the request's identity holds every permission in `required` — what
|
|
10
|
+
* the route, RPC and GraphQL `.permission()` gates ask.
|
|
11
|
+
*
|
|
12
|
+
* The permissions are the user's own (top-level `auth.permissions`, or
|
|
13
|
+
* `auth.user.permissions` where warden puts them). When the request was
|
|
14
|
+
* authenticated with a token, each permission must ALSO be allowed by
|
|
15
|
+
* `auth.user.currentAccessToken`: an intersection, never a union. A token
|
|
16
|
+
* scope named like a permission grants nothing the user lacks, and a token
|
|
17
|
+
* limited to `profile` does not carry every permission of its user. A token
|
|
18
|
+
* whose `allows` cannot be called denies.
|
|
19
|
+
*/
|
|
20
|
+
export function holdsPermissions(
|
|
21
|
+
auth: AuthState | undefined,
|
|
22
|
+
required: readonly string[],
|
|
23
|
+
): boolean {
|
|
24
|
+
const granted = auth?.permissions ?? auth?.user?.permissions ?? []
|
|
25
|
+
const token = auth?.user?.currentAccessToken
|
|
26
|
+
return required.every((permission) => {
|
|
27
|
+
if (!granted.includes(permission)) return false
|
|
28
|
+
if (token === undefined || token === null) return true
|
|
29
|
+
return typeof token.allows === 'function' && token.allows(permission) === true
|
|
30
|
+
})
|
|
31
|
+
}
|