@t4h.framework/oidc 0.3.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.
- package/.ai/skills/framework-oidc/SKILL.md +263 -0
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +10 -14
- package/dist/models/OIDCAuthorization.d.ts +0 -2
- package/dist/models/OIDCAuthorization.d.ts.map +1 -1
- package/dist/models/OIDCAuthorization.js +0 -2
- package/dist/models/OIDCAuthorization.js.map +1 -1
- package/dist/oidc-authorize.d.ts.map +1 -1
- package/dist/oidc-authorize.js +21 -33
- package/dist/oidc-authorize.js.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: framework-oidc
|
|
3
|
+
description: >-
|
|
4
|
+
Wire OpenID Connect authorization in T4H Framework apps and workflows using
|
|
5
|
+
@t4h.framework/oidc: OIDCAuthorization manifest, OIDCAuthorizationContext,
|
|
6
|
+
RFC 7662 bearer token introspection with client_secret_basic, and
|
|
7
|
+
discoveryUrl. Use when securing App/Workflow with OIDC, token
|
|
8
|
+
validation, OpenID Provider discovery, introspection_endpoint, attaching
|
|
9
|
+
authorization to manifests, or testing @t4h.framework/oidc/authorize.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# @t4h.framework/oidc
|
|
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** (**RFC 7662**), authenticating that call with **`client_secret_basic`**.
|
|
15
|
+
|
|
16
|
+
Package path: `framework/packages/oidc`. Peer dependency: `@t4h.framework/core` (same major line). Node `>=22`.
|
|
17
|
+
|
|
18
|
+
For workflow Activities, Claims, and `History.reconciler`, use the **framework-core** skill. OIDC only gates access at the App/Workflow authorization layer.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Imports
|
|
23
|
+
|
|
24
|
+
| Entry | Import | Purpose |
|
|
25
|
+
|--------|--------|---------|
|
|
26
|
+
| `@t4h.framework/oidc` | `OIDCAuthorization`, `OIDCAuthorizationContext`, `OIDCAuthorizationManifest` | Author-time manifest |
|
|
27
|
+
| `@t4h.framework/oidc/authorize` | default `oidc` handler | Runtime/tests only — `(context) => Promise<boolean>` |
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { OIDCAuthorization } from '@t4h.framework/oidc'
|
|
31
|
+
import type { OIDCAuthorizationContext } from '@t4h.framework/oidc'
|
|
32
|
+
|
|
33
|
+
// Direct handler import (unit tests, custom runtime wiring):
|
|
34
|
+
import oidc from '@t4h.framework/oidc/authorize'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Where it fits (core `Authorization`)
|
|
40
|
+
|
|
41
|
+
Core exposes an abstract `Authorization` with `toManifest(): AuthorizationManifest`:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
type AuthorizationManifest<TContext extends object = any> = {
|
|
45
|
+
context?: TContext
|
|
46
|
+
pathToModule: string
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Attach `OIDCAuthorization` on **`App`** or **`Workflow`** options; `toManifest()` embeds `authorization` in the published manifest. The deployed runtime resolves dynamic fields (especially `token`), then loads `@t4h.framework/oidc/authorize` with the manifest `context`.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { App } from '@t4h.framework/core'
|
|
54
|
+
import { OIDCAuthorization } from '@t4h.framework/oidc'
|
|
55
|
+
|
|
56
|
+
export default new App({
|
|
57
|
+
id: 'my-app',
|
|
58
|
+
authorization: new OIDCAuthorization({
|
|
59
|
+
discoveryUrl:
|
|
60
|
+
'https://auth.example.com/.well-known/openid-configuration',
|
|
61
|
+
clientId: 'my-service',
|
|
62
|
+
clientSecret: process.env.OIDC_CLIENT_SECRET!,
|
|
63
|
+
token: 'Bearer <incoming-access-token-from-runtime>',
|
|
64
|
+
}),
|
|
65
|
+
workflows: [/* ... */],
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Per-workflow override:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { Workflow } from '@t4h.framework/core'
|
|
73
|
+
import { OIDCAuthorization } from '@t4h.framework/oidc'
|
|
74
|
+
|
|
75
|
+
const secured = new Workflow(
|
|
76
|
+
{
|
|
77
|
+
id: 'orders/v1',
|
|
78
|
+
authorization: new OIDCAuthorization({ /* same context shape */ }),
|
|
79
|
+
},
|
|
80
|
+
async input => ({ ok: true }),
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**App-level vs workflow-level:** set on whichever manifest should enforce OIDC. Workflow `authorization` in `toManifest()` is `authorization?.toManifest() ?? null`; App forwards `authorization?.toManifest()` at app scope.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## `OIDCAuthorization`
|
|
89
|
+
|
|
90
|
+
Subclass of `Authorization<OIDCAuthorizationContext>`. Constructor stores context; `toManifest()` always sets:
|
|
91
|
+
|
|
92
|
+
- `pathToModule: '@t4h.framework/oidc/authorize'`
|
|
93
|
+
- `context`: full context object (`discoveryUrl`, `clientId`, `clientSecret`, `token`)
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
new OIDCAuthorization({
|
|
97
|
+
discoveryUrl: 'https://auth.example.com/.well-known/openid-configuration',
|
|
98
|
+
clientId: 'svc',
|
|
99
|
+
clientSecret: 'secret',
|
|
100
|
+
token: 'Bearer t',
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
There is **no** built-in mapping from environment variables — supply `clientSecret`, `discoveryUrl`, etc. from your app/hosting layer when constructing the instance.
|
|
105
|
+
|
|
106
|
+
### Runtime token placeholder
|
|
107
|
+
|
|
108
|
+
`token` is authored as a manifest placeholder the runtime replaces:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
token: '<<headers.authorization>>',
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Author with whatever placeholder or value your deployment pipeline documents; the handler always receives the resolved string at runtime.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## `OIDCAuthorizationContext`
|
|
119
|
+
|
|
120
|
+
| Field | Required | Description |
|
|
121
|
+
|-------|----------|-------------|
|
|
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. |
|
|
125
|
+
| `token` | Yes | Credential to verify. Must use a `Bearer ` prefix for the token value to be sent to introspection (see below). |
|
|
126
|
+
|
|
127
|
+
Types exported: `OIDCAuthorizationContext`, `OIDCAuthorizationManifest` (`AuthorizationManifest<OIDCAuthorizationContext>`).
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Runtime handler (`@t4h.framework/oidc/authorize`)
|
|
132
|
+
|
|
133
|
+
Default export: async function `(context: OIDCAuthorizationContext) => Promise<boolean>`.
|
|
134
|
+
|
|
135
|
+
### Flow
|
|
136
|
+
|
|
137
|
+
```mermaid
|
|
138
|
+
sequenceDiagram
|
|
139
|
+
participant R as Runtime handler
|
|
140
|
+
participant D as discoveryUrl
|
|
141
|
+
participant I as introspection_endpoint
|
|
142
|
+
|
|
143
|
+
R->>D: GET discovery JSON
|
|
144
|
+
R->>I: POST form { token, token_type_hint } + Basic client credentials
|
|
145
|
+
I-->>R: { active }
|
|
146
|
+
R-->>R: true if active else false
|
|
147
|
+
```
|
|
148
|
+
|
|
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`
|
|
155
|
+
Non-OK HTTP → throw `Introspection request failed: ...`.
|
|
156
|
+
3. **Result** — parse the RFC 7662 response (`active` plus optional `scope`, `client_id`, `exp`, `sub`, `aud`, …):
|
|
157
|
+
- `active: true` → return `true`
|
|
158
|
+
- anything else, including a missing `active` → return `false`
|
|
159
|
+
|
|
160
|
+
Network failures from `fetch` propagate as rejected promises (not converted to `false`).
|
|
161
|
+
|
|
162
|
+
### Bearer prefix rules
|
|
163
|
+
|
|
164
|
+
Incoming `token` is parsed as an RFC 6750 bearer credential (scheme matched case-insensitively):
|
|
165
|
+
|
|
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).
|
|
168
|
+
|
|
169
|
+
Always author/runtime-resolve credentials as `Bearer <access-token>` when you expect introspection of the caller’s token.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Authoring checklist
|
|
174
|
+
|
|
175
|
+
1. Add `@t4h.framework/oidc` dependency alongside `@t4h.framework/core`.
|
|
176
|
+
2. Construct `new OIDCAuthorization({ discoveryUrl, clientId, clientSecret, token, ... })`.
|
|
177
|
+
3. Attach to `App` and/or `Workflow` `authorization` option.
|
|
178
|
+
4. Ensure IdP discovery document exposes `introspection_endpoint`.
|
|
179
|
+
5. Register a confidential OAuth client allowed to call **introspection** with **`client_secret_basic`**.
|
|
180
|
+
6. Pass incoming caller token with `Bearer ` prefix (or use a runtime placeholder like `<<headers.authorization>>`).
|
|
181
|
+
|
|
182
|
+
Do **not** look for OIDC Activities or Claim classes in this package — they do not exist here.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Testing
|
|
187
|
+
|
|
188
|
+
Package tests use **Vitest** with `vi.stubGlobal('fetch', ...)`.
|
|
189
|
+
|
|
190
|
+
### `OIDCAuthorization.toManifest()`
|
|
191
|
+
|
|
192
|
+
Assert manifest shape:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
expect(authorization.toManifest()).toEqual({
|
|
196
|
+
pathToModule: '@t4h.framework/oidc/authorize',
|
|
197
|
+
context: {
|
|
198
|
+
discoveryUrl: '...',
|
|
199
|
+
clientId: 'svc',
|
|
200
|
+
clientSecret: 'secret',
|
|
201
|
+
token: 'Bearer t',
|
|
202
|
+
},
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Handler (`import oidc from '@t4h.framework/oidc/authorize'`)
|
|
207
|
+
|
|
208
|
+
Mock two `fetch` calls in order: discovery → introspection.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const baseContext = {
|
|
212
|
+
discoveryUrl: 'https://auth.example.com/.well-known/openid-configuration',
|
|
213
|
+
clientId: 'client-id',
|
|
214
|
+
clientSecret: 'client-secret',
|
|
215
|
+
token: 'Bearer incoming-token',
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Happy path: true
|
|
219
|
+
fetchMock
|
|
220
|
+
.mockResolvedValueOnce(jsonResponse({
|
|
221
|
+
introspection_endpoint: 'https://auth.example.com/oauth/introspect',
|
|
222
|
+
}))
|
|
223
|
+
.mockResolvedValueOnce(jsonResponse({ active: true }))
|
|
224
|
+
|
|
225
|
+
await expect(oidc(baseContext)).resolves.toBe(true)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Verify wire format from tests:
|
|
229
|
+
|
|
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.
|
|
234
|
+
|
|
235
|
+
Run package tests: `yarn test` in `packages/oidc`.
|
|
236
|
+
|
|
237
|
+
Workflow-level authorization behavior in process is owned by the **runtime** (outside this package); this package only defines manifest + handler contract.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Common mistakes
|
|
242
|
+
|
|
243
|
+
| Mistake | Consequence |
|
|
244
|
+
|---------|-------------|
|
|
245
|
+
| Raw JWT without `Bearer ` prefix | Denied up front; no provider request is made |
|
|
246
|
+
| Expecting env auto-wiring | Secrets/URLs must be set when constructing `OIDCAuthorization` |
|
|
247
|
+
| Missing `introspection_endpoint` on IdP | Discovery JSON incomplete; runtime fails at fetch/parse |
|
|
248
|
+
| Treating `false` as HTTP error | `false` means inactive token; errors are thrown separately |
|
|
249
|
+
| Adding OIDC as a Claim provider | Wrong abstraction — use `Authorization` on App/Workflow |
|
|
250
|
+
| Importing handler from main entry | Use `@t4h.framework/oidc/authorize` (default export) |
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## API summary
|
|
255
|
+
|
|
256
|
+
| Symbol | Kind | Role |
|
|
257
|
+
|--------|------|------|
|
|
258
|
+
| `OIDCAuthorization` | class | `Authorization` subclass; `toManifest()` → oidc authorize module |
|
|
259
|
+
| `OIDCAuthorizationContext` | type | Manifest/runtime context |
|
|
260
|
+
| `OIDCAuthorizationManifest` | type | `AuthorizationManifest<OIDCAuthorizationContext>` |
|
|
261
|
+
| default `oidc` | function | `@t4h.framework/oidc/authorize` — discovery + RFC 7662 introspection |
|
|
262
|
+
|
|
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
|
|
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 `
|
|
27
|
-
| `clientId` | Yes | OAuth 2.0 client identifier used
|
|
28
|
-
| `clientSecret` | Yes | OAuth 2.0 client secret used
|
|
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 `
|
|
44
|
-
2. **
|
|
45
|
-
3. **
|
|
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`
|
|
52
|
-
- If the incoming credential does not
|
|
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;
|
|
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;
|
|
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;
|
|
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"}
|
package/dist/oidc-authorize.js
CHANGED
|
@@ -1,53 +1,41 @@
|
|
|
1
|
+
const BEARER_CREDENTIAL = /^Bearer +(?<token>[^ ]+) *$/i;
|
|
1
2
|
function getAccessToken(token) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
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:
|
|
38
|
-
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/
|
|
43
|
-
|
|
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
|
-
|
|
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":"
|
|
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.
|
|
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",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"types": "./dist/oidc.d.ts",
|
|
27
27
|
"files": [
|
|
28
28
|
"dist",
|
|
29
|
-
"LICENSE"
|
|
29
|
+
"LICENSE",
|
|
30
|
+
".ai"
|
|
30
31
|
],
|
|
31
32
|
"scripts": {
|
|
32
33
|
"build": "tsc --project tsconfig.build.json",
|
|
@@ -36,12 +37,12 @@
|
|
|
36
37
|
"test:watch": "vitest"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@t4h.framework/core": "^0.
|
|
40
|
+
"@t4h.framework/core": "^0.9.1",
|
|
40
41
|
"typescript": "^5.9.3",
|
|
41
42
|
"vitest": "^4.0.18"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
|
-
"@t4h.framework/core": "^0.
|
|
45
|
+
"@t4h.framework/core": "^0.9.1"
|
|
45
46
|
},
|
|
46
47
|
"packageManager": "yarn@4.12.0",
|
|
47
48
|
"engines": {
|