better-supabase 0.5.0 → 0.6.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.
- package/README.md +299 -41
- package/dist/access-sql-B0dF-SoF.js +181 -0
- package/dist/actor-CQTlc1Hh.d.ts +43 -0
- package/dist/adapter-Bc1o3zyy.js +1106 -0
- package/dist/agents-CVHGd7It.d.ts +94 -0
- package/dist/ai-cache-CBWs1W0k.js +80 -0
- package/dist/ai-cache-DaDu_G5L.d.ts +64 -0
- package/dist/ai-chat-7amvXjmQ.d.ts +590 -0
- package/dist/ai-chat-B24UI962.js +743 -0
- package/dist/ai-files-CSPNswUY.js +319 -0
- package/dist/ai-sdk/agents/index.d.ts +74 -0
- package/dist/ai-sdk/agents/index.js +159 -0
- package/dist/ai-sdk/batches/index.d.ts +74 -0
- package/dist/ai-sdk/batches/index.js +180 -0
- package/dist/ai-sdk/cache/index.d.ts +38 -0
- package/dist/ai-sdk/cache/index.js +127 -0
- package/dist/ai-sdk/chat/index.d.ts +2 -0
- package/dist/ai-sdk/chat/index.js +246 -0
- package/dist/ai-sdk/embeddings/index.d.ts +3 -0
- package/dist/ai-sdk/embeddings/index.js +2 -0
- package/dist/ai-sdk/files/index.d.ts +61 -0
- package/dist/ai-sdk/files/index.js +111 -0
- package/dist/ai-sdk/index.d.ts +145 -0
- package/dist/ai-sdk/index.js +174 -0
- package/dist/ai-sdk/mcp/index.d.ts +102 -0
- package/dist/ai-sdk/mcp/index.js +362 -0
- package/dist/ai-sdk/memory/index.d.ts +67 -0
- package/dist/ai-sdk/memory/index.js +175 -0
- package/dist/ai-sdk/react/index.d.ts +2 -0
- package/dist/ai-sdk/react/index.js +3 -0
- package/dist/ai-sdk/react/server.d.ts +6 -0
- package/dist/ai-sdk/react/server.js +7 -0
- package/dist/ai-sdk/workflow/index.d.ts +249 -0
- package/dist/ai-sdk/workflow/index.js +575 -0
- package/dist/ai-sdk/workflow/react/index.d.ts +20 -0
- package/dist/ai-sdk/workflow/react/index.js +66 -0
- package/dist/ai-sdk/workflow/react/server.d.ts +10 -0
- package/dist/ai-sdk/workflow/react/server.js +12 -0
- package/dist/ai-tasks-Cd1HMFvB.d.ts +131 -0
- package/dist/announcements-CiyOs-xP.js +107 -0
- package/dist/announcements-ZRGnScWI.d.ts +72 -0
- package/dist/api-keys-rCGtKaZA.d.ts +156 -0
- package/dist/assistant-DwdpDH1q.d.ts +147 -0
- package/dist/astro/index.d.ts +86 -0
- package/dist/astro/index.js +93 -0
- package/dist/attributes-5dYul0u0.js +13 -0
- package/dist/auth-link-BjFPqwLz.d.ts +97 -0
- package/dist/auth-link-DrDX9KOT.js +114 -0
- package/dist/authorization-BFCqamMs.d.ts +172 -0
- package/dist/before-route-CnR0C0Jr.js +30 -0
- package/dist/bind-Bu_ZNa3W.d.ts +62 -0
- package/dist/bind-l4kAvYSv.js +97 -0
- package/dist/block-events-CrT8r7Fe.js +76 -0
- package/dist/block-fields-GFkxl4dP.js +49 -0
- package/dist/block-fields-SJ6fEf3o.d.ts +31 -0
- package/dist/block-helpers-CKtbFcRv.d.ts +37 -0
- package/dist/block-helpers-DWHxHsEQ.js +190 -0
- package/dist/block-hooks-BmjMyXTa.js +114 -0
- package/dist/block-hooks-plbZ0AbJ.d.ts +107 -0
- package/dist/{kit-transport-DJlHQUBU.js → block-transport-BLr-ZgLc.js} +11 -7
- package/dist/block-transport-DvaoPuZ9.d.ts +48 -0
- package/dist/blocks/agents/index.d.ts +3 -0
- package/dist/blocks/agents/index.js +123 -0
- package/dist/blocks/ai-cache/index.d.ts +3 -0
- package/dist/blocks/ai-cache/index.js +3 -0
- package/dist/blocks/ai-chat/index.d.ts +31 -0
- package/dist/blocks/ai-chat/index.js +5 -0
- package/dist/blocks/ai-chat/react/index.d.ts +77 -0
- package/dist/blocks/ai-chat/react/index.js +387 -0
- package/dist/blocks/ai-chat/react/server.d.ts +16 -0
- package/dist/blocks/ai-chat/react/server.js +14 -0
- package/dist/blocks/ai-files/index.d.ts +14 -0
- package/dist/blocks/ai-files/index.js +3 -0
- package/dist/blocks/ai-providers/index.d.ts +7 -0
- package/dist/blocks/ai-providers/index.js +211 -0
- package/dist/blocks/ai-tasks/index.d.ts +3 -0
- package/dist/blocks/ai-tasks/index.js +203 -0
- package/dist/blocks/announcements/index.d.ts +3 -0
- package/dist/blocks/announcements/index.js +3 -0
- package/dist/blocks/announcements/react/index.d.ts +31 -0
- package/dist/blocks/announcements/react/index.js +86 -0
- package/dist/blocks/announcements/react/server.d.ts +6 -0
- package/dist/blocks/announcements/react/server.js +7 -0
- package/dist/blocks/api-keys/index.d.ts +35 -0
- package/dist/blocks/api-keys/index.js +334 -0
- package/dist/blocks/attachments/index.d.ts +210 -0
- package/dist/blocks/attachments/index.js +222 -0
- package/dist/blocks/audit/index.d.ts +366 -0
- package/dist/blocks/audit/index.js +714 -0
- package/dist/blocks/billing/index.d.ts +265 -0
- package/dist/blocks/billing/index.js +400 -0
- package/dist/blocks/comments/index.d.ts +200 -0
- package/dist/blocks/comments/index.js +207 -0
- package/dist/blocks/connectors/index.d.ts +3 -0
- package/dist/blocks/connectors/index.js +164 -0
- package/dist/blocks/data-lifecycle/index.d.ts +251 -0
- package/dist/blocks/data-lifecycle/index.js +353 -0
- package/dist/blocks/entitlements/index.d.ts +14 -0
- package/dist/blocks/entitlements/index.js +25 -0
- package/dist/blocks/flags/index.d.ts +226 -0
- package/dist/blocks/flags/index.js +282 -0
- package/dist/blocks/inbox/index.d.ts +6 -0
- package/dist/blocks/inbox/index.js +499 -0
- package/dist/blocks/inbox/react/index.d.ts +2 -0
- package/dist/blocks/inbox/react/index.js +225 -0
- package/dist/blocks/inbox/react/server.d.ts +10 -0
- package/dist/blocks/inbox/react/server.js +12 -0
- package/dist/blocks/index.d.ts +65 -0
- package/dist/blocks/index.js +77 -0
- package/dist/blocks/jobs/index.d.ts +297 -0
- package/dist/blocks/jobs/index.js +1051 -0
- package/dist/blocks/knowledge/index.d.ts +3 -0
- package/dist/blocks/knowledge/index.js +3 -0
- package/dist/blocks/memory/index.d.ts +8 -0
- package/dist/blocks/memory/index.js +289 -0
- package/dist/blocks/notifications/index.d.ts +297 -0
- package/dist/blocks/notifications/index.js +425 -0
- package/dist/blocks/notifications/react/index.d.ts +44 -0
- package/dist/blocks/notifications/react/index.js +67 -0
- package/dist/blocks/notifications/react/server.d.ts +6 -0
- package/dist/blocks/notifications/react/server.js +7 -0
- package/dist/blocks/onboarding/index.d.ts +3 -0
- package/dist/blocks/onboarding/index.js +64 -0
- package/dist/blocks/onboarding/react/index.d.ts +28 -0
- package/dist/blocks/onboarding/react/index.js +78 -0
- package/dist/blocks/onboarding/react/server.d.ts +7 -0
- package/dist/blocks/onboarding/react/server.js +7 -0
- package/dist/blocks/organizations/index.d.ts +241 -0
- package/dist/blocks/organizations/index.js +400 -0
- package/dist/blocks/outbox/index.d.ts +3 -0
- package/dist/blocks/outbox/index.js +266 -0
- package/dist/blocks/profiles/index.d.ts +40 -0
- package/dist/blocks/profiles/index.js +28 -0
- package/dist/blocks/push/index.d.ts +185 -0
- package/dist/blocks/push/index.js +258 -0
- package/dist/blocks/settings/index.d.ts +89 -0
- package/dist/blocks/settings/index.js +104 -0
- package/dist/blocks/sso/index.d.ts +198 -0
- package/dist/blocks/sso/index.js +1297 -0
- package/dist/blocks/usage/index.d.ts +4 -0
- package/dist/blocks/usage/index.js +233 -0
- package/dist/blocks/waitlist/index.d.ts +117 -0
- package/dist/blocks/waitlist/index.js +132 -0
- package/dist/blocks/webhooks/index.d.ts +304 -0
- package/dist/{webhooks → blocks/webhooks}/index.js +468 -148
- package/dist/blocks/workflow-builder/index.d.ts +21 -0
- package/dist/blocks/workflow-builder/index.js +5 -0
- package/dist/blocks/workflow-builder/react/index.d.ts +47 -0
- package/dist/blocks/workflow-builder/react/index.js +119 -0
- package/dist/blocks/workflow-builder/react/server.d.ts +8 -0
- package/dist/blocks/workflow-builder/react/server.js +11 -0
- package/dist/blocks/workflows/index.d.ts +3 -0
- package/dist/blocks/workflows/index.js +3 -0
- package/dist/blocks/workflows/react/index.d.ts +47 -0
- package/dist/blocks/workflows/react/index.js +137 -0
- package/dist/blocks/workflows/react/server.d.ts +8 -0
- package/dist/blocks/workflows/react/server.js +11 -0
- package/dist/{index-SaTsEfGP.d.ts → bucket-Cl46E3jS.d.ts} +134 -91
- package/dist/{simplify-_0oUKqCd.js → build-BXIzE6Xv.js} +222 -124
- package/dist/{channel-FQW-CnGR.d.ts → channel-_2Mx1Oyu.d.ts} +2 -2
- package/dist/channels-CkgQ_X60.js +114 -0
- package/dist/chat-sdk/index.d.ts +303 -0
- package/dist/chat-sdk/index.js +733 -0
- package/dist/chat-sdk/react/index.d.ts +2 -0
- package/dist/chat-sdk/react/index.js +3 -0
- package/dist/chat-sdk/react/server.d.ts +3 -0
- package/dist/chat-sdk/react/server.js +2 -0
- package/dist/claims-DDBQT6BK.js +61 -0
- package/dist/{claims-BAf6c7is.js → claims-PqzCxaTD.js} +39 -2
- package/dist/cli/bin.js +1 -1
- package/dist/cli/{codemod-DqV7612m.js → codemod-Bl_tQpHn.js} +105 -2
- package/dist/cli/config-B9HkL8nF.js +2 -0
- package/dist/cli/config-DU7EJImt.js +49 -0
- package/dist/cli/config-NiPxAmUF.js +1077 -0
- package/dist/cli/{dist-xn4wlboc.js → dist-DRAVaE6w.js} +111 -1
- package/dist/cli/{doctor-L7Z8mYTx.js → doctor-B4JOm_ox.js} +992 -316
- package/dist/cli/{env-DmKv_G4A.js → env-CJLbUVPB.js} +1 -1
- package/dist/cli/{env-jIFUADLY.js → env-qXgyMV6r.js} +2 -2
- package/dist/cli/fresh-import-BeYf9I3x.js +40 -0
- package/dist/cli/gen-BGLb_zou.js +3100 -0
- package/dist/cli/gen-DAH8FM2W.js +2 -0
- package/dist/cli/index.d.ts +44 -5
- package/dist/cli/index.js +7 -7
- package/dist/cli/{init-Saf5-R9T.js → init-BIIwclPg.js} +242 -27
- package/dist/cli/{introspect--H7oGmcd.js → introspect-DNUTuNvb.js} +5 -5
- package/dist/cli/{io-k41_z-jT.js → io-DI1mTdSA.js} +12 -3
- package/dist/cli/{keys-ByKHnvP9.js → keys-BGCnnb-s.js} +8 -6
- package/dist/cli/{ohash-D7bG_EOb.js → ohash-BjQY1cBb.js} +1 -1
- package/dist/cli/{openapi-Cp5Fyta-.js → openapi-BlRJ18s3.js} +2 -2
- package/dist/cli/{project-B_GeXyHh.js → project-DZRaBaY7.js} +11 -6
- package/dist/cli/read-sets-CETOSFx4.js +223 -0
- package/dist/cli/{run-BKR2W_79.js → run-DNWxHynX.js} +33 -23
- package/dist/cli/{seed-CUlqWh-8.js → seed-DQzqT7LP.js} +8 -6
- package/dist/cli/{skills-LJJIhLFR.js → skills-QQs4q68J.js} +3 -2
- package/dist/cli/sql-D0GUtUPp.js +536 -0
- package/dist/cli/{config-CPVpXP5I.js → supabase-toml-B-bIukQF.js} +72 -942
- package/dist/cli/typegen-version-DeFbtzYI.js +8 -0
- package/dist/cli/{version-BDJuO0UR.js → version-hr88OZG0.js} +1 -1
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.js +11 -71
- package/dist/client/native/index.d.ts +228 -0
- package/dist/client/native/index.js +361 -0
- package/dist/client-CI0Z2zWX.js +277 -0
- package/dist/cloud-event-BnmD5fb9.js +46 -0
- package/dist/cloud-event-C1JCc1bH.d.ts +50 -0
- package/dist/{compiler-BXH_aZ7N.d.ts → compiler-DY_wkj4R.d.ts} +2 -2
- package/dist/compose-BIBNgAnq.js +53 -0
- package/dist/composite-Bt1YK3Ke.d.ts +146 -0
- package/dist/composite-DhE66P5u.js +130 -0
- package/dist/config/index.d.ts +6 -4
- package/dist/config/index.js +5 -5
- package/dist/{config-T_ejwyd3.d.ts → config-D44Y7PAv.d.ts} +406 -205
- package/dist/{config-DreERlKo.js → config-Dn7F5k9A.js} +62 -14
- package/dist/connector-Dv8kK0MO.d.ts +107 -0
- package/dist/connectors-hkLCwyEz.d.ts +120 -0
- package/dist/context-BCrWe7ow.d.ts +237 -0
- package/dist/credentials/index.d.ts +31 -0
- package/dist/credentials/index.js +168 -0
- package/dist/cron-DVPUg5k3.js +145 -0
- package/dist/csv-CPAzEmB6.js +25 -0
- package/dist/db-exception-CnWcYPxL.js +15 -0
- package/dist/{define-cNKzqwL_.js → define-B-TVjC25.js} +784 -227
- package/dist/{define-Bp_BZa5h.d.ts → define-CJJkkr1-.d.ts} +353 -61
- package/dist/edge/index.d.ts +69 -11
- package/dist/edge/index.js +191 -75
- package/dist/elysia/index.d.ts +59 -0
- package/dist/elysia/index.js +62 -0
- package/dist/embeddings-4ZvFzjsU.js +127 -0
- package/dist/embeddings-C9KGQnXt.d.ts +68 -0
- package/dist/{refresh-BTrQ_ZTp.d.ts → ended-jLfvw8h-.d.ts} +20 -2
- package/dist/{entitlements-DUxU4oHe.d.ts → entitlements-DHrY2UDZ.d.ts} +16 -7
- package/dist/entitlements-DRl6ZJUm.js +24 -0
- package/dist/envelope-DqnYGRw7.js +47 -0
- package/dist/{errors-DtwXa6b8.js → errors-B11Duf73.js} +82 -13
- package/dist/errors-DLjJHPLK.js +44 -0
- package/dist/escape-like-DHfx6jRr.js +7 -0
- package/dist/escape-like-ImTK0ps_.d.ts +15 -0
- package/dist/eve/index.d.ts +220 -0
- package/dist/eve/index.js +504 -0
- package/dist/eve-gT2Fpjut.d.ts +151 -0
- package/dist/events/index.d.ts +4 -3
- package/dist/events/index.js +9 -50
- package/dist/events-DWDhx77e.d.ts +549 -0
- package/dist/executor-Bq28caBo.js +718 -0
- package/dist/executor-C-R4pucm.js +83 -0
- package/dist/executor-CaWigTyR.d.ts +148 -0
- package/dist/executor-D14tGopJ.d.ts +36 -0
- package/dist/{executor-Cghdstwu.d.ts → executor-DbvExCFC.d.ts} +7 -1
- package/dist/{sql-Coh6n4cC.js → executor-De1Of3m3.js} +210 -11
- package/dist/executor-DvsBbNBS.d.ts +261 -0
- package/dist/expo/index.d.ts +79 -0
- package/dist/expo/index.js +129 -0
- package/dist/expo-sqlite/index.d.ts +58 -0
- package/dist/expo-sqlite/index.js +104 -0
- package/dist/field-errors-BFOa7WhB.d.ts +99 -0
- package/dist/field-errors-VF1yb8yI.js +13 -0
- package/dist/framework-CONDyPYx.js +51 -0
- package/dist/framework-DuVb4Xa8.d.ts +25 -0
- package/dist/gateway-C1ssRHnf.d.ts +130 -0
- package/dist/graph-BGyLLFLX.js +265 -0
- package/dist/h3/index.d.ts +59 -0
- package/dist/h3/index.js +55 -0
- package/dist/h3/v1/index.d.ts +63 -0
- package/dist/h3/v1/index.js +49 -0
- package/dist/history-BZih6SFP.js +64 -0
- package/dist/hono/index.d.ts +56 -7
- package/dist/hono/index.js +97 -45
- package/dist/{react-C_s7kiNz.js → hooks-1BTCJsiw.js} +104 -133
- package/dist/http-Cv4T29xh.js +40 -0
- package/dist/http-DdYadv-o.js +126 -0
- package/dist/image-url-pIVlO6R1.js +41 -0
- package/dist/impersonation-DcqaTBHx.js +148 -0
- package/dist/inbox-i7E1ppo6.d.ts +384 -0
- package/dist/index-ACiNPN4Z.d.ts +74 -0
- package/dist/{index-DgILJRXj.d.ts → index-Bepi-Hgg.d.ts} +91 -28
- package/dist/index-BkWT_uBO.d.ts +51 -0
- package/dist/index-BpFYMsi0.d.ts +53 -0
- package/dist/{index-C7f8cOBM.d.ts → index-Bxih3l0u.d.ts} +2 -2
- package/dist/index-CaxrMD1A.d.ts +1 -0
- package/dist/index-ClmjK_o-.d.ts +55 -0
- package/dist/index-CtuakVRc.d.ts +267 -0
- package/dist/index-D48jVS-N.d.ts +246 -0
- package/dist/index-DDrYaUcq.d.ts +138 -0
- package/dist/index-DQh4VCc9.d.ts +98 -0
- package/dist/{live-D9W2M5aO.d.ts → index-DunNRdnT.d.ts} +136 -38
- package/dist/index.d.ts +65 -18
- package/dist/index.js +42 -14
- package/dist/invalidate-BBqhpxyB.d.ts +19 -0
- package/dist/json-CEnpTHlZ.js +16 -0
- package/dist/json-schema-CvER4kgT.js +502 -0
- package/dist/keys-4MnkWTTh.js +35 -0
- package/dist/kit-B5tid-q5.d.ts +60 -0
- package/dist/kit-Fm-Fv7q5.js +78 -0
- package/dist/knowledge-BXBbT0Tr.d.ts +176 -0
- package/dist/knowledge-CDmiMLew.js +255 -0
- package/dist/lint/index.d.ts +1 -1
- package/dist/lint/index.js +37 -0
- package/dist/list/index.d.ts +3 -2
- package/dist/list/index.js +1 -444
- package/dist/{index-fag8VJZ2.d.ts → list-query-BRt-zDF7.d.ts} +20 -12
- package/dist/list-query-gl808ac1.js +459 -0
- package/dist/{live-CdJBOc4f.js → live-CTAZQkwl.js} +99 -14
- package/dist/logger-DWVYUmwi.d.ts +18 -0
- package/dist/mcp/index.d.ts +38 -11
- package/dist/mcp/index.js +82 -46
- package/dist/mcp/sdk/index.d.ts +105 -0
- package/dist/mcp/sdk/index.js +126 -0
- package/dist/message-CaKlqdsX.js +219 -0
- package/dist/message-CfDoR0Y6.d.ts +94 -0
- package/dist/messages-B9-5887d.js +715 -0
- package/dist/messages-DYP0sUTe.d.ts +59 -0
- package/dist/{mfa-CusDgPaD.d.ts → mfa-Daak8ukG.d.ts} +8 -2
- package/dist/modules-DpPwzC7E.d.ts +192 -0
- package/dist/modules-dxrVK3ID.js +228 -0
- package/dist/nestjs/index.d.ts +68 -0
- package/dist/nestjs/index.js +164 -0
- package/dist/next/client/index.d.ts +35 -0
- package/dist/next/client/index.js +36 -0
- package/dist/next/image/index.js +3 -24
- package/dist/next/index.d.ts +150 -51
- package/dist/next/index.js +261 -107
- package/dist/node/index.d.ts +4 -0
- package/dist/node/index.js +210 -0
- package/dist/node-headers-AO06VhqH.d.ts +24 -0
- package/dist/node-headers-C71zNQHe.js +55 -0
- package/dist/nuxt/index.d.ts +60 -0
- package/dist/nuxt/index.js +64 -0
- package/dist/onboarding-BZGHYJks.d.ts +62 -0
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +3 -3
- package/dist/orpc/index.d.ts +70 -11
- package/dist/orpc/index.js +73 -24
- package/dist/otel/index.d.ts +7 -7
- package/dist/otel/index.js +87 -57
- package/dist/outbox-Cq7pPYvl.d.ts +161 -0
- package/dist/{plugin-o144f322.d.ts → plugin-FGY8UhYm.d.ts} +30 -10
- package/dist/plugin-tfXW1-qO.js +45 -0
- package/dist/plugins/actor/index.d.ts +2 -2
- package/dist/plugins/actor/index.js +3 -3
- package/dist/plugins/rules/index.d.ts +11 -4
- package/dist/plugins/rules/index.js +40 -13
- package/dist/plugins/soft-delete/index.d.ts +5 -4
- package/dist/plugins/soft-delete/index.js +10 -5
- package/dist/plugins/tenant/index.d.ts +2 -2
- package/dist/plugins/tenant/index.js +18 -11
- package/dist/plugins/timestamps/index.d.ts +5 -3
- package/dist/plugins/timestamps/index.js +6 -4
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/plugins/validation/index.js +10 -6
- package/dist/{pool-C9voM4Kw.d.ts → pool-CAu2nLrL.d.ts} +2 -2
- package/dist/postgres/index.d.ts +4 -4
- package/dist/postgres/index.js +1 -2
- package/dist/{postgrest-CruYKAvV.d.ts → postgrest-BLVdEnfe.d.ts} +13 -3
- package/dist/powersync/index.d.ts +43 -0
- package/dist/powersync/index.js +199 -0
- package/dist/powersync/react/index.d.ts +87 -0
- package/dist/powersync/react/index.js +108 -0
- package/dist/{problem-BW1KhzSG.js → problem-BreOl8b8.js} +14 -3
- package/dist/{problem-Blxe8S31.d.ts → problem-DD8LZFjc.d.ts} +21 -3
- package/dist/provider-BH5UWZJ6.d.ts +113 -0
- package/dist/provider-Di3sRutW.js +57 -0
- package/dist/query/index.d.ts +4 -2
- package/dist/query/index.js +4 -3
- package/dist/{query-CCbo44lG.js → query-BQ-NaZon.js} +141 -24
- package/dist/{queue-BYUCjr59.d.ts → queue-CLJurhdy.d.ts} +258 -83
- package/dist/react/index.d.ts +11 -7
- package/dist/react/index.js +7 -3
- package/dist/react/native/index.d.ts +116 -0
- package/dist/react/native/index.js +130 -0
- package/dist/react/server.d.ts +28 -10
- package/dist/react/server.js +22 -5
- package/dist/react/session.d.ts +2 -2
- package/dist/react-4ZqkP8da.js +79 -0
- package/dist/react-F80mF4-t.js +409 -0
- package/dist/react-router/index.d.ts +83 -0
- package/dist/react-router/index.js +89 -0
- package/dist/react-topic-seAWjL3_.js +38 -0
- package/dist/{read-set-DQFt1N_Y.js → read-set-DuKWC9Mj.js} +67 -25
- package/dist/realtime/index.d.ts +4 -5
- package/dist/realtime/index.js +144 -82
- package/dist/refusal-CeJWxmkH.js +61 -0
- package/dist/refusal-D1rgbzNq.d.ts +15 -0
- package/dist/replicas-CwrYp5Zj.js +94 -0
- package/dist/{view-Bq64ki8-.js → resolve-D_IcnvFd.js} +239 -289
- package/dist/{resolve-C15TJq77.d.ts → resolve-KW8nCeL6.d.ts} +29 -45
- package/dist/{resource-CRxxwkQq.js → resource-BsvzCMlX.js} +4 -4
- package/dist/{resource-D8iqWMr-.d.ts → resource-D6Uc7Sk7.d.ts} +5 -5
- package/dist/respond-BUgsXNgk.js +91 -0
- package/dist/respond-DoBijysA.d.ts +93 -0
- package/dist/result-D7y2fT87.d.ts +282 -0
- package/dist/{result-BKiliPY7.js → result-DzUbqgdW.js} +27 -2
- package/dist/run-state-CPo-1X2B.d.ts +12 -0
- package/dist/run-state-CgBKzYMW.js +13 -0
- package/dist/search-CUWCRYKL.js +9 -0
- package/dist/server/index.d.ts +143 -21
- package/dist/server/index.js +46 -14
- package/dist/{respond-D2OQl62-.d.ts → server-CeVeeXwz.d.ts} +225 -80
- package/dist/session-ChiXDZdZ.d.ts +72 -0
- package/dist/shared-CBxosKK3.js +75 -0
- package/dist/{shared-D1dRUrAg.js → shared-CxWmcyqB.js} +2 -1
- package/dist/shared-DDSjEGDR.d.ts +11 -0
- package/dist/sign-in-BCV5S2WZ.d.ts +49 -0
- package/dist/sign-in-Cb1mwv6X.js +86 -0
- package/dist/{signers-Cc6r98SW.d.ts → signers-4y62_VIi.d.ts} +13 -9
- package/dist/simplify-DDO0j0LI.js +83 -0
- package/dist/solid/index.d.ts +131 -0
- package/dist/solid/index.js +344 -0
- package/dist/solid-start/index.d.ts +75 -0
- package/dist/solid-start/index.js +58 -0
- package/dist/{spec-pins-C3oc-Tdp.js → spec-pins--j6mTok4.js} +8 -1
- package/dist/{spec-pins-CqorWDnO.d.ts → spec-pins-C89s6DyW.d.ts} +7 -0
- package/dist/sql/index.d.ts +330 -194
- package/dist/sql/index.js +34901 -6489
- package/dist/ssr/index.d.ts +6 -5
- package/dist/ssr/index.js +3 -3
- package/dist/{standard-D7r6dMUu.js → standard-BK0cqJ7y.js} +4 -3
- package/dist/{standard-J4YgMOLm.d.ts → standard-CYsm7xpT.d.ts} +1 -1
- package/dist/stats-D_AD9GGu.js +1123 -0
- package/dist/storage/index.d.ts +5 -5
- package/dist/storage/index.js +1027 -368
- package/dist/store-BdH5ruDx.js +66 -0
- package/dist/store-CGjgWu5n.d.ts +68 -0
- package/dist/streams/index.d.ts +76 -0
- package/dist/streams/index.js +77 -0
- package/dist/streams/redis/index.d.ts +52 -0
- package/dist/streams/redis/index.js +185 -0
- package/dist/stripe-BMj5LtsT.js +38 -0
- package/dist/stripe-D04h-QBT.d.ts +136 -0
- package/dist/{support-Dk6vdmEw.js → support-BivNqSpK.js} +35 -12
- package/dist/{support-view-Da5peKFg.d.ts → support-view-33Oi07TT.d.ts} +1 -1
- package/dist/svelte/index.d.ts +114 -0
- package/dist/svelte/index.js +344 -0
- package/dist/sveltekit/index.d.ts +116 -0
- package/dist/sveltekit/index.js +106 -0
- package/dist/tags-B99_U9Pp.js +39 -0
- package/dist/tags-Cx-LnIQP.d.ts +30 -0
- package/dist/tanstack-db/index.d.ts +71 -0
- package/dist/tanstack-db/index.js +72 -0
- package/dist/tanstack-start/index.d.ts +75 -0
- package/dist/tanstack-start/index.js +95 -0
- package/dist/tee-CoEppCER.js +101 -0
- package/dist/{template-TxrGI5jR.js → template--jDkl3UP.js} +34 -13
- package/dist/template-CwO38Do8.d.ts +6 -0
- package/dist/temporal-BJGitm-h.js +79 -0
- package/dist/{temporal-required-CAt4Dc9Z.js → temporal-required-BVvW7SYJ.js} +3 -2
- package/dist/tenant-I1TEZ_BX.js +27 -0
- package/dist/tenant-icPeuUBN.d.ts +17 -0
- package/dist/testing/index.d.ts +343 -37
- package/dist/testing/index.js +1186 -79
- package/dist/timing-CyTQPubY.js +92 -0
- package/dist/ttl-Hr34gRu8.js +12 -0
- package/dist/{types-Dnf_wRDz.d.ts → types-6BdhkmZe.d.ts} +111 -45
- package/dist/types-BI-0Sl2v.d.ts +359 -0
- package/dist/types-C05bxW-W.d.ts +220 -0
- package/dist/types-DGs5L0VN.d.ts +192 -0
- package/dist/types-TaiEeBGA.d.ts +156 -0
- package/dist/usage-CrvrL2vw.d.ts +177 -0
- package/dist/user-change-XzvgafU4.js +130 -0
- package/dist/vault-CSO7ss5C.d.ts +50 -0
- package/dist/vercel-connect/index.d.ts +47 -0
- package/dist/vercel-connect/index.js +119 -0
- package/dist/{verify-Ce49ETPz.js → verify-DL41hbvY.js} +85 -13
- package/dist/verify-DPuWngD4.d.ts +172 -0
- package/dist/{version-DM_Roffb.js → version-Bvv6aLjZ.js} +1 -1
- package/dist/view-BeFcRV6G.js +99 -0
- package/dist/{view-BEf0diC5.d.ts → view-DbxLNu8L.d.ts} +35 -32
- package/dist/vue/index.d.ts +141 -0
- package/dist/vue/index.js +362 -0
- package/dist/watch-DI34G86j.d.ts +60 -0
- package/dist/watch-Xuxrvxq-.js +122 -0
- package/dist/{wire-D2dT86vi.js → wire-DtZQ4sdE.js} +15 -5
- package/dist/workflow-builder-DJ1oHn8g.js +398 -0
- package/dist/workflow-sdk/builder/index.d.ts +85 -0
- package/dist/workflow-sdk/builder/index.js +250 -0
- package/dist/workflow-sdk/index.d.ts +14 -0
- package/dist/workflow-sdk/index.js +3 -0
- package/dist/workflow-sdk/world/index.d.ts +107 -0
- package/dist/workflow-sdk/world/index.js +702 -0
- package/dist/workflow-sdk-B3yLD6Br.d.ts +98 -0
- package/dist/workflow-sdk-C2WJ_G2O.js +173 -0
- package/dist/workflow-sdk-world-D-prir6O.js +1397 -0
- package/dist/workflows-B0j9WKPu.d.ts +185 -0
- package/dist/workflows-wu1WWBn7.js +216 -0
- package/package.json +470 -29
- package/schemas/ai-message-v1.json +258 -0
- package/schemas/config-v1.json +1074 -140
- package/schemas/doctor-report-v1.json +21 -1
- package/schemas/snapshot-v2.json +8 -0
- package/skills/better-supabase/SKILL.md +41 -12
- package/skills/better-supabase/references/frontend.md +96 -0
- package/skills/better-supabase/references/plugins.md +10 -6
- package/skills/better-supabase/references/troubleshooting.md +2 -1
- package/skills/better-supabase-api/SKILL.md +76 -22
- package/skills/better-supabase-api/references/adapters.md +207 -0
- package/skills/better-supabase-api/references/ai.md +125 -0
- package/skills/better-supabase-api/references/expo.md +65 -0
- package/skills/better-supabase-api/references/powersync.md +35 -0
- package/skills/better-supabase-api/references/streams-credentials.md +105 -0
- package/skills/better-supabase-auth/SKILL.md +62 -18
- package/skills/better-supabase-auth/references/authorization-providers.md +51 -0
- package/skills/better-supabase-testing/SKILL.md +3 -1
- package/dist/cli/config-dtoekM0v.js +0 -2
- package/dist/cli/gen-BJc9vdyu.js +0 -2
- package/dist/cli/gen-C2osRcr5.js +0 -2204
- package/dist/cli/read-sets-BfMQcKy8.js +0 -289
- package/dist/cli/sql-DzPbfngx.js +0 -348
- package/dist/entitlements-CTEBm7IB.js +0 -20
- package/dist/events-aIO7vsIk.d.ts +0 -230
- package/dist/executor-BunPOO-k.d.ts +0 -402
- package/dist/executor-XycESQPH.js +0 -94
- package/dist/impersonation-VfaGc4Q_.js +0 -41
- package/dist/index-BGdgBPdf.d.ts +0 -161
- package/dist/index-DOESKG27.d.ts +0 -100
- package/dist/index-DmXSxbLi.d.ts +0 -62
- package/dist/index-Dxn2a5cw.d.ts +0 -50
- package/dist/jobs/index.d.ts +0 -226
- package/dist/jobs/index.js +0 -1007
- package/dist/json-schema-m1fiwKgN.js +0 -208
- package/dist/kit-events-CTFDfSwb.js +0 -60
- package/dist/kit-transport-B82k1djG.d.ts +0 -33
- package/dist/kits-CGLLrNPd.js +0 -157
- package/dist/notifications/index.d.ts +0 -162
- package/dist/notifications/index.js +0 -264
- package/dist/orgs/index.d.ts +0 -132
- package/dist/orgs/index.js +0 -245
- package/dist/permdock-sql-C2Inc6Xc.d.ts +0 -29
- package/dist/permdock-sql-DgOfeixi.js +0 -51
- package/dist/plugin-DwARMuQp.js +0 -59
- package/dist/server-Csx2sRjg.js +0 -655
- package/dist/stats-Bqob3WqT.js +0 -592
- package/dist/support-DT-OhSxD.d.ts +0 -88
- package/dist/template-mbvlysOo.d.ts +0 -6
- package/dist/temporal-CVlIV2DZ.js +0 -40
- package/dist/user-change-CHFEUro1.js +0 -49
- package/dist/webhooks/index.d.ts +0 -285
- package/skills/better-supabase-auth/references/permdock.md +0 -100
|
@@ -0,0 +1,1297 @@
|
|
|
1
|
+
import { n as dbError } from "../../errors-B11Duf73.js";
|
|
2
|
+
import { a as ok, r as err, t as AsyncResult } from "../../result-DzUbqgdW.js";
|
|
3
|
+
import { n as rpcTransport, r as sqlTransport } from "../../block-transport-BLr-ZgLc.js";
|
|
4
|
+
import { D as stringsOf, O as textOf, c as errorText, d as isRecord, h as optionalText, i as blockCall, k as toInstant, m as optionalInstant, n as applyTemporal, x as recordsOf, y as recordOf } from "../../block-helpers-DWHxHsEQ.js";
|
|
5
|
+
//#region src/blocks/sso/sso.ts
|
|
6
|
+
function recordOfDomain(value) {
|
|
7
|
+
const row = recordOf(value, "organization_domains.record");
|
|
8
|
+
return {
|
|
9
|
+
type: "TXT",
|
|
10
|
+
name: textOf(row["name"]),
|
|
11
|
+
value: textOf(row["value"])
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function domainOf(value) {
|
|
15
|
+
const row = recordOf(value, "organization_domains");
|
|
16
|
+
return {
|
|
17
|
+
id: textOf(row["id"]),
|
|
18
|
+
organizationId: textOf(row["organization_id"]),
|
|
19
|
+
domain: textOf(row["domain"]),
|
|
20
|
+
verifiedAt: optionalInstant(row["verified_at"]),
|
|
21
|
+
autoJoinRole: optionalText(row["auto_join_role"]),
|
|
22
|
+
enforceSso: row["enforce_sso"] === true,
|
|
23
|
+
createdBy: optionalText(row["created_by"]),
|
|
24
|
+
createdAt: toInstant(textOf(row["created_at"])),
|
|
25
|
+
record: recordOfDomain(row["record"])
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function providerOf(value) {
|
|
29
|
+
const row = recordOf(value, "organization_sso_providers");
|
|
30
|
+
return {
|
|
31
|
+
id: textOf(row["id"]),
|
|
32
|
+
organizationId: textOf(row["organization_id"]),
|
|
33
|
+
type: "saml",
|
|
34
|
+
metadataUrl: optionalText(row["metadata_url"]),
|
|
35
|
+
domains: stringsOf(row["domains"]),
|
|
36
|
+
createdAt: toInstant(textOf(row["created_at"]))
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function ssoDomainOf(value) {
|
|
40
|
+
if (!isRecord(value)) return void 0;
|
|
41
|
+
return {
|
|
42
|
+
domain: textOf(value["domain"]),
|
|
43
|
+
organizationId: textOf(value["organizationId"]),
|
|
44
|
+
providerId: textOf(value["providerId"]),
|
|
45
|
+
enforceSso: value["enforceSso"] === true
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Organization domains and SSO lookups as the caller. */
|
|
49
|
+
function createSso(options) {
|
|
50
|
+
applyTemporal(options);
|
|
51
|
+
const call = blockCall(options.transport, options.schema, options.mappers);
|
|
52
|
+
return {
|
|
53
|
+
addDomain: (organizationId, domain) => call("add_organization_domain", {
|
|
54
|
+
tenant: organizationId,
|
|
55
|
+
domain
|
|
56
|
+
}, domainOf),
|
|
57
|
+
domains: (organizationId) => call("list_organization_domains", { tenant: organizationId }, (value) => recordsOf(value, "list_organization_domains").map(domainOf)),
|
|
58
|
+
updateDomain: (domainId, settings) => call("organization_domain", { id: domainId }, (value) => isRecord(value) ? domainOf(value) : void 0).andThen(async (current) => current === void 0 ? err(dbError("not_found", "No domain you manage has this id", { hint: "SSO_DOMAIN_NOT_FOUND" })) : call("update_organization_domain", {
|
|
59
|
+
id: domainId,
|
|
60
|
+
auto_join_role: settings.autoJoinRole === void 0 ? current.autoJoinRole ?? null : settings.autoJoinRole,
|
|
61
|
+
enforce_sso: settings.enforceSso ?? current.enforceSso
|
|
62
|
+
}, domainOf)),
|
|
63
|
+
removeDomain: (domainId) => call("remove_organization_domain", { id: domainId }, (value) => value === true),
|
|
64
|
+
providers: (organizationId) => call("list_sso_providers", { tenant: organizationId }, (value) => recordsOf(value, "list_sso_providers").map(providerOf)),
|
|
65
|
+
domainFor: (email) => call("sso_domain_for", { email }, ssoDomainOf)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const NXDOMAIN = 3;
|
|
69
|
+
const TXT = 16;
|
|
70
|
+
/** TXT lookups over DNS-over-HTTPS (the `application/dns-json` API). */
|
|
71
|
+
function dohResolver(options = {}) {
|
|
72
|
+
const endpoint = options.endpoint ?? "https://cloudflare-dns.com/dns-query";
|
|
73
|
+
const fetcher = options.fetch ?? fetch;
|
|
74
|
+
return async (name, signal) => {
|
|
75
|
+
const url = new URL(endpoint);
|
|
76
|
+
url.searchParams.set("name", name);
|
|
77
|
+
url.searchParams.set("type", "TXT");
|
|
78
|
+
const response = await fetcher(url, {
|
|
79
|
+
headers: { accept: "application/dns-json" },
|
|
80
|
+
...signal ? { signal } : {}
|
|
81
|
+
});
|
|
82
|
+
if (!response.ok) throw new Error(`DNS lookup of ${name} failed with ${response.status}`);
|
|
83
|
+
const body = await response.json();
|
|
84
|
+
if (!isRecord(body)) throw new Error(`DNS lookup of ${name} returned no JSON`);
|
|
85
|
+
if (body["Status"] === NXDOMAIN) return [];
|
|
86
|
+
if (body["Status"] !== 0) throw new Error(`DNS lookup of ${name} failed with status ${String(body["Status"])}`);
|
|
87
|
+
return (Array.isArray(body["Answer"]) ? body["Answer"] : []).filter((answer) => isRecord(answer) && answer["type"] === TXT).map((answer) => txtValue(textOf(isRecord(answer) ? answer["data"] : "")));
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** `"abc" "def"` (one record split into strings) gives `abcdef`. */
|
|
91
|
+
function txtValue(data) {
|
|
92
|
+
const parts = [...data.matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match) => (match[1] ?? "").replaceAll(/\\(.)/g, "$1"));
|
|
93
|
+
return parts.length === 0 ? data : parts.join("");
|
|
94
|
+
}
|
|
95
|
+
function authStatusError(status, message) {
|
|
96
|
+
if (status === 400 || status === 422) return dbError("invalid_input", message);
|
|
97
|
+
if (status === 401) return dbError("unauthorized", message);
|
|
98
|
+
if (status === 403) return dbError("forbidden", message);
|
|
99
|
+
if (status === 404) return dbError("not_found", message);
|
|
100
|
+
if (status === 409) return dbError("conflict", message);
|
|
101
|
+
if (status === 429) return dbError("rate_limited", message);
|
|
102
|
+
return dbError("network", message);
|
|
103
|
+
}
|
|
104
|
+
/** Domain verification and SAML providers with a service-role client. */
|
|
105
|
+
function createSsoAdmin(options) {
|
|
106
|
+
applyTemporal(options);
|
|
107
|
+
const call = blockCall(options.transport, options.schema, options.mappers);
|
|
108
|
+
const resolver = options.resolver ?? dohResolver();
|
|
109
|
+
const auth = (method, path, body) => AsyncResult.from(async () => {
|
|
110
|
+
const admin = options.auth;
|
|
111
|
+
if (!admin) return err(dbError("invalid_request", "Pass auth to createSsoAdmin to manage SAML providers"));
|
|
112
|
+
const fetcher = admin.fetch ?? fetch;
|
|
113
|
+
let response;
|
|
114
|
+
try {
|
|
115
|
+
response = await fetcher(`${admin.url.replace(/\/+$/, "")}/auth/v1/admin/sso/providers${path}`, {
|
|
116
|
+
method,
|
|
117
|
+
headers: {
|
|
118
|
+
apikey: admin.secretKey,
|
|
119
|
+
authorization: `Bearer ${admin.secretKey}`,
|
|
120
|
+
"content-type": "application/json"
|
|
121
|
+
},
|
|
122
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
123
|
+
});
|
|
124
|
+
} catch (cause) {
|
|
125
|
+
return err(dbError("network", errorText(cause)));
|
|
126
|
+
}
|
|
127
|
+
const text = await response.text();
|
|
128
|
+
let json;
|
|
129
|
+
try {
|
|
130
|
+
json = text.length > 0 ? JSON.parse(text) : {};
|
|
131
|
+
} catch {
|
|
132
|
+
json = {};
|
|
133
|
+
}
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
const message = isRecord(json) ? optionalText(json["msg"]) ?? optionalText(json["message"]) ?? optionalText(json["error_description"]) : void 0;
|
|
136
|
+
return err(authStatusError(response.status, message ?? `Supabase Auth answered ${response.status}`));
|
|
137
|
+
}
|
|
138
|
+
return ok(isRecord(json) ? json : {});
|
|
139
|
+
});
|
|
140
|
+
const providerBody = (input) => ({
|
|
141
|
+
type: "saml",
|
|
142
|
+
...input.metadataUrl === void 0 ? {} : { metadata_url: input.metadataUrl },
|
|
143
|
+
...input.metadataXml === void 0 ? {} : { metadata_xml: input.metadataXml },
|
|
144
|
+
...input.domains === void 0 ? {} : { domains: [...input.domains] },
|
|
145
|
+
...input.attributeMapping === void 0 ? {} : { attribute_mapping: input.attributeMapping },
|
|
146
|
+
...input.nameIdFormat === void 0 ? {} : { name_id_format: input.nameIdFormat }
|
|
147
|
+
});
|
|
148
|
+
const register = (organizationId, providerId, domains, metadataUrl, actor) => call("register_sso_provider", {
|
|
149
|
+
tenant: organizationId,
|
|
150
|
+
provider: providerId,
|
|
151
|
+
domains,
|
|
152
|
+
metadata_url: metadataUrl ?? null,
|
|
153
|
+
actor: actor.actorId ?? null
|
|
154
|
+
}, providerOf);
|
|
155
|
+
const providerFor = (providerId, actor) => call("sso_provider", {
|
|
156
|
+
provider: providerId,
|
|
157
|
+
actor: actor.actorId ?? null
|
|
158
|
+
}, (value) => isRecord(value) ? providerOf(value) : void 0);
|
|
159
|
+
const missing = () => err(dbError("not_found", "No SSO provider has this id", { hint: "SSO_PROVIDER_NOT_FOUND" }));
|
|
160
|
+
const path = (providerId) => `/${encodeURIComponent(providerId)}`;
|
|
161
|
+
return {
|
|
162
|
+
verifyDomain: (domainId, request = {}) => call("organization_domain", { id: domainId }, (value) => isRecord(value) ? domainOf(value) : void 0).andThen(async (found) => {
|
|
163
|
+
if (found === void 0) return err(dbError("not_found", "No domain has this id", { hint: "SSO_DOMAIN_NOT_FOUND" }));
|
|
164
|
+
if (found.verifiedAt === void 0) {
|
|
165
|
+
let values;
|
|
166
|
+
try {
|
|
167
|
+
values = await resolver(found.record.name, request.signal);
|
|
168
|
+
} catch (cause) {
|
|
169
|
+
return err(dbError("network", errorText(cause)));
|
|
170
|
+
}
|
|
171
|
+
if (!values.includes(found.record.value)) return err(dbError("invalid_request", `No TXT record ${found.record.name} with the verification value yet`, { hint: "SSO_DOMAIN_RECORD_MISSING" }));
|
|
172
|
+
}
|
|
173
|
+
return call("verify_organization_domain", {
|
|
174
|
+
id: domainId,
|
|
175
|
+
actor: request.actorId ?? null
|
|
176
|
+
}, domainOf);
|
|
177
|
+
}),
|
|
178
|
+
addSamlProvider: (organizationId, input, actor = {}) => call("check_sso_provider", {
|
|
179
|
+
tenant: organizationId,
|
|
180
|
+
domains: input.domains,
|
|
181
|
+
actor: actor.actorId ?? null
|
|
182
|
+
}, () => void 0).andThen(() => auth("POST", "", providerBody(input))).andThen(async (created) => {
|
|
183
|
+
const providerId = textOf(created["id"]);
|
|
184
|
+
const recorded = await register(organizationId, providerId, input.domains, input.metadataUrl, actor);
|
|
185
|
+
if (!recorded.ok) await auth("DELETE", path(providerId));
|
|
186
|
+
return recorded;
|
|
187
|
+
}),
|
|
188
|
+
updateSamlProvider: (providerId, input, actor = {}) => providerFor(providerId, actor).andThen(async (found) => {
|
|
189
|
+
if (found === void 0) return missing();
|
|
190
|
+
const checked = await call("check_sso_provider", {
|
|
191
|
+
tenant: found.organizationId,
|
|
192
|
+
domains: input.domains,
|
|
193
|
+
actor: actor.actorId ?? null
|
|
194
|
+
}, () => void 0);
|
|
195
|
+
if (!checked.ok) return checked;
|
|
196
|
+
const updated = await auth("PUT", path(providerId), providerBody(input));
|
|
197
|
+
if (!updated.ok) return updated;
|
|
198
|
+
const metadataUrl = input.metadataXml === void 0 ? input.metadataUrl ?? found.metadataUrl : void 0;
|
|
199
|
+
return register(found.organizationId, providerId, input.domains, metadataUrl, actor);
|
|
200
|
+
}),
|
|
201
|
+
removeSamlProvider: (providerId, actor = {}) => providerFor(providerId, actor).andThen(async (found) => {
|
|
202
|
+
if (found === void 0) return ok(void 0);
|
|
203
|
+
const deleted = await auth("DELETE", path(providerId));
|
|
204
|
+
if (!deleted.ok && deleted.error.kind !== "not_found") return deleted;
|
|
205
|
+
return call("unregister_sso_provider", {
|
|
206
|
+
provider: providerId,
|
|
207
|
+
actor: actor.actorId ?? null
|
|
208
|
+
}, () => found);
|
|
209
|
+
})
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/blocks/sso/scim-filter.ts
|
|
214
|
+
/** SCIM filters (RFC 7644 §3.4.2.2): parsing and evaluation over resource JSON. */
|
|
215
|
+
/** A filter the service provider can't parse or apply: 400 `invalidFilter`. */
|
|
216
|
+
var ScimFilterError = class extends Error {
|
|
217
|
+
name = "ScimFilterError";
|
|
218
|
+
};
|
|
219
|
+
const COMPARE = [
|
|
220
|
+
"eq",
|
|
221
|
+
"ne",
|
|
222
|
+
"co",
|
|
223
|
+
"sw",
|
|
224
|
+
"ew",
|
|
225
|
+
"gt",
|
|
226
|
+
"ge",
|
|
227
|
+
"lt",
|
|
228
|
+
"le"
|
|
229
|
+
];
|
|
230
|
+
function tokenize(input) {
|
|
231
|
+
const tokens = [];
|
|
232
|
+
let i = 0;
|
|
233
|
+
while (i < input.length) {
|
|
234
|
+
const char = input.charAt(i);
|
|
235
|
+
if (/\s/.test(char)) i += 1;
|
|
236
|
+
else if (char === "(" || char === ")" || char === "[" || char === "]") {
|
|
237
|
+
tokens.push({
|
|
238
|
+
kind: "punct",
|
|
239
|
+
value: char
|
|
240
|
+
});
|
|
241
|
+
i += 1;
|
|
242
|
+
} else if (char === "\"") {
|
|
243
|
+
let end = i + 1;
|
|
244
|
+
while (end < input.length && input.charAt(end) !== "\"") end += input.charAt(end) === "\\" ? 2 : 1;
|
|
245
|
+
if (end >= input.length) throw new ScimFilterError("Unterminated string");
|
|
246
|
+
const raw = input.slice(i, end + 1);
|
|
247
|
+
let value;
|
|
248
|
+
try {
|
|
249
|
+
value = JSON.parse(raw);
|
|
250
|
+
} catch {
|
|
251
|
+
throw new ScimFilterError(`Invalid string ${raw}`);
|
|
252
|
+
}
|
|
253
|
+
tokens.push({
|
|
254
|
+
kind: "string",
|
|
255
|
+
value: String(value)
|
|
256
|
+
});
|
|
257
|
+
i = end + 1;
|
|
258
|
+
} else {
|
|
259
|
+
let end = i;
|
|
260
|
+
while (end < input.length && !/[\s()[\]"]/.test(input.charAt(end))) end += 1;
|
|
261
|
+
tokens.push({
|
|
262
|
+
kind: "word",
|
|
263
|
+
value: input.slice(i, end)
|
|
264
|
+
});
|
|
265
|
+
i = end;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return tokens;
|
|
269
|
+
}
|
|
270
|
+
const ATTR_PATH = /^(?:urn:[^\s]+:)?[A-Za-z$][\w$-]*(?:\.[A-Za-z$][\w$-]*)?$/;
|
|
271
|
+
/** Parses a SCIM filter; throws `ScimFilterError`. */
|
|
272
|
+
function parseFilter(input) {
|
|
273
|
+
const tokens = tokenize(input);
|
|
274
|
+
let position = 0;
|
|
275
|
+
const peek = () => tokens[position];
|
|
276
|
+
const keyword = (word) => {
|
|
277
|
+
const token = peek();
|
|
278
|
+
return token?.kind === "word" && token.value.toLowerCase() === word.toLowerCase();
|
|
279
|
+
};
|
|
280
|
+
const expect = (value) => {
|
|
281
|
+
const token = peek();
|
|
282
|
+
if (token?.kind !== "punct" || token.value !== value) throw new ScimFilterError(`Expected ${value}`);
|
|
283
|
+
position += 1;
|
|
284
|
+
};
|
|
285
|
+
const orExpression = () => {
|
|
286
|
+
let left = andExpression();
|
|
287
|
+
while (keyword("or")) {
|
|
288
|
+
position += 1;
|
|
289
|
+
left = {
|
|
290
|
+
op: "or",
|
|
291
|
+
left,
|
|
292
|
+
right: andExpression()
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return left;
|
|
296
|
+
};
|
|
297
|
+
const andExpression = () => {
|
|
298
|
+
let left = notExpression();
|
|
299
|
+
while (keyword("and")) {
|
|
300
|
+
position += 1;
|
|
301
|
+
left = {
|
|
302
|
+
op: "and",
|
|
303
|
+
left,
|
|
304
|
+
right: notExpression()
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return left;
|
|
308
|
+
};
|
|
309
|
+
const notExpression = () => {
|
|
310
|
+
if (keyword("not")) {
|
|
311
|
+
position += 1;
|
|
312
|
+
const open = peek();
|
|
313
|
+
if (open?.kind !== "punct" || open.value !== "(") throw new ScimFilterError("Expected ( after not");
|
|
314
|
+
position += 1;
|
|
315
|
+
const filter = orExpression();
|
|
316
|
+
expect(")");
|
|
317
|
+
return {
|
|
318
|
+
op: "not",
|
|
319
|
+
filter
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
return atom();
|
|
323
|
+
};
|
|
324
|
+
const atom = () => {
|
|
325
|
+
const token = peek();
|
|
326
|
+
if (token?.kind === "punct" && token.value === "(") {
|
|
327
|
+
position += 1;
|
|
328
|
+
const filter = orExpression();
|
|
329
|
+
expect(")");
|
|
330
|
+
return filter;
|
|
331
|
+
}
|
|
332
|
+
if (token?.kind !== "word" || !ATTR_PATH.test(token.value)) throw new ScimFilterError("Expected an attribute path");
|
|
333
|
+
position += 1;
|
|
334
|
+
const path = token.value;
|
|
335
|
+
const next = peek();
|
|
336
|
+
if (next?.kind === "punct" && next.value === "[") {
|
|
337
|
+
position += 1;
|
|
338
|
+
const filter = orExpression();
|
|
339
|
+
expect("]");
|
|
340
|
+
return {
|
|
341
|
+
op: "value",
|
|
342
|
+
path,
|
|
343
|
+
filter
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
if (next?.kind !== "word") throw new ScimFilterError(`Expected an operator after ${path}`);
|
|
347
|
+
const op = next.value.toLowerCase();
|
|
348
|
+
position += 1;
|
|
349
|
+
if (op === "pr") return {
|
|
350
|
+
op: "pr",
|
|
351
|
+
path
|
|
352
|
+
};
|
|
353
|
+
const compare = COMPARE.find((candidate) => candidate === op);
|
|
354
|
+
if (compare === void 0) throw new ScimFilterError(`Unknown operator ${next.value}`);
|
|
355
|
+
return {
|
|
356
|
+
op: compare,
|
|
357
|
+
path,
|
|
358
|
+
value: literal()
|
|
359
|
+
};
|
|
360
|
+
};
|
|
361
|
+
const literal = () => {
|
|
362
|
+
const token = peek();
|
|
363
|
+
position += 1;
|
|
364
|
+
if (token?.kind === "string") return token.value;
|
|
365
|
+
if (token?.kind === "word") {
|
|
366
|
+
const word = token.value.toLowerCase();
|
|
367
|
+
if (word === "true") return true;
|
|
368
|
+
if (word === "false") return false;
|
|
369
|
+
if (word === "null") return null;
|
|
370
|
+
if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(token.value)) return Number(token.value);
|
|
371
|
+
}
|
|
372
|
+
throw new ScimFilterError("Expected a value");
|
|
373
|
+
};
|
|
374
|
+
if (tokens.length === 0) throw new ScimFilterError("Empty filter");
|
|
375
|
+
const filter = orExpression();
|
|
376
|
+
if (position !== tokens.length) throw new ScimFilterError("Unexpected input after the filter");
|
|
377
|
+
return filter;
|
|
378
|
+
}
|
|
379
|
+
/** The attribute paths compared case-sensitively (RFC 7643 `caseExact`). */
|
|
380
|
+
const CASE_EXACT = /* @__PURE__ */ new Set([
|
|
381
|
+
"id",
|
|
382
|
+
"externalid",
|
|
383
|
+
"meta.version"
|
|
384
|
+
]);
|
|
385
|
+
const DATES = /* @__PURE__ */ new Set(["meta.created", "meta.lastmodified"]);
|
|
386
|
+
function lookup(record, name) {
|
|
387
|
+
const lower = name.toLowerCase();
|
|
388
|
+
for (const [key, value] of Object.entries(record)) if (key.toLowerCase() === lower) return value;
|
|
389
|
+
}
|
|
390
|
+
/** The URN prefix of `path` when it names one of the resource's schemas, and the rest. */
|
|
391
|
+
function relative(resource, path) {
|
|
392
|
+
const colon = path.lastIndexOf(":");
|
|
393
|
+
if (colon === -1) return path;
|
|
394
|
+
const urn = path.slice(0, colon).toLowerCase();
|
|
395
|
+
return (Array.isArray(resource["schemas"]) ? resource["schemas"].map((schema) => String(schema).toLowerCase()) : []).includes(urn) ? path.slice(colon + 1) : void 0;
|
|
396
|
+
}
|
|
397
|
+
/** The values at `path`; multi-valued complex attributes give their `value`s. */
|
|
398
|
+
function valuesAt(resource, path) {
|
|
399
|
+
const local = relative(resource, path);
|
|
400
|
+
if (local === void 0) return [];
|
|
401
|
+
const [attribute = "", sub] = local.split(".");
|
|
402
|
+
const value = lookup(resource, attribute);
|
|
403
|
+
if (value === void 0 || value === null) return [];
|
|
404
|
+
if (Array.isArray(value)) return value.map((item) => isRecord(item) ? lookup(item, sub ?? "value") : sub ? void 0 : item).filter((item) => item !== void 0 && item !== null);
|
|
405
|
+
if (isRecord(value)) {
|
|
406
|
+
if (sub === void 0) return [];
|
|
407
|
+
const inner = lookup(value, sub);
|
|
408
|
+
return inner === void 0 || inner === null ? [] : [inner];
|
|
409
|
+
}
|
|
410
|
+
return sub === void 0 ? [value] : [];
|
|
411
|
+
}
|
|
412
|
+
function compare(op, actual, expected, path) {
|
|
413
|
+
const key = path.slice(path.lastIndexOf(":") + 1).toLowerCase();
|
|
414
|
+
if (typeof actual === "boolean" || typeof expected === "boolean") {
|
|
415
|
+
if (op !== "eq" && op !== "ne") throw new ScimFilterError(`${op} does not apply to booleans`);
|
|
416
|
+
return op === "eq" ? actual === expected : actual !== expected;
|
|
417
|
+
}
|
|
418
|
+
if (expected === null) return op === "ne" ? actual !== null : actual === null;
|
|
419
|
+
if (typeof actual === "number" && typeof expected === "number") return order(op, actual - expected);
|
|
420
|
+
if (typeof actual !== "string") return op === "ne";
|
|
421
|
+
if (typeof expected !== "string") return op === "ne";
|
|
422
|
+
if (DATES.has(key)) {
|
|
423
|
+
const left = Date.parse(actual);
|
|
424
|
+
const right = Date.parse(expected);
|
|
425
|
+
if (!Number.isNaN(left) && !Number.isNaN(right)) return order(op, left - right);
|
|
426
|
+
}
|
|
427
|
+
const exact = CASE_EXACT.has(key);
|
|
428
|
+
const a = exact ? actual : actual.toLowerCase();
|
|
429
|
+
const b = exact ? expected : expected.toLowerCase();
|
|
430
|
+
switch (op) {
|
|
431
|
+
case "co": return a.includes(b);
|
|
432
|
+
case "sw": return a.startsWith(b);
|
|
433
|
+
case "ew": return a.endsWith(b);
|
|
434
|
+
case "eq":
|
|
435
|
+
case "ne":
|
|
436
|
+
case "gt":
|
|
437
|
+
case "ge":
|
|
438
|
+
case "lt":
|
|
439
|
+
case "le": return order(op, a < b ? -1 : a > b ? 1 : 0);
|
|
440
|
+
default: throw new ScimFilterError(`Unknown operator ${String(op)}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function order(op, difference) {
|
|
444
|
+
switch (op) {
|
|
445
|
+
case "eq": return difference === 0;
|
|
446
|
+
case "ne": return difference !== 0;
|
|
447
|
+
case "gt": return difference > 0;
|
|
448
|
+
case "ge": return difference >= 0;
|
|
449
|
+
case "lt": return difference < 0;
|
|
450
|
+
case "le": return difference <= 0;
|
|
451
|
+
case "co":
|
|
452
|
+
case "sw":
|
|
453
|
+
case "ew": throw new ScimFilterError(`${op} applies to strings`);
|
|
454
|
+
default: throw new ScimFilterError(`Unknown operator ${String(op)}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/** Whether `resource` matches; a multi-valued attribute matches when any value does. */
|
|
458
|
+
function matches(resource, filter) {
|
|
459
|
+
switch (filter.op) {
|
|
460
|
+
case "and": return matches(resource, filter.left) && matches(resource, filter.right);
|
|
461
|
+
case "or": return matches(resource, filter.left) || matches(resource, filter.right);
|
|
462
|
+
case "not": return !matches(resource, filter.filter);
|
|
463
|
+
case "pr": return valuesAt(resource, filter.path).some((value) => value !== "" && !(Array.isArray(value) && value.length === 0));
|
|
464
|
+
case "value": {
|
|
465
|
+
const local = relative(resource, filter.path);
|
|
466
|
+
const items = local === void 0 ? void 0 : lookup(resource, local);
|
|
467
|
+
return Array.isArray(items) && items.some((item) => isRecord(item) && matches(item, filter.filter));
|
|
468
|
+
}
|
|
469
|
+
case "eq":
|
|
470
|
+
case "ne":
|
|
471
|
+
case "co":
|
|
472
|
+
case "sw":
|
|
473
|
+
case "ew":
|
|
474
|
+
case "gt":
|
|
475
|
+
case "ge":
|
|
476
|
+
case "lt":
|
|
477
|
+
case "le": {
|
|
478
|
+
const values = valuesAt(resource, filter.path);
|
|
479
|
+
if (values.length === 0) return filter.op === "ne";
|
|
480
|
+
return values.some((value) => compare(filter.op, value, filter.value, filter.path));
|
|
481
|
+
}
|
|
482
|
+
default: throw new ScimFilterError(`Unknown filter ${JSON.stringify(filter)}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
//#endregion
|
|
486
|
+
//#region src/blocks/sso/scim-patch.ts
|
|
487
|
+
/** A PATCH the service provider rejects, with its `scimType`. */
|
|
488
|
+
var ScimPatchError = class extends Error {
|
|
489
|
+
name = "ScimPatchError";
|
|
490
|
+
scimType;
|
|
491
|
+
constructor(scimType, message) {
|
|
492
|
+
super(message);
|
|
493
|
+
this.scimType = scimType;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
const PATH = /^([A-Za-z$][\w$-]*)(?:\[(.*)\])?(?:\.([A-Za-z$][\w$-]*))?$/s;
|
|
497
|
+
const find = (attributes, name) => attributes?.find((a) => a.name.toLowerCase() === name.toLowerCase());
|
|
498
|
+
function parsePath(raw, urn, attributes) {
|
|
499
|
+
const prefix = `${urn.toLowerCase()}:`;
|
|
500
|
+
const local = raw.toLowerCase().startsWith(prefix) ? raw.slice(prefix.length) : raw;
|
|
501
|
+
const match = PATH.exec(local);
|
|
502
|
+
if (!match) throw new ScimPatchError("invalidPath", `Invalid path ${raw}`);
|
|
503
|
+
const [, name = "", filterText, subName] = match;
|
|
504
|
+
const attribute = find(attributes, name);
|
|
505
|
+
if (!attribute) throw new ScimPatchError("invalidPath", `Unknown attribute ${name}`);
|
|
506
|
+
if (attribute.mutability === "readOnly") throw new ScimPatchError("mutability", `${attribute.name} is read-only`);
|
|
507
|
+
let filter;
|
|
508
|
+
if (filterText !== void 0) {
|
|
509
|
+
if (!attribute.multiValued) throw new ScimPatchError("invalidPath", `${attribute.name} is not multi-valued`);
|
|
510
|
+
try {
|
|
511
|
+
filter = parseFilter(filterText);
|
|
512
|
+
} catch (cause) {
|
|
513
|
+
throw new ScimPatchError("invalidFilter", cause instanceof Error ? cause.message : String(cause));
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
let sub;
|
|
517
|
+
if (subName !== void 0) {
|
|
518
|
+
sub = find(attribute.subAttributes, subName);
|
|
519
|
+
if (!sub) throw new ScimPatchError("invalidPath", `Unknown attribute ${attribute.name}.${subName}`);
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
attribute,
|
|
523
|
+
filter,
|
|
524
|
+
sub
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
const itemsOf = (value) => {
|
|
528
|
+
return (Array.isArray(value) ? value : [value]).map((item) => isRecord(item) ? { ...item } : { value: item });
|
|
529
|
+
};
|
|
530
|
+
const without = (item, key) => Object.fromEntries(Object.entries(item).filter(([name]) => name !== key));
|
|
531
|
+
const sameValue = (a, b) => String(a["value"]).toLowerCase() === String(b["value"]).toLowerCase();
|
|
532
|
+
/** Keeps one `primary: true` value: the last one set. */
|
|
533
|
+
function onePrimary(items) {
|
|
534
|
+
const last = items.findLastIndex((item) => item["primary"] === true);
|
|
535
|
+
return items.map((item, index) => index !== last && item["primary"] === true ? {
|
|
536
|
+
...item,
|
|
537
|
+
primary: false
|
|
538
|
+
} : item);
|
|
539
|
+
}
|
|
540
|
+
function apply(doc, op, path, value) {
|
|
541
|
+
const { attribute, filter, sub } = path;
|
|
542
|
+
const key = attribute.name;
|
|
543
|
+
if (attribute.multiValued) {
|
|
544
|
+
const current = Array.isArray(doc[key]) ? itemsOf(doc[key]) : [];
|
|
545
|
+
if (filter === void 0) {
|
|
546
|
+
if (sub !== void 0) {
|
|
547
|
+
if (op === "remove") doc[key] = current.map((item) => without(item, sub.name));
|
|
548
|
+
else doc[key] = current.length === 0 ? [{ [sub.name]: value }] : current.map((item) => ({
|
|
549
|
+
...item,
|
|
550
|
+
[sub.name]: value
|
|
551
|
+
}));
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (op === "remove") {
|
|
555
|
+
if (value === void 0) delete doc[key];
|
|
556
|
+
else {
|
|
557
|
+
const gone = itemsOf(value);
|
|
558
|
+
doc[key] = current.filter((item) => !gone.some((other) => sameValue(item, other)));
|
|
559
|
+
}
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const added = itemsOf(value);
|
|
563
|
+
doc[key] = onePrimary(op === "replace" ? added : [...current, ...added.filter((item) => !current.some((other) => sameValue(item, other)))]);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const hit = (item) => matches(item, filter);
|
|
567
|
+
if (op === "remove") {
|
|
568
|
+
doc[key] = sub === void 0 ? current.filter((item) => !hit(item)) : current.map((item) => hit(item) ? without(item, sub.name) : item);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (!current.some(hit)) throw new ScimPatchError("noTarget", `No ${key} value matches the filter`);
|
|
572
|
+
doc[key] = onePrimary(current.map((item) => {
|
|
573
|
+
if (!hit(item)) return item;
|
|
574
|
+
if (sub !== void 0) return {
|
|
575
|
+
...item,
|
|
576
|
+
[sub.name]: value
|
|
577
|
+
};
|
|
578
|
+
if (!isRecord(value)) throw new ScimPatchError("invalidValue", `${key} values are objects`);
|
|
579
|
+
return op === "replace" ? { ...value } : {
|
|
580
|
+
...item,
|
|
581
|
+
...value
|
|
582
|
+
};
|
|
583
|
+
}));
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (sub !== void 0) {
|
|
587
|
+
const inner = isRecord(doc[key]) ? { ...doc[key] } : {};
|
|
588
|
+
if (op === "remove") delete inner[sub.name];
|
|
589
|
+
else inner[sub.name] = value;
|
|
590
|
+
doc[key] = inner;
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (op === "remove") {
|
|
594
|
+
if (attribute.required) throw new ScimPatchError("mutability", `${key} is required`);
|
|
595
|
+
delete doc[key];
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (attribute.type === "complex") {
|
|
599
|
+
if (!isRecord(value)) throw new ScimPatchError("invalidValue", `${key} is an object`);
|
|
600
|
+
doc[key] = op === "add" && isRecord(doc[key]) ? {
|
|
601
|
+
...doc[key],
|
|
602
|
+
...value
|
|
603
|
+
} : { ...value };
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
doc[key] = value;
|
|
607
|
+
}
|
|
608
|
+
const OPS = [
|
|
609
|
+
"add",
|
|
610
|
+
"replace",
|
|
611
|
+
"remove"
|
|
612
|
+
];
|
|
613
|
+
/**
|
|
614
|
+
* Applies a PatchOp body's `Operations` to a copy of `resource`, in order.
|
|
615
|
+
* Throws `ScimPatchError`; the original is never changed.
|
|
616
|
+
*/
|
|
617
|
+
function applyPatch(resource, body, urn, attributes, patchUrn) {
|
|
618
|
+
if (!isRecord(body) || !Array.isArray(body["schemas"]) || !body["schemas"].includes(patchUrn) || !Array.isArray(body["Operations"])) throw new ScimPatchError("invalidSyntax", `A PATCH body has schemas [${patchUrn}] and Operations`);
|
|
619
|
+
const doc = structuredClone(resource);
|
|
620
|
+
for (const operation of body["Operations"]) {
|
|
621
|
+
if (!isRecord(operation)) throw new ScimPatchError("invalidSyntax", "Each operation is an object");
|
|
622
|
+
const name = String(operation["op"]).toLowerCase();
|
|
623
|
+
const op = OPS.find((candidate) => candidate === name);
|
|
624
|
+
if (op === void 0) throw new ScimPatchError("invalidSyntax", `Unknown op ${String(operation["op"])}`);
|
|
625
|
+
const value = operation["value"];
|
|
626
|
+
const raw = operation["path"];
|
|
627
|
+
if (raw !== void 0 && typeof raw !== "string") throw new ScimPatchError("invalidPath", "path is a string");
|
|
628
|
+
if (raw !== void 0 && raw.length > 0) {
|
|
629
|
+
apply(doc, op, parsePath(raw, urn, attributes), value);
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (op === "remove") throw new ScimPatchError("noTarget", "remove needs a path");
|
|
633
|
+
if (!isRecord(value)) throw new ScimPatchError("invalidValue", "Without a path the value is an object of attributes");
|
|
634
|
+
for (const [key, inner] of Object.entries(value)) if (key.toLowerCase() === urn.toLowerCase() && isRecord(inner)) for (const [nested, nestedValue] of Object.entries(inner)) apply(doc, op, parsePath(nested, urn, attributes), nestedValue);
|
|
635
|
+
else if (key.toLowerCase() !== "schemas") apply(doc, op, parsePath(key, urn, attributes), inner);
|
|
636
|
+
}
|
|
637
|
+
return doc;
|
|
638
|
+
}
|
|
639
|
+
//#endregion
|
|
640
|
+
//#region src/blocks/sso/scim-schema.ts
|
|
641
|
+
/** SCIM 2.0 schema URIs and the attributes this service provider supports (RFC 7643). */
|
|
642
|
+
const SCIM_USER = "urn:ietf:params:scim:schemas:core:2.0:User";
|
|
643
|
+
const SCIM_GROUP = "urn:ietf:params:scim:schemas:core:2.0:Group";
|
|
644
|
+
const SCIM_LIST = "urn:ietf:params:scim:api:messages:2.0:ListResponse";
|
|
645
|
+
const SCIM_ERROR = "urn:ietf:params:scim:api:messages:2.0:Error";
|
|
646
|
+
const SCIM_PATCH = "urn:ietf:params:scim:api:messages:2.0:PatchOp";
|
|
647
|
+
const SCIM_SEARCH = "urn:ietf:params:scim:api:messages:2.0:SearchRequest";
|
|
648
|
+
const SCIM_SERVICE_PROVIDER_CONFIG = "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig";
|
|
649
|
+
const SCIM_RESOURCE_TYPE = "urn:ietf:params:scim:schemas:core:2.0:ResourceType";
|
|
650
|
+
const SCIM_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Schema";
|
|
651
|
+
const text = (name, description, extra = {}) => ({
|
|
652
|
+
name,
|
|
653
|
+
type: "string",
|
|
654
|
+
multiValued: false,
|
|
655
|
+
description,
|
|
656
|
+
required: false,
|
|
657
|
+
caseExact: false,
|
|
658
|
+
mutability: "readWrite",
|
|
659
|
+
returned: "default",
|
|
660
|
+
uniqueness: "none",
|
|
661
|
+
...extra
|
|
662
|
+
});
|
|
663
|
+
const reference = (mutability) => ({
|
|
664
|
+
name: "$ref",
|
|
665
|
+
type: "reference",
|
|
666
|
+
referenceTypes: ["User", "Group"],
|
|
667
|
+
multiValued: false,
|
|
668
|
+
description: "The URI of the resource.",
|
|
669
|
+
required: false,
|
|
670
|
+
caseExact: false,
|
|
671
|
+
mutability,
|
|
672
|
+
returned: "default",
|
|
673
|
+
uniqueness: "none"
|
|
674
|
+
});
|
|
675
|
+
const USER_ATTRIBUTES = [
|
|
676
|
+
text("userName", "Unique identifier for the User within the organization.", {
|
|
677
|
+
required: true,
|
|
678
|
+
uniqueness: "server"
|
|
679
|
+
}),
|
|
680
|
+
{
|
|
681
|
+
name: "name",
|
|
682
|
+
type: "complex",
|
|
683
|
+
multiValued: false,
|
|
684
|
+
description: "The components of the user's name.",
|
|
685
|
+
required: false,
|
|
686
|
+
mutability: "readWrite",
|
|
687
|
+
returned: "default",
|
|
688
|
+
uniqueness: "none",
|
|
689
|
+
subAttributes: [
|
|
690
|
+
text("formatted", "The full name."),
|
|
691
|
+
text("familyName", "The family name."),
|
|
692
|
+
text("givenName", "The given name.")
|
|
693
|
+
]
|
|
694
|
+
},
|
|
695
|
+
text("displayName", "The name of the User, suitable for display."),
|
|
696
|
+
{
|
|
697
|
+
name: "emails",
|
|
698
|
+
type: "complex",
|
|
699
|
+
multiValued: true,
|
|
700
|
+
description: "Email addresses for the user. The primary address links the User to an account at a verified domain.",
|
|
701
|
+
required: false,
|
|
702
|
+
mutability: "readWrite",
|
|
703
|
+
returned: "default",
|
|
704
|
+
uniqueness: "none",
|
|
705
|
+
subAttributes: [
|
|
706
|
+
text("value", "The email address."),
|
|
707
|
+
text("display", "A label for display."),
|
|
708
|
+
text("type", "The kind of address.", { canonicalValues: [
|
|
709
|
+
"work",
|
|
710
|
+
"home",
|
|
711
|
+
"other"
|
|
712
|
+
] }),
|
|
713
|
+
{
|
|
714
|
+
name: "primary",
|
|
715
|
+
type: "boolean",
|
|
716
|
+
multiValued: false,
|
|
717
|
+
description: "Whether this is the primary address.",
|
|
718
|
+
required: false,
|
|
719
|
+
mutability: "readWrite",
|
|
720
|
+
returned: "default"
|
|
721
|
+
}
|
|
722
|
+
]
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
name: "active",
|
|
726
|
+
type: "boolean",
|
|
727
|
+
multiValued: false,
|
|
728
|
+
description: "Whether the user may use the organization. False removes the membership.",
|
|
729
|
+
required: false,
|
|
730
|
+
mutability: "readWrite",
|
|
731
|
+
returned: "default"
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
name: "groups",
|
|
735
|
+
type: "complex",
|
|
736
|
+
multiValued: true,
|
|
737
|
+
description: "The groups the user belongs to, through Group membership.",
|
|
738
|
+
required: false,
|
|
739
|
+
mutability: "readOnly",
|
|
740
|
+
returned: "default",
|
|
741
|
+
subAttributes: [
|
|
742
|
+
text("value", "The id of the Group.", { mutability: "readOnly" }),
|
|
743
|
+
reference("readOnly"),
|
|
744
|
+
text("display", "The displayName of the Group.", { mutability: "readOnly" })
|
|
745
|
+
]
|
|
746
|
+
}
|
|
747
|
+
];
|
|
748
|
+
const GROUP_ATTRIBUTES = [text("displayName", "A name for the Group. Groups named like a role, or mapped in groupRoles, grant that role.", { required: true }), {
|
|
749
|
+
name: "members",
|
|
750
|
+
type: "complex",
|
|
751
|
+
multiValued: true,
|
|
752
|
+
description: "The Users in the Group.",
|
|
753
|
+
required: false,
|
|
754
|
+
mutability: "readWrite",
|
|
755
|
+
returned: "default",
|
|
756
|
+
subAttributes: [
|
|
757
|
+
text("value", "The id of the User.", { mutability: "immutable" }),
|
|
758
|
+
reference("immutable"),
|
|
759
|
+
text("type", "The kind of member.", {
|
|
760
|
+
canonicalValues: ["User"],
|
|
761
|
+
mutability: "immutable"
|
|
762
|
+
})
|
|
763
|
+
]
|
|
764
|
+
}];
|
|
765
|
+
/** The writable attribute names of each resource, for PATCH paths. */
|
|
766
|
+
const ATTRIBUTES = {
|
|
767
|
+
User: [...USER_ATTRIBUTES, text("externalId", "The client's identifier for the resource.", { caseExact: true })],
|
|
768
|
+
Group: [...GROUP_ATTRIBUTES, text("externalId", "The client's identifier for the resource.", { caseExact: true })]
|
|
769
|
+
};
|
|
770
|
+
/** `/ServiceProviderConfig` (RFC 7643 §5). */
|
|
771
|
+
function serviceProviderConfig(base, options) {
|
|
772
|
+
return {
|
|
773
|
+
schemas: [SCIM_SERVICE_PROVIDER_CONFIG],
|
|
774
|
+
...options.documentationUri === void 0 ? {} : { documentationUri: options.documentationUri },
|
|
775
|
+
patch: { supported: true },
|
|
776
|
+
bulk: {
|
|
777
|
+
supported: false,
|
|
778
|
+
maxOperations: 0,
|
|
779
|
+
maxPayloadSize: 0
|
|
780
|
+
},
|
|
781
|
+
filter: {
|
|
782
|
+
supported: true,
|
|
783
|
+
maxResults: options.maxResults
|
|
784
|
+
},
|
|
785
|
+
changePassword: { supported: false },
|
|
786
|
+
sort: { supported: false },
|
|
787
|
+
etag: { supported: true },
|
|
788
|
+
authenticationSchemes: [{
|
|
789
|
+
type: "oauthbearertoken",
|
|
790
|
+
name: "OAuth Bearer Token",
|
|
791
|
+
description: "An organization API key with the scim scope, sent as a bearer token.",
|
|
792
|
+
primary: true
|
|
793
|
+
}],
|
|
794
|
+
meta: {
|
|
795
|
+
resourceType: "ServiceProviderConfig",
|
|
796
|
+
location: `${base}/ServiceProviderConfig`
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
/** `/ResourceTypes` entries (RFC 7643 §6). */
|
|
801
|
+
function resourceTypes(base) {
|
|
802
|
+
return [{
|
|
803
|
+
schemas: [SCIM_RESOURCE_TYPE],
|
|
804
|
+
id: "User",
|
|
805
|
+
name: "User",
|
|
806
|
+
endpoint: "/Users",
|
|
807
|
+
description: "A member of the organization",
|
|
808
|
+
schema: SCIM_USER,
|
|
809
|
+
schemaExtensions: [],
|
|
810
|
+
meta: {
|
|
811
|
+
resourceType: "ResourceType",
|
|
812
|
+
location: `${base}/ResourceTypes/User`
|
|
813
|
+
}
|
|
814
|
+
}, {
|
|
815
|
+
schemas: [SCIM_RESOURCE_TYPE],
|
|
816
|
+
id: "Group",
|
|
817
|
+
name: "Group",
|
|
818
|
+
endpoint: "/Groups",
|
|
819
|
+
description: "A group of members, mapped to a role",
|
|
820
|
+
schema: SCIM_GROUP,
|
|
821
|
+
schemaExtensions: [],
|
|
822
|
+
meta: {
|
|
823
|
+
resourceType: "ResourceType",
|
|
824
|
+
location: `${base}/ResourceTypes/Group`
|
|
825
|
+
}
|
|
826
|
+
}];
|
|
827
|
+
}
|
|
828
|
+
/** `/Schemas` entries (RFC 7643 §7). */
|
|
829
|
+
function schemas(base) {
|
|
830
|
+
return [{
|
|
831
|
+
schemas: [SCIM_SCHEMA],
|
|
832
|
+
id: SCIM_USER,
|
|
833
|
+
name: "User",
|
|
834
|
+
description: "User Account",
|
|
835
|
+
attributes: USER_ATTRIBUTES,
|
|
836
|
+
meta: {
|
|
837
|
+
resourceType: "Schema",
|
|
838
|
+
location: `${base}/Schemas/${SCIM_USER}`
|
|
839
|
+
}
|
|
840
|
+
}, {
|
|
841
|
+
schemas: [SCIM_SCHEMA],
|
|
842
|
+
id: SCIM_GROUP,
|
|
843
|
+
name: "Group",
|
|
844
|
+
description: "Group",
|
|
845
|
+
attributes: GROUP_ATTRIBUTES,
|
|
846
|
+
meta: {
|
|
847
|
+
resourceType: "Schema",
|
|
848
|
+
location: `${base}/Schemas/${SCIM_GROUP}`
|
|
849
|
+
}
|
|
850
|
+
}];
|
|
851
|
+
}
|
|
852
|
+
//#endregion
|
|
853
|
+
//#region src/blocks/sso/scim.ts
|
|
854
|
+
const MEDIA_TYPE = "application/scim+json";
|
|
855
|
+
const MAX_BODY = 1048576;
|
|
856
|
+
var ScimError = class extends Error {
|
|
857
|
+
name = "ScimError";
|
|
858
|
+
status;
|
|
859
|
+
scimType;
|
|
860
|
+
headers;
|
|
861
|
+
constructor(status, detail, scimType, headers = {}) {
|
|
862
|
+
super(detail);
|
|
863
|
+
this.status = status;
|
|
864
|
+
this.scimType = scimType;
|
|
865
|
+
this.headers = headers;
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
function json(status, body, headers = {}) {
|
|
869
|
+
return new Response(JSON.stringify(body), {
|
|
870
|
+
status,
|
|
871
|
+
headers: {
|
|
872
|
+
"content-type": MEDIA_TYPE,
|
|
873
|
+
...headers
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
function errorResponse(error) {
|
|
878
|
+
return json(error.status, {
|
|
879
|
+
schemas: [SCIM_ERROR],
|
|
880
|
+
status: String(error.status),
|
|
881
|
+
...error.scimType === void 0 ? {} : { scimType: error.scimType },
|
|
882
|
+
detail: error.message
|
|
883
|
+
}, error.headers);
|
|
884
|
+
}
|
|
885
|
+
function fromDbError(error) {
|
|
886
|
+
if (error.hint === "SCIM_NOT_FOUND") return new ScimError(404, error.message);
|
|
887
|
+
if (error.hint === "SCIM_PRECONDITION") return new ScimError(412, "The resource changed; read it again");
|
|
888
|
+
if (error.kind === "conflict") return new ScimError(409, "A resource with this value exists", "uniqueness");
|
|
889
|
+
if (error.hint === "SCIM_INVALID" || error.kind === "invalid_input" || error.kind === "check" || error.kind === "not_null") return new ScimError(400, error.message, "invalidValue");
|
|
890
|
+
return new ScimError(500, "The service provider failed");
|
|
891
|
+
}
|
|
892
|
+
const etag = (version) => `W/"${String(version)}"`;
|
|
893
|
+
/** The version an `If-Match` header requires; `undefined` for none or `*`. */
|
|
894
|
+
function ifMatch(request) {
|
|
895
|
+
const header = request.headers.get("if-match")?.trim();
|
|
896
|
+
if (!header || header === "*") return void 0;
|
|
897
|
+
const version = /^(?:W\/)?"(\d+)"$/.exec(header)?.[1];
|
|
898
|
+
if (version === void 0) throw new ScimError(412, "If-Match names no current version");
|
|
899
|
+
return Number(version);
|
|
900
|
+
}
|
|
901
|
+
function meta(row, kind, location) {
|
|
902
|
+
return {
|
|
903
|
+
resourceType: kind,
|
|
904
|
+
created: toInstant(textOf(row["created_at"])).toString(),
|
|
905
|
+
lastModified: toInstant(textOf(row["updated_at"])).toString(),
|
|
906
|
+
location,
|
|
907
|
+
version: etag(row["version"])
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function compact(doc) {
|
|
911
|
+
return Object.fromEntries(Object.entries(doc).filter(([, value]) => value !== void 0));
|
|
912
|
+
}
|
|
913
|
+
function userOf(value, base) {
|
|
914
|
+
const row = recordOf(value, "scim_users");
|
|
915
|
+
const id = textOf(row["id"]);
|
|
916
|
+
const givenName = optionalText(row["given_name"]);
|
|
917
|
+
const familyName = optionalText(row["family_name"]);
|
|
918
|
+
return compact({
|
|
919
|
+
schemas: [SCIM_USER],
|
|
920
|
+
id,
|
|
921
|
+
externalId: optionalText(row["external_id"]),
|
|
922
|
+
userName: textOf(row["user_name"]),
|
|
923
|
+
name: givenName === void 0 && familyName === void 0 ? void 0 : compact({
|
|
924
|
+
givenName,
|
|
925
|
+
familyName,
|
|
926
|
+
formatted: [givenName, familyName].filter(Boolean).join(" ")
|
|
927
|
+
}),
|
|
928
|
+
displayName: optionalText(row["display_name"]),
|
|
929
|
+
emails: Array.isArray(row["emails"]) ? row["emails"] : [],
|
|
930
|
+
active: row["active"] !== false,
|
|
931
|
+
groups: recordsOf(row["groups"], "scim_users.groups").map((group) => ({
|
|
932
|
+
value: textOf(group["value"]),
|
|
933
|
+
display: textOf(group["display"]),
|
|
934
|
+
$ref: `${base}/Groups/${textOf(group["value"])}`
|
|
935
|
+
})),
|
|
936
|
+
meta: meta(row, "User", `${base}/Users/${id}`)
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
function groupOf(value, base) {
|
|
940
|
+
const row = recordOf(value, "scim_groups");
|
|
941
|
+
const id = textOf(row["id"]);
|
|
942
|
+
return compact({
|
|
943
|
+
schemas: [SCIM_GROUP],
|
|
944
|
+
id,
|
|
945
|
+
externalId: optionalText(row["external_id"]),
|
|
946
|
+
displayName: textOf(row["display_name"]),
|
|
947
|
+
members: recordsOf(row["members"], "scim_groups.members").map((member) => ({
|
|
948
|
+
value: textOf(member["value"]),
|
|
949
|
+
display: textOf(member["display"]),
|
|
950
|
+
type: "User",
|
|
951
|
+
$ref: `${base}/Users/${textOf(member["value"])}`
|
|
952
|
+
})),
|
|
953
|
+
meta: meta(row, "Group", `${base}/Groups/${id}`)
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
function stringField(doc, key) {
|
|
957
|
+
const value = doc[key];
|
|
958
|
+
if (value === void 0 || value === null) return void 0;
|
|
959
|
+
if (typeof value !== "string") throw new ScimError(400, `${key} is a string`, "invalidValue");
|
|
960
|
+
return value;
|
|
961
|
+
}
|
|
962
|
+
function booleanField(value) {
|
|
963
|
+
if (value === void 0 || value === null) return true;
|
|
964
|
+
if (typeof value === "boolean") return value;
|
|
965
|
+
if (typeof value === "string" && /^(true|false)$/i.test(value)) return value.toLowerCase() === "true";
|
|
966
|
+
throw new ScimError(400, "active is a boolean", "invalidValue");
|
|
967
|
+
}
|
|
968
|
+
/** Attribute names are case-insensitive: `UserName` reads as `userName`. */
|
|
969
|
+
function canonical(doc, kind) {
|
|
970
|
+
const names = [
|
|
971
|
+
...ATTRIBUTES[kind].map((attribute) => attribute.name),
|
|
972
|
+
"schemas",
|
|
973
|
+
"id",
|
|
974
|
+
"meta"
|
|
975
|
+
];
|
|
976
|
+
const out = {};
|
|
977
|
+
for (const [key, value] of Object.entries(doc)) {
|
|
978
|
+
const name = names.find((n) => n.toLowerCase() === key.toLowerCase());
|
|
979
|
+
out[name ?? key] = value;
|
|
980
|
+
}
|
|
981
|
+
return out;
|
|
982
|
+
}
|
|
983
|
+
function userData(input) {
|
|
984
|
+
const doc = canonical(input, "User");
|
|
985
|
+
const userName = stringField(doc, "userName");
|
|
986
|
+
if (userName === void 0 || userName.trim() === "") throw new ScimError(400, "userName is required", "invalidValue");
|
|
987
|
+
const name = isRecord(doc["name"]) ? doc["name"] : {};
|
|
988
|
+
const raw = doc["emails"] ?? [];
|
|
989
|
+
const emails = Array.isArray(raw) ? raw.filter(isRecord) : [];
|
|
990
|
+
if (!Array.isArray(raw) || emails.length !== raw.length || !emails.every((email) => typeof email["value"] === "string")) throw new ScimError(400, "emails are objects with a value", "invalidValue");
|
|
991
|
+
return {
|
|
992
|
+
userName,
|
|
993
|
+
externalId: stringField(doc, "externalId") ?? null,
|
|
994
|
+
displayName: stringField(doc, "displayName") ?? null,
|
|
995
|
+
givenName: stringField(name, "givenName") ?? null,
|
|
996
|
+
familyName: stringField(name, "familyName") ?? null,
|
|
997
|
+
emails: emails.map((email) => compact({
|
|
998
|
+
value: email["value"],
|
|
999
|
+
type: optionalText(email["type"]),
|
|
1000
|
+
display: optionalText(email["display"]),
|
|
1001
|
+
primary: email["primary"] === true ? true : void 0
|
|
1002
|
+
})),
|
|
1003
|
+
active: booleanField(doc["active"])
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function groupData(input) {
|
|
1007
|
+
const doc = canonical(input, "Group");
|
|
1008
|
+
const displayName = stringField(doc, "displayName");
|
|
1009
|
+
if (displayName === void 0 || displayName.trim() === "") throw new ScimError(400, "displayName is required", "invalidValue");
|
|
1010
|
+
const raw = doc["members"] ?? [];
|
|
1011
|
+
const members = Array.isArray(raw) ? raw.filter(isRecord) : [];
|
|
1012
|
+
if (!Array.isArray(raw) || members.length !== raw.length || !members.every((member) => typeof member["value"] === "string")) throw new ScimError(400, "members are objects with a value", "invalidValue");
|
|
1013
|
+
return {
|
|
1014
|
+
displayName,
|
|
1015
|
+
externalId: stringField(doc, "externalId") ?? null,
|
|
1016
|
+
members: members.map((member) => String(member["value"]))
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
/** `attributes` and `excludedAttributes` (RFC 7644 §3.4.2.5); `id` and `schemas` always stay. */
|
|
1020
|
+
function project(doc, attributes, excluded) {
|
|
1021
|
+
const always = /* @__PURE__ */ new Set(["id", "schemas"]);
|
|
1022
|
+
const top = (path) => {
|
|
1023
|
+
const local = path.slice(path.lastIndexOf(":") + 1);
|
|
1024
|
+
return (local.split(".")[0] ?? local).toLowerCase();
|
|
1025
|
+
};
|
|
1026
|
+
if (attributes && attributes.length > 0) {
|
|
1027
|
+
const wanted = new Set(attributes.map(top));
|
|
1028
|
+
return Object.fromEntries(Object.entries(doc).filter(([key]) => always.has(key) || wanted.has(key.toLowerCase())));
|
|
1029
|
+
}
|
|
1030
|
+
if (excluded && excluded.length > 0) {
|
|
1031
|
+
const dropped = new Set(excluded.map(top));
|
|
1032
|
+
return Object.fromEntries(Object.entries(doc).filter(([key]) => always.has(key) || !dropped.has(key.toLowerCase())));
|
|
1033
|
+
}
|
|
1034
|
+
return doc;
|
|
1035
|
+
}
|
|
1036
|
+
const list = (value) => value === null ? void 0 : value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
1037
|
+
function integer(value) {
|
|
1038
|
+
const number = typeof value === "string" ? Number(value) : value;
|
|
1039
|
+
return typeof number === "number" && Number.isInteger(number) ? number : void 0;
|
|
1040
|
+
}
|
|
1041
|
+
function queryOf(source, maxResults) {
|
|
1042
|
+
if (source.attributes?.length && source.excluded?.length) throw new ScimError(400, "attributes and excludedAttributes are mutually exclusive", "invalidSyntax");
|
|
1043
|
+
let filter;
|
|
1044
|
+
if (typeof source.filter === "string" && source.filter.trim() !== "") try {
|
|
1045
|
+
filter = parseFilter(source.filter);
|
|
1046
|
+
} catch (cause) {
|
|
1047
|
+
throw new ScimError(400, cause instanceof Error ? cause.message : String(cause), "invalidFilter");
|
|
1048
|
+
}
|
|
1049
|
+
return {
|
|
1050
|
+
filter,
|
|
1051
|
+
startIndex: Math.max(1, integer(source.startIndex) ?? 1),
|
|
1052
|
+
count: Math.min(maxResults, Math.max(0, integer(source.count) ?? maxResults)),
|
|
1053
|
+
attributes: source.attributes,
|
|
1054
|
+
excluded: source.excluded
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
async function readBody(request) {
|
|
1058
|
+
const text = await request.text();
|
|
1059
|
+
if (text.length > MAX_BODY) throw new ScimError(413, "The request body is too large");
|
|
1060
|
+
try {
|
|
1061
|
+
return JSON.parse(text);
|
|
1062
|
+
} catch {
|
|
1063
|
+
throw new ScimError(400, "The body is not JSON", "invalidSyntax");
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
function resourceBody(body, urn) {
|
|
1067
|
+
if (!isRecord(body)) throw new ScimError(400, "The body is a JSON object", "invalidSyntax");
|
|
1068
|
+
if (!(Array.isArray(body["schemas"]) ? body["schemas"] : []).some((schema) => String(schema).toLowerCase() === urn.toLowerCase())) throw new ScimError(400, `schemas must include ${urn}`, "invalidSyntax");
|
|
1069
|
+
return body;
|
|
1070
|
+
}
|
|
1071
|
+
const KINDS = {
|
|
1072
|
+
User: {
|
|
1073
|
+
endpoint: "Users",
|
|
1074
|
+
urn: SCIM_USER,
|
|
1075
|
+
list: "scim_list_users",
|
|
1076
|
+
get: "scim_get_user",
|
|
1077
|
+
save: "scim_save_user",
|
|
1078
|
+
remove: "scim_delete_user",
|
|
1079
|
+
of: userOf,
|
|
1080
|
+
data: userData
|
|
1081
|
+
},
|
|
1082
|
+
Group: {
|
|
1083
|
+
endpoint: "Groups",
|
|
1084
|
+
urn: SCIM_GROUP,
|
|
1085
|
+
list: "scim_list_groups",
|
|
1086
|
+
get: "scim_get_group",
|
|
1087
|
+
save: "scim_save_group",
|
|
1088
|
+
remove: "scim_delete_group",
|
|
1089
|
+
of: groupOf,
|
|
1090
|
+
data: groupData
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
/**
|
|
1094
|
+
* A SCIM 2.0 service provider (RFC 7644) as a web handler: `/Users` and
|
|
1095
|
+
* `/Groups` with filters, PATCH and ETags, plus the discovery endpoints.
|
|
1096
|
+
* Each request authenticates with an organization API key that has the
|
|
1097
|
+
* `scim` scope, and only touches that organization.
|
|
1098
|
+
*/
|
|
1099
|
+
function scimHandler(options) {
|
|
1100
|
+
applyTemporal(options);
|
|
1101
|
+
const call = blockCall(options.transport, options.schema);
|
|
1102
|
+
const basePath = (options.basePath ?? "").replace(/\/+$/, "");
|
|
1103
|
+
const scope = options.scope ?? "scim";
|
|
1104
|
+
const maxResults = options.maxResults ?? 100;
|
|
1105
|
+
const discovery = {
|
|
1106
|
+
maxResults,
|
|
1107
|
+
...options.documentationUri === void 0 ? {} : { documentationUri: options.documentationUri }
|
|
1108
|
+
};
|
|
1109
|
+
const rpc = async (fn, args) => {
|
|
1110
|
+
const result = await call(fn, args, (value) => value);
|
|
1111
|
+
if (!result.ok) throw fromDbError(result.error);
|
|
1112
|
+
return result.data;
|
|
1113
|
+
};
|
|
1114
|
+
const authenticate = async (request) => {
|
|
1115
|
+
const challenge = { "www-authenticate": "Bearer realm=\"SCIM\"" };
|
|
1116
|
+
const token = request.headers.get("authorization")?.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
|
|
1117
|
+
if (!token) throw new ScimError(401, "Send a bearer token", void 0, challenge);
|
|
1118
|
+
const checked = await options.keys.verify(token);
|
|
1119
|
+
if (!checked.ok) throw new ScimError(500, "Could not check the token");
|
|
1120
|
+
const check = checked.data;
|
|
1121
|
+
switch (check.status) {
|
|
1122
|
+
case "invalid": throw new ScimError(401, "The token is not valid", void 0, challenge);
|
|
1123
|
+
case "rate_limited": throw new ScimError(429, "Too many requests", void 0, { "retry-after": String(check.retryAfter) });
|
|
1124
|
+
case "ok": {
|
|
1125
|
+
const { key } = check;
|
|
1126
|
+
if (key.organizationId === void 0) throw new ScimError(403, "SCIM needs an organization API key");
|
|
1127
|
+
if (!key.scopes.includes(scope) && !key.scopes.includes("*")) throw new ScimError(403, `The key lacks the ${scope} scope`);
|
|
1128
|
+
return key.organizationId;
|
|
1129
|
+
}
|
|
1130
|
+
default: return check;
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
const discover = (request, url, base, segments) => {
|
|
1134
|
+
const [head, ...rest] = segments;
|
|
1135
|
+
const ids = rest.join("/");
|
|
1136
|
+
const listed = (items) => json(200, {
|
|
1137
|
+
schemas: [SCIM_LIST],
|
|
1138
|
+
totalResults: items.length,
|
|
1139
|
+
startIndex: 1,
|
|
1140
|
+
itemsPerPage: items.length,
|
|
1141
|
+
Resources: items
|
|
1142
|
+
});
|
|
1143
|
+
const one = (items) => {
|
|
1144
|
+
const found = items.find((item) => item["id"] === ids);
|
|
1145
|
+
if (!found) throw new ScimError(404, `No ${head} ${ids}`);
|
|
1146
|
+
return json(200, found);
|
|
1147
|
+
};
|
|
1148
|
+
if (head !== "ServiceProviderConfig" && head !== "ResourceTypes" && head !== "Schemas") return;
|
|
1149
|
+
if (request.method !== "GET") throw new ScimError(405, `${head} is read-only`);
|
|
1150
|
+
if (url.searchParams.has("filter")) throw new ScimError(403, "Discovery endpoints do not filter");
|
|
1151
|
+
if (head === "ServiceProviderConfig") return json(200, serviceProviderConfig(base, discovery));
|
|
1152
|
+
const items = head === "ResourceTypes" ? resourceTypes(base) : schemas(base);
|
|
1153
|
+
return ids === "" ? listed(items) : one(items);
|
|
1154
|
+
};
|
|
1155
|
+
const fetchAll = async (kind, tenant, base) => {
|
|
1156
|
+
const value = await rpc(KINDS[kind].list, { tenant });
|
|
1157
|
+
return recordsOf(value, KINDS[kind].list).map((row) => KINDS[kind].of(row, base));
|
|
1158
|
+
};
|
|
1159
|
+
const fetchOne = async (kind, tenant, id, base) => {
|
|
1160
|
+
if (!/^[0-9a-f-]{36}$/i.test(id)) throw new ScimError(404, `No ${kind} ${id}`);
|
|
1161
|
+
const value = await rpc(KINDS[kind].get, {
|
|
1162
|
+
tenant,
|
|
1163
|
+
id
|
|
1164
|
+
});
|
|
1165
|
+
if (!isRecord(value)) throw new ScimError(404, `No ${kind} ${id}`);
|
|
1166
|
+
return KINDS[kind].of(value, base);
|
|
1167
|
+
};
|
|
1168
|
+
const search = async (kind, tenant, base, query) => {
|
|
1169
|
+
let all = await fetchAll(kind, tenant, base);
|
|
1170
|
+
if (query.filter) {
|
|
1171
|
+
const filter = query.filter;
|
|
1172
|
+
try {
|
|
1173
|
+
all = all.filter((doc) => matches(doc, filter));
|
|
1174
|
+
} catch (cause) {
|
|
1175
|
+
if (cause instanceof ScimFilterError) throw new ScimError(400, cause.message, "invalidFilter");
|
|
1176
|
+
throw cause;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
const page = all.slice(query.startIndex - 1, query.startIndex - 1 + query.count).map((doc) => project(doc, query.attributes, query.excluded));
|
|
1180
|
+
return json(200, {
|
|
1181
|
+
schemas: [SCIM_LIST],
|
|
1182
|
+
totalResults: all.length,
|
|
1183
|
+
startIndex: query.startIndex,
|
|
1184
|
+
itemsPerPage: page.length,
|
|
1185
|
+
Resources: page
|
|
1186
|
+
});
|
|
1187
|
+
};
|
|
1188
|
+
const save = async (kind, tenant, id, doc, version, base) => {
|
|
1189
|
+
const value = await rpc(KINDS[kind].save, {
|
|
1190
|
+
tenant,
|
|
1191
|
+
id,
|
|
1192
|
+
data: KINDS[kind].data(doc),
|
|
1193
|
+
version: version ?? null
|
|
1194
|
+
});
|
|
1195
|
+
return KINDS[kind].of(value, base);
|
|
1196
|
+
};
|
|
1197
|
+
const respond = (status, doc, url, extra = {}) => {
|
|
1198
|
+
const metaOf = isRecord(doc["meta"]) ? doc["meta"] : {};
|
|
1199
|
+
return json(status, project(doc, list(url.searchParams.get("attributes")), list(url.searchParams.get("excludedAttributes"))), {
|
|
1200
|
+
etag: String(metaOf["version"]),
|
|
1201
|
+
...extra
|
|
1202
|
+
});
|
|
1203
|
+
};
|
|
1204
|
+
const resource = async (request, url, base, kind, id, tenant) => {
|
|
1205
|
+
const spec = KINDS[kind];
|
|
1206
|
+
const method = request.method;
|
|
1207
|
+
if (id === void 0 || id === ".search") {
|
|
1208
|
+
if (id === ".search") {
|
|
1209
|
+
if (method !== "POST") throw new ScimError(405, "Use POST for .search");
|
|
1210
|
+
const body = await readBody(request);
|
|
1211
|
+
if (!isRecord(body) || !Array.isArray(body["schemas"]) || !body["schemas"].includes("urn:ietf:params:scim:api:messages:2.0:SearchRequest")) throw new ScimError(400, `schemas must include ${SCIM_SEARCH}`, "invalidSyntax");
|
|
1212
|
+
const strings = (value) => Array.isArray(value) ? value.map(String) : void 0;
|
|
1213
|
+
return search(kind, tenant, base, queryOf({
|
|
1214
|
+
filter: body["filter"],
|
|
1215
|
+
startIndex: body["startIndex"],
|
|
1216
|
+
count: body["count"],
|
|
1217
|
+
attributes: strings(body["attributes"]),
|
|
1218
|
+
excluded: strings(body["excludedAttributes"])
|
|
1219
|
+
}, maxResults));
|
|
1220
|
+
}
|
|
1221
|
+
if (method === "GET") return search(kind, tenant, base, queryOf({
|
|
1222
|
+
filter: url.searchParams.get("filter") ?? void 0,
|
|
1223
|
+
startIndex: url.searchParams.get("startIndex") ?? void 0,
|
|
1224
|
+
count: url.searchParams.get("count") ?? void 0,
|
|
1225
|
+
attributes: list(url.searchParams.get("attributes")),
|
|
1226
|
+
excluded: list(url.searchParams.get("excludedAttributes"))
|
|
1227
|
+
}, maxResults));
|
|
1228
|
+
if (method === "POST") {
|
|
1229
|
+
const doc = resourceBody(await readBody(request), spec.urn);
|
|
1230
|
+
const created = await save(kind, tenant, null, doc, void 0, base);
|
|
1231
|
+
const location = String(isRecord(created["meta"]) ? created["meta"]["location"] : "");
|
|
1232
|
+
return respond(201, created, url, { location });
|
|
1233
|
+
}
|
|
1234
|
+
throw new ScimError(405, `${method} is not allowed on /${spec.endpoint}`);
|
|
1235
|
+
}
|
|
1236
|
+
const current = await fetchOne(kind, tenant, id, base);
|
|
1237
|
+
const currentMeta = isRecord(current["meta"]) ? current["meta"] : {};
|
|
1238
|
+
const currentVersion = Number(/\d+/.exec(String(currentMeta["version"]))?.[0]);
|
|
1239
|
+
const required = ifMatch(request);
|
|
1240
|
+
if (method !== "GET" && required !== void 0 && required !== currentVersion) throw new ScimError(412, "The resource changed; read it again");
|
|
1241
|
+
switch (method) {
|
|
1242
|
+
case "GET": {
|
|
1243
|
+
const unchanged = request.headers.get("if-none-match")?.trim();
|
|
1244
|
+
if (unchanged !== void 0 && unchanged === currentMeta["version"]) return new Response(null, {
|
|
1245
|
+
status: 304,
|
|
1246
|
+
headers: { etag: unchanged }
|
|
1247
|
+
});
|
|
1248
|
+
return respond(200, current, url);
|
|
1249
|
+
}
|
|
1250
|
+
case "PUT": {
|
|
1251
|
+
const doc = resourceBody(await readBody(request), spec.urn);
|
|
1252
|
+
return respond(200, await save(kind, tenant, id, doc, currentVersion, base), url);
|
|
1253
|
+
}
|
|
1254
|
+
case "PATCH": {
|
|
1255
|
+
const body = await readBody(request);
|
|
1256
|
+
let patched;
|
|
1257
|
+
try {
|
|
1258
|
+
patched = applyPatch(current, body, spec.urn, ATTRIBUTES[kind], SCIM_PATCH);
|
|
1259
|
+
} catch (cause) {
|
|
1260
|
+
if (cause instanceof ScimPatchError) throw new ScimError(400, cause.message, cause.scimType);
|
|
1261
|
+
throw cause;
|
|
1262
|
+
}
|
|
1263
|
+
return respond(200, await save(kind, tenant, id, patched, currentVersion, base), url);
|
|
1264
|
+
}
|
|
1265
|
+
case "DELETE":
|
|
1266
|
+
if (await rpc(spec.remove, {
|
|
1267
|
+
tenant,
|
|
1268
|
+
id
|
|
1269
|
+
}) !== true) throw new ScimError(404, `No ${kind} ${id}`);
|
|
1270
|
+
return new Response(null, { status: 204 });
|
|
1271
|
+
default: throw new ScimError(405, `${method} is not allowed`);
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
return async (request) => {
|
|
1275
|
+
try {
|
|
1276
|
+
const url = new URL(request.url);
|
|
1277
|
+
if (!url.pathname.startsWith(basePath)) throw new ScimError(404, "Not a SCIM endpoint");
|
|
1278
|
+
const base = `${url.origin}${basePath}`;
|
|
1279
|
+
const segments = url.pathname.slice(basePath.length).split("/").filter(Boolean).map(decodeURIComponent);
|
|
1280
|
+
const [head, id, ...rest] = segments;
|
|
1281
|
+
const discovered = discover(request, url, base, segments);
|
|
1282
|
+
if (discovered) return discovered;
|
|
1283
|
+
const tenant = await authenticate(request);
|
|
1284
|
+
if (head === "Me") throw new ScimError(501, "/Me is not supported");
|
|
1285
|
+
if (head === "Bulk") throw new ScimError(501, "Bulk operations are not supported");
|
|
1286
|
+
if (rest.length > 0) throw new ScimError(404, "Not a SCIM endpoint");
|
|
1287
|
+
if (head === "Users") return await resource(request, url, base, "User", id, tenant);
|
|
1288
|
+
if (head === "Groups") return await resource(request, url, base, "Group", id, tenant);
|
|
1289
|
+
throw new ScimError(404, "Not a SCIM endpoint");
|
|
1290
|
+
} catch (cause) {
|
|
1291
|
+
if (cause instanceof ScimError) return errorResponse(cause);
|
|
1292
|
+
return errorResponse(new ScimError(500, "The service provider failed"));
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
//#endregion
|
|
1297
|
+
export { SCIM_GROUP, SCIM_USER, ScimFilterError, createSso, createSsoAdmin, dohResolver, matches as matchesScimFilter, parseFilter as parseScimFilter, rpcTransport, scimHandler, sqlTransport };
|