@toa.io/extensions.exposition 1.0.0-alpha.149 → 1.0.0-alpha.150

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 (96) hide show
  1. package/components/identity.bans/operations/tsconfig.tsbuildinfo +1 -1
  2. package/components/identity.basic/operations/tsconfig.tsbuildinfo +1 -1
  3. package/components/identity.federation/manifest.toa.yaml +22 -25
  4. package/components/identity.federation/operations/authenticate.d.ts +6 -4
  5. package/components/identity.federation/operations/authenticate.js +18 -8
  6. package/components/identity.federation/operations/authenticate.js.map +1 -1
  7. package/components/identity.federation/operations/decode.d.ts +3 -2
  8. package/components/identity.federation/operations/decode.js +9 -29
  9. package/components/identity.federation/operations/decode.js.map +1 -1
  10. package/components/identity.federation/operations/incept.d.ts +3 -2
  11. package/components/identity.federation/operations/incept.js +15 -5
  12. package/components/identity.federation/operations/incept.js.map +1 -1
  13. package/components/identity.federation/operations/lib/Configuration.d.ts +39 -0
  14. package/components/identity.federation/operations/lib/Configuration.js +3 -0
  15. package/components/identity.federation/operations/lib/Configuration.js.map +1 -0
  16. package/components/identity.federation/operations/lib/Ctx.d.ts +7 -0
  17. package/components/identity.federation/operations/lib/Ctx.js +3 -0
  18. package/components/identity.federation/operations/lib/Ctx.js.map +1 -0
  19. package/components/identity.federation/operations/lib/Payload.d.ts +5 -0
  20. package/components/identity.federation/operations/lib/Payload.js +3 -0
  21. package/components/identity.federation/operations/lib/Payload.js.map +1 -0
  22. package/components/identity.federation/operations/lib/decode.d.ts +3 -0
  23. package/components/identity.federation/operations/lib/decode.js +59 -0
  24. package/components/identity.federation/operations/lib/decode.js.map +1 -0
  25. package/components/identity.federation/operations/lib/discovery.d.ts +4 -0
  26. package/components/identity.federation/operations/lib/{assertions-as-values.js → discovery.js} +23 -21
  27. package/components/identity.federation/operations/lib/discovery.js.map +1 -0
  28. package/components/identity.federation/operations/lib/errors.d.ts +11 -0
  29. package/components/identity.federation/operations/lib/errors.js +15 -0
  30. package/components/identity.federation/operations/lib/errors.js.map +1 -0
  31. package/components/identity.federation/operations/lib/exchange.d.ts +3 -0
  32. package/components/identity.federation/operations/lib/exchange.js +107 -0
  33. package/components/identity.federation/operations/lib/exchange.js.map +1 -0
  34. package/components/identity.federation/operations/lib/index.d.ts +3 -0
  35. package/components/identity.federation/operations/lib/index.js +8 -0
  36. package/components/identity.federation/operations/lib/index.js.map +1 -0
  37. package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -1
  38. package/components/identity.federation/operations/types/Scheme.d.ts +1 -0
  39. package/components/identity.federation/operations/types/Scheme.js +3 -0
  40. package/components/identity.federation/operations/types/Scheme.js.map +1 -0
  41. package/components/identity.federation/operations/types/configuration.d.ts +9 -4
  42. package/components/identity.federation/operations/types/context.d.ts +2 -18
  43. package/components/identity.federation/operations/types/index.d.ts +1 -0
  44. package/components/identity.federation/operations/types/index.js +1 -0
  45. package/components/identity.federation/operations/types/index.js.map +1 -1
  46. package/components/identity.federation/source/authenticate.ts +27 -11
  47. package/components/identity.federation/source/decode.ts +9 -7
  48. package/components/identity.federation/source/incept.ts +19 -7
  49. package/components/identity.federation/source/lib/Configuration.ts +39 -0
  50. package/components/identity.federation/source/lib/Ctx.ts +8 -0
  51. package/components/identity.federation/source/lib/Payload.ts +6 -0
  52. package/components/identity.federation/source/lib/decode.ts +48 -0
  53. package/components/identity.federation/source/lib/discovery.ts +30 -0
  54. package/components/identity.federation/source/lib/errors.ts +12 -0
  55. package/components/identity.federation/source/lib/exchange.ts +116 -0
  56. package/components/identity.federation/source/lib/index.ts +3 -0
  57. package/components/identity.federation/source/types/Scheme.ts +1 -0
  58. package/components/identity.federation/source/types/configuration.ts +9 -4
  59. package/components/identity.federation/source/types/context.ts +3 -20
  60. package/components/identity.federation/source/types/index.ts +1 -0
  61. package/components/identity.keys/operations/tsconfig.tsbuildinfo +1 -1
  62. package/components/identity.otp/operations/tsconfig.tsbuildinfo +1 -1
  63. package/components/identity.passkeys/operations/tsconfig.tsbuildinfo +1 -1
  64. package/components/identity.roles/operations/tsconfig.tsbuildinfo +1 -1
  65. package/components/identity.tokens/operations/tsconfig.tsbuildinfo +1 -1
  66. package/documentation/identity.md +41 -2
  67. package/features/auth.claims.feature +0 -1
  68. package/features/authorities.federation.feature +0 -1
  69. package/features/identity.federation.feature +53 -34
  70. package/features/map.feature +1 -2
  71. package/features/steps/{IdP.ts → IDP.ts} +141 -23
  72. package/package.json +10 -12
  73. package/source/HTTP/Server.ts +3 -2
  74. package/source/directives/auth/Authorization.ts +1 -0
  75. package/source/directives/auth/schemes.ts +1 -0
  76. package/source/directives/auth/types.ts +1 -1
  77. package/transpiled/HTTP/Server.js +3 -2
  78. package/transpiled/HTTP/Server.js.map +1 -1
  79. package/transpiled/directives/auth/Authorization.js +1 -0
  80. package/transpiled/directives/auth/Authorization.js.map +1 -1
  81. package/transpiled/directives/auth/schemes.js +1 -0
  82. package/transpiled/directives/auth/schemes.js.map +1 -1
  83. package/transpiled/directives/auth/types.d.ts +1 -1
  84. package/transpiled/tsconfig.tsbuildinfo +1 -1
  85. package/components/identity.federation/operations/lib/assertions-as-values.d.ts +0 -4
  86. package/components/identity.federation/operations/lib/assertions-as-values.js.map +0 -1
  87. package/components/identity.federation/operations/lib/get.d.ts +0 -1
  88. package/components/identity.federation/operations/lib/get.js +0 -64
  89. package/components/identity.federation/operations/lib/get.js.map +0 -1
  90. package/components/identity.federation/operations/lib/jwt.d.ts +0 -20
  91. package/components/identity.federation/operations/lib/jwt.js +0 -152
  92. package/components/identity.federation/operations/lib/jwt.js.map +0 -1
  93. package/components/identity.federation/source/lib/assertions-as-values.ts +0 -22
  94. package/components/identity.federation/source/lib/get.ts +0 -82
  95. package/components/identity.federation/source/lib/jwt.test.ts +0 -179
  96. package/components/identity.federation/source/lib/jwt.ts +0 -198
@@ -1,198 +0,0 @@
1
- import crypto from 'node:crypto'
2
- import * as assert from 'node:assert'
3
- import { get } from './get'
4
- import type { JwtHeader, IdToken, Trust } from '../types'
5
- import type { Stash } from '@toa.io/types'
6
-
7
- export function decodeJwt (token: string): {
8
- header: unknown
9
- payload: unknown
10
- rawHeader: string
11
- rawPayload: string
12
- signature: string
13
- } {
14
- const [rawHeader, rawPayload, signature] = token.split('.', 3)
15
-
16
- const header = JSON.parse(Buffer.from(rawHeader, 'base64url').toString())
17
- const payload = JSON.parse(Buffer.from(rawPayload, 'base64url').toString())
18
-
19
- return { header, payload, rawHeader, rawPayload, signature }
20
- }
21
-
22
- export function validateJwtHeader (header: unknown): asserts header is JwtHeader {
23
- assert.ok(header !== null && typeof header === 'object', 'Header is not an object')
24
- assert.ok('alg' in header, 'Header is missing alg')
25
- assert.ok(typeof header.alg === 'string', 'Header alg is not a string')
26
- assert.match(header.alg, /^RS256|HS\d{3}$/, `Unknown algorithm ${header.alg}`)
27
- assert.ok(!('kid' in header) || typeof header.kid === 'string', 'kid must be a string if present')
28
- }
29
-
30
- export function validateJwtPayload (payload: unknown,
31
- trusted: Trust[],
32
- header: JwtHeader): asserts payload is IdToken {
33
- assert.ok(trusted.length > 0, 'No trusted issuers provided')
34
-
35
- // the full list of validations is
36
- // at https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
37
- assert.ok(payload !== null && typeof payload === 'object', 'Payload is not an object')
38
-
39
- assert.ok('iss' in payload, 'Payload is missing iss')
40
- assert.ok(typeof payload.iss === 'string', 'Payload iss is not a string')
41
- assert.ok('aud' in payload, 'Payload is missing aud')
42
- assert.ok(typeof payload.aud === 'string' ||
43
- (Array.isArray(payload.aud) && payload.aud.every((e): e is string => typeof e === 'string')),
44
- 'Payload aud is not a string nor an array of strings')
45
-
46
- const issuer = trusted.find((config) => config.iss === payload.iss)
47
-
48
- assert.ok(issuer, `Unknown issuer: ${payload.iss}`)
49
-
50
- if (Array.isArray(issuer.aud)) {
51
- const tokenAud = payload.aud
52
-
53
- if (typeof tokenAud === 'string')
54
- assert.ok(issuer.aud.some((a) => a === tokenAud), `Unknown audience: ${tokenAud}`)
55
- else
56
- assert.ok(issuer.aud.some((a) => tokenAud.includes(a)), `Unknown audiences: ${tokenAud.join(', ')}`)
57
- }
58
-
59
- if (header.alg.startsWith('HS')) {
60
- const secrets = issuer.secrets
61
-
62
- assert.ok(secrets, `We don't have known secrets for ${payload.iss}`)
63
-
64
- const keys = secrets[header.alg]
65
-
66
- assert.ok(keys, `No known secrets for ${header.alg}`)
67
-
68
- if (typeof header.kid === 'string')
69
- assert.ok(header.kid in keys, `No secret ${header.kid} provided for ${header.alg}`)
70
- }
71
-
72
- assert.ok('sub' in payload, 'Payload is missing sub')
73
- assert.ok(typeof payload.sub === 'string', 'Payload sub is not a string')
74
-
75
- assert.ok('exp' in payload, 'Payload is missing exp')
76
- assert.ok(typeof payload.exp === 'number', 'Payload exp is not a number')
77
- assert.ok(Date.now() < payload.exp * 1000, 'Token is expired')
78
-
79
- assert.ok('iat' in payload, 'Payload is missing iat')
80
- assert.ok(typeof payload.iat === 'number', 'Payload iat is not a number')
81
- assert.ok(Date.now() >= payload.iat * 1000, 'Token was issued in the future')
82
- assert.ok(payload.exp >= payload.iat, 'Payload exp is before iat')
83
-
84
- if ('nbf' in payload) {
85
- assert.ok(typeof payload.nbf === 'number', 'Payload nbf is not a number')
86
- assert.ok(Date.now() >= payload.nbf * 1000, 'Token is not valid yet')
87
- }
88
-
89
- if ('jti' in payload)
90
- assert.ok(typeof payload.jti === 'string', 'Payload jti is not a string')
91
- }
92
-
93
- export async function validateSignature ({
94
- header: { kid, alg },
95
- payload: { iss },
96
- rawHeader,
97
- rawPayload,
98
- signature,
99
- trusted = []
100
- }: {
101
- readonly header: JwtHeader
102
- rawHeader: string
103
- readonly payload: IdToken
104
- rawPayload: string
105
- signature: string
106
- trusted?: Trust[]
107
- }): Promise<void> {
108
- if (alg.startsWith('HS')) {
109
- // symmetric algorithm, issuer is validated at this point
110
-
111
- const secrets = trusted.find((c) => c.iss === iss)!.secrets![alg]
112
- const secret = kid !== undefined ? secrets[kid] : Object.values(secrets)[0]
113
- const algorithm = alg.replace(/^HS(\d{3})$/, 'sha$1') // HS256 -> sha256
114
- const hmac = crypto.createHmac(algorithm, secret)
115
-
116
- hmac.update(rawHeader)
117
- hmac.update('.')
118
- hmac.update(rawPayload)
119
- assert.strictEqual(signature, hmac.digest('base64url'), 'Signature does not match')
120
-
121
- return
122
- }
123
-
124
- // Getting issuer public keys
125
- const oidcRequest = await get(new URL('/.well-known/openid-configuration', iss).href)
126
-
127
- assert.ok(oidcRequest.ok,
128
- `Failed to fetch OpenID configuration: ${oidcRequest.statusText}`)
129
-
130
- const { jwks_uri: jwksUri } = (await oidcRequest.json()) as { jwks_uri: string }
131
-
132
- const jwkRequest = await get(jwksUri)
133
-
134
- assert.ok(jwkRequest.ok, `Failed to fetch issuer keys: ${jwkRequest.statusText}`)
135
-
136
- const { keys } = (await jwkRequest.json()) as {
137
- keys: Array<{ use: string, kid?: string, alg?: string } & crypto.JsonWebKey>
138
- }
139
-
140
- // getting the corresponding signing key
141
- const signingKeys = keys.filter((k) => k.use === 'sig' && k.alg === alg)
142
-
143
- assert.ok(signingKeys.length > 0, 'No acceptable signing keys found')
144
-
145
- assert.ok(kid !== undefined || signingKeys.length === 1,
146
- 'Signing key selection is not deterministic')
147
-
148
- const signingKey = kid === undefined ? keys[0] : signingKeys.find((k) => k.kid === kid)
149
-
150
- assert.ok(signingKey, 'Signing key was not found in issuer keys')
151
-
152
- const verifyFunction = crypto.createVerify('RSA-SHA256')
153
-
154
- verifyFunction.write(rawHeader)
155
- verifyFunction.write('.')
156
- verifyFunction.write(rawPayload)
157
- verifyFunction.end()
158
-
159
- const signatureValid = verifyFunction.verify({ format: 'jwk', key: signingKey },
160
- signature,
161
- 'base64url')
162
-
163
- assert.ok(signatureValid, 'Failed to validate signature')
164
- }
165
-
166
- async function validateJti (token: IdToken, stash: Stash): Promise<void> {
167
- const key = `identity:federation:jti:${token.jti}`
168
- const used = await stash.exists(key)
169
-
170
- assert.ok(used === 0, 'Token has already been used')
171
-
172
- const ttl = token.exp - Math.floor(Date.now() / 1000)
173
-
174
- await stash.set(key, '1', 'EX', ttl)
175
- }
176
-
177
- export async function decode (token: string, trusted: Trust[] | undefined, stash: Stash): Promise<IdToken> {
178
- assert.ok(trusted !== undefined, 'No trusted issuers provided')
179
-
180
- const { header, payload, rawHeader, rawPayload, signature } = decodeJwt(token)
181
-
182
- validateJwtHeader(header)
183
- validateJwtPayload(payload, trusted, header)
184
-
185
- if (payload.jti !== undefined)
186
- await validateJti(payload, stash)
187
-
188
- await validateSignature({
189
- header,
190
- rawHeader,
191
- payload,
192
- rawPayload,
193
- signature,
194
- trusted
195
- })
196
-
197
- return payload
198
- }