@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
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
function redirect (input) {
|
|
2
|
-
return input.parameters.type in urls ? { url: urls[input.parameters.type] } :
|
|
2
|
+
return input.parameters.type in urls ? { url: urls[input.parameters.type] } : ERR_UNKNOWN
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
// an error a caller is meant to see carries a code, always
|
|
6
|
+
const ERR_UNKNOWN = new (class UnknownError extends Error {
|
|
7
|
+
code = 'ERROR'
|
|
8
|
+
})()
|
|
9
|
+
|
|
5
10
|
const urls = {
|
|
6
11
|
'rfc': 'https://www.rfc-editor.org/rfc/rfc9564.txt',
|
|
7
12
|
'img': 'https://www.w3.org/assets/logos/w3c/w3c-no-bars.svg'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.exposition",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.283",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa Exposition",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@simplewebauthn/server": "14.0.0",
|
|
22
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
23
|
-
"@toa.io/generic": "1.0.0-alpha.
|
|
24
|
-
"@toa.io/schemas": "1.0.0-alpha.
|
|
22
|
+
"@toa.io/core": "1.0.0-alpha.283",
|
|
23
|
+
"@toa.io/generic": "1.0.0-alpha.283",
|
|
24
|
+
"@toa.io/schemas": "1.0.0-alpha.283",
|
|
25
25
|
"bcryptjs": "3.0.3",
|
|
26
26
|
"content-type": "3.0.0",
|
|
27
27
|
"jose": "6.2.10",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"test": "echo \"Error: run tests from root\" && exit 1",
|
|
39
|
-
"transpile": "tsc && npm run transpile:bans && npm run transpile:basic && npm run transpile:tokens && npm run transpile:roles && npm run transpile:federation && npm run transpile:credentials && npm run transpile:keys && npm run transpile:passkeys && npm run transpile:otp",
|
|
39
|
+
"transpile": "tsc && npm run transpile:bans && npm run transpile:basic && npm run transpile:tokens && npm run transpile:roles && npm run transpile:federation && npm run transpile:credentials && npm run transpile:keys && npm run transpile:passkeys && npm run transpile:otp && npm run transpile:clients && npm run transpile:grants",
|
|
40
40
|
"transpile:bans": "tsc -p ./components/identity.bans",
|
|
41
41
|
"transpile:basic": "tsc -p ./components/identity.basic",
|
|
42
42
|
"transpile:tokens": "tsc -p ./components/identity.tokens",
|
|
@@ -46,6 +46,8 @@
|
|
|
46
46
|
"transpile:keys": "tsc -p ./components/identity.keys",
|
|
47
47
|
"transpile:passkeys": "tsc -p ./components/identity.passkeys",
|
|
48
48
|
"transpile:otp": "tsc -p ./components/identity.otp",
|
|
49
|
+
"transpile:clients": "tsc -p ./components/identity.clients",
|
|
50
|
+
"transpile:grants": "tsc -p ./components/identity.grants",
|
|
49
51
|
"features": "TSX_TSCONFIG_PATH=features/steps/tsconfig.json NODE_OPTIONS=--import=tsx cucumber-js",
|
|
50
52
|
"features:h2c": "TOA_EXPOSITION_PROTOCOL=h2c TOA_AGENT_PROTOCOL=h2c TSX_TSCONFIG_PATH=features/steps/tsconfig.json NODE_OPTIONS=--import=tsx cucumber-js",
|
|
51
53
|
"features:security": "TSX_TSCONFIG_PATH=features/steps/tsconfig.json NODE_OPTIONS=--import=tsx cucumber-js --tags @security",
|
|
@@ -56,5 +58,5 @@
|
|
|
56
58
|
"@types/cors": "2.8.19",
|
|
57
59
|
"@types/negotiator": "0.6.5"
|
|
58
60
|
},
|
|
59
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "2f52b023bed0ccb2ecdb2b82fe59fbe38bf282e1"
|
|
60
62
|
}
|
package/readme.md
CHANGED
|
@@ -138,6 +138,7 @@ exposition:
|
|
|
138
138
|
| `debug` | Output server errors. Default `false`. |
|
|
139
139
|
| `ip` | Header the client address is read from. See [Client address](documentation/ip.md). |
|
|
140
140
|
| `bouncer` | Meter of failed authentications per address. See [Identity authentication](documentation/identity.md#failed-authentications). |
|
|
141
|
+
| `oauth` | Authorization server exposed to OAuth clients. See [OAuth](documentation/oauth.md). |
|
|
141
142
|
|
|
142
143
|
### Observability
|
|
143
144
|
|
|
@@ -207,6 +208,8 @@ See also [telemetry ready probe](../telemetry/readme.md#ready-probe) and
|
|
|
207
208
|
- [Resource Tree Definition](documentation/tree.md)
|
|
208
209
|
- [Identity authentication](documentation/identity.md)
|
|
209
210
|
- [Access authorization](documentation/access.md)
|
|
211
|
+
- [OAuth authorization server](documentation/oauth.md)
|
|
212
|
+
- [Consent](documentation/consent.md)
|
|
210
213
|
- [BLOBs](documentation/octets.md)
|
|
211
214
|
- [Components and resources](documentation/components.md)
|
|
212
215
|
- [Caching](documentation/cache.md)
|
|
@@ -50,6 +50,36 @@ properties:
|
|
|
50
50
|
protocol:
|
|
51
51
|
type: string
|
|
52
52
|
enum: [h1, h2c]
|
|
53
|
+
oauth:
|
|
54
|
+
type: object
|
|
55
|
+
properties:
|
|
56
|
+
authorize:
|
|
57
|
+
description: The absolute URL of the consent page the application serves
|
|
58
|
+
type: string
|
|
59
|
+
pattern: ^https?://
|
|
60
|
+
resources:
|
|
61
|
+
description: Paths a token may be restricted to
|
|
62
|
+
type: array
|
|
63
|
+
items:
|
|
64
|
+
type: string
|
|
65
|
+
pattern: ^/
|
|
66
|
+
scopes:
|
|
67
|
+
type: array
|
|
68
|
+
items:
|
|
69
|
+
type: string
|
|
70
|
+
trust:
|
|
71
|
+
description: Origins whose Client ID Metadata Documents may be read
|
|
72
|
+
type: array
|
|
73
|
+
items:
|
|
74
|
+
type: string
|
|
75
|
+
pattern: ^https://
|
|
76
|
+
registration:
|
|
77
|
+
type: string
|
|
78
|
+
enum: [open, closed]
|
|
79
|
+
default: closed
|
|
80
|
+
required:
|
|
81
|
+
- authorize
|
|
82
|
+
additionalProperties: false
|
|
53
83
|
/: {}
|
|
54
84
|
authorities:
|
|
55
85
|
type: object
|
package/source/Annotation.ts
CHANGED
|
@@ -23,9 +23,32 @@ export interface Annotation {
|
|
|
23
23
|
|
|
24
24
|
/** Failed authentications an address may make; none are metered unless set. See `identity.md`. */
|
|
25
25
|
bouncer?: Bouncer
|
|
26
|
+
|
|
27
|
+
/** The authorization server this context exposes; none is exposed without it. */
|
|
28
|
+
oauth?: OAuth
|
|
26
29
|
'/'?: object // parsed and validated by RTD.syntax.parse
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
export interface OAuth {
|
|
33
|
+
/**
|
|
34
|
+
* Where a client sends the user to consent. The one endpoint of the flow that needs a
|
|
35
|
+
* person, and so the one this extension does not serve: an application builds it.
|
|
36
|
+
*/
|
|
37
|
+
authorize: string
|
|
38
|
+
|
|
39
|
+
/** Paths a token may be restricted to, each advertised as a protected resource. */
|
|
40
|
+
resources?: string[]
|
|
41
|
+
|
|
42
|
+
/** What a client may ask for. Each is a role, or a scope within one. */
|
|
43
|
+
scopes?: string[]
|
|
44
|
+
|
|
45
|
+
/** Origins whose Client ID Metadata Documents may be read. None are without it. */
|
|
46
|
+
trust?: string[]
|
|
47
|
+
|
|
48
|
+
/** Dynamic Client Registration, off unless opened. */
|
|
49
|
+
registration?: 'open' | 'closed'
|
|
50
|
+
}
|
|
51
|
+
|
|
29
52
|
export interface Bouncer {
|
|
30
53
|
/** what an address may fail at once, 20 by default */
|
|
31
54
|
attempts?: number
|
package/source/Factory.ts
CHANGED
|
@@ -54,7 +54,7 @@ export class Factory implements extensions.Factory {
|
|
|
54
54
|
const node = root.resolve()
|
|
55
55
|
const methods = new EndpointsFactory(remotes)
|
|
56
56
|
const directives = new DirectivesFactory(families, remotes, this.host, options)
|
|
57
|
-
const interception = new Interception(interceptors)
|
|
57
|
+
const interception = new Interception(interceptors, options)
|
|
58
58
|
const tree = new Tree(node, methods, directives)
|
|
59
59
|
|
|
60
60
|
const composition = new Composition(this.host)
|
package/source/HTTP/Server.ts
CHANGED
|
@@ -12,12 +12,13 @@ import { ClientError, Exception } from './exceptions.js'
|
|
|
12
12
|
import { Context } from './Context.js'
|
|
13
13
|
import { PROBE, Probe } from './Probe.js'
|
|
14
14
|
import type { IncomingMessage, Protocol, ServerResponse } from './types.js'
|
|
15
|
-
import type { Bouncer } from '../Annotation.js'
|
|
15
|
+
import type { Bouncer, OAuth } from '../Annotation.js'
|
|
16
16
|
|
|
17
17
|
export class Server extends Connector {
|
|
18
18
|
private readonly server: http.Server | http2.Http2Server
|
|
19
19
|
private readonly properties: Properties
|
|
20
|
-
|
|
20
|
+
/** Host to authority. A map: a `Host` of `constructor` names what a plain object answers. */
|
|
21
|
+
private readonly authorities: Map<string, string>
|
|
21
22
|
|
|
22
23
|
/** Tracked for the drain: `Http2Server` has no `closeIdleConnections`. */
|
|
23
24
|
private readonly sessions = new Set<http2.ServerHttp2Session>()
|
|
@@ -30,7 +31,7 @@ export class Server extends Connector {
|
|
|
30
31
|
super()
|
|
31
32
|
|
|
32
33
|
this.properties = properties
|
|
33
|
-
this.authorities =
|
|
34
|
+
this.authorities = new Map(Object.entries(properties.authorities).map(([key, value]) => [value, key]))
|
|
34
35
|
this.server = instantiate(properties.protocol)
|
|
35
36
|
this.probe = new Probe(properties.probe)
|
|
36
37
|
|
|
@@ -162,7 +163,7 @@ export class Server extends Connector {
|
|
|
162
163
|
|
|
163
164
|
assert(this.process !== undefined, 'Request processor is not attached')
|
|
164
165
|
|
|
165
|
-
const authority = this.authorities
|
|
166
|
+
const authority = this.authorities.get(host) ?? host
|
|
166
167
|
|
|
167
168
|
// if the request carries no trace context, the trace starts here
|
|
168
169
|
const remote = trace(request.headers)
|
|
@@ -239,7 +240,7 @@ export class Server extends Connector {
|
|
|
239
240
|
if (!response.writableEnded) {
|
|
240
241
|
response.statusCode = status
|
|
241
242
|
|
|
242
|
-
const message: OutgoingMessage = { status: response.statusCode }
|
|
243
|
+
const message: OutgoingMessage = { status: response.statusCode, authentic: true }
|
|
243
244
|
|
|
244
245
|
// eslint-disable-next-line max-depth
|
|
245
246
|
if (exception instanceof Exception && exception.headers !== undefined)
|
|
@@ -385,6 +386,9 @@ interface Properties {
|
|
|
385
386
|
|
|
386
387
|
/** Failed authentications an address may make; none are metered unless set. */
|
|
387
388
|
bouncer?: Bouncer
|
|
389
|
+
|
|
390
|
+
/** The authorization server this context exposes; none is exposed without it. */
|
|
391
|
+
oauth?: OAuth
|
|
388
392
|
}
|
|
389
393
|
|
|
390
394
|
export type Options = { authorities: Properties['authorities'] } & {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const type = 'application/x-www-form-urlencoded'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A form is read and never written: it is what an HTML form and a handful of protocols send,
|
|
5
|
+
* and nothing asks for a reply in it.
|
|
6
|
+
*
|
|
7
|
+
* A name repeated in a form is a list, which is how a form says one — `URLSearchParams` keeps
|
|
8
|
+
* every value, and a name given once stays the string it was written as.
|
|
9
|
+
*/
|
|
10
|
+
export function decode (buffer: Buffer, charset = 'utf-8'): any {
|
|
11
|
+
const params = new URLSearchParams(buffer.toString(charset as BufferEncoding))
|
|
12
|
+
const value: Record<string, string | string[]> = {}
|
|
13
|
+
|
|
14
|
+
for (const name of new Set(params.keys())) {
|
|
15
|
+
const values = params.getAll(name)
|
|
16
|
+
|
|
17
|
+
value[name] = values.length === 1 ? values[0] : values
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return value
|
|
21
|
+
}
|
|
@@ -2,7 +2,9 @@ import * as json from './json.js'
|
|
|
2
2
|
import * as yaml from './yaml.js'
|
|
3
3
|
import * as msgpack from './msgpack.js'
|
|
4
4
|
import * as text from './text.js'
|
|
5
|
+
import * as form from './form.js'
|
|
5
6
|
|
|
7
|
+
/** What a reply may be encoded as, and so what `accept` is negotiated against. */
|
|
6
8
|
export const formats: Record<string, Format> = {
|
|
7
9
|
[json.type]: json,
|
|
8
10
|
[yaml.type]: yaml,
|
|
@@ -12,10 +14,23 @@ export const formats: Record<string, Format> = {
|
|
|
12
14
|
|
|
13
15
|
export const types = Object.keys(formats)
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
/**
|
|
18
|
+
* What a request body may arrive as. Everything a reply may be, and a form besides — which
|
|
19
|
+
* is read and never written, so it is not something a reply can be asked for.
|
|
20
|
+
*/
|
|
21
|
+
export const decoders: Record<string, Decoder> = {
|
|
22
|
+
...formats,
|
|
23
|
+
[form.type]: form
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Decoder {
|
|
16
27
|
readonly type: string
|
|
28
|
+
|
|
29
|
+
decode: (buffer: Buffer, charset?: string) => any
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Format extends Decoder {
|
|
17
33
|
readonly multipart: string
|
|
18
34
|
|
|
19
35
|
encode: (value: any) => Buffer
|
|
20
|
-
decode: (buffer: Buffer, charset?: string) => any
|
|
21
36
|
}
|
|
@@ -7,7 +7,7 @@ import { generate } from 'randomstring'
|
|
|
7
7
|
import * as msgpack from 'msgpackr'
|
|
8
8
|
import { multipart, read, type OutgoingMessage } from './messages.js'
|
|
9
9
|
import { BadRequest, UnsupportedMediaType } from './exceptions.js'
|
|
10
|
-
import { formats } from './formats/index.js'
|
|
10
|
+
import { formats, types } from './formats/index.js'
|
|
11
11
|
import { Timing } from './Timing.js'
|
|
12
12
|
import type * as http from 'node:http'
|
|
13
13
|
import type { Context } from './Context.js'
|
|
@@ -87,6 +87,30 @@ describe('read', () => {
|
|
|
87
87
|
await assert.rejects(read(request), BadRequest)
|
|
88
88
|
})
|
|
89
89
|
|
|
90
|
+
it('should parse application/x-www-form-urlencoded', async () => {
|
|
91
|
+
const headers = { 'content-type': 'application/x-www-form-urlencoded' }
|
|
92
|
+
const form = 'grant_type=authorization_code&code=SplxlO&scope=a+b'
|
|
93
|
+
const request = createContext(generate(), headers, form)
|
|
94
|
+
|
|
95
|
+
assert.deepStrictEqual(await read(request), {
|
|
96
|
+
grant_type: 'authorization_code',
|
|
97
|
+
code: 'SplxlO',
|
|
98
|
+
scope: 'a b'
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('should read a repeated form name as the list it is', async () => {
|
|
103
|
+
const headers = { 'content-type': 'application/x-www-form-urlencoded' }
|
|
104
|
+
const request = createContext(generate(), headers, 'resource=one&resource=two')
|
|
105
|
+
|
|
106
|
+
assert.deepStrictEqual(await read(request), { resource: ['one', 'two'] })
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should not offer a form as a reply encoding', () => {
|
|
110
|
+
assert.ok(!types.includes('application/x-www-form-urlencoded'),
|
|
111
|
+
'a form is read and never written')
|
|
112
|
+
})
|
|
113
|
+
|
|
90
114
|
it('should output correct mulitpart format', async () => {
|
|
91
115
|
const response = new class extends PassThrough {
|
|
92
116
|
public readonly headers = new Headers()
|
package/source/HTTP/messages.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { pipeline } from 'node:stream/promises'
|
|
|
4
4
|
import { createHash } from 'node:crypto'
|
|
5
5
|
import * as contentType from 'content-type'
|
|
6
6
|
import { console } from 'openspan'
|
|
7
|
-
import { type Format,
|
|
7
|
+
import { type Format, decoders } from './formats/index.js'
|
|
8
8
|
import { BadRequest, NotAcceptable, UnsupportedMediaType } from './exceptions.js'
|
|
9
9
|
import type { Context } from './Context.js'
|
|
10
10
|
import type { ServerResponse } from './types.js'
|
|
@@ -49,10 +49,10 @@ export async function read (context: Context): Promise<any> {
|
|
|
49
49
|
|
|
50
50
|
const { type, parameters } = contentType.parse(header)
|
|
51
51
|
|
|
52
|
-
if (!(type in
|
|
52
|
+
if (!(type in decoders))
|
|
53
53
|
throw new UnsupportedMediaType()
|
|
54
54
|
|
|
55
|
-
const format =
|
|
55
|
+
const format = decoders[type]
|
|
56
56
|
const buf = await context.buffer()
|
|
57
57
|
|
|
58
58
|
try {
|
|
@@ -201,6 +201,13 @@ export interface OutgoingMessage {
|
|
|
201
201
|
|
|
202
202
|
/** tag the response with a hash of the encoded body, see `conditional` */
|
|
203
203
|
etag?: boolean
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Built by the gateway rather than returned by an operation, as a request is `authentic`
|
|
207
|
+
* when it was made by one: what is in it is the gateway's own, and the checks that answer
|
|
208
|
+
* for what an operation returns have nothing to say about it.
|
|
209
|
+
*/
|
|
210
|
+
authentic?: boolean
|
|
204
211
|
}
|
|
205
212
|
|
|
206
213
|
export interface Query {
|
package/source/Interception.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
import type * as http from './HTTP/index.js'
|
|
1
2
|
import type { Input, Output } from './io.js'
|
|
2
3
|
|
|
3
4
|
export class Interception implements Interceptor {
|
|
4
5
|
private readonly interceptors: Interceptor[]
|
|
5
6
|
|
|
6
|
-
public constructor (interceptors: Interceptor[]) {
|
|
7
|
+
public constructor (interceptors: Interceptor[], options: http.Options) {
|
|
7
8
|
this.interceptors = interceptors
|
|
8
9
|
|
|
9
10
|
// interceptors are module singletons, so a second gateway in one process —
|
|
10
11
|
// which is how the features run — would otherwise inherit the first one's state
|
|
11
|
-
for (const interceptor of interceptors)
|
|
12
|
+
for (const interceptor of interceptors) {
|
|
12
13
|
interceptor.reset?.()
|
|
14
|
+
interceptor.mount?.(options)
|
|
15
|
+
}
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
public async intercept (input: Input): Promise<Output> {
|
|
@@ -29,4 +32,7 @@ export interface Interceptor {
|
|
|
29
32
|
|
|
30
33
|
/** Discards whatever the interceptor accumulated while serving. */
|
|
31
34
|
reset?: () => void
|
|
35
|
+
|
|
36
|
+
/** What the annotation says, for an interceptor that answers from it. */
|
|
37
|
+
mount?: (options: http.Options) => void
|
|
32
38
|
}
|
package/source/deployment.ts
CHANGED
|
@@ -67,6 +67,9 @@ export function deployment (_: unknown, annotation?: Annotation): Dependency {
|
|
|
67
67
|
if (annotation.bouncer !== undefined)
|
|
68
68
|
properties.bouncer = annotation.bouncer
|
|
69
69
|
|
|
70
|
+
if (annotation.oauth !== undefined)
|
|
71
|
+
properties.oauth = annotation.oauth
|
|
72
|
+
|
|
70
73
|
service.variables!.push({
|
|
71
74
|
name: 'TOA_EXPOSITION_PROPERTIES',
|
|
72
75
|
value: JSON.stringify(properties)
|
|
@@ -83,4 +86,4 @@ export function deployment (_: unknown, annotation?: Annotation): Dependency {
|
|
|
83
86
|
return { services: [service] }
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
type Properties = Pick<Annotation, 'authorities' | 'debug' | 'protocol' | 'bouncer' | 'ip'>
|
|
89
|
+
type Properties = Pick<Annotation, 'authorities' | 'debug' | 'protocol' | 'bouncer' | 'ip' | 'oauth'>
|
|
@@ -16,7 +16,7 @@ import { Federation } from './Federation.js'
|
|
|
16
16
|
import { Anyone } from './Anyone.js'
|
|
17
17
|
import { Input, type Declaration } from './Input.js'
|
|
18
18
|
import { split } from './split.js'
|
|
19
|
-
import { PRIMARY,
|
|
19
|
+
import { PRIMARY, UNRECOGNIZED, providers as providersOf } from './schemes.js'
|
|
20
20
|
import { ATOM_GROUP } from '../../const.js'
|
|
21
21
|
import { Quotas, Sync } from '../io/lib/throttle/index.js'
|
|
22
22
|
import { Keys } from '../io/lib/throttle/Keys.js'
|
|
@@ -34,7 +34,7 @@ import type {
|
|
|
34
34
|
Identity,
|
|
35
35
|
Context,
|
|
36
36
|
Remote,
|
|
37
|
-
|
|
37
|
+
Components
|
|
38
38
|
} from './types.js'
|
|
39
39
|
|
|
40
40
|
export class Authorization implements DirectiveFamily<Directive, Extension> {
|
|
@@ -42,7 +42,7 @@ export class Authorization implements DirectiveFamily<Directive, Extension> {
|
|
|
42
42
|
public readonly name: string = 'auth'
|
|
43
43
|
public readonly mandatory: boolean = true
|
|
44
44
|
|
|
45
|
-
private readonly
|
|
45
|
+
private readonly components: Components = {}
|
|
46
46
|
private readonly discovery = {} as unknown as Discovery
|
|
47
47
|
private tokens: Component | null = null
|
|
48
48
|
private bans: Component | null = null
|
|
@@ -132,7 +132,7 @@ export class Authorization implements DirectiveFamily<Directive, Extension> {
|
|
|
132
132
|
if (identity === null)
|
|
133
133
|
return
|
|
134
134
|
|
|
135
|
-
if (identity.
|
|
135
|
+
if (identity.provider === PRIMARY && !identity.refresh)
|
|
136
136
|
return
|
|
137
137
|
|
|
138
138
|
if (await this.banned(identity))
|
|
@@ -140,7 +140,7 @@ export class Authorization implements DirectiveFamily<Directive, Extension> {
|
|
|
140
140
|
|
|
141
141
|
// a token carries the roles it was issued with, and the refresh is where they are read again;
|
|
142
142
|
// any other scheme has just read them, unless a Role directive already did
|
|
143
|
-
if (identity.
|
|
143
|
+
if (identity.provider === PRIMARY)
|
|
144
144
|
identity.roles = await Role.get(identity, this.discovery.roles)
|
|
145
145
|
else
|
|
146
146
|
identity.roles ??= await Role.get(identity, this.discovery.roles)
|
|
@@ -170,25 +170,40 @@ export class Authorization implements DirectiveFamily<Directive, Extension> {
|
|
|
170
170
|
throw new http.TooManyRequests(retry)
|
|
171
171
|
|
|
172
172
|
const [scheme, credentials] = split(authorization)
|
|
173
|
-
const
|
|
173
|
+
const candidates = providersOf(scheme)
|
|
174
174
|
|
|
175
|
-
if (
|
|
175
|
+
if (candidates === undefined)
|
|
176
176
|
throw new http.Unauthorized(`Unknown authentication scheme '${scheme}'`)
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
let provider: Remote | null = null
|
|
179
|
+
let result: AuthenticationResult | null = null
|
|
179
180
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
// the first provider that does not decline has claimed the credentials, and answers
|
|
182
|
+
// for them: a rejection from one that never recognized them is not the rejection
|
|
183
|
+
for (const candidate of candidates) {
|
|
184
|
+
this.components[candidate] ??= await this.discovery[candidate]
|
|
185
|
+
|
|
186
|
+
const answer = await this.components[candidate]!.invoke<AuthenticationResult>('authenticate', {
|
|
187
|
+
input: {
|
|
188
|
+
scheme,
|
|
189
|
+
authority,
|
|
190
|
+
credentials
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
if (declined(answer))
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
provider = candidate
|
|
198
|
+
result = answer
|
|
199
|
+
|
|
200
|
+
break
|
|
201
|
+
}
|
|
187
202
|
|
|
188
|
-
if (result instanceof Error) {
|
|
189
|
-
const code
|
|
203
|
+
if (result === null || result instanceof Error) {
|
|
204
|
+
const code = result === null ? UNRECOGNIZED : codeOf(result)
|
|
190
205
|
|
|
191
|
-
if (
|
|
206
|
+
if (code !== undefined) {
|
|
192
207
|
console.info('Authentication failed', { code })
|
|
193
208
|
|
|
194
209
|
context.rejection = code
|
|
@@ -201,9 +216,10 @@ export class Authorization implements DirectiveFamily<Directive, Extension> {
|
|
|
201
216
|
|
|
202
217
|
const identity = result.identity
|
|
203
218
|
|
|
204
|
-
if (
|
|
219
|
+
if (provider !== PRIMARY && (await this.banned(identity))) throw new http.Unauthorized()
|
|
205
220
|
|
|
206
221
|
identity.scheme = scheme
|
|
222
|
+
identity.provider = provider!
|
|
207
223
|
identity.refresh = result.refresh
|
|
208
224
|
|
|
209
225
|
return identity
|
|
@@ -228,6 +244,17 @@ export class Authorization implements DirectiveFamily<Directive, Extension> {
|
|
|
228
244
|
}
|
|
229
245
|
}
|
|
230
246
|
|
|
247
|
+
/** Whether a provider says the credentials are not of its kind, so the next is asked. */
|
|
248
|
+
function declined (result: AuthenticationResult): boolean {
|
|
249
|
+
return result instanceof Error && codeOf(result) === UNRECOGNIZED
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function codeOf (result: Error): string | undefined {
|
|
253
|
+
const code: string | unknown = (result as unknown as { code: string }).code
|
|
254
|
+
|
|
255
|
+
return typeof code === 'string' ? code : undefined
|
|
256
|
+
}
|
|
257
|
+
|
|
231
258
|
/** Whether the permissions admit the method on the path the request is routed by. */
|
|
232
259
|
export function permits (permissions: Record<string, string[]>, method: string, pathname: string): boolean {
|
|
233
260
|
return Object.entries(permissions).some(([pattern, methods]) =>
|
|
@@ -3,14 +3,14 @@ import { console } from 'openspan'
|
|
|
3
3
|
import * as http from '../../HTTP/index.js'
|
|
4
4
|
import { split } from './split.js'
|
|
5
5
|
import { create } from './create.js'
|
|
6
|
-
import { INCEPTION, UNKNOWN,
|
|
6
|
+
import { INCEPTION, UNKNOWN, providers as providersOf } from './schemes.js'
|
|
7
7
|
import { Role } from './Role.js'
|
|
8
8
|
import type { Component } from '@toa.io/core'
|
|
9
|
-
import type { Maybe } from '@toa.io/
|
|
10
|
-
import type { Directive, Discovery, Identity, Context,
|
|
9
|
+
import type { Maybe } from '@toa.io/core'
|
|
10
|
+
import type { Directive, Discovery, Identity, Context, Components, Ban } from './types.js'
|
|
11
11
|
|
|
12
12
|
export class Incept implements Directive {
|
|
13
|
-
private static readonly
|
|
13
|
+
private static readonly components: Components = {}
|
|
14
14
|
private static discovery: Discovery
|
|
15
15
|
private static bans: Component | null = null
|
|
16
16
|
|
|
@@ -26,12 +26,17 @@ export class Incept implements Directive {
|
|
|
26
26
|
|
|
27
27
|
public static async incept (context: Context, id: string): Promise<Identity> {
|
|
28
28
|
const [scheme, credentials] = split(context.request.headers.authorization!)
|
|
29
|
-
const
|
|
29
|
+
const candidates = providersOf(scheme)
|
|
30
30
|
|
|
31
|
-
if (
|
|
31
|
+
if (candidates === undefined)
|
|
32
32
|
throw new http.BadRequest('Authentication scheme is not supported')
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
// a scheme several providers claim is incepted by the one that incepts; `Bearer`
|
|
35
|
+
// carries an access token this gateway issued, and an identity cannot be incepted
|
|
36
|
+
// from a credential it would itself have to exist to hold
|
|
37
|
+
const provider = candidates.find((candidate) => INCEPTION.includes(candidate))
|
|
38
|
+
|
|
39
|
+
if (provider === undefined)
|
|
35
40
|
throw new http.Unauthorized()
|
|
36
41
|
|
|
37
42
|
Incept.bans ??= await Incept.discovery.bans
|
|
@@ -41,9 +46,9 @@ export class Incept implements Directive {
|
|
|
41
46
|
if (ban.banned)
|
|
42
47
|
throw new http.Unauthorized()
|
|
43
48
|
|
|
44
|
-
Incept.
|
|
49
|
+
Incept.components[provider] ??= await Incept.discovery[provider]
|
|
45
50
|
|
|
46
|
-
const identity = await Incept.
|
|
51
|
+
const identity = await Incept.components[provider]!.invoke<Maybe<Identity>>('incept', {
|
|
47
52
|
input: {
|
|
48
53
|
scheme,
|
|
49
54
|
authority: context.authority,
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { it } from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { providers } from './schemes.js'
|
|
5
5
|
|
|
6
6
|
it('should resolve a known scheme', () => {
|
|
7
|
-
assert.
|
|
8
|
-
assert.
|
|
7
|
+
assert.deepEqual(providers('basic'), ['basic'])
|
|
8
|
+
assert.deepEqual(providers('code'), ['federation'])
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('should resolve every provider claiming a scheme, in order', () => {
|
|
12
|
+
assert.deepEqual(providers('bearer'), ['tokens', 'federation'])
|
|
9
13
|
})
|
|
10
14
|
|
|
11
15
|
it('should not resolve a property of Object.prototype', () => {
|
|
12
16
|
for (const name of ['constructor', '__proto__', 'toString', 'hasOwnProperty'])
|
|
13
|
-
assert.equal(
|
|
17
|
+
assert.equal(providers(name), undefined)
|
|
14
18
|
})
|
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
import type { Remote, Scheme } from './types.js'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The providers of a scheme, in the order they are asked. More than one claims `Bearer`:
|
|
5
|
+
* an OpenID `id_token` is federated, and a token this gateway issued is presented that way
|
|
6
|
+
* by an OAuth client. Which of them a credential belongs to is each provider's own to say,
|
|
7
|
+
* so the order here is only which is asked first.
|
|
8
|
+
*/
|
|
9
|
+
export const PROVIDERS: Record<Scheme, Remote[]> = {
|
|
10
|
+
basic: ['basic'],
|
|
11
|
+
token: ['tokens'],
|
|
12
|
+
bearer: ['tokens', 'federation'],
|
|
13
|
+
code: ['federation'],
|
|
14
|
+
otp: ['otp']
|
|
9
15
|
}
|
|
10
16
|
|
|
11
17
|
export const INCEPTION: Remote[] = ['basic', 'federation']
|
|
12
|
-
export const PRIMARY:
|
|
18
|
+
export const PRIMARY: Remote = 'tokens'
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
|
-
* The
|
|
21
|
+
* The providers of a scheme, or nothing for a scheme that is not one — including a name a
|
|
16
22
|
* plain object answers on its own, like `constructor`.
|
|
17
23
|
*/
|
|
18
|
-
export function
|
|
24
|
+
export function providers (scheme: string): Remote[] | undefined {
|
|
19
25
|
return Object.hasOwn(PROVIDERS, scheme) ? PROVIDERS[scheme as Scheme] : undefined
|
|
20
26
|
}
|
|
21
27
|
|
|
28
|
+
/**
|
|
29
|
+
* What a provider answers when the credentials are not of its kind. The next provider of
|
|
30
|
+
* the scheme is asked; a provider that declines has done nothing, so asking is free of
|
|
31
|
+
* effect — which asking one that rejects would not be.
|
|
32
|
+
*/
|
|
33
|
+
export const UNRECOGNIZED = 'UNRECOGNIZED'
|
|
34
|
+
|
|
22
35
|
/**
|
|
23
36
|
* The rejection of credentials that are verifiable and owned by no identity: the one
|
|
24
37
|
* inception goes ahead with.
|