@kindgi/api 0.1.4 → 0.1.5

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 (516) hide show
  1. package/dist/agent-binding.d.ts +13 -0
  2. package/dist/agent-binding.d.ts.map +1 -1
  3. package/dist/agent-pins.d.ts.map +1 -1
  4. package/dist/agent-pins.js +18 -2
  5. package/dist/agent-pins.js.map +1 -1
  6. package/dist/app.d.ts +110 -17
  7. package/dist/app.d.ts.map +1 -1
  8. package/dist/app.js +163 -35
  9. package/dist/app.js.map +1 -1
  10. package/dist/block-publish.d.ts +56 -0
  11. package/dist/block-publish.d.ts.map +1 -0
  12. package/dist/block-publish.js +132 -0
  13. package/dist/block-publish.js.map +1 -0
  14. package/dist/caller.d.ts +24 -0
  15. package/dist/caller.d.ts.map +1 -0
  16. package/dist/caller.js +47 -0
  17. package/dist/caller.js.map +1 -0
  18. package/dist/capability-binding.d.ts +11 -0
  19. package/dist/capability-binding.d.ts.map +1 -1
  20. package/dist/cost-binding.d.ts +19 -0
  21. package/dist/cost-binding.d.ts.map +1 -1
  22. package/dist/cost-binding.js.map +1 -1
  23. package/dist/derive-agent-version.d.ts +7 -0
  24. package/dist/derive-agent-version.d.ts.map +1 -1
  25. package/dist/derive-agent-version.js +4 -0
  26. package/dist/derive-agent-version.js.map +1 -1
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/errors.js +75 -7
  29. package/dist/errors.js.map +1 -1
  30. package/dist/eval-case-binding.d.ts +16 -4
  31. package/dist/eval-case-binding.d.ts.map +1 -1
  32. package/dist/eval-run-binding.d.ts +31 -0
  33. package/dist/eval-run-binding.d.ts.map +1 -1
  34. package/dist/eval-run-dispatcher.d.ts +7 -0
  35. package/dist/eval-run-dispatcher.d.ts.map +1 -1
  36. package/dist/eval-run-dispatcher.js.map +1 -1
  37. package/dist/eval-sample.d.ts +11 -0
  38. package/dist/eval-sample.d.ts.map +1 -0
  39. package/dist/eval-sample.js +34 -0
  40. package/dist/eval-sample.js.map +1 -0
  41. package/dist/eval-suite-binding.d.ts +13 -0
  42. package/dist/eval-suite-binding.d.ts.map +1 -1
  43. package/dist/eval-suite-binding.js.map +1 -1
  44. package/dist/flow-binding.d.ts +18 -0
  45. package/dist/flow-binding.d.ts.map +1 -1
  46. package/dist/flow-pins.d.ts.map +1 -1
  47. package/dist/flow-pins.js +18 -2
  48. package/dist/flow-pins.js.map +1 -1
  49. package/dist/gate.d.ts.map +1 -1
  50. package/dist/gate.js +25 -0
  51. package/dist/gate.js.map +1 -1
  52. package/dist/handler-binding.d.ts +32 -0
  53. package/dist/handler-binding.d.ts.map +1 -1
  54. package/dist/hitl-binding.d.ts +23 -0
  55. package/dist/hitl-binding.d.ts.map +1 -1
  56. package/dist/identity-directory-binding.d.ts +82 -4
  57. package/dist/identity-directory-binding.d.ts.map +1 -1
  58. package/dist/identity-provider-binding.d.ts +197 -17
  59. package/dist/identity-provider-binding.d.ts.map +1 -1
  60. package/dist/improvement-pass-binding.d.ts +196 -0
  61. package/dist/improvement-pass-binding.d.ts.map +1 -0
  62. package/dist/improvement-pass-binding.js +9 -0
  63. package/dist/improvement-pass-binding.js.map +1 -0
  64. package/dist/index.d.ts +35 -17
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +11 -3
  67. package/dist/index.js.map +1 -1
  68. package/dist/judged-dispatcher.d.ts +25 -4
  69. package/dist/judged-dispatcher.d.ts.map +1 -1
  70. package/dist/judged-dispatcher.js +43 -6
  71. package/dist/judged-dispatcher.js.map +1 -1
  72. package/dist/judgment-binding.d.ts +25 -1
  73. package/dist/judgment-binding.d.ts.map +1 -1
  74. package/dist/mcp-endpoint-binding.d.ts +7 -0
  75. package/dist/mcp-endpoint-binding.d.ts.map +1 -1
  76. package/dist/memory-binding.d.ts +124 -17
  77. package/dist/memory-binding.d.ts.map +1 -1
  78. package/dist/memory-erasure-binding.d.ts +145 -0
  79. package/dist/memory-erasure-binding.d.ts.map +1 -0
  80. package/dist/memory-erasure-binding.js +4 -0
  81. package/dist/memory-erasure-binding.js.map +1 -0
  82. package/dist/middleware/auth.d.ts +66 -13
  83. package/dist/middleware/auth.d.ts.map +1 -1
  84. package/dist/middleware/auth.js +150 -50
  85. package/dist/middleware/auth.js.map +1 -1
  86. package/dist/middleware/authorize.d.ts +9 -0
  87. package/dist/middleware/authorize.d.ts.map +1 -1
  88. package/dist/middleware/authorize.js +122 -4
  89. package/dist/middleware/authorize.js.map +1 -1
  90. package/dist/middleware/error-mapper.d.ts +5 -0
  91. package/dist/middleware/error-mapper.d.ts.map +1 -1
  92. package/dist/middleware/error-mapper.js +14 -1
  93. package/dist/middleware/error-mapper.js.map +1 -1
  94. package/dist/middleware/idempotency.d.ts +70 -5
  95. package/dist/middleware/idempotency.d.ts.map +1 -1
  96. package/dist/middleware/idempotency.js +174 -41
  97. package/dist/middleware/idempotency.js.map +1 -1
  98. package/dist/middleware/key-project.d.ts +17 -0
  99. package/dist/middleware/key-project.d.ts.map +1 -0
  100. package/dist/middleware/key-project.js +75 -0
  101. package/dist/middleware/key-project.js.map +1 -0
  102. package/dist/middleware/principal.d.ts.map +1 -1
  103. package/dist/middleware/principal.js +9 -5
  104. package/dist/middleware/principal.js.map +1 -1
  105. package/dist/middleware/project-ref.d.ts.map +1 -1
  106. package/dist/middleware/project-ref.js +3 -0
  107. package/dist/middleware/project-ref.js.map +1 -1
  108. package/dist/openapi/generate.d.ts.map +1 -1
  109. package/dist/openapi/generate.js +5 -2
  110. package/dist/openapi/generate.js.map +1 -1
  111. package/dist/openapi/operations.d.ts.map +1 -1
  112. package/dist/openapi/operations.js +919 -140
  113. package/dist/openapi/operations.js.map +1 -1
  114. package/dist/openapi/schemas.d.ts +90 -13
  115. package/dist/openapi/schemas.d.ts.map +1 -1
  116. package/dist/openapi/schemas.js +2081 -359
  117. package/dist/openapi/schemas.js.map +1 -1
  118. package/dist/person-grants-binding.d.ts +77 -0
  119. package/dist/person-grants-binding.d.ts.map +1 -0
  120. package/dist/person-grants-binding.js +4 -0
  121. package/dist/person-grants-binding.js.map +1 -0
  122. package/dist/proposal-service.d.ts +121 -0
  123. package/dist/proposal-service.d.ts.map +1 -0
  124. package/dist/proposal-service.js +447 -0
  125. package/dist/proposal-service.js.map +1 -0
  126. package/dist/proposal-status.d.ts +60 -0
  127. package/dist/proposal-status.d.ts.map +1 -0
  128. package/dist/proposal-status.js +109 -0
  129. package/dist/proposal-status.js.map +1 -0
  130. package/dist/provider-binding.d.ts +10 -0
  131. package/dist/provider-binding.d.ts.map +1 -1
  132. package/dist/publish-refused.d.ts +28 -3
  133. package/dist/publish-refused.d.ts.map +1 -1
  134. package/dist/publish-refused.js +24 -3
  135. package/dist/publish-refused.js.map +1 -1
  136. package/dist/routes/adapters.d.ts +2 -1
  137. package/dist/routes/adapters.d.ts.map +1 -1
  138. package/dist/routes/adapters.js +3 -1
  139. package/dist/routes/adapters.js.map +1 -1
  140. package/dist/routes/agent-releases.d.ts +55 -3
  141. package/dist/routes/agent-releases.d.ts.map +1 -1
  142. package/dist/routes/agent-releases.js +85 -55
  143. package/dist/routes/agent-releases.js.map +1 -1
  144. package/dist/routes/agents.d.ts +12 -1
  145. package/dist/routes/agents.d.ts.map +1 -1
  146. package/dist/routes/agents.js +100 -2
  147. package/dist/routes/agents.js.map +1 -1
  148. package/dist/routes/approvals.d.ts +15 -3
  149. package/dist/routes/approvals.d.ts.map +1 -1
  150. package/dist/routes/approvals.js +133 -122
  151. package/dist/routes/approvals.js.map +1 -1
  152. package/dist/routes/artifacts.d.ts +25 -3
  153. package/dist/routes/artifacts.d.ts.map +1 -1
  154. package/dist/routes/artifacts.js +116 -7
  155. package/dist/routes/artifacts.js.map +1 -1
  156. package/dist/routes/auth.d.ts +25 -1
  157. package/dist/routes/auth.d.ts.map +1 -1
  158. package/dist/routes/auth.js +655 -281
  159. package/dist/routes/auth.js.map +1 -1
  160. package/dist/routes/blocks.d.ts.map +1 -1
  161. package/dist/routes/blocks.js +2 -42
  162. package/dist/routes/blocks.js.map +1 -1
  163. package/dist/routes/capabilities.d.ts +2 -1
  164. package/dist/routes/capabilities.d.ts.map +1 -1
  165. package/dist/routes/capabilities.js +6 -1
  166. package/dist/routes/capabilities.js.map +1 -1
  167. package/dist/routes/compliance.d.ts +9 -10
  168. package/dist/routes/compliance.d.ts.map +1 -1
  169. package/dist/routes/compliance.js +42 -61
  170. package/dist/routes/compliance.js.map +1 -1
  171. package/dist/routes/conversations.d.ts +8 -1
  172. package/dist/routes/conversations.d.ts.map +1 -1
  173. package/dist/routes/conversations.js +87 -8
  174. package/dist/routes/conversations.js.map +1 -1
  175. package/dist/routes/cost.d.ts +12 -1
  176. package/dist/routes/cost.d.ts.map +1 -1
  177. package/dist/routes/cost.js +74 -3
  178. package/dist/routes/cost.js.map +1 -1
  179. package/dist/routes/denied.d.ts +11 -0
  180. package/dist/routes/denied.d.ts.map +1 -0
  181. package/dist/routes/denied.js +17 -0
  182. package/dist/routes/denied.js.map +1 -0
  183. package/dist/routes/deployments.d.ts +10 -1
  184. package/dist/routes/deployments.d.ts.map +1 -1
  185. package/dist/routes/deployments.js +171 -13
  186. package/dist/routes/deployments.js.map +1 -1
  187. package/dist/routes/eval-comparison.d.ts.map +1 -1
  188. package/dist/routes/eval-comparison.js +69 -1
  189. package/dist/routes/eval-comparison.js.map +1 -1
  190. package/dist/routes/eval-overrides.d.ts +18 -0
  191. package/dist/routes/eval-overrides.d.ts.map +1 -0
  192. package/dist/routes/eval-overrides.js +81 -0
  193. package/dist/routes/eval-overrides.js.map +1 -0
  194. package/dist/routes/eval-runs.d.ts +10 -1
  195. package/dist/routes/eval-runs.d.ts.map +1 -1
  196. package/dist/routes/eval-runs.js +66 -7
  197. package/dist/routes/eval-runs.js.map +1 -1
  198. package/dist/routes/eval-suites.d.ts.map +1 -1
  199. package/dist/routes/eval-suites.js +28 -2
  200. package/dist/routes/eval-suites.js.map +1 -1
  201. package/dist/routes/event-triggers.d.ts +5 -1
  202. package/dist/routes/event-triggers.d.ts.map +1 -1
  203. package/dist/routes/event-triggers.js +26 -3
  204. package/dist/routes/event-triggers.js.map +1 -1
  205. package/dist/routes/export-signing-keys.d.ts +12 -0
  206. package/dist/routes/export-signing-keys.d.ts.map +1 -0
  207. package/dist/routes/export-signing-keys.js +24 -0
  208. package/dist/routes/export-signing-keys.js.map +1 -0
  209. package/dist/routes/flows.d.ts.map +1 -1
  210. package/dist/routes/flows.js +9 -1
  211. package/dist/routes/flows.js.map +1 -1
  212. package/dist/routes/gate-policies.d.ts +4 -1
  213. package/dist/routes/gate-policies.d.ts.map +1 -1
  214. package/dist/routes/gate-policies.js +29 -5
  215. package/dist/routes/gate-policies.js.map +1 -1
  216. package/dist/routes/guardrails.d.ts +16 -14
  217. package/dist/routes/guardrails.d.ts.map +1 -1
  218. package/dist/routes/guardrails.js +39 -3
  219. package/dist/routes/guardrails.js.map +1 -1
  220. package/dist/routes/identity.d.ts +20 -4
  221. package/dist/routes/identity.d.ts.map +1 -1
  222. package/dist/routes/identity.js +246 -6
  223. package/dist/routes/identity.js.map +1 -1
  224. package/dist/routes/improvement-passes.d.ts +52 -0
  225. package/dist/routes/improvement-passes.d.ts.map +1 -0
  226. package/dist/routes/improvement-passes.js +449 -0
  227. package/dist/routes/improvement-passes.js.map +1 -0
  228. package/dist/routes/judged-suites.d.ts +50 -0
  229. package/dist/routes/judged-suites.d.ts.map +1 -1
  230. package/dist/routes/judged-suites.js +66 -27
  231. package/dist/routes/judged-suites.js.map +1 -1
  232. package/dist/routes/judgment-context.d.ts +18 -2
  233. package/dist/routes/judgment-context.d.ts.map +1 -1
  234. package/dist/routes/judgment-context.js +63 -5
  235. package/dist/routes/judgment-context.js.map +1 -1
  236. package/dist/routes/judgment-flow-context.d.ts.map +1 -1
  237. package/dist/routes/judgment-flow-context.js +14 -2
  238. package/dist/routes/judgment-flow-context.js.map +1 -1
  239. package/dist/routes/judgments.d.ts.map +1 -1
  240. package/dist/routes/judgments.js +8 -1
  241. package/dist/routes/judgments.js.map +1 -1
  242. package/dist/routes/mcp.d.ts.map +1 -1
  243. package/dist/routes/mcp.js +26 -1
  244. package/dist/routes/mcp.js.map +1 -1
  245. package/dist/routes/memory-access.d.ts +43 -0
  246. package/dist/routes/memory-access.d.ts.map +1 -0
  247. package/dist/routes/memory-access.js +114 -0
  248. package/dist/routes/memory-access.js.map +1 -0
  249. package/dist/routes/memory-erasures.d.ts +25 -0
  250. package/dist/routes/memory-erasures.d.ts.map +1 -0
  251. package/dist/routes/memory-erasures.js +214 -0
  252. package/dist/routes/memory-erasures.js.map +1 -0
  253. package/dist/routes/memory-parse.d.ts +38 -0
  254. package/dist/routes/memory-parse.d.ts.map +1 -0
  255. package/dist/routes/memory-parse.js +199 -0
  256. package/dist/routes/memory-parse.js.map +1 -0
  257. package/dist/routes/memory.d.ts +9 -4
  258. package/dist/routes/memory.d.ts.map +1 -1
  259. package/dist/routes/memory.js +226 -234
  260. package/dist/routes/memory.js.map +1 -1
  261. package/dist/routes/observations.d.ts +4 -1
  262. package/dist/routes/observations.d.ts.map +1 -1
  263. package/dist/routes/observations.js +8 -2
  264. package/dist/routes/observations.js.map +1 -1
  265. package/dist/routes/pagination.d.ts +7 -0
  266. package/dist/routes/pagination.d.ts.map +1 -1
  267. package/dist/routes/pagination.js +10 -0
  268. package/dist/routes/pagination.js.map +1 -1
  269. package/dist/routes/policies.d.ts +2 -1
  270. package/dist/routes/policies.d.ts.map +1 -1
  271. package/dist/routes/policies.js +3 -1
  272. package/dist/routes/policies.js.map +1 -1
  273. package/dist/routes/project-mismatch.d.ts +22 -0
  274. package/dist/routes/project-mismatch.d.ts.map +1 -0
  275. package/dist/routes/project-mismatch.js +34 -0
  276. package/dist/routes/project-mismatch.js.map +1 -0
  277. package/dist/routes/projects.d.ts +15 -4
  278. package/dist/routes/projects.d.ts.map +1 -1
  279. package/dist/routes/projects.js +100 -9
  280. package/dist/routes/projects.js.map +1 -1
  281. package/dist/routes/proposals.d.ts +25 -17
  282. package/dist/routes/proposals.d.ts.map +1 -1
  283. package/dist/routes/proposals.js +340 -507
  284. package/dist/routes/proposals.js.map +1 -1
  285. package/dist/routes/provenance.d.ts +17 -3
  286. package/dist/routes/provenance.d.ts.map +1 -1
  287. package/dist/routes/provenance.js +79 -103
  288. package/dist/routes/provenance.js.map +1 -1
  289. package/dist/routes/providers.d.ts +17 -1
  290. package/dist/routes/providers.d.ts.map +1 -1
  291. package/dist/routes/providers.js +110 -3
  292. package/dist/routes/providers.js.map +1 -1
  293. package/dist/routes/public-run-tokens.d.ts.map +1 -1
  294. package/dist/routes/public-run-tokens.js +3 -0
  295. package/dist/routes/public-run-tokens.js.map +1 -1
  296. package/dist/routes/readable-projects.d.ts +26 -0
  297. package/dist/routes/readable-projects.d.ts.map +1 -0
  298. package/dist/routes/readable-projects.js +39 -0
  299. package/dist/routes/readable-projects.js.map +1 -0
  300. package/dist/routes/runs.d.ts +10 -0
  301. package/dist/routes/runs.d.ts.map +1 -1
  302. package/dist/routes/runs.js +92 -42
  303. package/dist/routes/runs.js.map +1 -1
  304. package/dist/routes/schedules.d.ts +21 -13
  305. package/dist/routes/schedules.d.ts.map +1 -1
  306. package/dist/routes/schedules.js +416 -55
  307. package/dist/routes/schedules.js.map +1 -1
  308. package/dist/routes/segments.d.ts +2 -0
  309. package/dist/routes/segments.d.ts.map +1 -1
  310. package/dist/routes/segments.js +5 -0
  311. package/dist/routes/segments.js.map +1 -1
  312. package/dist/routes/service-accounts.d.ts +17 -0
  313. package/dist/routes/service-accounts.d.ts.map +1 -0
  314. package/dist/routes/service-accounts.js +212 -0
  315. package/dist/routes/service-accounts.js.map +1 -0
  316. package/dist/routes/sign-in-options.d.ts +50 -0
  317. package/dist/routes/sign-in-options.d.ts.map +1 -0
  318. package/dist/routes/sign-in-options.js +98 -0
  319. package/dist/routes/sign-in-options.js.map +1 -0
  320. package/dist/routes/signing-keys.d.ts +2 -1
  321. package/dist/routes/signing-keys.d.ts.map +1 -1
  322. package/dist/routes/signing-keys.js +3 -1
  323. package/dist/routes/signing-keys.js.map +1 -1
  324. package/dist/routes/tenant-access.d.ts +13 -0
  325. package/dist/routes/tenant-access.d.ts.map +1 -0
  326. package/dist/routes/tenant-access.js +26 -0
  327. package/dist/routes/tenant-access.js.map +1 -0
  328. package/dist/routes/tenant.d.ts.map +1 -1
  329. package/dist/routes/tenant.js +4 -0
  330. package/dist/routes/tenant.js.map +1 -1
  331. package/dist/routes/token-sign-in.d.ts +36 -0
  332. package/dist/routes/token-sign-in.d.ts.map +1 -0
  333. package/dist/routes/token-sign-in.js +101 -0
  334. package/dist/routes/token-sign-in.js.map +1 -0
  335. package/dist/routes/tokens.d.ts +15 -5
  336. package/dist/routes/tokens.d.ts.map +1 -1
  337. package/dist/routes/tokens.js +162 -43
  338. package/dist/routes/tokens.js.map +1 -1
  339. package/dist/routes/tools.d.ts.map +1 -1
  340. package/dist/routes/tools.js +9 -1
  341. package/dist/routes/tools.js.map +1 -1
  342. package/dist/routes/trigger-access.d.ts +29 -0
  343. package/dist/routes/trigger-access.d.ts.map +1 -0
  344. package/dist/routes/trigger-access.js +33 -0
  345. package/dist/routes/trigger-access.js.map +1 -0
  346. package/dist/routes/webhook-endpoints.d.ts +2 -1
  347. package/dist/routes/webhook-endpoints.d.ts.map +1 -1
  348. package/dist/routes/webhook-endpoints.js +11 -3
  349. package/dist/routes/webhook-endpoints.js.map +1 -1
  350. package/dist/routes/webhooks.d.ts +5 -1
  351. package/dist/routes/webhooks.d.ts.map +1 -1
  352. package/dist/routes/webhooks.js +25 -2
  353. package/dist/routes/webhooks.js.map +1 -1
  354. package/dist/run-failure.d.ts +21 -0
  355. package/dist/run-failure.d.ts.map +1 -0
  356. package/dist/run-failure.js +29 -0
  357. package/dist/run-failure.js.map +1 -0
  358. package/dist/service-account-binding.d.ts +108 -0
  359. package/dist/service-account-binding.d.ts.map +1 -0
  360. package/dist/service-account-binding.js +4 -0
  361. package/dist/service-account-binding.js.map +1 -0
  362. package/dist/session-store-binding.d.ts +61 -13
  363. package/dist/session-store-binding.d.ts.map +1 -1
  364. package/dist/signed-export.d.ts +89 -0
  365. package/dist/signed-export.d.ts.map +1 -0
  366. package/dist/signed-export.js +149 -0
  367. package/dist/signed-export.js.map +1 -0
  368. package/dist/supervisor-binding.d.ts +186 -413
  369. package/dist/supervisor-binding.d.ts.map +1 -1
  370. package/dist/testing/app-bindings.d.ts +33 -0
  371. package/dist/testing/app-bindings.d.ts.map +1 -0
  372. package/dist/testing/app-bindings.js +112 -0
  373. package/dist/testing/app-bindings.js.map +1 -0
  374. package/dist/testing/index.d.ts +11 -0
  375. package/dist/testing/index.d.ts.map +1 -0
  376. package/dist/testing/index.js +13 -0
  377. package/dist/testing/index.js.map +1 -0
  378. package/dist/testing/stub-binding.d.ts +34 -0
  379. package/dist/testing/stub-binding.d.ts.map +1 -0
  380. package/dist/testing/stub-binding.js +42 -0
  381. package/dist/testing/stub-binding.js.map +1 -0
  382. package/dist/testing/trigger-registry.d.ts +21 -0
  383. package/dist/testing/trigger-registry.d.ts.map +1 -0
  384. package/dist/testing/trigger-registry.js +206 -0
  385. package/dist/testing/trigger-registry.js.map +1 -0
  386. package/dist/token-admin.d.ts +46 -8
  387. package/dist/token-admin.d.ts.map +1 -1
  388. package/dist/token-admin.js.map +1 -1
  389. package/dist/tool-binding.d.ts +13 -0
  390. package/dist/tool-binding.d.ts.map +1 -1
  391. package/dist/trigger-binding.d.ts +2 -2
  392. package/dist/trigger-binding.d.ts.map +1 -1
  393. package/dist/trigger-binding.js +1 -1
  394. package/dist/trigger-binding.js.map +1 -1
  395. package/dist/types.d.ts +16 -0
  396. package/dist/types.d.ts.map +1 -1
  397. package/dist/webhook-endpoint-binding.d.ts +19 -3
  398. package/dist/webhook-endpoint-binding.d.ts.map +1 -1
  399. package/dist/webhook-endpoint-binding.js +1 -1
  400. package/dist/webhook-endpoint-binding.js.map +1 -1
  401. package/openapi.json +15387 -8789
  402. package/package.json +26 -22
  403. package/src/agent-binding.ts +14 -0
  404. package/src/agent-pins.ts +18 -2
  405. package/src/app.ts +366 -80
  406. package/src/block-publish.ts +181 -0
  407. package/src/caller.ts +62 -0
  408. package/src/capability-binding.ts +12 -0
  409. package/src/cost-binding.ts +19 -0
  410. package/src/derive-agent-version.ts +9 -1
  411. package/src/errors.ts +75 -7
  412. package/src/eval-case-binding.ts +17 -1
  413. package/src/eval-run-binding.ts +31 -0
  414. package/src/eval-run-dispatcher.ts +7 -0
  415. package/src/eval-sample.ts +42 -0
  416. package/src/eval-suite-binding.ts +14 -0
  417. package/src/flow-binding.ts +19 -0
  418. package/src/flow-pins.ts +18 -2
  419. package/src/gate.ts +26 -0
  420. package/src/handler-binding.ts +36 -1
  421. package/src/hitl-binding.ts +24 -0
  422. package/src/identity-directory-binding.ts +85 -4
  423. package/src/identity-provider-binding.ts +216 -21
  424. package/src/improvement-pass-binding.ts +193 -0
  425. package/src/index.ts +153 -31
  426. package/src/judged-dispatcher.ts +78 -11
  427. package/src/judgment-binding.ts +25 -1
  428. package/src/mcp-endpoint-binding.ts +7 -0
  429. package/src/memory-binding.ts +121 -17
  430. package/src/memory-erasure-binding.ts +156 -0
  431. package/src/middleware/auth.ts +228 -62
  432. package/src/middleware/authorize.ts +155 -3
  433. package/src/middleware/error-mapper.ts +15 -1
  434. package/src/middleware/idempotency.ts +230 -43
  435. package/src/middleware/key-project.ts +86 -0
  436. package/src/middleware/principal.ts +9 -5
  437. package/src/middleware/project-ref.ts +3 -0
  438. package/src/openapi/generate.ts +11 -2
  439. package/src/openapi/operations.ts +1060 -143
  440. package/src/openapi/schemas.ts +2387 -469
  441. package/src/person-grants-binding.ts +76 -0
  442. package/src/proposal-service.ts +640 -0
  443. package/src/proposal-status.ts +176 -0
  444. package/src/provider-binding.ts +10 -0
  445. package/src/publish-refused.ts +56 -9
  446. package/src/routes/adapters.ts +4 -0
  447. package/src/routes/agent-releases.ts +121 -69
  448. package/src/routes/agents.ts +128 -1
  449. package/src/routes/approvals.ts +164 -154
  450. package/src/routes/artifacts.ts +181 -11
  451. package/src/routes/auth.ts +804 -336
  452. package/src/routes/blocks.ts +2 -53
  453. package/src/routes/capabilities.ts +10 -1
  454. package/src/routes/compliance.ts +60 -99
  455. package/src/routes/conversations.ts +101 -7
  456. package/src/routes/cost.ts +110 -4
  457. package/src/routes/denied.ts +28 -0
  458. package/src/routes/deployments.ts +210 -17
  459. package/src/routes/eval-comparison.ts +81 -2
  460. package/src/routes/eval-overrides.ts +108 -0
  461. package/src/routes/eval-runs.ts +89 -6
  462. package/src/routes/eval-suites.ts +32 -2
  463. package/src/routes/event-triggers.ts +27 -3
  464. package/src/routes/export-signing-keys.ts +31 -0
  465. package/src/routes/flows.ts +12 -1
  466. package/src/routes/gate-policies.ts +37 -6
  467. package/src/routes/guardrails.ts +61 -2
  468. package/src/routes/identity.ts +328 -10
  469. package/src/routes/improvement-passes.ts +541 -0
  470. package/src/routes/judged-suites.ts +113 -43
  471. package/src/routes/judgment-context.ts +76 -5
  472. package/src/routes/judgment-flow-context.ts +16 -2
  473. package/src/routes/judgments.ts +17 -3
  474. package/src/routes/mcp.ts +29 -1
  475. package/src/routes/memory-access.ts +160 -0
  476. package/src/routes/memory-erasures.ts +240 -0
  477. package/src/routes/memory-parse.ts +250 -0
  478. package/src/routes/memory.ts +244 -286
  479. package/src/routes/observations.ts +15 -2
  480. package/src/routes/pagination.ts +12 -0
  481. package/src/routes/policies.ts +7 -1
  482. package/src/routes/project-mismatch.ts +56 -0
  483. package/src/routes/projects.ts +107 -13
  484. package/src/routes/proposals.ts +370 -611
  485. package/src/routes/provenance.ts +99 -133
  486. package/src/routes/providers.ts +142 -3
  487. package/src/routes/public-run-tokens.ts +3 -0
  488. package/src/routes/readable-projects.ts +66 -0
  489. package/src/routes/runs.ts +119 -39
  490. package/src/routes/schedules.ts +472 -60
  491. package/src/routes/segments.ts +11 -0
  492. package/src/routes/service-accounts.ts +262 -0
  493. package/src/routes/sign-in-options.ts +168 -0
  494. package/src/routes/signing-keys.ts +7 -1
  495. package/src/routes/tenant-access.ts +37 -0
  496. package/src/routes/tenant.ts +6 -0
  497. package/src/routes/token-sign-in.ts +161 -0
  498. package/src/routes/tokens.ts +172 -46
  499. package/src/routes/tools.ts +12 -1
  500. package/src/routes/trigger-access.ts +72 -0
  501. package/src/routes/webhook-endpoints.ts +15 -3
  502. package/src/routes/webhooks.ts +26 -2
  503. package/src/run-failure.ts +42 -0
  504. package/src/service-account-binding.ts +113 -0
  505. package/src/session-store-binding.ts +65 -13
  506. package/src/signed-export.ts +250 -0
  507. package/src/supervisor-binding.ts +169 -409
  508. package/src/testing/app-bindings.ts +142 -0
  509. package/src/testing/index.ts +21 -0
  510. package/src/testing/stub-binding.ts +53 -0
  511. package/src/testing/trigger-registry.ts +259 -0
  512. package/src/token-admin.ts +40 -8
  513. package/src/tool-binding.ts +14 -0
  514. package/src/trigger-binding.ts +8 -1
  515. package/src/types.ts +16 -0
  516. package/src/webhook-endpoint-binding.ts +18 -3
@@ -1,9 +1,11 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (C) 2026 Kindgi Inc.
3
- import { createHash, randomBytes } from 'node:crypto';
3
+ import { createHash, randomBytes, randomUUID } from 'node:crypto';
4
4
  import { Hono } from 'hono';
5
5
  import { statusFor, toWireError } from '../errors.js';
6
6
  import { encodeSessionToken } from '../middleware/auth.js';
7
+ import { withholdFromReplay } from '../middleware/idempotency.js';
8
+ import { tenantResourceAccess } from './tenant-access.js';
7
9
  const DEFAULT_STATE_TTL_MS = 10 * 60 * 1000;
8
10
  /**
9
11
  * Build the auth router. Two Hono routers are returned — one gated
@@ -20,6 +22,11 @@ export function authRouters(options) {
20
22
  const refreshToken = options.refreshToken;
21
23
  const stateTtlMs = options.stateTtlMs ?? DEFAULT_STATE_TTL_MS;
22
24
  const authed = new Hono();
25
+ // The provider catalog is tenant-wide: any GET (the list, one provider,
26
+ // its sign-in URLs) needs `read` on the tenant, any change `admin`.
27
+ const providerAccess = tenantResourceAccess(options.authorizer);
28
+ authed.use('/providers', providerAccess);
29
+ authed.use('/providers/*', providerAccess);
23
30
  // ---------- GET /providers ----------
24
31
  authed.get('/providers', async (c) => {
25
32
  const tenantId = c.get('tenantId');
@@ -50,9 +57,94 @@ export function authRouters(options) {
50
57
  }, requestId));
51
58
  }
52
59
  const outcome = await identityProvider.register({ tenantId, config: parsed.value });
60
+ if (outcome.kind === 'invalid') {
61
+ c.status(statusFor('identity-provider-invalid'));
62
+ return c.json(toWireError({
63
+ code: 'identity-provider-invalid',
64
+ message: outcome.message,
65
+ providerId: parsed.value.providerId,
66
+ }, requestId));
67
+ }
53
68
  c.status(201);
54
- return c.json({ providerId: outcome.providerId });
69
+ return c.json({
70
+ providerId: outcome.providerId,
71
+ ...(outcome.provider !== undefined && {
72
+ provider: serializeProviderConfig(outcome.provider),
73
+ }),
74
+ });
75
+ });
76
+ // ---------- GET /providers/:providerId ----------
77
+ authed.get('/providers/:providerId', async (c) => {
78
+ const tenantId = c.get('tenantId');
79
+ const providerId = c.req.param('providerId');
80
+ const found = await identityProvider.get({ tenantId, providerId });
81
+ if (found === null)
82
+ return providerNotFound(c, providerId);
83
+ return c.json(serializeProviderConfig(found));
55
84
  });
85
+ // ---------- GET /providers/:providerId/sign-in ----------
86
+ const signInUrls = identityProvider.signInUrls;
87
+ if (signInUrls !== undefined) {
88
+ authed.get('/providers/:providerId/sign-in', async (c) => {
89
+ const requestId = c.get('requestId');
90
+ const tenantId = c.get('tenantId');
91
+ const providerId = c.req.param('providerId');
92
+ const asked = c.req.query('kind');
93
+ if (asked !== undefined && asked !== 'oidc' && asked !== 'saml' && asked !== 'oauth2') {
94
+ c.status(statusFor('bad-input'));
95
+ return c.json(toWireError({ code: 'bad-input', message: '`kind` must be `oidc`, `saml` or `oauth2`' }, requestId));
96
+ }
97
+ const registered = await identityProvider.get({ tenantId, providerId });
98
+ const kind = asked ?? registered?.kind;
99
+ if (kind === undefined) {
100
+ c.status(statusFor('bad-input'));
101
+ return c.json(toWireError({
102
+ code: 'bad-input',
103
+ message: `No identity provider "${providerId}" is registered yet: say which \`kind\` it will be (\`?kind=oidc\` or \`?kind=saml\`)`,
104
+ }, requestId));
105
+ }
106
+ const signIn = await signInUrls({ tenantId, providerId, kind });
107
+ if (signIn === undefined) {
108
+ c.status(statusFor('bad-input'));
109
+ return c.json(toWireError({
110
+ code: 'bad-input',
111
+ message: `This deployment doesn't sign in with \`${kind}\` providers`,
112
+ }, requestId));
113
+ }
114
+ return c.json({ providerId, kind, signIn, registered: registered !== null });
115
+ });
116
+ }
117
+ // ---------- PATCH /providers/:providerId ----------
118
+ const update = identityProvider.update;
119
+ if (update !== undefined) {
120
+ authed.patch('/providers/:providerId', async (c) => {
121
+ const requestId = c.get('requestId');
122
+ const tenantId = c.get('tenantId');
123
+ const providerId = c.req.param('providerId');
124
+ const parsedJson = await parseJsonBody(c);
125
+ if (parsedJson.kind === 'err') {
126
+ c.status(statusFor('bad-input'));
127
+ return c.json(toWireError(parsedJson.error, requestId));
128
+ }
129
+ const existing = await identityProvider.get({ tenantId, providerId });
130
+ if (existing === null)
131
+ return providerNotFound(c, providerId);
132
+ const merged = mergeProviderChanges(existing, parsedJson.value);
133
+ const parsed = merged.kind === 'err' ? merged : parseProviderConfig(merged.value);
134
+ if (parsed.kind === 'err') {
135
+ c.status(statusFor(parsed.error.code));
136
+ return c.json(toWireError(parsed.error, requestId));
137
+ }
138
+ const outcome = await update({ tenantId, config: parsed.value });
139
+ if (outcome.kind === 'not-found')
140
+ return providerNotFound(c, providerId);
141
+ if (outcome.kind === 'invalid') {
142
+ c.status(statusFor('identity-provider-invalid'));
143
+ return c.json(toWireError({ code: 'identity-provider-invalid', message: outcome.message, providerId }, requestId));
144
+ }
145
+ return c.json({ providerId, provider: serializeProviderConfig(outcome.provider) });
146
+ });
147
+ }
56
148
  // ---------- POST /providers/:providerId/unregister ----------
57
149
  authed.post('/providers/:providerId/unregister', async (c) => {
58
150
  const requestId = c.get('requestId');
@@ -69,89 +161,100 @@ export function authRouters(options) {
69
161
  }
70
162
  return c.json({ providerId, unregistered: true });
71
163
  });
72
- // ---------- POST /login/:providerId ----------
73
- authed.post('/login/:providerId', async (c) => {
74
- const requestId = c.get('requestId');
75
- const tenantId = c.get('tenantId');
76
- const providerId = c.req.param('providerId');
77
- const config = await identityProvider.get({ tenantId, providerId });
78
- if (config === null) {
79
- c.status(statusFor('identity-provider-not-found'));
80
- return c.json(toWireError({
81
- code: 'identity-provider-not-found',
82
- message: `No identity provider registered with id "${providerId}"`,
83
- providerId,
84
- }, requestId));
85
- }
86
- let redirectUri;
87
- const parsedBody = await parseOptionalJsonBody(c);
88
- if (parsedBody.kind === 'err') {
89
- c.status(statusFor('bad-input'));
90
- return c.json(toWireError(parsedBody.error, requestId));
91
- }
92
- if (parsedBody.value !== undefined) {
93
- const raw = parsedBody.value.redirectUri;
94
- if (raw !== undefined) {
95
- if (typeof raw !== 'string' || raw.length === 0) {
96
- c.status(statusFor('bad-input'));
97
- return c.json(toWireError({ code: 'bad-input', message: '`redirectUri` must be a non-empty string' }, requestId));
164
+ if (exchangeCode !== undefined) {
165
+ // ---------- POST /login/:providerId ----------
166
+ authed.post('/login/:providerId', async (c) => {
167
+ const requestId = c.get('requestId');
168
+ const tenantId = c.get('tenantId');
169
+ const providerId = c.req.param('providerId');
170
+ const found = await identityProvider.get({ tenantId, providerId });
171
+ if (found === null) {
172
+ c.status(statusFor('identity-provider-not-found'));
173
+ return c.json(toWireError({
174
+ code: 'identity-provider-not-found',
175
+ message: `No identity provider registered with id "${providerId}"`,
176
+ providerId,
177
+ }, requestId));
178
+ }
179
+ const config = oauthFlowOf(found);
180
+ if (config === null) {
181
+ c.status(statusFor('invalid-provider-config'));
182
+ return c.json(toWireError({
183
+ code: 'invalid-provider-config',
184
+ message: `Identity provider "${providerId}" (${found.kind}) signs in through the deployment's own sign-in, not this endpoint`,
185
+ providerId,
186
+ }, requestId));
187
+ }
188
+ let redirectUri;
189
+ const parsedBody = await parseOptionalJsonBody(c);
190
+ if (parsedBody.kind === 'err') {
191
+ c.status(statusFor('bad-input'));
192
+ return c.json(toWireError(parsedBody.error, requestId));
193
+ }
194
+ if (parsedBody.value !== undefined) {
195
+ const raw = parsedBody.value.redirectUri;
196
+ if (raw !== undefined) {
197
+ if (typeof raw !== 'string' || raw.length === 0) {
198
+ c.status(statusFor('bad-input'));
199
+ return c.json(toWireError({ code: 'bad-input', message: '`redirectUri` must be a non-empty string' }, requestId));
200
+ }
201
+ redirectUri = raw;
98
202
  }
99
- redirectUri = raw;
100
203
  }
101
- }
102
- const state = base64Url(randomBytes(32));
103
- const codeVerifier = base64Url(randomBytes(64));
104
- const codeChallenge = base64Url(createHash('sha256').update(codeVerifier).digest());
105
- const effectiveRedirect = redirectUri ??
106
- (typeof config.metadata?.defaultRedirectUri === 'string'
107
- ? String(config.metadata?.defaultRedirectUri)
108
- : '');
109
- if (effectiveRedirect.length === 0) {
110
- c.status(statusFor('bad-input'));
111
- return c.json(toWireError({
112
- code: 'bad-input',
113
- message: '`redirectUri` must be supplied in the body or via `metadata.defaultRedirectUri` on the provider config',
114
- }, requestId));
115
- }
116
- // OAuth 2.1 BCP redirect-URI allowlist enforcement. Absent /
117
- // empty list means pass-through (no allowlist check); populated
118
- // list requires an exact-string match against the effective
119
- // redirect URI (either the body-supplied value or the resolved
120
- // `metadata.defaultRedirectUri`). This is the primary gate — the
121
- // callback route re-verifies against the same list for
122
- // belt-and-suspenders defense in case the allowlist tightened
123
- // between login and callback.
124
- if (config.allowedRedirectUris !== undefined &&
125
- config.allowedRedirectUris.length > 0 &&
126
- !config.allowedRedirectUris.includes(effectiveRedirect)) {
127
- c.status(statusFor('redirect-uri-not-allowed'));
128
- return c.json(toWireError({
129
- code: 'redirect-uri-not-allowed',
130
- message: `redirect_uri "${effectiveRedirect}" is not in the provider's allowedRedirectUris list`,
204
+ const state = base64Url(randomBytes(32));
205
+ const codeVerifier = base64Url(randomBytes(64));
206
+ const codeChallenge = base64Url(createHash('sha256').update(codeVerifier).digest());
207
+ const effectiveRedirect = redirectUri ??
208
+ (typeof config.metadata?.defaultRedirectUri === 'string'
209
+ ? String(config.metadata?.defaultRedirectUri)
210
+ : '');
211
+ if (effectiveRedirect.length === 0) {
212
+ c.status(statusFor('bad-input'));
213
+ return c.json(toWireError({
214
+ code: 'bad-input',
215
+ message: '`redirectUri` must be supplied in the body or via `metadata.defaultRedirectUri` on the provider config',
216
+ }, requestId));
217
+ }
218
+ // OAuth 2.1 BCP redirect-URI allowlist enforcement. Absent /
219
+ // empty list means pass-through (no allowlist check); populated
220
+ // list requires an exact-string match against the effective
221
+ // redirect URI (either the body-supplied value or the resolved
222
+ // `metadata.defaultRedirectUri`). This is the primary gate — the
223
+ // callback route re-verifies against the same list for
224
+ // belt-and-suspenders defense in case the allowlist tightened
225
+ // between login and callback.
226
+ if (config.allowedRedirectUris !== undefined &&
227
+ config.allowedRedirectUris.length > 0 &&
228
+ !config.allowedRedirectUris.includes(effectiveRedirect)) {
229
+ c.status(statusFor('redirect-uri-not-allowed'));
230
+ return c.json(toWireError({
231
+ code: 'redirect-uri-not-allowed',
232
+ message: `redirect_uri "${effectiveRedirect}" is not in the provider's allowedRedirectUris list`,
233
+ providerId,
234
+ }, requestId));
235
+ }
236
+ await stateStore.put({
237
+ state,
238
+ tenantId,
131
239
  providerId,
132
- }, requestId));
133
- }
134
- await stateStore.put({
135
- state,
136
- tenantId,
137
- providerId,
138
- codeVerifier,
139
- redirectUri: effectiveRedirect,
140
- expiresAt: Date.now() + stateTtlMs,
141
- });
142
- const authorizationUrl = buildAuthorizationUrl({
143
- config,
144
- state,
145
- codeChallenge,
146
- redirectUri: effectiveRedirect,
147
- });
148
- return c.json({
149
- authorizationUrl,
150
- state,
151
- codeChallenge,
152
- codeChallengeMethod: 'S256',
240
+ codeVerifier,
241
+ redirectUri: effectiveRedirect,
242
+ expiresAt: Date.now() + stateTtlMs,
243
+ });
244
+ const authorizationUrl = buildAuthorizationUrl({
245
+ config,
246
+ state,
247
+ codeChallenge,
248
+ redirectUri: effectiveRedirect,
249
+ });
250
+ return c.json({
251
+ authorizationUrl,
252
+ state,
253
+ codeChallenge,
254
+ codeChallengeMethod: 'S256',
255
+ });
153
256
  });
154
- });
257
+ }
155
258
  // ---------- POST /refresh ----------
156
259
  authed.post('/refresh', async (c) => {
157
260
  const requestId = c.get('requestId');
@@ -164,6 +267,15 @@ export function authRouters(options) {
164
267
  message: 'Refresh is only valid for session tokens (`kgi_sk_*`); bearer tokens are managed via `/v1/tokens`',
165
268
  }, requestId));
166
269
  }
270
+ if (c.get('sessionCookieName') !== undefined) {
271
+ // Refresh answers with the new token in its body, which a browser
272
+ // session must never hand to page scripts: it ends at its TTL.
273
+ c.status(statusFor('cookie-session-not-refreshable'));
274
+ return c.json(toWireError({
275
+ code: 'cookie-session-not-refreshable',
276
+ message: 'A browser session (cookie) is not refreshed: sign in again when it ends',
277
+ }, requestId));
278
+ }
167
279
  const current = await sessionStore.get({ tenantId, sessionId });
168
280
  if (current === null || current.revokedAt !== undefined) {
169
281
  c.status(statusFor('session-not-found'));
@@ -199,7 +311,7 @@ export function authRouters(options) {
199
311
  const fresh = await sessionStore.get({ tenantId, sessionId: createdSession.sessionId });
200
312
  if (fresh === null)
201
313
  throw new Error('session vanished immediately after create');
202
- created = { session: fresh, rawToken: encodeSessionToken(createdSession.sessionId) };
314
+ created = { session: fresh, rawToken: sessionTokenOf(createdSession) };
203
315
  }
204
316
  else {
205
317
  // Rotate the framework token only; keep provider tokens as-is.
@@ -207,7 +319,7 @@ export function authRouters(options) {
207
319
  tenantId,
208
320
  userId: current.userId,
209
321
  providerId: current.providerId,
210
- accessToken: current.accessToken,
322
+ ...(current.accessToken !== undefined && { accessToken: current.accessToken }),
211
323
  ...(current.refreshToken !== undefined && { refreshToken: current.refreshToken }),
212
324
  expiresAt: current.expiresAt,
213
325
  scopes: current.scopes,
@@ -216,7 +328,7 @@ export function authRouters(options) {
216
328
  const fresh = await sessionStore.get({ tenantId, sessionId: createdSession.sessionId });
217
329
  if (fresh === null)
218
330
  throw new Error('session vanished immediately after create');
219
- created = { session: fresh, rawToken: encodeSessionToken(createdSession.sessionId) };
331
+ created = { session: fresh, rawToken: sessionTokenOf(createdSession) };
220
332
  }
221
333
  // OAuth 2.1 BCP: mark the old session as ROTATED (not just revoked)
222
334
  // so the middleware can return `401 refresh-token-invalid` on reuse
@@ -224,6 +336,8 @@ export function authRouters(options) {
224
336
  // compliant clients retry with the fresh token instead of forcing
225
337
  // a full re-auth.
226
338
  await sessionStore.revoke({ tenantId, sessionId, reason: 'rotate' });
339
+ // A session token: an Idempotency-Key repeat doesn't get it.
340
+ withholdFromReplay(c);
227
341
  return c.json({
228
342
  sessionToken: created.rawToken,
229
343
  sessionId: created.session.id,
@@ -231,110 +345,126 @@ export function authRouters(options) {
231
345
  });
232
346
  });
233
347
  // ---------- POST /logout ----------
234
- authed.post('/logout', async (c) => {
235
- const requestId = c.get('requestId');
236
- const tenantId = c.get('tenantId');
237
- const sessionId = c.get('sessionId');
238
- if (sessionId === undefined) {
239
- c.status(statusFor('auth-not-session-token'));
240
- return c.json(toWireError({
241
- code: 'auth-not-session-token',
242
- message: 'Logout is only valid for session tokens (`kgi_sk_*`); bearer tokens are managed via `/v1/tokens`',
243
- }, requestId));
244
- }
245
- const outcome = await sessionStore.revoke({ tenantId, sessionId });
246
- return c.json({ sessionId, revoked: outcome.revoked });
247
- });
348
+ authed.post('/logout', logoutHandler(sessionStore, options.auditEvents));
248
349
  const callback = new Hono();
249
350
  // ---------- POST /callback/:providerId (mounted outside the bearer chain) ----------
250
- callback.post('/:providerId', async (c) => {
251
- const requestId = c.get('requestId');
252
- const providerId = c.req.param('providerId');
253
- const parsedJson = await parseJsonBody(c);
254
- if (parsedJson.kind === 'err') {
255
- c.status(statusFor('bad-input'));
256
- return c.json(toWireError(parsedJson.error, requestId));
257
- }
258
- const parsedCallback = parseCallbackBody(parsedJson.value);
259
- if (parsedCallback.kind === 'err') {
260
- c.status(statusFor(parsedCallback.error.code));
261
- return c.json(toWireError(parsedCallback.error, requestId));
262
- }
263
- const { code, state } = parsedCallback.value;
264
- // `state` alone is uniquely identifying (256 bits of entropy). The
265
- // store returns the row's `tenantId` so we know which tenant the
266
- // callback belongs to — cross-checking `providerId` guards against
267
- // a stolen `state` being replayed against the wrong provider mount.
268
- const entry = await stateStore.take({ state, providerId });
269
- if (entry === null) {
270
- c.status(statusFor('oauth-state-invalid'));
271
- return c.json(toWireError({
272
- code: 'oauth-state-invalid',
273
- message: '`state` is unknown, expired, or already consumed',
274
- }, requestId));
275
- }
276
- // Belt-and-suspenders redirect-URI check. Login already validated
277
- // the stored redirect_uri against `allowedRedirectUris` when the
278
- // row was written, but the allowlist may have tightened between
279
- // login and callback — if so, refuse the exchange rather than
280
- // handing the caller a session under a redirect the tenant no
281
- // longer trusts. Also runs when the provider config went missing
282
- // (unregister mid-flight) so we don't silently proceed.
283
- const currentConfig = await identityProvider.get({
284
- tenantId: entry.tenantId,
285
- providerId,
286
- });
287
- if (currentConfig !== null &&
288
- currentConfig.allowedRedirectUris !== undefined &&
289
- currentConfig.allowedRedirectUris.length > 0 &&
290
- !currentConfig.allowedRedirectUris.includes(entry.redirectUri)) {
291
- c.status(statusFor('redirect-uri-mismatch'));
292
- return c.json(toWireError({
293
- code: 'redirect-uri-mismatch',
294
- message: "redirect_uri from the login state row is not in the provider's current allowedRedirectUris list",
351
+ if (exchangeCode !== undefined) {
352
+ callback.post('/:providerId', async (c) => {
353
+ const requestId = c.get('requestId');
354
+ const providerId = c.req.param('providerId');
355
+ const parsedJson = await parseJsonBody(c);
356
+ if (parsedJson.kind === 'err') {
357
+ c.status(statusFor('bad-input'));
358
+ return c.json(toWireError(parsedJson.error, requestId));
359
+ }
360
+ const parsedCallback = parseCallbackBody(parsedJson.value);
361
+ if (parsedCallback.kind === 'err') {
362
+ c.status(statusFor(parsedCallback.error.code));
363
+ return c.json(toWireError(parsedCallback.error, requestId));
364
+ }
365
+ const { code, state } = parsedCallback.value;
366
+ // `state` alone is uniquely identifying (256 bits of entropy). The
367
+ // store returns the row's `tenantId` so we know which tenant the
368
+ // callback belongs to — cross-checking `providerId` guards against
369
+ // a stolen `state` being replayed against the wrong provider mount.
370
+ const entry = await stateStore.take({ state, providerId });
371
+ if (entry === null) {
372
+ c.status(statusFor('oauth-state-invalid'));
373
+ return c.json(toWireError({
374
+ code: 'oauth-state-invalid',
375
+ message: '`state` is unknown, expired, or already consumed',
376
+ }, requestId));
377
+ }
378
+ // Belt-and-suspenders redirect-URI check. Login already validated
379
+ // the stored redirect_uri against `allowedRedirectUris` when the
380
+ // row was written, but the allowlist may have tightened between
381
+ // login and callback — if so, refuse the exchange rather than
382
+ // handing the caller a session under a redirect the tenant no
383
+ // longer trusts. Also runs when the provider config went missing
384
+ // (unregister mid-flight) so we don't silently proceed.
385
+ const currentConfig = await identityProvider.get({
386
+ tenantId: entry.tenantId,
295
387
  providerId,
296
- }, requestId));
297
- }
298
- let outcome;
299
- try {
300
- outcome = await exchangeCode({
388
+ });
389
+ const currentAllowlist = currentConfig === null ? undefined : oauthFlowOf(currentConfig)?.allowedRedirectUris;
390
+ if (currentAllowlist !== undefined &&
391
+ currentAllowlist.length > 0 &&
392
+ !currentAllowlist.includes(entry.redirectUri)) {
393
+ c.status(statusFor('redirect-uri-mismatch'));
394
+ return c.json(toWireError({
395
+ code: 'redirect-uri-mismatch',
396
+ message: "redirect_uri from the login state row is not in the provider's current allowedRedirectUris list",
397
+ providerId,
398
+ }, requestId));
399
+ }
400
+ let outcome;
401
+ try {
402
+ outcome = await exchangeCode({
403
+ tenantId: entry.tenantId,
404
+ providerId,
405
+ code,
406
+ codeVerifier: entry.codeVerifier,
407
+ redirectUri: entry.redirectUri,
408
+ });
409
+ }
410
+ catch (err) {
411
+ c.status(statusFor('oauth-code-exchange-failed'));
412
+ return c.json(toWireError({
413
+ code: 'oauth-code-exchange-failed',
414
+ message: err instanceof Error ? err.message : 'code exchange failed',
415
+ }, requestId));
416
+ }
417
+ const created = await sessionStore.create({
301
418
  tenantId: entry.tenantId,
419
+ userId: outcome.userId,
302
420
  providerId,
303
- code,
304
- codeVerifier: entry.codeVerifier,
305
- redirectUri: entry.redirectUri,
421
+ accessToken: outcome.accessToken,
422
+ ...(outcome.refreshToken !== undefined && { refreshToken: outcome.refreshToken }),
423
+ expiresAt: outcome.expiresAt.toISOString(),
424
+ scopes: outcome.scopes,
425
+ ...(outcome.claims !== undefined && { metadata: outcome.claims }),
426
+ });
427
+ // A session token: an Idempotency-Key repeat doesn't get it.
428
+ withholdFromReplay(c);
429
+ c.status(201);
430
+ return c.json({
431
+ sessionToken: sessionTokenOf(created),
432
+ sessionId: created.sessionId,
433
+ expiresAt: created.expiresAt,
306
434
  });
307
- }
308
- catch (err) {
309
- c.status(statusFor('oauth-code-exchange-failed'));
310
- return c.json(toWireError({
311
- code: 'oauth-code-exchange-failed',
312
- message: err instanceof Error ? err.message : 'code exchange failed',
313
- }, requestId));
314
- }
315
- const created = await sessionStore.create({
316
- tenantId: entry.tenantId,
317
- userId: outcome.userId,
318
- providerId,
319
- accessToken: outcome.accessToken,
320
- ...(outcome.refreshToken !== undefined && { refreshToken: outcome.refreshToken }),
321
- expiresAt: outcome.expiresAt.toISOString(),
322
- scopes: outcome.scopes,
323
- ...(outcome.claims !== undefined && { metadata: outcome.claims }),
324
- });
325
- c.status(201);
326
- return c.json({
327
- sessionToken: encodeSessionToken(created.sessionId),
328
- sessionId: created.sessionId,
329
- expiresAt: created.expiresAt,
330
435
  });
331
- });
436
+ }
332
437
  return { authed, callback };
333
438
  }
334
439
  // ---------- helpers ----------
440
+ /** The token a store minted, or the older `kgi_sk_<sessionId>` for a store that mints none. */
441
+ function sessionTokenOf(created) {
442
+ return created.token ?? encodeSessionToken(created.sessionId);
443
+ }
335
444
  function base64Url(buf) {
336
445
  return buf.toString('base64url');
337
446
  }
447
+ const DEFAULT_OIDC_SCOPES = ['openid', 'email', 'profile'];
448
+ /**
449
+ * The provider's OAuth flow settings, or `null` when it has none: a SAML
450
+ * provider, or an OIDC one whose endpoints the deployment hasn't
451
+ * discovered (it signs in through the deployment's own browser flow).
452
+ */
453
+ function oauthFlowOf(config) {
454
+ if (config.kind === 'saml')
455
+ return null;
456
+ if (config.kind === 'oidc' && config.authorizationEndpoint === undefined)
457
+ return null;
458
+ return {
459
+ clientId: config.clientId,
460
+ authorizationEndpoint: config.authorizationEndpoint,
461
+ scopes: config.scopes ?? DEFAULT_OIDC_SCOPES,
462
+ ...(config.allowedRedirectUris !== undefined && {
463
+ allowedRedirectUris: config.allowedRedirectUris,
464
+ }),
465
+ ...(config.metadata !== undefined && { metadata: config.metadata }),
466
+ };
467
+ }
338
468
  function buildAuthorizationUrl(input) {
339
469
  const { config, state, codeChallenge, redirectUri } = input;
340
470
  const url = new URL(config.authorizationEndpoint);
@@ -347,25 +477,113 @@ function buildAuthorizationUrl(input) {
347
477
  url.searchParams.set('code_challenge_method', 'S256');
348
478
  return url.toString();
349
479
  }
480
+ function providerNotFound(c, providerId) {
481
+ c.status(statusFor('identity-provider-not-found'));
482
+ return c.json(toWireError({
483
+ code: 'identity-provider-not-found',
484
+ message: `No identity provider registered with id "${providerId}"`,
485
+ providerId,
486
+ }, c.get('requestId')));
487
+ }
488
+ /** What the issuer's discovery gave: stale once the issuer changes. */
489
+ const DISCOVERED_OIDC_FIELDS = [
490
+ 'authorizationEndpoint',
491
+ 'tokenEndpoint',
492
+ 'userinfoEndpoint',
493
+ 'jwksEndpoint',
494
+ ];
495
+ /**
496
+ * A PATCH body merged into the stored provider: a field given replaces
497
+ * it, `null` removes it, `providerId` and `kind` can't change. `signIn`
498
+ * is the deployment's, so it never comes from the stored copy. A new
499
+ * `issuer` drops the endpoints discovered from the old one.
500
+ */
501
+ function mergeProviderChanges(existing, body) {
502
+ if (body === null || typeof body !== 'object' || Array.isArray(body)) {
503
+ return { kind: 'err', error: { code: 'bad-input', message: 'Request body must be an object' } };
504
+ }
505
+ const changes = body;
506
+ if (changes.providerId !== undefined && changes.providerId !== existing.providerId) {
507
+ return invalid("`providerId` can't change: register the provider under the new id, then unregister this one");
508
+ }
509
+ if (changes.kind !== undefined && changes.kind !== existing.kind) {
510
+ return invalid("`kind` can't change: register a new provider of that kind, then unregister this one");
511
+ }
512
+ const { signIn: _deployments, ...stored } = serializeProviderConfig(existing);
513
+ const merged = stored;
514
+ if (existing.kind === 'oidc' &&
515
+ changes.issuer !== undefined &&
516
+ changes.issuer !== existing.issuer) {
517
+ for (const field of DISCOVERED_OIDC_FIELDS)
518
+ delete merged[field];
519
+ }
520
+ for (const [field, value] of Object.entries(changes)) {
521
+ if (value === null)
522
+ delete merged[field];
523
+ else
524
+ merged[field] = value;
525
+ }
526
+ return { kind: 'ok', value: merged };
527
+ }
350
528
  function serializeProviderConfig(c) {
351
- // Every field except clientSecretRef (which is a REFERENCE, not the
352
- // secret itself) is safe to return. Clients need enough to render an
353
- // "sign in with X" button and know what scopes they're granting.
354
- return {
529
+ // References to secrets (`clientSecretRef`, `spSigningKeyRef`…) are safe
530
+ // to return: they aren't the secrets. Clients need enough to render a
531
+ // "Sign in with X" button and to configure the identity provider.
532
+ const base = {
355
533
  providerId: c.providerId,
356
534
  kind: c.kind,
357
- clientId: c.clientId,
358
- clientSecretRef: c.clientSecretRef,
359
- authorizationEndpoint: c.authorizationEndpoint,
360
- tokenEndpoint: c.tokenEndpoint,
361
- ...(c.userinfoEndpoint !== undefined && { userinfoEndpoint: c.userinfoEndpoint }),
362
- scopes: c.scopes,
363
- ...(c.allowedRedirectUris !== undefined && {
364
- allowedRedirectUris: c.allowedRedirectUris,
365
- }),
366
- ...(c.claimMapping !== undefined && { claimMapping: c.claimMapping }),
367
- ...(c.metadata !== undefined && { metadata: c.metadata }),
535
+ ...definedOf(c, ['displayName', 'domains', 'join', 'signIn', 'metadata']),
368
536
  };
537
+ switch (c.kind) {
538
+ case 'oauth2':
539
+ return {
540
+ ...base,
541
+ clientId: c.clientId,
542
+ clientSecretRef: c.clientSecretRef,
543
+ authorizationEndpoint: c.authorizationEndpoint,
544
+ tokenEndpoint: c.tokenEndpoint,
545
+ scopes: c.scopes,
546
+ ...definedOf(c, ['userinfoEndpoint', 'allowedRedirectUris', 'claimMapping']),
547
+ };
548
+ case 'oidc':
549
+ return {
550
+ ...base,
551
+ issuer: c.issuer,
552
+ clientId: c.clientId,
553
+ clientSecretRef: c.clientSecretRef,
554
+ ...definedOf(c, [
555
+ 'scopes',
556
+ 'authorizationEndpoint',
557
+ 'tokenEndpoint',
558
+ 'userinfoEndpoint',
559
+ 'jwksEndpoint',
560
+ 'allowedRedirectUris',
561
+ 'claimMapping',
562
+ ]),
563
+ };
564
+ case 'saml':
565
+ return {
566
+ ...base,
567
+ ...definedOf(c, [
568
+ 'idpMetadataXml',
569
+ 'idpEntityId',
570
+ 'idpSsoUrl',
571
+ 'idpCertificates',
572
+ 'spSigningKeyRef',
573
+ 'spDecryptionKeyRef',
574
+ 'wantAssertionsSigned',
575
+ 'attributeMapping',
576
+ ]),
577
+ };
578
+ }
579
+ }
580
+ /** The named fields of `obj` that are set. */
581
+ function definedOf(obj, keys) {
582
+ const out = {};
583
+ for (const k of keys)
584
+ if (obj[k] !== undefined)
585
+ out[k] = obj[k];
586
+ return out;
369
587
  }
370
588
  async function parseJsonBody(c) {
371
589
  try {
@@ -396,120 +614,228 @@ async function parseOptionalJsonBody(c) {
396
614
  };
397
615
  }
398
616
  }
617
+ /**
618
+ * Fields that would carry a secret itself rather than a reference to one:
619
+ * refused outright, so a secret sent by mistake is never stored.
620
+ */
621
+ const PLAINTEXT_SECRET_FIELDS = {
622
+ clientSecret: 'clientSecretRef',
623
+ spSigningKey: 'spSigningKeyRef',
624
+ spDecryptionKey: 'spDecryptionKeyRef',
625
+ privateKey: 'spSigningKeyRef',
626
+ };
627
+ const DOMAIN_RE = /^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
628
+ function invalid(message) {
629
+ return { kind: 'err', error: { code: 'invalid-provider-config', message } };
630
+ }
631
+ /** Reads typed fields off a request body, collecting the first problem. */
632
+ class FieldReader {
633
+ b;
634
+ error;
635
+ constructor(b) {
636
+ this.b = b;
637
+ }
638
+ str(k) {
639
+ const v = this.b[k];
640
+ if (typeof v !== 'string' || v.length === 0) {
641
+ this.error ??= invalid(`Field \`${k}\` must be a non-empty string`);
642
+ return '';
643
+ }
644
+ return v;
645
+ }
646
+ optStr(k) {
647
+ const v = this.b[k];
648
+ if (v === undefined)
649
+ return undefined;
650
+ if (typeof v !== 'string' || v.length === 0) {
651
+ this.error ??= invalid(`\`${k}\` must be a non-empty string when supplied`);
652
+ return undefined;
653
+ }
654
+ return v;
655
+ }
656
+ url(k, required) {
657
+ const v = required ? this.str(k) : this.optStr(k);
658
+ if (v === undefined || v === '')
659
+ return v;
660
+ try {
661
+ new URL(v);
662
+ }
663
+ catch {
664
+ this.error ??= invalid(`\`${k}\` must be an absolute URL`);
665
+ }
666
+ return v;
667
+ }
668
+ strings(k, required) {
669
+ const v = this.b[k];
670
+ if (v === undefined) {
671
+ if (required)
672
+ this.error ??= invalid(`\`${k}\` must be an array of strings`);
673
+ return undefined;
674
+ }
675
+ if (!Array.isArray(v) || v.some((x) => typeof x !== 'string' || x.length === 0)) {
676
+ this.error ??= invalid(`\`${k}\` must be an array of non-empty strings`);
677
+ return undefined;
678
+ }
679
+ return v;
680
+ }
681
+ bool(k) {
682
+ const v = this.b[k];
683
+ if (v === undefined)
684
+ return undefined;
685
+ if (typeof v !== 'boolean') {
686
+ this.error ??= invalid(`\`${k}\` must be a boolean when supplied`);
687
+ return undefined;
688
+ }
689
+ return v;
690
+ }
691
+ object(k) {
692
+ const v = this.b[k];
693
+ if (v === undefined)
694
+ return undefined;
695
+ if (v === null || typeof v !== 'object' || Array.isArray(v)) {
696
+ this.error ??= invalid(`\`${k}\` must be an object when supplied`);
697
+ return undefined;
698
+ }
699
+ return v;
700
+ }
701
+ }
399
702
  function parseProviderConfig(body) {
400
703
  if (body === null || typeof body !== 'object' || Array.isArray(body)) {
401
704
  return { kind: 'err', error: { code: 'bad-input', message: 'Request body must be an object' } };
402
705
  }
403
706
  const b = body;
404
- const requiredStr = (k) => {
405
- const v = b[k];
406
- if (typeof v !== 'string' || v.length === 0) {
407
- return {
408
- kind: 'err',
409
- error: {
410
- code: 'invalid-provider-config',
411
- message: `Field \`${k}\` must be a non-empty string`,
412
- },
413
- };
707
+ for (const [field, instead] of Object.entries(PLAINTEXT_SECRET_FIELDS)) {
708
+ if (b[field] !== undefined) {
709
+ return invalid(`\`${field}\` is never accepted: register \`${instead}\`, a reference the deployment resolves`);
414
710
  }
415
- return v;
416
- };
417
- const providerId = requiredStr('providerId');
418
- if (typeof providerId !== 'string')
419
- return providerId;
420
- const clientId = requiredStr('clientId');
421
- if (typeof clientId !== 'string')
422
- return clientId;
423
- const clientSecretRef = requiredStr('clientSecretRef');
424
- if (typeof clientSecretRef !== 'string')
425
- return clientSecretRef;
426
- const authorizationEndpoint = requiredStr('authorizationEndpoint');
427
- if (typeof authorizationEndpoint !== 'string')
428
- return authorizationEndpoint;
429
- const tokenEndpoint = requiredStr('tokenEndpoint');
430
- if (typeof tokenEndpoint !== 'string')
431
- return tokenEndpoint;
432
- const kindRaw = b.kind;
433
- if (kindRaw !== 'oauth2' && kindRaw !== 'oidc') {
434
- return {
435
- kind: 'err',
436
- error: {
437
- code: 'invalid-provider-config',
438
- message: '`kind` must be `oauth2` or `oidc`',
439
- },
440
- };
441
711
  }
442
- const scopesRaw = b.scopes;
443
- if (!Array.isArray(scopesRaw) || scopesRaw.some((s) => typeof s !== 'string')) {
444
- return {
445
- kind: 'err',
446
- error: {
447
- code: 'invalid-provider-config',
448
- message: '`scopes` must be an array of strings',
449
- },
450
- };
712
+ const kind = b.kind;
713
+ if (kind !== 'oauth2' && kind !== 'oidc' && kind !== 'saml') {
714
+ return invalid('`kind` must be `oidc`, `saml` or `oauth2`');
451
715
  }
452
- const scopes = scopesRaw;
453
- const userinfoEndpointRaw = b.userinfoEndpoint;
454
- if (userinfoEndpointRaw !== undefined && typeof userinfoEndpointRaw !== 'string') {
455
- return {
456
- kind: 'err',
457
- error: {
458
- code: 'invalid-provider-config',
459
- message: '`userinfoEndpoint` must be a string when supplied',
460
- },
461
- };
716
+ const r = new FieldReader(b);
717
+ const providerId = r.str('providerId');
718
+ const displayName = r.optStr('displayName');
719
+ const domainsRaw = r.strings('domains', false);
720
+ const domains = domainsRaw?.map((d) => d.toLowerCase());
721
+ if (domains?.some((d) => !DOMAIN_RE.test(d))) {
722
+ r.error ??= invalid('`domains` must be email domains, e.g. `acme.com`');
462
723
  }
463
- const metadataRaw = b.metadata;
464
- if (metadataRaw !== undefined &&
465
- (metadataRaw === null || typeof metadataRaw !== 'object' || Array.isArray(metadataRaw))) {
466
- return {
467
- kind: 'err',
468
- error: {
469
- code: 'invalid-provider-config',
470
- message: '`metadata` must be an object when supplied',
471
- },
472
- };
724
+ const join = b.join;
725
+ if (join !== undefined && join !== 'invite' && join !== 'domain') {
726
+ r.error ??= invalid('`join` must be `invite` or `domain`');
473
727
  }
474
- const allowedRedirectUrisRaw = b.allowedRedirectUris;
475
- let allowedRedirectUris;
476
- if (allowedRedirectUrisRaw !== undefined) {
477
- if (!Array.isArray(allowedRedirectUrisRaw) ||
478
- allowedRedirectUrisRaw.some((s) => typeof s !== 'string' || s.length === 0)) {
479
- return {
480
- kind: 'err',
481
- error: {
482
- code: 'invalid-provider-config',
483
- message: '`allowedRedirectUris` must be an array of non-empty strings when supplied',
484
- },
485
- };
486
- }
487
- allowedRedirectUris = allowedRedirectUrisRaw;
728
+ if (join === 'domain' && (domains === undefined || domains.length === 0)) {
729
+ r.error ??= invalid('`join: domain` needs `domains`');
488
730
  }
489
- const claimMappingRaw = b.claimMapping;
731
+ const metadata = r.object('metadata');
732
+ const base = {
733
+ providerId,
734
+ ...(displayName !== undefined && { displayName }),
735
+ ...(domains !== undefined && { domains }),
736
+ ...(join !== undefined && { join: join }),
737
+ ...(metadata !== undefined && { metadata }),
738
+ };
490
739
  let claimMapping;
491
- if (claimMappingRaw !== undefined) {
492
- const parsed = parseClaimMapping(claimMappingRaw);
740
+ if (kind !== 'saml' && b.claimMapping !== undefined) {
741
+ const parsed = parseClaimMapping(b.claimMapping);
493
742
  if (parsed.kind === 'err')
494
743
  return parsed;
495
744
  claimMapping = parsed.value;
496
745
  }
497
- return {
498
- kind: 'ok',
499
- value: {
500
- providerId,
501
- kind: kindRaw,
746
+ let value;
747
+ if (kind === 'oauth2') {
748
+ const clientId = r.str('clientId');
749
+ const clientSecretRef = r.str('clientSecretRef');
750
+ const authorizationEndpoint = r.str('authorizationEndpoint');
751
+ const tokenEndpoint = r.str('tokenEndpoint');
752
+ const userinfoEndpoint = r.optStr('userinfoEndpoint');
753
+ const scopes = r.strings('scopes', true) ?? [];
754
+ const allowedRedirectUris = r.strings('allowedRedirectUris', false);
755
+ value = {
756
+ ...base,
757
+ kind,
502
758
  clientId,
503
759
  clientSecretRef,
504
760
  authorizationEndpoint,
505
761
  tokenEndpoint,
506
- ...(typeof userinfoEndpointRaw === 'string' && { userinfoEndpoint: userinfoEndpointRaw }),
507
762
  scopes,
763
+ ...(userinfoEndpoint !== undefined && { userinfoEndpoint }),
508
764
  ...(allowedRedirectUris !== undefined && { allowedRedirectUris }),
509
765
  ...(claimMapping !== undefined && { claimMapping }),
510
- ...(metadataRaw !== undefined && { metadata: metadataRaw }),
511
- },
512
- };
766
+ };
767
+ }
768
+ else if (kind === 'oidc') {
769
+ const issuer = r.url('issuer', true) ?? '';
770
+ const clientId = r.str('clientId');
771
+ const clientSecretRef = r.str('clientSecretRef');
772
+ const scopes = r.strings('scopes', false);
773
+ const authorizationEndpoint = r.url('authorizationEndpoint', false);
774
+ const tokenEndpoint = r.url('tokenEndpoint', false);
775
+ const userinfoEndpoint = r.url('userinfoEndpoint', false);
776
+ const jwksEndpoint = r.url('jwksEndpoint', false);
777
+ const allowedRedirectUris = r.strings('allowedRedirectUris', false);
778
+ value = {
779
+ ...base,
780
+ kind,
781
+ issuer,
782
+ clientId,
783
+ clientSecretRef,
784
+ ...(scopes !== undefined && { scopes }),
785
+ ...(authorizationEndpoint !== undefined && { authorizationEndpoint }),
786
+ ...(tokenEndpoint !== undefined && { tokenEndpoint }),
787
+ ...(userinfoEndpoint !== undefined && { userinfoEndpoint }),
788
+ ...(jwksEndpoint !== undefined && { jwksEndpoint }),
789
+ ...(allowedRedirectUris !== undefined && { allowedRedirectUris }),
790
+ ...(claimMapping !== undefined && { claimMapping }),
791
+ };
792
+ }
793
+ else {
794
+ const idpMetadataXml = r.optStr('idpMetadataXml');
795
+ const idpEntityId = r.optStr('idpEntityId');
796
+ const idpSsoUrl = r.url('idpSsoUrl', false);
797
+ const idpCertificates = r.strings('idpCertificates', false);
798
+ if (idpMetadataXml === undefined &&
799
+ (idpEntityId === undefined ||
800
+ idpSsoUrl === undefined ||
801
+ idpCertificates === undefined ||
802
+ idpCertificates.length === 0)) {
803
+ r.error ??= invalid('A SAML provider needs `idpMetadataXml`, or `idpEntityId` + `idpSsoUrl` + `idpCertificates`');
804
+ }
805
+ const spSigningKeyRef = r.optStr('spSigningKeyRef');
806
+ const spDecryptionKeyRef = r.optStr('spDecryptionKeyRef');
807
+ const wantAssertionsSigned = r.bool('wantAssertionsSigned');
808
+ const mappingRaw = r.object('attributeMapping');
809
+ let attributeMapping;
810
+ if (mappingRaw !== undefined) {
811
+ const m = new FieldReader(mappingRaw);
812
+ const userId = m.optStr('userId');
813
+ const email = m.optStr('email');
814
+ const displayName = m.optStr('displayName');
815
+ if (m.error !== undefined)
816
+ r.error ??= invalid(`\`attributeMapping\`: ${m.error.error.message}`);
817
+ attributeMapping = {
818
+ ...(userId !== undefined && { userId }),
819
+ ...(email !== undefined && { email }),
820
+ ...(displayName !== undefined && { displayName }),
821
+ };
822
+ }
823
+ value = {
824
+ ...base,
825
+ kind,
826
+ ...(idpMetadataXml !== undefined && { idpMetadataXml }),
827
+ ...(idpEntityId !== undefined && { idpEntityId }),
828
+ ...(idpSsoUrl !== undefined && { idpSsoUrl }),
829
+ ...(idpCertificates !== undefined && { idpCertificates }),
830
+ ...(spSigningKeyRef !== undefined && { spSigningKeyRef }),
831
+ ...(spDecryptionKeyRef !== undefined && { spDecryptionKeyRef }),
832
+ ...(wantAssertionsSigned !== undefined && { wantAssertionsSigned }),
833
+ ...(attributeMapping !== undefined && { attributeMapping }),
834
+ };
835
+ }
836
+ if (r.error !== undefined)
837
+ return r.error;
838
+ return { kind: 'ok', value };
513
839
  }
514
840
  function parseClaimMapping(raw) {
515
841
  if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
@@ -627,4 +953,52 @@ function parseCallbackBody(body) {
627
953
  }
628
954
  return { kind: 'ok', value: { code, state } };
629
955
  }
956
+ /**
957
+ * `POST /v1/auth/logout`: revokes the caller's session; a browser session
958
+ * loses its cookie too. Also mounted on its own with cookie sessions and
959
+ * no identity providers (console token sign-in).
960
+ */
961
+ export function logoutHandler(sessionStore, auditEvents) {
962
+ return async (c) => {
963
+ const requestId = c.get('requestId');
964
+ const tenantId = c.get('tenantId');
965
+ const sessionId = c.get('sessionId');
966
+ if (sessionId === undefined) {
967
+ c.status(statusFor('auth-not-session-token'));
968
+ return c.json(toWireError({
969
+ code: 'auth-not-session-token',
970
+ message: 'Logout is only valid for session tokens (`kgi_sk_*`); bearer tokens are managed via `/v1/tokens`',
971
+ }, requestId));
972
+ }
973
+ const outcome = await sessionStore.revoke({ tenantId, sessionId });
974
+ const userId = c.get('userId');
975
+ if (auditEvents !== undefined && outcome.revoked) {
976
+ try {
977
+ const appended = await auditEvents.append([
978
+ {
979
+ id: randomUUID(),
980
+ tenantId,
981
+ kind: 'signed-out',
982
+ timestamp: new Date().toISOString(),
983
+ actor: userId !== undefined ? `user:${userId}` : 'system',
984
+ outcome: 'succeeded',
985
+ payload: { v: 1, doc: { sessionId } },
986
+ },
987
+ ]);
988
+ if (appended.kind === 'err') {
989
+ c.get('log').warn(`signed-out audit event failed: ${appended.error.message}`);
990
+ }
991
+ }
992
+ catch (cause) {
993
+ c.get('log').warn(`signed-out audit event failed: ${cause instanceof Error ? cause.message : String(cause)}`);
994
+ }
995
+ }
996
+ const cookieName = c.get('sessionCookieName');
997
+ if (cookieName !== undefined) {
998
+ // A browser session: the cookie goes with it.
999
+ c.header('Set-Cookie', `${cookieName}=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax`);
1000
+ }
1001
+ return c.json({ sessionId, revoked: outcome.revoked });
1002
+ };
1003
+ }
630
1004
  //# sourceMappingURL=auth.js.map