@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.
- package/components/identity.bans/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.basic/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/manifest.toa.yaml +22 -25
- package/components/identity.federation/operations/authenticate.d.ts +6 -4
- package/components/identity.federation/operations/authenticate.js +18 -8
- package/components/identity.federation/operations/authenticate.js.map +1 -1
- package/components/identity.federation/operations/decode.d.ts +3 -2
- package/components/identity.federation/operations/decode.js +9 -29
- package/components/identity.federation/operations/decode.js.map +1 -1
- package/components/identity.federation/operations/incept.d.ts +3 -2
- package/components/identity.federation/operations/incept.js +15 -5
- package/components/identity.federation/operations/incept.js.map +1 -1
- package/components/identity.federation/operations/lib/Configuration.d.ts +39 -0
- package/components/identity.federation/operations/lib/Configuration.js +3 -0
- package/components/identity.federation/operations/lib/Configuration.js.map +1 -0
- package/components/identity.federation/operations/lib/Ctx.d.ts +7 -0
- package/components/identity.federation/operations/lib/Ctx.js +3 -0
- package/components/identity.federation/operations/lib/Ctx.js.map +1 -0
- package/components/identity.federation/operations/lib/Payload.d.ts +5 -0
- package/components/identity.federation/operations/lib/Payload.js +3 -0
- package/components/identity.federation/operations/lib/Payload.js.map +1 -0
- package/components/identity.federation/operations/lib/decode.d.ts +3 -0
- package/components/identity.federation/operations/lib/decode.js +59 -0
- package/components/identity.federation/operations/lib/decode.js.map +1 -0
- package/components/identity.federation/operations/lib/discovery.d.ts +4 -0
- package/components/identity.federation/operations/lib/{assertions-as-values.js → discovery.js} +23 -21
- package/components/identity.federation/operations/lib/discovery.js.map +1 -0
- package/components/identity.federation/operations/lib/errors.d.ts +11 -0
- package/components/identity.federation/operations/lib/errors.js +15 -0
- package/components/identity.federation/operations/lib/errors.js.map +1 -0
- package/components/identity.federation/operations/lib/exchange.d.ts +3 -0
- package/components/identity.federation/operations/lib/exchange.js +107 -0
- package/components/identity.federation/operations/lib/exchange.js.map +1 -0
- package/components/identity.federation/operations/lib/index.d.ts +3 -0
- package/components/identity.federation/operations/lib/index.js +8 -0
- package/components/identity.federation/operations/lib/index.js.map +1 -0
- package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/operations/types/Scheme.d.ts +1 -0
- package/components/identity.federation/operations/types/Scheme.js +3 -0
- package/components/identity.federation/operations/types/Scheme.js.map +1 -0
- package/components/identity.federation/operations/types/configuration.d.ts +9 -4
- package/components/identity.federation/operations/types/context.d.ts +2 -18
- package/components/identity.federation/operations/types/index.d.ts +1 -0
- package/components/identity.federation/operations/types/index.js +1 -0
- package/components/identity.federation/operations/types/index.js.map +1 -1
- package/components/identity.federation/source/authenticate.ts +27 -11
- package/components/identity.federation/source/decode.ts +9 -7
- package/components/identity.federation/source/incept.ts +19 -7
- package/components/identity.federation/source/lib/Configuration.ts +39 -0
- package/components/identity.federation/source/lib/Ctx.ts +8 -0
- package/components/identity.federation/source/lib/Payload.ts +6 -0
- package/components/identity.federation/source/lib/decode.ts +48 -0
- package/components/identity.federation/source/lib/discovery.ts +30 -0
- package/components/identity.federation/source/lib/errors.ts +12 -0
- package/components/identity.federation/source/lib/exchange.ts +116 -0
- package/components/identity.federation/source/lib/index.ts +3 -0
- package/components/identity.federation/source/types/Scheme.ts +1 -0
- package/components/identity.federation/source/types/configuration.ts +9 -4
- package/components/identity.federation/source/types/context.ts +3 -20
- package/components/identity.federation/source/types/index.ts +1 -0
- package/components/identity.keys/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.otp/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.passkeys/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.roles/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.tokens/operations/tsconfig.tsbuildinfo +1 -1
- package/documentation/identity.md +41 -2
- package/features/auth.claims.feature +0 -1
- package/features/authorities.federation.feature +0 -1
- package/features/identity.federation.feature +53 -34
- package/features/map.feature +1 -2
- package/features/steps/{IdP.ts → IDP.ts} +141 -23
- package/package.json +10 -12
- package/source/HTTP/Server.ts +3 -2
- package/source/directives/auth/Authorization.ts +1 -0
- package/source/directives/auth/schemes.ts +1 -0
- package/source/directives/auth/types.ts +1 -1
- package/transpiled/HTTP/Server.js +3 -2
- package/transpiled/HTTP/Server.js.map +1 -1
- package/transpiled/directives/auth/Authorization.js +1 -0
- package/transpiled/directives/auth/Authorization.js.map +1 -1
- package/transpiled/directives/auth/schemes.js +1 -0
- package/transpiled/directives/auth/schemes.js.map +1 -1
- package/transpiled/directives/auth/types.d.ts +1 -1
- package/transpiled/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/operations/lib/assertions-as-values.d.ts +0 -4
- package/components/identity.federation/operations/lib/assertions-as-values.js.map +0 -1
- package/components/identity.federation/operations/lib/get.d.ts +0 -1
- package/components/identity.federation/operations/lib/get.js +0 -64
- package/components/identity.federation/operations/lib/get.js.map +0 -1
- package/components/identity.federation/operations/lib/jwt.d.ts +0 -20
- package/components/identity.federation/operations/lib/jwt.js +0 -152
- package/components/identity.federation/operations/lib/jwt.js.map +0 -1
- package/components/identity.federation/source/lib/assertions-as-values.ts +0 -22
- package/components/identity.federation/source/lib/get.ts +0 -82
- package/components/identity.federation/source/lib/jwt.test.ts +0 -179
- 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
|
-
}
|