@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.
Files changed (195) hide show
  1. package/README.md +1 -0
  2. package/dist/Codemods.d.ts +16 -0
  3. package/dist/Codemods.d.ts.map +1 -1
  4. package/dist/Codemods.js +182 -0
  5. package/dist/Codemods.js.map +1 -1
  6. package/dist/Ignitor.d.ts +11 -1
  7. package/dist/Ignitor.d.ts.map +1 -1
  8. package/dist/Ignitor.js +27 -21
  9. package/dist/Ignitor.js.map +1 -1
  10. package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -1
  11. package/dist/bodyparser/BodyParserMiddleware.js +3 -0
  12. package/dist/bodyparser/BodyParserMiddleware.js.map +1 -1
  13. package/dist/bodyparser/qsParse.d.ts +10 -1
  14. package/dist/bodyparser/qsParse.d.ts.map +1 -1
  15. package/dist/bodyparser/qsParse.js +53 -15
  16. package/dist/bodyparser/qsParse.js.map +1 -1
  17. package/dist/codemods/source.d.ts +57 -0
  18. package/dist/codemods/source.d.ts.map +1 -0
  19. package/dist/codemods/source.js +282 -0
  20. package/dist/codemods/source.js.map +1 -0
  21. package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
  22. package/dist/graphql/GraphQLEngine.js +3 -2
  23. package/dist/graphql/GraphQLEngine.js.map +1 -1
  24. package/dist/http/Exception.d.ts +14 -1
  25. package/dist/http/Exception.d.ts.map +1 -1
  26. package/dist/http/Exception.js +10 -2
  27. package/dist/http/Exception.js.map +1 -1
  28. package/dist/http/HttpContext.d.ts +11 -0
  29. package/dist/http/HttpContext.d.ts.map +1 -1
  30. package/dist/http/HttpContext.js +2 -1
  31. package/dist/http/HttpContext.js.map +1 -1
  32. package/dist/http/RedirectBuilder.d.ts +9 -3
  33. package/dist/http/RedirectBuilder.d.ts.map +1 -1
  34. package/dist/http/RedirectBuilder.js +39 -37
  35. package/dist/http/RedirectBuilder.js.map +1 -1
  36. package/dist/http/Request.d.ts +42 -17
  37. package/dist/http/Request.d.ts.map +1 -1
  38. package/dist/http/Request.js +65 -29
  39. package/dist/http/Request.js.map +1 -1
  40. package/dist/http/SseStream.js +6 -7
  41. package/dist/http/SseStream.js.map +1 -1
  42. package/dist/http/permissions.d.ts +19 -0
  43. package/dist/http/permissions.d.ts.map +1 -0
  44. package/dist/http/permissions.js +28 -0
  45. package/dist/http/permissions.js.map +1 -0
  46. package/dist/http-client/HttpClient.d.ts +111 -0
  47. package/dist/http-client/HttpClient.d.ts.map +1 -0
  48. package/dist/http-client/HttpClient.js +386 -0
  49. package/dist/http-client/HttpClient.js.map +1 -0
  50. package/dist/http-client/addresses.d.ts +14 -0
  51. package/dist/http-client/addresses.d.ts.map +1 -0
  52. package/dist/http-client/addresses.js +57 -0
  53. package/dist/http-client/addresses.js.map +1 -0
  54. package/dist/http-client/fake.d.ts +50 -0
  55. package/dist/http-client/fake.d.ts.map +1 -0
  56. package/dist/http-client/fake.js +95 -0
  57. package/dist/http-client/fake.js.map +1 -0
  58. package/dist/http-client/index.d.ts +7 -0
  59. package/dist/http-client/index.d.ts.map +1 -0
  60. package/dist/http-client/index.js +7 -0
  61. package/dist/http-client/index.js.map +1 -0
  62. package/dist/index.d.ts +5 -4
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +2 -2
  65. package/dist/index.js.map +1 -1
  66. package/dist/middleware/Pipeline.d.ts.map +1 -1
  67. package/dist/middleware/Pipeline.js +6 -3
  68. package/dist/middleware/Pipeline.js.map +1 -1
  69. package/dist/native/generated.d.ts +108 -3
  70. package/dist/native/generated.d.ts.map +1 -1
  71. package/dist/rpc/RpcProvider.d.ts +6 -0
  72. package/dist/rpc/RpcProvider.d.ts.map +1 -1
  73. package/dist/rpc/RpcProvider.js +4 -1
  74. package/dist/rpc/RpcProvider.js.map +1 -1
  75. package/dist/rpc/RpcRouter.d.ts +27 -14
  76. package/dist/rpc/RpcRouter.d.ts.map +1 -1
  77. package/dist/rpc/RpcRouter.js +95 -38
  78. package/dist/rpc/RpcRouter.js.map +1 -1
  79. package/dist/scheduler/locks/RedisLockBackend.d.ts.map +1 -1
  80. package/dist/scheduler/locks/RedisLockBackend.js +13 -2
  81. package/dist/scheduler/locks/RedisLockBackend.js.map +1 -1
  82. package/dist/security/CookieSigner.d.ts.map +1 -1
  83. package/dist/security/CookieSigner.js +17 -3
  84. package/dist/security/CookieSigner.js.map +1 -1
  85. package/dist/security/SignedUrl.d.ts.map +1 -1
  86. package/dist/security/SignedUrl.js +6 -1
  87. package/dist/security/SignedUrl.js.map +1 -1
  88. package/dist/server/Server.d.ts +75 -9
  89. package/dist/server/Server.d.ts.map +1 -1
  90. package/dist/server/Server.js +60 -9
  91. package/dist/server/Server.js.map +1 -1
  92. package/dist/server/nativeGrpc.d.ts +27 -0
  93. package/dist/server/nativeGrpc.d.ts.map +1 -0
  94. package/dist/server/nativeGrpc.js +47 -0
  95. package/dist/server/nativeGrpc.js.map +1 -0
  96. package/dist/server/nativeLimits.d.ts +49 -0
  97. package/dist/server/nativeLimits.d.ts.map +1 -0
  98. package/dist/server/nativeLimits.js +67 -0
  99. package/dist/server/nativeLimits.js.map +1 -0
  100. package/dist/session/Session.d.ts +45 -0
  101. package/dist/session/Session.d.ts.map +1 -1
  102. package/dist/session/Session.js +31 -2
  103. package/dist/session/Session.js.map +1 -1
  104. package/dist/session/SessionMiddleware.d.ts.map +1 -1
  105. package/dist/session/SessionMiddleware.js +52 -13
  106. package/dist/session/SessionMiddleware.js.map +1 -1
  107. package/dist/session/config.d.ts +5 -0
  108. package/dist/session/config.d.ts.map +1 -1
  109. package/dist/session/config.js +8 -0
  110. package/dist/session/config.js.map +1 -1
  111. package/dist/session/drivers/CookieDriver.d.ts +12 -3
  112. package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
  113. package/dist/session/drivers/CookieDriver.js +20 -6
  114. package/dist/session/drivers/CookieDriver.js.map +1 -1
  115. package/dist/session/drivers/DatabaseDriver.d.ts +2 -0
  116. package/dist/session/drivers/DatabaseDriver.d.ts.map +1 -1
  117. package/dist/session/drivers/DatabaseDriver.js +6 -0
  118. package/dist/session/drivers/DatabaseDriver.js.map +1 -1
  119. package/dist/session/drivers/FileDriver.d.ts +8 -0
  120. package/dist/session/drivers/FileDriver.d.ts.map +1 -1
  121. package/dist/session/drivers/FileDriver.js +85 -11
  122. package/dist/session/drivers/FileDriver.js.map +1 -1
  123. package/dist/session/drivers/MemoryDriver.d.ts +2 -0
  124. package/dist/session/drivers/MemoryDriver.d.ts.map +1 -1
  125. package/dist/session/drivers/MemoryDriver.js +21 -5
  126. package/dist/session/drivers/MemoryDriver.js.map +1 -1
  127. package/dist/session/drivers/RedisDriver.d.ts +2 -0
  128. package/dist/session/drivers/RedisDriver.d.ts.map +1 -1
  129. package/dist/session/drivers/RedisDriver.js +6 -0
  130. package/dist/session/drivers/RedisDriver.js.map +1 -1
  131. package/dist/session/errors.d.ts +27 -0
  132. package/dist/session/errors.d.ts.map +1 -1
  133. package/dist/session/errors.js +33 -0
  134. package/dist/session/errors.js.map +1 -1
  135. package/dist/session/templateTags.d.ts +14 -0
  136. package/dist/session/templateTags.d.ts.map +1 -1
  137. package/dist/session/templateTags.js +45 -17
  138. package/dist/session/templateTags.js.map +1 -1
  139. package/dist/storage/StaticMiddleware.d.ts.map +1 -1
  140. package/dist/storage/StaticMiddleware.js +29 -18
  141. package/dist/storage/StaticMiddleware.js.map +1 -1
  142. package/events.darwin-arm64.node +0 -0
  143. package/events.darwin-x64.node +0 -0
  144. package/events.linux-arm64-gnu.node +0 -0
  145. package/events.linux-x64-gnu.node +0 -0
  146. package/events.win32-x64-msvc.node +0 -0
  147. package/index.darwin-arm64.node +0 -0
  148. package/index.darwin-x64.node +0 -0
  149. package/index.linux-arm64-gnu.node +0 -0
  150. package/index.linux-x64-gnu.node +0 -0
  151. package/index.win32-x64-msvc.node +0 -0
  152. package/package.json +11 -2
  153. package/scheduler.darwin-arm64.node +0 -0
  154. package/scheduler.darwin-x64.node +0 -0
  155. package/scheduler.linux-arm64-gnu.node +0 -0
  156. package/scheduler.linux-x64-gnu.node +0 -0
  157. package/scheduler.win32-x64-msvc.node +0 -0
  158. package/src/Codemods.ts +265 -0
  159. package/src/Ignitor.ts +48 -20
  160. package/src/bodyparser/BodyParserMiddleware.ts +3 -0
  161. package/src/bodyparser/qsParse.ts +69 -14
  162. package/src/codemods/source.ts +303 -0
  163. package/src/graphql/GraphQLEngine.ts +3 -2
  164. package/src/http/Exception.ts +17 -2
  165. package/src/http/HttpContext.ts +14 -1
  166. package/src/http/RedirectBuilder.ts +48 -37
  167. package/src/http/Request.ts +87 -29
  168. package/src/http/SseStream.ts +6 -7
  169. package/src/http/permissions.ts +31 -0
  170. package/src/http-client/HttpClient.ts +505 -0
  171. package/src/http-client/addresses.ts +58 -0
  172. package/src/http-client/fake.ts +141 -0
  173. package/src/http-client/index.ts +21 -0
  174. package/src/index.ts +9 -1
  175. package/src/middleware/Pipeline.ts +7 -3
  176. package/src/native/generated.ts +123 -3
  177. package/src/rpc/RpcProvider.ts +10 -1
  178. package/src/rpc/RpcRouter.ts +126 -42
  179. package/src/scheduler/locks/RedisLockBackend.ts +12 -4
  180. package/src/security/CookieSigner.ts +17 -3
  181. package/src/security/SignedUrl.ts +6 -1
  182. package/src/server/Server.ts +103 -9
  183. package/src/server/nativeGrpc.ts +59 -0
  184. package/src/server/nativeLimits.ts +94 -0
  185. package/src/session/Session.ts +72 -2
  186. package/src/session/SessionMiddleware.ts +53 -14
  187. package/src/session/config.ts +7 -0
  188. package/src/session/drivers/CookieDriver.ts +21 -6
  189. package/src/session/drivers/DatabaseDriver.ts +10 -0
  190. package/src/session/drivers/FileDriver.ts +91 -11
  191. package/src/session/drivers/MemoryDriver.ts +20 -5
  192. package/src/session/drivers/RedisDriver.ts +7 -0
  193. package/src/session/errors.ts +48 -0
  194. package/src/session/templateTags.ts +57 -0
  195. package/src/storage/StaticMiddleware.ts +28 -18
@@ -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
- * Enable/disable trusting proxy headers (`X-Forwarded-Proto`/`-Host`/`-For`)
249
- * for `protocol()`/`host()`/`ips()`. OFF by default — turning it on when the
250
- * app is NOT behind a trusted proxy lets clients spoof their scheme/host/IP,
251
- * so it's an explicit opt-in (same stance as {@link setMethodSpoofing}).
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
- setTrustProxy(enabled: boolean): void {
254
- this.#trustProxy = enabled
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 trust-proxy is enabled; otherwise the `Host`
276
- * header. Returns null when neither is present.
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.#trustProxy) {
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`). Honours
291
- * `X-Forwarded-Proto` only under trust-proxy; otherwise the connection scheme
292
- * the Rust layer reported (defaulting to `http`).
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.#trustProxy) {
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 client IP chain (AdonisJS `ips`). Under trust-proxy, the parsed
310
- * `X-Forwarded-For` list (left-most = original client); otherwise just the
311
- * single resolved {@link ip}.
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
- if (this.#trustProxy) {
315
- const forwarded = this.#raw.headers['x-forwarded-for']
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
- /** Get the raw body as a string (decoded from base64 if binary). */
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
- /** Get the raw body as a Buffer (preserves binary integrity for file uploads). */
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
- body: this.body(),
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 = {}
@@ -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). `data` is safe:
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 by SSE but each line must repeat the
261
- // `data: ` prefix. Splitting on `\n` keeps the browser's
262
- // reassembly intact (it joins back with `\n`).
263
- for (const line of data.split('\n')) {
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
+ }