@t4h.framework/oidc 0.4.0 → 0.5.0

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.
@@ -3,15 +3,15 @@ name: framework-oidc
3
3
  description: >-
4
4
  Wire OpenID Connect authorization in T4H Framework apps and workflows using
5
5
  @t4h.framework/oidc: OIDCAuthorization manifest, OIDCAuthorizationContext,
6
- Bearer token introspection via OAuth client credentials, discoveryUrl,
7
- scope, and resource. Use when securing App/Workflow with OIDC, token
6
+ RFC 7662 bearer token introspection with client_secret_basic, and
7
+ discoveryUrl. Use when securing App/Workflow with OIDC, token
8
8
  validation, OpenID Provider discovery, introspection_endpoint, attaching
9
9
  authorization to manifests, or testing @t4h.framework/oidc/authorize.
10
10
  ---
11
11
 
12
12
  # @t4h.framework/oidc
13
13
 
14
- OpenID Connect–oriented **authorization** for the T4H Framework. This package does **not** define Activities or Claims; it extends core’s `Authorization` and supplies a runtime handler that validates an incoming credential via **token introspection** after obtaining a service token with the **OAuth 2.0 client credentials** grant.
14
+ OpenID Connect–oriented **authorization** for the T4H Framework. This package does **not** define Activities or Claims; it extends core’s `Authorization` and supplies a runtime handler that validates an incoming credential via **token introspection** (**RFC 7662**), authenticating that call with **`client_secret_basic`**.
15
15
 
16
16
  Package path: `framework/packages/oidc`. Peer dependency: `@t4h.framework/core` (same major line). Node `>=22`.
17
17
 
@@ -61,7 +61,6 @@ export default new App({
61
61
  clientId: 'my-service',
62
62
  clientSecret: process.env.OIDC_CLIENT_SECRET!,
63
63
  token: 'Bearer <incoming-access-token-from-runtime>',
64
- scope: ['introspection'],
65
64
  }),
66
65
  workflows: [/* ... */],
67
66
  })
@@ -91,7 +90,7 @@ const secured = new Workflow(
91
90
  Subclass of `Authorization<OIDCAuthorizationContext>`. Constructor stores context; `toManifest()` always sets:
92
91
 
93
92
  - `pathToModule: '@t4h.framework/oidc/authorize'`
94
- - `context`: full context object (including `scope` / `resource` as `undefined` when omitted)
93
+ - `context`: full context object (`discoveryUrl`, `clientId`, `clientSecret`, `token`)
95
94
 
96
95
  ```typescript
97
96
  new OIDCAuthorization({
@@ -99,8 +98,6 @@ new OIDCAuthorization({
99
98
  clientId: 'svc',
100
99
  clientSecret: 'secret',
101
100
  token: 'Bearer t',
102
- scope: ['a', 'b'], // optional
103
- resource: 'https://api.example.com', // optional
104
101
  })
105
102
  ```
106
103
 
@@ -122,12 +119,10 @@ Author with whatever placeholder or value your deployment pipeline documents; th
122
119
 
123
120
  | Field | Required | Description |
124
121
  |-------|----------|-------------|
125
- | `discoveryUrl` | Yes | OpenID Provider configuration document URL (JSON with `token_endpoint` and `introspection_endpoint`). |
126
- | `clientId` | Yes | OAuth 2.0 client id for client credentials grant. |
127
- | `clientSecret` | Yes | OAuth 2.0 client secret for client credentials grant. |
122
+ | `discoveryUrl` | Yes | OpenID Provider configuration document URL (JSON with `introspection_endpoint`). |
123
+ | `clientId` | Yes | OAuth 2.0 client id used as the Basic username on introspection. |
124
+ | `clientSecret` | Yes | OAuth 2.0 client secret used as the Basic password on introspection. |
128
125
  | `token` | Yes | Credential to verify. Must use a `Bearer ` prefix for the token value to be sent to introspection (see below). |
129
- | `scope` | No | Scopes for the client-credentials token (`string[]`, joined with spaces on the wire). |
130
- | `resource` | No | If set, sent as `resource` on the client credentials request (RFC 8707 style). |
131
126
 
132
127
  Types exported: `OIDCAuthorizationContext`, `OIDCAuthorizationManifest` (`AuthorizationManifest<OIDCAuthorizationContext>`).
133
128
 
@@ -143,41 +138,33 @@ Default export: async function `(context: OIDCAuthorizationContext) => Promise<b
143
138
  sequenceDiagram
144
139
  participant R as Runtime handler
145
140
  participant D as discoveryUrl
146
- participant T as token_endpoint
147
141
  participant I as introspection_endpoint
148
142
 
149
143
  R->>D: GET discovery JSON
150
- R->>T: POST client_credentials
151
- T-->>R: access_token
152
- R->>I: POST JSON { token } + Bearer access_token
144
+ R->>I: POST form { token, token_type_hint } + Basic client credentials
153
145
  I-->>R: { active }
154
146
  R-->>R: true if active else false
155
147
  ```
156
148
 
157
- 1. **Discovery** — `GET` `discoveryUrl`; parse JSON as `{ token_endpoint, introspection_endpoint }`. Non-OK HTTP → throw `Discovery request failed: <status> <statusText>`.
158
- 2. **Client credentials** — `POST` `token_endpoint` with `application/x-www-form-urlencoded` body:
159
- - `grant_type=client_credentials`
160
- - `client_id`, `client_secret`
161
- - optional `scope` (space-joined from array)
162
- - optional `resource`
163
- Non-OK HTTP → throw `Token request failed: ...`.
164
- 3. **Introspection** — `POST` `introspection_endpoint` with:
165
- - `Content-Type: application/json`
166
- - `Authorization: Bearer <access_token>` (from step 2)
167
- - body `JSON.stringify({ token: <stripped incoming token or null> })`
149
+ 1. **Discovery** — `GET` `discoveryUrl`; parse JSON as `{ introspection_endpoint }`. Non-OK HTTP → throw `Discovery request failed: <status> <statusText>`.
150
+ 2. **Introspection** — `POST` `introspection_endpoint` per **RFC 7662** with:
151
+ - `Content-Type: application/x-www-form-urlencoded`
152
+ - `Accept: application/json`
153
+ - `Authorization: Basic base64(<form-urlencoded clientId>:<form-urlencoded clientSecret>)` (`client_secret_basic`, RFC 6749 section 2.3.1)
154
+ - body `URLSearchParams` with `token=<stripped incoming token>` and `token_type_hint=access_token`
168
155
  Non-OK HTTP → throw `Introspection request failed: ...`.
169
- 4. **Result** — parse `{ active: boolean }`:
156
+ 3. **Result** — parse the RFC 7662 response (`active` plus optional `scope`, `client_id`, `exp`, `sub`, `aud`, …):
170
157
  - `active: true` → return `true`
171
- - `active: false` → return `false`
158
+ - anything else, including a missing `active` → return `false`
172
159
 
173
160
  Network failures from `fetch` propagate as rejected promises (not converted to `false`).
174
161
 
175
162
  ### Bearer prefix rules
176
163
 
177
- Incoming `token` is parsed with a simple prefix check:
164
+ Incoming `token` is parsed as an RFC 6750 bearer credential (scheme matched case-insensitively):
178
165
 
179
- - Starts with `Bearer ` → introspection body uses the substring after the first `Bearer ` (e.g. `Bearer abc.def` → `"abc.def"`).
180
- - Does **not** start with `Bearer ` → introspection body sends `{ "token": null }` (typically yields `active: false`).
166
+ - `Bearer <token>` → introspection sends the token value (e.g. `Bearer abc.def` → `token=abc.def`).
167
+ - Anything else → the handler returns `false` immediately, with **no** discovery, token or introspection request (`token` is a required RFC 7662 parameter).
181
168
 
182
169
  Always author/runtime-resolve credentials as `Bearer <access-token>` when you expect introspection of the caller’s token.
183
170
 
@@ -188,10 +175,9 @@ Always author/runtime-resolve credentials as `Bearer <access-token>` when you ex
188
175
  1. Add `@t4h.framework/oidc` dependency alongside `@t4h.framework/core`.
189
176
  2. Construct `new OIDCAuthorization({ discoveryUrl, clientId, clientSecret, token, ... })`.
190
177
  3. Attach to `App` and/or `Workflow` `authorization` option.
191
- 4. Ensure IdP discovery document exposes `token_endpoint` and `introspection_endpoint`.
192
- 5. Register a confidential OAuth client allowed to use **client credentials** and call **introspection**.
178
+ 4. Ensure IdP discovery document exposes `introspection_endpoint`.
179
+ 5. Register a confidential OAuth client allowed to call **introspection** with **`client_secret_basic`**.
193
180
  6. Pass incoming caller token with `Bearer ` prefix (or use a runtime placeholder like `<<headers.authorization>>`).
194
- 7. Optionally set `scope` (e.g. scopes required for introspection on the service token) and `resource` (audience/resource indicator for the CC token).
195
181
 
196
182
  Do **not** look for OIDC Activities or Claim classes in this package — they do not exist here.
197
183
 
@@ -212,8 +198,6 @@ expect(authorization.toManifest()).toEqual({
212
198
  discoveryUrl: '...',
213
199
  clientId: 'svc',
214
200
  clientSecret: 'secret',
215
- scope: undefined, // when omitted
216
- resource: undefined,
217
201
  token: 'Bearer t',
218
202
  },
219
203
  })
@@ -221,7 +205,7 @@ expect(authorization.toManifest()).toEqual({
221
205
 
222
206
  ### Handler (`import oidc from '@t4h.framework/oidc/authorize'`)
223
207
 
224
- Mock three `fetch` calls in order: discovery → token → introspection.
208
+ Mock two `fetch` calls in order: discovery → introspection.
225
209
 
226
210
  ```typescript
227
211
  const baseContext = {
@@ -234,10 +218,8 @@ const baseContext = {
234
218
  // Happy path: true
235
219
  fetchMock
236
220
  .mockResolvedValueOnce(jsonResponse({
237
- token_endpoint: 'https://auth.example.com/oauth/token',
238
221
  introspection_endpoint: 'https://auth.example.com/oauth/introspect',
239
222
  }))
240
- .mockResolvedValueOnce(jsonResponse({ access_token: 'cc-access-token' }))
241
223
  .mockResolvedValueOnce(jsonResponse({ active: true }))
242
224
 
243
225
  await expect(oidc(baseContext)).resolves.toBe(true)
@@ -245,11 +227,10 @@ await expect(oidc(baseContext)).resolves.toBe(true)
245
227
 
246
228
  Verify wire format from tests:
247
229
 
248
- - Token request: `URLSearchParams` with `grant_type`, `client_id`, `client_secret`; optional `scope` (`openid profile` from `['openid','profile']`); optional `resource`.
249
- - Introspection: `Authorization: Bearer cc-access-token`, body `{ token: 'incoming-token' }` after stripping `Bearer `.
250
- - Missing Bearer prefix: body `{ token: null }`.
251
- - Inactive token: third response `{ active: false }` → `false`.
252
- - HTTP errors on discovery/token/introspection: thrown `Error` messages as documented above.
230
+ - Introspection: `Authorization: Basic <base64 client-id:client-secret>`, form body `token=incoming-token&token_type_hint=access_token` after stripping `Bearer `.
231
+ - Missing Bearer prefix: resolves `false` with `fetch` never called.
232
+ - Inactive token: second response `{ active: false }` → `false`; a response without `active` also yields `false`.
233
+ - HTTP errors on discovery/introspection: thrown `Error` messages as documented above.
253
234
 
254
235
  Run package tests: `yarn test` in `packages/oidc`.
255
236
 
@@ -261,7 +242,7 @@ Workflow-level authorization behavior in process is owned by the **runtime** (ou
261
242
 
262
243
  | Mistake | Consequence |
263
244
  |---------|-------------|
264
- | Raw JWT without `Bearer ` prefix | Introspection gets `token: null`; usually denied |
245
+ | Raw JWT without `Bearer ` prefix | Denied up front; no provider request is made |
265
246
  | Expecting env auto-wiring | Secrets/URLs must be set when constructing `OIDCAuthorization` |
266
247
  | Missing `introspection_endpoint` on IdP | Discovery JSON incomplete; runtime fails at fetch/parse |
267
248
  | Treating `false` as HTTP error | `false` means inactive token; errors are thrown separately |
@@ -277,6 +258,6 @@ Workflow-level authorization behavior in process is owned by the **runtime** (ou
277
258
  | `OIDCAuthorization` | class | `Authorization` subclass; `toManifest()` → oidc authorize module |
278
259
  | `OIDCAuthorizationContext` | type | Manifest/runtime context |
279
260
  | `OIDCAuthorizationManifest` | type | `AuthorizationManifest<OIDCAuthorizationContext>` |
280
- | default `oidc` | function | `@t4h.framework/oidc/authorize` — discovery + CC + introspection |
261
+ | default `oidc` | function | `@t4h.framework/oidc/authorize` — discovery + RFC 7662 introspection |
281
262
 
282
263
  See [README.md](../../../README.md) in this package for the same behavior described for humans.
package/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # @t4h.framework/oidc
2
+
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#147](https://github.com/tech4humans-brasil/framework/pull/147) [`45182d4`](https://github.com/tech4humans-brasil/framework/commit/45182d4a27c86353747646aa9b70a39bc4c56610) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - Makes the `oidc` authorize handler follow RFC 7662. The introspection request now posts `token` and `token_type_hint=access_token` as `application/x-www-form-urlencoded` (instead of a JSON body), sends `Accept: application/json`, and only allows the run when the response reports `active: true`. The incoming credential is parsed as an RFC 6750 bearer credential with a case-insensitive scheme, and a credential without a `Bearer ` prefix is denied up front — `token` is a required introspection parameter, so no request is made for it.
8
+
9
+ The client credentials grant is gone: the handler now authenticates the introspection call directly with `client_secret_basic` (RFC 6749 section 2.3.1), so a check costs one round-trip instead of two and your OAuth client only needs permission to call introspection. **Breaking:** `scope` and `resource` were removed from `OIDCAuthorizationContext` — they only fed the client credentials request. Drop them from your `OIDCAuthorization` context and make sure the client is allowed to introspect with `client_secret_basic`.
10
+
11
+ ## 0.4.0
12
+
13
+ ### Patch Changes
14
+
15
+ - Update skills ([`6d7e7bc`](https://github.com/tech4humans-brasil/framework/commit/6d7e7bca0c780a9c53a0c9e5aa0a865c5af67959))
16
+
17
+ ## 0.3.0
18
+
19
+ ### Minor Changes
20
+
21
+ - Implements oidc package ([#53](https://github.com/tech4humans-brasil/framework/pull/53))
22
+ - Adds .ai/skills in packages ([#88](https://github.com/tech4humans-brasil/framework/pull/88))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tech for humans
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @t4h.framework/oidc
2
2
 
3
- OpenID Connect–oriented authorization for the T4H Framework. It exposes a core `Authorization` manifest and a runtime handler that obtains an access token via the OAuth 2.0 client credentials grant, then validates an incoming credential through the **token introspection** endpoint from the issuer’s OpenID configuration.
3
+ OpenID Connect–oriented authorization for the T4H Framework. It exposes a core `Authorization` manifest and a runtime handler that validates an incoming credential through the **token introspection** endpoint ([RFC 7662](https://www.rfc-editor.org/rfc/rfc7662)) from the issuer’s OpenID configuration.
4
4
 
5
5
  ## Peer dependency
6
6
 
@@ -23,12 +23,10 @@ You construct it with a context object; your app or hosting layer is responsible
23
23
 
24
24
  | Field | Required | Description |
25
25
  |-------|----------|-------------|
26
- | `discoveryUrl` | Yes | OpenID Provider configuration document URL (must return JSON with at least `token_endpoint` and `introspection_endpoint`). |
27
- | `clientId` | Yes | OAuth 2.0 client identifier used for the client credentials grant. |
28
- | `clientSecret` | Yes | OAuth 2.0 client secret used for the client credentials grant. |
26
+ | `discoveryUrl` | Yes | OpenID Provider configuration document URL (must return JSON with at least `introspection_endpoint`). |
27
+ | `clientId` | Yes | OAuth 2.0 client identifier used to authenticate the introspection call. |
28
+ | `clientSecret` | Yes | OAuth 2.0 client secret used to authenticate the introspection call. |
29
29
  | `token` | Yes | Credential to verify. Must use a `Bearer ` prefix (see below). |
30
- | `scope` | No | Scopes requested on the client credentials token (array of strings, joined with spaces for the request). |
31
- | `resource` | No | If set, sent as the `resource` parameter on the client credentials request (RFC 8707 style resource indication). |
32
30
 
33
31
  ## Runtime handler (`oidc/authorize`)
34
32
 
@@ -40,16 +38,15 @@ The default export is an async function:
40
38
 
41
39
  Behavior:
42
40
 
43
- 1. **Discovery** — `GET` `discoveryUrl` and read JSON with `token_endpoint` and `introspection_endpoint`.
44
- 2. **Client credentials** — `POST` the token endpoint with `grant_type=client_credentials`, `client_id`, `client_secret`, and optional `scope` / `resource` (`application/x-www-form-urlencoded`).
45
- 3. **Introspection** — `POST` the introspection endpoint with JSON body `{ "token": "<access token>" }`, where the value is taken from `token` after stripping the leading `Bearer ` prefix.
46
- 4. **Authorization header** — The introspection call uses `Authorization: Bearer <access_token>` with the access token from step 2.
41
+ 1. **Discovery** — `GET` `discoveryUrl` and read JSON with `introspection_endpoint`.
42
+ 2. **Introspection** — `POST` the introspection endpoint following [RFC 7662](https://www.rfc-editor.org/rfc/rfc7662), with `token` and `token_type_hint=access_token` as `application/x-www-form-urlencoded`, where `token` is taken from the context `token` after stripping the `Bearer ` prefix (the scheme is matched case-insensitively).
43
+ 3. **Client authentication** — RFC 7662 requires the caller to authorize itself; the handler uses `client_secret_basic` (RFC 6749 section 2.3.1), sending `Authorization: Basic base64(clientId:clientSecret)` with both values form-urlencoded. Your client must be allowed to call introspection with this method.
47
44
 
48
45
  Result:
49
46
 
50
- - Returns `true` if the introspection response includes `"active": true`.
51
- - Returns `false` when the introspection response includes `"active": false`.
52
- - If the incoming credential does not start with `Bearer `, the handler sends `token: null` to introspection (final result then depends on the provider response, and is typically `false`).
47
+ - Returns `true` only if the introspection response includes `"active": true`.
48
+ - Returns `false` for any other response, including a missing `active` member.
49
+ - If the incoming credential does not carry a `Bearer ` prefix, the handler returns `false` without contacting the provider — `token` is a required introspection parameter.
53
50
 
54
51
  There is no built-in mapping from environment variables; all configuration comes from the manifest context.
55
52
 
@@ -63,7 +60,6 @@ const authorization = new OIDCAuthorization({
63
60
  clientId: 'my-service',
64
61
  clientSecret: process.env.OIDC_CLIENT_SECRET!,
65
62
  token: 'Bearer <incoming-access-token-from-runtime>',
66
- scope: ['introspection'],
67
63
  })
68
64
  ```
69
65
 
@@ -4,8 +4,6 @@ export type OIDCAuthorizationContext = {
4
4
  clientId: string;
5
5
  clientSecret: string;
6
6
  token: string;
7
- scope?: string[];
8
- resource?: string;
9
7
  };
10
8
  export type OIDCAuthorizationManifest = AuthorizationManifest<OIDCAuthorizationContext>;
11
9
  export declare class OIDCAuthorization extends Authorization<OIDCAuthorizationContext> {
@@ -1 +1 @@
1
- {"version":3,"file":"OIDCAuthorization.d.ts","sourceRoot":"","sources":["../../src/models/OIDCAuthorization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAE/E,MAAM,MAAM,wBAAwB,GAAG;IACrC,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,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;CAa/C"}
1
+ {"version":3,"file":"OIDCAuthorization.d.ts","sourceRoot":"","sources":["../../src/models/OIDCAuthorization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAE/E,MAAM,MAAM,wBAAwB,GAAG;IACrC,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;CACd,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;CAW/C"}
@@ -11,8 +11,6 @@ export class OIDCAuthorization extends Authorization {
11
11
  discoveryUrl: this.context.discoveryUrl,
12
12
  clientId: this.context.clientId,
13
13
  clientSecret: this.context.clientSecret,
14
- scope: this.context.scope,
15
- resource: this.context.resource,
16
14
  token: this.context.token,
17
15
  },
18
16
  pathToModule: '@t4h.framework/oidc/authorize',
@@ -1 +1 @@
1
- {"version":3,"file":"OIDCAuthorization.js","sourceRoot":"","sources":["../../src/models/OIDCAuthorization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA8B,MAAM,qBAAqB,CAAA;AAc/E,MAAM,OAAO,iBAAkB,SAAQ,aAAuC;IAC/C;IAA7B,YAA6B,OAAiC;QAC5D,KAAK,EAAE,CAAA;QADoB,YAAO,GAAP,OAAO,CAA0B;IAE9D,CAAC;IAEM,UAAU;QACf,OAAO;YACL,OAAO,EAAE;gBACP,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY;gBACvC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAC/B,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY;gBACvC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK;gBACzB,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAC/B,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK;aAC1B;YACD,YAAY,EAAE,+BAA+B;SAC9C,CAAA;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"OIDCAuthorization.js","sourceRoot":"","sources":["../../src/models/OIDCAuthorization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA8B,MAAM,qBAAqB,CAAA;AAY/E,MAAM,OAAO,iBAAkB,SAAQ,aAAuC;IAC/C;IAA7B,YAA6B,OAAiC;QAC5D,KAAK,EAAE,CAAA;QADoB,YAAO,GAAP,OAAO,CAA0B;IAE9D,CAAC;IAEM,UAAU;QACf,OAAO;YACL,OAAO,EAAE;gBACP,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY;gBACvC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAC/B,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY;gBACvC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK;aAC1B;YACD,YAAY,EAAE,+BAA+B;SAC9C,CAAA;IACH,CAAC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"oidc-authorize.d.ts","sourceRoot":"","sources":["../src/oidc-authorize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAA;AAezD,wBAA8B,IAAI,CAChC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,OAAO,CAAC,CAiElB"}
1
+ {"version":3,"file":"oidc-authorize.d.ts","sourceRoot":"","sources":["../src/oidc-authorize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAA;AAmCzD,wBAA8B,IAAI,CAChC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,OAAO,CAAC,CA6ClB"}
@@ -1,53 +1,41 @@
1
+ const BEARER_CREDENTIAL = /^Bearer +(?<token>[^ ]+) *$/i;
1
2
  function getAccessToken(token) {
2
- if (!token.startsWith('Bearer '))
3
- return null;
4
- const [, accessToken] = token.split('Bearer ');
5
- return accessToken;
3
+ const match = BEARER_CREDENTIAL.exec(token);
4
+ return match?.groups?.token ?? null;
5
+ }
6
+ function encodeCredential(value) {
7
+ const [, encoded] = new URLSearchParams({ value }).toString().split('=');
8
+ return encoded;
6
9
  }
7
10
  export default async function oidc(context) {
11
+ const accessToken = getAccessToken(context.token);
12
+ if (!accessToken)
13
+ return false;
8
14
  const discoveryResponse = await fetch(context.discoveryUrl, {
9
15
  method: 'GET',
16
+ headers: {
17
+ Accept: 'application/json',
18
+ },
10
19
  });
11
20
  if (!discoveryResponse.ok)
12
21
  throw new Error(`Discovery request failed: ${discoveryResponse.status} ${discoveryResponse.statusText}`);
13
22
  const discovery = (await discoveryResponse.json());
14
- const scope = Array.isArray(context.scope)
15
- ? context.scope.join(' ')
16
- : context.scope;
17
- const body = {
18
- grant_type: 'client_credentials',
19
- client_id: context.clientId,
20
- client_secret: context.clientSecret,
21
- };
22
- if (scope)
23
- body.scope = scope;
24
- if (context.resource)
25
- body.resource = context.resource;
26
- const tokenResponse = await fetch(discovery.token_endpoint, {
27
- body: new URLSearchParams(body),
28
- method: 'POST',
29
- headers: {
30
- 'Content-Type': 'application/x-www-form-urlencoded',
31
- },
32
- });
33
- if (!tokenResponse.ok)
34
- throw new Error(`Token request failed: ${tokenResponse.status} ${tokenResponse.statusText}`);
35
- const { access_token } = (await tokenResponse.json());
23
+ const credentials = Buffer.from(`${encodeCredential(context.clientId)}:${encodeCredential(context.clientSecret)}`).toString('base64');
36
24
  const introspectionResponse = await fetch(discovery.introspection_endpoint, {
37
- body: JSON.stringify({
38
- token: getAccessToken(context.token),
25
+ body: new URLSearchParams({
26
+ token: accessToken,
27
+ token_type_hint: 'access_token',
39
28
  }),
40
29
  method: 'POST',
41
30
  headers: {
42
- 'Content-Type': 'application/json',
43
- Authorization: `Bearer ${access_token}`,
31
+ 'Content-Type': 'application/x-www-form-urlencoded',
32
+ Accept: 'application/json',
33
+ Authorization: `Basic ${credentials}`,
44
34
  },
45
35
  });
46
36
  if (!introspectionResponse.ok)
47
37
  throw new Error(`Introspection request failed: ${introspectionResponse.status} ${introspectionResponse.statusText}`);
48
38
  const introspection = (await introspectionResponse.json());
49
- if (!introspection.active)
50
- return false;
51
- return true;
39
+ return introspection.active === true;
52
40
  }
53
41
  //# sourceMappingURL=oidc-authorize.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"oidc-authorize.js","sourceRoot":"","sources":["../src/oidc-authorize.ts"],"names":[],"mappings":"AAOA,SAAS,cAAc,CAAC,KAAa;IACnC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAA;IAE7C,MAAM,CAAC,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAE9C,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,IAAI,CAChC,OAAiC;IAEjC,MAAM,iBAAiB,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE;QAC1D,MAAM,EAAE,KAAK;KACd,CAAC,CAAA;IAEF,IAAI,CAAC,iBAAiB,CAAC,EAAE;QACvB,MAAM,IAAI,KAAK,CACb,6BAA6B,iBAAiB,CAAC,MAAM,IAAI,iBAAiB,CAAC,UAAU,EAAE,CACxF,CAAA;IAEH,MAAM,SAAS,GAAG,CAAC,MAAM,iBAAiB,CAAC,IAAI,EAAE,CAAwB,CAAA;IAEzE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC;QACxC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;QACzB,CAAC,CAAC,OAAO,CAAC,KAAK,CAAA;IAEjB,MAAM,IAAI,GAA2B;QACnC,UAAU,EAAE,oBAAoB;QAChC,SAAS,EAAE,OAAO,CAAC,QAAQ;QAC3B,aAAa,EAAE,OAAO,CAAC,YAAY;KACpC,CAAA;IAED,IAAI,KAAK;QAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IAC7B,IAAI,OAAO,CAAC,QAAQ;QAAE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;IAEtD,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,cAAc,EAAE;QAC1D,IAAI,EAAE,IAAI,eAAe,CAAC,IAAI,CAAC;QAC/B,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,mCAAmC;SACpD;KACF,CAAC,CAAA;IAEF,IAAI,CAAC,aAAa,CAAC,EAAE;QACnB,MAAM,IAAI,KAAK,CACb,yBAAyB,aAAa,CAAC,MAAM,IAAI,aAAa,CAAC,UAAU,EAAE,CAC5E,CAAA;IAEH,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,aAAa,CAAC,IAAI,EAAE,CAEnD,CAAA;IAED,MAAM,qBAAqB,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,sBAAsB,EAAE;QAC1E,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC;SACrC,CAAC;QACF,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,YAAY,EAAE;SACxC;KACF,CAAC,CAAA;IAEF,IAAI,CAAC,qBAAqB,CAAC,EAAE;QAC3B,MAAM,IAAI,KAAK,CACb,iCAAiC,qBAAqB,CAAC,MAAM,IAAI,qBAAqB,CAAC,UAAU,EAAE,CACpG,CAAA;IAEH,MAAM,aAAa,GAAG,CAAC,MAAM,qBAAqB,CAAC,IAAI,EAAE,CAExD,CAAA;IAED,IAAI,CAAC,aAAa,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEvC,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"oidc-authorize.js","sourceRoot":"","sources":["../src/oidc-authorize.ts"],"names":[],"mappings":"AAqBA,MAAM,iBAAiB,GAAG,8BAA8B,CAAA;AAExD,SAAS,cAAc,CAAC,KAAa;IACnC,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAE3C,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI,CAAA;AACrC,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC,GAAG,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAExE,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,IAAI,CAChC,OAAiC;IAEjC,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAEjD,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAA;IAE9B,MAAM,iBAAiB,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE;QAC1D,MAAM,EAAE,KAAK;QACb,OAAO,EAAE;YACP,MAAM,EAAE,kBAAkB;SAC3B;KACF,CAAC,CAAA;IAEF,IAAI,CAAC,iBAAiB,CAAC,EAAE;QACvB,MAAM,IAAI,KAAK,CACb,6BAA6B,iBAAiB,CAAC,MAAM,IAAI,iBAAiB,CAAC,UAAU,EAAE,CACxF,CAAA;IAEH,MAAM,SAAS,GAAG,CAAC,MAAM,iBAAiB,CAAC,IAAI,EAAE,CAAwB,CAAA;IAEzE,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAC7B,GAAG,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAClF,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAEpB,MAAM,qBAAqB,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,sBAAsB,EAAE;QAC1E,IAAI,EAAE,IAAI,eAAe,CAAC;YACxB,KAAK,EAAE,WAAW;YAClB,eAAe,EAAE,cAAc;SAChC,CAAC;QACF,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,mCAAmC;YACnD,MAAM,EAAE,kBAAkB;YAC1B,aAAa,EAAE,SAAS,WAAW,EAAE;SACtC;KACF,CAAC,CAAA;IAEF,IAAI,CAAC,qBAAqB,CAAC,EAAE;QAC3B,MAAM,IAAI,KAAK,CACb,iCAAiC,qBAAqB,CAAC,MAAM,IAAI,qBAAqB,CAAC,UAAU,EAAE,CACpG,CAAA;IAEH,MAAM,aAAa,GACjB,CAAC,MAAM,qBAAqB,CAAC,IAAI,EAAE,CAA0B,CAAA;IAE/D,OAAO,aAAa,CAAC,MAAM,KAAK,IAAI,CAAA;AACtC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t4h.framework/oidc",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "OIDC module for the T4H Framework authorization",
5
5
  "homepage": "https://github.com/tech4humans-brasil/framework/tree/main/packages/oidc",
6
6
  "bugs": "https://github.com/tech4humans-brasil/framework/issues",
@@ -37,12 +37,12 @@
37
37
  "test:watch": "vitest"
38
38
  },
39
39
  "devDependencies": {
40
- "@t4h.framework/core": "^0.8.0",
40
+ "@t4h.framework/core": "^0.9.1",
41
41
  "typescript": "^5.9.3",
42
42
  "vitest": "^4.0.18"
43
43
  },
44
44
  "peerDependencies": {
45
- "@t4h.framework/core": "^0.8.0"
45
+ "@t4h.framework/core": "^0.9.1"
46
46
  },
47
47
  "packageManager": "yarn@4.12.0",
48
48
  "engines": {
@@ -1,3 +0,0 @@
1
- import type { OIDCAuthorizationOptions } from '../oidc.js';
2
- export declare function clientCredentials(options: OIDCAuthorizationOptions): Promise<boolean>;
3
- //# sourceMappingURL=client-credentials.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-credentials.d.ts","sourceRoot":"","sources":["../../src/helpers/client-credentials.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAE1D,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,oBA0BxE"}
@@ -1,18 +0,0 @@
1
- import * as client from 'openid-client';
2
- export async function clientCredentials(options) {
3
- const discovery = await client.discovery(new URL(options.url), options.clientId, options.clientSecret);
4
- const scope = Array.isArray(options.scope)
5
- ? options.scope.join(' ')
6
- : options.scope;
7
- const params = new URLSearchParams();
8
- if (scope)
9
- params.set('scope', scope);
10
- if (options.resource)
11
- params.set('resource', options.resource);
12
- const { access_token } = await client.clientCredentialsGrant(discovery, params);
13
- const token = await client.tokenIntrospection(discovery, access_token);
14
- if (!token.active)
15
- return false;
16
- return true;
17
- }
18
- //# sourceMappingURL=client-credentials.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-credentials.js","sourceRoot":"","sources":["../../src/helpers/client-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAIvC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAiC;IACvE,MAAM,SAAS,GAAyB,MAAM,MAAM,CAAC,SAAS,CAC5D,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,EACpB,OAAO,CAAC,QAAQ,EAChB,OAAO,CAAC,YAAY,CACrB,CAAA;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC;QACxC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;QACzB,CAAC,CAAC,OAAO,CAAC,KAAK,CAAA;IAEjB,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAA;IAEpC,IAAI,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IACrC,IAAI,OAAO,CAAC,QAAQ;QAAE,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAE9D,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAC1D,SAAS,EACT,MAAM,CACP,CAAA;IAED,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;IAEtE,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAE/B,OAAO,IAAI,CAAA;AACb,CAAC"}