permdock 0.0.0 → 0.1.0

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 (290) hide show
  1. package/README.md +268 -1
  2. package/dist/a2a/index.d.ts +178 -0
  3. package/dist/a2a/index.js +271 -0
  4. package/dist/ai-sdk/index.d.ts +74 -0
  5. package/dist/ai-sdk/index.js +137 -0
  6. package/dist/approval-required-TzNB9oQA.js +6 -0
  7. package/dist/approvals/index.d.ts +3 -0
  8. package/dist/approvals/index.js +4 -0
  9. package/dist/approvals-DaxY-Bdg.js +218 -0
  10. package/dist/approvers-aH0PJkmf.js +20 -0
  11. package/dist/arazzo-CTeVuXr5.js +70 -0
  12. package/dist/arazzo-aoTkqYsJ.js +54 -0
  13. package/dist/authzen/index.d.ts +48 -0
  14. package/dist/authzen/index.js +336 -0
  15. package/dist/better-auth/index.d.ts +92 -0
  16. package/dist/better-auth/index.js +324 -0
  17. package/dist/budget-JqcRqyhK.d.ts +11 -0
  18. package/dist/budget-NbEtbJC_.js +11 -0
  19. package/dist/capability-B68OFnZx.js +171 -0
  20. package/dist/capability-BDG_nkxt.d.ts +99 -0
  21. package/dist/catalog/index.d.ts +12 -0
  22. package/dist/catalog/index.js +2 -0
  23. package/dist/catalog-B0E6bggc.js +48 -0
  24. package/dist/catalog-H-vFGwWK.js +55 -0
  25. package/dist/catalog-doc-BSo_Q_-6.js +298 -0
  26. package/dist/catalog-idsHc0vo.js +2 -0
  27. package/dist/catalog-path-CnI0XTFK.js +12 -0
  28. package/dist/claude-agent/index.d.ts +81 -0
  29. package/dist/claude-agent/index.js +83 -0
  30. package/dist/clerk/index.d.ts +56 -0
  31. package/dist/clerk/index.js +280 -0
  32. package/dist/cli/bin.d.ts +1 -0
  33. package/dist/cli/bin.js +20 -0
  34. package/dist/cli/index.d.ts +46 -0
  35. package/dist/cli/index.js +5 -0
  36. package/dist/cloud/index.d.ts +91 -0
  37. package/dist/cloud/index.js +390 -0
  38. package/dist/cloud-BNQTrmrr.js +50 -0
  39. package/dist/cloud-Cuxr6Czw.js +117 -0
  40. package/dist/collect-B7_TIfmg.js +49 -0
  41. package/dist/collect-O-tOtbnk.js +350 -0
  42. package/dist/collect-fRPAQIWb.js +2 -0
  43. package/dist/commands-BMh_Etm2.js +93 -0
  44. package/dist/compact-y6vfu2_f.js +19 -0
  45. package/dist/compare-pfyVnBjE.js +12 -0
  46. package/dist/compile-CQbvyIBr.d.ts +30 -0
  47. package/dist/compile-D22iwvuV.js +558 -0
  48. package/dist/config-BPYmjTOp.js +26 -0
  49. package/dist/connection-JvqwHODC.js +194 -0
  50. package/dist/context-CKEAabd7.js +73 -0
  51. package/dist/context-gtAwAILx.js +6 -0
  52. package/dist/convex/index.d.ts +40 -0
  53. package/dist/convex/index.js +55 -0
  54. package/dist/create-BKF3dgFk.d.ts +162 -0
  55. package/dist/create-CSkhDU9T.js +222 -0
  56. package/dist/credential-B_rkUNlY.js +326 -0
  57. package/dist/credentials-BgRX3WEg.js +268 -0
  58. package/dist/describe-Bt1YD5uS.js +99 -0
  59. package/dist/describe-DernyItf.d.ts +37 -0
  60. package/dist/diff-BUzf5Poi.js +48 -0
  61. package/dist/diff-x5Il8LVa.js +418 -0
  62. package/dist/digest-B3kc_PgQ.d.ts +19 -0
  63. package/dist/digest-BI-6aa6d.js +27 -0
  64. package/dist/doctor-BGle8Gqi.js +1314 -0
  65. package/dist/doctor-Bqo6FwUa.js +53 -0
  66. package/dist/drizzle/index.d.ts +72 -0
  67. package/dist/drizzle/index.js +160 -0
  68. package/dist/duration-xK2jGFVc.js +25 -0
  69. package/dist/elysia/index.d.ts +71 -0
  70. package/dist/elysia/index.js +103 -0
  71. package/dist/errors-DSMnAVNa.js +43 -0
  72. package/dist/errors-DbZ1YOnb.d.ts +138 -0
  73. package/dist/errors-Iy71s7Oj.js +177 -0
  74. package/dist/errors-m3u9TmDu.js +27 -0
  75. package/dist/evaluations-DsAU9ppl.js +479 -0
  76. package/dist/eve/index.d.ts +95 -0
  77. package/dist/eve/index.js +181 -0
  78. package/dist/express/index.d.ts +46 -0
  79. package/dist/express/index.js +108 -0
  80. package/dist/fastify/index.d.ts +56 -0
  81. package/dist/fastify/index.js +141 -0
  82. package/dist/files-B_wAGYmt.js +121 -0
  83. package/dist/fixtures-Cvaw0gDG.js +64 -0
  84. package/dist/freeze-y1_jQL4v.js +31 -0
  85. package/dist/from-snapshot-BMGnhmgv.js +552 -0
  86. package/dist/generate-CH7ThJ14.js +173 -0
  87. package/dist/grantee-Bv04_Vzf.js +521 -0
  88. package/dist/headers-B5RRv3Xm.js +6 -0
  89. package/dist/headers-C8SHQccD.d.ts +6 -0
  90. package/dist/helpers-DnSOrhB9.js +682 -0
  91. package/dist/hono/index.d.ts +70 -0
  92. package/dist/hono/index.js +131 -0
  93. package/dist/hosted-BwKQ1BTA.js +265 -0
  94. package/dist/http-DPuDaChb.d.ts +13 -0
  95. package/dist/http-GgVApX1k.js +111 -0
  96. package/dist/index-CltcQ4Bf.d.ts +138 -0
  97. package/dist/index-LF_hS-yn.d.ts +177 -0
  98. package/dist/index-fG6NSXlc.d.ts +9 -0
  99. package/dist/index-xIxJLwOQ.d.ts +415 -0
  100. package/dist/index.d.ts +11 -0
  101. package/dist/index.js +21 -0
  102. package/dist/jwt/index.d.ts +87 -0
  103. package/dist/jwt/index.js +571 -0
  104. package/dist/kernel-YOGCWNLf.js +211 -0
  105. package/dist/kysely/index.d.ts +77 -0
  106. package/dist/kysely/index.js +129 -0
  107. package/dist/load-BZ24CpZP.js +69 -0
  108. package/dist/load-QOOm1z70.js +2 -0
  109. package/dist/load-jose-BL6LH4bL.js +11 -0
  110. package/dist/lru-8q4WZjBV.js +28 -0
  111. package/dist/manifest-DDQbC6K0.d.ts +118 -0
  112. package/dist/map-DowSO96j.js +158 -0
  113. package/dist/map-claims-B_6I1Rn-.js +360 -0
  114. package/dist/map-error-RJ3ZsUyA.js +16 -0
  115. package/dist/markers-B0hYLvMD.js +60 -0
  116. package/dist/may-use-CnFEjVEp.js +37 -0
  117. package/dist/mcp/index.d.ts +127 -0
  118. package/dist/mcp/index.js +563 -0
  119. package/dist/nest/index.d.ts +80 -0
  120. package/dist/nest/index.js +270 -0
  121. package/dist/next/client.d.ts +24 -0
  122. package/dist/next/client.js +31 -0
  123. package/dist/next/index.d.ts +110 -0
  124. package/dist/next/index.js +195 -0
  125. package/dist/next/plugin.d.ts +14 -0
  126. package/dist/next/plugin.js +2 -0
  127. package/dist/node/index.d.ts +43 -0
  128. package/dist/node/index.js +66 -0
  129. package/dist/openai/index.d.ts +71 -0
  130. package/dist/openai/index.js +105 -0
  131. package/dist/openapi/index.d.ts +93 -0
  132. package/dist/openapi/index.js +3 -0
  133. package/dist/openapi-CwePaF7C.js +164 -0
  134. package/dist/openapi-DWD0Uc_4.js +250 -0
  135. package/dist/openapi-DcqM8nU7.js +306 -0
  136. package/dist/openapi-import-BWRUNNeW.js +5959 -0
  137. package/dist/openapi-schema-D8j_T9tO.js +6902 -0
  138. package/dist/orpc/index.d.ts +64 -0
  139. package/dist/orpc/index.js +188 -0
  140. package/dist/otel/index.d.ts +13 -0
  141. package/dist/otel/index.js +202 -0
  142. package/dist/package-root-zGkxO3oN.js +30 -0
  143. package/dist/parse-DPfIvFMU.js +135 -0
  144. package/dist/pdp/index.d.ts +22 -0
  145. package/dist/pdp/index.js +795 -0
  146. package/dist/peer-BOr_kJE1.js +31 -0
  147. package/dist/permdock-yoZ7EjxX.js +2956 -0
  148. package/dist/permissions-B4apnahQ.d.ts +185 -0
  149. package/dist/permissions-B8vCg_bI.d.ts +52 -0
  150. package/dist/permissions-CofRqesP.js +142 -0
  151. package/dist/permissions-DC7GROPA.js +600 -0
  152. package/dist/pg-4GJWDUxG.js +953 -0
  153. package/dist/pins-CqE1jZuF.js +12 -0
  154. package/dist/plugin-4gr7IxFJ.js +65 -0
  155. package/dist/policy-ANp36PnI.js +971 -0
  156. package/dist/policy-CInmNGkM.d.ts +1959 -0
  157. package/dist/policy-Cq2fB8Q5.d.ts +20 -0
  158. package/dist/prisma/index.d.ts +135 -0
  159. package/dist/prisma/index.js +430 -0
  160. package/dist/problem-Cgp9-KZA.js +226 -0
  161. package/dist/prompt-CXYhLfsS.js +32 -0
  162. package/dist/protected-B4K3BKoX.js +138 -0
  163. package/dist/protected-BuPAycJT.d.ts +26 -0
  164. package/dist/protected-wY7nXhY9.d.ts +23 -0
  165. package/dist/provider-_wx46HpB.js +48 -0
  166. package/dist/provider-wTAUda2v.d.ts +6 -0
  167. package/dist/react/index.d.ts +6 -0
  168. package/dist/react/index.js +6 -0
  169. package/dist/react/provider-client.d.ts +2 -0
  170. package/dist/react/provider-client.js +3 -0
  171. package/dist/react-native/index.d.ts +42 -0
  172. package/dist/react-native/index.js +174 -0
  173. package/dist/refs-ChLSxbST.js +90 -0
  174. package/dist/relations-r4soIFZB.js +426 -0
  175. package/dist/revocations-Cy06JNx2.d.ts +21 -0
  176. package/dist/rls-BOFU3chG.js +233 -0
  177. package/dist/rls-CJVja9Ww.js +2119 -0
  178. package/dist/rls-graph-DxQ39PJj.js +371 -0
  179. package/dist/rls-helpers-BxRuif0l.js +1346 -0
  180. package/dist/rls-import-CXV6LBlS.js +942 -0
  181. package/dist/rls-migrate-DrFPbf8p.js +366 -0
  182. package/dist/row-check-BajTgQZ-.d.ts +43 -0
  183. package/dist/run-CJ6UvS3a.js +155 -0
  184. package/dist/runtime-DP9ttFh1.js +27 -0
  185. package/dist/saas-BRrZzJkM.js +1193 -0
  186. package/dist/schema-P8yoma__.js +296 -0
  187. package/dist/scim/index.d.ts +2 -0
  188. package/dist/scim/index.js +2 -0
  189. package/dist/scim-9NFa0XVZ.js +1445 -0
  190. package/dist/scopes-cKVp_WTw.js +212 -0
  191. package/dist/server/index.d.ts +123 -0
  192. package/dist/server/index.js +8 -0
  193. package/dist/sha256-BO0swRXL.js +150 -0
  194. package/dist/sink-Bd9PSyfq.js +141 -0
  195. package/dist/sink-Cfr9-7YC.d.ts +104 -0
  196. package/dist/skills-C_lpV4Jx.js +50 -0
  197. package/dist/skills-Dx3QLnMY.js +110 -0
  198. package/dist/snapshot-DE8dE_hz.js +1051 -0
  199. package/dist/solid/index.d.ts +63 -0
  200. package/dist/solid/index.js +197 -0
  201. package/dist/source-DoBBYJi2.js +6 -0
  202. package/dist/sources-DOpW5Ila.d.ts +347 -0
  203. package/dist/sources-DVHM3Mwq.js +284 -0
  204. package/dist/sql-Bll1d7y0.js +22 -0
  205. package/dist/src-CTNT-PBW.js +430 -0
  206. package/dist/ssf/index.d.ts +3 -0
  207. package/dist/ssf/index.js +622 -0
  208. package/dist/store-Bq90395i.d.ts +27 -0
  209. package/dist/store-C6hFW6ta.js +508 -0
  210. package/dist/stores-CkG2HrSg.js +140 -0
  211. package/dist/stores-CvOnu_2T.d.ts +41 -0
  212. package/dist/stream-BhMfZOwr.d.ts +12 -0
  213. package/dist/stream-Q_xlB6Ko.js +52 -0
  214. package/dist/style-CTm1AXDT.js +22 -0
  215. package/dist/subject-C6dLxqHU.js +24 -0
  216. package/dist/subject-settings-xt7V845g.js +82 -0
  217. package/dist/supabase/index.d.ts +67 -0
  218. package/dist/supabase/index.js +4 -0
  219. package/dist/supabase/middleware.d.ts +77 -0
  220. package/dist/supabase/middleware.js +68 -0
  221. package/dist/supabase-D8UlBA7Z.js +83 -0
  222. package/dist/supabase-DTxGtYdi.js +796 -0
  223. package/dist/supabase-hook-DW-xP4hU.js +2 -0
  224. package/dist/supabase-hook-D_OqGb_V.js +1528 -0
  225. package/dist/svelte/Protected.svelte +34 -0
  226. package/dist/svelte/index.d.ts +10 -0
  227. package/dist/svelte/index.js +53 -0
  228. package/dist/svelte/runtime.d.ts +6 -0
  229. package/dist/svelte/runtime.js +3 -0
  230. package/dist/svelte/source.d.ts +10 -0
  231. package/dist/svelte/source.js +8 -0
  232. package/dist/terminal/index.d.ts +171 -0
  233. package/dist/terminal/index.js +821 -0
  234. package/dist/testing/index.d.ts +656 -0
  235. package/dist/testing/index.js +3208 -0
  236. package/dist/testing/saas/index.d.ts +194 -0
  237. package/dist/testing/saas/index.js +3 -0
  238. package/dist/testing/saas/permissions.d.ts +2 -0
  239. package/dist/testing/saas/permissions.js +2 -0
  240. package/dist/text-diff-BdkkYSbi.js +15 -0
  241. package/dist/thenable-okc2xDsj.js +10 -0
  242. package/dist/timeout-D51niuQP.js +8 -0
  243. package/dist/trpc/index.d.ts +80 -0
  244. package/dist/trpc/index.js +205 -0
  245. package/dist/types-B8w5PFtS.d.ts +141 -0
  246. package/dist/types-B9dmiiab.d.ts +66 -0
  247. package/dist/types-BBK-exvJ.d.ts +177 -0
  248. package/dist/types-BvBk1wgn.d.ts +118 -0
  249. package/dist/types-CxquUByI.d.ts +9 -0
  250. package/dist/types-D6Z6-rKJ.d.ts +312 -0
  251. package/dist/types-DbWdEfcm.d.ts +87 -0
  252. package/dist/types-bva3i4Gf.d.ts +98 -0
  253. package/dist/types-i4BAOvdb.d.ts +97 -0
  254. package/dist/unplugin/index.d.ts +7 -0
  255. package/dist/unplugin/index.js +30 -0
  256. package/dist/usage-1ozMubLY.js +42 -0
  257. package/dist/usage-CUnK6Ou9.js +530 -0
  258. package/dist/usage-PyEor6GA.js +2 -0
  259. package/dist/validity-D0uRmJKJ.js +224 -0
  260. package/dist/verifier-B90agnhl.js +461 -0
  261. package/dist/version-CnSh33Fj.js +16 -0
  262. package/dist/vocabulary-Dk0fs-Jk.d.ts +56 -0
  263. package/dist/vue/index.d.ts +57 -0
  264. package/dist/vue/index.js +203 -0
  265. package/dist/webmcp/index.d.ts +75 -0
  266. package/dist/webmcp/index.js +313 -0
  267. package/package.json +423 -4
  268. package/rulesets/permdock.yaml +29 -0
  269. package/schemas/catalog-v1.json +507 -0
  270. package/schemas/openapi/oas-3.1.json +1298 -0
  271. package/schemas/openapi/oas-3.2.json +1516 -0
  272. package/schemas/openapi/overlay-1.1.json +79 -0
  273. package/schemas/supabase-claims-v1.json +139 -0
  274. package/schemas/supabase-manifest-v1.json +217 -0
  275. package/skills/permdock/SKILL.md +66 -0
  276. package/skills/permdock-agents/SKILL.md +77 -0
  277. package/skills/permdock-agents/references/runtimes.md +211 -0
  278. package/skills/permdock-approvals/SKILL.md +80 -0
  279. package/skills/permdock-approvals/references/stores-and-resume.md +83 -0
  280. package/skills/permdock-audit/SKILL.md +67 -0
  281. package/skills/permdock-credentials/SKILL.md +55 -0
  282. package/skills/permdock-credentials/references/tokens-keys-links.md +127 -0
  283. package/skills/permdock-data/SKILL.md +57 -0
  284. package/skills/permdock-data/references/queries-and-rls.md +54 -0
  285. package/skills/permdock-data/references/relationships.md +65 -0
  286. package/skills/permdock-tenancy/SKILL.md +77 -0
  287. package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +83 -0
  288. package/skills/permdock-wire/SKILL.md +116 -0
  289. package/skills/permdock-wire/references/adapters.md +480 -0
  290. package/skills/permdock-wire/references/policy-options.md +43 -0
@@ -0,0 +1,796 @@
1
+ import { t as compact } from "./compact-y6vfu2_f.js";
2
+ import { n as freezeDeep } from "./freeze-y1_jQL4v.js";
3
+ import { t as ignoreRejection } from "./thenable-okc2xDsj.js";
4
+ import { t as anonymousSubject } from "./subject-C6dLxqHU.js";
5
+ import { o as parseCapability } from "./capability-B68OFnZx.js";
6
+ import { t as loadJose } from "./load-jose-BL6LH4bL.js";
7
+ import "./budget-NbEtbJC_.js";
8
+ import { a as quoteSqlTable, i as quoteSqlLiteral, r as quoteSqlIdent } from "./sql-Bll1d7y0.js";
9
+ import "./sources-DVHM3Mwq.js";
10
+ //#region src/supabase/capability.ts
11
+ const DEFAULT_TTL = 300;
12
+ const MAX_TTL = 3600;
13
+ function isSecret(key) {
14
+ return typeof key.secret === "string";
15
+ }
16
+ /**
17
+ * Exchanges a verified link subject (from `subjectFromCapability`) for a
18
+ * short-lived Supabase access token with role `anon` and the capability in a
19
+ * `capability` claim, which `permdock rls generate --capabilities` policies
20
+ * read. `undefined` for any subject that is not a live link.
21
+ */
22
+ async function exchangeCapability(subject, options) {
23
+ const alg = options.alg ?? "ES256";
24
+ const secret = isSecret(options.key);
25
+ if (secret !== (alg === "HS256")) throw new Error("PermDock: exchangeCapability signs HS256 with { secret } only, and ES256 / RS256 with a private JWK only.");
26
+ if (!secret && (options.kid === void 0 || options.kid.length === 0)) throw new Error("PermDock: exchangeCapability needs the kid of the imported signing key.");
27
+ const principal = subject.principal;
28
+ if (principal === null || principal.kind !== "link") return;
29
+ const capability = parseCapability(principal["capability"]);
30
+ if (capability?.holder !== "link" || capability.id !== principal.id) return;
31
+ const now = Math.floor(options.now ?? Date.now() / 1e3);
32
+ const ttl = Math.min(Math.max(1, options.ttl ?? DEFAULT_TTL), MAX_TTL);
33
+ const exp = Math.min(now + ttl, Math.floor(capability.expiresAt));
34
+ if (exp <= now) return;
35
+ const jose = await loadJose();
36
+ const key = isSecret(options.key) ? new TextEncoder().encode(options.key.secret) : await jose.importJWK(options.key, alg);
37
+ const jwt = new jose.SignJWT({
38
+ role: "anon",
39
+ capability
40
+ }).setProtectedHeader(secret ? {
41
+ alg: "HS256",
42
+ typ: "JWT"
43
+ } : {
44
+ alg,
45
+ ...options.kid === void 0 ? {} : { kid: options.kid },
46
+ typ: "JWT"
47
+ }).setIssuedAt(now).setExpirationTime(exp);
48
+ if (options.issuer !== void 0) jwt.setIssuer(options.issuer);
49
+ return jwt.sign(key);
50
+ }
51
+ //#endregion
52
+ //#region src/supabase/claims.ts
53
+ const KNOWN = /* @__PURE__ */ new Set([
54
+ "user_role",
55
+ "roles",
56
+ "memberships",
57
+ "memberships_truncated",
58
+ "attrs",
59
+ "authz_ver",
60
+ "client_id",
61
+ "scope",
62
+ "act",
63
+ "app_metadata"
64
+ ]);
65
+ function isRecord$1(value) {
66
+ return value !== null && typeof value === "object" && !Array.isArray(value);
67
+ }
68
+ function own(record, key) {
69
+ return Object.hasOwn(record, key) ? record[key] : void 0;
70
+ }
71
+ function issue(issues, path, message) {
72
+ issues.push({
73
+ message,
74
+ path: [...path]
75
+ });
76
+ }
77
+ function isStrings(value) {
78
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
79
+ }
80
+ function optional(record, key, path, issues, check, expected) {
81
+ const value = own(record, key);
82
+ if (value !== void 0 && !check(value)) issue(issues, [...path, key], `Expected ${expected}`);
83
+ }
84
+ const isString = (value) => typeof value === "string";
85
+ function checkMembership(item, path, issues) {
86
+ if (!isRecord$1(item)) {
87
+ issue(issues, path, "Expected a membership object");
88
+ return;
89
+ }
90
+ const roles = own(item, "roles");
91
+ if (!isStrings(roles) || roles.length === 0) issue(issues, [...path, "roles"], "Expected a non-empty array of strings");
92
+ for (const key of [
93
+ "scope",
94
+ "id",
95
+ "tenant",
96
+ "team",
97
+ "via",
98
+ "grantedBy",
99
+ "reason"
100
+ ]) optional(item, key, path, issues, isString, "a string");
101
+ optional(item, "within", path, issues, (value) => isRecord$1(value) && Object.values(value).every((id) => typeof id === "string"), "an object of string ids");
102
+ optional(item, "on", path, issues, (value) => isRecord$1(value) && typeof own(value, "resource") === "string" && typeof own(value, "id") === "string", "an object with string resource and id");
103
+ optional(item, "expiresAt", path, issues, (value) => typeof value === "number" && Number.isFinite(value), "a number of seconds since the epoch");
104
+ optional(item, "member", path, issues, (value) => isRecord$1(value) && typeof own(value, "group") === "string" && own(value, "group") !== "", "an object with a non-empty string group");
105
+ optional(item, "managedBy", path, issues, (value) => value === "idp", "'idp'");
106
+ optional(item, "entitlements", path, issues, isStrings, "an array of strings");
107
+ optional(item, "grants", path, issues, isRecord$1, "an object");
108
+ if (!(own(item, "scope") !== void 0 && own(item, "id") !== void 0 || own(item, "tenant") !== void 0 || own(item, "team") !== void 0 || own(item, "on") !== void 0)) issue(issues, path, "Expected scope and id, tenant, team or on");
109
+ }
110
+ function checkPermDockClaims(record, tenantClaim, path, issues) {
111
+ optional(record, "user_role", path, issues, (value) => value === null || typeof value === "string" || isStrings(value), "a string, an array of strings or null");
112
+ optional(record, "roles", path, issues, isStrings, "an array of strings");
113
+ const memberships = own(record, "memberships");
114
+ if (memberships !== void 0) {
115
+ if (Array.isArray(memberships)) for (const [index, item] of memberships.entries()) checkMembership(item, [
116
+ ...path,
117
+ "memberships",
118
+ index
119
+ ], issues);
120
+ else issue(issues, [...path, "memberships"], "Expected an array");
121
+ }
122
+ optional(record, "memberships_truncated", path, issues, (value) => typeof value === "boolean", "a boolean");
123
+ optional(record, tenantClaim, path, issues, isString, "a string");
124
+ optional(record, "attrs", path, issues, isRecord$1, "an object");
125
+ optional(record, "authz_ver", path, issues, (value) => Number.isInteger(value), "an integer");
126
+ }
127
+ /** The outer level's `kind`: none, `support` with a `session_id`, or `impersonation`. */
128
+ function checkActKind(level, path, issues) {
129
+ const marked = own(level, "kind");
130
+ const kind = marked === void 0 && own(level, "session_id") !== void 0 ? "support" : marked;
131
+ optional(level, "reason", path, issues, isString, "a string");
132
+ if (kind === void 0 || kind === "impersonation") return;
133
+ if (kind !== "support") {
134
+ issue(issues, [...path, "kind"], "Expected 'support' or 'impersonation'");
135
+ return;
136
+ }
137
+ const session = own(level, "session_id");
138
+ if (typeof session !== "string" || session === "") issue(issues, [...path, "session_id"], "Expected a non-empty string");
139
+ optional(level, "read_only", path, issues, (value) => typeof value === "boolean", "a boolean");
140
+ }
141
+ function checkAct(value, issues) {
142
+ let current = value;
143
+ const path = ["act"];
144
+ while (current !== void 0) {
145
+ if (!isRecord$1(current)) {
146
+ issue(issues, path, "Expected an object");
147
+ return;
148
+ }
149
+ const sub = own(current, "sub");
150
+ if (typeof sub !== "string" || sub === "") issue(issues, [...path, "sub"], "Expected a non-empty string");
151
+ if (path.length === 1) checkActKind(current, path, issues);
152
+ current = own(current, "act");
153
+ path.push("act");
154
+ }
155
+ }
156
+ function checkClaims(value, tenantClaim) {
157
+ const issues = [];
158
+ if (!isRecord$1(value)) {
159
+ issue(issues, [], "Expected a claims object");
160
+ return issues;
161
+ }
162
+ checkPermDockClaims(value, tenantClaim, [], issues);
163
+ optional(value, "client_id", [], issues, isString, "a string");
164
+ optional(value, "scope", [], issues, (scope) => typeof scope === "string" || isStrings(scope), "a string or an array of strings");
165
+ const act = own(value, "act");
166
+ if (act !== void 0) checkAct(act, issues);
167
+ const meta = own(value, "app_metadata");
168
+ if (meta !== void 0) {
169
+ if (isRecord$1(meta)) checkPermDockClaims(meta, tenantClaim, ["app_metadata"], issues);
170
+ else issue(issues, ["app_metadata"], "Expected an object");
171
+ }
172
+ return issues;
173
+ }
174
+ function merge(base, app) {
175
+ const issues = [...base.issues ?? [], ...app.issues ?? []];
176
+ if (issues.length > 0) return { issues };
177
+ const baseValue = base.value;
178
+ const appValue = app.value;
179
+ if (!isRecord$1(appValue)) return { issues: [{ message: "Expected the extension to output an object" }] };
180
+ return { value: isRecord$1(baseValue) ? {
181
+ ...baseValue,
182
+ ...appValue
183
+ } : appValue };
184
+ }
185
+ function schemaOf(validate) {
186
+ const schema = {
187
+ "~standard": Object.freeze({
188
+ version: 1,
189
+ vendor: "permdock",
190
+ validate
191
+ }),
192
+ extend(app) {
193
+ if (!isRecord$1(app) && typeof app !== "function") throw new TypeError("PermDock: supabaseClaims().extend needs a Standard Schema");
194
+ const props = Reflect.get(app, "~standard");
195
+ if (!isRecord$1(props) || typeof props["validate"] !== "function") throw new TypeError("PermDock: supabaseClaims().extend needs a Standard Schema");
196
+ const run = app["~standard"].validate.bind(app["~standard"]);
197
+ return schemaOf((value) => {
198
+ const base = validate(value);
199
+ const extra = run(value);
200
+ if (base instanceof Promise || extra instanceof Promise) return Promise.all([base, extra]).then(([b, e]) => merge(b, e));
201
+ return merge(base, extra);
202
+ });
203
+ }
204
+ };
205
+ return Object.freeze(schema);
206
+ }
207
+ /**
208
+ * A Standard Schema v1 for PermDock's Supabase claims. Unknown claims pass
209
+ * through unchanged; a known claim with the wrong shape is an issue, so the
210
+ * whole token is rejected as `schemas/supabase-claims-v1.json` rejects it.
211
+ * Needs no validation library.
212
+ *
213
+ * ```ts
214
+ * sb.claims(supabaseClaims().extend(z.object({ datetime_preferences: Prefs })))
215
+ * ```
216
+ */
217
+ function supabaseClaims(options = {}) {
218
+ const tenantClaim = options.tenantClaim ?? "tenant_id";
219
+ if (typeof tenantClaim !== "string" || tenantClaim === "" || KNOWN.has(tenantClaim)) throw new TypeError(`PermDock: supabaseClaims tenantClaim must be a claim name other than ${[...KNOWN].join(", ")}`);
220
+ return schemaOf((value) => {
221
+ const issues = checkClaims(value, tenantClaim);
222
+ return issues.length > 0 ? { issues } : { value };
223
+ });
224
+ }
225
+ //#endregion
226
+ //#region src/supabase/rls.ts
227
+ function isTable(value) {
228
+ return value !== null && typeof value === "object" && "table" in value && typeof value.table === "string";
229
+ }
230
+ function supabaseRls(options = {}) {
231
+ const raw = options.memberships;
232
+ const memberships = raw === void 0 ? void 0 : isTable(raw) ? { tenant: raw } : raw;
233
+ return compact({
234
+ dialect: "supabase",
235
+ roleClaim: options.roleClaim ?? "user_role",
236
+ tenantClaim: options.tenantClaim ?? "tenant_id",
237
+ tenantType: options.tenantType,
238
+ memberships,
239
+ suspension: options.suspension
240
+ });
241
+ }
242
+ const ident = (name) => quoteSqlIdent(name);
243
+ const table = (name) => quoteSqlTable(name);
244
+ const literal = quoteSqlLiteral;
245
+ function qualifiedTable(name) {
246
+ return table(name.includes(".") ? name : `public.${name}`);
247
+ }
248
+ function textArray(values) {
249
+ return values.length === 0 ? `'{}'::text[]` : `array[${values.map(literal).join(", ")}]::text[]`;
250
+ }
251
+ function activeRow(row, id, text = false) {
252
+ const parts = [`s.${ident(row.id)}${text ? "::text" : ""} = ${id}`];
253
+ if (row.disabledAt !== void 0) parts.push(`s.${ident(row.disabledAt)} is null`);
254
+ if (row.status !== void 0) {
255
+ if (row.active === void 0 || row.active.length === 0) throw new TypeError("PermDock: a suspension status column needs its active values");
256
+ parts.push(`s.${ident(row.status)}::text = any(${textArray(row.active)})`);
257
+ }
258
+ if (row.disabledAt === void 0 && row.status === void 0) throw new TypeError("PermDock: a suspension table needs disabledAt or status");
259
+ return `exists (select 1 from ${qualifiedTable(row.table)} s where ${parts.join(" and ")})`;
260
+ }
261
+ /** Early `return false` for a suspended user, or a tenant request for a suspended instance. */
262
+ function suspendedGuards(suspension, scope, uid) {
263
+ const lines = [];
264
+ const users = suspension?.users;
265
+ if (users !== void 0) lines.push(` if not ${activeRow(users, uid)} then
266
+ return false; -- suspended user
267
+ end if;`);
268
+ const tenant = suspension?.scopes?.[scope];
269
+ if (tenant !== void 0) lines.push(` if requested_tenant is not null and not ${activeRow(tenant, "requested_tenant", true)} then
270
+ return false; -- suspended ${scope}
271
+ end if;`);
272
+ return lines.map((line) => `\n${line}`).join("");
273
+ }
274
+ function claimEntries(where, value) {
275
+ return `array(select ${value} from jsonb_array_elements_text(cg.g) e where ${where})`;
276
+ }
277
+ /** A custom role's keys hold `requested_permission` in the first scope. */
278
+ function customHolds(q, scope, allows, denies, includes) {
279
+ return `exists (
280
+ select 1 from ${q("role_permissions")} rp
281
+ where rp.permission = requested_permission::text
282
+ and rp.scope = ${literal(scope)}
283
+ and rp.effect = 'allow'
284
+ and rp.grant_key in (select ${q("permdock_custom_keys")}(
285
+ ${allows},
286
+ ${denies},
287
+ ${includes},
288
+ ${literal(scope)}
289
+ ))
290
+ )`;
291
+ }
292
+ function customDatabaseBranch(q, scope, memberships, declared) {
293
+ const match = `c.tenant_id::text = requested_tenant and c.scope = ${literal(scope)} and c.scope_id is null and c.role = m.${ident(memberships.role)}::text`;
294
+ const rows = (source, value, extra) => `array(select c.${value} from ${q(source)} c where ${match}${extra})`;
295
+ return ` or exists (
296
+ select 1
297
+ from ${qualifiedTable(memberships.table)} m
298
+ where m.${ident(memberships.user)} = v_member_user
299
+ and m.${ident(memberships.tenant)} = v_member_tenant
300
+ and not (m.${ident(memberships.role)}::text = any(${textArray(declared)}))${memberships.expiresAt === void 0 ? "" : `\n and (m.${ident(memberships.expiresAt)} is null or m.${ident(memberships.expiresAt)} > now())`}
301
+ and ${customHolds(q, scope, rows("custom_role_permissions", "permission", " and c.effect = 'allow'"), rows("custom_role_permissions", "permission", " and c.effect = 'deny'"), rows("custom_role_includes", "include_role", ""))}
302
+ )`;
303
+ }
304
+ /**
305
+ * The `role_permissions` match for one effect. An allow counts only without
306
+ * row conditions (`grant_key = permission`): `authorize()` never sees the row.
307
+ * A deny counts with or without them, so a conditional deny still denies.
308
+ */
309
+ function effectMatch(scope, effect) {
310
+ return `rp.permission = requested_permission::text
311
+ and rp.scope = ${scope}
312
+ and rp.effect = '${effect}'${effect === "allow" ? "\n and rp.grant_key = rp.permission" : ""}`;
313
+ }
314
+ function databaseBody(q, scope, memberships, custom, suspension) {
315
+ const global = (effect) => `exists (
316
+ select 1
317
+ from ${q("user_roles")} ur
318
+ join ${q("role_permissions")} rp on rp.role = ur.role::text
319
+ where ur.user_id = uid
320
+ and ${effectMatch(`'global'`, effect)}
321
+ )`;
322
+ const tenantColumn = memberships?.tenant;
323
+ let tenantBranch = ` if requested_tenant is not null then
324
+ return false; -- no memberships table configured
325
+ end if;`;
326
+ if (memberships !== void 0 && tenantColumn !== void 0) {
327
+ const held = (effect) => `exists (
328
+ select 1
329
+ from ${qualifiedTable(memberships.table)} m
330
+ join ${q("role_permissions")} rp on rp.role = m.${ident(memberships.role)}::text
331
+ where m.${ident(memberships.user)} = v_member_user
332
+ and m.${ident(tenantColumn)} = v_member_tenant
333
+ and ${effectMatch(literal(scope), effect)}${memberships.expiresAt === void 0 ? "" : `\n and (m.${ident(memberships.expiresAt)} is null or m.${ident(memberships.expiresAt)} > now())`}
334
+ )`;
335
+ tenantBranch = ` if requested_tenant is not null then
336
+ begin
337
+ v_member_user := uid;
338
+ v_member_tenant := requested_tenant;
339
+ exception when invalid_text_representation or numeric_value_out_of_range then
340
+ return false; -- not an id of the memberships table
341
+ end;
342
+ return (${held("allow")}${custom === void 0 ? "" : customDatabaseBranch(q, scope, {
343
+ ...memberships,
344
+ tenant: tenantColumn
345
+ }, custom.declared)})
346
+ and not ${held("deny")}
347
+ and not ${global("deny")};
348
+ end if;`;
349
+ }
350
+ return `declare
351
+ uid uuid := (select auth.uid());${memberships !== void 0 && tenantColumn !== void 0 ? `
352
+ v_member_user ${qualifiedTable(memberships.table)}.${ident(memberships.user)}%type;
353
+ v_member_tenant ${qualifiedTable(memberships.table)}.${ident(tenantColumn)}%type;` : ""}
354
+ begin
355
+ if uid is null then
356
+ return false;
357
+ end if;${suspendedGuards(suspension, scope, "uid")}
358
+ ${tenantBranch}
359
+ return ${global("allow")}
360
+ and not ${global("deny")};
361
+ end;`;
362
+ }
363
+ /** A claim membership whose `expiresAt` (epoch seconds) has passed no longer holds its roles. */
364
+ const CLAIM_UNEXPIRED = `case jsonb_typeof(m -> 'expiresAt')
365
+ when 'number' then (m ->> 'expiresAt')::numeric > extract(epoch from now())
366
+ else true
367
+ end`;
368
+ function jwtBody(q, scope, custom, suspension) {
369
+ const memberships = `jsonb_array_elements(
370
+ case jsonb_typeof(coalesce(claims -> 'memberships', claims -> 'app_metadata' -> 'memberships'))
371
+ when 'array' then coalesce(claims -> 'memberships', claims -> 'app_metadata' -> 'memberships')
372
+ else '[]'::jsonb
373
+ end
374
+ ) m
375
+ cross join lateral jsonb_array_elements_text(
376
+ case jsonb_typeof(m -> 'roles') when 'array' then m -> 'roles' else '[]'::jsonb end
377
+ ) r(role)`;
378
+ const customBranch = custom === void 0 ? "" : ` or exists (
379
+ select 1
380
+ from ${memberships}
381
+ cross join lateral (select m -> 'grants' -> r.role as g) cg
382
+ where m ->> 'scope' = ${literal(scope)}
383
+ and m ->> 'id' = requested_tenant
384
+ and ${CLAIM_UNEXPIRED}
385
+ and jsonb_typeof(cg.g) = 'array'
386
+ and not (r.role = any(${textArray(custom.declared)}))
387
+ and ${customHolds(q, scope, claimEntries("left(e, 1) not in ('-', '@')", "e"), claimEntries("left(e, 1) = '-'", "substr(e, 2)"), claimEntries("left(e, 1) = '@'", "substr(e, 2)"))}
388
+ )`;
389
+ const held = (effect) => `exists (
390
+ select 1
391
+ from ${memberships}
392
+ join ${q("role_permissions")} rp on rp.role = r.role
393
+ where m ->> 'scope' = ${literal(scope)}
394
+ and m ->> 'id' = requested_tenant
395
+ and ${CLAIM_UNEXPIRED}
396
+ and ${effectMatch(literal(scope), effect)}
397
+ )`;
398
+ const global = (effect) => `exists (
399
+ select 1
400
+ from jsonb_array_elements_text(
401
+ case jsonb_typeof(role_claim)
402
+ when 'array' then role_claim
403
+ when 'string' then jsonb_build_array(role_claim)
404
+ else '[]'::jsonb
405
+ end
406
+ ) r(role)
407
+ join ${q("role_permissions")} rp on rp.role = r.role
408
+ where ${effectMatch(`'global'`, effect)}
409
+ )`;
410
+ return `declare
411
+ claims jsonb := (select auth.jwt());
412
+ role_claim jsonb;
413
+ begin
414
+ if claims is null or (select auth.uid()) is null then
415
+ return false;
416
+ end if;${suspendedGuards(suspension, scope, "(select auth.uid())")}
417
+ -- a top-level null (no role row) falls back to app_metadata, like subjectFromSupabase
418
+ role_claim := coalesce(nullif(claims -> 'user_role', 'null'::jsonb), claims -> 'app_metadata' -> 'user_role');
419
+ if requested_tenant is not null then
420
+ return (${held("allow")}${customBranch})
421
+ and not ${held("deny")}
422
+ and not ${global("deny")};
423
+ end if;
424
+ return ${global("allow")}
425
+ and not ${global("deny")};
426
+ end;`;
427
+ }
428
+ /**
429
+ * `authorize(requested_permission, requested_tenant text default null)` for Supabase's RBAC
430
+ * scaffold, over the `role_permissions (role, permission, grant_key, scope, effect)` table that
431
+ * `permdock rls generate` seeds. `database` reads `user_roles` (and the memberships table for a
432
+ * tenant) on every call; `jwt` reads the hook-injected `user_role` and `memberships` claims. A
433
+ * tenant request with no memberships source is denied, never answered from global roles. It
434
+ * answers "does a role hold this permission" without seeing a row: an allow with row conditions
435
+ * does not count, and a deny the user's tenant or global roles hold overrides every allow.
436
+ * Generated policies call the per-statement RLS helpers (`permdock_has`, `permitted_<scope>_ids`)
437
+ * instead. Only `authenticated` may execute it.
438
+ */
439
+ function authorizeSql(options = {}) {
440
+ const schema = options.schema ?? "permdock";
441
+ const q = (name) => table(`${schema}.${name}`);
442
+ const memberships = typeof options.tenant === "object" ? options.tenant : void 0;
443
+ const scope = options.scope ?? "tenant";
444
+ if (!/^[a-z][a-z0-9_]*$/u.test(scope)) throw new TypeError(`PermDock: unsafe scope name '${scope}'`);
445
+ const body = options.authorize === "jwt" ? jwtBody(q, scope, options.customRoles, options.suspension) : databaseBody(q, scope, memberships, options.customRoles, options.suspension);
446
+ const signature = `${q("authorize")}(${q("app_permission")}, text)`;
447
+ return `create or replace function ${q("authorize")}(
448
+ requested_permission ${q("app_permission")},
449
+ requested_tenant text default null
450
+ )
451
+ returns boolean
452
+ language plpgsql
453
+ stable
454
+ security definer
455
+ set search_path = ''
456
+ as $$
457
+ ${body}
458
+ $$;
459
+ revoke execute on function ${signature} from public, anon;
460
+ grant execute on function ${signature} to authenticated;
461
+ `;
462
+ }
463
+ //#endregion
464
+ //#region src/supabase/subject.ts
465
+ const REGISTERED = /* @__PURE__ */ new Set([
466
+ "sub",
467
+ "role",
468
+ "iss",
469
+ "aud",
470
+ "exp",
471
+ "iat",
472
+ "nbf",
473
+ "aal",
474
+ "amr",
475
+ "acr",
476
+ "session_id",
477
+ "email",
478
+ "phone",
479
+ "is_anonymous",
480
+ "app_metadata",
481
+ "user_metadata",
482
+ "act",
483
+ "client_id",
484
+ "scope"
485
+ ]);
486
+ function isRecord(value) {
487
+ return value !== null && typeof value === "object" && !Array.isArray(value);
488
+ }
489
+ /** Top-level first, then `app_metadata`. `null` counts as absent: the RBAC hook writes `null` for a user with no role row. */
490
+ function readClaim(claims, name) {
491
+ const top = Object.hasOwn(claims, name) ? claims[name] : void 0;
492
+ if (top !== void 0 && top !== null) return top;
493
+ const meta = claims["app_metadata"];
494
+ if (isRecord(meta) && Object.hasOwn(meta, name)) return meta[name] ?? void 0;
495
+ }
496
+ function asRoles(value, declared) {
497
+ const raw = typeof value === "string" ? value === "" ? [] : [value] : Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
498
+ if (declared === void 0) return raw;
499
+ const allowed = new Set(declared);
500
+ return raw.filter((role) => allowed.has(role));
501
+ }
502
+ /** Reads the `memberships` claim; an entry it cannot read is dropped and its index reported. */
503
+ function readMemberships(value) {
504
+ if (!Array.isArray(value)) return {
505
+ memberships: [],
506
+ dropped: []
507
+ };
508
+ const out = [];
509
+ const dropped = [];
510
+ for (const [index, item] of value.entries()) {
511
+ const membership = asMembership(item);
512
+ if (membership === void 0) dropped.push(index);
513
+ else out.push(membership);
514
+ }
515
+ return {
516
+ memberships: out,
517
+ dropped
518
+ };
519
+ }
520
+ function asMembership(item) {
521
+ if (!isRecord(item) || !Array.isArray(item["roles"])) return;
522
+ const roles = item["roles"].filter((role) => typeof role === "string");
523
+ if (roles.length === 0) return;
524
+ const scope = typeof item["scope"] === "string" ? item["scope"] : void 0;
525
+ const id = typeof item["id"] === "string" ? item["id"] : void 0;
526
+ const within = isRecord(item["within"]) ? Object.fromEntries(Object.entries(item["within"]).filter((entry) => typeof entry[1] === "string")) : void 0;
527
+ const tenant = typeof item["tenant"] === "string" ? item["tenant"] : void 0;
528
+ const team = typeof item["team"] === "string" ? item["team"] : void 0;
529
+ const onRecord = isRecord(item["on"]) ? item["on"] : void 0;
530
+ const on = onRecord !== void 0 && typeof onRecord["resource"] === "string" && typeof onRecord["id"] === "string" ? {
531
+ resource: onRecord["resource"],
532
+ id: onRecord["id"]
533
+ } : void 0;
534
+ if ((scope === void 0 || id === void 0) && tenant === void 0 && team === void 0 && on === void 0) return;
535
+ return compact({
536
+ roles,
537
+ scope,
538
+ id,
539
+ within,
540
+ tenant,
541
+ team,
542
+ on,
543
+ via: typeof item["via"] === "string" ? item["via"] : void 0,
544
+ expiresAt: typeof item["expiresAt"] === "number" ? item["expiresAt"] : void 0,
545
+ grantedBy: typeof item["grantedBy"] === "string" ? item["grantedBy"] : void 0,
546
+ reason: typeof item["reason"] === "string" ? item["reason"] : void 0,
547
+ member: memberOf(item["member"]),
548
+ managedBy: item["managedBy"] === "idp" ? "idp" : void 0,
549
+ entitlements: Array.isArray(item["entitlements"]) ? item["entitlements"].filter((seat) => typeof seat === "string") : void 0
550
+ });
551
+ }
552
+ function memberOf(value) {
553
+ if (value === null || typeof value !== "object" || !Object.hasOwn(value, "group")) return;
554
+ const group = value.group;
555
+ return typeof group === "string" && group !== "" ? { group } : void 0;
556
+ }
557
+ function asStrings(value) {
558
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
559
+ }
560
+ /** `claims[plans][tenant]`: only the active tenant's entry, never another tenant's. */
561
+ function plansFor(claims, name, tenant) {
562
+ if (name === void 0 || tenant === void 0) return [];
563
+ const byTenant = readClaim(claims, name);
564
+ if (!isRecord(byTenant) || !Object.hasOwn(byTenant, tenant)) return [];
565
+ return asStrings(byTenant[tenant]);
566
+ }
567
+ const INVALID_CHAIN = Object.freeze({
568
+ ok: false,
569
+ reason: "invalid-chain"
570
+ });
571
+ const NO_ACTOR = Object.freeze({ ok: true });
572
+ /** Own entries of one `act` level, with the nested `act` copied the same way. */
573
+ function copyAct(level) {
574
+ const entries = Object.entries(level).map(([key, value]) => [key, key === "act" && isRecord(value) ? copyAct(value) : value]);
575
+ return Object.fromEntries(entries);
576
+ }
577
+ /**
578
+ * The outer `act` level by its `kind`, the marker better-supabase writes: none is an OAuth client
579
+ * or agent chain, `support` a support session (with `session_id`), `impersonation` an admin acting
580
+ * as the user. Any other `kind`, or a support level without `session_id`, is invalid.
581
+ */
582
+ function actingParty(outer, id) {
583
+ const marked = Object.hasOwn(outer, "kind") ? outer["kind"] : void 0;
584
+ const kind = marked === void 0 && Object.hasOwn(outer, "session_id") ? "support" : marked;
585
+ const chain = copyAct(outer);
586
+ const reason = typeof outer["reason"] === "string" ? outer["reason"] : void 0;
587
+ switch (kind) {
588
+ case void 0: return freezeDeep({
589
+ ok: true,
590
+ actor: {
591
+ id,
592
+ kind: "oauth-client",
593
+ chain
594
+ }
595
+ });
596
+ case "support": {
597
+ const session = outer["session_id"];
598
+ const readOnly = Object.hasOwn(outer, "read_only") ? outer["read_only"] : false;
599
+ if (typeof session !== "string" || session === "" || typeof readOnly !== "boolean") return INVALID_CHAIN;
600
+ return freezeDeep({
601
+ ok: true,
602
+ actor: compact({
603
+ id,
604
+ kind: "support",
605
+ sessionId: session,
606
+ readOnly,
607
+ reason,
608
+ chain
609
+ })
610
+ });
611
+ }
612
+ case "impersonation": return freezeDeep({
613
+ ok: true,
614
+ actor: compact({
615
+ id,
616
+ kind: "impersonation",
617
+ reason,
618
+ chain
619
+ })
620
+ });
621
+ default: return INVALID_CHAIN;
622
+ }
623
+ }
624
+ function readActor(claims) {
625
+ if (Object.hasOwn(claims, "act") && claims["act"] !== void 0) {
626
+ let current = claims["act"];
627
+ while (current !== void 0) {
628
+ const level = isRecord(current) ? current["sub"] : void 0;
629
+ if (!isRecord(current) || typeof level !== "string" || level === "") return INVALID_CHAIN;
630
+ current = Object.hasOwn(current, "act") ? current["act"] : void 0;
631
+ }
632
+ const outer = claims["act"];
633
+ const sub = isRecord(outer) ? outer["sub"] : void 0;
634
+ if (!isRecord(outer) || typeof sub !== "string") return INVALID_CHAIN;
635
+ return actingParty(outer, sub);
636
+ }
637
+ const client = Object.hasOwn(claims, "client_id") ? claims["client_id"] : void 0;
638
+ if (typeof client === "string" && client !== "") return freezeDeep({
639
+ ok: true,
640
+ actor: {
641
+ id: client,
642
+ kind: "oauth-client"
643
+ }
644
+ });
645
+ return NO_ACTOR;
646
+ }
647
+ /**
648
+ * Who acts for the user: the outermost `sub` of an RFC 8693 `act` chain (the current
649
+ * actor; nested levels are prior actors, kept on `chain` for audit), else the
650
+ * OAuth `client_id` of a third-party app. The outer level's `kind` names the actor:
651
+ * none is `oauth-client`, `support` a support session, `impersonation` an admin acting as the
652
+ * user. Reads only `act` and `client_id`; the caller applies the role rule first (`anon` and
653
+ * `service_role` are anonymous and carry no actor). An `act` that is not a chain of objects each
654
+ * with a non-empty `sub`, that names another `kind`, or a support level without `session_id`,
655
+ * is `{ ok: false }` and must deny.
656
+ */
657
+ function actorOf(claims) {
658
+ try {
659
+ return isRecord(claims) ? readActor(claims) : NO_ACTOR;
660
+ } catch {
661
+ return INVALID_CHAIN;
662
+ }
663
+ }
664
+ /** The OAuth `scope` claim (a space-separated string or a list) as `scopes`; `undefined` when empty. */
665
+ function delegationOf(claims) {
666
+ try {
667
+ if (!isRecord(claims) || !Object.hasOwn(claims, "scope")) return;
668
+ const scope = claims["scope"];
669
+ const scopes = typeof scope === "string" ? scope.split(/\s+/u).filter(Boolean) : asStrings(scope);
670
+ return scopes.length > 0 ? freezeDeep({ scopes }) : void 0;
671
+ } catch {
672
+ return;
673
+ }
674
+ }
675
+ function subjectDelegation(claims, chain) {
676
+ const delegation = compact({
677
+ scopes: delegationOf(claims)?.scopes,
678
+ chain
679
+ });
680
+ return Object.keys(delegation).length === 0 ? void 0 : delegation;
681
+ }
682
+ function emit(options, reason, cause) {
683
+ try {
684
+ options.onAuth?.({
685
+ reason,
686
+ cause,
687
+ source: "supabase"
688
+ });
689
+ } catch {}
690
+ }
691
+ function validateClaims(extra, schema) {
692
+ const result = schema["~standard"].validate(extra);
693
+ if (result instanceof Promise) {
694
+ ignoreRejection(result);
695
+ return;
696
+ }
697
+ if ("issues" in result && result.issues !== void 0) return;
698
+ const value = result.value;
699
+ return isRecord(value) ? value : void 0;
700
+ }
701
+ function extraClaims(claims) {
702
+ const extra = {};
703
+ const meta = claims["app_metadata"];
704
+ if (isRecord(meta)) for (const [key, value] of Object.entries(meta)) extra[key] = value;
705
+ for (const [key, value] of Object.entries(claims)) {
706
+ if (REGISTERED.has(key) || key === "user_metadata") continue;
707
+ extra[key] = value;
708
+ }
709
+ return extra;
710
+ }
711
+ function mapClaims(claims, options) {
712
+ const role = claims["role"];
713
+ if (role === "anon" || role === "service_role") return anonymousSubject();
714
+ const id = claims["sub"];
715
+ if (typeof id !== "string" || id === "") return anonymousSubject();
716
+ const act = readActor(claims);
717
+ if (!act.ok) {
718
+ emit(options, "invalid-token", act.reason);
719
+ return anonymousSubject();
720
+ }
721
+ if (options.anonymousSignIns === "deny" && claims["is_anonymous"] === true) return anonymousSubject();
722
+ const actor = act.actor === void 0 ? void 0 : act.actor.kind === "support" ? {
723
+ id: act.actor.id,
724
+ kind: act.actor.kind,
725
+ sessionId: act.actor.sessionId,
726
+ readOnly: act.actor.readOnly
727
+ } : {
728
+ id: act.actor.id,
729
+ kind: act.actor.kind
730
+ };
731
+ const roleClaim = options.roles ?? "user_role";
732
+ const tenantClaim = options.tenant ?? "tenant_id";
733
+ const membershipsClaim = options.memberships ?? "memberships";
734
+ let extra = extraClaims(claims);
735
+ if (options.schema !== void 0) extra = validateClaims(extra, options.schema) ?? {};
736
+ const include = new Set(options.include ?? []);
737
+ const tenantValue = readClaim(claims, tenantClaim);
738
+ const version = readClaim(claims, "authz_ver");
739
+ const tenant = typeof tenantValue === "string" ? tenantValue : void 0;
740
+ const { memberships, dropped } = readMemberships(readClaim(claims, membershipsClaim));
741
+ if (dropped.length > 0) emit(options, "schema", "membership-dropped");
742
+ const plans = plansFor(claims, options.plans, tenant);
743
+ const principal = compact({
744
+ id,
745
+ kind: "user",
746
+ roles: asRoles(readClaim(claims, roleClaim), options.declared),
747
+ tenant,
748
+ plans: plans.length > 0 ? plans : void 0,
749
+ memberships,
750
+ membershipsTruncated: readClaim(claims, "memberships_truncated") === true ? true : void 0,
751
+ authzVersion: typeof version === "number" && Number.isInteger(version) ? version : void 0,
752
+ issuer: typeof claims["iss"] === "string" ? claims["iss"] : void 0,
753
+ assurance: typeof claims["aal"] === "string" ? { acr: claims["aal"] } : void 0,
754
+ claims: Object.keys(extra).length === 0 ? void 0 : extra,
755
+ email: include.has("email") && typeof claims["email"] === "string" ? claims["email"] : void 0,
756
+ phone: include.has("phone") && typeof claims["phone"] === "string" ? claims["phone"] : void 0,
757
+ is_anonymous: include.has("is_anonymous") && typeof claims["is_anonymous"] === "boolean" ? claims["is_anonymous"] : void 0
758
+ });
759
+ const session = typeof claims["session_id"] === "string" ? claims["session_id"] : void 0;
760
+ const expiresAt = typeof claims["exp"] === "number" ? claims["exp"] : void 0;
761
+ return freezeDeep(compact({
762
+ principal,
763
+ actor,
764
+ delegation: act.actor?.kind === "oauth-client" ? subjectDelegation(claims, act.actor.chain) : void 0,
765
+ context: {},
766
+ session,
767
+ expiresAt
768
+ }));
769
+ }
770
+ function subjectFromSupabase(claims, options = {}) {
771
+ try {
772
+ if (!isRecord(claims)) return anonymousSubject();
773
+ const rest = {};
774
+ for (const [key, value] of Object.entries(claims)) {
775
+ if (key === "user_metadata") continue;
776
+ rest[key] = value;
777
+ }
778
+ return mapClaims(rest, options);
779
+ } catch {
780
+ return anonymousSubject();
781
+ }
782
+ }
783
+ /**
784
+ * A verified session object (for example better-supabase's `AuthSession`) in, `Subject` out.
785
+ * Only `kind: 'user'` sessions map; `anon`, `service`, `invalid` and anything else are anonymous.
786
+ */
787
+ function subjectFromSupabaseSession(session, options = {}) {
788
+ try {
789
+ if (session === null || session === void 0 || session.kind !== "user") return anonymousSubject();
790
+ return subjectFromSupabase(session.claims, options);
791
+ } catch {
792
+ return anonymousSubject();
793
+ }
794
+ }
795
+ //#endregion
796
+ export { subjectFromSupabaseSession as a, supabaseClaims as c, subjectFromSupabase as i, exchangeCapability as l, delegationOf as n, authorizeSql as o, readMemberships as r, supabaseRls as s, actorOf as t };