@toa.io/extensions.exposition 1.0.0-alpha.282 → 1.0.0-alpha.283

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 (312) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/components/exposition.octets/manifest.toa.yaml +11 -0
  3. package/components/exposition.octets/types/index.d.ts +3 -0
  4. package/components/exposition.octets/types/toa.d.ts +41 -0
  5. package/components/identity.bans/types/index.d.ts +3 -0
  6. package/components/identity.bans/types/toa.d.ts +34 -0
  7. package/components/identity.basic/manifest.toa.yaml +13 -2
  8. package/components/identity.basic/operations/add.d.ts +3 -3
  9. package/components/identity.basic/operations/authenticate.d.ts +2 -2
  10. package/components/identity.basic/operations/check.d.ts +1 -1
  11. package/components/identity.basic/operations/delete.d.ts +2 -2
  12. package/components/identity.basic/operations/incept.d.ts +2 -2
  13. package/components/identity.basic/operations/info.d.ts +1 -1
  14. package/components/identity.basic/operations/transit.d.ts +4 -3
  15. package/components/identity.basic/operations/transit.js +2 -1
  16. package/components/identity.basic/operations/transit.js.map +1 -1
  17. package/components/identity.basic/source/add.ts +3 -3
  18. package/components/identity.basic/source/authenticate.ts +2 -2
  19. package/components/identity.basic/source/check.ts +1 -1
  20. package/components/identity.basic/source/delete.ts +2 -2
  21. package/components/identity.basic/source/incept.ts +2 -2
  22. package/components/identity.basic/source/info.ts +1 -1
  23. package/components/identity.basic/source/transit.ts +6 -4
  24. package/components/identity.basic/tsconfig.tsbuildinfo +1 -1
  25. package/components/identity.basic/types/index.d.ts +21 -0
  26. package/components/identity.basic/types/toa.d.ts +121 -0
  27. package/components/identity.clients/manifest.toa.yaml +243 -0
  28. package/components/identity.clients/operations/describe.d.ts +27 -0
  29. package/components/identity.clients/operations/describe.js +44 -0
  30. package/components/identity.clients/operations/describe.js.map +1 -0
  31. package/components/identity.clients/operations/lib/Context.d.ts +29 -0
  32. package/components/identity.clients/operations/lib/Context.js +2 -0
  33. package/components/identity.clients/operations/lib/Context.js.map +1 -0
  34. package/components/identity.clients/operations/lib/Entity.d.ts +29 -0
  35. package/components/identity.clients/operations/lib/Entity.js +2 -0
  36. package/components/identity.clients/operations/lib/Entity.js.map +1 -0
  37. package/components/identity.clients/operations/lib/canonical.d.ts +11 -0
  38. package/components/identity.clients/operations/lib/canonical.js +24 -0
  39. package/components/identity.clients/operations/lib/canonical.js.map +1 -0
  40. package/components/identity.clients/operations/lib/cimd.d.ts +8 -0
  41. package/components/identity.clients/operations/lib/cimd.js +86 -0
  42. package/components/identity.clients/operations/lib/cimd.js.map +1 -0
  43. package/components/identity.clients/operations/lib/errors.d.ts +7 -0
  44. package/components/identity.clients/operations/lib/errors.js +5 -0
  45. package/components/identity.clients/operations/lib/errors.js.map +1 -0
  46. package/components/identity.clients/operations/lib/index.d.ts +6 -0
  47. package/components/identity.clients/operations/lib/index.js +7 -0
  48. package/components/identity.clients/operations/lib/index.js.map +1 -0
  49. package/components/identity.clients/operations/lib/redirect.d.ts +10 -0
  50. package/components/identity.clients/operations/lib/redirect.js +32 -0
  51. package/components/identity.clients/operations/lib/redirect.js.map +1 -0
  52. package/components/identity.clients/operations/register.d.ts +37 -0
  53. package/components/identity.clients/operations/register.js +84 -0
  54. package/components/identity.clients/operations/register.js.map +1 -0
  55. package/components/identity.clients/source/describe.ts +65 -0
  56. package/components/identity.clients/source/lib/Context.ts +38 -0
  57. package/components/identity.clients/source/lib/Entity.ts +31 -0
  58. package/components/identity.clients/source/lib/canonical.test.ts +43 -0
  59. package/components/identity.clients/source/lib/canonical.ts +27 -0
  60. package/components/identity.clients/source/lib/cimd.test.ts +120 -0
  61. package/components/identity.clients/source/lib/cimd.ts +107 -0
  62. package/components/identity.clients/source/lib/errors.ts +4 -0
  63. package/components/identity.clients/source/lib/index.ts +6 -0
  64. package/components/identity.clients/source/lib/redirect.test.ts +41 -0
  65. package/components/identity.clients/source/lib/redirect.ts +35 -0
  66. package/components/identity.clients/source/register.ts +125 -0
  67. package/components/identity.clients/tsconfig.json +10 -0
  68. package/components/identity.clients/tsconfig.tsbuildinfo +1 -0
  69. package/components/identity.clients/types/index.d.ts +3 -0
  70. package/components/identity.clients/types/toa.d.ts +109 -0
  71. package/components/identity.credentials/operations/list.d.ts +2 -1
  72. package/components/identity.credentials/operations/list.js.map +1 -1
  73. package/components/identity.credentials/source/list.ts +2 -1
  74. package/components/identity.credentials/tsconfig.tsbuildinfo +1 -1
  75. package/components/identity.credentials/types/index.d.ts +3 -0
  76. package/components/identity.credentials/types/toa.d.ts +30 -0
  77. package/components/identity.federation/manifest.toa.yaml +37 -1
  78. package/components/identity.federation/operations/authenticate.d.ts +1 -1
  79. package/components/identity.federation/operations/lib/Ctx.d.ts +2 -2
  80. package/components/identity.federation/operations/lib/decode.js +22 -8
  81. package/components/identity.federation/operations/lib/decode.js.map +1 -1
  82. package/components/identity.federation/operations/lib/errors.d.ts +7 -0
  83. package/components/identity.federation/operations/lib/errors.js +4 -0
  84. package/components/identity.federation/operations/lib/errors.js.map +1 -1
  85. package/components/identity.federation/operations/lib/exchange.js +11 -6
  86. package/components/identity.federation/operations/lib/exchange.js.map +1 -1
  87. package/components/identity.federation/operations/types/configuration.d.ts +1 -1
  88. package/components/identity.federation/operations/types/context.d.ts +3 -2
  89. package/components/identity.federation/source/authenticate.ts +1 -1
  90. package/components/identity.federation/source/lib/Ctx.ts +2 -2
  91. package/components/identity.federation/source/lib/decode.ts +25 -9
  92. package/components/identity.federation/source/lib/errors.ts +5 -0
  93. package/components/identity.federation/source/lib/exchange.ts +11 -7
  94. package/components/identity.federation/source/types/configuration.ts +1 -1
  95. package/components/identity.federation/source/types/context.ts +3 -2
  96. package/components/identity.federation/tsconfig.tsbuildinfo +1 -1
  97. package/components/identity.federation/types/index.d.ts +3 -0
  98. package/components/identity.federation/types/toa.d.ts +127 -0
  99. package/components/identity.grants/manifest.toa.yaml +245 -0
  100. package/components/identity.grants/operations/authorize.d.ts +35 -0
  101. package/components/identity.grants/operations/authorize.js +54 -0
  102. package/components/identity.grants/operations/authorize.js.map +1 -0
  103. package/components/identity.grants/operations/exchange.d.ts +37 -0
  104. package/components/identity.grants/operations/exchange.js +89 -0
  105. package/components/identity.grants/operations/exchange.js.map +1 -0
  106. package/components/identity.grants/operations/lib/code.d.ts +15 -0
  107. package/components/identity.grants/operations/lib/code.js +30 -0
  108. package/components/identity.grants/operations/lib/code.js.map +1 -0
  109. package/components/identity.grants/operations/lib/identify.d.ts +6 -0
  110. package/components/identity.grants/operations/lib/identify.js +15 -0
  111. package/components/identity.grants/operations/lib/identify.js.map +1 -0
  112. package/components/identity.grants/operations/lib/index.d.ts +3 -0
  113. package/components/identity.grants/operations/lib/index.js +3 -0
  114. package/components/identity.grants/operations/lib/index.js.map +1 -0
  115. package/components/identity.grants/operations/list.d.ts +13 -0
  116. package/components/identity.grants/operations/list.js +18 -0
  117. package/components/identity.grants/operations/list.js.map +1 -0
  118. package/components/identity.grants/operations/revoke.d.ts +18 -0
  119. package/components/identity.grants/operations/revoke.js +25 -0
  120. package/components/identity.grants/operations/revoke.js.map +1 -0
  121. package/components/identity.grants/source/authorize.ts +90 -0
  122. package/components/identity.grants/source/exchange.ts +130 -0
  123. package/components/identity.grants/source/lib/code.ts +39 -0
  124. package/components/identity.grants/source/lib/identify.ts +16 -0
  125. package/components/identity.grants/source/lib/index.ts +3 -0
  126. package/components/identity.grants/source/list.ts +28 -0
  127. package/components/identity.grants/source/revoke.ts +38 -0
  128. package/components/identity.grants/tsconfig.json +10 -0
  129. package/components/identity.grants/tsconfig.tsbuildinfo +1 -0
  130. package/components/identity.grants/types/index.d.ts +60 -0
  131. package/components/identity.grants/types/toa.d.ts +108 -0
  132. package/components/identity.keys/manifest.toa.yaml +1 -0
  133. package/components/identity.keys/operations/create.d.ts +1 -1
  134. package/components/identity.keys/operations/revoke.d.ts +1 -1
  135. package/components/identity.keys/source/create.ts +1 -1
  136. package/components/identity.keys/source/revoke.ts +1 -1
  137. package/components/identity.keys/tsconfig.tsbuildinfo +1 -1
  138. package/components/identity.keys/types/index.d.ts +3 -0
  139. package/components/identity.keys/types/toa.d.ts +47 -0
  140. package/components/identity.otp/operations/lib/Context.d.ts +4 -2
  141. package/components/identity.otp/source/lib/Context.ts +4 -2
  142. package/components/identity.otp/tsconfig.tsbuildinfo +1 -1
  143. package/components/identity.otp/types/index.d.ts +3 -0
  144. package/components/identity.otp/types/toa.d.ts +56 -0
  145. package/components/identity.passkeys/manifest.toa.yaml +3 -0
  146. package/components/identity.passkeys/operations/authenticate.d.ts +1 -1
  147. package/components/identity.passkeys/operations/challenge.d.ts +1 -1
  148. package/components/identity.passkeys/operations/create.d.ts +1 -1
  149. package/components/identity.passkeys/operations/types/Context.d.ts +2 -1
  150. package/components/identity.passkeys/operations/use.d.ts +1 -1
  151. package/components/identity.passkeys/source/authenticate.ts +1 -1
  152. package/components/identity.passkeys/source/challenge.ts +1 -1
  153. package/components/identity.passkeys/source/create.ts +1 -1
  154. package/components/identity.passkeys/source/types/Context.ts +2 -1
  155. package/components/identity.passkeys/source/use.ts +1 -1
  156. package/components/identity.passkeys/tsconfig.tsbuildinfo +1 -1
  157. package/components/identity.passkeys/types/index.d.ts +3 -0
  158. package/components/identity.passkeys/types/toa.d.ts +120 -0
  159. package/components/identity.roles/operations/principal.d.ts +1 -1
  160. package/components/identity.roles/source/principal.ts +1 -1
  161. package/components/identity.roles/tsconfig.tsbuildinfo +1 -1
  162. package/components/identity.roles/types/index.d.ts +3 -0
  163. package/components/identity.roles/types/toa.d.ts +43 -0
  164. package/components/identity.tokens/manifest.toa.yaml +34 -3
  165. package/components/identity.tokens/operations/authenticate.d.ts +2 -1
  166. package/components/identity.tokens/operations/authenticate.js +8 -0
  167. package/components/identity.tokens/operations/authenticate.js.map +1 -1
  168. package/components/identity.tokens/operations/decrypt.d.ts +2 -1
  169. package/components/identity.tokens/operations/decrypt.js +2 -2
  170. package/components/identity.tokens/operations/decrypt.js.map +1 -1
  171. package/components/identity.tokens/operations/encrypt.d.ts +2 -1
  172. package/components/identity.tokens/operations/encrypt.js.map +1 -1
  173. package/components/identity.tokens/operations/issue.d.ts +2 -1
  174. package/components/identity.tokens/operations/issue.js +2 -1
  175. package/components/identity.tokens/operations/issue.js.map +1 -1
  176. package/components/identity.tokens/operations/lib/form.d.ts +8 -0
  177. package/components/identity.tokens/operations/lib/form.js +24 -0
  178. package/components/identity.tokens/operations/lib/form.js.map +1 -0
  179. package/components/identity.tokens/operations/lib/index.d.ts +2 -1
  180. package/components/identity.tokens/operations/lib/index.js +1 -1
  181. package/components/identity.tokens/operations/lib/index.js.map +1 -1
  182. package/components/identity.tokens/source/authenticate.test.ts +8 -5
  183. package/components/identity.tokens/source/authenticate.ts +12 -1
  184. package/components/identity.tokens/source/decrypt.test.ts +1 -1
  185. package/components/identity.tokens/source/decrypt.ts +4 -3
  186. package/components/identity.tokens/source/encrypt.test.ts +1 -1
  187. package/components/identity.tokens/source/encrypt.ts +2 -1
  188. package/components/identity.tokens/source/issue.test.ts +71 -0
  189. package/components/identity.tokens/source/issue.ts +4 -2
  190. package/components/identity.tokens/source/lib/form.test.ts +25 -0
  191. package/components/identity.tokens/source/lib/form.ts +31 -0
  192. package/components/identity.tokens/source/lib/index.ts +2 -1
  193. package/components/identity.tokens/tsconfig.tsbuildinfo +1 -1
  194. package/components/identity.tokens/types/index.d.ts +61 -0
  195. package/components/identity.tokens/types/toa.d.ts +113 -0
  196. package/documentation/cache.md +10 -1
  197. package/documentation/consent.md +101 -0
  198. package/documentation/identity.md +3 -1
  199. package/documentation/io.md +36 -2
  200. package/documentation/oauth.md +179 -0
  201. package/documentation/protocol.md +4 -0
  202. package/features/auth.bearer.feature +120 -0
  203. package/features/cache.feature +46 -0
  204. package/features/oauth.clients.feature +195 -0
  205. package/features/oauth.discovery.feature +165 -0
  206. package/features/oauth.grants.feature +286 -0
  207. package/features/steps/Gateway.ts +6 -1
  208. package/features/steps/Parameters.ts +4 -0
  209. package/features/steps/components/echo/manifest.toa.yaml +2 -0
  210. package/features/steps/components/octets.tester/manifest.toa.yaml +2 -0
  211. package/features/steps/components/octets.tester/operations/redirect.js +6 -1
  212. package/features/steps/components/users/manifest.toa.yaml +1 -0
  213. package/package.json +8 -6
  214. package/readme.md +3 -0
  215. package/schemas/annotation.cos.yaml +30 -0
  216. package/source/Annotation.ts +23 -0
  217. package/source/Factory.ts +1 -1
  218. package/source/HTTP/Server.ts +9 -5
  219. package/source/HTTP/formats/form.ts +21 -0
  220. package/source/HTTP/formats/index.ts +17 -2
  221. package/source/HTTP/messages.test.ts +25 -1
  222. package/source/HTTP/messages.ts +10 -3
  223. package/source/Interception.ts +8 -2
  224. package/source/deployment.ts +4 -1
  225. package/source/directives/auth/Authorization.ts +46 -19
  226. package/source/directives/auth/Incept.ts +14 -9
  227. package/source/directives/auth/schemes.test.ts +8 -4
  228. package/source/directives/auth/schemes.ts +22 -9
  229. package/source/directives/auth/types.ts +5 -2
  230. package/source/directives/cache/Cache.ts +13 -4
  231. package/source/directives/cache/Control.ts +0 -3
  232. package/source/directives/flow/Fetch.ts +1 -1
  233. package/source/directives/index.ts +2 -1
  234. package/source/directives/io/IO.ts +2 -0
  235. package/source/directives/io/Output.ts +4 -1
  236. package/source/directives/io/Status.test.ts +84 -0
  237. package/source/directives/io/Status.ts +46 -0
  238. package/source/directives/oauth/Discovery.ts +76 -0
  239. package/source/directives/oauth/documents.ts +113 -0
  240. package/source/directives/oauth/index.ts +3 -0
  241. package/source/directives/octets/Delete.ts +1 -1
  242. package/source/directives/octets/Get.ts +1 -1
  243. package/source/directives/octets/Workflow.ts +1 -1
  244. package/source/directives/octets/workflows/Execution.ts +1 -1
  245. package/source/index.ts +6 -0
  246. package/source/octets.d.ts +17 -0
  247. package/source/userland.ts +47 -0
  248. package/transpiled/Annotation.d.ts +17 -0
  249. package/transpiled/Factory.js +1 -1
  250. package/transpiled/Factory.js.map +1 -1
  251. package/transpiled/HTTP/Server.d.ts +4 -1
  252. package/transpiled/HTTP/Server.js +4 -3
  253. package/transpiled/HTTP/Server.js.map +1 -1
  254. package/transpiled/HTTP/formats/form.d.ts +9 -0
  255. package/transpiled/HTTP/formats/form.js +18 -0
  256. package/transpiled/HTTP/formats/form.js.map +1 -0
  257. package/transpiled/HTTP/formats/index.d.ts +10 -2
  258. package/transpiled/HTTP/formats/index.js +10 -0
  259. package/transpiled/HTTP/formats/index.js.map +1 -1
  260. package/transpiled/HTTP/messages.d.ts +6 -0
  261. package/transpiled/HTTP/messages.js +3 -3
  262. package/transpiled/HTTP/messages.js.map +1 -1
  263. package/transpiled/Interception.d.ts +4 -1
  264. package/transpiled/Interception.js +4 -2
  265. package/transpiled/Interception.js.map +1 -1
  266. package/transpiled/deployment.js +2 -0
  267. package/transpiled/deployment.js.map +1 -1
  268. package/transpiled/directives/auth/Authorization.d.ts +1 -1
  269. package/transpiled/directives/auth/Authorization.js +38 -18
  270. package/transpiled/directives/auth/Authorization.js.map +1 -1
  271. package/transpiled/directives/auth/Incept.d.ts +1 -1
  272. package/transpiled/directives/auth/Incept.js +11 -7
  273. package/transpiled/directives/auth/Incept.js.map +1 -1
  274. package/transpiled/directives/auth/schemes.d.ts +16 -4
  275. package/transpiled/directives/auth/schemes.js +20 -8
  276. package/transpiled/directives/auth/schemes.js.map +1 -1
  277. package/transpiled/directives/auth/types.d.ts +4 -2
  278. package/transpiled/directives/cache/Cache.js +9 -2
  279. package/transpiled/directives/cache/Cache.js.map +1 -1
  280. package/transpiled/directives/cache/Control.js +0 -2
  281. package/transpiled/directives/cache/Control.js.map +1 -1
  282. package/transpiled/directives/index.js +2 -1
  283. package/transpiled/directives/index.js.map +1 -1
  284. package/transpiled/directives/io/IO.js +2 -0
  285. package/transpiled/directives/io/IO.js.map +1 -1
  286. package/transpiled/directives/io/Output.js +4 -1
  287. package/transpiled/directives/io/Output.js.map +1 -1
  288. package/transpiled/directives/io/Status.d.ts +19 -0
  289. package/transpiled/directives/io/Status.js +34 -0
  290. package/transpiled/directives/io/Status.js.map +1 -0
  291. package/transpiled/directives/oauth/Discovery.d.ts +23 -0
  292. package/transpiled/directives/oauth/Discovery.js +58 -0
  293. package/transpiled/directives/oauth/Discovery.js.map +1 -0
  294. package/transpiled/directives/oauth/documents.d.ts +9 -0
  295. package/transpiled/directives/oauth/documents.js +82 -0
  296. package/transpiled/directives/oauth/documents.js.map +1 -0
  297. package/transpiled/directives/oauth/index.d.ts +2 -0
  298. package/transpiled/directives/oauth/index.js +3 -0
  299. package/transpiled/directives/oauth/index.js.map +1 -0
  300. package/transpiled/index.d.ts +2 -0
  301. package/transpiled/userland.d.ts +39 -0
  302. package/transpiled/userland.js +7 -0
  303. package/transpiled/userland.js.map +1 -0
  304. package/tsconfig.tsbuildinfo +1 -1
  305. package/components/identity.basic/operations/types.d.ts +0 -61
  306. package/components/identity.basic/operations/types.js +0 -2
  307. package/components/identity.basic/operations/types.js.map +0 -1
  308. package/components/identity.basic/source/types.ts +0 -69
  309. package/components/identity.tokens/operations/lib/types.d.ts +0 -88
  310. package/components/identity.tokens/operations/lib/types.js +0 -2
  311. package/components/identity.tokens/operations/lib/types.js.map +0 -1
  312. package/components/identity.tokens/source/lib/types.ts +0 -102
@@ -0,0 +1,195 @@
1
+ @security
2
+ Feature: Client registration
3
+
4
+ A client that speaks nothing else registers what it is and is given an identifier for it.
5
+ The identifier is a hash of the metadata, so a client that cannot remember it registered
6
+ here does not leave another row every time it connects.
7
+
8
+ Background:
9
+ Given the `identity.clients` database is empty
10
+ And the `identity.basic` database contains:
11
+ | _id | authority | username | password |
12
+ | efe3a65ebbee47ed95a73edd911ea328 | nex | developer | $2b$10$ZRSKkgZoGnrcTNA5w5eCcu3pxDzdTduhteVYXcp56AaNcilNkwJ.O |
13
+ And the annotation:
14
+ """yaml
15
+ authorities:
16
+ nex: nex.toa.io
17
+ oauth:
18
+ authorize: https://app.nex.toa.io/oauth/authorize
19
+ registration: open
20
+ """
21
+
22
+ Scenario: A client registers what it is
23
+ When the following request is received:
24
+ """
25
+ POST /identity/clients/ HTTP/1.1
26
+ host: nex.toa.io
27
+ content-type: application/yaml
28
+ accept: application/yaml
29
+
30
+ client_name: Claude
31
+ redirect_uris:
32
+ - https://claude.ai/api/mcp/auth_callback
33
+ token_endpoint_auth_method: none
34
+ """
35
+ Then the following reply is sent:
36
+ """
37
+ 201 Created
38
+ cache-control: no-store
39
+
40
+ client_id: ${{ client }}
41
+ client_name: Claude
42
+ redirect_uris:
43
+ - https://claude.ai/api/mcp/auth_callback
44
+ grant_types:
45
+ - authorization_code
46
+ response_types:
47
+ - code
48
+ token_endpoint_auth_method: none
49
+ """
50
+
51
+ Scenario: The same client is the same client
52
+ When the following request is received:
53
+ """
54
+ POST /identity/clients/ HTTP/1.1
55
+ host: nex.toa.io
56
+ content-type: application/yaml
57
+ accept: application/yaml
58
+
59
+ client_name: Claude
60
+ redirect_uris:
61
+ - https://claude.ai/api/mcp/auth_callback
62
+ - http://localhost/callback
63
+ """
64
+ Then the following reply is sent:
65
+ """
66
+ 201 Created
67
+
68
+ client_id: ${{ first }}
69
+ """
70
+ # the same metadata, written in another order
71
+ When the following request is received:
72
+ """
73
+ POST /identity/clients/ HTTP/1.1
74
+ host: nex.toa.io
75
+ content-type: application/yaml
76
+ accept: application/yaml
77
+
78
+ client_name: Claude
79
+ redirect_uris:
80
+ - http://localhost/callback
81
+ - https://claude.ai/api/mcp/auth_callback
82
+ """
83
+ Then the following reply is sent:
84
+ """
85
+ 201 Created
86
+
87
+ client_id: ${{ first }}
88
+ """
89
+
90
+ Scenario: A redirect a code must not be sent to
91
+ When the following request is received:
92
+ """
93
+ POST /identity/clients/ HTTP/1.1
94
+ host: nex.toa.io
95
+ content-type: application/yaml
96
+ accept: application/yaml
97
+
98
+ client_name: Insecure
99
+ redirect_uris:
100
+ - http://example.com/callback
101
+ """
102
+ Then the following reply is sent:
103
+ """
104
+ 400 Bad Request
105
+ cache-control: no-store
106
+
107
+ error: invalid_redirect_uri
108
+ """
109
+
110
+ Scenario: A client that would authenticate at the token endpoint
111
+ When the following request is received:
112
+ """
113
+ POST /identity/clients/ HTTP/1.1
114
+ host: nex.toa.io
115
+ content-type: application/yaml
116
+ accept: application/yaml
117
+
118
+ client_name: Confidential
119
+ redirect_uris:
120
+ - https://claude.ai/api/mcp/auth_callback
121
+ token_endpoint_auth_method: client_secret_post
122
+ """
123
+ Then the following reply is sent:
124
+ """
125
+ 400 Bad Request
126
+
127
+ error: invalid_client_metadata
128
+ """
129
+
130
+ Scenario: Registration is advertised once it is opened
131
+ When the following request is received:
132
+ """
133
+ GET /.well-known/oauth-authorization-server HTTP/1.1
134
+ host: nex.toa.io
135
+ accept: application/yaml
136
+ """
137
+ Then the following reply is sent:
138
+ """
139
+ 200 OK
140
+
141
+ registration_endpoint: https://nex.toa.io/identity/clients/
142
+ """
143
+
144
+ Scenario: The consent page reads who is asking
145
+ When the following request is received:
146
+ """
147
+ POST /identity/clients/ HTTP/1.1
148
+ host: nex.toa.io
149
+ content-type: application/yaml
150
+ accept: application/yaml
151
+
152
+ client_name: Claude
153
+ client_uri: https://claude.ai
154
+ redirect_uris:
155
+ - https://claude.ai/api/mcp/auth_callback
156
+ """
157
+ Then the following reply is sent:
158
+ """
159
+ 201 Created
160
+
161
+ client_id: ${{ client }}
162
+ """
163
+ # the page is authenticated by the time it asks, so this is not anonymous
164
+ When the following request is received:
165
+ """
166
+ GET /identity/clients/${{ client }}/ HTTP/1.1
167
+ host: nex.toa.io
168
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
169
+ accept: application/yaml
170
+ """
171
+ Then the following reply is sent:
172
+ """
173
+ 200 OK
174
+
175
+ client_id: ${{ client }}
176
+ client_name: Claude
177
+ client_uri: https://claude.ai
178
+ redirect_uris:
179
+ - https://claude.ai/api/mcp/auth_callback
180
+ """
181
+
182
+ Scenario: A client nobody registered
183
+ When the following request is received:
184
+ """
185
+ GET /identity/clients/00000000000000000000000000000000/ HTTP/1.1
186
+ host: nex.toa.io
187
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
188
+ accept: application/yaml
189
+ """
190
+ Then the following reply is sent:
191
+ """
192
+ 422 Unprocessable Entity
193
+
194
+ code: UNKNOWN_CLIENT
195
+ """
@@ -0,0 +1,165 @@
1
+ @security
2
+ Feature: Authorization server discovery
3
+
4
+ What a client reads before it can authenticate. Both documents are public, are a
5
+ function of the annotation alone, and are answered before anything is routed.
6
+
7
+ Background:
8
+ Given the annotation:
9
+ """yaml
10
+ authorities:
11
+ nex: nex.toa.io
12
+ oauth:
13
+ authorize: https://app.nex.toa.io/oauth/authorize
14
+ resources: ['/mcp/']
15
+ scopes: [app:notes]
16
+ /:
17
+ io:output: true
18
+ /mcp/**:
19
+ anyone: true
20
+ GET:
21
+ dev:stub: Tools
22
+ """
23
+
24
+ Scenario: The authorization server names its endpoints
25
+ When the following request is received:
26
+ """
27
+ GET /.well-known/oauth-authorization-server HTTP/1.1
28
+ host: nex.toa.io
29
+ accept: application/yaml
30
+ """
31
+ Then the following reply is sent:
32
+ """
33
+ 200 OK
34
+
35
+ issuer: https://nex.toa.io
36
+ authorization_endpoint: https://app.nex.toa.io/oauth/authorize
37
+ token_endpoint: https://nex.toa.io/identity/grants/
38
+ response_types_supported:
39
+ - code
40
+ grant_types_supported:
41
+ - authorization_code
42
+ code_challenge_methods_supported:
43
+ - S256
44
+ token_endpoint_auth_methods_supported:
45
+ - none
46
+ client_id_metadata_document_supported: true
47
+ authorization_response_iss_parameter_supported: true
48
+ scopes_supported:
49
+ - app:notes
50
+ """
51
+
52
+ Scenario: Registration is not advertised unless it is opened
53
+ When the following request is received:
54
+ """
55
+ GET /.well-known/oauth-authorization-server HTTP/1.1
56
+ host: nex.toa.io
57
+ accept: application/yaml
58
+ """
59
+ Then the reply does not contain:
60
+ """
61
+ registration_endpoint
62
+ """
63
+
64
+ Scenario: The protected resource is named by the path it is reached at
65
+ When the following request is received:
66
+ """
67
+ GET /.well-known/oauth-protected-resource/mcp HTTP/1.1
68
+ host: nex.toa.io
69
+ accept: application/yaml
70
+ """
71
+ Then the following reply is sent:
72
+ """
73
+ 200 OK
74
+
75
+ resource: https://nex.toa.io/mcp
76
+ authorization_servers:
77
+ - https://nex.toa.io
78
+ bearer_methods_supported:
79
+ - header
80
+ scopes_supported:
81
+ - app:notes
82
+ """
83
+
84
+ Scenario: The origin has a document of its own
85
+ When the following request is received:
86
+ """
87
+ GET /.well-known/oauth-protected-resource HTTP/1.1
88
+ host: nex.toa.io
89
+ accept: application/yaml
90
+ """
91
+ Then the following reply is sent:
92
+ """
93
+ 200 OK
94
+
95
+ resource: https://nex.toa.io
96
+ authorization_servers:
97
+ - https://nex.toa.io
98
+ bearer_methods_supported:
99
+ - header
100
+ scopes_supported:
101
+ - app:notes
102
+ """
103
+
104
+ Scenario: A client holding a stale token still reads discovery
105
+ When the following request is received:
106
+ """
107
+ GET /.well-known/oauth-authorization-server HTTP/1.1
108
+ host: nex.toa.io
109
+ authorization: Bearer expired.and.useless
110
+ accept: application/yaml
111
+ """
112
+ Then the following reply is sent:
113
+ """
114
+ 200 OK
115
+
116
+ issuer: https://nex.toa.io
117
+ """
118
+
119
+ Scenario: The challenge names the resource the request was refused at
120
+ When the following request is received:
121
+ """
122
+ GET /mcp/tools/ HTTP/1.1
123
+ host: nex.toa.io
124
+ accept: text/plain
125
+ """
126
+ Then the following reply is sent:
127
+ """
128
+ 401 Unauthorized
129
+ www-authenticate: Bearer resource_metadata="https://nex.toa.io/.well-known/oauth-protected-resource/mcp", scope="app:notes"
130
+ """
131
+
132
+ Scenario: A host named after a property a plain object answers on its own
133
+ When the following request is received:
134
+ """
135
+ GET /.well-known/oauth-authorization-server HTTP/1.1
136
+ host: constructor
137
+ accept: application/yaml
138
+ """
139
+ Then the following reply is sent:
140
+ """
141
+ 404 Not Found
142
+ """
143
+
144
+ Scenario: An authorization server nobody annotated is not discovered
145
+ Given the annotation:
146
+ """yaml
147
+ authorities:
148
+ nex: nex.toa.io
149
+ /:
150
+ io:output: true
151
+ /hello/:
152
+ anonymous: true
153
+ GET:
154
+ dev:stub: Hello
155
+ """
156
+ When the following request is received:
157
+ """
158
+ GET /.well-known/oauth-authorization-server HTTP/1.1
159
+ host: nex.toa.io
160
+ accept: application/yaml
161
+ """
162
+ Then the following reply is sent:
163
+ """
164
+ 404 Not Found
165
+ """
@@ -0,0 +1,286 @@
1
+ @security
2
+ Feature: Authorization code flow
3
+
4
+ A client is refused, finds the authorization server, registers, is consented to by a user,
5
+ and exchanges the code it was given for a token it can come back with. The consent page is
6
+ the application's own — the two calls in the background are what it would make.
7
+
8
+ Background:
9
+ Given the `identity.clients` database is empty
10
+ And the `identity.grants` database is empty
11
+ And the `identity.basic` database contains:
12
+ | _id | authority | username | password |
13
+ | efe3a65ebbee47ed95a73edd911ea328 | nex | developer | $2b$10$ZRSKkgZoGnrcTNA5w5eCcu3pxDzdTduhteVYXcp56AaNcilNkwJ.O |
14
+ And the annotation:
15
+ """yaml
16
+ authorities:
17
+ nex: nex.toa.io
18
+ oauth:
19
+ authorize: https://app.nex.toa.io/oauth/authorize
20
+ resources: ['/mcp/']
21
+ registration: open
22
+ /:
23
+ io:output: true
24
+ /mcp/**:
25
+ anyone: true
26
+ GET:
27
+ dev:stub: Tools
28
+ """
29
+ When the following request is received:
30
+ """
31
+ POST /identity/clients/ HTTP/1.1
32
+ host: nex.toa.io
33
+ content-type: application/yaml
34
+ accept: application/yaml
35
+
36
+ client_name: Claude
37
+ redirect_uris:
38
+ - https://claude.ai/api/mcp/auth_callback
39
+ """
40
+ Then the following reply is sent:
41
+ """
42
+ 201 Created
43
+
44
+ client_id: ${{ client }}
45
+ """
46
+ When the following request is received:
47
+ """
48
+ POST /identity/grants/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
49
+ host: nex.toa.io
50
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
51
+ content-type: application/yaml
52
+ accept: application/yaml
53
+
54
+ client: ${{ client }}
55
+ redirect: https://claude.ai/api/mcp/auth_callback
56
+ challenge: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
57
+ method: S256
58
+ """
59
+ Then the following reply is sent:
60
+ """
61
+ 201 Created
62
+ cache-control: no-store
63
+
64
+ code: ${{ code }}
65
+ expires_in: 60
66
+ """
67
+
68
+ Scenario: A client comes back with the token it was given
69
+ # nothing yet, and the client is told where to get one
70
+ When the following request is received:
71
+ """
72
+ GET /mcp/tools/ HTTP/1.1
73
+ host: nex.toa.io
74
+ accept: text/plain
75
+ """
76
+ Then the following reply is sent:
77
+ """
78
+ 401 Unauthorized
79
+ www-authenticate: Bearer resource_metadata="https://nex.toa.io/.well-known/oauth-protected-resource/mcp"
80
+ """
81
+ When the following request is received:
82
+ """
83
+ POST /identity/grants/ HTTP/1.1
84
+ host: nex.toa.io
85
+ content-type: application/x-www-form-urlencoded
86
+ accept: application/yaml
87
+
88
+ grant_type=authorization_code&code=${{ code }}&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk&redirect_uri=https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback&client_id=${{ client }}
89
+ """
90
+ Then the following reply is sent:
91
+ """
92
+ 200 OK
93
+ cache-control: no-store
94
+
95
+ access_token: ${{ access_token }}
96
+ token_type: Bearer
97
+ """
98
+ When the following request is received:
99
+ """
100
+ GET /mcp/tools/ HTTP/1.1
101
+ host: nex.toa.io
102
+ authorization: Bearer ${{ access_token }}
103
+ accept: text/plain
104
+ """
105
+ Then the following reply is sent:
106
+ """
107
+ 200 OK
108
+
109
+ Tools
110
+ """
111
+
112
+ Scenario: A code is spent once
113
+ When the following request is received:
114
+ """
115
+ POST /identity/grants/ HTTP/1.1
116
+ host: nex.toa.io
117
+ content-type: application/x-www-form-urlencoded
118
+ accept: application/yaml
119
+
120
+ grant_type=authorization_code&code=${{ code }}&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
121
+ """
122
+ Then the following reply is sent:
123
+ """
124
+ 200 OK
125
+ """
126
+ When the following request is received:
127
+ """
128
+ POST /identity/grants/ HTTP/1.1
129
+ host: nex.toa.io
130
+ content-type: application/x-www-form-urlencoded
131
+ accept: application/yaml
132
+
133
+ grant_type=authorization_code&code=${{ code }}&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
134
+ """
135
+ Then the following reply is sent:
136
+ """
137
+ 400 Bad Request
138
+
139
+ error: invalid_grant
140
+ """
141
+
142
+ Scenario: A verifier that did not make that challenge
143
+ When the following request is received:
144
+ """
145
+ POST /identity/grants/ HTTP/1.1
146
+ host: nex.toa.io
147
+ content-type: application/x-www-form-urlencoded
148
+ accept: application/yaml
149
+
150
+ grant_type=authorization_code&code=${{ code }}&code_verifier=this-is-not-the-verifier-that-made-that-challenge
151
+ """
152
+ Then the following reply is sent:
153
+ """
154
+ 400 Bad Request
155
+
156
+ error: invalid_grant
157
+ """
158
+ # the code was spent trying, so guessing at it buys nothing
159
+ When the following request is received:
160
+ """
161
+ POST /identity/grants/ HTTP/1.1
162
+ host: nex.toa.io
163
+ content-type: application/x-www-form-urlencoded
164
+ accept: application/yaml
165
+
166
+ grant_type=authorization_code&code=${{ code }}&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
167
+ """
168
+ Then the following reply is sent:
169
+ """
170
+ 400 Bad Request
171
+
172
+ error: invalid_grant
173
+ """
174
+
175
+ Scenario: A redirect swapped between authorizing and exchanging
176
+ When the following request is received:
177
+ """
178
+ POST /identity/grants/ HTTP/1.1
179
+ host: nex.toa.io
180
+ content-type: application/x-www-form-urlencoded
181
+ accept: application/yaml
182
+
183
+ grant_type=authorization_code&code=${{ code }}&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk&redirect_uri=https%3A%2F%2Fevil.example%2Fcallback
184
+ """
185
+ Then the following reply is sent:
186
+ """
187
+ 400 Bad Request
188
+
189
+ error: invalid_grant
190
+ """
191
+
192
+ Scenario: A grant nobody may be sent a code for
193
+ When the following request is received:
194
+ """
195
+ POST /identity/grants/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
196
+ host: nex.toa.io
197
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
198
+ content-type: application/yaml
199
+ accept: application/yaml
200
+
201
+ client: ${{ client }}
202
+ redirect: https://evil.example/callback
203
+ challenge: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
204
+ method: S256
205
+ """
206
+ Then the following reply is sent:
207
+ """
208
+ 400 Bad Request
209
+
210
+ error: invalid_request
211
+ """
212
+
213
+ Scenario: A challenge method that proves nothing
214
+ When the following request is received:
215
+ """
216
+ POST /identity/grants/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
217
+ host: nex.toa.io
218
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
219
+ content-type: application/yaml
220
+ accept: application/yaml
221
+
222
+ client: ${{ client }}
223
+ redirect: https://claude.ai/api/mcp/auth_callback
224
+ challenge: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
225
+ method: plain
226
+ """
227
+ Then the following reply is sent:
228
+ """
229
+ 400 Bad Request
230
+
231
+ error: invalid_request
232
+ """
233
+
234
+ Scenario: A grant only its own identity may see
235
+ When the following request is received:
236
+ """
237
+ GET /identity/grants/00000000000000000000000000000000/ HTTP/1.1
238
+ host: nex.toa.io
239
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
240
+ accept: application/yaml
241
+ """
242
+ Then the following reply is sent:
243
+ """
244
+ 403 Forbidden
245
+ """
246
+
247
+ Scenario: Taking back what was allowed
248
+ When the following request is received:
249
+ """
250
+ POST /identity/grants/ HTTP/1.1
251
+ host: nex.toa.io
252
+ content-type: application/x-www-form-urlencoded
253
+ accept: application/yaml
254
+
255
+ grant_type=authorization_code&code=${{ code }}&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
256
+ """
257
+ Then the following reply is sent:
258
+ """
259
+ 200 OK
260
+
261
+ access_token: ${{ access_token }}
262
+ """
263
+ When the following request is received:
264
+ """
265
+ GET /identity/grants/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
266
+ host: nex.toa.io
267
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
268
+ accept: application/yaml
269
+ """
270
+ Then the following reply is sent:
271
+ """
272
+ 200 OK
273
+
274
+ - id: ${{ grant }}
275
+ client: ${{ client }}
276
+ """
277
+ When the following request is received:
278
+ """
279
+ DELETE /identity/grants/efe3a65ebbee47ed95a73edd911ea328/${{ grant }}/ HTTP/1.1
280
+ host: nex.toa.io
281
+ authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
282
+ """
283
+ Then the following reply is sent:
284
+ """
285
+ 204 No Content
286
+ """
@@ -29,7 +29,7 @@ export class Gateway {
29
29
  process.env.TOA_EXPOSITION = JSON.stringify(tree)
30
30
  }
31
31
 
32
- const { debug, authorities, bouncer, ip } = annotation
32
+ const { debug, authorities, bouncer, ip, oauth } = annotation
33
33
  const properties = Object.assign({}, DEFAULT_PROPERTIES)
34
34
 
35
35
  if (debug !== undefined)
@@ -44,6 +44,9 @@ export class Gateway {
44
44
  if (authorities !== undefined)
45
45
  properties.authorities = authorities
46
46
 
47
+ if (oauth !== undefined)
48
+ properties.oauth = oauth
49
+
47
50
  process.env.TOA_EXPOSITION_PROPERTIES = JSON.stringify(properties)
48
51
 
49
52
  await Gateway.stop()
@@ -215,6 +218,8 @@ const DEFAULT_CONFIGURATION: Record<string, object> = {
215
218
  // features do not run; the variable is the local override that stands in for it
216
219
  'realtime.streams': {},
217
220
  'identity.basic': {},
221
+ 'identity.clients': {},
222
+ 'identity.grants': {},
218
223
  'identity.federation': {},
219
224
  'identity.otp': {},
220
225
  'identity.passkeys': {},
@@ -16,6 +16,10 @@ setDefaultTimeout(60 * 1000)
16
16
 
17
17
  process.env.TOA_DEV = '1'
18
18
 
19
+ // a reply is checked against what the operation declares, so the suite runs Toa under the
20
+ // contract it asks applications to keep
21
+ process.env.TOA_ENV ??= 'local'
22
+
19
23
  // the gateway answers for itself, as it does in a deployment: telemetry's probe tracks the
20
24
  // nested composition, which connects before route discovery settles
21
25
  process.env.TOA_TELEMETRY_READY ??= JSON.stringify(false)
@@ -54,3 +54,5 @@ operations:
54
54
  properties:
55
55
  buf:
56
56
  type: string
57
+ error:
58
+ errors: [CODE]
@@ -5,6 +5,7 @@ storages: octets
5
5
 
6
6
  operations:
7
7
  foo: &operation
8
+ errors: [ERROR, NOT_FOUND]
8
9
  input:
9
10
  properties:
10
11
  identity:
@@ -38,6 +39,7 @@ operations:
38
39
  authority: *operation
39
40
  identity: *operation
40
41
  redirect:
42
+ errors: [ERROR]
41
43
  input:
42
44
  properties:
43
45
  parameters: