@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.
- package/CHANGELOG.md +27 -0
- package/components/exposition.octets/manifest.toa.yaml +11 -0
- package/components/exposition.octets/types/index.d.ts +3 -0
- package/components/exposition.octets/types/toa.d.ts +41 -0
- package/components/identity.bans/types/index.d.ts +3 -0
- package/components/identity.bans/types/toa.d.ts +34 -0
- package/components/identity.basic/manifest.toa.yaml +13 -2
- package/components/identity.basic/operations/add.d.ts +3 -3
- package/components/identity.basic/operations/authenticate.d.ts +2 -2
- package/components/identity.basic/operations/check.d.ts +1 -1
- package/components/identity.basic/operations/delete.d.ts +2 -2
- package/components/identity.basic/operations/incept.d.ts +2 -2
- package/components/identity.basic/operations/info.d.ts +1 -1
- package/components/identity.basic/operations/transit.d.ts +4 -3
- package/components/identity.basic/operations/transit.js +2 -1
- package/components/identity.basic/operations/transit.js.map +1 -1
- package/components/identity.basic/source/add.ts +3 -3
- package/components/identity.basic/source/authenticate.ts +2 -2
- package/components/identity.basic/source/check.ts +1 -1
- package/components/identity.basic/source/delete.ts +2 -2
- package/components/identity.basic/source/incept.ts +2 -2
- package/components/identity.basic/source/info.ts +1 -1
- package/components/identity.basic/source/transit.ts +6 -4
- package/components/identity.basic/tsconfig.tsbuildinfo +1 -1
- package/components/identity.basic/types/index.d.ts +21 -0
- package/components/identity.basic/types/toa.d.ts +121 -0
- package/components/identity.clients/manifest.toa.yaml +243 -0
- package/components/identity.clients/operations/describe.d.ts +27 -0
- package/components/identity.clients/operations/describe.js +44 -0
- package/components/identity.clients/operations/describe.js.map +1 -0
- package/components/identity.clients/operations/lib/Context.d.ts +29 -0
- package/components/identity.clients/operations/lib/Context.js +2 -0
- package/components/identity.clients/operations/lib/Context.js.map +1 -0
- package/components/identity.clients/operations/lib/Entity.d.ts +29 -0
- package/components/identity.clients/operations/lib/Entity.js +2 -0
- package/components/identity.clients/operations/lib/Entity.js.map +1 -0
- package/components/identity.clients/operations/lib/canonical.d.ts +11 -0
- package/components/identity.clients/operations/lib/canonical.js +24 -0
- package/components/identity.clients/operations/lib/canonical.js.map +1 -0
- package/components/identity.clients/operations/lib/cimd.d.ts +8 -0
- package/components/identity.clients/operations/lib/cimd.js +86 -0
- package/components/identity.clients/operations/lib/cimd.js.map +1 -0
- package/components/identity.clients/operations/lib/errors.d.ts +7 -0
- package/components/identity.clients/operations/lib/errors.js +5 -0
- package/components/identity.clients/operations/lib/errors.js.map +1 -0
- package/components/identity.clients/operations/lib/index.d.ts +6 -0
- package/components/identity.clients/operations/lib/index.js +7 -0
- package/components/identity.clients/operations/lib/index.js.map +1 -0
- package/components/identity.clients/operations/lib/redirect.d.ts +10 -0
- package/components/identity.clients/operations/lib/redirect.js +32 -0
- package/components/identity.clients/operations/lib/redirect.js.map +1 -0
- package/components/identity.clients/operations/register.d.ts +37 -0
- package/components/identity.clients/operations/register.js +84 -0
- package/components/identity.clients/operations/register.js.map +1 -0
- package/components/identity.clients/source/describe.ts +65 -0
- package/components/identity.clients/source/lib/Context.ts +38 -0
- package/components/identity.clients/source/lib/Entity.ts +31 -0
- package/components/identity.clients/source/lib/canonical.test.ts +43 -0
- package/components/identity.clients/source/lib/canonical.ts +27 -0
- package/components/identity.clients/source/lib/cimd.test.ts +120 -0
- package/components/identity.clients/source/lib/cimd.ts +107 -0
- package/components/identity.clients/source/lib/errors.ts +4 -0
- package/components/identity.clients/source/lib/index.ts +6 -0
- package/components/identity.clients/source/lib/redirect.test.ts +41 -0
- package/components/identity.clients/source/lib/redirect.ts +35 -0
- package/components/identity.clients/source/register.ts +125 -0
- package/components/identity.clients/tsconfig.json +10 -0
- package/components/identity.clients/tsconfig.tsbuildinfo +1 -0
- package/components/identity.clients/types/index.d.ts +3 -0
- package/components/identity.clients/types/toa.d.ts +109 -0
- package/components/identity.credentials/operations/list.d.ts +2 -1
- package/components/identity.credentials/operations/list.js.map +1 -1
- package/components/identity.credentials/source/list.ts +2 -1
- package/components/identity.credentials/tsconfig.tsbuildinfo +1 -1
- package/components/identity.credentials/types/index.d.ts +3 -0
- package/components/identity.credentials/types/toa.d.ts +30 -0
- package/components/identity.federation/manifest.toa.yaml +37 -1
- package/components/identity.federation/operations/authenticate.d.ts +1 -1
- package/components/identity.federation/operations/lib/Ctx.d.ts +2 -2
- package/components/identity.federation/operations/lib/decode.js +22 -8
- package/components/identity.federation/operations/lib/decode.js.map +1 -1
- package/components/identity.federation/operations/lib/errors.d.ts +7 -0
- package/components/identity.federation/operations/lib/errors.js +4 -0
- package/components/identity.federation/operations/lib/errors.js.map +1 -1
- package/components/identity.federation/operations/lib/exchange.js +11 -6
- package/components/identity.federation/operations/lib/exchange.js.map +1 -1
- package/components/identity.federation/operations/types/configuration.d.ts +1 -1
- package/components/identity.federation/operations/types/context.d.ts +3 -2
- package/components/identity.federation/source/authenticate.ts +1 -1
- package/components/identity.federation/source/lib/Ctx.ts +2 -2
- package/components/identity.federation/source/lib/decode.ts +25 -9
- package/components/identity.federation/source/lib/errors.ts +5 -0
- package/components/identity.federation/source/lib/exchange.ts +11 -7
- package/components/identity.federation/source/types/configuration.ts +1 -1
- package/components/identity.federation/source/types/context.ts +3 -2
- package/components/identity.federation/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/types/index.d.ts +3 -0
- package/components/identity.federation/types/toa.d.ts +127 -0
- package/components/identity.grants/manifest.toa.yaml +245 -0
- package/components/identity.grants/operations/authorize.d.ts +35 -0
- package/components/identity.grants/operations/authorize.js +54 -0
- package/components/identity.grants/operations/authorize.js.map +1 -0
- package/components/identity.grants/operations/exchange.d.ts +37 -0
- package/components/identity.grants/operations/exchange.js +89 -0
- package/components/identity.grants/operations/exchange.js.map +1 -0
- package/components/identity.grants/operations/lib/code.d.ts +15 -0
- package/components/identity.grants/operations/lib/code.js +30 -0
- package/components/identity.grants/operations/lib/code.js.map +1 -0
- package/components/identity.grants/operations/lib/identify.d.ts +6 -0
- package/components/identity.grants/operations/lib/identify.js +15 -0
- package/components/identity.grants/operations/lib/identify.js.map +1 -0
- package/components/identity.grants/operations/lib/index.d.ts +3 -0
- package/components/identity.grants/operations/lib/index.js +3 -0
- package/components/identity.grants/operations/lib/index.js.map +1 -0
- package/components/identity.grants/operations/list.d.ts +13 -0
- package/components/identity.grants/operations/list.js +18 -0
- package/components/identity.grants/operations/list.js.map +1 -0
- package/components/identity.grants/operations/revoke.d.ts +18 -0
- package/components/identity.grants/operations/revoke.js +25 -0
- package/components/identity.grants/operations/revoke.js.map +1 -0
- package/components/identity.grants/source/authorize.ts +90 -0
- package/components/identity.grants/source/exchange.ts +130 -0
- package/components/identity.grants/source/lib/code.ts +39 -0
- package/components/identity.grants/source/lib/identify.ts +16 -0
- package/components/identity.grants/source/lib/index.ts +3 -0
- package/components/identity.grants/source/list.ts +28 -0
- package/components/identity.grants/source/revoke.ts +38 -0
- package/components/identity.grants/tsconfig.json +10 -0
- package/components/identity.grants/tsconfig.tsbuildinfo +1 -0
- package/components/identity.grants/types/index.d.ts +60 -0
- package/components/identity.grants/types/toa.d.ts +108 -0
- package/components/identity.keys/manifest.toa.yaml +1 -0
- package/components/identity.keys/operations/create.d.ts +1 -1
- package/components/identity.keys/operations/revoke.d.ts +1 -1
- package/components/identity.keys/source/create.ts +1 -1
- package/components/identity.keys/source/revoke.ts +1 -1
- package/components/identity.keys/tsconfig.tsbuildinfo +1 -1
- package/components/identity.keys/types/index.d.ts +3 -0
- package/components/identity.keys/types/toa.d.ts +47 -0
- package/components/identity.otp/operations/lib/Context.d.ts +4 -2
- package/components/identity.otp/source/lib/Context.ts +4 -2
- package/components/identity.otp/tsconfig.tsbuildinfo +1 -1
- package/components/identity.otp/types/index.d.ts +3 -0
- package/components/identity.otp/types/toa.d.ts +56 -0
- package/components/identity.passkeys/manifest.toa.yaml +3 -0
- package/components/identity.passkeys/operations/authenticate.d.ts +1 -1
- package/components/identity.passkeys/operations/challenge.d.ts +1 -1
- package/components/identity.passkeys/operations/create.d.ts +1 -1
- package/components/identity.passkeys/operations/types/Context.d.ts +2 -1
- package/components/identity.passkeys/operations/use.d.ts +1 -1
- package/components/identity.passkeys/source/authenticate.ts +1 -1
- package/components/identity.passkeys/source/challenge.ts +1 -1
- package/components/identity.passkeys/source/create.ts +1 -1
- package/components/identity.passkeys/source/types/Context.ts +2 -1
- package/components/identity.passkeys/source/use.ts +1 -1
- package/components/identity.passkeys/tsconfig.tsbuildinfo +1 -1
- package/components/identity.passkeys/types/index.d.ts +3 -0
- package/components/identity.passkeys/types/toa.d.ts +120 -0
- package/components/identity.roles/operations/principal.d.ts +1 -1
- package/components/identity.roles/source/principal.ts +1 -1
- package/components/identity.roles/tsconfig.tsbuildinfo +1 -1
- package/components/identity.roles/types/index.d.ts +3 -0
- package/components/identity.roles/types/toa.d.ts +43 -0
- package/components/identity.tokens/manifest.toa.yaml +34 -3
- package/components/identity.tokens/operations/authenticate.d.ts +2 -1
- package/components/identity.tokens/operations/authenticate.js +8 -0
- package/components/identity.tokens/operations/authenticate.js.map +1 -1
- package/components/identity.tokens/operations/decrypt.d.ts +2 -1
- package/components/identity.tokens/operations/decrypt.js +2 -2
- package/components/identity.tokens/operations/decrypt.js.map +1 -1
- package/components/identity.tokens/operations/encrypt.d.ts +2 -1
- package/components/identity.tokens/operations/encrypt.js.map +1 -1
- package/components/identity.tokens/operations/issue.d.ts +2 -1
- package/components/identity.tokens/operations/issue.js +2 -1
- package/components/identity.tokens/operations/issue.js.map +1 -1
- package/components/identity.tokens/operations/lib/form.d.ts +8 -0
- package/components/identity.tokens/operations/lib/form.js +24 -0
- package/components/identity.tokens/operations/lib/form.js.map +1 -0
- package/components/identity.tokens/operations/lib/index.d.ts +2 -1
- package/components/identity.tokens/operations/lib/index.js +1 -1
- package/components/identity.tokens/operations/lib/index.js.map +1 -1
- package/components/identity.tokens/source/authenticate.test.ts +8 -5
- package/components/identity.tokens/source/authenticate.ts +12 -1
- package/components/identity.tokens/source/decrypt.test.ts +1 -1
- package/components/identity.tokens/source/decrypt.ts +4 -3
- package/components/identity.tokens/source/encrypt.test.ts +1 -1
- package/components/identity.tokens/source/encrypt.ts +2 -1
- package/components/identity.tokens/source/issue.test.ts +71 -0
- package/components/identity.tokens/source/issue.ts +4 -2
- package/components/identity.tokens/source/lib/form.test.ts +25 -0
- package/components/identity.tokens/source/lib/form.ts +31 -0
- package/components/identity.tokens/source/lib/index.ts +2 -1
- package/components/identity.tokens/tsconfig.tsbuildinfo +1 -1
- package/components/identity.tokens/types/index.d.ts +61 -0
- package/components/identity.tokens/types/toa.d.ts +113 -0
- package/documentation/cache.md +10 -1
- package/documentation/consent.md +101 -0
- package/documentation/identity.md +3 -1
- package/documentation/io.md +36 -2
- package/documentation/oauth.md +179 -0
- package/documentation/protocol.md +4 -0
- package/features/auth.bearer.feature +120 -0
- package/features/cache.feature +46 -0
- package/features/oauth.clients.feature +195 -0
- package/features/oauth.discovery.feature +165 -0
- package/features/oauth.grants.feature +286 -0
- package/features/steps/Gateway.ts +6 -1
- package/features/steps/Parameters.ts +4 -0
- package/features/steps/components/echo/manifest.toa.yaml +2 -0
- package/features/steps/components/octets.tester/manifest.toa.yaml +2 -0
- package/features/steps/components/octets.tester/operations/redirect.js +6 -1
- package/features/steps/components/users/manifest.toa.yaml +1 -0
- package/package.json +8 -6
- package/readme.md +3 -0
- package/schemas/annotation.cos.yaml +30 -0
- package/source/Annotation.ts +23 -0
- package/source/Factory.ts +1 -1
- package/source/HTTP/Server.ts +9 -5
- package/source/HTTP/formats/form.ts +21 -0
- package/source/HTTP/formats/index.ts +17 -2
- package/source/HTTP/messages.test.ts +25 -1
- package/source/HTTP/messages.ts +10 -3
- package/source/Interception.ts +8 -2
- package/source/deployment.ts +4 -1
- package/source/directives/auth/Authorization.ts +46 -19
- package/source/directives/auth/Incept.ts +14 -9
- package/source/directives/auth/schemes.test.ts +8 -4
- package/source/directives/auth/schemes.ts +22 -9
- package/source/directives/auth/types.ts +5 -2
- package/source/directives/cache/Cache.ts +13 -4
- package/source/directives/cache/Control.ts +0 -3
- package/source/directives/flow/Fetch.ts +1 -1
- package/source/directives/index.ts +2 -1
- package/source/directives/io/IO.ts +2 -0
- package/source/directives/io/Output.ts +4 -1
- package/source/directives/io/Status.test.ts +84 -0
- package/source/directives/io/Status.ts +46 -0
- package/source/directives/oauth/Discovery.ts +76 -0
- package/source/directives/oauth/documents.ts +113 -0
- package/source/directives/oauth/index.ts +3 -0
- package/source/directives/octets/Delete.ts +1 -1
- package/source/directives/octets/Get.ts +1 -1
- package/source/directives/octets/Workflow.ts +1 -1
- package/source/directives/octets/workflows/Execution.ts +1 -1
- package/source/index.ts +6 -0
- package/source/octets.d.ts +17 -0
- package/source/userland.ts +47 -0
- package/transpiled/Annotation.d.ts +17 -0
- package/transpiled/Factory.js +1 -1
- package/transpiled/Factory.js.map +1 -1
- package/transpiled/HTTP/Server.d.ts +4 -1
- package/transpiled/HTTP/Server.js +4 -3
- package/transpiled/HTTP/Server.js.map +1 -1
- package/transpiled/HTTP/formats/form.d.ts +9 -0
- package/transpiled/HTTP/formats/form.js +18 -0
- package/transpiled/HTTP/formats/form.js.map +1 -0
- package/transpiled/HTTP/formats/index.d.ts +10 -2
- package/transpiled/HTTP/formats/index.js +10 -0
- package/transpiled/HTTP/formats/index.js.map +1 -1
- package/transpiled/HTTP/messages.d.ts +6 -0
- package/transpiled/HTTP/messages.js +3 -3
- package/transpiled/HTTP/messages.js.map +1 -1
- package/transpiled/Interception.d.ts +4 -1
- package/transpiled/Interception.js +4 -2
- package/transpiled/Interception.js.map +1 -1
- package/transpiled/deployment.js +2 -0
- package/transpiled/deployment.js.map +1 -1
- package/transpiled/directives/auth/Authorization.d.ts +1 -1
- package/transpiled/directives/auth/Authorization.js +38 -18
- package/transpiled/directives/auth/Authorization.js.map +1 -1
- package/transpiled/directives/auth/Incept.d.ts +1 -1
- package/transpiled/directives/auth/Incept.js +11 -7
- package/transpiled/directives/auth/Incept.js.map +1 -1
- package/transpiled/directives/auth/schemes.d.ts +16 -4
- package/transpiled/directives/auth/schemes.js +20 -8
- package/transpiled/directives/auth/schemes.js.map +1 -1
- package/transpiled/directives/auth/types.d.ts +4 -2
- package/transpiled/directives/cache/Cache.js +9 -2
- package/transpiled/directives/cache/Cache.js.map +1 -1
- package/transpiled/directives/cache/Control.js +0 -2
- package/transpiled/directives/cache/Control.js.map +1 -1
- package/transpiled/directives/index.js +2 -1
- package/transpiled/directives/index.js.map +1 -1
- package/transpiled/directives/io/IO.js +2 -0
- package/transpiled/directives/io/IO.js.map +1 -1
- package/transpiled/directives/io/Output.js +4 -1
- package/transpiled/directives/io/Output.js.map +1 -1
- package/transpiled/directives/io/Status.d.ts +19 -0
- package/transpiled/directives/io/Status.js +34 -0
- package/transpiled/directives/io/Status.js.map +1 -0
- package/transpiled/directives/oauth/Discovery.d.ts +23 -0
- package/transpiled/directives/oauth/Discovery.js +58 -0
- package/transpiled/directives/oauth/Discovery.js.map +1 -0
- package/transpiled/directives/oauth/documents.d.ts +9 -0
- package/transpiled/directives/oauth/documents.js +82 -0
- package/transpiled/directives/oauth/documents.js.map +1 -0
- package/transpiled/directives/oauth/index.d.ts +2 -0
- package/transpiled/directives/oauth/index.js +3 -0
- package/transpiled/directives/oauth/index.js.map +1 -0
- package/transpiled/index.d.ts +2 -0
- package/transpiled/userland.d.ts +39 -0
- package/transpiled/userland.js +7 -0
- package/transpiled/userland.js.map +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/components/identity.basic/operations/types.d.ts +0 -61
- package/components/identity.basic/operations/types.js +0 -2
- package/components/identity.basic/operations/types.js.map +0 -1
- package/components/identity.basic/source/types.ts +0 -69
- package/components/identity.tokens/operations/lib/types.d.ts +0 -88
- package/components/identity.tokens/operations/lib/types.js +0 -2
- package/components/identity.tokens/operations/lib/types.js.map +0 -1
- package/components/identity.tokens/source/lib/types.ts +0 -102
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Consent
|
|
2
|
+
|
|
3
|
+
The one endpoint of the [authorization server](oauth.md) that needs a person, and so the one this
|
|
4
|
+
extension does not serve. An application serves it at the URL its `exposition.oauth.authorize`
|
|
5
|
+
names.
|
|
6
|
+
|
|
7
|
+
The page is reached by a client opening it in the user's browser. It authenticates the user, shows
|
|
8
|
+
them who is asking, and on their assent redirects back to the client with a code.
|
|
9
|
+
|
|
10
|
+
## What the page receives
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
GET https://app.example.com/oauth/authorize
|
|
14
|
+
?response_type=code
|
|
15
|
+
&client_id=https%3A%2F%2Fclaude.ai%2Foauth%2Fclaude-code-client-metadata
|
|
16
|
+
&redirect_uri=http%3A%2F%2Flocalhost%3A3118%2Fcallback
|
|
17
|
+
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
|
|
18
|
+
&code_challenge_method=S256
|
|
19
|
+
&state=af0ifjsldkj
|
|
20
|
+
&resource=https%3A%2F%2Fapi.example.com%2Fmcp
|
|
21
|
+
&scope=app:notes
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`state` is the client's and is returned untouched. `code_challenge`, `code_challenge_method`,
|
|
25
|
+
`client_id`, `redirect_uri` and `resource` are passed on as they arrived.
|
|
26
|
+
|
|
27
|
+
## What the page does
|
|
28
|
+
|
|
29
|
+
The user is authenticated by whatever means the application already offers. The page then holds
|
|
30
|
+
that identity's own `Token`, and makes two calls with it.
|
|
31
|
+
|
|
32
|
+
Who is asking:
|
|
33
|
+
|
|
34
|
+
```http
|
|
35
|
+
GET /identity/clients/https%3A%2F%2Fclaude.ai%2Foauth%2Fclaude-code-client-metadata/ HTTP/1.1
|
|
36
|
+
authorization: Token …
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
client_id: https://claude.ai/oauth/claude-code-client-metadata
|
|
41
|
+
client_name: Claude Code
|
|
42
|
+
client_uri: https://claude.ai
|
|
43
|
+
logo_uri: https://claude.ai/logo.png
|
|
44
|
+
redirect_uris: [http://localhost/callback, http://127.0.0.1/callback]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The page displays `client_name`, and **the host of `redirect_uri`**. A loopback redirect is
|
|
48
|
+
indistinguishable from any other local process, so the user is told where the code will be sent.
|
|
49
|
+
|
|
50
|
+
On assent:
|
|
51
|
+
|
|
52
|
+
```http
|
|
53
|
+
POST /identity/grants/:identity/ HTTP/1.1
|
|
54
|
+
authorization: Token …
|
|
55
|
+
content-type: application/json
|
|
56
|
+
|
|
57
|
+
{"client": "…", "redirect": "…", "challenge": "…", "method": "S256",
|
|
58
|
+
"scope": ["app:notes"], "resource": ["https://api.example.com/mcp"]}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```http
|
|
62
|
+
201 Created
|
|
63
|
+
cache-control: no-store
|
|
64
|
+
|
|
65
|
+
{"code": "SplxlOBeZQQYbYS6WxSbIA", "expires_in": 60}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`:identity` is the id of the identity that consents, which is the one the page authenticated.
|
|
69
|
+
|
|
70
|
+
## What the page answers
|
|
71
|
+
|
|
72
|
+
On assent, a redirect carrying the code, the client's `state`, and the issuer:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
302 Found
|
|
76
|
+
location: http://localhost:3118/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj&iss=https%3A%2F%2Fapi.example.com
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
A single-page application does this with `location.assign`; nothing requires a server redirect.
|
|
80
|
+
|
|
81
|
+
`iss` is [RFC 9207](https://www.rfc-editor.org/rfc/rfc9207), and the metadata declares that it is
|
|
82
|
+
sent, so a client rejects a response without it. Its value is the `issuer` of
|
|
83
|
+
`/.well-known/oauth-authorization-server`, which the page may read.
|
|
84
|
+
|
|
85
|
+
On refusal, a redirect saying so, not an error page:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
302 Found
|
|
89
|
+
location: http://localhost:3118/callback?error=access_denied&state=af0ifjsldkj&iss=https%3A%2F%2Fapi.example.com
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Errors
|
|
93
|
+
|
|
94
|
+
`400` with `error` and `error_description` is answered when the client is unknown, when the
|
|
95
|
+
redirect is not one that client may receive a code at, or when the challenge method is not `S256`.
|
|
96
|
+
A redirect that was not registered must not be redirected to; the page states the refusal itself.
|
|
97
|
+
|
|
98
|
+
## Consent is asked every time
|
|
99
|
+
|
|
100
|
+
A grant is recorded, and `GET /identity/grants/:identity/` lists what a user allowed, but it is
|
|
101
|
+
not consulted to skip this page.
|
|
@@ -83,7 +83,9 @@ the request path.
|
|
|
83
83
|
|
|
84
84
|
### Bearer scheme
|
|
85
85
|
|
|
86
|
-
OpenID tokens issued by trusted providers
|
|
86
|
+
OpenID tokens issued by trusted providers, and access tokens issued by
|
|
87
|
+
[this authorization server](oauth.md) to OAuth clients. Both are presented the same way, and each
|
|
88
|
+
provider says which credentials are its own; a credential neither claims is refused.
|
|
87
89
|
For more information, refer
|
|
88
90
|
to [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html),
|
|
89
91
|
[RFC6750](https://datatracker.ietf.org/doc/html/rfc6750).
|
package/documentation/io.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# I/O restrictions
|
|
2
2
|
|
|
3
3
|
The Exposition comes with `io` directives to control access to the operation's input and output
|
|
4
|
-
properties.
|
|
4
|
+
properties, and to let an operation state the status of its reply.
|
|
5
5
|
|
|
6
6
|
## Input
|
|
7
7
|
|
|
@@ -53,7 +53,41 @@ GET:
|
|
|
53
53
|
io:output: false
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
Output restrictions are not applied to stream responses
|
|
56
|
+
Output restrictions are not applied to stream responses, nor to a reply the gateway built out of an
|
|
57
|
+
exception — an operation that returns an error is answered with a code and a message of the
|
|
58
|
+
gateway's own, and a list of permitted properties has nothing to say about those. A reply the
|
|
59
|
+
operation returned is restricted whatever status it carries, see [Status](#status).
|
|
60
|
+
|
|
61
|
+
## Status
|
|
62
|
+
|
|
63
|
+
The `io:status` optional directive names a property of the reply that carries the status of the
|
|
64
|
+
response. The property is removed from the body.
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
POST:
|
|
68
|
+
endpoint: register
|
|
69
|
+
io:status: status
|
|
70
|
+
io:output: [client_id, client_name, error, error_description]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
return { status: 400, error: 'invalid_redirect_uri' }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```http
|
|
78
|
+
400 Bad Request
|
|
79
|
+
|
|
80
|
+
error: invalid_redirect_uri
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A reply that does not carry the property is answered with the status it would have been. A property
|
|
84
|
+
that is not a number is an error.
|
|
85
|
+
|
|
86
|
+
This is for an outcome the operation knows and the transport does not — a protocol that states its
|
|
87
|
+
error as a body rather than as a status of its own, or a request that created where it might have
|
|
88
|
+
updated. Such an operation returns no error: both replies are its output, and `io:output` permits
|
|
89
|
+
the properties of both. An operation that does return an error is unaffected — that reply is the
|
|
90
|
+
gateway's, and is not restricted.
|
|
57
91
|
|
|
58
92
|
## Throttling
|
|
59
93
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# OAuth 2.1 Authorization Server
|
|
2
|
+
|
|
3
|
+
An application exposes an authorization server by annotating one, and none without it. A client
|
|
4
|
+
authorized this way holds a token of the identity that consented, and presents it as `Bearer`.
|
|
5
|
+
|
|
6
|
+
## Annotation
|
|
7
|
+
|
|
8
|
+
```yaml
|
|
9
|
+
exposition:
|
|
10
|
+
authorities:
|
|
11
|
+
local: api.example.com
|
|
12
|
+
oauth:
|
|
13
|
+
authorize: https://app.example.com/oauth/authorize
|
|
14
|
+
resources: ['/mcp/']
|
|
15
|
+
scopes: [app:notes, app:notes:read]
|
|
16
|
+
registration: closed
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
<dl>
|
|
20
|
+
<dt><code>authorize</code></dt>
|
|
21
|
+
<dd>Required. The absolute URL of the consent page. The application serves it; see
|
|
22
|
+
<a href="consent.md">Consent</a>.</dd>
|
|
23
|
+
<dt><code>resources</code></dt>
|
|
24
|
+
<dd>Paths advertised as protected resources, each with a document of its own and named by the
|
|
25
|
+
challenge a request to it is refused with. A token is not yet bound to one, see
|
|
26
|
+
<a href="#audience">Audience</a>.</dd>
|
|
27
|
+
<dt><code>scopes</code></dt>
|
|
28
|
+
<dd>Advertised as what a client may ask for. Each is a <a href="access.md#roles">role</a>, or a
|
|
29
|
+
scope within one.</dd>
|
|
30
|
+
<dt><code>registration</code></dt>
|
|
31
|
+
<dd><code>open</code> advertises the registration endpoint, <code>closed</code> does not.
|
|
32
|
+
Defaults to <code>closed</code>.</dd>
|
|
33
|
+
</dl>
|
|
34
|
+
|
|
35
|
+
The issuer is the authority's configured host, over `https` unless the host is a loopback one.
|
|
36
|
+
|
|
37
|
+
## Discovery
|
|
38
|
+
|
|
39
|
+
Two documents are served, at the paths their specifications fix, to anyone.
|
|
40
|
+
|
|
41
|
+
`/.well-known/oauth-authorization-server` is
|
|
42
|
+
[RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) metadata:
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
issuer: https://api.example.com
|
|
46
|
+
authorization_endpoint: https://app.example.com/oauth/authorize
|
|
47
|
+
token_endpoint: https://api.example.com/identity/grants/
|
|
48
|
+
registration_endpoint: https://api.example.com/identity/clients/
|
|
49
|
+
response_types_supported: [code]
|
|
50
|
+
grant_types_supported: [authorization_code]
|
|
51
|
+
code_challenge_methods_supported: [S256]
|
|
52
|
+
token_endpoint_auth_methods_supported: [none]
|
|
53
|
+
client_id_metadata_document_supported: true
|
|
54
|
+
authorization_response_iss_parameter_supported: true
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`/.well-known/oauth-protected-resource` is
|
|
58
|
+
[RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) metadata. Each configured resource has one of
|
|
59
|
+
its own, at the path the well-known segment is followed by: `/mcp/` is read at
|
|
60
|
+
`/.well-known/oauth-protected-resource/mcp`.
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
resource: https://api.example.com/mcp
|
|
64
|
+
authorization_servers: [https://api.example.com]
|
|
65
|
+
bearer_methods_supported: [header]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`/.well-known/openid-configuration` answers with the authorization server metadata, for a client
|
|
69
|
+
that reads only that.
|
|
70
|
+
|
|
71
|
+
A reply of `401` carries the challenge that names the document:
|
|
72
|
+
|
|
73
|
+
```http
|
|
74
|
+
401 Unauthorized
|
|
75
|
+
www-authenticate: Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource/mcp", scope="app:notes"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Clients
|
|
79
|
+
|
|
80
|
+
A `client_id` that is an `https` URL is a
|
|
81
|
+
[Client ID Metadata Document](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00):
|
|
82
|
+
the client publishes what it is at that URL, and the document is read from there. Only origins
|
|
83
|
+
`identity.clients` is configured to trust are read, and an empty list reads none:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
configuration:
|
|
87
|
+
identity.clients:
|
|
88
|
+
trust:
|
|
89
|
+
- https://claude.ai
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Anything else was registered through
|
|
93
|
+
[RFC 7591](https://www.rfc-editor.org/rfc/rfc7591), which `registration: open` advertises:
|
|
94
|
+
|
|
95
|
+
```http
|
|
96
|
+
POST /identity/clients/ HTTP/1.1
|
|
97
|
+
content-type: application/json
|
|
98
|
+
|
|
99
|
+
{"client_name": "Claude", "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"]}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```http
|
|
103
|
+
201 Created
|
|
104
|
+
|
|
105
|
+
{"client_id": "c8f3a1e4d8c9b2f6a0e5d7c3b1a9f8e2", "client_name": "Claude", …}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
A registration is addressed by a hash of what it says, so the same metadata is one client however
|
|
109
|
+
many times it arrives. A registration cannot be changed: a revision is a different client.
|
|
110
|
+
|
|
111
|
+
Only public clients are supported. `token_endpoint_auth_method` other than `none` is refused.
|
|
112
|
+
|
|
113
|
+
`redirect_uris` are matched exactly, except for a loopback address, where the port is ignored
|
|
114
|
+
([RFC 8252 §7.3](https://datatracker.ietf.org/doc/html/rfc8252#section-7.3)).
|
|
115
|
+
|
|
116
|
+
`GET /identity/clients/:id/` returns a client to any authenticated identity, which is what the
|
|
117
|
+
consent page reads.
|
|
118
|
+
|
|
119
|
+
## Token endpoint
|
|
120
|
+
|
|
121
|
+
`POST /identity/grants/`, anonymous, `application/x-www-form-urlencoded`.
|
|
122
|
+
|
|
123
|
+
```http
|
|
124
|
+
POST /identity/grants/ HTTP/1.1
|
|
125
|
+
content-type: application/x-www-form-urlencoded
|
|
126
|
+
|
|
127
|
+
grant_type=authorization_code&code=SplxlO&code_verifier=dBjftJeZ&redirect_uri=…&client_id=…
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```http
|
|
131
|
+
200 OK
|
|
132
|
+
cache-control: no-store
|
|
133
|
+
|
|
134
|
+
{"access_token": "…", "token_type": "Bearer", "expires_in": 2592000}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`code_verifier` is required: the code was issued against its `S256` hash
|
|
138
|
+
([RFC 7636](https://www.rfc-editor.org/rfc/rfc7636)). A code may be redeemed once, and is spent
|
|
139
|
+
whether or not the verifier matches. Every refusal is `invalid_grant`.
|
|
140
|
+
|
|
141
|
+
A code is valid for `identity.grants.lifetime` seconds, 60 by default. A token is valid for
|
|
142
|
+
`identity.grants.token` seconds, 30 days by default; `0` is until it is revoked.
|
|
143
|
+
|
|
144
|
+
## Grants
|
|
145
|
+
|
|
146
|
+
A grant is what a user allowed a client. One is held per identity per client.
|
|
147
|
+
|
|
148
|
+
`GET /identity/grants/:identity/` lists them. `DELETE /identity/grants/:identity/:id/` revokes one,
|
|
149
|
+
which disables the key its token was issued under: the token stops being one within
|
|
150
|
+
`identity.tokens.cache.ttl`.
|
|
151
|
+
|
|
152
|
+
## Tokens
|
|
153
|
+
|
|
154
|
+
An access token is a token of the identity that consented, carrying the
|
|
155
|
+
[roles](access.md#roles) that identity holds, or the subset the client asked for as `scope`. It is
|
|
156
|
+
presented as `Bearer`, which `identity.federation` also answers to — see
|
|
157
|
+
[Bearer scheme](identity.md#bearer-scheme).
|
|
158
|
+
|
|
159
|
+
A `scope` a client asks for is not checked against `scopes_supported`: it is granted if the
|
|
160
|
+
consenting identity holds it, and refused as `invalid_scope` otherwise. What is advertised says
|
|
161
|
+
what an application means to offer, not what the server will refuse.
|
|
162
|
+
|
|
163
|
+
## Audience
|
|
164
|
+
|
|
165
|
+
`resource` ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707)) is accepted at both endpoints and
|
|
166
|
+
recorded on the grant. **It does not yet restrict the token.** An access token carries the rights
|
|
167
|
+
of the identity that consented, over every path that identity may reach, whichever resource it was
|
|
168
|
+
asked for.
|
|
169
|
+
|
|
170
|
+
Restricting it is a `permissions` argument at the `identity.tokens.issue` call in
|
|
171
|
+
`identity.grants.exchange` — `{'/mcp/**': ['*']}` for a resource at `/mcp/` — enforced by
|
|
172
|
+
`permits()`, which is already what reads a token's permissions on every request. It waits on the
|
|
173
|
+
resource that will check the audience itself.
|
|
174
|
+
|
|
175
|
+
## References
|
|
176
|
+
|
|
177
|
+
- [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1)
|
|
178
|
+
- [Consent](consent.md), the page an application serves
|
|
179
|
+
- [Features](../features/oauth.grants.feature)
|
|
@@ -9,6 +9,10 @@ The following media types are supported for both requests and responses:
|
|
|
9
9
|
- `application/msgpack` using [msgpackr](https://github.com/kriszyp/msgpackr)
|
|
10
10
|
- `text/plain`
|
|
11
11
|
|
|
12
|
+
A request may also be sent as `application/x-www-form-urlencoded`. A form is read and never
|
|
13
|
+
written, so it is not a format a response is negotiated to. A name repeated in a form is read as
|
|
14
|
+
the list it is.
|
|
15
|
+
|
|
12
16
|
The response format is determined by content negotiation
|
|
13
17
|
using [negotiator](https://github.com/jshttp/negotiator).
|
|
14
18
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
@security
|
|
2
|
+
Feature: More than one provider claims Bearer
|
|
3
|
+
|
|
4
|
+
An OpenID `id_token` and a token this gateway issued are both presented as `Bearer`,
|
|
5
|
+
and only their own provider can verify either. The gateway asks each in turn.
|
|
6
|
+
|
|
7
|
+
Background:
|
|
8
|
+
Given local IDP is running
|
|
9
|
+
And the `identity.basic` database contains:
|
|
10
|
+
| _id | authority | username | password |
|
|
11
|
+
| efe3a65ebbee47ed95a73edd911ea328 | nex | developer | $2b$10$ZRSKkgZoGnrcTNA5w5eCcu3pxDzdTduhteVYXcp56AaNcilNkwJ.O |
|
|
12
|
+
And the annotation:
|
|
13
|
+
"""yaml
|
|
14
|
+
/:
|
|
15
|
+
io:output: true
|
|
16
|
+
/hello/:id:
|
|
17
|
+
auth:id: id
|
|
18
|
+
GET:
|
|
19
|
+
dev:stub: Hello
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
Scenario: A token this gateway issued is presented as Bearer
|
|
23
|
+
When the following request is received:
|
|
24
|
+
"""
|
|
25
|
+
GET /hello/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
|
|
26
|
+
host: nex.toa.io
|
|
27
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
28
|
+
accept: text/plain
|
|
29
|
+
"""
|
|
30
|
+
Then the following reply is sent:
|
|
31
|
+
"""
|
|
32
|
+
200 OK
|
|
33
|
+
authorization: Token ${{ token }}
|
|
34
|
+
content-type: text/plain
|
|
35
|
+
|
|
36
|
+
Hello
|
|
37
|
+
"""
|
|
38
|
+
When the following request is received:
|
|
39
|
+
"""
|
|
40
|
+
GET /hello/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
|
|
41
|
+
host: nex.toa.io
|
|
42
|
+
authorization: Bearer ${{ token }}
|
|
43
|
+
accept: text/plain
|
|
44
|
+
"""
|
|
45
|
+
Then the following reply is sent:
|
|
46
|
+
"""
|
|
47
|
+
200 OK
|
|
48
|
+
content-type: text/plain
|
|
49
|
+
|
|
50
|
+
Hello
|
|
51
|
+
"""
|
|
52
|
+
# the token was answered by its own provider, so nothing rotates it
|
|
53
|
+
And the reply does not contain:
|
|
54
|
+
"""
|
|
55
|
+
authorization: Token
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
Scenario: A Bearer this gateway did not issue is answered by federation
|
|
59
|
+
Given the IDP token for Stranger is issued
|
|
60
|
+
When the following request is received:
|
|
61
|
+
"""
|
|
62
|
+
GET /hello/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
|
|
63
|
+
host: nex.toa.io
|
|
64
|
+
authorization: Bearer ${{ Stranger.id_token }}
|
|
65
|
+
accept: text/plain
|
|
66
|
+
"""
|
|
67
|
+
# `identity.tokens` declines it, and federation rejects an issuer it does not trust
|
|
68
|
+
Then the following reply is sent:
|
|
69
|
+
"""
|
|
70
|
+
401 Unauthorized
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
Scenario: A malformed Bearer is rejected, not an error
|
|
74
|
+
When the following request is received:
|
|
75
|
+
"""
|
|
76
|
+
GET /hello/efe3a65ebbee47ed95a73edd911ea328/ HTTP/1.1
|
|
77
|
+
host: nex.toa.io
|
|
78
|
+
authorization: Bearer not.a.token
|
|
79
|
+
accept: text/plain
|
|
80
|
+
"""
|
|
81
|
+
Then the following reply is sent:
|
|
82
|
+
"""
|
|
83
|
+
401 Unauthorized
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
Scenario: Both kinds of Bearer against one authority
|
|
87
|
+
Given the `identity.federation` configuration:
|
|
88
|
+
"""yaml
|
|
89
|
+
trust:
|
|
90
|
+
- iss: http://localhost:44444
|
|
91
|
+
aud: test
|
|
92
|
+
"""
|
|
93
|
+
And the IDP token for User is issued
|
|
94
|
+
When the following request is received:
|
|
95
|
+
"""
|
|
96
|
+
GET /identity/ HTTP/1.1
|
|
97
|
+
host: nex.toa.io
|
|
98
|
+
authorization: Bearer ${{ User.id_token }}
|
|
99
|
+
accept: application/yaml
|
|
100
|
+
"""
|
|
101
|
+
Then the following reply is sent:
|
|
102
|
+
"""
|
|
103
|
+
200 OK
|
|
104
|
+
authorization: Token ${{ federated }}
|
|
105
|
+
|
|
106
|
+
id: ${{ User.id }}
|
|
107
|
+
"""
|
|
108
|
+
When the following request is received:
|
|
109
|
+
"""
|
|
110
|
+
GET /identity/ HTTP/1.1
|
|
111
|
+
host: nex.toa.io
|
|
112
|
+
authorization: Bearer ${{ federated }}
|
|
113
|
+
accept: application/yaml
|
|
114
|
+
"""
|
|
115
|
+
Then the following reply is sent:
|
|
116
|
+
"""
|
|
117
|
+
200 OK
|
|
118
|
+
|
|
119
|
+
id: ${{ User.id }}
|
|
120
|
+
"""
|
package/features/cache.feature
CHANGED
|
@@ -320,3 +320,49 @@ Feature: Caching
|
|
|
320
320
|
200 OK
|
|
321
321
|
cache-control: private, no-cache
|
|
322
322
|
"""
|
|
323
|
+
|
|
324
|
+
Scenario: An exact value is set whatever the method
|
|
325
|
+
Given the annotation:
|
|
326
|
+
"""yaml
|
|
327
|
+
/:
|
|
328
|
+
io:output: true
|
|
329
|
+
/tokens/:
|
|
330
|
+
anonymous: true
|
|
331
|
+
cache:exact: no-store
|
|
332
|
+
POST:
|
|
333
|
+
dev:stub: issued
|
|
334
|
+
"""
|
|
335
|
+
When the following request is received:
|
|
336
|
+
"""
|
|
337
|
+
POST /tokens/ HTTP/1.1
|
|
338
|
+
host: nex.toa.io
|
|
339
|
+
accept: text/plain
|
|
340
|
+
"""
|
|
341
|
+
# whether a reply may be stored is not a question about the method's cacheability
|
|
342
|
+
Then the following reply is sent:
|
|
343
|
+
"""
|
|
344
|
+
201 Created
|
|
345
|
+
cache-control: no-store
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
Scenario: Cacheability is still asked of safe methods only
|
|
349
|
+
Given the annotation:
|
|
350
|
+
"""yaml
|
|
351
|
+
/:
|
|
352
|
+
io:output: true
|
|
353
|
+
/notes/:
|
|
354
|
+
anonymous: true
|
|
355
|
+
cache:control: max-age=60000, public
|
|
356
|
+
POST:
|
|
357
|
+
dev:stub: created
|
|
358
|
+
"""
|
|
359
|
+
When the following request is received:
|
|
360
|
+
"""
|
|
361
|
+
POST /notes/ HTTP/1.1
|
|
362
|
+
host: nex.toa.io
|
|
363
|
+
accept: text/plain
|
|
364
|
+
"""
|
|
365
|
+
Then the reply does not contain:
|
|
366
|
+
"""
|
|
367
|
+
cache-control:
|
|
368
|
+
"""
|