@t4h.framework/jwks 0.7.6 → 0.7.8

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.
@@ -1,276 +1,108 @@
1
1
  ---
2
2
  name: framework-jwks
3
3
  description: >-
4
- JWKS-backed authorization for T4H Framework workflows and apps. Use when
5
- importing @t4h.framework/jwks, wiring JWKSAuthorization on Workflow or App,
6
- validating RS256 JWTs against a JWKS endpoint, verifying detached signatures
7
- with kid/algorithm/signatureFormat, configuring issuer/audience checks, or
8
- implementing the @t4h.framework/jwks/authorize runtime handler. Requires
9
- @t4h.framework/core. Package path: framework/packages/jwks.
4
+ Verifies RS256 JWTs and detached public-key signatures against a JWKS
5
+ endpoint with @t4h.framework/jwks. Use when a T4H Framework workflow or App
6
+ needs JWKSAuthorization, when an async activity such as webhook() declares
7
+ auth { type: 'jwt' } or auth { type: 'jwks' } for its callbacks, or when the
8
+ user mentions a JWKS URL, .well-known/jwks.json, kid, issuer, audience,
9
+ JWKSAlgorithm, a bearer JWT (Google Pub/Sub push, Cloud Tasks) or a signature
10
+ verified with a key published in a JWKS.
10
11
  ---
11
12
 
12
13
  # @t4h.framework/jwks
13
14
 
14
- JWKS-backed **Authorization** for workflows and apps. Exposes `JWKSAuthorization` (core manifest) and a default runtime handler at `@t4h.framework/jwks/authorize` that fetches signing keys from a JWKS URL and verifies credentials.
15
+ The package has two uses. Pick the one that matches the task:
15
16
 
16
- **Peer dependency:** `@t4h.framework/core` (same major line).
17
+ - **Protect a workflow or an `App`**: `new JWKSAuthorization(context)`.
18
+ - **Verify the callbacks of an async activity**: declare
19
+ `auth: { type: 'jwt', … }` or `auth: { type: 'jwks', … }` on the activity
20
+ (for `webhook()`, see the **framework-webhook** skill). The host verifies
21
+ each callback with `@t4h.framework/jwks/callback`. You do not import it.
17
22
 
18
- **Node:** `>=22`.
23
+ ## Protect a workflow or an App
19
24
 
20
- Package path: `framework/packages/jwks`. See [README.md](../../../README.md) for overview (field names below match **source**, not older README examples that mention `value`).
21
-
22
- ---
23
-
24
- ## Imports
25
-
26
- | Entry | Purpose |
27
- |-------|---------|
28
- | `@t4h.framework/jwks` | `JWKSAuthorization`, `JWKSAuthorizationContext`, `JWKSAuthorizationManifest`, `JWKSAlgorithm`, re-exports authorize module |
29
- | `@t4h.framework/jwks/authorize` | Default async handler: `(context) => Promise<boolean>` |
30
- | `@t4h.framework/jwks/callback` | Default export `jwksCallback`: `(auth, delivery) => Promise<boolean>`, the authorizer hosts pass to the core `AsyncActivityCallback.authorize` for `jwt` (bearer JWT) and `jwks` (detached signature over the body, key resolved by `kid` from a JWKS URL) |
31
-
32
- ```typescript
33
- import {
34
- JWKSAuthorization,
35
- type JWKSAuthorizationContext,
36
- JWKSAlgorithm,
37
- } from '@t4h.framework/jwks'
38
- ```
39
-
40
- Do **not** assume OIDC discovery, env-variable mapping, or `credentialSource` helpers—this package does not read HTTP headers or env. Your app or hosting layer supplies `signature`, `rawPayload`, and `url`.
41
-
42
- ---
43
-
44
- ## Wiring authorization (core)
45
-
46
- `JWKSAuthorization` extends core `Authorization`. Attach to a **Workflow** or **App**; `toManifest()` registers the handler module for the platform.
25
+ The handler picks the path from `signature`: a value that decodes as a JWT
26
+ takes the JWT path, any other value takes the raw-payload path.
47
27
 
48
28
  ```typescript
49
- import { App, Workflow } from '@t4h.framework/core'
50
29
  import { JWKSAuthorization } from '@t4h.framework/jwks'
51
30
 
52
- const authorization = new JWKSAuthorization({
31
+ export const authorization = new JWKSAuthorization({
53
32
  url: 'https://auth.example.com/.well-known/jwks.json',
54
- signature: '<credential-from-runtime>',
55
- rawPayload: '<payload-or-same-as-jwt>',
33
+ signature: '<jwt-from-the-request>',
34
+ rawPayload: '',
35
+ issuer: 'https://auth.example.com/',
56
36
  audience: 'https://api.example.com',
57
37
  })
58
-
59
- const workflow = new Workflow(
60
- { id: 'protected/v1', authorization },
61
- async () => ({ ok: true }),
62
- )
63
-
64
- const app = new App({
65
- id: 'my-app',
66
- workflows: [workflow],
67
- authorization, // optional app-level default
68
- })
69
38
  ```
70
39
 
71
- Manifest shape (from tests):
72
-
73
- ```typescript
74
- {
75
- pathToModule: '@t4h.framework/jwks/authorize',
76
- context: {
77
- url: string
78
- signature: string
79
- rawPayload: string
80
- issuer?: string
81
- audience?: string
82
- kid?: string
83
- algorithm?: string
84
- signatureFormat?: string
85
- },
86
- }
87
- ```
88
-
89
- Optional manifest fields are still present as `undefined` when omitted (`toManifest` copies all keys).
90
-
91
- ---
92
-
93
- ## Context: `JWKSAuthorizationContext`
40
+ Attach it to the `authorization` option of a `Workflow` or an `App`. For a
41
+ detached signature, give the signed bytes in `rawPayload` and set `kid`,
42
+ `algorithm` and `signatureFormat`.
94
43
 
95
- | Field | Required | Description |
96
- |-------|----------|-------------|
97
- | `url` | Yes | JWKS document URL (`jwks-rsa` `jwksUri`). |
98
- | `signature` | Yes | Credential to verify: JWT string, or detached signature for non-JWT path. Empty string → handler returns `false`. |
99
- | `rawPayload` | Yes | Payload bytes as string for non-JWT verification; for JWT path, tests pass the same string as `signature` (handler only uses `signature` on JWT path). |
100
- | `issuer` | No | When set, passed to `jwt.verify` as expected `iss`. Mismatch **rejects** (throws). |
101
- | `audience` | No | When set, passed to `jwt.verify` as expected `aud`. |
102
- | `kid` | Conditional | Required when `signature` is **not** a decodable JWT (non-JWT path). |
103
- | `algorithm` | Conditional | Non-JWT path: **enum key** on `JWKSAlgorithm` (e.g. `RS256`), not the Node crypto string. |
104
- | `signatureFormat` | Conditional | Non-JWT path: encoding for the signature buffer (`hex`, `base64`, etc.—`BinaryToTextEncoding`). |
105
-
106
- ---
107
-
108
- ## Runtime handler (`@t4h.framework/jwks/authorize`)
109
-
110
- Default export:
111
-
112
- ```typescript
113
- (options: JWKSAuthorizationContext) => Promise<boolean>
114
- ```
115
-
116
- Behavior (from `jwks-authorize.ts` and tests):
117
-
118
- 1. If `signature` is falsy → **`false`** (no throw).
119
- 2. Build `JwksClient({ jwksUri: options.url })`.
120
- 3. `jwt.decode(signature, { complete: true })`:
121
- - **Decoded (JWT path)** → `validateJwt(signature, client, { issuer, audience })`.
122
- - **Not decoded (non-JWT path)** → require `kid`, `algorithm`, `signatureFormat`; else **throw** with fixed messages (see below).
123
-
124
- ### JWT path (`validateJwt`)
125
-
126
- - Algorithm must be **RS256** (`decoded.header.alg === 'RS256'`); otherwise **`false`** (e.g. HS256 tokens return `false`, not throw).
127
- - Header must include **`kid`**; used to `getSigningKey(kid)` from JWKS.
128
- - **`exp` enforced** (`ignoreExpiration: false`). Expired or wrong signing key → **`jwt.verify` rejects** (promise rejection / throw from handler).
129
- - Optional `issuer` / `audience` from context applied when present.
130
-
131
- ### Non-JWT path
132
-
133
- Uses Node `crypto.createVerify` with public key from JWKS:
44
+ ## Verify async activity callbacks
134
45
 
135
46
  ```typescript
136
- authorizer.update(options.rawPayload)
137
- authorizer.verify(signingKey, options.signature, signatureFormat)
138
- ```
139
-
140
- Throws if any required field is missing:
141
-
142
- | Missing | Error message |
143
- |---------|----------------|
144
- | `kid` | `KID is required for payload verification` |
145
- | `algorithm` | `Algorithm is required for payload verification` |
146
- | `signatureFormat` | `Signature format is required for payload verification` |
147
-
148
- `algorithm` must be a **key** of `JWKSAlgorithm`:
149
-
150
- ```typescript
151
- export enum JWKSAlgorithm {
152
- RS256 = 'RSA-SHA256',
153
- RS384 = 'RSA-SHA384',
154
- RS512 = 'RSA-SHA512',
155
- PS256 = 'RSA-PSS-SHA256',
156
- PS384 = 'RSA-PSS-SHA384',
157
- PS512 = 'RSA-PSS-SHA512',
158
- ES256 = 'ECDSA-SHA256',
159
- ES384 = 'ECDSA-SHA384',
160
- ES512 = 'ECDSA-SHA512',
47
+ auth: {
48
+ type: ('jwt', jwksUri, issuer, audience)
161
49
  }
162
50
  ```
163
51
 
164
- Handler uses `JWKSAlgorithm[algorithm]` as the `createVerify` algorithm name. Pass `algorithm: 'RS256'`, not `'RSA-SHA256'`.
165
-
166
- Returns **`true`** on successful verification, **`false`** when verification fails (non-JWT invalid signature).
167
-
168
- ---
169
-
170
- ## Examples
171
-
172
- ### RS256 JWT (typical API Bearer token)
173
-
174
- ```typescript
175
- import { JWKSAuthorization } from '@t4h.framework/jwks'
176
-
177
- const authorization = new JWKSAuthorization({
178
- url: 'https://id.example.com/.well-known/jwks.json',
179
- signature: bearerToken, // without "Bearer " prefix if your layer strips it
180
- rawPayload: bearerToken,
181
- issuer: 'https://issuer.example.com',
182
- audience: 'my-api',
183
- })
184
- ```
185
-
186
- Token must be signed RS256 with `kid` in header. Platform invokes `@t4h.framework/jwks/authorize` with manifest `context` when the protected workflow runs.
187
-
188
- ### Detached signature (non-JWT)
189
-
190
- ```typescript
191
- new JWKSAuthorization({
192
- url: 'https://id.example.com/jwks',
193
- signature: detachedSignatureHex,
194
- rawPayload: canonicalPayloadString,
195
- kid: 'key-1',
196
- algorithm: 'RS256',
197
- signatureFormat: 'hex',
198
- })
199
- ```
200
-
201
- If `signature` accidentally looks like a JWT to `jwt.decode`, the JWT path runs instead—structure payloads accordingly.
202
-
203
- ---
204
-
205
- ## Return values vs errors
206
-
207
- | Situation | Result |
208
- |-----------|--------|
209
- | Empty `signature` | `false` |
210
- | Valid RS256 JWT, matching JWKS key | `true` |
211
- | JWT wrong alg (e.g. HS256), no `kid`, undecodable token | `false` |
212
- | Expired JWT, issuer mismatch, signature mismatch | **Rejected** (throw from `jwt.verify`) |
213
- | Non-JWT missing `kid` / `algorithm` / `signatureFormat` | **Throw** (configuration error) |
214
- | Non-JWT verify fails | `false` |
215
-
216
- Design callers so manifest context is complete **before** the handler runs; missing non-JWT fields are programmer errors, not silent denials.
217
-
218
- ---
219
-
220
- ## Testing the handler locally
221
-
222
- Unit tests mock `jwks-rsa` and use real RSA key pairs + `jsonwebtoken`:
223
-
224
52
  ```typescript
225
- import jwks from '@t4h.framework/jwks/authorize'
226
- import jwt from 'jsonwebtoken'
227
-
228
- // Valid JWT
229
- const token = jwt.sign({ sub: 'user-1' }, privateKeyPem, {
53
+ auth: {
54
+ type: 'jwks',
55
+ jwksUri: 'https://partner.example.com/.well-known/jwks.json',
56
+ header: 'x-signature',
57
+ kidHeader: 'x-key-id',
230
58
  algorithm: 'RS256',
231
- keyid: 'kid-1',
232
- expiresIn: '1h',
233
- })
234
-
235
- await expect(
236
- jwks({
237
- url: 'https://example.com/jwks',
238
- signature: token,
239
- rawPayload: token,
240
- }),
241
- ).resolves.toBe(true)
59
+ signatureFormat: 'base64',
60
+ timestampHeader: 'x-timestamp',
61
+ toleranceInSeconds: 300,
62
+ signedPayload: '{timestamp}.{body}',
63
+ }
242
64
  ```
243
65
 
244
- Test files: `src/__tests__/jwks-authorize.spec.ts`, `src/helpers/__tests__/validateJwt.spec.ts`, `src/models/__tests__/JWKSAuthorization.spec.ts`.
245
-
246
- `JWKSAuthorization` tests only assert `toManifest()` shape—handler tests cover verification behavior.
247
-
248
- ---
249
-
250
- ## Dependencies (implementation)
251
-
252
- - **`jwks-rsa`** — `JwksClient`, `getSigningKey(kid)`, `getPublicKey()`.
253
- - **`jsonwebtoken`** — decode/verify on JWT path.
254
-
255
- ---
256
-
257
- ## What this package does not do
258
-
259
- - No built-in OIDC discovery or `JWKS_ISSUER` env mapping.
260
- - No HTTP header / query / env extraction for credentials.
261
- - JWT path supports **RS256 only** (not RS384/ES256 via JWT path—those enum values apply to non-JWT `createVerify` only).
262
- - Does not define **Claims** or **Activities**—only **Authorization** manifests for core `Workflow` / `App`.
263
-
264
- For workflow orchestration and claims use **framework-core**; for the `TestWorkflowEnvironment` testing harness use **framework-workflow-testing**.
265
-
266
- ---
267
-
268
- ## Quick checklist for agents
269
-
270
- 1. Add `@t4h.framework/jwks` and peer `@t4h.framework/core`.
271
- 2. Build `JWKSAuthorizationContext` with `url`, `signature`, `rawPayload` at minimum.
272
- 3. Attach `new JWKSAuthorization(context)` to `Workflow` and/or `App` `authorization` option.
273
- 4. JWT: ensure RS256 + `kid`; optional `issuer` / `audience`.
274
- 5. Non-JWT: set `kid`, `algorithm` (`JWKSAlgorithm` key), `signatureFormat`.
275
- 6. Expect `false` for deny, throws for misconfiguration or JWT verify failures (expired, wrong iss).
276
- 7. Do not reference APIs absent from `src/` and tests.
66
+ On `jwks`, each of `kid`, `algorithm` and `signatureFormat` is a fixed value or
67
+ a `*Header` name. A fixed value wins. `signatureFormat` defaults to `'hex'`.
68
+
69
+ ## Traps
70
+
71
+ - The field is `signature`. There is no `value` field.
72
+ - The JWT path accepts only RS256 tokens with a `kid` in the header. HS256 or
73
+ ES256 tokens give `false`.
74
+ - On the JWT path `rawPayload` is not used. Pass `''`.
75
+ - `algorithm` is a `JWKSAlgorithm` KEY such as `'RS256'`, not the Node name
76
+ `'RSA-SHA256'`. Today only `RS256`, `RS384` and `RS512` verify; the `PS*` and
77
+ `ES*` keys throw `Invalid digest`.
78
+ - A detached signature that happens to decode as a JWT takes the JWT path.
79
+ - `JWKSAuthorization`: a denied raw-payload signature gives `false`, but an
80
+ expired JWT, an `issuer` or `audience` mismatch, a bad JWT signature or a
81
+ missing `kid`/`algorithm`/`signatureFormat` THROWS. On a callback each error
82
+ becomes a refusal.
83
+ - Always set `issuer` and `audience` for a JWT. Without them a token minted for
84
+ another service passes.
85
+ - Callback timestamps: all digits with 13 or more digits are milliseconds,
86
+ fewer are seconds. Other strings go through `Date.parse`.
87
+ - The package does not read HTTP headers or env, and has no OIDC discovery. For
88
+ a token from an OIDC provider, see **framework-oidc**.
89
+
90
+ ## Test
91
+
92
+ The handler fetches keys over HTTP from `url`. Serve a JWKS from a local HTTP
93
+ server in the test (export the public key with
94
+ `publicKey.export({ format: 'jwk' })` and add `kid`), sign a token with
95
+ `jsonwebtoken` (`algorithm: 'RS256'`, `keyid`), and call the default export of
96
+ `@t4h.framework/jwks/authorize`.
97
+
98
+ ## Related skills
99
+
100
+ - **framework-hmac**: shared-secret signatures.
101
+ - **framework-webhook**: wait for a partner callback that uses this `auth`.
102
+
103
+ ## Reference
104
+
105
+ - Guide: <https://tech4humans-brasil.github.io/framework/jwks/>
106
+ - Types: `node_modules/@t4h.framework/jwks/dist/jwks.d.ts`
107
+ - Callback `auth` types: `AsyncActivityCallbackAuthJwt` and
108
+ `AsyncActivityCallbackAuthJwks` in `node_modules/@t4h.framework/core/dist/`
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @t4h.framework/jwks
2
2
 
3
+ ## 0.7.8
4
+
5
+ ### Patch Changes
6
+
7
+ - [#257](https://github.com/tech4humans-brasil/framework/pull/257) [`586bfb4`](https://github.com/tech4humans-brasil/framework/commit/586bfb4663f4a478527ee0c8db360603f034d615) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The jwks README is now short and links to the site. The site page now describes the `jwt` and `jwks` callback `auth`, the results and errors, and says that only the `RS*` keys verify a detached signature.
8
+
9
+ ## 0.7.7
10
+
11
+ ### Patch Changes
12
+
13
+ - [#243](https://github.com/tech4humans-brasil/framework/pull/243) [`0ffd663`](https://github.com/tech4humans-brasil/framework/commit/0ffd663ce8369edd7b54206cb3216568ad1de471) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The README examples read each value from `environments`, an `EnvDesign` in `src/constants/environments.ts`, and pass each credential as a `Secret`. They no longer use `process.env`, the raw `env` from the core or a literal in a `Secret` field. The jwks README describes the real context fields (`signature`, `rawPayload`) and says that a token that `jwt.verify` rejects throws.
14
+
15
+ - [#240](https://github.com/tech4humans-brasil/framework/pull/240) [`0606115`](https://github.com/tech4humans-brasil/framework/commit/0606115e2e0baa994e9f0ed3559ee54ecac88e44) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The skills do not repeat a rule that a lint rule, `tsc` or a runtime error already enforces. Each such rule is now one line that names the gate. The examples that broke a gate (`process.env`, `as any`, an inline secret, an `EnvDesign` in a workflow file, `ref.output` before `wait()`) are correct now.
16
+
17
+ - [#228](https://github.com/tech4humans-brasil/framework/pull/228) [`d29e1ab`](https://github.com/tech4humans-brasil/framework/commit/d29e1ab29a039b13c3e4b075472bd615566ae75e) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - Uses exact types and `unknown` in place of `any`, and checks external data with guards.
18
+ - harness: the fix-issues prompt lists each sensor as `- checkTypes: PASS`. Before, it printed `- 0: [object Object]`.
19
+ - hmac: an algorithm name such as `constructor` throws `Unsupported algorithm`.
20
+ - oidc: a discovery response with no `introspection_endpoint`, or a null introspection body, throws a clear error.
21
+ - `HttpRequestActivity.toOutput` takes `HttpRequestActivityOutput` in place of `any`.
22
+
3
23
  ## 0.7.6
4
24
 
5
25
  ## 0.7.5
package/README.md CHANGED
@@ -1,73 +1,31 @@
1
1
  # @t4h.framework/jwks
2
2
 
3
- JWKS-backed authorization for the T4H Framework. It exposes a core `Authorization` manifest and a runtime handler that validates credentials against keys from a JWKS endpoint.
3
+ JWKS authorization for the T4H Framework. It verifies an RS256 JWT, or a
4
+ detached signature over a raw payload, against the public keys of a JWKS
5
+ endpoint, for a workflow, an app or the callbacks of an async activity.
4
6
 
5
- ## Peer dependency
7
+ ## Install
6
8
 
7
- Requires `@t4h.framework/core` (same major line as this package).
8
-
9
- ## Exports
10
-
11
- | Entry | Purpose |
12
- |------------------|---------|
13
- | `@t4h.framework/jwks` | `JWKSAuthorization`, `JWKSAuthorizationContext`, and related types |
14
- | `@t4h.framework/jwks/authorize` | Default async handler invoked by the runtime with the manifest context |
15
- | `@t4h.framework/jwks/callback` | Default export `jwksCallback`: the `jwt` (bearer JWT) and `jwks` (detached signature verified with a JWKS key) callback authorizer for the core `AsyncActivityCallback.authorize` |
16
-
17
- ## `JWKSAuthorization`
18
-
19
- Subclass of [`Authorization`](https://github.com/tech4humans-brasil/framework/tree/main/packages/core) from core. `toManifest()` points the runtime at `@t4h.framework/jwks/authorize` and passes a `JWKSAuthorizationContext`.
20
-
21
- Construct it with a full `JWKSAuthorizationContext` (including `url` and `value`). How you obtain `value`—headers, query, env, or runtime injection—is up to your app and deployment pipeline; this package does not read HTTP or env by itself.
22
-
23
- ## Context: `JWKSAuthorizationContext`
24
-
25
- | Field | Required | Description |
26
- |-------|----------|-------------|
27
- | `url` | Yes | JWKS document URL (`jwks-rsa` `jwksUri`). |
28
- | `value` | Yes | Credential to verify (Bearer token, raw payload, etc., depending on your app). Empty string yields `false`. |
29
- | `issuer` | No | If set, passed to JWT verification as expected `iss`. |
30
- | `audience` | No | If set, passed to JWT verification as expected `aud`. |
31
- | `kid` | Conditional | Required when `value` is **not** a decodable JWT (see below). |
32
- | `algorithm` | Conditional | Same as `kid` for non-JWT `value`. |
33
- | `signatureFormat` | Conditional | Same as `kid` for non-JWT `value` (encoding for the signature buffer, e.g. `hex` or `base64`). |
34
-
35
- There is no built-in OIDC discovery (`JWKS_ISSUER`), no environment-variable mapping in this package, and no `credentialSource` helper—you choose where `value` comes from in your application or hosting layer.
36
-
37
- ## Runtime handler (`jwks/authorize`)
38
-
39
- The default export is an async function:
40
-
41
- ```ts
42
- (options: JWKSAuthorizationContext) => Promise<boolean>
9
+ ```bash
10
+ npm install @t4h.framework/jwks @t4h.framework/core
43
11
  ```
44
12
 
45
- - Returns `false` if `value` is missing or verification fails.
46
- - Returns `true` if verification succeeds.
47
-
48
- ### JWT path
49
-
50
- If `value` decodes as a JWT (`jsonwebtoken` `decode` with `complete: true` succeeds), verification uses `validateJwt` ([source](https://github.com/tech4humans-brasil/framework/blob/main/packages/jwks/src/helpers/validateJwt.ts)):
51
-
52
- - Algorithm **RS256** only (`alg` in the header must be `RS256`).
53
- - Header must include `kid` (used to fetch the signing key from JWKS).
54
- - `exp` is enforced (token must not be expired).
55
- - Optional `issuer` and `audience` from the context are applied when present.
56
-
57
- ### Non-JWT path
58
-
59
- If `value` is not treated as a JWT by the decoder, the handler expects `kid`, `algorithm`, and `signatureFormat` on the context. If any of these are missing, it **throws** with a clear error. It loads the public key from JWKS by `kid` and verifies using Node’s `crypto.createVerify`.
60
-
61
- ## Example
13
+ ## Usage
62
14
 
63
15
  ```typescript
64
16
  import { JWKSAuthorization } from '@t4h.framework/jwks'
65
17
 
66
18
  const authorization = new JWKSAuthorization({
67
19
  url: 'https://auth.example.com/.well-known/jwks.json',
68
- value: '<token-or-payload-from-runtime>',
20
+ signature: '<jwt-from-the-request>',
21
+ rawPayload: '',
22
+ issuer: 'https://auth.example.com/',
69
23
  audience: 'https://api.example.com',
70
24
  })
71
25
  ```
72
26
 
73
- Wire `authorization` on an `App` or `Workflow` per the core package. The platform loads `@t4h.framework/jwks/authorize` with the manifest context when a protected workflow runs.
27
+ Attach `authorization` to a `Workflow` or an `App`.
28
+
29
+ ## Documentation
30
+
31
+ <https://tech4humans-brasil.github.io/framework/jwks/>
@@ -9,7 +9,7 @@ export async function validateJwt(token, client, options) {
9
9
  return false;
10
10
  const key = await client.getSigningKey(decoded.header.kid);
11
11
  const publicKey = key.getPublicKey();
12
- const isValid = await jwt.verify(token, publicKey, {
12
+ const isValid = jwt.verify(token, publicKey, {
13
13
  algorithms: ['RS256'],
14
14
  ignoreExpiration: false,
15
15
  issuer: options?.issuer,
@@ -1 +1 @@
1
- {"version":3,"file":"validateJwt.js","sourceRoot":"","sources":["../../src/helpers/validateJwt.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAA;AAQ9B,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAa,EACb,MAAkB,EAClB,OAA4B;IAE5B,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAErD,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAE1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG;QAAE,OAAO,KAAK,CAAA;IAErC,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,KAAK,OAAO;QAAE,OAAO,KAAK,CAAA;IAEhD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAE1D,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,EAAE,CAAA;IAEpC,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE;QACjD,UAAU,EAAE,CAAC,OAAO,CAAC;QACrB,gBAAgB,EAAE,KAAK;QACvB,MAAM,EAAE,OAAO,EAAE,MAAM;QACvB,QAAQ,EAAE,OAAO,EAAE,QAAQ;KAC5B,CAAC,CAAA;IAEF,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAA;AACpC,CAAC"}
1
+ {"version":3,"file":"validateJwt.js","sourceRoot":"","sources":["../../src/helpers/validateJwt.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAA;AAQ9B,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAa,EACb,MAAkB,EAClB,OAA4B;IAE5B,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAErD,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAE1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG;QAAE,OAAO,KAAK,CAAA;IAErC,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,KAAK,OAAO;QAAE,OAAO,KAAK,CAAA;IAEhD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAE1D,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,EAAE,CAAA;IAEpC,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE;QAC3C,UAAU,EAAE,CAAC,OAAO,CAAC;QACrB,gBAAgB,EAAE,KAAK;QACvB,MAAM,EAAE,OAAO,EAAE,MAAM;QACvB,QAAQ,EAAE,OAAO,EAAE,QAAQ;KAC5B,CAAC,CAAA;IAEF,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAA;AACpC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"jwks-callback.d.ts","sourceRoot":"","sources":["../src/jwks-callback.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,6BAA6B,EAClC,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAA;AAyG5B,wBAA8B,YAAY,CACxC,IAAI,EAAE,4BAA4B,GAAG,6BAA6B,EAClE,QAAQ,EAAE,qBAAqB,CAAC,QAAQ,GACvC,OAAO,CAAC,OAAO,CAAC,CAIlB"}
1
+ {"version":3,"file":"jwks-callback.d.ts","sourceRoot":"","sources":["../src/jwks-callback.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,6BAA6B,EAClC,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAA;AA+G5B,wBAA8B,YAAY,CACxC,IAAI,EAAE,4BAA4B,GAAG,6BAA6B,EAClE,QAAQ,EAAE,qBAAqB,CAAC,QAAQ,GACvC,OAAO,CAAC,OAAO,CAAC,CAIlB"}
@@ -1,14 +1,18 @@
1
1
  import { AsyncActivityCallback, } from '@t4h.framework/core';
2
2
  import jwks from './jwks-authorize.js';
3
+ function toEpochMilliseconds(timestamp) {
4
+ if (!/^\d+$/.test(timestamp))
5
+ return Date.parse(timestamp);
6
+ const isAlreadyInMilliseconds = timestamp.length >= 13;
7
+ if (isAlreadyInMilliseconds)
8
+ return Number(timestamp);
9
+ return Number(timestamp) * 1000;
10
+ }
3
11
  function isWithinTolerance(timestamp, toleranceInSeconds) {
4
12
  const value = timestamp.trim();
5
13
  if (!value)
6
14
  return false;
7
- const signedAt = /^\d+$/.test(value)
8
- ? value.length >= 13
9
- ? Number(value)
10
- : Number(value) * 1000
11
- : Date.parse(value);
15
+ const signedAt = toEpochMilliseconds(value);
12
16
  if (Number.isNaN(signedAt))
13
17
  return false;
14
18
  return Math.abs(Date.now() - signedAt) / 1000 <= toleranceInSeconds;
@@ -1 +1 @@
1
- {"version":3,"file":"jwks-callback.js","sourceRoot":"","sources":["../src/jwks-callback.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"jwks-callback.js","sourceRoot":"","sources":["../src/jwks-callback.ts"],"names":[],"mappings":"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"}
@@ -8,7 +8,7 @@ export type JWKSAuthorizationContext = {
8
8
  audience?: string;
9
9
  kid?: string;
10
10
  algorithm?: string;
11
- signatureFormat?: BinaryToTextEncoding | string;
11
+ signatureFormat?: BinaryToTextEncoding | (string & {});
12
12
  };
13
13
  export type JWKSAuthorizationManifest = AuthorizationManifest<JWKSAuthorizationContext>;
14
14
  export declare class JWKSAuthorization extends Authorization<JWKSAuthorizationContext> {
@@ -1 +1 @@
1
- {"version":3,"file":"JWKSAuthorization.d.ts","sourceRoot":"","sources":["../../src/models/JWKSAuthorization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAE/E,MAAM,MAAM,wBAAwB,GAAG;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAAA;CAChD,CAAA;AAED,MAAM,MAAM,yBAAyB,GACnC,qBAAqB,CAAC,wBAAwB,CAAC,CAAA;AAEjD,qBAAa,iBAAkB,SAAQ,aAAa,CAAC,wBAAwB,CAAC;IAChE,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,wBAAwB;IAIvD,UAAU,IAAI,yBAAyB;CAe/C"}
1
+ {"version":3,"file":"JWKSAuthorization.d.ts","sourceRoot":"","sources":["../../src/models/JWKSAuthorization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAE/E,MAAM,MAAM,wBAAwB,GAAG;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,oBAAoB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;CACvD,CAAA;AAED,MAAM,MAAM,yBAAyB,GACnC,qBAAqB,CAAC,wBAAwB,CAAC,CAAA;AAEjD,qBAAa,iBAAkB,SAAQ,aAAa,CAAC,wBAAwB,CAAC;IAChE,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,wBAAwB;IAIvD,UAAU,IAAI,yBAAyB;CAe/C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t4h.framework/jwks",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "description": "JWKS module for the T4H Framework authorization",
5
5
  "homepage": "https://github.com/tech4humans-brasil/framework/tree/main/packages/jwks",
6
6
  "bugs": "https://github.com/tech4humans-brasil/framework/issues",
@@ -36,12 +36,14 @@
36
36
  "scripts": {
37
37
  "build": "tsc --project tsconfig.build.json",
38
38
  "check-types": "tsc --noEmit",
39
+ "check-package": "publint --strict && attw --pack . --profile esm-only",
40
+ "lint": "eslint . --max-warnings 0",
39
41
  "prepublishOnly": "yarn build",
40
42
  "test": "vitest run",
41
43
  "test:watch": "vitest"
42
44
  },
43
45
  "devDependencies": {
44
- "@t4h.framework/core": "^0.19.0",
46
+ "@t4h.framework/core": "^0.21.0",
45
47
  "@types/jsonwebtoken": "^9",
46
48
  "typescript": "^5.9.3",
47
49
  "vitest": "^4.0.18"