@pithy-sh/core 0.1.1 → 0.1.3

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 (393) hide show
  1. package/dist/address/address.d.ts +57 -0
  2. package/dist/address/address.d.ts.map +1 -0
  3. package/dist/address/address.js +71 -0
  4. package/dist/audit/auditEvent.d.ts +89 -0
  5. package/dist/audit/auditEvent.d.ts.map +1 -0
  6. package/dist/audit/auditEvent.js +69 -0
  7. package/dist/audit/recorder.d.ts +18 -0
  8. package/dist/audit/recorder.d.ts.map +1 -0
  9. package/dist/audit/recorder.js +8 -0
  10. package/dist/capability/bindings.d.ts +62 -0
  11. package/dist/capability/bindings.d.ts.map +1 -0
  12. package/dist/capability/bindings.js +97 -0
  13. package/dist/capability/capability.d.ts +501 -0
  14. package/dist/capability/capability.d.ts.map +1 -0
  15. package/dist/capability/capability.js +16 -0
  16. package/dist/capability/client.d.ts +70 -0
  17. package/dist/capability/client.d.ts.map +1 -0
  18. package/dist/capability/client.js +86 -0
  19. package/dist/capability/compose.d.ts +25 -0
  20. package/dist/capability/compose.d.ts.map +1 -0
  21. package/dist/capability/compose.js +54 -0
  22. package/dist/capability/composition.d.ts +26 -0
  23. package/dist/capability/composition.d.ts.map +1 -0
  24. package/dist/capability/composition.js +84 -0
  25. package/dist/capability/config.d.ts +7 -0
  26. package/dist/capability/config.d.ts.map +1 -0
  27. package/dist/capability/config.js +14 -0
  28. package/dist/capability/devSecret.d.ts +32 -0
  29. package/dist/capability/devSecret.d.ts.map +1 -0
  30. package/dist/capability/devSecret.js +28 -0
  31. package/dist/capability/manifest.d.ts +298 -0
  32. package/dist/capability/manifest.d.ts.map +1 -0
  33. package/dist/capability/manifest.js +356 -0
  34. package/dist/capability/secretOrigin.d.ts +121 -0
  35. package/dist/capability/secretOrigin.d.ts.map +1 -0
  36. package/dist/capability/secretOrigin.js +134 -0
  37. package/dist/capability/settings.d.ts +119 -0
  38. package/dist/capability/settings.d.ts.map +1 -0
  39. package/dist/capability/settings.js +66 -0
  40. package/dist/capability/validateBindings.d.ts +4 -0
  41. package/dist/capability/validateBindings.d.ts.map +1 -0
  42. package/dist/capability/validateBindings.js +34 -0
  43. package/dist/capability/vanishingKey.d.ts +31 -0
  44. package/dist/capability/vanishingKey.d.ts.map +1 -0
  45. package/dist/capability/vanishingKey.js +79 -0
  46. package/dist/controlPlane/audit/actions.d.ts +70 -0
  47. package/dist/controlPlane/audit/actions.d.ts.map +1 -0
  48. package/dist/controlPlane/audit/actions.js +76 -0
  49. package/dist/controlPlane/capability.d.ts +47 -0
  50. package/dist/controlPlane/capability.d.ts.map +1 -0
  51. package/dist/controlPlane/capability.js +138 -0
  52. package/dist/controlPlane/config/config.d.ts +19 -0
  53. package/dist/controlPlane/config/config.d.ts.map +1 -0
  54. package/dist/controlPlane/config/config.js +85 -0
  55. package/dist/controlPlane/context.d.ts +27 -0
  56. package/dist/controlPlane/context.d.ts.map +1 -0
  57. package/dist/controlPlane/context.js +29 -0
  58. package/dist/controlPlane/data/connection.d.ts +79 -0
  59. package/dist/controlPlane/data/connection.d.ts.map +1 -0
  60. package/dist/controlPlane/data/connection.js +55 -0
  61. package/dist/controlPlane/data/keyLifecycle.d.ts +62 -0
  62. package/dist/controlPlane/data/keyLifecycle.d.ts.map +1 -0
  63. package/dist/controlPlane/data/keyLifecycle.js +126 -0
  64. package/dist/controlPlane/data/replay.d.ts +18 -0
  65. package/dist/controlPlane/data/replay.d.ts.map +1 -0
  66. package/dist/controlPlane/data/replay.js +20 -0
  67. package/dist/controlPlane/data/tables.d.ts +34 -0
  68. package/dist/controlPlane/data/tables.d.ts.map +1 -0
  69. package/dist/controlPlane/data/tables.js +37 -0
  70. package/dist/controlPlane/discovery/adminRoute.d.ts +313 -0
  71. package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -0
  72. package/dist/controlPlane/discovery/adminRoute.js +136 -0
  73. package/dist/controlPlane/discovery/configuration.d.ts +108 -0
  74. package/dist/controlPlane/discovery/configuration.d.ts.map +1 -0
  75. package/dist/controlPlane/discovery/configuration.js +187 -0
  76. package/dist/controlPlane/discovery/drift.d.ts +64 -0
  77. package/dist/controlPlane/discovery/drift.d.ts.map +1 -0
  78. package/dist/controlPlane/discovery/drift.js +58 -0
  79. package/dist/controlPlane/discovery/health.d.ts +99 -0
  80. package/dist/controlPlane/discovery/health.d.ts.map +1 -0
  81. package/dist/controlPlane/discovery/health.js +151 -0
  82. package/dist/controlPlane/discovery/healthSummary.d.ts +236 -0
  83. package/dist/controlPlane/discovery/healthSummary.d.ts.map +1 -0
  84. package/dist/controlPlane/discovery/healthSummary.js +278 -0
  85. package/dist/controlPlane/error/errors.d.ts +67 -0
  86. package/dist/controlPlane/error/errors.d.ts.map +1 -0
  87. package/dist/controlPlane/error/errors.js +79 -0
  88. package/dist/controlPlane/http/cors.d.ts +112 -0
  89. package/dist/controlPlane/http/cors.d.ts.map +1 -0
  90. package/dist/controlPlane/http/cors.js +192 -0
  91. package/dist/controlPlane/http/guard.d.ts +77 -0
  92. package/dist/controlPlane/http/guard.d.ts.map +1 -0
  93. package/dist/controlPlane/http/guard.js +142 -0
  94. package/dist/controlPlane/http/handlers.d.ts +179 -0
  95. package/dist/controlPlane/http/handlers.d.ts.map +1 -0
  96. package/dist/controlPlane/http/handlers.js +245 -0
  97. package/dist/controlPlane/http/responses.d.ts +99 -0
  98. package/dist/controlPlane/http/responses.d.ts.map +1 -0
  99. package/dist/controlPlane/http/responses.js +63 -0
  100. package/dist/controlPlane/http/routes.d.ts +40 -0
  101. package/dist/controlPlane/http/routes.d.ts.map +1 -0
  102. package/dist/controlPlane/http/routes.js +83 -0
  103. package/dist/controlPlane/http/schemas.d.ts +39 -0
  104. package/dist/controlPlane/http/schemas.d.ts.map +1 -0
  105. package/dist/controlPlane/http/schemas.js +31 -0
  106. package/dist/controlPlane/http/verify.d.ts +84 -0
  107. package/dist/controlPlane/http/verify.d.ts.map +1 -0
  108. package/dist/controlPlane/http/verify.js +65 -0
  109. package/dist/controlPlane/migrations/0001_init.d.ts +51 -0
  110. package/dist/controlPlane/migrations/0001_init.d.ts.map +1 -0
  111. package/dist/controlPlane/migrations/0001_init.js +65 -0
  112. package/dist/controlPlane/replay/d1Guard.d.ts +29 -0
  113. package/dist/controlPlane/replay/d1Guard.d.ts.map +1 -0
  114. package/dist/controlPlane/replay/d1Guard.js +39 -0
  115. package/dist/controlPlane/replay/guard.d.ts +52 -0
  116. package/dist/controlPlane/replay/guard.d.ts.map +1 -0
  117. package/dist/controlPlane/replay/guard.js +0 -0
  118. package/dist/controlPlane/replay/kvGuard.d.ts +99 -0
  119. package/dist/controlPlane/replay/kvGuard.d.ts.map +1 -0
  120. package/dist/controlPlane/replay/kvGuard.js +101 -0
  121. package/dist/controlPlane/scope/scope.d.ts +68 -0
  122. package/dist/controlPlane/scope/scope.d.ts.map +1 -0
  123. package/dist/controlPlane/scope/scope.js +63 -0
  124. package/dist/controlPlane/token/base64url.d.ts +10 -0
  125. package/dist/controlPlane/token/base64url.d.ts.map +1 -0
  126. package/dist/controlPlane/token/base64url.js +48 -0
  127. package/dist/controlPlane/token/claims.d.ts +83 -0
  128. package/dist/controlPlane/token/claims.d.ts.map +1 -0
  129. package/dist/controlPlane/token/claims.js +86 -0
  130. package/dist/controlPlane/token/digest.d.ts +44 -0
  131. package/dist/controlPlane/token/digest.d.ts.map +1 -0
  132. package/dist/controlPlane/token/digest.js +56 -0
  133. package/dist/controlPlane/token/jws.d.ts +52 -0
  134. package/dist/controlPlane/token/jws.d.ts.map +1 -0
  135. package/dist/controlPlane/token/jws.js +65 -0
  136. package/dist/controlPlane/token/mint.d.ts +56 -0
  137. package/dist/controlPlane/token/mint.d.ts.map +1 -0
  138. package/dist/controlPlane/token/mint.js +55 -0
  139. package/dist/controlPlane/wire.d.ts +122 -0
  140. package/dist/controlPlane/wire.d.ts.map +1 -0
  141. package/dist/controlPlane/wire.js +123 -0
  142. package/dist/createBackend.d.ts +58 -0
  143. package/dist/createBackend.d.ts.map +1 -0
  144. package/dist/createBackend.js +170 -0
  145. package/dist/createEntrypoint.d.ts +53 -0
  146. package/dist/createEntrypoint.d.ts.map +1 -0
  147. package/dist/createEntrypoint.js +52 -0
  148. package/dist/data/boundParameters.d.ts +83 -0
  149. package/dist/data/boundParameters.d.ts.map +1 -0
  150. package/dist/data/boundParameters.js +162 -0
  151. package/dist/data/codecs.d.ts +34 -0
  152. package/dist/data/codecs.d.ts.map +1 -0
  153. package/dist/data/codecs.js +135 -0
  154. package/dist/data/cursor.d.ts +81 -0
  155. package/dist/data/cursor.d.ts.map +1 -0
  156. package/dist/data/cursor.js +104 -0
  157. package/dist/data/databases.d.ts +55 -0
  158. package/dist/data/databases.d.ts.map +1 -0
  159. package/dist/data/databases.js +32 -0
  160. package/dist/data/db.d.ts +42 -0
  161. package/dist/data/db.d.ts.map +1 -0
  162. package/dist/data/db.js +24 -0
  163. package/dist/data/withD1Retry.d.ts +91 -0
  164. package/dist/data/withD1Retry.d.ts.map +1 -0
  165. package/dist/data/withD1Retry.js +152 -0
  166. package/dist/entitlement/entitlement.d.ts +118 -0
  167. package/dist/entitlement/entitlement.d.ts.map +1 -0
  168. package/dist/entitlement/entitlement.js +59 -0
  169. package/dist/entitlement/gateScan.d.ts +26 -0
  170. package/dist/entitlement/gateScan.d.ts.map +1 -0
  171. package/dist/entitlement/gateScan.js +96 -0
  172. package/dist/entitlement/require.d.ts +12 -0
  173. package/dist/entitlement/require.d.ts.map +1 -0
  174. package/dist/entitlement/require.js +123 -0
  175. package/dist/env/ambient.d.ts +52 -0
  176. package/dist/env/ambient.d.ts.map +1 -0
  177. package/dist/env/ambient.js +35 -0
  178. package/dist/env/ci.d.ts +36 -0
  179. package/dist/env/ci.d.ts.map +1 -0
  180. package/dist/env/ci.js +40 -0
  181. package/dist/env/stem.d.ts +23 -0
  182. package/dist/env/stem.d.ts.map +1 -0
  183. package/dist/env/stem.js +29 -0
  184. package/dist/error/cause.d.ts +143 -0
  185. package/dist/error/cause.d.ts.map +1 -0
  186. package/dist/error/cause.js +192 -0
  187. package/dist/error/client.d.ts +35 -0
  188. package/dist/error/client.d.ts.map +1 -0
  189. package/dist/error/client.js +38 -0
  190. package/dist/error/extend.d.ts +101 -0
  191. package/dist/error/extend.d.ts.map +1 -0
  192. package/dist/error/extend.js +76 -0
  193. package/dist/error/http.d.ts +1516 -0
  194. package/dist/error/http.d.ts.map +1 -0
  195. package/dist/error/http.js +72 -0
  196. package/dist/error/payload.d.ts +3034 -0
  197. package/dist/error/payload.d.ts.map +1 -0
  198. package/dist/error/payload.js +1110 -0
  199. package/dist/error/pithyError.d.ts +154 -0
  200. package/dist/error/pithyError.d.ts.map +1 -0
  201. package/dist/error/pithyError.js +197 -0
  202. package/dist/error/terminal.d.ts +28 -0
  203. package/dist/error/terminal.d.ts.map +1 -0
  204. package/dist/error/terminal.js +33 -0
  205. package/dist/http/authContext.d.ts +21 -0
  206. package/dist/http/authContext.d.ts.map +1 -0
  207. package/dist/http/authContext.js +23 -0
  208. package/dist/http/routeContract.d.ts +39 -0
  209. package/dist/http/routeContract.d.ts.map +1 -0
  210. package/dist/http/routeContract.js +82 -0
  211. package/dist/http/sameOrigin.d.ts +47 -0
  212. package/dist/http/sameOrigin.d.ts.map +1 -0
  213. package/dist/http/sameOrigin.js +24 -0
  214. package/dist/http/signedWebhook.d.ts +225 -0
  215. package/dist/http/signedWebhook.d.ts.map +1 -0
  216. package/dist/http/signedWebhook.js +247 -0
  217. package/dist/http/validation.d.ts +37 -0
  218. package/dist/http/validation.d.ts.map +1 -0
  219. package/dist/http/validation.js +14 -0
  220. package/dist/http/verification.d.ts +10 -0
  221. package/dist/http/verification.d.ts.map +1 -0
  222. package/dist/http/verification.js +17 -0
  223. package/dist/i18n/acceptLanguage.d.ts +22 -0
  224. package/dist/i18n/acceptLanguage.d.ts.map +1 -0
  225. package/dist/i18n/acceptLanguage.js +55 -0
  226. package/dist/i18n/catalog.d.ts +39 -0
  227. package/dist/i18n/catalog.d.ts.map +1 -0
  228. package/dist/i18n/catalog.js +57 -0
  229. package/dist/i18n/locale.d.ts +74 -0
  230. package/dist/i18n/locale.d.ts.map +1 -0
  231. package/dist/i18n/locale.js +109 -0
  232. package/dist/i18n/localeMarker.d.ts +29 -0
  233. package/dist/i18n/localeMarker.d.ts.map +1 -0
  234. package/dist/i18n/localeMarker.js +112 -0
  235. package/dist/i18n/match.d.ts +26 -0
  236. package/dist/i18n/match.d.ts.map +1 -0
  237. package/dist/i18n/match.js +88 -0
  238. package/dist/i18n/registry.d.ts +25 -0
  239. package/dist/i18n/registry.d.ts.map +1 -0
  240. package/dist/i18n/registry.js +51 -0
  241. package/dist/i18n/translator.d.ts +107 -0
  242. package/dist/i18n/translator.d.ts.map +1 -0
  243. package/dist/i18n/translator.js +80 -0
  244. package/dist/index.d.ts +32 -0
  245. package/dist/index.d.ts.map +1 -0
  246. package/dist/index.js +17 -0
  247. package/dist/kv/kv.d.ts +166 -0
  248. package/dist/kv/kv.d.ts.map +1 -0
  249. package/dist/kv/kv.js +261 -0
  250. package/dist/kv/namespaces.d.ts +58 -0
  251. package/dist/kv/namespaces.d.ts.map +1 -0
  252. package/dist/kv/namespaces.js +40 -0
  253. package/dist/logger/local.d.ts +41 -0
  254. package/dist/logger/local.d.ts.map +1 -0
  255. package/dist/logger/local.js +45 -0
  256. package/dist/logger/logger.d.ts +56 -0
  257. package/dist/logger/logger.d.ts.map +1 -0
  258. package/dist/logger/logger.js +83 -0
  259. package/dist/logger/record.d.ts +919 -0
  260. package/dist/logger/record.d.ts.map +1 -0
  261. package/dist/logger/record.js +79 -0
  262. package/dist/logger/worker.d.ts +74 -0
  263. package/dist/logger/worker.d.ts.map +1 -0
  264. package/dist/logger/worker.js +54 -0
  265. package/dist/migrations/batch.d.ts +10 -0
  266. package/dist/migrations/batch.d.ts.map +1 -0
  267. package/dist/migrations/batch.js +193 -0
  268. package/dist/migrations/bookkeeping.d.ts +24 -0
  269. package/dist/migrations/bookkeeping.d.ts.map +1 -0
  270. package/dist/migrations/bookkeeping.js +57 -0
  271. package/dist/migrations/owner.d.ts +41 -0
  272. package/dist/migrations/owner.d.ts.map +1 -0
  273. package/dist/migrations/owner.js +105 -0
  274. package/dist/migrations/registry.d.ts +36 -0
  275. package/dist/migrations/registry.d.ts.map +1 -0
  276. package/dist/migrations/registry.js +70 -0
  277. package/dist/migrations/runner.d.ts +67 -0
  278. package/dist/migrations/runner.d.ts.map +1 -0
  279. package/dist/migrations/runner.js +218 -0
  280. package/dist/naming/domains.d.ts +108 -0
  281. package/dist/naming/domains.d.ts.map +1 -0
  282. package/dist/naming/domains.js +156 -0
  283. package/dist/naming/environment.d.ts +118 -0
  284. package/dist/naming/environment.d.ts.map +1 -0
  285. package/dist/naming/environment.js +190 -0
  286. package/dist/naming/feature.d.ts +68 -0
  287. package/dist/naming/feature.d.ts.map +1 -0
  288. package/dist/naming/feature.js +106 -0
  289. package/dist/naming/limits.d.ts +229 -0
  290. package/dist/naming/limits.d.ts.map +1 -0
  291. package/dist/naming/limits.js +187 -0
  292. package/dist/naming/provisionScope.d.ts +91 -0
  293. package/dist/naming/provisionScope.d.ts.map +1 -0
  294. package/dist/naming/provisionScope.js +65 -0
  295. package/dist/naming/resource.d.ts +169 -0
  296. package/dist/naming/resource.d.ts.map +1 -0
  297. package/dist/naming/resource.js +220 -0
  298. package/dist/naming/resourceNames.d.ts +83 -0
  299. package/dist/naming/resourceNames.d.ts.map +1 -0
  300. package/dist/naming/resourceNames.js +102 -0
  301. package/dist/naming/segment.d.ts +29 -0
  302. package/dist/naming/segment.d.ts.map +1 -0
  303. package/dist/naming/segment.js +31 -0
  304. package/dist/projection/asRead.d.ts +72 -0
  305. package/dist/projection/asRead.d.ts.map +1 -0
  306. package/dist/projection/asRead.js +169 -0
  307. package/dist/projection/published.d.ts +95 -0
  308. package/dist/projection/published.d.ts.map +1 -0
  309. package/dist/projection/published.js +114 -0
  310. package/dist/schema/describedness.d.ts +55 -0
  311. package/dist/schema/describedness.d.ts.map +1 -0
  312. package/dist/schema/describedness.js +180 -0
  313. package/dist/seed/compose.d.ts +41 -0
  314. package/dist/seed/compose.d.ts.map +1 -0
  315. package/dist/seed/compose.js +51 -0
  316. package/dist/seed/devLogin.d.ts +50 -0
  317. package/dist/seed/devLogin.d.ts.map +1 -0
  318. package/dist/seed/devLogin.js +52 -0
  319. package/dist/seed/exampleIdentities.d.ts +58 -0
  320. package/dist/seed/exampleIdentities.d.ts.map +1 -0
  321. package/dist/seed/exampleIdentities.js +26 -0
  322. package/dist/seed/metadata.d.ts +20 -0
  323. package/dist/seed/metadata.d.ts.map +1 -0
  324. package/dist/seed/metadata.js +27 -0
  325. package/dist/seed/seed.d.ts +264 -0
  326. package/dist/seed/seed.d.ts.map +1 -0
  327. package/dist/seed/seed.js +37 -0
  328. package/dist/seed/seededRows.d.ts +26 -0
  329. package/dist/seed/seededRows.d.ts.map +1 -0
  330. package/dist/seed/seededRows.js +20 -0
  331. package/dist/seed/writeD1.d.ts +43 -0
  332. package/dist/seed/writeD1.d.ts.map +1 -0
  333. package/dist/seed/writeD1.js +73 -0
  334. package/dist/seed/writeKv.d.ts +29 -0
  335. package/dist/seed/writeKv.d.ts.map +1 -0
  336. package/dist/seed/writeKv.js +63 -0
  337. package/dist/semver/semver.d.ts +62 -0
  338. package/dist/semver/semver.d.ts.map +1 -0
  339. package/dist/semver/semver.js +137 -0
  340. package/dist/text/comments.d.ts +62 -0
  341. package/dist/text/comments.d.ts.map +1 -0
  342. package/dist/text/comments.js +161 -0
  343. package/dist/version.generated.d.ts +5 -0
  344. package/dist/version.generated.d.ts.map +1 -0
  345. package/dist/version.generated.js +7 -0
  346. package/dist/worker/health.d.ts +39 -0
  347. package/dist/worker/health.d.ts.map +1 -0
  348. package/dist/worker/health.js +41 -0
  349. package/dist/worker/identity.d.ts +191 -0
  350. package/dist/worker/identity.d.ts.map +1 -0
  351. package/dist/worker/identity.js +162 -0
  352. package/dist/workflow/bindings.d.ts +41 -0
  353. package/dist/workflow/bindings.d.ts.map +1 -0
  354. package/dist/workflow/bindings.js +53 -0
  355. package/dist/workflow/dispatch.d.ts +98 -0
  356. package/dist/workflow/dispatch.d.ts.map +1 -0
  357. package/dist/workflow/dispatch.js +148 -0
  358. package/dist/workflow/dispatchRoute.d.ts +80 -0
  359. package/dist/workflow/dispatchRoute.d.ts.map +1 -0
  360. package/dist/workflow/dispatchRoute.js +134 -0
  361. package/dist/workflow/faults.d.ts +97 -0
  362. package/dist/workflow/faults.d.ts.map +1 -0
  363. package/dist/workflow/faults.js +111 -0
  364. package/dist/workflow/host.d.ts +176 -0
  365. package/dist/workflow/host.d.ts.map +1 -0
  366. package/dist/workflow/host.js +116 -0
  367. package/dist/workflow/hostEntry.d.ts +50 -0
  368. package/dist/workflow/hostEntry.d.ts.map +1 -0
  369. package/dist/workflow/hostEntry.js +24 -0
  370. package/dist/workflow/hostEnv.d.ts +143 -0
  371. package/dist/workflow/hostEnv.d.ts.map +1 -0
  372. package/dist/workflow/hostEnv.js +170 -0
  373. package/dist/workflow/loopback.d.ts +52 -0
  374. package/dist/workflow/loopback.d.ts.map +1 -0
  375. package/dist/workflow/loopback.js +76 -0
  376. package/dist/workflow/naming.d.ts +79 -0
  377. package/dist/workflow/naming.d.ts.map +1 -0
  378. package/dist/workflow/naming.js +138 -0
  379. package/dist/workflow/register.d.ts +19 -0
  380. package/dist/workflow/register.d.ts.map +1 -0
  381. package/dist/workflow/register.js +40 -0
  382. package/dist/workflow/schemas.d.ts +44 -0
  383. package/dist/workflow/schemas.d.ts.map +1 -0
  384. package/dist/workflow/schemas.js +43 -0
  385. package/dist/workflow/spec.d.ts +81 -0
  386. package/dist/workflow/spec.d.ts.map +1 -0
  387. package/dist/workflow/spec.js +0 -0
  388. package/dist/workflow/stepMessage.d.ts +122 -0
  389. package/dist/workflow/stepMessage.d.ts.map +1 -0
  390. package/dist/workflow/stepMessage.js +152 -0
  391. package/package.json +20 -9
  392. package/src/capability/manifest.ts +21 -0
  393. package/src/version.generated.ts +1 -1
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * One granted operation. Validated on the way into the connection row and on the way out of a token,
4
+ * so a malformed scope is a parse failure at the boundary rather than a silent never-match — a scope
5
+ * that reads as a grant in the row and denies at every call looks like a bug in the seam.
6
+ */
7
+ export declare const ControlPlaneScope: z.ZodString;
8
+ export type ControlPlaneScope = z.output<typeof ControlPlaneScope>;
9
+ /**
10
+ * The requirement for a route that needs a **verified caller but no authorization** — today, only
11
+ * `GET /control-plane/ping`.
12
+ *
13
+ * Ping is not a scope, and modeling it as one would be dishonest: granting it would change nothing
14
+ * and withholding it would change nothing, because ping must work for a connection granted nothing at
15
+ * all. It is the call that proves a newly registered key works *before* the key it replaces is
16
+ * expired, and that ordering is the entire safety property of rotation. A ping that could be withheld
17
+ * would let a connection reach a state where the replacement can never be proven — and then either the
18
+ * old key is never expired, or it is expired anyway and nobody can get back in.
19
+ *
20
+ * A symbol rather than a magic string, so it is unforgeable by construction. Claims arrive over the
21
+ * wire as JSON, and no JSON value is ever equal to this — a caller cannot present it, only a route can
22
+ * require it.
23
+ */
24
+ export declare const ANY_VERIFIED_CALLER: unique symbol;
25
+ /**
26
+ * What a route demands of a caller: one named operation, or merely that it verified.
27
+ *
28
+ * `requireControlPlane` takes this rather than an optional scope on purpose. An optional argument is a
29
+ * thing you can forget, and forgetting it on an admin route would mean shipping an unscoped one; a
30
+ * required argument means the only way to write a route with no authorization is to say
31
+ * {@link ANY_VERIFIED_CALLER} out loud, which is as deliberate as naming a scope.
32
+ */
33
+ export type ControlPlaneRequirement = ControlPlaneScope | typeof ANY_VERIFIED_CALLER;
34
+ /**
35
+ * Read this Worker's composed capability manifest — the discovery-over-configuration call.
36
+ *
37
+ * Annotated `: ControlPlaneScope` like every capability's scope, and not merely for symmetry: that
38
+ * annotation is how `tooling/browser-scopes` finds a control-plane scope at all. Matching the type
39
+ * rather than the `_SCOPE` suffix is what keeps `PLAY_SCOPE` (a Google OAuth URL) and `GLOBAL_SCOPE`
40
+ * (an environment name) out of a gate that has nothing to say about either.
41
+ */
42
+ export declare const MANIFEST_READ_SCOPE: ControlPlaneScope;
43
+ /**
44
+ * Register a new public key, expire a superseded one, and read the registration state. One scope for
45
+ * the whole key lifecycle, granted separately at connect: an adopter who does not want a management
46
+ * client changing keys on a schedule simply does not grant it, and then it *cannot*, whatever the
47
+ * client intends. Better than a toggle anyone has to be trusted to honor.
48
+ */
49
+ export declare const KEYS_ROTATE_SCOPE: ControlPlaneScope;
50
+ /**
51
+ * Every grantable scope the seam's own routes use — what `pithy dashboard connect` offers by default.
52
+ * Ping is absent because it is not grantable; see {@link ANY_VERIFIED_CALLER}.
53
+ */
54
+ export declare const SEAM_SCOPES: readonly ControlPlaneScope[];
55
+ /**
56
+ * Does this call carry what the route requires?
57
+ *
58
+ * Both sides must agree, and they are checked against different things on purpose. `granted` is what
59
+ * the **adopter** stored on the connection; `presented` is the single scope the **caller** put in the
60
+ * token for this one call. A scope the adopter never granted is denied however the token is written,
61
+ * and a token that claims a broad grant still only exercises the one operation it named.
62
+ *
63
+ * Matching is exact string equality. There is no prefix or wildcard rule, because
64
+ * `payments:entitlements` must not quietly confer `payments:entitlements:revoke` — prefix semantics on
65
+ * an authorization check is how a read grant becomes a write one without anybody deciding it should.
66
+ */
67
+ export declare function scopeCovers(required: ControlPlaneRequirement, presented: string, granted: readonly string[]): boolean;
68
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/scope/scope.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAexB;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,aAQ3B,CAAC;AACJ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEnE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,EAAE,OAAO,MAA2D,CAAC;AAErG;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,GAAG,OAAO,mBAAmB,CAAC;AAErF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,iBAAmC,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,iBAAiC,CAAC;AAElE;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,iBAAiB,EAA6C,CAAC;AAElG;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,uBAAuB,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAIrH"}
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * One granted operation. Validated on the way into the connection row and on the way out of a token,
4
+ * so a malformed scope is a parse failure at the boundary rather than a silent never-match — a scope
5
+ * that reads as a grant in the row and denies at every call looks like a bug in the seam.
6
+ */
7
+ const ControlPlaneScope = z.string().regex(/^[a-z][a-z0-9]*(?::[a-z][a-z0-9_]*)+$/, "A control-plane scope must be lowercase colon-separated segments naming one operation (e.g. `manifest:read`, `payments:entitlements:grant`).").describe("One management operation a connection may perform, as a colon-separated `resource:action` code. The taxonomy is federated: each capability declares its own scopes, and core holds only the seam's.");
8
+ /**
9
+ * The requirement for a route that needs a **verified caller but no authorization** — today, only
10
+ * `GET /control-plane/ping`.
11
+ *
12
+ * Ping is not a scope, and modeling it as one would be dishonest: granting it would change nothing
13
+ * and withholding it would change nothing, because ping must work for a connection granted nothing at
14
+ * all. It is the call that proves a newly registered key works *before* the key it replaces is
15
+ * expired, and that ordering is the entire safety property of rotation. A ping that could be withheld
16
+ * would let a connection reach a state where the replacement can never be proven — and then either the
17
+ * old key is never expired, or it is expired anyway and nobody can get back in.
18
+ *
19
+ * A symbol rather than a magic string, so it is unforgeable by construction. Claims arrive over the
20
+ * wire as JSON, and no JSON value is ever equal to this — a caller cannot present it, only a route can
21
+ * require it.
22
+ */
23
+ const ANY_VERIFIED_CALLER = Symbol.for("pithy.controlPlane.anyVerifiedCaller");
24
+ /**
25
+ * Read this Worker's composed capability manifest — the discovery-over-configuration call.
26
+ *
27
+ * Annotated `: ControlPlaneScope` like every capability's scope, and not merely for symmetry: that
28
+ * annotation is how `tooling/browser-scopes` finds a control-plane scope at all. Matching the type
29
+ * rather than the `_SCOPE` suffix is what keeps `PLAY_SCOPE` (a Google OAuth URL) and `GLOBAL_SCOPE`
30
+ * (an environment name) out of a gate that has nothing to say about either.
31
+ */
32
+ const MANIFEST_READ_SCOPE = "manifest:read";
33
+ /**
34
+ * Register a new public key, expire a superseded one, and read the registration state. One scope for
35
+ * the whole key lifecycle, granted separately at connect: an adopter who does not want a management
36
+ * client changing keys on a schedule simply does not grant it, and then it *cannot*, whatever the
37
+ * client intends. Better than a toggle anyone has to be trusted to honor.
38
+ */
39
+ const KEYS_ROTATE_SCOPE = "keys:rotate";
40
+ /**
41
+ * Every grantable scope the seam's own routes use — what `pithy dashboard connect` offers by default.
42
+ * Ping is absent because it is not grantable; see {@link ANY_VERIFIED_CALLER}.
43
+ */
44
+ const SEAM_SCOPES = [MANIFEST_READ_SCOPE, KEYS_ROTATE_SCOPE];
45
+ /**
46
+ * Does this call carry what the route requires?
47
+ *
48
+ * Both sides must agree, and they are checked against different things on purpose. `granted` is what
49
+ * the **adopter** stored on the connection; `presented` is the single scope the **caller** put in the
50
+ * token for this one call. A scope the adopter never granted is denied however the token is written,
51
+ * and a token that claims a broad grant still only exercises the one operation it named.
52
+ *
53
+ * Matching is exact string equality. There is no prefix or wildcard rule, because
54
+ * `payments:entitlements` must not quietly confer `payments:entitlements:revoke` — prefix semantics on
55
+ * an authorization check is how a read grant becomes a write one without anybody deciding it should.
56
+ */
57
+ function scopeCovers(required, presented, granted) {
58
+ if (required === ANY_VERIFIED_CALLER) return true;
59
+ if (presented !== required) return false;
60
+ return granted.includes(required);
61
+ }
62
+ //#endregion
63
+ export { ANY_VERIFIED_CALLER, ControlPlaneScope, KEYS_ROTATE_SCOPE, MANIFEST_READ_SCOPE, SEAM_SCOPES, scopeCovers };
@@ -0,0 +1,10 @@
1
+ /** Encode raw bytes as unpadded base64url. */
2
+ export declare function base64UrlEncode(bytes: Uint8Array): string;
3
+ /**
4
+ * Decode unpadded base64url to raw bytes, or throw {@link ControlPlaneInvalidCredentialError}.
5
+ *
6
+ * Every rejection is the same error for the same reason the verification steps are: a caller learns
7
+ * that the credential is not valid here, never which character gave it away.
8
+ */
9
+ export declare function base64UrlDecode(value: string): Uint8Array;
10
+ //# sourceMappingURL=base64url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64url.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/token/base64url.ts"],"names":[],"mappings":"AAyBA,8CAA8C;AAC9C,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAOzD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAuBzD"}
@@ -0,0 +1,48 @@
1
+ import { ControlPlaneInvalidCredentialError } from "../error/errors.js";
2
+ //#region src/controlPlane/token/base64url.ts
3
+ /**
4
+ * base64url, the encoding every part of a compact JWS is written in (RFC 4648 §5). Core ships no
5
+ * base64 helper, so the seam brings its own rather than reaching into another package's crypto.
6
+ *
7
+ * Two properties matter here and nowhere else in the tree.
8
+ *
9
+ * **Decoding is strict.** A token segment is attacker-supplied by definition, and `atob` is famously
10
+ * forgiving — it will happily digest whitespace and, on some runtimes, characters that mean nothing.
11
+ * Silently decoding malformed input produces bytes that parse into *something*, and something is
12
+ * exactly what an attacker wants a verifier to work with. So anything outside the url alphabet is a
13
+ * throw, and the throw is a credential failure that renders as a 401 through the one handler.
14
+ *
15
+ * **Padding is rejected, not tolerated.** RFC 7515 §2 mandates unpadded segments. Accepting `=`
16
+ * would give one token two spellings, and a token with two spellings is two strings to log, two to
17
+ * compare, and one more thing an equality check can be wrong about.
18
+ */
19
+ /** The url alphabet, unpadded. Whitespace, `+`, `/` and `=` are all outside it, deliberately. */
20
+ const BASE64URL_PATTERN = /^[A-Za-z0-9_-]*$/;
21
+ /** Encode raw bytes as unpadded base64url. */
22
+ function base64UrlEncode(bytes) {
23
+ let binary = "";
24
+ for (const byte of bytes) binary += String.fromCharCode(byte);
25
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
26
+ }
27
+ /**
28
+ * Decode unpadded base64url to raw bytes, or throw {@link ControlPlaneInvalidCredentialError}.
29
+ *
30
+ * Every rejection is the same error for the same reason the verification steps are: a caller learns
31
+ * that the credential is not valid here, never which character gave it away.
32
+ */
33
+ function base64UrlDecode(value) {
34
+ if (!BASE64URL_PATTERN.test(value)) throw new ControlPlaneInvalidCredentialError({ detail: "token segment is not unpadded base64url (RFC 4648 §5)" });
35
+ if (value.length % 4 === 1) throw new ControlPlaneInvalidCredentialError({ detail: "token segment has an impossible base64url length" });
36
+ const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
37
+ let binary;
38
+ try {
39
+ binary = atob(padded);
40
+ } catch (cause) {
41
+ throw new ControlPlaneInvalidCredentialError({ detail: "token segment failed base64url decode" }, { cause });
42
+ }
43
+ const bytes = new Uint8Array(binary.length);
44
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
45
+ return bytes;
46
+ }
47
+ //#endregion
48
+ export { base64UrlDecode, base64UrlEncode };
@@ -0,0 +1,83 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The token a management client presents: what it claims, and what shape the envelope around it must
4
+ * have. This is the wire contract between the hosted dashboard and an adopter's Worker.
5
+ *
6
+ * **Not Cloudflare's control plane.** That is the outbound provisioning REST API this project calls
7
+ * through `@pithy-sh/cloudflare`. This is the inbound admin seam, authenticated by the adopter, in the
8
+ * adopter's Worker. The names collide; nothing else does.
9
+ *
10
+ * These schemas describe *untrusted* data. Parsing one proves the token is well-formed and nothing
11
+ * more — a forged token parses exactly as cleanly as a real one. Authenticity comes only from
12
+ * `verifyEd25519` (`./jws`), and nothing may act on these values before it returns true.
13
+ */
14
+ /**
15
+ * How long a token may live, from `iat` to `exp`.
16
+ *
17
+ * A minute is the whole budget: mint, send, verify. Nothing legitimate needs more, because the client
18
+ * mints per call rather than caching a credential.
19
+ *
20
+ * **This cap is what makes the replay window sound.** Replay defense is a `jti` held in a KV set with
21
+ * a TTL, and a set with a TTL only covers tokens that expire inside it. Without a cap, a client could
22
+ * mint an `exp` an hour out; the `jti` would age out after {@link CONTROL_PLANE_JTI_TTL_SECONDS} and
23
+ * the same token would then replay freely for the remaining fifty-odd minutes. So the cap is enforced
24
+ * at verification, not merely recommended — a token whose lifetime exceeds it is rejected however
25
+ * validly it is signed.
26
+ */
27
+ export declare const CONTROL_PLANE_MAX_TOKEN_LIFETIME_SECONDS = 60;
28
+ /**
29
+ * How far the two clocks may disagree before a valid call is refused.
30
+ *
31
+ * Applied at both ends: a token is not "not yet valid" until it is this far in the future, and not
32
+ * expired until this far in the past. Machines that never sync do exist, and a management client
33
+ * locked out by a drifting clock has no way to notice, let alone fix it. A minute is generous enough
34
+ * to survive that and short enough to stay inside the replay window below.
35
+ */
36
+ export declare const CONTROL_PLANE_CLOCK_SKEW_SECONDS = 60;
37
+ /**
38
+ * How long a spent `jti` stays in the replay set.
39
+ *
40
+ * It must **outlive** the longest window in which its token could still verify — the max lifetime plus
41
+ * a skew at each end, so 60 + 60 + 60 = 180. Shorter, and a token becomes replayable the moment its id
42
+ * ages out; longer only costs storage in a store whose entries expire on their own.
43
+ *
44
+ * 300 rather than exactly 180, because equal is not longer. At 180 the memory ends on the same instant
45
+ * the token stops being accepted, which leaves the boundary decided by whichever clock rounds first —
46
+ * and "the replay window is only open for the last instant" is not a property worth defending. The two
47
+ * extra minutes cost nothing and make the relation a strict inequality, which is what
48
+ * `ControlPlaneConfig` now enforces and `claims.test.ts` asserts.
49
+ */
50
+ export declare const CONTROL_PLANE_JTI_TTL_SECONDS = 300;
51
+ /**
52
+ * The JWS protected header.
53
+ *
54
+ * **`alg` is a pinned literal, and that is the algorithm-confusion defense.** The classic JWT breaks
55
+ * are both header-driven: `"alg":"none"` asking the verifier to skip verification, and swapping RS256
56
+ * for HS256 so a *public* key is used as an HMAC secret. Both need the verifier to take its algorithm
57
+ * from the token. Here the schema decides and the token merely has to agree, so neither attack has a
58
+ * shape it can be written in — the header is rejected at parse, before a key is ever imported.
59
+ */
60
+ export declare const ControlPlaneJwsHeader: z.ZodObject<{
61
+ alg: z.ZodLiteral<"EdDSA">;
62
+ typ: z.ZodLiteral<"JWT">;
63
+ kid: z.ZodString;
64
+ }, z.core.$strip>;
65
+ export type ControlPlaneJwsHeader = z.infer<typeof ControlPlaneJwsHeader>;
66
+ /**
67
+ * The token payload.
68
+ *
69
+ * Every claim is a check the Worker performs against something it already knows — the connection row,
70
+ * its own clock, its own environment, the body it just read. None of them are informational.
71
+ */
72
+ export declare const ControlPlaneClaims: z.ZodObject<{
73
+ iss: z.ZodURL;
74
+ aud: z.ZodUUID;
75
+ sub: z.ZodString;
76
+ scope: z.ZodString;
77
+ jti: z.ZodString;
78
+ iat: z.ZodInt;
79
+ exp: z.ZodInt;
80
+ bodySha256: z.ZodNullable<z.ZodString>;
81
+ }, z.core.$strip>;
82
+ export type ControlPlaneClaims = z.infer<typeof ControlPlaneClaims>;
83
+ //# sourceMappingURL=claims.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claims.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/token/claims.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wCAAwC,KAAK,CAAC;AAE3D;;;;;;;GAOG;AACH,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,6BAA6B,MAAM,CAAC;AAKjD;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB;;;;iBAoB/B,CAAC;AACJ,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE1E;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;iBAiD5B,CAAC;AACJ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
@@ -0,0 +1,86 @@
1
+ import { ControlPlaneScope } from "../scope/scope.js";
2
+ import { z } from "zod";
3
+ //#region src/controlPlane/token/claims.ts
4
+ /**
5
+ * The token a management client presents: what it claims, and what shape the envelope around it must
6
+ * have. This is the wire contract between the hosted dashboard and an adopter's Worker.
7
+ *
8
+ * **Not Cloudflare's control plane.** That is the outbound provisioning REST API this project calls
9
+ * through `@pithy-sh/cloudflare`. This is the inbound admin seam, authenticated by the adopter, in the
10
+ * adopter's Worker. The names collide; nothing else does.
11
+ *
12
+ * These schemas describe *untrusted* data. Parsing one proves the token is well-formed and nothing
13
+ * more — a forged token parses exactly as cleanly as a real one. Authenticity comes only from
14
+ * `verifyEd25519` (`./jws`), and nothing may act on these values before it returns true.
15
+ */
16
+ /**
17
+ * How long a token may live, from `iat` to `exp`.
18
+ *
19
+ * A minute is the whole budget: mint, send, verify. Nothing legitimate needs more, because the client
20
+ * mints per call rather than caching a credential.
21
+ *
22
+ * **This cap is what makes the replay window sound.** Replay defense is a `jti` held in a KV set with
23
+ * a TTL, and a set with a TTL only covers tokens that expire inside it. Without a cap, a client could
24
+ * mint an `exp` an hour out; the `jti` would age out after {@link CONTROL_PLANE_JTI_TTL_SECONDS} and
25
+ * the same token would then replay freely for the remaining fifty-odd minutes. So the cap is enforced
26
+ * at verification, not merely recommended — a token whose lifetime exceeds it is rejected however
27
+ * validly it is signed.
28
+ */
29
+ const CONTROL_PLANE_MAX_TOKEN_LIFETIME_SECONDS = 60;
30
+ /**
31
+ * How far the two clocks may disagree before a valid call is refused.
32
+ *
33
+ * Applied at both ends: a token is not "not yet valid" until it is this far in the future, and not
34
+ * expired until this far in the past. Machines that never sync do exist, and a management client
35
+ * locked out by a drifting clock has no way to notice, let alone fix it. A minute is generous enough
36
+ * to survive that and short enough to stay inside the replay window below.
37
+ */
38
+ const CONTROL_PLANE_CLOCK_SKEW_SECONDS = 60;
39
+ /**
40
+ * How long a spent `jti` stays in the replay set.
41
+ *
42
+ * It must **outlive** the longest window in which its token could still verify — the max lifetime plus
43
+ * a skew at each end, so 60 + 60 + 60 = 180. Shorter, and a token becomes replayable the moment its id
44
+ * ages out; longer only costs storage in a store whose entries expire on their own.
45
+ *
46
+ * 300 rather than exactly 180, because equal is not longer. At 180 the memory ends on the same instant
47
+ * the token stops being accepted, which leaves the boundary decided by whichever clock rounds first —
48
+ * and "the replay window is only open for the last instant" is not a property worth defending. The two
49
+ * extra minutes cost nothing and make the relation a strict inequality, which is what
50
+ * `ControlPlaneConfig` now enforces and `claims.test.ts` asserts.
51
+ */
52
+ const CONTROL_PLANE_JTI_TTL_SECONDS = 300;
53
+ /** A base64url SHA-256: 32 bytes, unpadded, exactly 43 characters. */
54
+ const BODY_SHA256_PATTERN = /^[A-Za-z0-9_-]{43}$/;
55
+ /**
56
+ * The JWS protected header.
57
+ *
58
+ * **`alg` is a pinned literal, and that is the algorithm-confusion defense.** The classic JWT breaks
59
+ * are both header-driven: `"alg":"none"` asking the verifier to skip verification, and swapping RS256
60
+ * for HS256 so a *public* key is used as an HMAC secret. Both need the verifier to take its algorithm
61
+ * from the token. Here the schema decides and the token merely has to agree, so neither attack has a
62
+ * shape it can be written in — the header is rejected at parse, before a key is ever imported.
63
+ */
64
+ const ControlPlaneJwsHeader = z.object({
65
+ alg: z.literal("EdDSA").describe("The signature algorithm, pinned to EdDSA (Ed25519). A literal rather than a choice: the verifier never takes its algorithm from the token, so `none` and HMAC-for-RSA confusion are unrepresentable."),
66
+ typ: z.literal("JWT").describe("The token type, always `JWT`. Pinned so a JWS meant for another purpose cannot be replayed here."),
67
+ kid: z.string().min(1).max(64).describe("Which registered key signed this token, matched against the connection's `keys`. A hint for lookup only — an unknown or forged `kid` selects no key and the call is denied.")
68
+ }).describe("The protected header of a control-plane token. Untrusted until the signature verifies; `alg` is pinned rather than read.");
69
+ /**
70
+ * The token payload.
71
+ *
72
+ * Every claim is a check the Worker performs against something it already knows — the connection row,
73
+ * its own clock, its own environment, the body it just read. None of them are informational.
74
+ */
75
+ const ControlPlaneClaims = z.object({
76
+ iss: z.url().describe("The management client's origin. Checked against the `issuer` the adopter stored on the connection, so a token minted by another origin is denied even if it is validly signed."),
77
+ aud: z.uuid().describe("The connection this token addresses — the row's UUID. A UUID because it is externally exposed; checked so a token for one adopter's connection cannot be presented to another's Worker."),
78
+ sub: z.string().min(1).max(256).describe("The dashboard user acting, in the management client's own id space. Recorded as the audit `actorId` so the trail says which person acted, not merely which service."),
79
+ scope: ControlPlaneScope.describe("The single operation this token authorizes. One call, one scope: a token carrying the whole grant would make every call as dangerous as the most dangerous one it could make."),
80
+ jti: z.string().min(1).max(128).describe("A unique id for this one token, claimed in the replay set on first use. Bounded so an unbounded string cannot be used to bloat that set."),
81
+ iat: z.int().describe("When the token was minted, in seconds since the epoch. With `exp` it bounds the lifetime, which is capped at CONTROL_PLANE_MAX_TOKEN_LIFETIME_SECONDS."),
82
+ exp: z.int().describe("When the token stops being accepted, in seconds since the epoch. Honored within CONTROL_PLANE_CLOCK_SKEW_SECONDS, and never further out than the max lifetime allows."),
83
+ bodySha256: z.string().regex(BODY_SHA256_PATTERN, "bodySha256 must be a base64url SHA-256 digest.").nullable().describe("Base64url SHA-256 of the request body, binding this token to one call — or null for a request with no body. The signature covers the header and claims only, so without this a token could be lifted onto a different body for the same route.")
84
+ }).describe("The claims a management client signs for exactly one control-plane call. Well-formed after parsing; authentic only after the signature verifies.");
85
+ //#endregion
86
+ export { CONTROL_PLANE_CLOCK_SKEW_SECONDS, CONTROL_PLANE_JTI_TTL_SECONDS, CONTROL_PLANE_MAX_TOKEN_LIFETIME_SECONDS, ControlPlaneClaims, ControlPlaneJwsHeader };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The two primitives the body-binding check needs: hash a request body, then compare that hash to the
3
+ * one the token committed to.
4
+ *
5
+ * Body binding is what stops a verified token being lifted off one call and stapled to another. The
6
+ * signature covers the header and claims, never the body — so the claims carry `bodySha256`, and the
7
+ * Worker recomputes it. Without that, an intercepted `keys:rotate` token would authorize *any*
8
+ * `keys:rotate` body, including one registering the interceptor's key.
9
+ */
10
+ /**
11
+ * SHA-256 a request body, base64url-encoded — the exact form `bodySha256` is written in.
12
+ *
13
+ * **A view is copied before it is hashed, and that is a fix rather than a formality** (#315).
14
+ * `crypto.subtle.digest` takes a `BufferSource`, which is `ArrayBufferView<ArrayBuffer> | ArrayBuffer`
15
+ * — a plain `Uint8Array` is `Uint8Array<ArrayBufferLike>`, which also admits a view onto a
16
+ * `SharedArrayBuffer`, and so does not satisfy it. `@cloudflare/workers-types` spells `BufferSource`
17
+ * loosely enough that the mismatch never surfaced in a Worker program; an adopter compiling this
18
+ * module against the DOM lib got a type error inside our source, with nothing on their side to fix.
19
+ *
20
+ * The copy is what makes the narrowing true rather than asserted, and the reason it is worth its cost:
21
+ * a digest of memory another thread can write is a check that does not bind what it checked. Copying
22
+ * fixes the bytes at the instant they are hashed, so `bodySha256` commits to what the verifier
23
+ * actually compared. The bodies here are small signed control-plane requests, and the alternative was
24
+ * a cast that would have kept the hazard and hidden the argument.
25
+ */
26
+ export declare function sha256Base64Url(bytes: ArrayBuffer | Uint8Array): Promise<string>;
27
+ /**
28
+ * Compare two strings without leaking, through timing, where they first differ.
29
+ *
30
+ * The honest caveat first: `===` on a **digest** is a weak oracle at best. To exploit the early return
31
+ * an attacker must steer the digest byte by byte, which means finding preimages — so this is not the
32
+ * classic "guess the MAC one byte at a time" break that constant-time comparison was invented for.
33
+ *
34
+ * We do it anyway, for two reasons. The threat model is not fixed: this helper is the one every future
35
+ * comparison in the seam will reach for, and the next thing compared may well be a value an attacker
36
+ * *can* choose freely, at which point a plain `===` becomes a real break with no diff to notice it in.
37
+ * And the cost is a loop over 43 characters. Paying it always is cheaper than auditing, every time,
38
+ * whether this particular comparison happened to be safe.
39
+ *
40
+ * The scan runs to the end of the longer string and folds the length difference into the same
41
+ * accumulator, so neither an early match nor an early mismatch changes the work done.
42
+ */
43
+ export declare function timingSafeEqual(a: string, b: string): boolean;
44
+ //# sourceMappingURL=digest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/token/digest.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAItF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAS7D"}
@@ -0,0 +1,56 @@
1
+ import { base64UrlEncode } from "./base64url.js";
2
+ //#region src/controlPlane/token/digest.ts
3
+ /**
4
+ * The two primitives the body-binding check needs: hash a request body, then compare that hash to the
5
+ * one the token committed to.
6
+ *
7
+ * Body binding is what stops a verified token being lifted off one call and stapled to another. The
8
+ * signature covers the header and claims, never the body — so the claims carry `bodySha256`, and the
9
+ * Worker recomputes it. Without that, an intercepted `keys:rotate` token would authorize *any*
10
+ * `keys:rotate` body, including one registering the interceptor's key.
11
+ */
12
+ /**
13
+ * SHA-256 a request body, base64url-encoded — the exact form `bodySha256` is written in.
14
+ *
15
+ * **A view is copied before it is hashed, and that is a fix rather than a formality** (#315).
16
+ * `crypto.subtle.digest` takes a `BufferSource`, which is `ArrayBufferView<ArrayBuffer> | ArrayBuffer`
17
+ * — a plain `Uint8Array` is `Uint8Array<ArrayBufferLike>`, which also admits a view onto a
18
+ * `SharedArrayBuffer`, and so does not satisfy it. `@cloudflare/workers-types` spells `BufferSource`
19
+ * loosely enough that the mismatch never surfaced in a Worker program; an adopter compiling this
20
+ * module against the DOM lib got a type error inside our source, with nothing on their side to fix.
21
+ *
22
+ * The copy is what makes the narrowing true rather than asserted, and the reason it is worth its cost:
23
+ * a digest of memory another thread can write is a check that does not bind what it checked. Copying
24
+ * fixes the bytes at the instant they are hashed, so `bodySha256` commits to what the verifier
25
+ * actually compared. The bodies here are small signed control-plane requests, and the alternative was
26
+ * a cast that would have kept the hazard and hidden the argument.
27
+ */
28
+ async function sha256Base64Url(bytes) {
29
+ const source = bytes instanceof Uint8Array ? new Uint8Array(bytes) : bytes;
30
+ const digest = await crypto.subtle.digest("SHA-256", source);
31
+ return base64UrlEncode(new Uint8Array(digest));
32
+ }
33
+ /**
34
+ * Compare two strings without leaking, through timing, where they first differ.
35
+ *
36
+ * The honest caveat first: `===` on a **digest** is a weak oracle at best. To exploit the early return
37
+ * an attacker must steer the digest byte by byte, which means finding preimages — so this is not the
38
+ * classic "guess the MAC one byte at a time" break that constant-time comparison was invented for.
39
+ *
40
+ * We do it anyway, for two reasons. The threat model is not fixed: this helper is the one every future
41
+ * comparison in the seam will reach for, and the next thing compared may well be a value an attacker
42
+ * *can* choose freely, at which point a plain `===` becomes a real break with no diff to notice it in.
43
+ * And the cost is a loop over 43 characters. Paying it always is cheaper than auditing, every time,
44
+ * whether this particular comparison happened to be safe.
45
+ *
46
+ * The scan runs to the end of the longer string and folds the length difference into the same
47
+ * accumulator, so neither an early match nor an early mismatch changes the work done.
48
+ */
49
+ function timingSafeEqual(a, b) {
50
+ let difference = a.length ^ b.length;
51
+ const width = Math.max(a.length, b.length);
52
+ for (let i = 0; i < width; i++) difference |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
53
+ return difference === 0;
54
+ }
55
+ //#endregion
56
+ export { sha256Base64Url, timingSafeEqual };
@@ -0,0 +1,52 @@
1
+ import type { Ed25519PublicJwk } from "../data/connection";
2
+ import { ControlPlaneClaims, ControlPlaneJwsHeader } from "./claims";
3
+ /**
4
+ * Compact JWS: parse, then verify. Two functions, in that order, and the order is the security model.
5
+ *
6
+ * **No JWT library.** Core's runtime dependencies are fixed and `jose` is not among them — but the
7
+ * better reason is that the whole surface is one algorithm, one key type, one token shape. Ed25519
8
+ * over WebCrypto is a dozen lines; a general-purpose library brings the algorithm agility that causes
9
+ * the JWT vulnerabilities in the first place. There is nothing to negotiate here, so nothing
10
+ * negotiates.
11
+ */
12
+ /** What {@link parseCompactJws} hands back: shape only, authenticity not yet established. */
13
+ export interface ParsedCompactJws {
14
+ /** The validated protected header. `alg` is EdDSA because the schema says so, not because the token did. */
15
+ header: ControlPlaneJwsHeader;
16
+ /** The validated claims. Well-formed, entirely unproven. */
17
+ claims: ControlPlaneClaims;
18
+ /** The first two segments verbatim — the exact bytes the signature covers. */
19
+ signingInput: string;
20
+ /** The raw Ed25519 signature, 64 bytes when genuine. */
21
+ signature: Uint8Array;
22
+ }
23
+ /**
24
+ * Split a compact JWS, decode it, and validate both segments through their schemas.
25
+ *
26
+ * **Everything this returns is untrusted.** Parsing proves the token is well-formed; a forged token is
27
+ * well-formed too. No caller may read a claim, load a connection by `aud`, or act on `scope` before
28
+ * {@link verifyEd25519} has returned true for the `signingInput` and `signature` returned alongside
29
+ * them. The only exception is the `kid` lookup that finds the key to verify *with* — and that lookup
30
+ * grants nothing on its own.
31
+ *
32
+ * Every failure throws the one {@link ControlPlaneInvalidCredentialError}. Segment count, base64,
33
+ * JSON, schema — a caller cannot tell which, and so cannot use the response to find out how far a
34
+ * forgery got.
35
+ */
36
+ export declare function parseCompactJws(token: string): ParsedCompactJws;
37
+ /**
38
+ * Verify an Ed25519 signature over the signing input with a registered public key.
39
+ *
40
+ * `false` rather than a throw is the contract, and it covers WebCrypto throwing too. An unimportable
41
+ * key means the adopter's connection row holds something malformed — that is a denial, not a Worker
42
+ * crash, and it must render as the same 401 a bad signature does. Turning a bad row into a 500 would
43
+ * both leak that the row is bad and take the route down for everyone.
44
+ *
45
+ * **The signature is copied before it is verified**, for the reason `sha256Base64Url` copies a body
46
+ * (#315): `crypto.subtle.verify` takes a `BufferSource`, which excludes a view onto a
47
+ * `SharedArrayBuffer`, and a bare `Uint8Array` does not exclude one. Under the DOM lib that was a type
48
+ * error in our source that an adopter's browser program could not fix; under Workers types it was a
49
+ * signature verified against bytes another thread could still be writing. Sixty-four bytes.
50
+ */
51
+ export declare function verifyEd25519(signingInput: string, signature: Uint8Array, jwk: Ed25519PublicJwk): Promise<boolean>;
52
+ //# sourceMappingURL=jws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jws.d.ts","sourceRoot":"","sources":["../../../src/controlPlane/token/jws.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAErE;;;;;;;;GAQG;AAEH,6FAA6F;AAC7F,MAAM,WAAW,gBAAgB;IAC/B,4GAA4G;IAC5G,MAAM,EAAE,qBAAqB,CAAC;IAC9B,4DAA4D;IAC5D,MAAM,EAAE,kBAAkB,CAAC;IAC3B,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,SAAS,EAAE,UAAU,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAiB/D;AAoBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,UAAU,EACrB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,OAAO,CAAC,CAYlB"}
@@ -0,0 +1,65 @@
1
+ import { ControlPlaneClaims, ControlPlaneJwsHeader } from "./claims.js";
2
+ import { ControlPlaneInvalidCredentialError } from "../error/errors.js";
3
+ import { base64UrlDecode } from "./base64url.js";
4
+ //#region src/controlPlane/token/jws.ts
5
+ /**
6
+ * Split a compact JWS, decode it, and validate both segments through their schemas.
7
+ *
8
+ * **Everything this returns is untrusted.** Parsing proves the token is well-formed; a forged token is
9
+ * well-formed too. No caller may read a claim, load a connection by `aud`, or act on `scope` before
10
+ * {@link verifyEd25519} has returned true for the `signingInput` and `signature` returned alongside
11
+ * them. The only exception is the `kid` lookup that finds the key to verify *with* — and that lookup
12
+ * grants nothing on its own.
13
+ *
14
+ * Every failure throws the one {@link ControlPlaneInvalidCredentialError}. Segment count, base64,
15
+ * JSON, schema — a caller cannot tell which, and so cannot use the response to find out how far a
16
+ * forgery got.
17
+ */
18
+ function parseCompactJws(token) {
19
+ const segments = token.split(".");
20
+ const [encodedHeader, encodedClaims, encodedSignature] = segments;
21
+ if (segments.length !== 3 || !encodedHeader || !encodedClaims || !encodedSignature) throw new ControlPlaneInvalidCredentialError({ detail: `expected 3 non-empty compact JWS segments, got ${segments.length}` });
22
+ return {
23
+ header: decodeSegment(encodedHeader, ControlPlaneJwsHeader, "header"),
24
+ claims: decodeSegment(encodedClaims, ControlPlaneClaims, "claims"),
25
+ signingInput: `${encodedHeader}.${encodedClaims}`,
26
+ signature: base64UrlDecode(encodedSignature)
27
+ };
28
+ }
29
+ /** Decode one JSON segment and validate it, collapsing every failure into the one credential error. */
30
+ function decodeSegment(segment, schema, label) {
31
+ let json;
32
+ try {
33
+ json = JSON.parse(new TextDecoder().decode(base64UrlDecode(segment)));
34
+ } catch (cause) {
35
+ if (cause instanceof ControlPlaneInvalidCredentialError) throw cause;
36
+ throw new ControlPlaneInvalidCredentialError({ detail: `token ${label} is not JSON` }, { cause });
37
+ }
38
+ const result = schema.safeParse(json);
39
+ if (!result.success) throw new ControlPlaneInvalidCredentialError({ detail: `token ${label} failed schema validation` });
40
+ return result.data;
41
+ }
42
+ /**
43
+ * Verify an Ed25519 signature over the signing input with a registered public key.
44
+ *
45
+ * `false` rather than a throw is the contract, and it covers WebCrypto throwing too. An unimportable
46
+ * key means the adopter's connection row holds something malformed — that is a denial, not a Worker
47
+ * crash, and it must render as the same 401 a bad signature does. Turning a bad row into a 500 would
48
+ * both leak that the row is bad and take the route down for everyone.
49
+ *
50
+ * **The signature is copied before it is verified**, for the reason `sha256Base64Url` copies a body
51
+ * (#315): `crypto.subtle.verify` takes a `BufferSource`, which excludes a view onto a
52
+ * `SharedArrayBuffer`, and a bare `Uint8Array` does not exclude one. Under the DOM lib that was a type
53
+ * error in our source that an adopter's browser program could not fix; under Workers types it was a
54
+ * signature verified against bytes another thread could still be writing. Sixty-four bytes.
55
+ */
56
+ async function verifyEd25519(signingInput, signature, jwk) {
57
+ try {
58
+ const key = await crypto.subtle.importKey("jwk", jwk, { name: "Ed25519" }, false, ["verify"]);
59
+ return await crypto.subtle.verify("Ed25519", key, new Uint8Array(signature), new TextEncoder().encode(signingInput));
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+ //#endregion
65
+ export { parseCompactJws, verifyEd25519 };