@t4h.framework/oidc 0.6.10 → 0.6.12
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 +81 -256
- package/CHANGELOG.md +31 -0
- package/README.md +24 -52
- package/dist/oidc-authorize.d.ts.map +1 -1
- package/dist/oidc-authorize.js +26 -8
- package/dist/oidc-authorize.js.map +1 -1
- package/package.json +4 -2
|
@@ -1,263 +1,88 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: framework-oidc
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
5
|
-
@t4h.framework/oidc: OIDCAuthorization
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
Protects a T4H Framework App or Workflow with OpenID Connect using
|
|
5
|
+
@t4h.framework/oidc: `OIDCAuthorization` with `discoveryUrl`, `clientId`,
|
|
6
|
+
`clientSecret` and a `token` placeholder such as
|
|
7
|
+
'<<headers.authorization>>', checked by RFC 7662 token introspection with
|
|
8
|
+
client_secret_basic. Use when an app or workflow must run only for callers
|
|
9
|
+
with an active access token from an OpenID Provider (Keycloak, Auth0,
|
|
10
|
+
Zitadel), when setting the `authorization` option, or when testing
|
|
11
|
+
`@t4h.framework/oidc/authorize`.
|
|
10
12
|
---
|
|
11
13
|
|
|
12
14
|
# @t4h.framework/oidc
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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.
|
|
16
|
+
`OIDCAuthorization` gates the incoming call of an `App` or a `Workflow`. Before
|
|
17
|
+
the run starts, the check reads the discovery document, sends the caller's
|
|
18
|
+
token to the provider's `introspection_endpoint`, and allows the run only when
|
|
19
|
+
the response has `"active": true`.
|
|
20
|
+
|
|
21
|
+
This package does not authenticate outgoing calls. To call an API with an
|
|
22
|
+
OAuth2 token, use the **framework-http** skill. To check a JWT locally against
|
|
23
|
+
the provider's keys, with no introspection call, use the **framework-jwks**
|
|
24
|
+
skill.
|
|
25
|
+
|
|
26
|
+
## Protect an app or a workflow
|
|
27
|
+
|
|
28
|
+
1. Declare the values in the `EnvDesign` in `src/constants/environments.ts`.
|
|
29
|
+
Use `type: String` for the secret: `clientSecret` is a `string`, not a
|
|
30
|
+
`Secret`.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
export const environments = EnvDesign({
|
|
34
|
+
OIDC_DISCOVERY_URL: { type: String },
|
|
35
|
+
OIDC_CLIENT: { type: String },
|
|
36
|
+
OIDC_SECRET: { type: String },
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
2. Set `authorization` on the `App` (all its workflows) or on one `Workflow`:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { OIDCAuthorization } from '@t4h.framework/oidc'
|
|
44
|
+
|
|
45
|
+
const authorization = new OIDCAuthorization({
|
|
46
|
+
discoveryUrl: environments.OIDC_DISCOVERY_URL,
|
|
47
|
+
clientId: environments.OIDC_CLIENT,
|
|
48
|
+
clientSecret: environments.OIDC_SECRET,
|
|
49
|
+
token: '<<headers.authorization>>',
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
export default new App({
|
|
53
|
+
id: 'my-app',
|
|
54
|
+
workflows: [cashback],
|
|
55
|
+
authorization,
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Traps
|
|
60
|
+
|
|
61
|
+
- `token` is a placeholder, not a value. The runtime replaces
|
|
62
|
+
`<<headers.authorization>>` with the `Authorization` header of the incoming
|
|
63
|
+
request before the check runs.
|
|
64
|
+
- The token must be `Bearer <token>` (any letter case, no spaces in the
|
|
65
|
+
token). Any other form is denied without a call to the provider.
|
|
66
|
+
- `@t4h.framework/core/no-literal-secret-in-authorization` rejects a literal
|
|
67
|
+
`clientSecret`. The package reads no env by itself: pass each value from the
|
|
68
|
+
`EnvDesign`.
|
|
69
|
+
- `discoveryUrl` is the well-known document
|
|
70
|
+
(`.../.well-known/openid-configuration`), not the introspection endpoint.
|
|
71
|
+
- The provider must let the client call introspection with
|
|
72
|
+
`client_secret_basic`.
|
|
73
|
+
- `false` means "denied". An HTTP error from the provider, a missing
|
|
74
|
+
`introspection_endpoint` or a network failure throws instead.
|
|
75
|
+
|
|
76
|
+
## Test
|
|
77
|
+
|
|
78
|
+
- `OIDCAuthorization`: compare `toManifest()` with
|
|
79
|
+
`{ pathToModule: '@t4h.framework/oidc/authorize', context: { ... } }`.
|
|
80
|
+
- The check: `import oidc from '@t4h.framework/oidc/authorize'`, stub the
|
|
81
|
+
global `fetch` with `vi.stubGlobal`, and resolve it two times in order:
|
|
82
|
+
the discovery document, then the introspection response.
|
|
83
|
+
|
|
84
|
+
## Reference
|
|
85
|
+
|
|
86
|
+
- Guide, errors and testing: https://tech4humans-brasil.github.io/framework/oidc/
|
|
87
|
+
- Types: `node_modules/@t4h.framework/oidc/dist/oidc.d.ts` and
|
|
88
|
+
`node_modules/@t4h.framework/oidc/dist/oidc-authorize.d.ts`
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @t4h.framework/oidc
|
|
2
2
|
|
|
3
|
+
## 0.6.12
|
|
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 oidc docs now describe the errors of the check and how to test it, and no longer say that it uses the client credentials grant. The README is short and the skill keeps only the traps and links to the site.
|
|
8
|
+
|
|
9
|
+
## 0.6.11
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#241](https://github.com/tech4humans-brasil/framework/pull/241) [`f6b8454`](https://github.com/tech4humans-brasil/framework/commit/f6b845484d22e7c8e7240fe2e113f1cdea7c8ce3) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - Adds seven rules to `@t4h.framework/core/eslint` and turns them on in `configs.recommended`:
|
|
14
|
+
- `no-io-in-workflow` reports `fetch` and calls to network, file, database and mail modules in a workflow and in the `constructor`, `toInput` and `toOutput` of an activity. It also reports `zlib` and `stream` calls there, because they work on the content in memory on each replay.
|
|
15
|
+
- `no-binary-subclass` reports a class that extends `Binary`.
|
|
16
|
+
- `no-framework-activity-in-app` reports `new` and `extends` on an activity of a framework package other than the core.
|
|
17
|
+
- `no-testing-import-in-source` reports an import of a `@t4h.framework/<package>/testing` entry out of a test file.
|
|
18
|
+
- `require-workflow-id` reports a `Workflow` with no `id`.
|
|
19
|
+
- `no-secret-to-string` reports a `Secret` that becomes a string.
|
|
20
|
+
- `no-literal-secret-in-authorization` reports a string literal in the `secret` or `clientSecret` field of an `Authorization`.
|
|
21
|
+
|
|
22
|
+
The package skills now name these rules in place of the text that the rules enforce. The docs and skills give each `Workflow` an `id`, read the HMAC secret from `EnvDesign`, and do not document a custom HTTP activity.
|
|
23
|
+
|
|
24
|
+
- [#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.
|
|
25
|
+
|
|
26
|
+
- [#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.
|
|
27
|
+
|
|
28
|
+
- [#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.
|
|
29
|
+
- harness: the fix-issues prompt lists each sensor as `- checkTypes: PASS`. Before, it printed `- 0: [object Object]`.
|
|
30
|
+
- hmac: an algorithm name such as `constructor` throws `Unsupported algorithm`.
|
|
31
|
+
- oidc: a discovery response with no `introspection_endpoint`, or a null introspection body, throws a clear error.
|
|
32
|
+
- `HttpRequestActivity.toOutput` takes `HttpRequestActivityOutput` in place of `any`.
|
|
33
|
+
|
|
3
34
|
## 0.6.10
|
|
4
35
|
|
|
5
36
|
## 0.6.9
|
package/README.md
CHANGED
|
@@ -1,66 +1,38 @@
|
|
|
1
1
|
# @t4h.framework/oidc
|
|
2
2
|
|
|
3
|
-
OpenID Connect
|
|
3
|
+
OpenID Connect authorization for T4H Framework apps and workflows. It lets a
|
|
4
|
+
run start only when the OpenID Provider reports the caller's token as active,
|
|
5
|
+
through token introspection ([RFC 7662](https://www.rfc-editor.org/rfc/rfc7662)).
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Install
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Exports
|
|
10
|
-
|
|
11
|
-
| Entry | Purpose |
|
|
12
|
-
|--------|---------|
|
|
13
|
-
| `@t4h.framework/oidc` | `OIDCAuthorization`, `OIDCAuthorizationContext`, and related types |
|
|
14
|
-
| `@t4h.framework/oidc/authorize` | Default async handler invoked by the runtime with the manifest context |
|
|
15
|
-
|
|
16
|
-
## `OIDCAuthorization`
|
|
17
|
-
|
|
18
|
-
Subclass of [`Authorization`](https://github.com/tech4humans-brasil/framework/tree/main/packages/core) from core. `toManifest()` points the runtime at `@t4h.framework/oidc/authorize` and passes an `OIDCAuthorizationContext`.
|
|
19
|
-
|
|
20
|
-
You construct it with a context object; your app or hosting layer is responsible for supplying the credential string (`token`) when the workflow runs.
|
|
21
|
-
|
|
22
|
-
## Context: `OIDCAuthorizationContext`
|
|
23
|
-
|
|
24
|
-
| Field | Required | Description |
|
|
25
|
-
|-------|----------|-------------|
|
|
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
|
-
| `token` | Yes | Credential to verify. Must use a `Bearer ` prefix (see below). |
|
|
30
|
-
|
|
31
|
-
## Runtime handler (`oidc/authorize`)
|
|
32
|
-
|
|
33
|
-
The default export is an async function:
|
|
34
|
-
|
|
35
|
-
```ts
|
|
36
|
-
(context: OIDCAuthorizationContext) => Promise<boolean>
|
|
9
|
+
```bash
|
|
10
|
+
npm install @t4h.framework/oidc
|
|
37
11
|
```
|
|
38
12
|
|
|
39
|
-
|
|
40
|
-
|
|
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.
|
|
44
|
-
|
|
45
|
-
Result:
|
|
13
|
+
Peer dependency: `@t4h.framework/core`.
|
|
46
14
|
|
|
47
|
-
|
|
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.
|
|
50
|
-
|
|
51
|
-
There is no built-in mapping from environment variables; all configuration comes from the manifest context.
|
|
52
|
-
|
|
53
|
-
## Example
|
|
15
|
+
## Usage
|
|
54
16
|
|
|
55
17
|
```typescript
|
|
18
|
+
import { App } from '@t4h.framework/core'
|
|
56
19
|
import { OIDCAuthorization } from '@t4h.framework/oidc'
|
|
57
20
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
21
|
+
import { environments } from './constants/environments.js'
|
|
22
|
+
|
|
23
|
+
export default new App({
|
|
24
|
+
id: 'my-app',
|
|
25
|
+
workflows: [cashback],
|
|
26
|
+
authorization: new OIDCAuthorization({
|
|
27
|
+
discoveryUrl: environments.OIDC_DISCOVERY_URL,
|
|
28
|
+
clientId: environments.OIDC_CLIENT,
|
|
29
|
+
clientSecret: environments.OIDC_SECRET,
|
|
30
|
+
token: '<<headers.authorization>>',
|
|
31
|
+
}),
|
|
63
32
|
})
|
|
64
33
|
```
|
|
65
34
|
|
|
66
|
-
|
|
35
|
+
## Documentation
|
|
36
|
+
|
|
37
|
+
Configuration, the validation steps, errors and testing:
|
|
38
|
+
https://tech4humans-brasil.github.io/framework/oidc/
|
|
@@ -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;AAyCzD,wBAA8B,IAAI,CAChC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,OAAO,CAAC,CA8ClB"}
|
package/dist/oidc-authorize.js
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
const BEARER_CREDENTIAL = /^Bearer +(?<token>[^ ]+) *$/i;
|
|
2
|
+
const CREDENTIAL_PREFIX = 'value=';
|
|
2
3
|
function getAccessToken(token) {
|
|
3
4
|
const match = BEARER_CREDENTIAL.exec(token);
|
|
4
5
|
return match?.groups?.token ?? null;
|
|
5
6
|
}
|
|
6
7
|
function encodeCredential(value) {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
return new URLSearchParams({ value })
|
|
9
|
+
.toString()
|
|
10
|
+
.slice(CREDENTIAL_PREFIX.length);
|
|
11
|
+
}
|
|
12
|
+
function getIntrospectionEndpoint(discovery) {
|
|
13
|
+
if (typeof discovery === 'object' &&
|
|
14
|
+
discovery !== null &&
|
|
15
|
+
'introspection_endpoint' in discovery &&
|
|
16
|
+
typeof discovery.introspection_endpoint === 'string')
|
|
17
|
+
return discovery.introspection_endpoint;
|
|
18
|
+
throw new Error('Discovery response has no introspection_endpoint');
|
|
19
|
+
}
|
|
20
|
+
function isActive(introspection) {
|
|
21
|
+
if (introspection === null)
|
|
22
|
+
throw new Error('Introspection response body is null');
|
|
23
|
+
return (typeof introspection === 'object' &&
|
|
24
|
+
'active' in introspection &&
|
|
25
|
+
introspection.active === true);
|
|
9
26
|
}
|
|
10
27
|
export default async function oidc(context) {
|
|
11
28
|
const accessToken = getAccessToken(context.token);
|
|
@@ -18,10 +35,11 @@ export default async function oidc(context) {
|
|
|
18
35
|
},
|
|
19
36
|
});
|
|
20
37
|
if (!discoveryResponse.ok)
|
|
21
|
-
throw new Error(`Discovery request failed: ${discoveryResponse.status} ${discoveryResponse.statusText}`);
|
|
22
|
-
const discovery =
|
|
38
|
+
throw new Error(`Discovery request failed: ${String(discoveryResponse.status)} ${discoveryResponse.statusText}`);
|
|
39
|
+
const discovery = await discoveryResponse.json();
|
|
40
|
+
const introspectionEndpoint = getIntrospectionEndpoint(discovery);
|
|
23
41
|
const credentials = Buffer.from(`${encodeCredential(context.clientId)}:${encodeCredential(context.clientSecret)}`).toString('base64');
|
|
24
|
-
const introspectionResponse = await fetch(
|
|
42
|
+
const introspectionResponse = await fetch(introspectionEndpoint, {
|
|
25
43
|
body: new URLSearchParams({
|
|
26
44
|
token: accessToken,
|
|
27
45
|
token_type_hint: 'access_token',
|
|
@@ -34,8 +52,8 @@ export default async function oidc(context) {
|
|
|
34
52
|
},
|
|
35
53
|
});
|
|
36
54
|
if (!introspectionResponse.ok)
|
|
37
|
-
throw new Error(`Introspection request failed: ${introspectionResponse.status} ${introspectionResponse.statusText}`);
|
|
38
|
-
const introspection =
|
|
39
|
-
return introspection
|
|
55
|
+
throw new Error(`Introspection request failed: ${String(introspectionResponse.status)} ${introspectionResponse.statusText}`);
|
|
56
|
+
const introspection = await introspectionResponse.json();
|
|
57
|
+
return isActive(introspection);
|
|
40
58
|
}
|
|
41
59
|
//# 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":"AAEA,MAAM,iBAAiB,GAAG,8BAA8B,CAAA;AAExD,MAAM,iBAAiB,GAAG,QAAQ,CAAA;AAElC,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,OAAO,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC;SAClC,QAAQ,EAAE;SACV,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAA;AACpC,CAAC;AAED,SAAS,wBAAwB,CAAC,SAAkB;IAClD,IACE,OAAO,SAAS,KAAK,QAAQ;QAC7B,SAAS,KAAK,IAAI;QAClB,wBAAwB,IAAI,SAAS;QACrC,OAAO,SAAS,CAAC,sBAAsB,KAAK,QAAQ;QAEpD,OAAO,SAAS,CAAC,sBAAsB,CAAA;IAEzC,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAA;AACrE,CAAC;AAED,SAAS,QAAQ,CAAC,aAAsB;IACtC,IAAI,aAAa,KAAK,IAAI;QACxB,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IAExD,OAAO,CACL,OAAO,aAAa,KAAK,QAAQ;QACjC,QAAQ,IAAI,aAAa;QACzB,aAAa,CAAC,MAAM,KAAK,IAAI,CAC9B,CAAA;AACH,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,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,iBAAiB,CAAC,UAAU,EAAE,CAChG,CAAA;IAEH,MAAM,SAAS,GAAY,MAAM,iBAAiB,CAAC,IAAI,EAAE,CAAA;IAEzD,MAAM,qBAAqB,GAAG,wBAAwB,CAAC,SAAS,CAAC,CAAA;IAEjE,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,qBAAqB,EAAE;QAC/D,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,MAAM,CAAC,qBAAqB,CAAC,MAAM,CAAC,IAAI,qBAAqB,CAAC,UAAU,EAAE,CAC5G,CAAA;IAEH,MAAM,aAAa,GAAY,MAAM,qBAAqB,CAAC,IAAI,EAAE,CAAA;IAEjE,OAAO,QAAQ,CAAC,aAAa,CAAC,CAAA;AAChC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t4h.framework/oidc",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.12",
|
|
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",
|
|
@@ -32,12 +32,14 @@
|
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsc --project tsconfig.build.json",
|
|
34
34
|
"check-types": "tsc --noEmit",
|
|
35
|
+
"check-package": "publint --strict && attw --pack . --profile esm-only",
|
|
36
|
+
"lint": "eslint . --max-warnings 0",
|
|
35
37
|
"prepublishOnly": "yarn build",
|
|
36
38
|
"test": "vitest run",
|
|
37
39
|
"test:watch": "vitest"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
|
-
"@t4h.framework/core": "^0.
|
|
42
|
+
"@t4h.framework/core": "^0.21.0",
|
|
41
43
|
"typescript": "^5.9.3",
|
|
42
44
|
"vitest": "^4.0.18"
|
|
43
45
|
},
|