@_mustachio/openauth 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (712) hide show
  1. package/dist/esm/adapters/d1/audit-log.js +15 -0
  2. package/dist/esm/adapters/d1/config-store.js +45 -0
  3. package/dist/esm/adapters/d1/index.js +12 -0
  4. package/dist/esm/adapters/d1/key-store.js +116 -0
  5. package/dist/esm/adapters/d1/method-store.js +52 -0
  6. package/dist/esm/adapters/d1/migrations.js +77 -0
  7. package/dist/esm/adapters/d1/session-store.js +134 -0
  8. package/dist/esm/adapters/d1/session.js +18 -0
  9. package/dist/esm/adapters/d1/token-store.js +140 -0
  10. package/dist/esm/adapters/durable-object/index.js +3 -0
  11. package/dist/esm/adapters/durable-object/session-store.js +139 -0
  12. package/dist/esm/adapters/durable-object/types.js +0 -0
  13. package/dist/esm/adapters/dynamo/audit-log.js +23 -0
  14. package/dist/esm/adapters/dynamo/client.js +0 -0
  15. package/dist/esm/adapters/dynamo/config-store.js +57 -0
  16. package/dist/esm/adapters/dynamo/index.js +10 -0
  17. package/dist/esm/adapters/dynamo/key-store.js +188 -0
  18. package/dist/esm/adapters/dynamo/method-store.js +72 -0
  19. package/dist/esm/adapters/dynamo/passkey-credential-store.js +83 -0
  20. package/dist/esm/adapters/dynamo/sdk.js +128 -0
  21. package/dist/esm/adapters/dynamo/session-store.js +173 -0
  22. package/dist/esm/adapters/dynamo/token-store.js +178 -0
  23. package/dist/esm/adapters/kms/in-memory-backing.js +28 -0
  24. package/dist/esm/adapters/kms/index.js +5 -0
  25. package/dist/esm/adapters/kms/key-store.js +129 -0
  26. package/dist/esm/adapters/kms/sdk.js +23 -0
  27. package/dist/esm/adapters/kms/types.js +0 -0
  28. package/dist/esm/adapters/kv/audit-log.js +21 -0
  29. package/dist/esm/adapters/kv/config-store.js +43 -0
  30. package/dist/esm/adapters/kv/index.js +5 -0
  31. package/dist/esm/adapters/kv/method-store.js +63 -0
  32. package/dist/esm/adapters/kv/types.js +0 -0
  33. package/dist/esm/adapters/memory/audit-log.js +16 -0
  34. package/dist/esm/adapters/memory/clock.js +5 -0
  35. package/dist/esm/adapters/memory/config-store.js +31 -0
  36. package/dist/esm/adapters/memory/index.js +10 -0
  37. package/dist/esm/adapters/memory/key-store.js +78 -0
  38. package/dist/esm/adapters/memory/method-store.js +43 -0
  39. package/dist/esm/adapters/memory/passkey-credential-store.js +53 -0
  40. package/dist/esm/adapters/memory/session-store.js +82 -0
  41. package/dist/esm/adapters/memory/token-store.js +91 -0
  42. package/dist/esm/adapters/postgres/audit-log.js +16 -0
  43. package/dist/esm/adapters/postgres/config-store.js +48 -0
  44. package/dist/esm/adapters/postgres/executor.js +21 -0
  45. package/dist/esm/adapters/postgres/index.js +13 -0
  46. package/dist/esm/adapters/postgres/key-store.js +191 -0
  47. package/dist/esm/adapters/postgres/method-store.js +61 -0
  48. package/dist/esm/adapters/postgres/migrations.js +111 -0
  49. package/dist/esm/adapters/postgres/passkey-credential-store.js +80 -0
  50. package/dist/esm/adapters/postgres/session-store.js +135 -0
  51. package/dist/esm/adapters/postgres/token-store.js +151 -0
  52. package/dist/esm/client.js +26 -10
  53. package/dist/esm/domain/audit.js +13 -0
  54. package/dist/esm/domain/authorize.js +212 -0
  55. package/dist/esm/domain/callback.js +173 -0
  56. package/dist/esm/domain/client-auth.js +61 -0
  57. package/dist/esm/domain/client-credentials.js +91 -0
  58. package/dist/esm/domain/crypto.js +97 -0
  59. package/dist/esm/domain/discovery.js +41 -0
  60. package/dist/esm/domain/introspect.js +51 -0
  61. package/dist/esm/domain/jwt.js +43 -0
  62. package/dist/esm/domain/method-cache.js +136 -0
  63. package/dist/esm/domain/method-dispatch.js +36 -0
  64. package/dist/esm/domain/method-route.js +108 -0
  65. package/dist/esm/domain/pkce.js +16 -0
  66. package/dist/esm/domain/refresh.js +91 -0
  67. package/dist/esm/domain/revoke.js +81 -0
  68. package/dist/esm/domain/state-envelope.js +80 -0
  69. package/dist/esm/domain/token-exchange.js +101 -0
  70. package/dist/esm/domain/token.js +205 -0
  71. package/dist/esm/domain/userinfo.js +27 -0
  72. package/dist/esm/http/context.js +7 -0
  73. package/dist/esm/http/cookies.js +98 -0
  74. package/dist/esm/http/errors.js +71 -0
  75. package/dist/esm/http/handlers/authorize.js +163 -0
  76. package/dist/esm/http/handlers/callback.js +53 -0
  77. package/dist/esm/http/handlers/metadata.js +34 -0
  78. package/dist/esm/http/handlers/method-route.js +87 -0
  79. package/dist/esm/http/handlers/revocation.js +98 -0
  80. package/dist/esm/http/handlers/token.js +167 -0
  81. package/dist/esm/http/handlers/userinfo.js +54 -0
  82. package/dist/esm/http/middleware/error.js +15 -0
  83. package/dist/esm/http/middleware/tenant.js +102 -0
  84. package/dist/esm/http/resolver.js +15 -0
  85. package/dist/esm/http/router.js +35 -0
  86. package/dist/esm/http/schemas/authorize.js +24 -0
  87. package/dist/esm/http/schemas/revocation.js +18 -0
  88. package/dist/esm/http/schemas/token.js +57 -0
  89. package/dist/esm/index.js +121 -9
  90. package/dist/esm/methods/code.js +221 -0
  91. package/dist/esm/methods/m2m.js +41 -0
  92. package/dist/esm/methods/oauth2-factory.js +84 -0
  93. package/dist/esm/methods/oauth2-generic.js +196 -0
  94. package/dist/esm/methods/oidc-generic.js +53 -0
  95. package/dist/esm/methods/passkey.js +275 -0
  96. package/dist/esm/methods/password-hash.js +79 -0
  97. package/dist/esm/methods/password.js +166 -0
  98. package/dist/esm/methods/providers/apple.js +29 -0
  99. package/dist/esm/methods/providers/cognito.js +37 -0
  100. package/dist/esm/methods/providers/discord.js +28 -0
  101. package/dist/esm/methods/providers/facebook.js +42 -0
  102. package/dist/esm/methods/providers/github.js +29 -0
  103. package/dist/esm/methods/providers/google.js +29 -0
  104. package/dist/esm/methods/providers/index.js +17 -0
  105. package/dist/esm/methods/providers/jumpcloud.js +27 -0
  106. package/dist/esm/methods/providers/keycloak.js +32 -0
  107. package/dist/esm/methods/providers/linkedin.js +28 -0
  108. package/dist/esm/methods/providers/microsoft.js +28 -0
  109. package/dist/esm/methods/providers/slack.js +30 -0
  110. package/dist/esm/methods/providers/spotify.js +28 -0
  111. package/dist/esm/methods/providers/twitch.js +28 -0
  112. package/dist/esm/methods/providers/x.js +28 -0
  113. package/dist/esm/methods/providers/yahoo.js +27 -0
  114. package/dist/esm/pkce.js +4 -16
  115. package/dist/esm/ports/audit-log.js +0 -0
  116. package/dist/esm/ports/config-store.js +0 -0
  117. package/dist/esm/ports/key-store.js +0 -0
  118. package/dist/esm/ports/method-store.js +0 -0
  119. package/dist/esm/ports/session-store.js +0 -0
  120. package/dist/esm/ports/token-store.js +0 -0
  121. package/dist/esm/types/authorization.js +0 -0
  122. package/dist/esm/types/error.js +65 -0
  123. package/dist/esm/types/flow.js +0 -0
  124. package/dist/esm/types/idp.js +0 -0
  125. package/dist/esm/types/method.js +0 -0
  126. package/dist/esm/types/picker.js +0 -0
  127. package/dist/esm/types/result.js +11 -0
  128. package/dist/esm/types/subject.js +0 -0
  129. package/dist/esm/types/tenant.js +5 -0
  130. package/dist/esm/types/token.js +0 -0
  131. package/dist/esm/ui/forms.js +253 -0
  132. package/dist/esm/ui/index.js +8 -0
  133. package/dist/esm/ui/picker.js +49 -0
  134. package/dist/types/adapters/d1/audit-log.d.ts +19 -0
  135. package/dist/types/adapters/d1/audit-log.d.ts.map +1 -0
  136. package/dist/types/adapters/d1/config-store.d.ts +20 -0
  137. package/dist/types/adapters/d1/config-store.d.ts.map +1 -0
  138. package/dist/types/adapters/d1/index.d.ts +35 -0
  139. package/dist/types/adapters/d1/index.d.ts.map +1 -0
  140. package/dist/types/adapters/d1/key-store.d.ts +17 -0
  141. package/dist/types/adapters/d1/key-store.d.ts.map +1 -0
  142. package/dist/types/adapters/d1/method-store.d.ts +21 -0
  143. package/dist/types/adapters/d1/method-store.d.ts.map +1 -0
  144. package/dist/types/adapters/d1/migrations.d.ts +18 -0
  145. package/dist/types/adapters/d1/migrations.d.ts.map +1 -0
  146. package/dist/types/adapters/d1/session-store.d.ts +20 -0
  147. package/dist/types/adapters/d1/session-store.d.ts.map +1 -0
  148. package/dist/types/adapters/d1/session.d.ts +18 -0
  149. package/dist/types/adapters/d1/session.d.ts.map +1 -0
  150. package/dist/types/adapters/d1/token-store.d.ts +29 -0
  151. package/dist/types/adapters/d1/token-store.d.ts.map +1 -0
  152. package/dist/types/adapters/d1/types.d.ts +43 -0
  153. package/dist/types/adapters/d1/types.d.ts.map +1 -0
  154. package/dist/types/adapters/durable-object/index.d.ts +17 -0
  155. package/dist/types/adapters/durable-object/index.d.ts.map +1 -0
  156. package/dist/types/adapters/durable-object/session-store.d.ts +21 -0
  157. package/dist/types/adapters/durable-object/session-store.d.ts.map +1 -0
  158. package/dist/types/adapters/durable-object/types.d.ts +23 -0
  159. package/dist/types/adapters/durable-object/types.d.ts.map +1 -0
  160. package/dist/types/adapters/dynamo/audit-log.d.ts +19 -0
  161. package/dist/types/adapters/dynamo/audit-log.d.ts.map +1 -0
  162. package/dist/types/adapters/dynamo/client.d.ts +111 -0
  163. package/dist/types/adapters/dynamo/client.d.ts.map +1 -0
  164. package/dist/types/adapters/dynamo/config-store.d.ts +21 -0
  165. package/dist/types/adapters/dynamo/config-store.d.ts.map +1 -0
  166. package/dist/types/adapters/dynamo/index.d.ts +60 -0
  167. package/dist/types/adapters/dynamo/index.d.ts.map +1 -0
  168. package/dist/types/adapters/dynamo/key-store.d.ts +23 -0
  169. package/dist/types/adapters/dynamo/key-store.d.ts.map +1 -0
  170. package/dist/types/adapters/dynamo/method-store.d.ts +20 -0
  171. package/dist/types/adapters/dynamo/method-store.d.ts.map +1 -0
  172. package/dist/types/adapters/dynamo/passkey-credential-store.d.ts +47 -0
  173. package/dist/types/adapters/dynamo/passkey-credential-store.d.ts.map +1 -0
  174. package/dist/types/adapters/dynamo/sdk.d.ts +23 -0
  175. package/dist/types/adapters/dynamo/sdk.d.ts.map +1 -0
  176. package/dist/types/adapters/dynamo/session-store.d.ts +20 -0
  177. package/dist/types/adapters/dynamo/session-store.d.ts.map +1 -0
  178. package/dist/types/adapters/dynamo/token-store.d.ts +29 -0
  179. package/dist/types/adapters/dynamo/token-store.d.ts.map +1 -0
  180. package/dist/types/adapters/kms/in-memory-backing.d.ts +9 -0
  181. package/dist/types/adapters/kms/in-memory-backing.d.ts.map +1 -0
  182. package/dist/types/adapters/kms/index.d.ts +35 -0
  183. package/dist/types/adapters/kms/index.d.ts.map +1 -0
  184. package/dist/types/adapters/kms/key-store.d.ts +20 -0
  185. package/dist/types/adapters/kms/key-store.d.ts.map +1 -0
  186. package/dist/types/adapters/kms/sdk.d.ts +16 -0
  187. package/dist/types/adapters/kms/sdk.d.ts.map +1 -0
  188. package/dist/types/adapters/kms/types.d.ts +58 -0
  189. package/dist/types/adapters/kms/types.d.ts.map +1 -0
  190. package/dist/types/adapters/kv/audit-log.d.ts +22 -0
  191. package/dist/types/adapters/kv/audit-log.d.ts.map +1 -0
  192. package/dist/types/adapters/kv/config-store.d.ts +25 -0
  193. package/dist/types/adapters/kv/config-store.d.ts.map +1 -0
  194. package/dist/types/adapters/kv/index.d.ts +21 -0
  195. package/dist/types/adapters/kv/index.d.ts.map +1 -0
  196. package/dist/types/adapters/kv/method-store.d.ts +23 -0
  197. package/dist/types/adapters/kv/method-store.d.ts.map +1 -0
  198. package/dist/types/adapters/kv/types.d.ts +28 -0
  199. package/dist/types/adapters/kv/types.d.ts.map +1 -0
  200. package/dist/types/adapters/memory/audit-log.d.ts +18 -0
  201. package/dist/types/adapters/memory/audit-log.d.ts.map +1 -0
  202. package/dist/types/adapters/memory/clock.d.ts +7 -0
  203. package/dist/types/adapters/memory/clock.d.ts.map +1 -0
  204. package/dist/types/adapters/memory/config-store.d.ts +19 -0
  205. package/dist/types/adapters/memory/config-store.d.ts.map +1 -0
  206. package/dist/types/adapters/memory/index.d.ts +15 -0
  207. package/dist/types/adapters/memory/index.d.ts.map +1 -0
  208. package/dist/types/adapters/memory/key-store.d.ts +17 -0
  209. package/dist/types/adapters/memory/key-store.d.ts.map +1 -0
  210. package/dist/types/adapters/memory/method-store.d.ts +26 -0
  211. package/dist/types/adapters/memory/method-store.d.ts.map +1 -0
  212. package/dist/types/adapters/memory/passkey-credential-store.d.ts +43 -0
  213. package/dist/types/adapters/memory/passkey-credential-store.d.ts.map +1 -0
  214. package/dist/types/adapters/memory/session-store.d.ts +19 -0
  215. package/dist/types/adapters/memory/session-store.d.ts.map +1 -0
  216. package/dist/types/adapters/memory/token-store.d.ts +28 -0
  217. package/dist/types/adapters/memory/token-store.d.ts.map +1 -0
  218. package/dist/types/adapters/postgres/audit-log.d.ts +18 -0
  219. package/dist/types/adapters/postgres/audit-log.d.ts.map +1 -0
  220. package/dist/types/adapters/postgres/config-store.d.ts +21 -0
  221. package/dist/types/adapters/postgres/config-store.d.ts.map +1 -0
  222. package/dist/types/adapters/postgres/executor.d.ts +48 -0
  223. package/dist/types/adapters/postgres/executor.d.ts.map +1 -0
  224. package/dist/types/adapters/postgres/index.d.ts +39 -0
  225. package/dist/types/adapters/postgres/index.d.ts.map +1 -0
  226. package/dist/types/adapters/postgres/key-store.d.ts +23 -0
  227. package/dist/types/adapters/postgres/key-store.d.ts.map +1 -0
  228. package/dist/types/adapters/postgres/method-store.d.ts +21 -0
  229. package/dist/types/adapters/postgres/method-store.d.ts.map +1 -0
  230. package/dist/types/adapters/postgres/migrations.d.ts +22 -0
  231. package/dist/types/adapters/postgres/migrations.d.ts.map +1 -0
  232. package/dist/types/adapters/postgres/passkey-credential-store.d.ts +48 -0
  233. package/dist/types/adapters/postgres/passkey-credential-store.d.ts.map +1 -0
  234. package/dist/types/adapters/postgres/session-store.d.ts +20 -0
  235. package/dist/types/adapters/postgres/session-store.d.ts.map +1 -0
  236. package/dist/types/adapters/postgres/token-store.d.ts +30 -0
  237. package/dist/types/adapters/postgres/token-store.d.ts.map +1 -0
  238. package/dist/types/client.d.ts +1 -1
  239. package/dist/types/client.d.ts.map +1 -1
  240. package/dist/types/domain/audit.d.ts +17 -0
  241. package/dist/types/domain/audit.d.ts.map +1 -0
  242. package/dist/types/domain/authorize.d.ts +125 -0
  243. package/dist/types/domain/authorize.d.ts.map +1 -0
  244. package/dist/types/domain/callback.d.ts +70 -0
  245. package/dist/types/domain/callback.d.ts.map +1 -0
  246. package/dist/types/domain/client-auth.d.ts +57 -0
  247. package/dist/types/domain/client-auth.d.ts.map +1 -0
  248. package/dist/types/domain/client-credentials.d.ts +52 -0
  249. package/dist/types/domain/client-credentials.d.ts.map +1 -0
  250. package/dist/types/domain/crypto.d.ts +53 -0
  251. package/dist/types/domain/crypto.d.ts.map +1 -0
  252. package/dist/types/domain/discovery.d.ts +44 -0
  253. package/dist/types/domain/discovery.d.ts.map +1 -0
  254. package/dist/types/domain/introspect.d.ts +71 -0
  255. package/dist/types/domain/introspect.d.ts.map +1 -0
  256. package/dist/types/domain/jwt.d.ts +48 -0
  257. package/dist/types/domain/jwt.d.ts.map +1 -0
  258. package/dist/types/domain/method-cache.d.ts +42 -0
  259. package/dist/types/domain/method-cache.d.ts.map +1 -0
  260. package/dist/types/domain/method-dispatch.d.ts +37 -0
  261. package/dist/types/domain/method-dispatch.d.ts.map +1 -0
  262. package/dist/types/domain/method-route.d.ts +77 -0
  263. package/dist/types/domain/method-route.d.ts.map +1 -0
  264. package/dist/types/domain/pkce.d.ts +15 -0
  265. package/dist/types/domain/pkce.d.ts.map +1 -0
  266. package/dist/types/domain/refresh.d.ts +43 -0
  267. package/dist/types/domain/refresh.d.ts.map +1 -0
  268. package/dist/types/domain/revoke.d.ts +61 -0
  269. package/dist/types/domain/revoke.d.ts.map +1 -0
  270. package/dist/types/domain/state-envelope.d.ts +30 -0
  271. package/dist/types/domain/state-envelope.d.ts.map +1 -0
  272. package/dist/types/domain/token-exchange.d.ts +67 -0
  273. package/dist/types/domain/token-exchange.d.ts.map +1 -0
  274. package/dist/types/domain/token.d.ts +100 -0
  275. package/dist/types/domain/token.d.ts.map +1 -0
  276. package/dist/types/domain/userinfo.d.ts +22 -0
  277. package/dist/types/domain/userinfo.d.ts.map +1 -0
  278. package/dist/types/http/context.d.ts +63 -0
  279. package/dist/types/http/context.d.ts.map +1 -0
  280. package/dist/types/http/cookies.d.ts +45 -0
  281. package/dist/types/http/cookies.d.ts.map +1 -0
  282. package/dist/types/http/errors.d.ts +36 -0
  283. package/dist/types/http/errors.d.ts.map +1 -0
  284. package/dist/types/http/handlers/authorize.d.ts +3 -0
  285. package/dist/types/http/handlers/authorize.d.ts.map +1 -0
  286. package/dist/types/http/handlers/callback.d.ts +3 -0
  287. package/dist/types/http/handlers/callback.d.ts.map +1 -0
  288. package/dist/types/http/handlers/metadata.d.ts +4 -0
  289. package/dist/types/http/handlers/metadata.d.ts.map +1 -0
  290. package/dist/types/http/handlers/method-route.d.ts +3 -0
  291. package/dist/types/http/handlers/method-route.d.ts.map +1 -0
  292. package/dist/types/http/handlers/revocation.d.ts +4 -0
  293. package/dist/types/http/handlers/revocation.d.ts.map +1 -0
  294. package/dist/types/http/handlers/token.d.ts +3 -0
  295. package/dist/types/http/handlers/token.d.ts.map +1 -0
  296. package/dist/types/http/handlers/userinfo.d.ts +3 -0
  297. package/dist/types/http/handlers/userinfo.d.ts.map +1 -0
  298. package/dist/types/http/middleware/error.d.ts +9 -0
  299. package/dist/types/http/middleware/error.d.ts.map +1 -0
  300. package/dist/types/http/middleware/tenant.d.ts +30 -0
  301. package/dist/types/http/middleware/tenant.d.ts.map +1 -0
  302. package/dist/types/http/resolver.d.ts +22 -0
  303. package/dist/types/http/resolver.d.ts.map +1 -0
  304. package/dist/types/http/router.d.ts +21 -0
  305. package/dist/types/http/router.d.ts.map +1 -0
  306. package/dist/types/http/schemas/authorize.d.ts +52 -0
  307. package/dist/types/http/schemas/authorize.d.ts.map +1 -0
  308. package/dist/types/http/schemas/revocation.d.ts +41 -0
  309. package/dist/types/http/schemas/revocation.d.ts.map +1 -0
  310. package/dist/types/http/schemas/token.d.ts +227 -0
  311. package/dist/types/http/schemas/token.d.ts.map +1 -0
  312. package/dist/types/index.d.ts +63 -13
  313. package/dist/types/index.d.ts.map +1 -1
  314. package/dist/types/methods/code.d.ts +96 -0
  315. package/dist/types/methods/code.d.ts.map +1 -0
  316. package/dist/types/methods/m2m.d.ts +41 -0
  317. package/dist/types/methods/m2m.d.ts.map +1 -0
  318. package/dist/types/methods/oauth2-factory.d.ts +133 -0
  319. package/dist/types/methods/oauth2-factory.d.ts.map +1 -0
  320. package/dist/types/methods/oauth2-generic.d.ts +75 -0
  321. package/dist/types/methods/oauth2-generic.d.ts.map +1 -0
  322. package/dist/types/methods/oidc-generic.d.ts +26 -0
  323. package/dist/types/methods/oidc-generic.d.ts.map +1 -0
  324. package/dist/types/methods/passkey.d.ts +106 -0
  325. package/dist/types/methods/passkey.d.ts.map +1 -0
  326. package/dist/types/methods/password-hash.d.ts +25 -0
  327. package/dist/types/methods/password-hash.d.ts.map +1 -0
  328. package/dist/types/methods/password.d.ts +70 -0
  329. package/dist/types/methods/password.d.ts.map +1 -0
  330. package/dist/types/methods/providers/apple.d.ts +29 -0
  331. package/dist/types/methods/providers/apple.d.ts.map +1 -0
  332. package/dist/types/methods/providers/cognito.d.ts +36 -0
  333. package/dist/types/methods/providers/cognito.d.ts.map +1 -0
  334. package/dist/types/methods/providers/discord.d.ts +22 -0
  335. package/dist/types/methods/providers/discord.d.ts.map +1 -0
  336. package/dist/types/methods/providers/facebook.d.ts +29 -0
  337. package/dist/types/methods/providers/facebook.d.ts.map +1 -0
  338. package/dist/types/methods/providers/github.d.ts +25 -0
  339. package/dist/types/methods/providers/github.d.ts.map +1 -0
  340. package/dist/types/methods/providers/google.d.ts +28 -0
  341. package/dist/types/methods/providers/google.d.ts.map +1 -0
  342. package/dist/types/methods/providers/index.d.ts +24 -0
  343. package/dist/types/methods/providers/index.d.ts.map +1 -0
  344. package/dist/types/methods/providers/jumpcloud.d.ts +22 -0
  345. package/dist/types/methods/providers/jumpcloud.d.ts.map +1 -0
  346. package/dist/types/methods/providers/keycloak.d.ts +30 -0
  347. package/dist/types/methods/providers/keycloak.d.ts.map +1 -0
  348. package/dist/types/methods/providers/linkedin.d.ts +22 -0
  349. package/dist/types/methods/providers/linkedin.d.ts.map +1 -0
  350. package/dist/types/methods/providers/microsoft.d.ts +30 -0
  351. package/dist/types/methods/providers/microsoft.d.ts.map +1 -0
  352. package/dist/types/methods/providers/slack.d.ts +25 -0
  353. package/dist/types/methods/providers/slack.d.ts.map +1 -0
  354. package/dist/types/methods/providers/spotify.d.ts +22 -0
  355. package/dist/types/methods/providers/spotify.d.ts.map +1 -0
  356. package/dist/types/methods/providers/twitch.d.ts +23 -0
  357. package/dist/types/methods/providers/twitch.d.ts.map +1 -0
  358. package/dist/types/methods/providers/x.d.ts +25 -0
  359. package/dist/types/methods/providers/x.d.ts.map +1 -0
  360. package/dist/types/methods/providers/yahoo.d.ts +22 -0
  361. package/dist/types/methods/providers/yahoo.d.ts.map +1 -0
  362. package/dist/types/pkce.d.ts +1 -2
  363. package/dist/types/pkce.d.ts.map +1 -1
  364. package/dist/types/ports/audit-log.d.ts +139 -0
  365. package/dist/types/ports/audit-log.d.ts.map +1 -0
  366. package/dist/types/ports/config-store.d.ts +32 -0
  367. package/dist/types/ports/config-store.d.ts.map +1 -0
  368. package/dist/types/ports/key-store.d.ts +98 -0
  369. package/dist/types/ports/key-store.d.ts.map +1 -0
  370. package/dist/types/ports/method-store.d.ts +29 -0
  371. package/dist/types/ports/method-store.d.ts.map +1 -0
  372. package/dist/types/ports/session-store.d.ts +82 -0
  373. package/dist/types/ports/session-store.d.ts.map +1 -0
  374. package/dist/types/ports/token-store.d.ts +93 -0
  375. package/dist/types/ports/token-store.d.ts.map +1 -0
  376. package/dist/types/types/authorization.d.ts +52 -0
  377. package/dist/types/types/authorization.d.ts.map +1 -0
  378. package/dist/types/types/error.d.ts +92 -0
  379. package/dist/types/types/error.d.ts.map +1 -0
  380. package/dist/types/types/flow.d.ts +87 -0
  381. package/dist/types/types/flow.d.ts.map +1 -0
  382. package/dist/types/types/idp.d.ts +234 -0
  383. package/dist/types/types/idp.d.ts.map +1 -0
  384. package/dist/types/types/method.d.ts +256 -0
  385. package/dist/types/types/method.d.ts.map +1 -0
  386. package/dist/types/types/picker.d.ts +31 -0
  387. package/dist/types/types/picker.d.ts.map +1 -0
  388. package/dist/types/types/result.d.ts +29 -0
  389. package/dist/types/types/result.d.ts.map +1 -0
  390. package/dist/types/types/subject.d.ts +47 -0
  391. package/dist/types/types/subject.d.ts.map +1 -0
  392. package/dist/types/types/tenant.d.ts +239 -0
  393. package/dist/types/types/tenant.d.ts.map +1 -0
  394. package/dist/types/types/token.d.ts +103 -0
  395. package/dist/types/types/token.d.ts.map +1 -0
  396. package/dist/types/ui/forms.d.ts +35 -0
  397. package/dist/types/ui/forms.d.ts.map +1 -0
  398. package/dist/types/ui/index.d.ts +14 -0
  399. package/dist/types/ui/index.d.ts.map +1 -0
  400. package/dist/types/ui/picker.d.ts +18 -0
  401. package/dist/types/ui/picker.d.ts.map +1 -0
  402. package/package.json +41 -6
  403. package/src/adapters/d1/audit-log.ts +32 -0
  404. package/src/adapters/d1/config-store.ts +67 -0
  405. package/src/adapters/d1/index.ts +41 -0
  406. package/src/adapters/d1/key-store.ts +218 -0
  407. package/src/adapters/d1/method-store.ts +103 -0
  408. package/src/adapters/d1/migrations.ts +89 -0
  409. package/src/adapters/d1/session-store.ts +230 -0
  410. package/src/adapters/d1/session.ts +38 -0
  411. package/src/adapters/d1/token-store.ts +251 -0
  412. package/src/adapters/d1/types.ts +53 -0
  413. package/src/adapters/durable-object/index.ts +23 -0
  414. package/src/adapters/durable-object/session-store.ts +227 -0
  415. package/src/adapters/durable-object/types.ts +25 -0
  416. package/src/adapters/dynamo/audit-log.ts +35 -0
  417. package/src/adapters/dynamo/client.ts +118 -0
  418. package/src/adapters/dynamo/config-store.ts +72 -0
  419. package/src/adapters/dynamo/index.ts +81 -0
  420. package/src/adapters/dynamo/key-store.ts +267 -0
  421. package/src/adapters/dynamo/method-store.ts +105 -0
  422. package/src/adapters/dynamo/passkey-credential-store.ts +132 -0
  423. package/src/adapters/dynamo/sdk.ts +189 -0
  424. package/src/adapters/dynamo/session-store.ts +229 -0
  425. package/src/adapters/dynamo/token-store.ts +248 -0
  426. package/src/adapters/kms/in-memory-backing.ts +36 -0
  427. package/src/adapters/kms/index.ts +39 -0
  428. package/src/adapters/kms/key-store.ts +176 -0
  429. package/src/adapters/kms/sdk.ts +39 -0
  430. package/src/adapters/kms/types.ts +61 -0
  431. package/src/adapters/kv/audit-log.ts +40 -0
  432. package/src/adapters/kv/config-store.ts +63 -0
  433. package/src/adapters/kv/index.ts +20 -0
  434. package/src/adapters/kv/method-store.ts +98 -0
  435. package/src/adapters/kv/types.ts +26 -0
  436. package/src/adapters/memory/audit-log.ts +28 -0
  437. package/src/adapters/memory/clock.ts +7 -0
  438. package/src/adapters/memory/config-store.ts +41 -0
  439. package/src/adapters/memory/index.ts +26 -0
  440. package/src/adapters/memory/key-store.ts +102 -0
  441. package/src/adapters/memory/method-store.ts +75 -0
  442. package/src/adapters/memory/passkey-credential-store.ts +97 -0
  443. package/src/adapters/memory/session-store.ts +128 -0
  444. package/src/adapters/memory/token-store.ts +165 -0
  445. package/src/adapters/postgres/audit-log.ts +34 -0
  446. package/src/adapters/postgres/config-store.ts +72 -0
  447. package/src/adapters/postgres/executor.ts +76 -0
  448. package/src/adapters/postgres/index.ts +60 -0
  449. package/src/adapters/postgres/key-store.ts +334 -0
  450. package/src/adapters/postgres/method-store.ts +104 -0
  451. package/src/adapters/postgres/migrations.ts +131 -0
  452. package/src/adapters/postgres/passkey-credential-store.ts +150 -0
  453. package/src/adapters/postgres/session-store.ts +216 -0
  454. package/src/adapters/postgres/token-store.ts +263 -0
  455. package/src/client.ts +50 -16
  456. package/src/domain/audit.ts +28 -0
  457. package/src/domain/authorize.ts +405 -0
  458. package/src/domain/callback.ts +263 -0
  459. package/src/domain/client-auth.ts +116 -0
  460. package/src/domain/client-credentials.ts +178 -0
  461. package/src/domain/crypto.ts +168 -0
  462. package/src/domain/discovery.ts +83 -0
  463. package/src/domain/introspect.ts +145 -0
  464. package/src/domain/jwt.ts +106 -0
  465. package/src/domain/method-cache.ts +220 -0
  466. package/src/domain/method-dispatch.ts +95 -0
  467. package/src/domain/method-route.ts +208 -0
  468. package/src/domain/pkce.ts +37 -0
  469. package/src/domain/refresh.ts +169 -0
  470. package/src/domain/revoke.ts +161 -0
  471. package/src/domain/state-envelope.ts +142 -0
  472. package/src/domain/token-exchange.ts +232 -0
  473. package/src/domain/token.ts +357 -0
  474. package/src/domain/userinfo.ts +56 -0
  475. package/src/http/context.ts +86 -0
  476. package/src/http/cookies.ts +167 -0
  477. package/src/http/errors.ts +98 -0
  478. package/src/http/handlers/authorize.ts +221 -0
  479. package/src/http/handlers/callback.ts +77 -0
  480. package/src/http/handlers/metadata.ts +44 -0
  481. package/src/http/handlers/method-route.ts +115 -0
  482. package/src/http/handlers/revocation.ts +151 -0
  483. package/src/http/handlers/token.ts +244 -0
  484. package/src/http/handlers/userinfo.ts +66 -0
  485. package/src/http/middleware/error.ts +26 -0
  486. package/src/http/middleware/tenant.ts +165 -0
  487. package/src/http/resolver.ts +34 -0
  488. package/src/http/router.ts +66 -0
  489. package/src/http/schemas/authorize.ts +58 -0
  490. package/src/http/schemas/revocation.ts +23 -0
  491. package/src/http/schemas/token.ts +85 -0
  492. package/src/index.ts +266 -20
  493. package/src/methods/code.ts +359 -0
  494. package/src/methods/m2m.ts +82 -0
  495. package/src/methods/oauth2-factory.ts +151 -0
  496. package/src/methods/oauth2-generic.ts +360 -0
  497. package/src/methods/oidc-generic.ts +109 -0
  498. package/src/methods/passkey.ts +419 -0
  499. package/src/methods/password-hash.ts +134 -0
  500. package/src/methods/password.ts +298 -0
  501. package/src/methods/providers/apple.ts +42 -0
  502. package/src/methods/providers/cognito.ts +55 -0
  503. package/src/methods/providers/discord.ts +37 -0
  504. package/src/methods/providers/facebook.ts +54 -0
  505. package/src/methods/providers/github.ts +42 -0
  506. package/src/methods/providers/google.ts +43 -0
  507. package/src/methods/providers/index.ts +23 -0
  508. package/src/methods/providers/jumpcloud.ts +36 -0
  509. package/src/methods/providers/keycloak.ts +42 -0
  510. package/src/methods/providers/linkedin.ts +37 -0
  511. package/src/methods/providers/microsoft.ts +42 -0
  512. package/src/methods/providers/slack.ts +42 -0
  513. package/src/methods/providers/spotify.ts +37 -0
  514. package/src/methods/providers/twitch.ts +38 -0
  515. package/src/methods/providers/x.ts +40 -0
  516. package/src/methods/providers/yahoo.ts +36 -0
  517. package/src/pkce.ts +20 -21
  518. package/src/ports/CONSISTENCY.md +88 -0
  519. package/src/ports/audit-log.ts +148 -0
  520. package/src/ports/config-store.ts +34 -0
  521. package/src/ports/key-store.ts +106 -0
  522. package/src/ports/method-store.ts +41 -0
  523. package/src/ports/session-store.ts +98 -0
  524. package/src/ports/token-store.ts +103 -0
  525. package/src/types/authorization.ts +53 -0
  526. package/src/types/error.ts +123 -0
  527. package/src/types/flow.ts +89 -0
  528. package/src/types/idp.ts +269 -0
  529. package/src/types/method.ts +270 -0
  530. package/src/types/picker.ts +32 -0
  531. package/src/types/result.ts +23 -0
  532. package/src/types/subject.ts +50 -0
  533. package/src/types/tenant.ts +248 -0
  534. package/src/types/token.ts +101 -0
  535. package/src/ui/forms.ts +289 -0
  536. package/src/ui/index.ts +14 -0
  537. package/src/ui/picker.ts +63 -0
  538. package/dist/esm/issuer.js +0 -691
  539. package/dist/esm/jwt.js +0 -16
  540. package/dist/esm/keys.js +0 -113
  541. package/dist/esm/provider/apple.js +0 -28
  542. package/dist/esm/provider/arctic.js +0 -43
  543. package/dist/esm/provider/code.js +0 -58
  544. package/dist/esm/provider/cognito.js +0 -16
  545. package/dist/esm/provider/discord.js +0 -15
  546. package/dist/esm/provider/facebook.js +0 -24
  547. package/dist/esm/provider/github.js +0 -15
  548. package/dist/esm/provider/google.js +0 -25
  549. package/dist/esm/provider/index.js +0 -3
  550. package/dist/esm/provider/jumpcloud.js +0 -15
  551. package/dist/esm/provider/keycloak.js +0 -15
  552. package/dist/esm/provider/linkedin.js +0 -15
  553. package/dist/esm/provider/m2m.js +0 -16
  554. package/dist/esm/provider/microsoft.js +0 -24
  555. package/dist/esm/provider/oauth2.js +0 -119
  556. package/dist/esm/provider/oidc.js +0 -69
  557. package/dist/esm/provider/passkey.js +0 -315
  558. package/dist/esm/provider/password.js +0 -306
  559. package/dist/esm/provider/provider.js +0 -10
  560. package/dist/esm/provider/slack.js +0 -15
  561. package/dist/esm/provider/spotify.js +0 -15
  562. package/dist/esm/provider/twitch.js +0 -15
  563. package/dist/esm/provider/x.js +0 -16
  564. package/dist/esm/provider/yahoo.js +0 -15
  565. package/dist/esm/random.js +0 -27
  566. package/dist/esm/storage/aws.js +0 -39
  567. package/dist/esm/storage/cloudflare.js +0 -42
  568. package/dist/esm/storage/dynamo.js +0 -116
  569. package/dist/esm/storage/memory.js +0 -88
  570. package/dist/esm/storage/storage.js +0 -36
  571. package/dist/esm/subject.js +0 -7
  572. package/dist/esm/ui/base.js +0 -407
  573. package/dist/esm/ui/code.js +0 -151
  574. package/dist/esm/ui/form.js +0 -43
  575. package/dist/esm/ui/icon.js +0 -92
  576. package/dist/esm/ui/passkey.js +0 -329
  577. package/dist/esm/ui/password.js +0 -338
  578. package/dist/esm/ui/select.js +0 -187
  579. package/dist/esm/ui/theme.js +0 -115
  580. package/dist/esm/util.js +0 -54
  581. package/dist/types/issuer.d.ts +0 -607
  582. package/dist/types/issuer.d.ts.map +0 -1
  583. package/dist/types/jwt.d.ts +0 -6
  584. package/dist/types/jwt.d.ts.map +0 -1
  585. package/dist/types/keys.d.ts +0 -18
  586. package/dist/types/keys.d.ts.map +0 -1
  587. package/dist/types/provider/apple.d.ts +0 -108
  588. package/dist/types/provider/apple.d.ts.map +0 -1
  589. package/dist/types/provider/arctic.d.ts +0 -16
  590. package/dist/types/provider/arctic.d.ts.map +0 -1
  591. package/dist/types/provider/code.d.ts +0 -74
  592. package/dist/types/provider/code.d.ts.map +0 -1
  593. package/dist/types/provider/cognito.d.ts +0 -64
  594. package/dist/types/provider/cognito.d.ts.map +0 -1
  595. package/dist/types/provider/discord.d.ts +0 -38
  596. package/dist/types/provider/discord.d.ts.map +0 -1
  597. package/dist/types/provider/facebook.d.ts +0 -74
  598. package/dist/types/provider/facebook.d.ts.map +0 -1
  599. package/dist/types/provider/github.d.ts +0 -38
  600. package/dist/types/provider/github.d.ts.map +0 -1
  601. package/dist/types/provider/google.d.ts +0 -74
  602. package/dist/types/provider/google.d.ts.map +0 -1
  603. package/dist/types/provider/index.d.ts +0 -4
  604. package/dist/types/provider/index.d.ts.map +0 -1
  605. package/dist/types/provider/jumpcloud.d.ts +0 -38
  606. package/dist/types/provider/jumpcloud.d.ts.map +0 -1
  607. package/dist/types/provider/keycloak.d.ts +0 -67
  608. package/dist/types/provider/keycloak.d.ts.map +0 -1
  609. package/dist/types/provider/linkedin.d.ts +0 -6
  610. package/dist/types/provider/linkedin.d.ts.map +0 -1
  611. package/dist/types/provider/m2m.d.ts +0 -34
  612. package/dist/types/provider/m2m.d.ts.map +0 -1
  613. package/dist/types/provider/microsoft.d.ts +0 -89
  614. package/dist/types/provider/microsoft.d.ts.map +0 -1
  615. package/dist/types/provider/oauth2.d.ts +0 -133
  616. package/dist/types/provider/oauth2.d.ts.map +0 -1
  617. package/dist/types/provider/oidc.d.ts +0 -91
  618. package/dist/types/provider/oidc.d.ts.map +0 -1
  619. package/dist/types/provider/passkey.d.ts +0 -143
  620. package/dist/types/provider/passkey.d.ts.map +0 -1
  621. package/dist/types/provider/password.d.ts +0 -210
  622. package/dist/types/provider/password.d.ts.map +0 -1
  623. package/dist/types/provider/provider.d.ts +0 -29
  624. package/dist/types/provider/provider.d.ts.map +0 -1
  625. package/dist/types/provider/slack.d.ts +0 -59
  626. package/dist/types/provider/slack.d.ts.map +0 -1
  627. package/dist/types/provider/spotify.d.ts +0 -38
  628. package/dist/types/provider/spotify.d.ts.map +0 -1
  629. package/dist/types/provider/twitch.d.ts +0 -38
  630. package/dist/types/provider/twitch.d.ts.map +0 -1
  631. package/dist/types/provider/x.d.ts +0 -38
  632. package/dist/types/provider/x.d.ts.map +0 -1
  633. package/dist/types/provider/yahoo.d.ts +0 -38
  634. package/dist/types/provider/yahoo.d.ts.map +0 -1
  635. package/dist/types/random.d.ts +0 -3
  636. package/dist/types/random.d.ts.map +0 -1
  637. package/dist/types/storage/aws.d.ts +0 -4
  638. package/dist/types/storage/aws.d.ts.map +0 -1
  639. package/dist/types/storage/cloudflare.d.ts +0 -34
  640. package/dist/types/storage/cloudflare.d.ts.map +0 -1
  641. package/dist/types/storage/dynamo.d.ts +0 -65
  642. package/dist/types/storage/dynamo.d.ts.map +0 -1
  643. package/dist/types/storage/memory.d.ts +0 -49
  644. package/dist/types/storage/memory.d.ts.map +0 -1
  645. package/dist/types/storage/storage.d.ts +0 -15
  646. package/dist/types/storage/storage.d.ts.map +0 -1
  647. package/dist/types/subject.d.ts +0 -122
  648. package/dist/types/subject.d.ts.map +0 -1
  649. package/dist/types/ui/base.d.ts +0 -5
  650. package/dist/types/ui/base.d.ts.map +0 -1
  651. package/dist/types/ui/code.d.ts +0 -104
  652. package/dist/types/ui/code.d.ts.map +0 -1
  653. package/dist/types/ui/form.d.ts +0 -6
  654. package/dist/types/ui/form.d.ts.map +0 -1
  655. package/dist/types/ui/icon.d.ts +0 -6
  656. package/dist/types/ui/icon.d.ts.map +0 -1
  657. package/dist/types/ui/passkey.d.ts +0 -5
  658. package/dist/types/ui/passkey.d.ts.map +0 -1
  659. package/dist/types/ui/password.d.ts +0 -139
  660. package/dist/types/ui/password.d.ts.map +0 -1
  661. package/dist/types/ui/select.d.ts +0 -55
  662. package/dist/types/ui/select.d.ts.map +0 -1
  663. package/dist/types/ui/theme.d.ts +0 -207
  664. package/dist/types/ui/theme.d.ts.map +0 -1
  665. package/dist/types/util.d.ts +0 -8
  666. package/dist/types/util.d.ts.map +0 -1
  667. package/src/css.d.ts +0 -4
  668. package/src/issuer.ts +0 -1628
  669. package/src/jwt.ts +0 -17
  670. package/src/keys.ts +0 -139
  671. package/src/provider/apple.ts +0 -127
  672. package/src/provider/arctic.ts +0 -66
  673. package/src/provider/code.ts +0 -227
  674. package/src/provider/cognito.ts +0 -74
  675. package/src/provider/discord.ts +0 -45
  676. package/src/provider/facebook.ts +0 -84
  677. package/src/provider/github.ts +0 -45
  678. package/src/provider/google.ts +0 -85
  679. package/src/provider/index.ts +0 -3
  680. package/src/provider/jumpcloud.ts +0 -45
  681. package/src/provider/keycloak.ts +0 -75
  682. package/src/provider/linkedin.ts +0 -12
  683. package/src/provider/m2m.ts +0 -56
  684. package/src/provider/microsoft.ts +0 -100
  685. package/src/provider/oauth2.ts +0 -297
  686. package/src/provider/oidc.ts +0 -179
  687. package/src/provider/passkey.ts +0 -655
  688. package/src/provider/password.ts +0 -672
  689. package/src/provider/provider.ts +0 -33
  690. package/src/provider/slack.ts +0 -67
  691. package/src/provider/spotify.ts +0 -45
  692. package/src/provider/twitch.ts +0 -45
  693. package/src/provider/x.ts +0 -46
  694. package/src/provider/yahoo.ts +0 -45
  695. package/src/random.ts +0 -24
  696. package/src/storage/aws.ts +0 -59
  697. package/src/storage/cloudflare.ts +0 -77
  698. package/src/storage/dynamo.ts +0 -193
  699. package/src/storage/memory.ts +0 -135
  700. package/src/storage/storage.ts +0 -46
  701. package/src/subject.ts +0 -130
  702. package/src/ui/base.tsx +0 -118
  703. package/src/ui/code.tsx +0 -215
  704. package/src/ui/form.tsx +0 -40
  705. package/src/ui/icon.tsx +0 -95
  706. package/src/ui/passkey.tsx +0 -321
  707. package/src/ui/password.tsx +0 -405
  708. package/src/ui/select.tsx +0 -221
  709. package/src/ui/theme.ts +0 -319
  710. package/src/ui/ui.css +0 -252
  711. package/src/util.ts +0 -58
  712. /package/dist/esm/{css.d.js → adapters/d1/types.js} +0 -0
@@ -0,0 +1,169 @@
1
+ /**
2
+ * `/token` endpoint logic for `grant_type=refresh_token`.
3
+ *
4
+ * Rotation semantics (plan §"Cross-cutting Decisions" + §"Port consistency
5
+ * contracts"):
6
+ *
7
+ * - Refresh tokens rotate on every use. The presented token is consumed
8
+ * (CAS); a fresh refresh token is issued, keyed by the same `family`.
9
+ * - If the same token is presented a second time within the reuse window
10
+ * (default 60 s), the family is revoked and the request is rejected
11
+ * with `invalid_grant`. Audit: `refresh_reuse_detected`.
12
+ * - Outside the reuse window the row should be GC'd; presentation
13
+ * returns `invalid_grant` ("unknown token").
14
+ */
15
+ import type { AuditLog } from "../ports/audit-log"
16
+ import type { ConfigStore } from "../ports/config-store"
17
+ import type { KeyStore } from "../ports/key-store"
18
+ import type { TokenStore } from "../ports/token-store"
19
+ import { authError, type AuthError } from "../types/error"
20
+ import type { Result } from "../types/result"
21
+ import { err, isErr } from "../types/result"
22
+ import type { TenantContext } from "../types/tenant"
23
+ import type { TokenResponse } from "../types/token"
24
+
25
+ import { safeAudit } from "./audit"
26
+ import { verifyClientCredentials } from "./client-auth"
27
+ import { mintTokens } from "./token"
28
+
29
+ export type RefreshGrantRequest = {
30
+ grantType: "refresh_token"
31
+ refreshToken: string
32
+ /** Optional per RFC 6749 — narrow scope. If absent, original scopes are reused. */
33
+ scope?: string
34
+ /** Confidential clients authenticate at /token. */
35
+ clientId?: string
36
+ clientSecret?: string
37
+ }
38
+
39
+ export type RefreshTokensDeps = {
40
+ configStore: ConfigStore
41
+ tokenStore: TokenStore
42
+ keyStore: KeyStore
43
+ auditLog?: AuditLog
44
+ issuerUrl: string
45
+ clock: () => number
46
+ /** Reuse-detection window (ms). Default 60 s. */
47
+ reuseWindowMs?: number
48
+ newRefreshToken?: () => string
49
+ }
50
+
51
+ export async function refreshTokens(
52
+ req: RefreshGrantRequest,
53
+ deps: RefreshTokensDeps,
54
+ ): Promise<Result<TokenResponse, AuthError>> {
55
+ // Peek before consuming so we can authenticate the client without
56
+ // burning the token on auth failures (RFC 6749 §6).
57
+ const peek = await deps.tokenStore.peekRefresh(req.refreshToken)
58
+ if (isErr(peek)) return err(peek.error)
59
+ const peekedPayload = peek.value
60
+
61
+ const tenantCfg = await deps.configStore.getTenantConfig(
62
+ peekedPayload.tenantId,
63
+ )
64
+ if (isErr(tenantCfg)) return err(tenantCfg.error)
65
+
66
+ // Authenticate the *presenting* client first, then check ownership.
67
+ // Wrong-secret, wrong-client, and unknown-presenting-client all collapse
68
+ // to the same `invalid_grant` response so an attacker holding a stolen
69
+ // refresh token cannot probe candidate `client_id` values: every
70
+ // misroute looks identical on the wire (RFC 6749 §5.2; H4 of the
71
+ // post-rebuild review).
72
+ const INVALID_REFRESH_DESC = "refresh token is invalid"
73
+ const client = tenantCfg.value.clients.find(
74
+ (c) => c.id === peekedPayload.clientId,
75
+ )
76
+ if (!client) {
77
+ return err(authError.invalidGrant(INVALID_REFRESH_DESC))
78
+ }
79
+
80
+ if (req.clientId !== undefined) {
81
+ const presenting = tenantCfg.value.clients.find(
82
+ (c) => c.id === req.clientId,
83
+ )
84
+ if (!presenting) {
85
+ return err(authError.invalidGrant(INVALID_REFRESH_DESC))
86
+ }
87
+ const authErr = await verifyClientCredentials(presenting, req.clientSecret)
88
+ if (authErr) return err(authError.invalidGrant(INVALID_REFRESH_DESC))
89
+ if (req.clientId !== peekedPayload.clientId) {
90
+ return err(authError.invalidGrant(INVALID_REFRESH_DESC))
91
+ }
92
+ } else if (client.type === "confidential") {
93
+ return err(authError.invalidGrant(INVALID_REFRESH_DESC))
94
+ }
95
+
96
+ const consumed = await deps.tokenStore.consumeRefresh(req.refreshToken, {
97
+ reuseWindowMs: deps.reuseWindowMs,
98
+ })
99
+ if (isErr(consumed)) {
100
+ if (consumed.error.code === "invalid_grant" && consumed.error.reuseSignal) {
101
+ // Reuse-detection signal arrives typed on the AuthError. tenantId /
102
+ // subjectId come from the signal when the adapter emits it; we fall
103
+ // back to the peeked payload's fields for adapters that don't
104
+ // (older 4th-party adapters that satisfy the port without the
105
+ // structured carrier). `family` always prefers the signal — it's
106
+ // the discriminating identifier the adapter knows.
107
+ const signal = consumed.error.reuseSignal
108
+ await safeAudit(deps, {
109
+ kind: "refresh_reuse_detected",
110
+ tenantId: peekedPayload.tenantId,
111
+ clientId: peekedPayload.clientId,
112
+ family: signal.family,
113
+ timestamp: deps.clock(),
114
+ })
115
+ }
116
+ return err(consumed.error)
117
+ }
118
+ const payload = consumed.value
119
+
120
+ // Narrow scope if requested (must be subset of original).
121
+ const requestedScopes = req.scope
122
+ ? req.scope.split(" ").filter(Boolean)
123
+ : payload.scopes
124
+ for (const s of requestedScopes) {
125
+ if (!payload.scopes.includes(s)) {
126
+ return err(
127
+ authError.invalidScope(
128
+ `requested scope "${s}" not granted by original refresh token`,
129
+ ),
130
+ )
131
+ }
132
+ }
133
+
134
+ const tenant: TenantContext = {
135
+ id: payload.tenantId,
136
+ config: tenantCfg.value,
137
+ request: { raw: new Request("about:blank"), custom: {} },
138
+ }
139
+
140
+ return mintTokens({
141
+ tenant,
142
+ claim: payload.claim,
143
+ payload: {
144
+ tenantId: payload.tenantId,
145
+ clientId: payload.clientId,
146
+ // Carry original method provenance forward — descendant tokens
147
+ // surface the method that started the chain via `mid` / `mkind`,
148
+ // not the grant-type literal "refresh".
149
+ methodId: payload.methodId,
150
+ methodKind: payload.methodKind,
151
+ scopes: requestedScopes,
152
+ audience: payload.audience,
153
+ },
154
+ family: payload.family,
155
+ deps,
156
+ }).then(async (result) => {
157
+ if (result.ok) {
158
+ await safeAudit(deps, {
159
+ kind: "token_refreshed",
160
+ tenantId: payload.tenantId,
161
+ clientId: payload.clientId,
162
+ subjectId: payload.subjectId,
163
+ family: payload.family,
164
+ timestamp: deps.clock(),
165
+ })
166
+ }
167
+ return result
168
+ })
169
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * `/revoke` endpoint logic (RFC 7009).
3
+ *
4
+ * Access tokens are stateless JWTs in this IdP; revocation has to wait
5
+ * for natural expiry. `tokenTypeHint: "access_token"` resolves to a
6
+ * no-op per RFC 7009 §2.2 (invalid hint is ignored).
7
+ *
8
+ * Refresh tokens are server-side state. RFC 7009 §2.2 requires the
9
+ * authorization server to verify that the presented token was issued to
10
+ * the authenticated client and refuse otherwise. We implement this via
11
+ * `peekRefresh` (non-destructive) → ownership check → `consumeRefresh`
12
+ * (destructive). The peek/consume race is benign: if a concurrent caller
13
+ * consumes the token in between, our consume returns `invalid_grant`,
14
+ * which RFC 7009 §2.2 says we treat as a successful no-op.
15
+ *
16
+ * Anonymous revoke (no `client_id`) is permitted only for tokens issued
17
+ * to public clients. Confidential-client tokens MUST authenticate per
18
+ * RFC 7009 §2.1.
19
+ */
20
+ import type { AuditLog } from "../ports/audit-log"
21
+ import type { ConfigStore } from "../ports/config-store"
22
+ import type { TokenStore } from "../ports/token-store"
23
+ import type { AuthError } from "../types/error"
24
+ import { authError } from "../types/error"
25
+ import type { Result } from "../types/result"
26
+ import { err, isErr, ok } from "../types/result"
27
+ import type { TenantId } from "../types/tenant"
28
+
29
+ import { safeAudit } from "./audit"
30
+ import { verifyClientCredentials } from "./client-auth"
31
+
32
+ export type RevokeRequest = {
33
+ token: string
34
+ /** RFC 7009 hint: `"access_token"` or `"refresh_token"`. */
35
+ tokenTypeHint?: "access_token" | "refresh_token"
36
+ /** Optional — required when revoking a confidential client's token. */
37
+ clientId?: string
38
+ clientSecret?: string
39
+ }
40
+
41
+ export type RevokeDeps = {
42
+ tokenStore: TokenStore
43
+ configStore: ConfigStore
44
+ auditLog?: AuditLog
45
+ clock: () => number
46
+ }
47
+
48
+ /**
49
+ * Revoke a single refresh token. Anonymous revokes are accepted for
50
+ * public-client tokens; confidential-client tokens require valid
51
+ * credentials.
52
+ */
53
+ export async function revokeToken(
54
+ req: RevokeRequest,
55
+ deps: RevokeDeps,
56
+ ): Promise<Result<void, AuthError>> {
57
+ if (req.tokenTypeHint === "access_token") {
58
+ return ok(undefined)
59
+ }
60
+
61
+ // RFC 7009 §2.2: unknown tokens are successful no-ops. Peek tells us
62
+ // the token's owner without consuming so we can authenticate first.
63
+ const peek = await deps.tokenStore.peekRefresh(req.token)
64
+ if (isErr(peek)) {
65
+ return ok(undefined)
66
+ }
67
+ const payload = peek.value
68
+
69
+ // Load the issuing client so we can decide whether auth is required.
70
+ const tenantCfg = await deps.configStore.getTenantConfig(payload.tenantId)
71
+ if (isErr(tenantCfg)) {
72
+ // Tenant evaporated. Treat as no-op — caller can't usefully retry.
73
+ return ok(undefined)
74
+ }
75
+ const client = tenantCfg.value.clients.find((c) => c.id === payload.clientId)
76
+ if (!client) {
77
+ return ok(undefined)
78
+ }
79
+
80
+ // Confidential clients MUST authenticate per RFC 7009 §2.1.
81
+ if (client.type === "confidential") {
82
+ if (!req.clientId || !req.clientSecret) {
83
+ return err(
84
+ authError.invalidClient(
85
+ "confidential client must authenticate to revoke its tokens",
86
+ ),
87
+ )
88
+ }
89
+ }
90
+
91
+ // If a client_id was presented but it doesn't own the token, RFC 7009
92
+ // §2.2 still wants a successful no-op response — surfacing
93
+ // `invalid_grant` here would let a different client probe for token
94
+ // existence. Audit the attempt so operators can spot abuse.
95
+ if (req.clientId !== undefined && req.clientId !== payload.clientId) {
96
+ await safeAudit(deps, {
97
+ kind: "custom",
98
+ type: "revoke_wrong_client_attempt",
99
+ tenantId: payload.tenantId,
100
+ data: {
101
+ presentedClientId: req.clientId,
102
+ tokenClientId: payload.clientId,
103
+ subjectId: payload.subjectId,
104
+ },
105
+ timestamp: deps.clock(),
106
+ })
107
+ return ok(undefined)
108
+ }
109
+
110
+ // Credentials presented by the owning client must validate. Anonymous
111
+ // revoke of a public-client token is permitted.
112
+ if (req.clientId !== undefined) {
113
+ const authErr = await verifyClientCredentials(client, req.clientSecret)
114
+ if (authErr) return err(authErr)
115
+ }
116
+
117
+ const consumed = await deps.tokenStore.consumeRefresh(req.token, {
118
+ reuseWindowMs: 0,
119
+ })
120
+ if (isErr(consumed)) {
121
+ return ok(undefined)
122
+ }
123
+ await safeAudit(deps, {
124
+ kind: "token_revoked",
125
+ tenantId: payload.tenantId,
126
+ clientId: payload.clientId,
127
+ subjectId: payload.subjectId,
128
+ family: payload.family,
129
+ reason: "client_revoke",
130
+ timestamp: deps.clock(),
131
+ })
132
+ return ok(undefined)
133
+ }
134
+
135
+ /**
136
+ * Revoke every refresh token for a `(tenantId, subjectId)` pair and emit
137
+ * a `token_revoked` audit event with `reason: "subject_revoke"`.
138
+ *
139
+ * Public primitive exposed for host-driven "log me out of all devices"
140
+ * flows. Not called from the reuse-detection path — that stays at
141
+ * family-scope per OAuth 2.0 Security BCP §4.13.2 (revoking sibling
142
+ * families would punish legitimate other-device sessions sharing the
143
+ * same subject). See ARCHITECTURE.md §"Reuse detection scope".
144
+ */
145
+ export async function revokeAllForSubject(
146
+ tenantId: TenantId,
147
+ subjectId: string,
148
+ deps: { tokenStore: TokenStore; auditLog?: AuditLog; clock: () => number },
149
+ ): Promise<Result<void, AuthError>> {
150
+ const res = await deps.tokenStore.revokeBySubject(tenantId, subjectId)
151
+ if (isErr(res)) return err(res.error)
152
+ await safeAudit(deps, {
153
+ kind: "token_revoked",
154
+ tenantId,
155
+ clientId: null,
156
+ subjectId,
157
+ reason: "subject_revoke",
158
+ timestamp: deps.clock(),
159
+ })
160
+ return ok(undefined)
161
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * MAC-signed `state` envelope used by the tenant-recovery chain.
3
+ *
4
+ * Format (intentionally compact, well under 256 base64url bytes):
5
+ *
6
+ * `<b64u(payloadJson)>.<b64u(hmacSha256(payloadJson, key[kid]))>`
7
+ *
8
+ * where `payloadJson` is canonical JSON over `{tenantId, flowId, nonce, kid}`.
9
+ *
10
+ * **Nothing sensitive is in the envelope** — only the minimal recovery
11
+ * tuple. Sensitive data (`clientPkce`, `appRedirectUri`, scopes, etc.)
12
+ * lives in the server-side `FlowRecord`, identified by `flowId`.
13
+ *
14
+ * See `ARCHITECTURE.md` §"Tenant recovery across redirects" and plan
15
+ * §"Crypto — HMAC-SHA-256".
16
+ */
17
+ import { authError, type AuthError } from "../types/error"
18
+ import type { Result } from "../types/result"
19
+ import { err, ok } from "../types/result"
20
+ import type {
21
+ StateEnvelope,
22
+ StateKey,
23
+ StateKeyRing,
24
+ TenantId,
25
+ } from "../types/tenant"
26
+ import { asTenantId } from "../types/tenant"
27
+
28
+ import { base64url, hmacSign, hmacVerify, importHmacKey, utf8 } from "./crypto"
29
+
30
+ /**
31
+ * Serialize envelope fields in a fixed order so the MAC input is
32
+ * deterministic regardless of object-key insertion order.
33
+ */
34
+ function canonicalize(env: StateEnvelope): string {
35
+ return JSON.stringify({
36
+ tenantId: env.tenantId,
37
+ flowId: env.flowId,
38
+ nonce: env.nonce,
39
+ kid: env.kid,
40
+ })
41
+ }
42
+
43
+ /** Mint a signed state string for the given envelope under the active key. */
44
+ export async function mintStateEnvelope(
45
+ envelope: Omit<StateEnvelope, "kid">,
46
+ keyRing: StateKeyRing,
47
+ ): Promise<string> {
48
+ const active = keyRing.active
49
+ const stamped: StateEnvelope = { ...envelope, kid: active.kid }
50
+ const payloadBytes = utf8.encode(canonicalize(stamped))
51
+ const key = await importHmacKey(active.key, ["sign"])
52
+ const sig = await hmacSign(key, payloadBytes)
53
+ return `${base64url.encode(payloadBytes)}.${base64url.encode(sig)}`
54
+ }
55
+
56
+ /**
57
+ * Verify and parse a state string. Returns `unknown_state` on malformed
58
+ * input, unknown `kid`, or signature mismatch.
59
+ *
60
+ * The MAC verification step uses `crypto.subtle.verify`, which is
61
+ * timing-safe by spec.
62
+ */
63
+ export async function verifyStateEnvelope(
64
+ state: string,
65
+ keyRing: StateKeyRing,
66
+ ): Promise<Result<StateEnvelope, AuthError>> {
67
+ if (typeof state !== "string" || !state.includes(".")) {
68
+ return err(authError.unknownState("malformed state"))
69
+ }
70
+ const dotIdx = state.indexOf(".")
71
+ const headerB64u = state.slice(0, dotIdx)
72
+ const sigB64u = state.slice(dotIdx + 1)
73
+ if (!headerB64u || !sigB64u || sigB64u.includes(".")) {
74
+ return err(authError.unknownState("malformed state"))
75
+ }
76
+
77
+ let payloadBytes: Uint8Array
78
+ let sigBytes: Uint8Array
79
+ try {
80
+ payloadBytes = base64url.decode(headerB64u)
81
+ sigBytes = base64url.decode(sigB64u)
82
+ } catch {
83
+ return err(authError.unknownState("malformed state encoding"))
84
+ }
85
+
86
+ let parsed: unknown
87
+ try {
88
+ parsed = JSON.parse(utf8.decode(payloadBytes))
89
+ } catch {
90
+ return err(authError.unknownState("malformed state payload"))
91
+ }
92
+ if (!isEnvelopeShape(parsed)) {
93
+ return err(authError.unknownState("state payload missing fields"))
94
+ }
95
+
96
+ const candidate = findKey(keyRing, parsed.kid)
97
+ if (!candidate) {
98
+ return err(authError.unknownState(`unknown state kid "${parsed.kid}"`))
99
+ }
100
+
101
+ const key = await importHmacKey(candidate.key, ["verify"])
102
+ const okSig = await hmacVerify(key, sigBytes, payloadBytes)
103
+ if (!okSig) {
104
+ return err(authError.unknownState("state signature mismatch"))
105
+ }
106
+
107
+ // Cross-check that the payload's `kid` matches the verifying key — guards
108
+ // against a malicious sender re-encoding the header to claim a different
109
+ // key was used while the MAC was actually computed under another.
110
+ if (parsed.kid !== candidate.kid) {
111
+ return err(authError.unknownState("state kid drift"))
112
+ }
113
+
114
+ return ok({
115
+ tenantId: asTenantId(parsed.tenantId),
116
+ flowId: parsed.flowId,
117
+ nonce: parsed.nonce,
118
+ kid: parsed.kid,
119
+ })
120
+ }
121
+
122
+ function findKey(ring: StateKeyRing, kid: string): StateKey | undefined {
123
+ return ring.verify.find((k) => k.kid === kid)
124
+ }
125
+
126
+ type EnvelopeShape = {
127
+ tenantId: TenantId & string
128
+ flowId: string
129
+ nonce: string
130
+ kid: string
131
+ }
132
+
133
+ function isEnvelopeShape(value: unknown): value is EnvelopeShape {
134
+ if (!value || typeof value !== "object") return false
135
+ const v = value as Record<string, unknown>
136
+ return (
137
+ typeof v.tenantId === "string" &&
138
+ typeof v.flowId === "string" &&
139
+ typeof v.nonce === "string" &&
140
+ typeof v.kid === "string"
141
+ )
142
+ }
@@ -0,0 +1,232 @@
1
+ /**
2
+ * `/token` logic for `grant_type=urn:ietf:params:oauth:grant-type:token-exchange`
3
+ * (RFC 8693).
4
+ *
5
+ * Scope: **audience-switch / impersonation only.** A subject who holds an
6
+ * access token for tenant A can present it here to receive a fresh
7
+ * access + refresh pair scoped to tenant B (the `audience` parameter).
8
+ * Delegation (`actor_token`) is rejected at the HTTP layer.
9
+ *
10
+ * Flow:
11
+ * 1. Verify the `subject_token` JWT against the IdP's own signing keys.
12
+ * Expired / wrong-issuer / bad-sig tokens fail with `invalid_grant`
13
+ * (per RFC 8693 §2.4).
14
+ * 2. Refuse if the host hasn't configured `exchangeAudience` — return
15
+ * `unsupported_grant_type` for graceful degradation.
16
+ * 3. Narrow scope if the caller requested a subset.
17
+ * 4. Call `exchangeAudience(currentClaim, request)`. The host returns
18
+ * either the SubjectClaim that should anchor the new tokens at the
19
+ * target tenant, or an `AuthError` (typically `invalid_target`).
20
+ * 5. Load the target tenant config via `ConfigStore.getTenantConfig`.
21
+ * This validates that the audience is a real partition.
22
+ * 6. Mint a fresh access + refresh pair with `tid = audience` and a
23
+ * brand-new refresh family. The previous family lives untouched.
24
+ * 7. Audit `token_exchanged`.
25
+ *
26
+ * Notes:
27
+ * - The new tokens' `clientId` is the **authenticated** client (the
28
+ * one doing the exchange), not the original audience of the
29
+ * subject_token. Hosts that need a different client must register
30
+ * one in the target tenant.
31
+ * - `methodId` / `methodKind` are preserved from the subject_token so
32
+ * the audit trail still names the original authentication method.
33
+ */
34
+ import type { AuditLog } from "../ports/audit-log"
35
+ import type { ConfigStore } from "../ports/config-store"
36
+ import type { KeyStore } from "../ports/key-store"
37
+ import type { TokenStore } from "../ports/token-store"
38
+ import { authError, type AuthError } from "../types/error"
39
+ import type { ExchangeAudience } from "../types/idp"
40
+ import type { Result } from "../types/result"
41
+ import { err, isErr, ok } from "../types/result"
42
+ import type { SubjectClaim } from "../types/subject"
43
+ import type { ClientConfig, TenantContext, TenantId } from "../types/tenant"
44
+ import type { AccessTokenClaims, TokenResponse } from "../types/token"
45
+
46
+ import { safeAudit } from "./audit"
47
+ import { verifyClientCredentials } from "./client-auth"
48
+ import { randomId } from "./crypto"
49
+ import { verifyAccessToken } from "./jwt"
50
+ import { mintTokens } from "./token"
51
+
52
+ export type TokenExchangeRequest = {
53
+ grantType: "urn:ietf:params:oauth:grant-type:token-exchange"
54
+ subjectToken: string
55
+ audience: string
56
+ /** Confidential clients authenticate. Public clients use the subject_token alone. */
57
+ clientId?: string
58
+ clientSecret?: string
59
+ /** Optional subset of subject_token scopes. */
60
+ scope?: string
61
+ }
62
+
63
+ export type TokenExchangeResponse = TokenResponse & {
64
+ /** RFC 8693 §2.2.1. Always `access_token` for this grant. */
65
+ issued_token_type: "urn:ietf:params:oauth:token-type:access_token"
66
+ }
67
+
68
+ export type ExchangeTokenDeps = {
69
+ configStore: ConfigStore
70
+ tokenStore: TokenStore
71
+ keyStore: KeyStore
72
+ auditLog?: AuditLog
73
+ exchangeAudience?: ExchangeAudience
74
+ issuerUrl: string
75
+ clock: () => number
76
+ newRefreshFamily?: () => string
77
+ }
78
+
79
+ export async function exchangeToken(
80
+ req: TokenExchangeRequest,
81
+ deps: ExchangeTokenDeps,
82
+ ): Promise<Result<TokenExchangeResponse, AuthError>> {
83
+ // 1. Graceful degradation: refuse cleanly if not configured.
84
+ if (!deps.exchangeAudience) {
85
+ return err(
86
+ authError.unsupportedGrantType(
87
+ "token-exchange is not configured on this IdP",
88
+ ),
89
+ )
90
+ }
91
+
92
+ // 2. Verify the subject_token.
93
+ const keysRes = await deps.keyStore.signingKeys()
94
+ if (isErr(keysRes)) return err(keysRes.error)
95
+ let subjectClaims: AccessTokenClaims
96
+ try {
97
+ subjectClaims = await verifyAccessToken(req.subjectToken, keysRes.value, {
98
+ issuer: deps.issuerUrl,
99
+ })
100
+ } catch (e) {
101
+ return err(
102
+ authError.invalidGrant(
103
+ `subject_token verification failed: ${
104
+ e instanceof Error ? e.message : "unknown"
105
+ }`,
106
+ ),
107
+ )
108
+ }
109
+ if (!subjectClaims.claim) {
110
+ return err(
111
+ authError.invalidGrant(
112
+ "subject_token does not carry a SubjectClaim — was it issued by this IdP?",
113
+ ),
114
+ )
115
+ }
116
+
117
+ // 3. Load the issuing tenant + client so we can authenticate the caller.
118
+ const fromTenantId = subjectClaims.tid
119
+ const fromTenantCfg = await deps.configStore.getTenantConfig(fromTenantId)
120
+ if (isErr(fromTenantCfg)) return err(fromTenantCfg.error)
121
+ // The client doing the exchange — defaults to the subject_token's `aud`
122
+ // when not presented (public-client case).
123
+ const callerClientId = req.clientId ?? subjectClaims.aud
124
+ const callerClient = fromTenantCfg.value.clients.find(
125
+ (c: ClientConfig) => c.id === callerClientId,
126
+ )
127
+ if (!callerClient) {
128
+ return err(authError.invalidClient(`unknown client "${callerClientId}"`))
129
+ }
130
+ // Same client-auth rules as auth_code / refresh: confidential clients
131
+ // MUST authenticate; public clients MUST NOT present a secret.
132
+ const authErr = await verifyClientCredentials(callerClient, req.clientSecret)
133
+ if (authErr) return err(authErr)
134
+
135
+ // 4. Scope subset check (RFC 8693 §2.1 — `scope` MAY be requested).
136
+ const subjectScopes = (subjectClaims.scope ?? "")
137
+ .split(" ")
138
+ .filter((s) => s.length > 0)
139
+ const requestedScopes = req.scope
140
+ ? req.scope.split(" ").filter((s) => s.length > 0)
141
+ : subjectScopes
142
+ for (const s of requestedScopes) {
143
+ if (!subjectScopes.includes(s)) {
144
+ return err(
145
+ authError.invalidScope(
146
+ `requested scope "${s}" not granted by subject_token`,
147
+ ),
148
+ )
149
+ }
150
+ }
151
+
152
+ // 5. Host decision — does the subject get to access `audience`?
153
+ const audienceClaim = await deps.exchangeAudience(subjectClaims.claim, {
154
+ audience: req.audience,
155
+ ...(req.scope !== undefined ? { requestedScopes } : {}),
156
+ clientId: callerClient.id,
157
+ fromTenantId,
158
+ })
159
+ // The hook may return either a SubjectClaim (success) or an
160
+ // AuthError (rejection). Distinguish by checking for the `code`
161
+ // discriminator — AuthError is a closed taxonomy, all variants have
162
+ // `code: string`.
163
+ if (
164
+ typeof (audienceClaim as { code?: unknown }).code === "string" &&
165
+ typeof (audienceClaim as { description?: unknown }).description === "string"
166
+ ) {
167
+ return err(audienceClaim as AuthError)
168
+ }
169
+ const newClaim = audienceClaim as SubjectClaim
170
+
171
+ // 6. Load the target tenant config so mintTokens has a TenantContext.
172
+ const targetTenantId = req.audience as TenantId
173
+ const targetTenantCfg = await deps.configStore.getTenantConfig(targetTenantId)
174
+ if (isErr(targetTenantCfg)) return err(targetTenantCfg.error)
175
+ // Verify the calling client exists at the target tenant — otherwise
176
+ // the issued token's `aud` would name a client the target tenant
177
+ // doesn't know about.
178
+ const targetClient = targetTenantCfg.value.clients.find(
179
+ (c: ClientConfig) => c.id === callerClient.id,
180
+ )
181
+ if (!targetClient) {
182
+ return err(
183
+ authError.invalidTarget(
184
+ `client "${callerClient.id}" is not registered in target tenant "${targetTenantId}"`,
185
+ ),
186
+ )
187
+ }
188
+
189
+ // 7. Mint. Fresh family — previous family is untouched.
190
+ const targetTenant: TenantContext = {
191
+ id: targetTenantId,
192
+ config: targetTenantCfg.value,
193
+ request: { raw: new Request("about:blank"), custom: {} },
194
+ }
195
+ const minted = await mintTokens({
196
+ tenant: targetTenant,
197
+ claim: newClaim,
198
+ payload: {
199
+ tenantId: targetTenantId,
200
+ clientId: callerClient.id,
201
+ // Preserve provenance — operators can trace exchanged tokens back
202
+ // to the originating method through the audit chain.
203
+ methodId: subjectClaims.mid ?? "token_exchange",
204
+ methodKind: subjectClaims.mkind ?? "token_exchange",
205
+ scopes: requestedScopes,
206
+ ...(subjectClaims.aud !== undefined &&
207
+ subjectClaims.aud !== callerClient.id
208
+ ? { audience: subjectClaims.aud }
209
+ : {}),
210
+ },
211
+ family: (deps.newRefreshFamily ?? randomId)(),
212
+ deps,
213
+ })
214
+ if (isErr(minted)) return err(minted.error)
215
+
216
+ // 8. Audit. mintTokens already emits `token_issued`; the additional
217
+ // `token_exchanged` event records the cross-tenant context.
218
+ await safeAudit(deps, {
219
+ kind: "token_exchanged",
220
+ tenantId: targetTenantId,
221
+ fromTenantId,
222
+ clientId: callerClient.id,
223
+ subjectId: subjectClaims.sub,
224
+ family: "exchanged",
225
+ timestamp: deps.clock(),
226
+ })
227
+
228
+ return ok({
229
+ ...minted.value,
230
+ issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
231
+ })
232
+ }