@yielded/oauth 0.1.0-beta.30 → 0.1.0-beta.31
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/README.md +26 -53
- package/dist/OAuth.d.mts +4 -2
- package/dist/OAuth.mjs +21 -10
- package/dist/OAuth.mjs.map +1 -1
- package/dist/Oidc.d.mts +15 -5
- package/dist/Oidc.mjs +38 -13
- package/dist/Oidc.mjs.map +1 -1
- package/package.json +1 -1
- package/src/OAuth.ts +16 -7
- package/src/Oidc.ts +59 -13
package/README.md
CHANGED
|
@@ -1,60 +1,33 @@
|
|
|
1
1
|
# @yielded/oauth
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Effect OAuth authorization-code and refresh flows, PKCE S256, and signed OpenID
|
|
4
|
+
Connect. Runtime dependencies are Effect and `@yielded/jose`; applications own
|
|
5
|
+
registered callbacks, provider configuration, permissions, identity, and grant storage.
|
|
5
6
|
|
|
6
7
|
Use `OAuth.make` for an installed client and `Oidc.makeVerifier` for its OIDC
|
|
7
8
|
verifier. Both require `HttpClient` and an owning `Scope`; keep that scope open
|
|
8
|
-
for their lifetime. `Pkce` requires Effect `Crypto`; verification requires
|
|
9
|
-
`Signature` from `@yielded/crypto`
|
|
10
|
-
|
|
11
|
-
issuer's OIDC discovery endpoint.
|
|
12
|
-
|
|
13
|
-
Closing either factory's scope cancels and joins its active operations; pending
|
|
14
|
-
results and subsequent calls fail with `OAuthUnavailable`. Caller interruption
|
|
15
|
-
cancels and joins that operation while leaving the client or verifier usable.
|
|
9
|
+
for their lifetime. `Pkce` requires Effect `Crypto`; verification also requires
|
|
10
|
+
`Signature` from `@yielded/crypto`. `Oidc.discover` retrieves metadata from the
|
|
11
|
+
exact trusted issuer. Closing a factory's scope cancels its work and prevents reuse.
|
|
16
12
|
|
|
17
13
|
`client.codeGrant` and `client.refreshGrant` return private `TokenReceipt` values.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
committed request. Implementation defects, including those in injected services,
|
|
39
|
-
remain in Effect's defect channel.
|
|
40
|
-
|
|
41
|
-
Provider endpoints require HTTPS and exact response URLs. Redirects, cookies and
|
|
42
|
-
HTTP tracing are disabled for Fetch; custom HttpClients must preserve cancellation
|
|
43
|
-
and have no retry, redirect or cookie middleware. Installed profile endpoints and
|
|
44
|
-
headers are validated before credentials are sent. Responses are bounded to 1 MiB
|
|
45
|
-
by default (configurable downward). JSON is limited to 64 object/array levels and
|
|
46
|
-
65,536 values, including the root in both limits; excess depth or size fails with
|
|
47
|
-
`OAuthUnavailable`. Installed metadata and returned JSON snapshots are deeply
|
|
48
|
-
frozen. Requests to token/revocation endpoints are bounded to 128 KiB and
|
|
49
|
-
authorization URLs to 16 KiB. `timeoutMs` covers each request
|
|
50
|
-
through body consumption. Applications bound their complete multi-step workflow.
|
|
51
|
-
The current Fetch reference is captured when constructing the client/verifier.
|
|
52
|
-
|
|
53
|
-
Each verifier owns one scoped JOSE JWKS cache: at most 64 keys and 64 waiters, a
|
|
54
|
-
10-minute cache lifetime and a 30-second refresh cooldown. There is no global
|
|
55
|
-
issuer cache, background polling, stale-on-error fallback, or token-supplied key
|
|
56
|
-
endpoint. The supported profile requires advertised code, S256 and RS256 support;
|
|
57
|
-
other OAuth grants and ID-token signature algorithms are outside this profile.
|
|
58
|
-
|
|
59
|
-
See [third-party notices](THIRD_PARTY_NOTICES.md) for pinned upstream contract
|
|
60
|
-
sources and licenses.
|
|
14
|
+
Check the provider-specific receipt, then call `OAuth.tokens`. A receipt alone
|
|
15
|
+
does not establish a successful grant or authenticated identity. Tokens stay
|
|
16
|
+
redacted. For initial OIDC sign-in, require an ID token and call `verifier.verify`
|
|
17
|
+
with the captured verification time, nonce, access token, and authorization code.
|
|
18
|
+
On refresh, verify any returned ID token against the previous identity; accepting
|
|
19
|
+
an omitted ID token is application policy.
|
|
20
|
+
|
|
21
|
+
Each exchange, refresh, or revocation makes one request. An uncertain outcome
|
|
22
|
+
requires application recovery, not an automatic retry. Provider endpoints require
|
|
23
|
+
HTTPS. Custom HttpClients must preserve cancellation and disable retries,
|
|
24
|
+
redirects, and ambient cookies. Bound the complete workflow as well as individual
|
|
25
|
+
requests; keep token receipts out of logs and telemetry.
|
|
26
|
+
|
|
27
|
+
Each verifier owns a scoped JWKS cache. It never takes a key endpoint from a token
|
|
28
|
+
or serves expired keys after refresh failure. Configure the issuer's advertised
|
|
29
|
+
code flow and signing algorithms. Keep S256 PKCE enabled unless the issuer cannot
|
|
30
|
+
support it. Other OAuth grants are outside this package's profile.
|
|
31
|
+
|
|
32
|
+
See the [public API comments](src/OAuth.ts), [OIDC options](src/Oidc.ts), and
|
|
33
|
+
[third-party notices](THIRD_PARTY_NOTICES.md).
|
package/dist/OAuth.d.mts
CHANGED
|
@@ -69,14 +69,16 @@ export interface AuthorizationInput extends Parameters {
|
|
|
69
69
|
readonly redirectUri: string;
|
|
70
70
|
readonly scopes: ReadonlyArray<string>;
|
|
71
71
|
readonly state: Redacted.Redacted<string>;
|
|
72
|
-
readonly codeChallenge
|
|
72
|
+
readonly codeChallenge?: string;
|
|
73
73
|
readonly nonce?: Redacted.Redacted<string>;
|
|
74
74
|
readonly maxAgeSeconds?: number;
|
|
75
|
+
readonly prompt?: "none" | "login" | "consent" | "select_account";
|
|
76
|
+
readonly loginHint?: string;
|
|
75
77
|
}
|
|
76
78
|
export interface CodeGrantInput extends Parameters {
|
|
77
79
|
readonly code: Redacted.Redacted<string>;
|
|
78
80
|
readonly redirectUri: string;
|
|
79
|
-
readonly pkceVerifier
|
|
81
|
+
readonly pkceVerifier?: Redacted.Redacted<string>;
|
|
80
82
|
}
|
|
81
83
|
export interface RefreshGrantInput extends Parameters {
|
|
82
84
|
readonly refreshToken: Redacted.Redacted<string>;
|
package/dist/OAuth.mjs
CHANGED
|
@@ -70,15 +70,22 @@ const Authorization = Schema.Struct({
|
|
|
70
70
|
redirectUri: Callback,
|
|
71
71
|
scopes: Scopes,
|
|
72
72
|
state: secret(256),
|
|
73
|
-
codeChallenge: Challenge,
|
|
73
|
+
codeChallenge: Schema.optionalKey(Challenge),
|
|
74
74
|
nonce: Schema.optionalKey(secret(256)),
|
|
75
|
-
maxAgeSeconds: Schema.optionalKey(integer(0, 86400))
|
|
75
|
+
maxAgeSeconds: Schema.optionalKey(integer(0, 86400)),
|
|
76
|
+
prompt: Schema.optionalKey(Schema.Literals([
|
|
77
|
+
"none",
|
|
78
|
+
"login",
|
|
79
|
+
"consent",
|
|
80
|
+
"select_account"
|
|
81
|
+
])),
|
|
82
|
+
loginHint: Schema.optionalKey(text(1024))
|
|
76
83
|
});
|
|
77
84
|
const CodeGrant = Schema.Struct({
|
|
78
85
|
...additional,
|
|
79
86
|
code: secret(16384),
|
|
80
87
|
redirectUri: Callback,
|
|
81
|
-
pkceVerifier: Verifier
|
|
88
|
+
pkceVerifier: Schema.optionalKey(Verifier)
|
|
82
89
|
});
|
|
83
90
|
const RefreshGrant = Schema.Struct({
|
|
84
91
|
...additional,
|
|
@@ -183,8 +190,8 @@ const make = Effect.fnUntraced(function* (input) {
|
|
|
183
190
|
}));
|
|
184
191
|
const send = Effect.fnUntraced(function* (body) {
|
|
185
192
|
yield* available;
|
|
186
|
-
const request$
|
|
187
|
-
return yield* json(yield* request(http, request$
|
|
193
|
+
const request$2 = yield* authenticated(new URL(metadata.token_endpoint).href, options.clientId, authentication, body);
|
|
194
|
+
return yield* json(yield* request(http, request$2, options));
|
|
188
195
|
});
|
|
189
196
|
return {
|
|
190
197
|
metadata,
|
|
@@ -199,10 +206,14 @@ const make = Effect.fnUntraced(function* (input) {
|
|
|
199
206
|
body.set("response_mode", "query");
|
|
200
207
|
body.set("scope", value.scopes.join(" "));
|
|
201
208
|
body.set("state", yield* reveal(value.state));
|
|
202
|
-
|
|
203
|
-
|
|
209
|
+
if (value.codeChallenge !== void 0) {
|
|
210
|
+
body.set("code_challenge", value.codeChallenge);
|
|
211
|
+
body.set("code_challenge_method", "S256");
|
|
212
|
+
}
|
|
204
213
|
if (value.nonce !== void 0) body.set("nonce", yield* reveal(value.nonce));
|
|
205
214
|
if (value.maxAgeSeconds !== void 0) body.set("max_age", String(value.maxAgeSeconds));
|
|
215
|
+
if (value.prompt !== void 0) body.set("prompt", value.prompt);
|
|
216
|
+
if (value.loginHint !== void 0) body.set("login_hint", value.loginHint);
|
|
206
217
|
for (const [key, item] of body) url.searchParams.append(key, item);
|
|
207
218
|
if (url.href.length > 16384) return yield* ConfigurationError.make({ reason: "parameters" });
|
|
208
219
|
return Redacted.make(url.href);
|
|
@@ -213,7 +224,7 @@ const make = Effect.fnUntraced(function* (input) {
|
|
|
213
224
|
const body = parameters(value);
|
|
214
225
|
body.set("grant_type", "authorization_code");
|
|
215
226
|
body.set("code", yield* reveal(value.code));
|
|
216
|
-
body.set("code_verifier", yield* reveal(value.pkceVerifier));
|
|
227
|
+
if (value.pkceVerifier !== void 0) body.set("code_verifier", yield* reveal(value.pkceVerifier));
|
|
217
228
|
body.set("redirect_uri", value.redirectUri);
|
|
218
229
|
return yield* send(body);
|
|
219
230
|
}, use),
|
|
@@ -230,8 +241,8 @@ const make = Effect.fnUntraced(function* (input) {
|
|
|
230
241
|
yield* available;
|
|
231
242
|
if (profile === void 0) return yield* ConfigurationError.make({ reason: "endpoint" });
|
|
232
243
|
const token = yield* reveal(yield* decode(Bearer, input));
|
|
233
|
-
const request$
|
|
234
|
-
const response = yield* request(http, request$
|
|
244
|
+
const request$1 = HttpClientRequest.get(new URL(profile.url).href).pipe(HttpClientRequest.acceptJson, HttpClientRequest.setHeaders(profile.headers ?? {}), HttpClientRequest.setHeader("authorization", `Bearer ${token}`));
|
|
245
|
+
const response = yield* request(http, request$1, options);
|
|
235
246
|
if (response.status !== 200) return yield* Unavailable.make({});
|
|
236
247
|
return yield* reveal((yield* json(response)).body);
|
|
237
248
|
}, use),
|
package/dist/OAuth.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuth.mjs","names":["V.JsonObject","V.Issuer","V.ProtocolEndpoint","V.Endpoint","V.text","V.secret","V.integer","V.Headers","V.Parameters","V.Resources","V.Callback","V.Scopes","V.Challenge","V.Verifier","V.decode","Transport.isJson","V.reveal","V.configuration","V.freeze","Ownership.make","Transport.capture","request","Transport.json","Transport.request"],"sources":["../src/OAuth.ts"],"sourcesContent":["import { Effect, Redacted, Schema, type Scope } from \"effect\";\nimport { Base64 } from \"effect/encoding\";\nimport type { HttpClient } from \"effect/http\";\nimport { HttpClientRequest } from \"effect/http\";\n\nimport { ConfigurationError, Rejected, Unavailable } from \"./Errors\";\nimport * as Ownership from \"./internal/ownership\";\nimport * as Transport from \"./internal/transport\";\nimport * as V from \"./internal/validation\";\n\nexport const JsonObject = V.JsonObject;\nexport type JsonObject = typeof JsonObject.Type;\n\nexport const Metadata = Schema.Struct({\n issuer: V.Issuer,\n authorization_endpoint: V.ProtocolEndpoint,\n token_endpoint: V.ProtocolEndpoint,\n jwks_uri: Schema.optionalKey(V.Endpoint),\n userinfo_endpoint: Schema.optionalKey(V.Endpoint),\n revocation_endpoint: Schema.optionalKey(V.ProtocolEndpoint),\n code_challenge_methods_supported: Schema.optionalKey(\n Schema.Array(V.text(64)).check(Schema.isMaxLength(64)),\n ),\n response_types_supported: Schema.optionalKey(\n Schema.Array(V.text(64)).check(Schema.isMaxLength(64)),\n ),\n grant_types_supported: Schema.optionalKey(\n Schema.Array(V.text(128)).check(Schema.isMaxLength(64)),\n ),\n id_token_signing_alg_values_supported: Schema.optionalKey(\n Schema.Array(V.text(64)).check(Schema.isMaxLength(64)),\n ),\n token_endpoint_auth_methods_supported: Schema.optionalKey(\n Schema.Array(V.text(64)).check(Schema.isMaxLength(64)),\n ),\n revocation_endpoint_auth_methods_supported: Schema.optionalKey(\n Schema.Array(V.text(64)).check(Schema.isMaxLength(64)),\n ),\n authorization_response_iss_parameter_supported: Schema.optionalKey(Schema.Boolean),\n});\n\nexport type Metadata = typeof Metadata.Type;\n\nexport const Authentication = Schema.Union([\n Schema.Struct({ method: Schema.Literal(\"client_secret_basic\"), secret: V.secret(4096) }),\n Schema.Struct({ method: Schema.Literal(\"client_secret_post\"), secret: V.secret(4096) }),\n Schema.Struct({ method: Schema.Literal(\"none\"), publicClient: Schema.Literal(true) }),\n]);\n\nexport type Authentication = typeof Authentication.Type;\n\n/** Uninterpreted private receipt. Inspect provider extensions before calling tokens. */\nexport const TokenReceipt = Schema.Struct({\n status: V.integer(100, 599),\n contentType: Schema.NullOr(Schema.String.check(Schema.isMaxLength(512))),\n body: Schema.Redacted(JsonObject, { disallowJsonEncode: true }),\n});\n\nexport type TokenReceipt = typeof TokenReceipt.Type;\n\nexport const TokenSet = Schema.Struct({\n tokenType: Schema.Literal(\"bearer\"),\n accessToken: V.secret(16384),\n refreshToken: Schema.optionalKey(V.secret(16384)),\n idToken: Schema.optionalKey(V.secret(65536)),\n expiresIn: Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))),\n scope: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(16447))),\n});\n\nexport type TokenSet = typeof TokenSet.Type;\n\nexport interface RequestOptions {\n readonly timeoutMs: number;\n readonly maxResponseBytes?: number;\n}\n\nexport interface ClientOptions extends RequestOptions {\n readonly metadata: Metadata;\n readonly clientId: string;\n readonly authentication: Authentication;\n readonly profile?: { readonly url: string; readonly headers?: Readonly<Record<string, string>> };\n readonly revocationAuthentication?: Authentication;\n}\n\nexport interface Parameters {\n readonly parameters?: Readonly<Record<string, string>>;\n readonly resources?: ReadonlyArray<string>;\n}\n\nexport interface AuthorizationInput extends Parameters {\n readonly redirectUri: string;\n readonly scopes: ReadonlyArray<string>;\n readonly state: Redacted.Redacted<string>;\n readonly codeChallenge: string;\n readonly nonce?: Redacted.Redacted<string>;\n readonly maxAgeSeconds?: number;\n}\n\nexport interface CodeGrantInput extends Parameters {\n readonly code: Redacted.Redacted<string>;\n readonly redirectUri: string;\n readonly pkceVerifier: Redacted.Redacted<string>;\n}\n\nexport interface RefreshGrantInput extends Parameters {\n readonly refreshToken: Redacted.Redacted<string>;\n readonly scopes?: ReadonlyArray<string>;\n}\n\nexport interface RevocationInput {\n readonly token: Redacted.Redacted<string>;\n readonly tokenTypeHint: \"access_token\" | \"refresh_token\";\n}\n\nexport interface Client {\n readonly metadata: Metadata;\n readonly authorizationUrl: (\n input: AuthorizationInput,\n ) => Effect.Effect<Redacted.Redacted<string>, ConfigurationError | Unavailable>;\n readonly codeGrant: (\n input: CodeGrantInput,\n ) => Effect.Effect<TokenReceipt, ConfigurationError | Unavailable>;\n readonly refreshGrant: (\n input: RefreshGrantInput,\n ) => Effect.Effect<TokenReceipt, ConfigurationError | Unavailable>;\n readonly fetchProfile: (\n accessToken: Redacted.Redacted<string>,\n ) => Effect.Effect<JsonObject, ConfigurationError | Unavailable>;\n readonly revoke: (\n input: RevocationInput,\n ) => Effect.Effect<void, ConfigurationError | Unavailable>;\n}\n\nconst ClientOptionsSchema = Schema.Struct({\n metadata: Metadata,\n clientId: V.text(1024),\n authentication: Authentication,\n ...V.RequestOptions.fields,\n profile: Schema.optionalKey(\n Schema.Struct({ url: V.Endpoint, headers: Schema.optionalKey(V.Headers) }),\n ),\n revocationAuthentication: Schema.optionalKey(Authentication),\n});\n\nconst additional = {\n parameters: Schema.optionalKey(V.Parameters),\n resources: Schema.optionalKey(V.Resources),\n};\n\nconst Authorization = Schema.Struct({\n ...additional,\n redirectUri: V.Callback,\n scopes: V.Scopes,\n state: V.secret(256),\n codeChallenge: V.Challenge,\n nonce: Schema.optionalKey(V.secret(256)),\n maxAgeSeconds: Schema.optionalKey(V.integer(0, 86400)),\n});\n\nconst CodeGrant = Schema.Struct({\n ...additional,\n code: V.secret(16384),\n redirectUri: V.Callback,\n pkceVerifier: V.Verifier,\n});\n\nconst RefreshGrant = Schema.Struct({\n ...additional,\n refreshToken: V.secret(16384),\n scopes: Schema.optionalKey(V.Scopes),\n});\n\nconst Revocation = Schema.Struct({\n token: V.secret(16384),\n tokenTypeHint: Schema.Literals([\"access_token\", \"refresh_token\"]),\n});\n\nconst ExpiresIn = Schema.Union([\n Schema.Finite,\n Schema.String.check(Schema.isPattern(/^(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$/u)).pipe(\n Schema.decodeTo(Schema.FiniteFromString),\n ),\n]).check(Schema.isGreaterThanOrEqualTo(0));\n\nconst RawToken = Schema.Struct({\n access_token: V.text(16384),\n token_type: Schema.String.check(Schema.isPattern(/^[Bb][Ee][Aa][Rr][Ee][Rr]$/)),\n refresh_token: Schema.optionalKey(V.text(16384)),\n id_token: Schema.optionalKey(V.text(65536)),\n expires_in: Schema.optionalKey(ExpiresIn),\n scope: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(16447))),\n});\n\nconst Terminal = Schema.Struct({\n error: V.text(128),\n error_description: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(4096))),\n error_uri: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(2048))),\n});\n\nconst successFields = [\n \"access_token\",\n \"refresh_token\",\n \"id_token\",\n \"token_type\",\n \"scope\",\n \"expires_in\",\n \"refresh_expires_in\",\n \"refresh_token_expires_in\",\n];\n\n/** Parse once provider-specific receipt checks have succeeded. expires_in accepts\n * finite nonnegative numbers and whole decimal strings (for example \"60.5\").\n * Whitespace, exponent/prefix syntax and nonfinite values are rejected. Raw\n * receipt extensions/scopes are untouched; no request, retry or claim verification. */\nexport const tokens = Effect.fnUntraced(function* (\n input: TokenReceipt,\n): Effect.fn.Return<TokenSet, Rejected | Unavailable> {\n const receipt = yield* V.decode(TokenReceipt, input);\n\n if (!Transport.isJson(receipt.contentType)) return yield* Unavailable.make({});\n const raw = yield* V.reveal(receipt.body);\n\n if (Object.hasOwn(raw, \"error\")) {\n if (successFields.some((key) => Object.hasOwn(raw, key))) return yield* Unavailable.make({});\n const terminal = yield* V.decode(Terminal, raw);\n\n if (receipt.status === 400 && terminal.error === \"invalid_grant\")\n return yield* Rejected.make({ reason: \"invalid_grant\" });\n\n return yield* Unavailable.make({});\n }\n if (\n receipt.status !== 200 ||\n Object.hasOwn(raw, \"error_description\") ||\n Object.hasOwn(raw, \"error_uri\")\n )\n return yield* Unavailable.make({});\n const value = yield* V.decode(RawToken, raw);\n\n return {\n tokenType: \"bearer\",\n accessToken: Redacted.make(value.access_token),\n ...(value.refresh_token === undefined\n ? {}\n : { refreshToken: Redacted.make(value.refresh_token) }),\n ...(value.id_token === undefined ? {} : { idToken: Redacted.make(value.id_token) }),\n ...(value.expires_in === undefined ? {} : { expiresIn: value.expires_in }),\n ...(value.scope === undefined ? {} : { scope: value.scope }),\n };\n});\n\nconst parameters = (input: Parameters) => {\n const values = new URLSearchParams(input.parameters);\n\n for (const resource of input.resources ?? []) values.append(\"resource\", resource);\n\n return values;\n};\n\n// RFC6749 Appendix B: encode client credentials before joining with the colon.\n// Adapted from oauth4webapi 3.8.8 src/index.ts::formUrlEncode (MIT, Filip Skokan).\n// Exact source commit and license: ../THIRD_PARTY_NOTICES.md.\nconst basicComponent = (value: string) =>\n encodeURIComponent(value).replace(/[-_.!~*'()]|%20/gu, (part) =>\n part === \"%20\" ? \"+\" : `%${part.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n\nconst authenticated = Effect.fnUntraced(function* (\n url: string,\n clientId: string,\n authentication: Authentication,\n body: URLSearchParams,\n) {\n let request = HttpClientRequest.post(url).pipe(HttpClientRequest.acceptJson);\n\n if (authentication.method === \"client_secret_basic\") {\n const secret = yield* V.reveal(authentication.secret);\n\n const credential = yield* Effect.try({\n try: () => Base64.encode(`${basicComponent(clientId)}:${basicComponent(secret)}`),\n catch: () => Unavailable.make({}),\n });\n\n request = HttpClientRequest.setHeader(request, \"authorization\", `Basic ${credential}`);\n } else {\n body.set(\"client_id\", clientId);\n if (authentication.method === \"client_secret_post\")\n body.set(\"client_secret\", yield* V.reveal(authentication.secret));\n }\n const encoded = body.toString();\n\n if (new TextEncoder().encode(encoded).length > 131072)\n return yield* ConfigurationError.make({ reason: \"parameters\" });\n\n return HttpClientRequest.bodyText(\n request,\n encoded,\n \"application/x-www-form-urlencoded;charset=UTF-8\",\n );\n});\n\n/** One installed client, exact endpoints, private credentials and owning Scope.\n * Supply an HttpClient without retry/redirect/cookie middleware. Fetch injection\n * active at construction is captured. Owner closure cancels and joins active\n * operations, then rejects their results as unavailable; caller interruption\n * cancels and joins its operation without closing the client. */\nexport const make = Effect.fnUntraced(function* (\n input: ClientOptions,\n): Effect.fn.Return<Client, ConfigurationError | Unavailable, HttpClient.HttpClient | Scope.Scope> {\n const options = yield* V.configuration(ClientOptionsSchema, input, \"metadata\");\n\n const authentication = yield* detachAuthentication(options.authentication);\n\n const revocationAuthentication =\n options.revocationAuthentication === undefined\n ? authentication\n : yield* detachAuthentication(options.revocationAuthentication);\n\n const metadata = V.freeze(options.metadata);\n\n const supported =\n metadata.token_endpoint_auth_methods_supported ??\n (metadata.jwks_uri === undefined ? undefined : [\"client_secret_basic\"]);\n\n const revokeSupported =\n metadata.revocation_endpoint_auth_methods_supported ??\n (metadata.jwks_uri === undefined ? undefined : [\"client_secret_basic\"]);\n\n if (\n (supported !== undefined && !supported.includes(authentication.method)) ||\n (options.revocationAuthentication !== undefined &&\n metadata.revocation_endpoint !== undefined &&\n revokeSupported !== undefined &&\n !revokeSupported.includes(revocationAuthentication.method))\n )\n return yield* ConfigurationError.make({ reason: \"authentication\" });\n const { available, use } = yield* Ownership.make;\n const http = yield* Transport.capture;\n const profile = options.profile === undefined ? undefined : V.freeze(options.profile);\n\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n if (authentication.method !== \"none\") Redacted.wipeUnsafe(authentication.secret);\n if (revocationAuthentication.method !== \"none\")\n Redacted.wipeUnsafe(revocationAuthentication.secret);\n }),\n );\n\n const send = Effect.fnUntraced(function* (body: URLSearchParams) {\n yield* available;\n\n const request = yield* authenticated(\n new URL(metadata.token_endpoint).href,\n options.clientId,\n authentication,\n body,\n );\n\n return yield* Transport.json(yield* Transport.request(http, request, options));\n });\n\n const authorizationUrl = Effect.fnUntraced(function* (input: AuthorizationInput) {\n yield* available;\n const value = yield* V.configuration(Authorization, input);\n const url = new URL(metadata.authorization_endpoint);\n const body = parameters(value);\n\n body.set(\"client_id\", options.clientId);\n body.set(\"redirect_uri\", value.redirectUri);\n body.set(\"response_type\", \"code\");\n body.set(\"response_mode\", \"query\");\n body.set(\"scope\", value.scopes.join(\" \"));\n body.set(\"state\", yield* V.reveal(value.state));\n body.set(\"code_challenge\", value.codeChallenge);\n body.set(\"code_challenge_method\", \"S256\");\n if (value.nonce !== undefined) body.set(\"nonce\", yield* V.reveal(value.nonce));\n if (value.maxAgeSeconds !== undefined) body.set(\"max_age\", String(value.maxAgeSeconds));\n for (const [key, item] of body) url.searchParams.append(key, item);\n if (url.href.length > 16384) return yield* ConfigurationError.make({ reason: \"parameters\" });\n\n return Redacted.make(url.href);\n }, use);\n\n const codeGrant = Effect.fnUntraced(function* (input: CodeGrantInput) {\n yield* available;\n const value = yield* V.configuration(CodeGrant, input);\n const body = parameters(value);\n\n body.set(\"grant_type\", \"authorization_code\");\n body.set(\"code\", yield* V.reveal(value.code));\n body.set(\"code_verifier\", yield* V.reveal(value.pkceVerifier));\n body.set(\"redirect_uri\", value.redirectUri);\n\n return yield* send(body);\n }, use);\n\n const refreshGrant = Effect.fnUntraced(function* (input: RefreshGrantInput) {\n yield* available;\n const value = yield* V.configuration(RefreshGrant, input);\n const body = parameters(value);\n\n body.set(\"grant_type\", \"refresh_token\");\n body.set(\"refresh_token\", yield* V.reveal(value.refreshToken));\n if (value.scopes !== undefined) body.set(\"scope\", value.scopes.join(\" \"));\n\n return yield* send(body);\n }, use);\n\n const fetchProfile = Effect.fnUntraced(function* (input: Redacted.Redacted<string>) {\n yield* available;\n if (profile === undefined) return yield* ConfigurationError.make({ reason: \"endpoint\" });\n const token = yield* V.reveal(yield* V.decode(Bearer, input));\n\n const request = HttpClientRequest.get(new URL(profile.url).href).pipe(\n HttpClientRequest.acceptJson,\n HttpClientRequest.setHeaders(profile.headers ?? {}),\n HttpClientRequest.setHeader(\"authorization\", `Bearer ${token}`),\n );\n\n const response = yield* Transport.request(http, request, options);\n\n if (response.status !== 200) return yield* Unavailable.make({});\n\n return yield* V.reveal((yield* Transport.json(response)).body);\n }, use);\n\n const revoke = Effect.fnUntraced(function* (input: RevocationInput) {\n yield* available;\n if (metadata.revocation_endpoint === undefined)\n return yield* ConfigurationError.make({ reason: \"endpoint\" });\n if (revokeSupported !== undefined && !revokeSupported.includes(revocationAuthentication.method))\n return yield* ConfigurationError.make({ reason: \"authentication\" });\n const value = yield* V.configuration(Revocation, input);\n\n const request = yield* authenticated(\n new URL(metadata.revocation_endpoint).href,\n options.clientId,\n revocationAuthentication,\n new URLSearchParams({\n token: yield* V.reveal(value.token),\n token_type_hint: value.tokenTypeHint,\n }),\n );\n\n const response = yield* Transport.request(http, request, options);\n\n if (response.status !== 200) return yield* Unavailable.make({});\n }, use);\n\n return { metadata, authorizationUrl, codeGrant, refreshGrant, fetchProfile, revoke };\n});\n\nconst Bearer = Schema.Redacted(V.text(16384).check(Schema.isPattern(/^[A-Za-z0-9._~+/-]+=*$/u)), {\n disallowJsonEncode: true,\n});\n\nconst detachAuthentication = Effect.fnUntraced(function* (\n authentication: Authentication,\n): Effect.fn.Return<Authentication, Unavailable> {\n return authentication.method === \"none\"\n ? { ...authentication }\n : { ...authentication, secret: Redacted.make(yield* V.reveal(authentication.secret)) };\n});\n"],"mappings":";;;;;;;;AAUA,MAAa,aAAaA;AAG1B,MAAa,WAAW,OAAO,OAAO;CACpC,QAAQC;CACR,wBAAwBC;CACxB,gBAAgBA;CAChB,UAAU,OAAO,YAAYC,QAAU;CACvC,mBAAmB,OAAO,YAAYA,QAAU;CAChD,qBAAqB,OAAO,YAAYD,gBAAkB;CAC1D,kCAAkC,OAAO,YACvC,OAAO,MAAME,KAAO,EAAE,CAAC,CAAC,CAAC,MAAM,OAAO,YAAY,EAAE,CAAC,CACvD;CACA,0BAA0B,OAAO,YAC/B,OAAO,MAAMA,KAAO,EAAE,CAAC,CAAC,CAAC,MAAM,OAAO,YAAY,EAAE,CAAC,CACvD;CACA,uBAAuB,OAAO,YAC5B,OAAO,MAAMA,KAAO,GAAG,CAAC,CAAC,CAAC,MAAM,OAAO,YAAY,EAAE,CAAC,CACxD;CACA,uCAAuC,OAAO,YAC5C,OAAO,MAAMA,KAAO,EAAE,CAAC,CAAC,CAAC,MAAM,OAAO,YAAY,EAAE,CAAC,CACvD;CACA,uCAAuC,OAAO,YAC5C,OAAO,MAAMA,KAAO,EAAE,CAAC,CAAC,CAAC,MAAM,OAAO,YAAY,EAAE,CAAC,CACvD;CACA,4CAA4C,OAAO,YACjD,OAAO,MAAMA,KAAO,EAAE,CAAC,CAAC,CAAC,MAAM,OAAO,YAAY,EAAE,CAAC,CACvD;CACA,gDAAgD,OAAO,YAAY,OAAO,OAAO;AACnF,CAAC;AAID,MAAa,iBAAiB,OAAO,MAAM;CACzC,OAAO,OAAO;EAAE,QAAQ,OAAO,QAAQ,qBAAqB;EAAG,QAAQC,OAAS,IAAI;CAAE,CAAC;CACvF,OAAO,OAAO;EAAE,QAAQ,OAAO,QAAQ,oBAAoB;EAAG,QAAQA,OAAS,IAAI;CAAE,CAAC;CACtF,OAAO,OAAO;EAAE,QAAQ,OAAO,QAAQ,MAAM;EAAG,cAAc,OAAO,QAAQ,IAAI;CAAE,CAAC;AACtF,CAAC;;AAKD,MAAa,eAAe,OAAO,OAAO;CACxC,QAAQC,QAAU,KAAK,GAAG;CAC1B,aAAa,OAAO,OAAO,OAAO,OAAO,MAAM,OAAO,YAAY,GAAG,CAAC,CAAC;CACvE,MAAM,OAAO,SAAS,YAAY,EAAE,oBAAoB,KAAK,CAAC;AAChE,CAAC;AAID,MAAa,WAAW,OAAO,OAAO;CACpC,WAAW,OAAO,QAAQ,QAAQ;CAClC,aAAaD,OAAS,KAAK;CAC3B,cAAc,OAAO,YAAYA,OAAS,KAAK,CAAC;CAChD,SAAS,OAAO,YAAYA,OAAS,KAAK,CAAC;CAC3C,WAAW,OAAO,YAAY,OAAO,OAAO,MAAM,OAAO,uBAAuB,CAAC,CAAC,CAAC;CACnF,OAAO,OAAO,YAAY,OAAO,OAAO,MAAM,OAAO,YAAY,KAAK,CAAC,CAAC;AAC1E,CAAC;AAkED,MAAM,sBAAsB,OAAO,OAAO;CACxC,UAAU;CACV,UAAUD,KAAO,IAAI;CACrB,gBAAgB;CAChB,GAAA,eAAoB;CACpB,SAAS,OAAO,YACd,OAAO,OAAO;EAAE,KAAKD;EAAY,SAAS,OAAO,YAAYI,OAAS;CAAE,CAAC,CAC3E;CACA,0BAA0B,OAAO,YAAY,cAAc;AAC7D,CAAC;AAED,MAAM,aAAa;CACjB,YAAY,OAAO,YAAYC,UAAY;CAC3C,WAAW,OAAO,YAAYC,SAAW;AAC3C;AAEA,MAAM,gBAAgB,OAAO,OAAO;CAClC,GAAG;CACH,aAAaC;CACb,QAAQC;CACR,OAAON,OAAS,GAAG;CACnB,eAAeO;CACf,OAAO,OAAO,YAAYP,OAAS,GAAG,CAAC;CACvC,eAAe,OAAO,YAAYC,QAAU,GAAG,KAAK,CAAC;AACvD,CAAC;AAED,MAAM,YAAY,OAAO,OAAO;CAC9B,GAAG;CACH,MAAMD,OAAS,KAAK;CACpB,aAAaK;CACb,cAAcG;AAChB,CAAC;AAED,MAAM,eAAe,OAAO,OAAO;CACjC,GAAG;CACH,cAAcR,OAAS,KAAK;CAC5B,QAAQ,OAAO,YAAYM,MAAQ;AACrC,CAAC;AAED,MAAM,aAAa,OAAO,OAAO;CAC/B,OAAON,OAAS,KAAK;CACrB,eAAe,OAAO,SAAS,CAAC,gBAAgB,eAAe,CAAC;AAClE,CAAC;AAED,MAAM,YAAY,OAAO,MAAM,CAC7B,OAAO,QACP,OAAO,OAAO,MAAM,OAAO,UAAU,mCAAmC,CAAC,CAAC,CAAC,KACzE,OAAO,SAAS,OAAO,gBAAgB,CACzC,CACF,CAAC,CAAC,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAC;AAEzC,MAAM,WAAW,OAAO,OAAO;CAC7B,cAAcD,KAAO,KAAK;CAC1B,YAAY,OAAO,OAAO,MAAM,OAAO,UAAU,4BAA4B,CAAC;CAC9E,eAAe,OAAO,YAAYA,KAAO,KAAK,CAAC;CAC/C,UAAU,OAAO,YAAYA,KAAO,KAAK,CAAC;CAC1C,YAAY,OAAO,YAAY,SAAS;CACxC,OAAO,OAAO,YAAY,OAAO,OAAO,MAAM,OAAO,YAAY,KAAK,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,WAAW,OAAO,OAAO;CAC7B,OAAOA,KAAO,GAAG;CACjB,mBAAmB,OAAO,YAAY,OAAO,OAAO,MAAM,OAAO,YAAY,IAAI,CAAC,CAAC;CACnF,WAAW,OAAO,YAAY,OAAO,OAAO,MAAM,OAAO,YAAY,IAAI,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AAMA,MAAa,SAAS,OAAO,WAAW,WACtC,OACoD;CACpD,MAAM,UAAU,OAAOU,OAAS,cAAc,KAAK;CAEnD,IAAI,CAACC,OAAiB,QAAQ,WAAW,GAAG,OAAO,OAAO,YAAY,KAAK,CAAC,CAAC;CAC7E,MAAM,MAAM,OAAOC,OAAS,QAAQ,IAAI;CAExC,IAAI,OAAO,OAAO,KAAK,OAAO,GAAG;EAC/B,IAAI,cAAc,MAAM,QAAQ,OAAO,OAAO,KAAK,GAAG,CAAC,GAAG,OAAO,OAAO,YAAY,KAAK,CAAC,CAAC;EAC3F,MAAM,WAAW,OAAOF,OAAS,UAAU,GAAG;EAE9C,IAAI,QAAQ,WAAW,OAAO,SAAS,UAAU,iBAC/C,OAAO,OAAO,SAAS,KAAK,EAAE,QAAQ,gBAAgB,CAAC;EAEzD,OAAO,OAAO,YAAY,KAAK,CAAC,CAAC;CACnC;CACA,IACE,QAAQ,WAAW,OACnB,OAAO,OAAO,KAAK,mBAAmB,KACtC,OAAO,OAAO,KAAK,WAAW,GAE9B,OAAO,OAAO,YAAY,KAAK,CAAC,CAAC;CACnC,MAAM,QAAQ,OAAOA,OAAS,UAAU,GAAG;CAE3C,OAAO;EACL,WAAW;EACX,aAAa,SAAS,KAAK,MAAM,YAAY;EAC7C,GAAI,MAAM,kBAAkB,KAAA,IACxB,CAAC,IACD,EAAE,cAAc,SAAS,KAAK,MAAM,aAAa,EAAE;EACvD,GAAI,MAAM,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,SAAS,KAAK,MAAM,QAAQ,EAAE;EACjF,GAAI,MAAM,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,MAAM,WAAW;EACxE,GAAI,MAAM,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,MAAM,MAAM;CAC5D;AACF,CAAC;AAED,MAAM,cAAc,UAAsB;CACxC,MAAM,SAAS,IAAI,gBAAgB,MAAM,UAAU;CAEnD,KAAK,MAAM,YAAY,MAAM,aAAa,CAAC,GAAG,OAAO,OAAO,YAAY,QAAQ;CAEhF,OAAO;AACT;AAKA,MAAM,kBAAkB,UACtB,mBAAmB,KAAK,CAAC,CAAC,QAAQ,sBAAsB,SACtD,SAAS,QAAQ,MAAM,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,YAAY,GACzE;AAEF,MAAM,gBAAgB,OAAO,WAAW,WACtC,KACA,UACA,gBACA,MACA;CACA,IAAI,UAAU,kBAAkB,KAAK,GAAG,CAAC,CAAC,KAAK,kBAAkB,UAAU;CAE3E,IAAI,eAAe,WAAW,uBAAuB;EACnD,MAAM,SAAS,OAAOE,OAAS,eAAe,MAAM;EAEpD,MAAM,aAAa,OAAO,OAAO,IAAI;GACnC,WAAW,OAAO,OAAO,GAAG,eAAe,QAAQ,EAAE,GAAG,eAAe,MAAM,GAAG;GAChF,aAAa,YAAY,KAAK,CAAC,CAAC;EAClC,CAAC;EAED,UAAU,kBAAkB,UAAU,SAAS,iBAAiB,SAAS,YAAY;CACvF,OAAO;EACL,KAAK,IAAI,aAAa,QAAQ;EAC9B,IAAI,eAAe,WAAW,sBAC5B,KAAK,IAAI,iBAAiB,OAAOA,OAAS,eAAe,MAAM,CAAC;CACpE;CACA,MAAM,UAAU,KAAK,SAAS;CAE9B,IAAI,IAAI,YAAY,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,SAAS,QAC7C,OAAO,OAAO,mBAAmB,KAAK,EAAE,QAAQ,aAAa,CAAC;CAEhE,OAAO,kBAAkB,SACvB,SACA,SACA,iDACF;AACF,CAAC;;;;;;AAOD,MAAa,OAAO,OAAO,WAAW,WACpC,OACiG;CACjG,MAAM,UAAU,OAAOC,cAAgB,qBAAqB,OAAO,UAAU;CAE7E,MAAM,iBAAiB,OAAO,qBAAqB,QAAQ,cAAc;CAEzE,MAAM,2BACJ,QAAQ,6BAA6B,KAAA,IACjC,iBACA,OAAO,qBAAqB,QAAQ,wBAAwB;CAElE,MAAM,WAAWC,OAAS,QAAQ,QAAQ;CAE1C,MAAM,YACJ,SAAS,0CACR,SAAS,aAAa,KAAA,IAAY,KAAA,IAAY,CAAC,qBAAqB;CAEvE,MAAM,kBACJ,SAAS,+CACR,SAAS,aAAa,KAAA,IAAY,KAAA,IAAY,CAAC,qBAAqB;CAEvE,IACG,cAAc,KAAA,KAAa,CAAC,UAAU,SAAS,eAAe,MAAM,KACpE,QAAQ,6BAA6B,KAAA,KACpC,SAAS,wBAAwB,KAAA,KACjC,oBAAoB,KAAA,KACpB,CAAC,gBAAgB,SAAS,yBAAyB,MAAM,GAE3D,OAAO,OAAO,mBAAmB,KAAK,EAAE,QAAQ,iBAAiB,CAAC;CACpE,MAAM,EAAE,WAAW,QAAQ,OAAOC;CAClC,MAAM,OAAO,OAAOC;CACpB,MAAM,UAAU,QAAQ,YAAY,KAAA,IAAY,KAAA,IAAYF,OAAS,QAAQ,OAAO;CAEpF,OAAO,OAAO,mBACZ,OAAO,WAAW;EAChB,IAAI,eAAe,WAAW,QAAQ,SAAS,WAAW,eAAe,MAAM;EAC/E,IAAI,yBAAyB,WAAW,QACtC,SAAS,WAAW,yBAAyB,MAAM;CACvD,CAAC,CACH;CAEA,MAAM,OAAO,OAAO,WAAW,WAAW,MAAuB;EAC/D,OAAO;EAEP,MAAMG,YAAU,OAAO,cACrB,IAAI,IAAI,SAAS,cAAc,CAAC,CAAC,MACjC,QAAQ,UACR,gBACA,IACF;EAEA,OAAO,OAAOC,KAAe,OAAOC,QAAkB,MAAMF,WAAS,OAAO,CAAC;CAC/E,CAAC;CA0FD,OAAO;EAAE;EAAU,kBAxFM,OAAO,WAAW,WAAW,OAA2B;GAC/E,OAAO;GACP,MAAM,QAAQ,OAAOJ,cAAgB,eAAe,KAAK;GACzD,MAAM,MAAM,IAAI,IAAI,SAAS,sBAAsB;GACnD,MAAM,OAAO,WAAW,KAAK;GAE7B,KAAK,IAAI,aAAa,QAAQ,QAAQ;GACtC,KAAK,IAAI,gBAAgB,MAAM,WAAW;GAC1C,KAAK,IAAI,iBAAiB,MAAM;GAChC,KAAK,IAAI,iBAAiB,OAAO;GACjC,KAAK,IAAI,SAAS,MAAM,OAAO,KAAK,GAAG,CAAC;GACxC,KAAK,IAAI,SAAS,OAAOD,OAAS,MAAM,KAAK,CAAC;GAC9C,KAAK,IAAI,kBAAkB,MAAM,aAAa;GAC9C,KAAK,IAAI,yBAAyB,MAAM;GACxC,IAAI,MAAM,UAAU,KAAA,GAAW,KAAK,IAAI,SAAS,OAAOA,OAAS,MAAM,KAAK,CAAC;GAC7E,IAAI,MAAM,kBAAkB,KAAA,GAAW,KAAK,IAAI,WAAW,OAAO,MAAM,aAAa,CAAC;GACtF,KAAK,MAAM,CAAC,KAAK,SAAS,MAAM,IAAI,aAAa,OAAO,KAAK,IAAI;GACjE,IAAI,IAAI,KAAK,SAAS,OAAO,OAAO,OAAO,mBAAmB,KAAK,EAAE,QAAQ,aAAa,CAAC;GAE3F,OAAO,SAAS,KAAK,IAAI,IAAI;EAC/B,GAAG,GAoE+B;EAAG,WAlEnB,OAAO,WAAW,WAAW,OAAuB;GACpE,OAAO;GACP,MAAM,QAAQ,OAAOC,cAAgB,WAAW,KAAK;GACrD,MAAM,OAAO,WAAW,KAAK;GAE7B,KAAK,IAAI,cAAc,oBAAoB;GAC3C,KAAK,IAAI,QAAQ,OAAOD,OAAS,MAAM,IAAI,CAAC;GAC5C,KAAK,IAAI,iBAAiB,OAAOA,OAAS,MAAM,YAAY,CAAC;GAC7D,KAAK,IAAI,gBAAgB,MAAM,WAAW;GAE1C,OAAO,OAAO,KAAK,IAAI;EACzB,GAAG,GAuD0C;EAAG,cArD3B,OAAO,WAAW,WAAW,OAA0B;GAC1E,OAAO;GACP,MAAM,QAAQ,OAAOC,cAAgB,cAAc,KAAK;GACxD,MAAM,OAAO,WAAW,KAAK;GAE7B,KAAK,IAAI,cAAc,eAAe;GACtC,KAAK,IAAI,iBAAiB,OAAOD,OAAS,MAAM,YAAY,CAAC;GAC7D,IAAI,MAAM,WAAW,KAAA,GAAW,KAAK,IAAI,SAAS,MAAM,OAAO,KAAK,GAAG,CAAC;GAExE,OAAO,OAAO,KAAK,IAAI;EACzB,GAAG,GA2CwD;EAAG,cAzCzC,OAAO,WAAW,WAAW,OAAkC;GAClF,OAAO;GACP,IAAI,YAAY,KAAA,GAAW,OAAO,OAAO,mBAAmB,KAAK,EAAE,QAAQ,WAAW,CAAC;GACvF,MAAM,QAAQ,OAAOA,OAAS,OAAOF,OAAS,QAAQ,KAAK,CAAC;GAE5D,MAAMO,YAAU,kBAAkB,IAAI,IAAI,IAAI,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,KAC/D,kBAAkB,YAClB,kBAAkB,WAAW,QAAQ,WAAW,CAAC,CAAC,GAClD,kBAAkB,UAAU,iBAAiB,UAAU,OAAO,CAChE;GAEA,MAAM,WAAW,OAAOE,QAAkB,MAAMF,WAAS,OAAO;GAEhE,IAAI,SAAS,WAAW,KAAK,OAAO,OAAO,YAAY,KAAK,CAAC,CAAC;GAE9D,OAAO,OAAOL,QAAU,OAAOM,KAAe,QAAQ,EAAA,CAAG,IAAI;EAC/D,GAAG,GAyBsE;EAAG,QAvB7D,OAAO,WAAW,WAAW,OAAwB;GAClE,OAAO;GACP,IAAI,SAAS,wBAAwB,KAAA,GACnC,OAAO,OAAO,mBAAmB,KAAK,EAAE,QAAQ,WAAW,CAAC;GAC9D,IAAI,oBAAoB,KAAA,KAAa,CAAC,gBAAgB,SAAS,yBAAyB,MAAM,GAC5F,OAAO,OAAO,mBAAmB,KAAK,EAAE,QAAQ,iBAAiB,CAAC;GACpE,MAAM,QAAQ,OAAOL,cAAgB,YAAY,KAAK;GAEtD,MAAMI,YAAU,OAAO,cACrB,IAAI,IAAI,SAAS,mBAAmB,CAAC,CAAC,MACtC,QAAQ,UACR,0BACA,IAAI,gBAAgB;IAClB,OAAO,OAAOL,OAAS,MAAM,KAAK;IAClC,iBAAiB,MAAM;GACzB,CAAC,CACH;GAIA,KAAI,OAFoBO,QAAkB,MAAMF,WAAS,OAAO,EAAA,CAEnD,WAAW,KAAK,OAAO,OAAO,YAAY,KAAK,CAAC,CAAC;EAChE,GAAG,GAE8E;CAAE;AACrF,CAAC;AAED,MAAM,SAAS,OAAO,SAASjB,KAAO,KAAK,CAAC,CAAC,MAAM,OAAO,UAAU,yBAAyB,CAAC,GAAG,EAC/F,oBAAoB,KACtB,CAAC;AAED,MAAM,uBAAuB,OAAO,WAAW,WAC7C,gBAC+C;CAC/C,OAAO,eAAe,WAAW,SAC7B,EAAE,GAAG,eAAe,IACpB;EAAE,GAAG;EAAgB,QAAQ,SAAS,KAAK,OAAOY,OAAS,eAAe,MAAM,CAAC;CAAE;AACzF,CAAC"}
|
|
1
|
+
{"version":3,"file":"OAuth.mjs","names":["V.JsonObject","V.Issuer","V.ProtocolEndpoint","V.Endpoint","V.text","V.secret","V.integer","V.Headers","V.Parameters","V.Resources","V.Callback","V.Scopes","V.Challenge","V.Verifier","V.decode","Transport.isJson","V.reveal","V.configuration","V.freeze","Ownership.make","Transport.capture","request","Transport.json","Transport.request"],"sources":["../src/OAuth.ts"],"sourcesContent":["import { Effect, Redacted, Schema, type Scope } from \"effect\";\nimport { Base64 } from \"effect/encoding\";\nimport type { HttpClient } from \"effect/http\";\nimport { HttpClientRequest } from \"effect/http\";\n\nimport { ConfigurationError, Rejected, Unavailable } from \"./Errors\";\nimport * as Ownership from \"./internal/ownership\";\nimport * as Transport from \"./internal/transport\";\nimport * as V from \"./internal/validation\";\n\nexport const JsonObject = V.JsonObject;\nexport type JsonObject = typeof JsonObject.Type;\n\nexport const Metadata = Schema.Struct({\n issuer: V.Issuer,\n authorization_endpoint: V.ProtocolEndpoint,\n token_endpoint: V.ProtocolEndpoint,\n jwks_uri: Schema.optionalKey(V.Endpoint),\n userinfo_endpoint: Schema.optionalKey(V.Endpoint),\n revocation_endpoint: Schema.optionalKey(V.ProtocolEndpoint),\n code_challenge_methods_supported: Schema.optionalKey(\n Schema.Array(V.text(64)).check(Schema.isMaxLength(64)),\n ),\n response_types_supported: Schema.optionalKey(\n Schema.Array(V.text(64)).check(Schema.isMaxLength(64)),\n ),\n grant_types_supported: Schema.optionalKey(\n Schema.Array(V.text(128)).check(Schema.isMaxLength(64)),\n ),\n id_token_signing_alg_values_supported: Schema.optionalKey(\n Schema.Array(V.text(64)).check(Schema.isMaxLength(64)),\n ),\n token_endpoint_auth_methods_supported: Schema.optionalKey(\n Schema.Array(V.text(64)).check(Schema.isMaxLength(64)),\n ),\n revocation_endpoint_auth_methods_supported: Schema.optionalKey(\n Schema.Array(V.text(64)).check(Schema.isMaxLength(64)),\n ),\n authorization_response_iss_parameter_supported: Schema.optionalKey(Schema.Boolean),\n});\n\nexport type Metadata = typeof Metadata.Type;\n\nexport const Authentication = Schema.Union([\n Schema.Struct({ method: Schema.Literal(\"client_secret_basic\"), secret: V.secret(4096) }),\n Schema.Struct({ method: Schema.Literal(\"client_secret_post\"), secret: V.secret(4096) }),\n Schema.Struct({ method: Schema.Literal(\"none\"), publicClient: Schema.Literal(true) }),\n]);\n\nexport type Authentication = typeof Authentication.Type;\n\n/** Uninterpreted private receipt. Inspect provider extensions before calling tokens. */\nexport const TokenReceipt = Schema.Struct({\n status: V.integer(100, 599),\n contentType: Schema.NullOr(Schema.String.check(Schema.isMaxLength(512))),\n body: Schema.Redacted(JsonObject, { disallowJsonEncode: true }),\n});\n\nexport type TokenReceipt = typeof TokenReceipt.Type;\n\nexport const TokenSet = Schema.Struct({\n tokenType: Schema.Literal(\"bearer\"),\n accessToken: V.secret(16384),\n refreshToken: Schema.optionalKey(V.secret(16384)),\n idToken: Schema.optionalKey(V.secret(65536)),\n expiresIn: Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))),\n scope: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(16447))),\n});\n\nexport type TokenSet = typeof TokenSet.Type;\n\nexport interface RequestOptions {\n readonly timeoutMs: number;\n readonly maxResponseBytes?: number;\n}\n\nexport interface ClientOptions extends RequestOptions {\n readonly metadata: Metadata;\n readonly clientId: string;\n readonly authentication: Authentication;\n readonly profile?: { readonly url: string; readonly headers?: Readonly<Record<string, string>> };\n readonly revocationAuthentication?: Authentication;\n}\n\nexport interface Parameters {\n readonly parameters?: Readonly<Record<string, string>>;\n readonly resources?: ReadonlyArray<string>;\n}\n\nexport interface AuthorizationInput extends Parameters {\n readonly redirectUri: string;\n readonly scopes: ReadonlyArray<string>;\n readonly state: Redacted.Redacted<string>;\n readonly codeChallenge?: string;\n readonly nonce?: Redacted.Redacted<string>;\n readonly maxAgeSeconds?: number;\n readonly prompt?: \"none\" | \"login\" | \"consent\" | \"select_account\";\n readonly loginHint?: string;\n}\n\nexport interface CodeGrantInput extends Parameters {\n readonly code: Redacted.Redacted<string>;\n readonly redirectUri: string;\n readonly pkceVerifier?: Redacted.Redacted<string>;\n}\n\nexport interface RefreshGrantInput extends Parameters {\n readonly refreshToken: Redacted.Redacted<string>;\n readonly scopes?: ReadonlyArray<string>;\n}\n\nexport interface RevocationInput {\n readonly token: Redacted.Redacted<string>;\n readonly tokenTypeHint: \"access_token\" | \"refresh_token\";\n}\n\nexport interface Client {\n readonly metadata: Metadata;\n readonly authorizationUrl: (\n input: AuthorizationInput,\n ) => Effect.Effect<Redacted.Redacted<string>, ConfigurationError | Unavailable>;\n readonly codeGrant: (\n input: CodeGrantInput,\n ) => Effect.Effect<TokenReceipt, ConfigurationError | Unavailable>;\n readonly refreshGrant: (\n input: RefreshGrantInput,\n ) => Effect.Effect<TokenReceipt, ConfigurationError | Unavailable>;\n readonly fetchProfile: (\n accessToken: Redacted.Redacted<string>,\n ) => Effect.Effect<JsonObject, ConfigurationError | Unavailable>;\n readonly revoke: (\n input: RevocationInput,\n ) => Effect.Effect<void, ConfigurationError | Unavailable>;\n}\n\nconst ClientOptionsSchema = Schema.Struct({\n metadata: Metadata,\n clientId: V.text(1024),\n authentication: Authentication,\n ...V.RequestOptions.fields,\n profile: Schema.optionalKey(\n Schema.Struct({ url: V.Endpoint, headers: Schema.optionalKey(V.Headers) }),\n ),\n revocationAuthentication: Schema.optionalKey(Authentication),\n});\n\nconst additional = {\n parameters: Schema.optionalKey(V.Parameters),\n resources: Schema.optionalKey(V.Resources),\n};\n\nconst Authorization = Schema.Struct({\n ...additional,\n redirectUri: V.Callback,\n scopes: V.Scopes,\n state: V.secret(256),\n codeChallenge: Schema.optionalKey(V.Challenge),\n nonce: Schema.optionalKey(V.secret(256)),\n maxAgeSeconds: Schema.optionalKey(V.integer(0, 86400)),\n prompt: Schema.optionalKey(Schema.Literals([\"none\", \"login\", \"consent\", \"select_account\"])),\n loginHint: Schema.optionalKey(V.text(1024)),\n});\n\nconst CodeGrant = Schema.Struct({\n ...additional,\n code: V.secret(16384),\n redirectUri: V.Callback,\n pkceVerifier: Schema.optionalKey(V.Verifier),\n});\n\nconst RefreshGrant = Schema.Struct({\n ...additional,\n refreshToken: V.secret(16384),\n scopes: Schema.optionalKey(V.Scopes),\n});\n\nconst Revocation = Schema.Struct({\n token: V.secret(16384),\n tokenTypeHint: Schema.Literals([\"access_token\", \"refresh_token\"]),\n});\n\nconst ExpiresIn = Schema.Union([\n Schema.Finite,\n Schema.String.check(Schema.isPattern(/^(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$/u)).pipe(\n Schema.decodeTo(Schema.FiniteFromString),\n ),\n]).check(Schema.isGreaterThanOrEqualTo(0));\n\nconst RawToken = Schema.Struct({\n access_token: V.text(16384),\n token_type: Schema.String.check(Schema.isPattern(/^[Bb][Ee][Aa][Rr][Ee][Rr]$/)),\n refresh_token: Schema.optionalKey(V.text(16384)),\n id_token: Schema.optionalKey(V.text(65536)),\n expires_in: Schema.optionalKey(ExpiresIn),\n scope: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(16447))),\n});\n\nconst Terminal = Schema.Struct({\n error: V.text(128),\n error_description: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(4096))),\n error_uri: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(2048))),\n});\n\nconst successFields = [\n \"access_token\",\n \"refresh_token\",\n \"id_token\",\n \"token_type\",\n \"scope\",\n \"expires_in\",\n \"refresh_expires_in\",\n \"refresh_token_expires_in\",\n];\n\n/** Parse once provider-specific receipt checks have succeeded. expires_in accepts\n * finite nonnegative numbers and whole decimal strings (for example \"60.5\").\n * Whitespace, exponent/prefix syntax and nonfinite values are rejected. Raw\n * receipt extensions/scopes are untouched; no request, retry or claim verification. */\nexport const tokens = Effect.fnUntraced(function* (\n input: TokenReceipt,\n): Effect.fn.Return<TokenSet, Rejected | Unavailable> {\n const receipt = yield* V.decode(TokenReceipt, input);\n\n if (!Transport.isJson(receipt.contentType)) return yield* Unavailable.make({});\n const raw = yield* V.reveal(receipt.body);\n\n if (Object.hasOwn(raw, \"error\")) {\n if (successFields.some((key) => Object.hasOwn(raw, key))) return yield* Unavailable.make({});\n const terminal = yield* V.decode(Terminal, raw);\n\n if (receipt.status === 400 && terminal.error === \"invalid_grant\")\n return yield* Rejected.make({ reason: \"invalid_grant\" });\n\n return yield* Unavailable.make({});\n }\n if (\n receipt.status !== 200 ||\n Object.hasOwn(raw, \"error_description\") ||\n Object.hasOwn(raw, \"error_uri\")\n )\n return yield* Unavailable.make({});\n const value = yield* V.decode(RawToken, raw);\n\n return {\n tokenType: \"bearer\",\n accessToken: Redacted.make(value.access_token),\n ...(value.refresh_token === undefined\n ? {}\n : { refreshToken: Redacted.make(value.refresh_token) }),\n ...(value.id_token === undefined ? {} : { idToken: Redacted.make(value.id_token) }),\n ...(value.expires_in === undefined ? {} : { expiresIn: value.expires_in }),\n ...(value.scope === undefined ? {} : { scope: value.scope }),\n };\n});\n\nconst parameters = (input: Parameters) => {\n const values = new URLSearchParams(input.parameters);\n\n for (const resource of input.resources ?? []) values.append(\"resource\", resource);\n\n return values;\n};\n\n// RFC6749 Appendix B: encode client credentials before joining with the colon.\n// Adapted from oauth4webapi 3.8.8 src/index.ts::formUrlEncode (MIT, Filip Skokan).\n// Exact source commit and license: ../THIRD_PARTY_NOTICES.md.\nconst basicComponent = (value: string) =>\n encodeURIComponent(value).replace(/[-_.!~*'()]|%20/gu, (part) =>\n part === \"%20\" ? \"+\" : `%${part.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n\nconst authenticated = Effect.fnUntraced(function* (\n url: string,\n clientId: string,\n authentication: Authentication,\n body: URLSearchParams,\n) {\n let request = HttpClientRequest.post(url).pipe(HttpClientRequest.acceptJson);\n\n if (authentication.method === \"client_secret_basic\") {\n const secret = yield* V.reveal(authentication.secret);\n\n const credential = yield* Effect.try({\n try: () => Base64.encode(`${basicComponent(clientId)}:${basicComponent(secret)}`),\n catch: () => Unavailable.make({}),\n });\n\n request = HttpClientRequest.setHeader(request, \"authorization\", `Basic ${credential}`);\n } else {\n body.set(\"client_id\", clientId);\n if (authentication.method === \"client_secret_post\")\n body.set(\"client_secret\", yield* V.reveal(authentication.secret));\n }\n const encoded = body.toString();\n\n if (new TextEncoder().encode(encoded).length > 131072)\n return yield* ConfigurationError.make({ reason: \"parameters\" });\n\n return HttpClientRequest.bodyText(\n request,\n encoded,\n \"application/x-www-form-urlencoded;charset=UTF-8\",\n );\n});\n\n/** One installed client, exact endpoints, private credentials and owning Scope.\n * Supply an HttpClient without retry/redirect/cookie middleware. Fetch injection\n * active at construction is captured. Owner closure cancels and joins active\n * operations, then rejects their results as unavailable; caller interruption\n * cancels and joins its operation without closing the client. */\nexport const make = Effect.fnUntraced(function* (\n input: ClientOptions,\n): Effect.fn.Return<Client, ConfigurationError | Unavailable, HttpClient.HttpClient | Scope.Scope> {\n const options = yield* V.configuration(ClientOptionsSchema, input, \"metadata\");\n\n const authentication = yield* detachAuthentication(options.authentication);\n\n const revocationAuthentication =\n options.revocationAuthentication === undefined\n ? authentication\n : yield* detachAuthentication(options.revocationAuthentication);\n\n const metadata = V.freeze(options.metadata);\n\n const supported =\n metadata.token_endpoint_auth_methods_supported ??\n (metadata.jwks_uri === undefined ? undefined : [\"client_secret_basic\"]);\n\n const revokeSupported =\n metadata.revocation_endpoint_auth_methods_supported ??\n (metadata.jwks_uri === undefined ? undefined : [\"client_secret_basic\"]);\n\n if (\n (supported !== undefined && !supported.includes(authentication.method)) ||\n (options.revocationAuthentication !== undefined &&\n metadata.revocation_endpoint !== undefined &&\n revokeSupported !== undefined &&\n !revokeSupported.includes(revocationAuthentication.method))\n )\n return yield* ConfigurationError.make({ reason: \"authentication\" });\n const { available, use } = yield* Ownership.make;\n const http = yield* Transport.capture;\n const profile = options.profile === undefined ? undefined : V.freeze(options.profile);\n\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n if (authentication.method !== \"none\") Redacted.wipeUnsafe(authentication.secret);\n if (revocationAuthentication.method !== \"none\")\n Redacted.wipeUnsafe(revocationAuthentication.secret);\n }),\n );\n\n const send = Effect.fnUntraced(function* (body: URLSearchParams) {\n yield* available;\n\n const request = yield* authenticated(\n new URL(metadata.token_endpoint).href,\n options.clientId,\n authentication,\n body,\n );\n\n return yield* Transport.json(yield* Transport.request(http, request, options));\n });\n\n const authorizationUrl = Effect.fnUntraced(function* (input: AuthorizationInput) {\n yield* available;\n const value = yield* V.configuration(Authorization, input);\n const url = new URL(metadata.authorization_endpoint);\n const body = parameters(value);\n\n body.set(\"client_id\", options.clientId);\n body.set(\"redirect_uri\", value.redirectUri);\n body.set(\"response_type\", \"code\");\n body.set(\"response_mode\", \"query\");\n body.set(\"scope\", value.scopes.join(\" \"));\n body.set(\"state\", yield* V.reveal(value.state));\n if (value.codeChallenge !== undefined) {\n body.set(\"code_challenge\", value.codeChallenge);\n body.set(\"code_challenge_method\", \"S256\");\n }\n if (value.nonce !== undefined) body.set(\"nonce\", yield* V.reveal(value.nonce));\n if (value.maxAgeSeconds !== undefined) body.set(\"max_age\", String(value.maxAgeSeconds));\n if (value.prompt !== undefined) body.set(\"prompt\", value.prompt);\n if (value.loginHint !== undefined) body.set(\"login_hint\", value.loginHint);\n for (const [key, item] of body) url.searchParams.append(key, item);\n if (url.href.length > 16384) return yield* ConfigurationError.make({ reason: \"parameters\" });\n\n return Redacted.make(url.href);\n }, use);\n\n const codeGrant = Effect.fnUntraced(function* (input: CodeGrantInput) {\n yield* available;\n const value = yield* V.configuration(CodeGrant, input);\n const body = parameters(value);\n\n body.set(\"grant_type\", \"authorization_code\");\n body.set(\"code\", yield* V.reveal(value.code));\n if (value.pkceVerifier !== undefined)\n body.set(\"code_verifier\", yield* V.reveal(value.pkceVerifier));\n body.set(\"redirect_uri\", value.redirectUri);\n\n return yield* send(body);\n }, use);\n\n const refreshGrant = Effect.fnUntraced(function* (input: RefreshGrantInput) {\n yield* available;\n const value = yield* V.configuration(RefreshGrant, input);\n const body = parameters(value);\n\n body.set(\"grant_type\", \"refresh_token\");\n body.set(\"refresh_token\", yield* V.reveal(value.refreshToken));\n if (value.scopes !== undefined) body.set(\"scope\", value.scopes.join(\" \"));\n\n return yield* send(body);\n }, use);\n\n const fetchProfile = Effect.fnUntraced(function* (input: Redacted.Redacted<string>) {\n yield* available;\n if (profile === undefined) return yield* ConfigurationError.make({ reason: \"endpoint\" });\n const token = yield* V.reveal(yield* V.decode(Bearer, input));\n\n const request = HttpClientRequest.get(new URL(profile.url).href).pipe(\n HttpClientRequest.acceptJson,\n HttpClientRequest.setHeaders(profile.headers ?? {}),\n HttpClientRequest.setHeader(\"authorization\", `Bearer ${token}`),\n );\n\n const response = yield* Transport.request(http, request, options);\n\n if (response.status !== 200) return yield* Unavailable.make({});\n\n return yield* V.reveal((yield* Transport.json(response)).body);\n }, use);\n\n const revoke = Effect.fnUntraced(function* (input: RevocationInput) {\n yield* available;\n if (metadata.revocation_endpoint === undefined)\n return yield* ConfigurationError.make({ reason: \"endpoint\" });\n if (revokeSupported !== undefined && !revokeSupported.includes(revocationAuthentication.method))\n return yield* ConfigurationError.make({ reason: \"authentication\" });\n const value = yield* V.configuration(Revocation, input);\n\n const request = yield* authenticated(\n new URL(metadata.revocation_endpoint).href,\n options.clientId,\n revocationAuthentication,\n new URLSearchParams({\n token: yield* V.reveal(value.token),\n token_type_hint: value.tokenTypeHint,\n }),\n );\n\n const response = yield* Transport.request(http, request, options);\n\n if (response.status !== 200) return yield* Unavailable.make({});\n }, use);\n\n return { metadata, authorizationUrl, codeGrant, refreshGrant, fetchProfile, revoke };\n});\n\nconst Bearer = Schema.Redacted(V.text(16384).check(Schema.isPattern(/^[A-Za-z0-9._~+/-]+=*$/u)), {\n disallowJsonEncode: true,\n});\n\nconst detachAuthentication = Effect.fnUntraced(function* (\n authentication: Authentication,\n): Effect.fn.Return<Authentication, Unavailable> {\n return authentication.method === \"none\"\n ? { ...authentication }\n : { ...authentication, secret: Redacted.make(yield* V.reveal(authentication.secret)) };\n});\n"],"mappings":"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"}
|
package/dist/Oidc.d.mts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { ConfigurationError, Rejected, Unavailable } from "./Errors.mjs";
|
|
2
2
|
import { JsonObject, Metadata, RequestOptions } from "./OAuth.mjs";
|
|
3
|
-
import { Crypto, DateTime, Effect, Redacted, Scope } from "effect";
|
|
3
|
+
import { Crypto, DateTime, Effect, Redacted, Schema, Scope } from "effect";
|
|
4
4
|
import { HttpClient } from "effect/http";
|
|
5
5
|
import * as Jwks from "@yielded/jose/Jwks";
|
|
6
6
|
import * as Jws from "@yielded/jose/Jws";
|
|
7
7
|
//#region src/Oidc.d.ts
|
|
8
|
+
export declare const IdTokenAlgorithm: Schema.Literals<readonly ["RS256", "PS256", "ES256", "EdDSA"]>;
|
|
9
|
+
export type IdTokenAlgorithm = typeof IdTokenAlgorithm.Type;
|
|
10
|
+
export declare const defaultIdTokenAlgorithms: readonly IdTokenAlgorithm[];
|
|
8
11
|
export interface VerifierOptions extends RequestOptions {
|
|
9
12
|
readonly metadata: Metadata;
|
|
10
13
|
readonly clientId: string;
|
|
14
|
+
/** Algorithms this verifier will accept. Discovery must advertise at least one.
|
|
15
|
+
* Defaults to RS256, PS256, ES256 and EdDSA. */
|
|
16
|
+
readonly algorithms?: ReadonlyArray<IdTokenAlgorithm>;
|
|
17
|
+
/** Require advertised S256 PKCE. Defaults to true. Set false only for issuers
|
|
18
|
+
* that cannot complete authorization-code + PKCE. */
|
|
19
|
+
readonly pkceS256?: boolean;
|
|
11
20
|
}
|
|
12
21
|
export interface VerificationInput {
|
|
13
22
|
readonly verificationStartedAt: DateTime.Utc;
|
|
@@ -48,10 +57,11 @@ export declare const discover: (input: string, inputOptions: RequestOptions) =>
|
|
|
48
57
|
readonly revocation_endpoint_auth_methods_supported?: readonly string[];
|
|
49
58
|
readonly authorization_response_iss_parameter_supported?: boolean;
|
|
50
59
|
}, ConfigurationError | Unavailable, HttpClient.HttpClient>;
|
|
51
|
-
/**
|
|
52
|
-
* to the caller's Scope. Owner closure cancels
|
|
53
|
-
* returning Unavailable; caller interruption joins
|
|
54
|
-
* the verifier. No process cache or automatic
|
|
60
|
+
/** Signed ID tokens using advertised RS256, PS256, ES256 or EdDSA. Verification
|
|
61
|
+
* and the bounded JWKS cache belong to the caller's Scope. Owner closure cancels
|
|
62
|
+
* and joins active verification, returning Unavailable; caller interruption joins
|
|
63
|
+
* its operation without closing the verifier. No process cache or automatic
|
|
64
|
+
* exchange retry is created. */
|
|
55
65
|
export declare const makeVerifier: (input: VerifierOptions) => Effect.Effect<Verifier, ConfigurationError | Unavailable, HttpClient.HttpClient | Scope.Scope>;
|
|
56
66
|
//#endregion
|
|
57
67
|
//# sourceMappingURL=Oidc.d.mts.map
|
package/dist/Oidc.mjs
CHANGED
|
@@ -9,10 +9,24 @@ import { HttpClient, HttpClientRequest } from "effect/http";
|
|
|
9
9
|
import * as Jwks from "@yielded/jose/Jwks";
|
|
10
10
|
import * as Jwt from "@yielded/jose/Jwt";
|
|
11
11
|
//#region src/Oidc.ts
|
|
12
|
+
const IdTokenAlgorithm = Schema.Literals([
|
|
13
|
+
"RS256",
|
|
14
|
+
"PS256",
|
|
15
|
+
"ES256",
|
|
16
|
+
"EdDSA"
|
|
17
|
+
]);
|
|
18
|
+
const defaultIdTokenAlgorithms = [
|
|
19
|
+
"RS256",
|
|
20
|
+
"PS256",
|
|
21
|
+
"ES256",
|
|
22
|
+
"EdDSA"
|
|
23
|
+
];
|
|
12
24
|
const Configuration = Schema.Struct({
|
|
13
25
|
metadata: Metadata,
|
|
14
26
|
clientId: text(1024),
|
|
15
|
-
...RequestOptions.fields
|
|
27
|
+
...RequestOptions.fields,
|
|
28
|
+
algorithms: Schema.optionalKey(Schema.Array(IdTokenAlgorithm).check(Schema.isMinLength(1), Schema.isMaxLength(4))),
|
|
29
|
+
pkceS256: Schema.optionalKey(Schema.Boolean)
|
|
16
30
|
});
|
|
17
31
|
const VerifyInput = Schema.Struct({
|
|
18
32
|
verificationStartedAt: Schema.DateTimeUtc,
|
|
@@ -35,9 +49,16 @@ const IdClaims = Schema.Struct({
|
|
|
35
49
|
nbf: Schema.optionalKey(NumericDate),
|
|
36
50
|
auth_time: Schema.optionalKey(NumericDate),
|
|
37
51
|
nonce: Schema.optionalKey(text(256)),
|
|
38
|
-
at_hash: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}
|
|
39
|
-
c_hash: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}
|
|
52
|
+
at_hash: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}(?:[A-Za-z0-9_-]{21})?$/u))),
|
|
53
|
+
c_hash: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}(?:[A-Za-z0-9_-]{21})?$/u)))
|
|
40
54
|
});
|
|
55
|
+
const tokenHash = (algorithm) => algorithm === "EdDSA" ? {
|
|
56
|
+
digest: "SHA-512",
|
|
57
|
+
octets: 32
|
|
58
|
+
} : algorithm === "RS256" || algorithm === "PS256" || algorithm === "ES256" ? {
|
|
59
|
+
digest: "SHA-256",
|
|
60
|
+
octets: 16
|
|
61
|
+
} : void 0;
|
|
41
62
|
/** OIDC discovery uses the trusted issuer identifier, retaining its exact
|
|
42
63
|
* spelling for claim comparison even if its network URL normalizes. */
|
|
43
64
|
const discover = Effect.fnUntraced(function* (input, inputOptions) {
|
|
@@ -54,14 +75,16 @@ const discover = Effect.fnUntraced(function* (input, inputOptions) {
|
|
|
54
75
|
return freeze(metadata);
|
|
55
76
|
});
|
|
56
77
|
const claimFailure = () => Rejected.make({ reason: "claims" });
|
|
57
|
-
/**
|
|
58
|
-
* to the caller's Scope. Owner closure cancels
|
|
59
|
-
* returning Unavailable; caller interruption joins
|
|
60
|
-
* the verifier. No process cache or automatic
|
|
78
|
+
/** Signed ID tokens using advertised RS256, PS256, ES256 or EdDSA. Verification
|
|
79
|
+
* and the bounded JWKS cache belong to the caller's Scope. Owner closure cancels
|
|
80
|
+
* and joins active verification, returning Unavailable; caller interruption joins
|
|
81
|
+
* its operation without closing the verifier. No process cache or automatic
|
|
82
|
+
* exchange retry is created. */
|
|
61
83
|
const makeVerifier = Effect.fnUntraced(function* (input) {
|
|
62
84
|
const options = yield* configuration(Configuration, input, "metadata");
|
|
63
85
|
const metadata = freeze(options.metadata);
|
|
64
|
-
|
|
86
|
+
const advertised = defaultIdTokenAlgorithms.filter((algorithm) => (options.algorithms ?? defaultIdTokenAlgorithms).includes(algorithm)).filter((algorithm) => metadata.id_token_signing_alg_values_supported?.includes(algorithm));
|
|
87
|
+
if (metadata.jwks_uri === void 0 || advertised.length === 0 || !metadata.response_types_supported?.includes("code") || (options.pkceS256 ?? true) && !metadata.code_challenge_methods_supported?.includes("S256")) return yield* ConfigurationError.make({ reason: "metadata" });
|
|
65
88
|
const http = yield* capture;
|
|
66
89
|
const context = yield* Layer.build(Jwks.layerRemote({
|
|
67
90
|
url: metadata.jwks_uri,
|
|
@@ -77,7 +100,7 @@ const makeVerifier = Effect.fnUntraced(function* (input) {
|
|
|
77
100
|
const accessToken = policy.accessToken === void 0 ? void 0 : yield* reveal(policy.accessToken);
|
|
78
101
|
const code = policy.code === void 0 ? void 0 : yield* reveal(policy.code);
|
|
79
102
|
const verified = yield* Jwt.verifyWithKeySet(JsonObject, token, {
|
|
80
|
-
algorithms:
|
|
103
|
+
algorithms: advertised,
|
|
81
104
|
issuer: metadata.issuer,
|
|
82
105
|
audience: options.clientId,
|
|
83
106
|
requiredClaims: [
|
|
@@ -95,12 +118,14 @@ const makeVerifier = Effect.fnUntraced(function* (input) {
|
|
|
95
118
|
if (policy.previous === void 0) {
|
|
96
119
|
if (claims.nonce !== expectedNonce || policy.maxAgeSeconds !== void 0 && (claims.auth_time === void 0 || claims.auth_time + policy.maxAgeSeconds < Math.floor(now))) return yield* claimFailure();
|
|
97
120
|
} else if (claims.sub !== policy.previous.subject || claims.nonce !== void 0 && claims.nonce !== expectedNonce || claims.auth_time !== void 0 && claims.auth_time !== policy.previous.authTime) return yield* claimFailure();
|
|
121
|
+
const hashProfile = tokenHash(verified.protectedHeader.alg);
|
|
122
|
+
if (hashProfile === void 0) return yield* claimFailure();
|
|
98
123
|
for (const [hash, secret] of [[claims.at_hash, accessToken], [claims.c_hash, code]]) {
|
|
99
124
|
if (hash === void 0) continue;
|
|
100
125
|
if (secret === void 0) return yield* claimFailure();
|
|
101
|
-
const digest = yield* (yield* Crypto.Crypto).digest(
|
|
102
|
-
if (digest.length !==
|
|
103
|
-
if (hash !== Base64Url.encode(digest.subarray(0,
|
|
126
|
+
const digest = yield* (yield* Crypto.Crypto).digest(hashProfile.digest, new TextEncoder().encode(secret)).pipe(Effect.mapError(() => Unavailable.make({})));
|
|
127
|
+
if (digest.length !== hashProfile.octets * 2) return yield* Unavailable.make({});
|
|
128
|
+
if (hash !== Base64Url.encode(digest.subarray(0, hashProfile.octets))) return yield* claimFailure();
|
|
104
129
|
}
|
|
105
130
|
const finished = (yield* Clock.currentTimeMillis) / 1e3;
|
|
106
131
|
if (claims.exp <= finished || policy.previous === void 0 && policy.maxAgeSeconds !== void 0 && claims.auth_time !== void 0 && claims.auth_time + policy.maxAgeSeconds < Math.floor(finished)) return yield* claimFailure();
|
|
@@ -115,6 +140,6 @@ const makeVerifier = Effect.fnUntraced(function* (input) {
|
|
|
115
140
|
}, use) };
|
|
116
141
|
});
|
|
117
142
|
//#endregion
|
|
118
|
-
export { discover, makeVerifier };
|
|
143
|
+
export { IdTokenAlgorithm, defaultIdTokenAlgorithms, discover, makeVerifier };
|
|
119
144
|
|
|
120
145
|
//# sourceMappingURL=Oidc.mjs.map
|
package/dist/Oidc.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Oidc.mjs","names":["V.text","V.secret","V.integer","V.NumericDate","V.configuration","V.Issuer","V.RequestOptions","Transport.capture","Transport.request","Transport.json","V.reveal","V.freeze","Ownership.make","V.decode"],"sources":["../src/Oidc.ts"],"sourcesContent":["import * as Jwks from \"@yielded/jose/Jwks\";\nimport type * as Jws from \"@yielded/jose/Jws\";\nimport * as Jwt from \"@yielded/jose/Jwt\";\nimport {\n Clock,\n Context,\n Crypto,\n DateTime,\n Effect,\n Layer,\n Redacted,\n Schema,\n type Scope,\n} from \"effect\";\nimport { Base64Url } from \"effect/encoding\";\nimport { HttpClient, HttpClientRequest } from \"effect/http\";\n\nimport { ConfigurationError, Rejected, Unavailable } from \"./Errors\";\nimport * as Ownership from \"./internal/ownership\";\nimport * as Transport from \"./internal/transport\";\nimport * as V from \"./internal/validation\";\nimport { JsonObject, Metadata, type RequestOptions } from \"./OAuth\";\n\nexport interface VerifierOptions extends RequestOptions {\n readonly metadata: Metadata;\n readonly clientId: string;\n}\n\nexport interface VerificationInput {\n readonly verificationStartedAt: DateTime.Utc;\n readonly nonce: Redacted.Redacted<string>;\n readonly maxAgeSeconds?: number;\n readonly accessToken?: Redacted.Redacted<string>;\n readonly code?: Redacted.Redacted<string>;\n /** Refresh continuation: same subject, optional nonce must match, auth_time cannot change. */\n readonly previous?: { readonly subject: string; readonly authTime?: number };\n}\n\nexport interface Verified {\n readonly claims: Redacted.Redacted<JsonObject>;\n readonly subject: string;\n readonly authTime?: number;\n readonly upstreamAuthenticatedAt?: DateTime.Utc;\n}\n\ntype VerificationRequirements =\n | Exclude<Effect.Services<ReturnType<typeof Jws.verifyWithKeySet>>, Jwks.Jwks>\n | Crypto.Crypto;\n\nexport interface Verifier {\n readonly verify: (\n token: Redacted.Redacted<string>,\n input: VerificationInput,\n ) => Effect.Effect<Verified, Rejected | Unavailable, VerificationRequirements>;\n}\n\nconst Configuration = Schema.Struct({\n metadata: Metadata,\n clientId: V.text(1024),\n ...V.RequestOptions.fields,\n});\n\nconst VerifyInput = Schema.Struct({\n verificationStartedAt: Schema.DateTimeUtc,\n nonce: V.secret(256),\n maxAgeSeconds: Schema.optional(V.integer(0, 86400)),\n accessToken: Schema.optional(V.secret(16384)),\n code: Schema.optional(V.secret(16384)),\n previous: Schema.optional(\n Schema.Struct({ subject: V.text(1024), authTime: Schema.optional(V.NumericDate) }),\n ),\n});\n\nconst IdClaims = Schema.Struct({\n iss: V.text(2048),\n sub: V.text(1024),\n aud: Schema.Union([\n V.text(1024),\n Schema.Array(V.text(1024)).check(Schema.isMinLength(1), Schema.isMaxLength(1)),\n ]),\n azp: Schema.optionalKey(V.text(1024)),\n exp: V.NumericDate,\n iat: V.NumericDate,\n nbf: Schema.optionalKey(V.NumericDate),\n auth_time: Schema.optionalKey(V.NumericDate),\n nonce: Schema.optionalKey(V.text(256)),\n // The RS256 half digest is 128 bits (22 base64url characters).\n at_hash: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}$/u))),\n c_hash: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}$/u))),\n});\n\n/** OIDC discovery uses the trusted issuer identifier, retaining its exact\n * spelling for claim comparison even if its network URL normalizes. */\nexport const discover = Effect.fnUntraced(function* (\n input: string,\n inputOptions: RequestOptions,\n): Effect.fn.Return<Metadata, ConfigurationError | Unavailable, HttpClient.HttpClient> {\n const issuer = yield* V.configuration(V.Issuer, input, \"issuer\");\n\n const options = yield* V.configuration(V.RequestOptions, inputOptions);\n\n const url = new URL(issuer);\n\n url.pathname = `${url.pathname.replace(/\\/$/u, \"\")}/.well-known/openid-configuration`;\n const http = yield* Transport.capture;\n\n const response = yield* Transport.request(\n http,\n HttpClientRequest.get(url.href).pipe(HttpClientRequest.acceptJson),\n options,\n );\n\n if (response.status !== 200) return yield* Unavailable.make({});\n const document = yield* Transport.json(response);\n const metadata = yield* V.configuration(Metadata, yield* V.reveal(document.body), \"metadata\");\n\n if (metadata.issuer !== issuer) return yield* ConfigurationError.make({ reason: \"issuer\" });\n\n return V.freeze(metadata);\n});\n\nconst claimFailure = () => Rejected.make({ reason: \"claims\" });\n\n/** RS256-only signed ID tokens. Verification and the bounded JWKS cache belong\n * to the caller's Scope. Owner closure cancels and joins active verification,\n * returning Unavailable; caller interruption joins its operation without closing\n * the verifier. No process cache or automatic exchange retry is created. */\nexport const makeVerifier = Effect.fnUntraced(function* (\n input: VerifierOptions,\n): Effect.fn.Return<\n Verifier,\n ConfigurationError | Unavailable,\n HttpClient.HttpClient | Scope.Scope\n> {\n const options = yield* V.configuration(Configuration, input, \"metadata\");\n\n const metadata = V.freeze(options.metadata);\n\n if (\n metadata.jwks_uri === undefined ||\n !metadata.code_challenge_methods_supported?.includes(\"S256\") ||\n !metadata.response_types_supported?.includes(\"code\") ||\n !metadata.id_token_signing_alg_values_supported?.includes(\"RS256\")\n )\n return yield* ConfigurationError.make({ reason: \"metadata\" });\n const http = yield* Transport.capture;\n\n const context = yield* Layer.build(\n Jwks.layerRemote({\n url: metadata.jwks_uri,\n timeoutMs: options.timeoutMs,\n maxResponseBytes: options.maxResponseBytes,\n }).pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, http))),\n ).pipe(Effect.mapError(() => Unavailable.make({})));\n\n const keys = Context.get(context, Jwks.Jwks);\n const { use } = yield* Ownership.make;\n\n const verify = Effect.fnUntraced(function* (\n token: Redacted.Redacted<string>,\n input: VerificationInput,\n ): Effect.fn.Return<Verified, Rejected | Unavailable, VerificationRequirements> {\n // Capture caller policy and secret values before key lookup or cryptography.\n const policy = yield* V.decode(VerifyInput, input);\n const start = DateTime.toEpochMillis(policy.verificationStartedAt);\n const expectedNonce = yield* V.reveal(policy.nonce);\n\n const accessToken =\n policy.accessToken === undefined ? undefined : yield* V.reveal(policy.accessToken);\n\n const code = policy.code === undefined ? undefined : yield* V.reveal(policy.code);\n\n // Jwt verifies the signature before any registered/application claim checks.\n // Unauthenticated malformed/key/signature failures must never burn a receipt.\n const verified = yield* Jwt.verifyWithKeySet(JsonObject, token, {\n algorithms: [\"RS256\"],\n issuer: metadata.issuer,\n audience: options.clientId,\n requiredClaims: [\"iss\", \"sub\", \"aud\", \"exp\", \"iat\"],\n clockTolerance: 0,\n }).pipe(\n Effect.provideService(Jwks.Jwks, keys),\n Effect.mapError((error) =>\n error._tag === \"JoseClaimValidationFailed\" ? claimFailure() : Unavailable.make({}),\n ),\n );\n\n const claims = yield* V.decode(IdClaims, verified.claims).pipe(Effect.mapError(claimFailure));\n\n const now = (yield* Clock.currentTimeMillis) / 1000;\n\n if (\n start < 0 ||\n start > now * 1000 ||\n (claims.azp !== undefined && claims.azp !== options.clientId) ||\n claims.exp <= now ||\n claims.iat > now ||\n (claims.nbf !== undefined && claims.nbf > now) ||\n (claims.auth_time !== undefined && claims.auth_time > now)\n )\n return yield* claimFailure();\n if (policy.previous === undefined) {\n if (\n claims.nonce !== expectedNonce ||\n (policy.maxAgeSeconds !== undefined &&\n (claims.auth_time === undefined ||\n claims.auth_time + policy.maxAgeSeconds < Math.floor(now)))\n )\n return yield* claimFailure();\n } else if (\n claims.sub !== policy.previous.subject ||\n (claims.nonce !== undefined && claims.nonce !== expectedNonce) ||\n (claims.auth_time !== undefined && claims.auth_time !== policy.previous.authTime)\n )\n return yield* claimFailure();\n for (const [hash, secret] of [\n [claims.at_hash, accessToken],\n [claims.c_hash, code],\n ] as const) {\n if (hash === undefined) continue;\n if (secret === undefined) return yield* claimFailure();\n const crypto = yield* Crypto.Crypto;\n\n const digest = yield* crypto\n .digest(\"SHA-256\", new TextEncoder().encode(secret))\n .pipe(Effect.mapError(() => Unavailable.make({})));\n\n if (digest.length !== 32) return yield* Unavailable.make({});\n if (hash !== Base64Url.encode(digest.subarray(0, 16))) return yield* claimFailure();\n }\n // Re-read time after optional digest I/O so expired tokens cannot escape.\n const finished = (yield* Clock.currentTimeMillis) / 1000;\n\n if (\n claims.exp <= finished ||\n (policy.previous === undefined &&\n policy.maxAgeSeconds !== undefined &&\n claims.auth_time !== undefined &&\n claims.auth_time + policy.maxAgeSeconds < Math.floor(finished))\n )\n return yield* claimFailure();\n\n return {\n claims: Redacted.make(V.freeze(verified.claims)),\n subject: claims.sub,\n ...(claims.auth_time === undefined\n ? {}\n : {\n authTime: claims.auth_time,\n upstreamAuthenticatedAt: DateTime.makeUnsafe(Math.min(start, claims.auth_time * 1000)),\n }),\n };\n }, use);\n\n return { verify };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Oidc.mjs","names":["V.text","V.secret","V.integer","V.NumericDate","V.configuration","V.Issuer","V.RequestOptions","Transport.capture","Transport.request","Transport.json","V.reveal","V.freeze","Ownership.make","V.decode"],"sources":["../src/Oidc.ts"],"sourcesContent":["import * as Jwks from \"@yielded/jose/Jwks\";\nimport type * as Jws from \"@yielded/jose/Jws\";\nimport * as Jwt from \"@yielded/jose/Jwt\";\nimport {\n Clock,\n Context,\n Crypto,\n DateTime,\n Effect,\n Layer,\n Redacted,\n Schema,\n type Scope,\n} from \"effect\";\nimport { Base64Url } from \"effect/encoding\";\nimport { HttpClient, HttpClientRequest } from \"effect/http\";\n\nimport { ConfigurationError, Rejected, Unavailable } from \"./Errors\";\nimport * as Ownership from \"./internal/ownership\";\nimport * as Transport from \"./internal/transport\";\nimport * as V from \"./internal/validation\";\nimport { JsonObject, Metadata, type RequestOptions } from \"./OAuth\";\n\nexport const IdTokenAlgorithm = Schema.Literals([\"RS256\", \"PS256\", \"ES256\", \"EdDSA\"]);\nexport type IdTokenAlgorithm = typeof IdTokenAlgorithm.Type;\n\nexport const defaultIdTokenAlgorithms: readonly IdTokenAlgorithm[] = [\n \"RS256\",\n \"PS256\",\n \"ES256\",\n \"EdDSA\",\n];\n\nexport interface VerifierOptions extends RequestOptions {\n readonly metadata: Metadata;\n readonly clientId: string;\n /** Algorithms this verifier will accept. Discovery must advertise at least one.\n * Defaults to RS256, PS256, ES256 and EdDSA. */\n readonly algorithms?: ReadonlyArray<IdTokenAlgorithm>;\n /** Require advertised S256 PKCE. Defaults to true. Set false only for issuers\n * that cannot complete authorization-code + PKCE. */\n readonly pkceS256?: boolean;\n}\n\nexport interface VerificationInput {\n readonly verificationStartedAt: DateTime.Utc;\n readonly nonce: Redacted.Redacted<string>;\n readonly maxAgeSeconds?: number;\n readonly accessToken?: Redacted.Redacted<string>;\n readonly code?: Redacted.Redacted<string>;\n /** Refresh continuation: same subject, optional nonce must match, auth_time cannot change. */\n readonly previous?: { readonly subject: string; readonly authTime?: number };\n}\n\nexport interface Verified {\n readonly claims: Redacted.Redacted<JsonObject>;\n readonly subject: string;\n readonly authTime?: number;\n readonly upstreamAuthenticatedAt?: DateTime.Utc;\n}\n\ntype VerificationRequirements =\n | Exclude<Effect.Services<ReturnType<typeof Jws.verifyWithKeySet>>, Jwks.Jwks>\n | Crypto.Crypto;\n\nexport interface Verifier {\n readonly verify: (\n token: Redacted.Redacted<string>,\n input: VerificationInput,\n ) => Effect.Effect<Verified, Rejected | Unavailable, VerificationRequirements>;\n}\n\nconst Configuration = Schema.Struct({\n metadata: Metadata,\n clientId: V.text(1024),\n ...V.RequestOptions.fields,\n algorithms: Schema.optionalKey(\n Schema.Array(IdTokenAlgorithm).check(Schema.isMinLength(1), Schema.isMaxLength(4)),\n ),\n pkceS256: Schema.optionalKey(Schema.Boolean),\n});\n\nconst VerifyInput = Schema.Struct({\n verificationStartedAt: Schema.DateTimeUtc,\n nonce: V.secret(256),\n maxAgeSeconds: Schema.optional(V.integer(0, 86400)),\n accessToken: Schema.optional(V.secret(16384)),\n code: Schema.optional(V.secret(16384)),\n previous: Schema.optional(\n Schema.Struct({ subject: V.text(1024), authTime: Schema.optional(V.NumericDate) }),\n ),\n});\n\nconst IdClaims = Schema.Struct({\n iss: V.text(2048),\n sub: V.text(1024),\n aud: Schema.Union([\n V.text(1024),\n Schema.Array(V.text(1024)).check(Schema.isMinLength(1), Schema.isMaxLength(1)),\n ]),\n azp: Schema.optionalKey(V.text(1024)),\n exp: V.NumericDate,\n iat: V.NumericDate,\n nbf: Schema.optionalKey(V.NumericDate),\n auth_time: Schema.optionalKey(V.NumericDate),\n nonce: Schema.optionalKey(V.text(256)),\n // Leftmost half of the signing-algorithm hash, unpadded base64url: 22\n // characters for SHA-256 (RS256, PS256, ES256) and 43 for SHA-512 (Ed25519).\n at_hash: Schema.optionalKey(\n Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}(?:[A-Za-z0-9_-]{21})?$/u)),\n ),\n c_hash: Schema.optionalKey(\n Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}(?:[A-Za-z0-9_-]{21})?$/u)),\n ),\n});\n\nconst tokenHash = (algorithm: string) =>\n algorithm === \"EdDSA\"\n ? { digest: \"SHA-512\" as const, octets: 32 }\n : algorithm === \"RS256\" || algorithm === \"PS256\" || algorithm === \"ES256\"\n ? { digest: \"SHA-256\" as const, octets: 16 }\n : undefined;\n\n/** OIDC discovery uses the trusted issuer identifier, retaining its exact\n * spelling for claim comparison even if its network URL normalizes. */\nexport const discover = Effect.fnUntraced(function* (\n input: string,\n inputOptions: RequestOptions,\n): Effect.fn.Return<Metadata, ConfigurationError | Unavailable, HttpClient.HttpClient> {\n const issuer = yield* V.configuration(V.Issuer, input, \"issuer\");\n\n const options = yield* V.configuration(V.RequestOptions, inputOptions);\n\n const url = new URL(issuer);\n\n url.pathname = `${url.pathname.replace(/\\/$/u, \"\")}/.well-known/openid-configuration`;\n const http = yield* Transport.capture;\n\n const response = yield* Transport.request(\n http,\n HttpClientRequest.get(url.href).pipe(HttpClientRequest.acceptJson),\n options,\n );\n\n if (response.status !== 200) return yield* Unavailable.make({});\n const document = yield* Transport.json(response);\n const metadata = yield* V.configuration(Metadata, yield* V.reveal(document.body), \"metadata\");\n\n if (metadata.issuer !== issuer) return yield* ConfigurationError.make({ reason: \"issuer\" });\n\n return V.freeze(metadata);\n});\n\nconst claimFailure = () => Rejected.make({ reason: \"claims\" });\n\n/** Signed ID tokens using advertised RS256, PS256, ES256 or EdDSA. Verification\n * and the bounded JWKS cache belong to the caller's Scope. Owner closure cancels\n * and joins active verification, returning Unavailable; caller interruption joins\n * its operation without closing the verifier. No process cache or automatic\n * exchange retry is created. */\nexport const makeVerifier = Effect.fnUntraced(function* (\n input: VerifierOptions,\n): Effect.fn.Return<\n Verifier,\n ConfigurationError | Unavailable,\n HttpClient.HttpClient | Scope.Scope\n> {\n const options = yield* V.configuration(Configuration, input, \"metadata\");\n\n const metadata = V.freeze(options.metadata);\n\n const algorithms = defaultIdTokenAlgorithms.filter((algorithm) =>\n (options.algorithms ?? defaultIdTokenAlgorithms).includes(algorithm),\n );\n\n const advertised = algorithms.filter((algorithm) =>\n metadata.id_token_signing_alg_values_supported?.includes(algorithm),\n );\n\n if (\n metadata.jwks_uri === undefined ||\n advertised.length === 0 ||\n !metadata.response_types_supported?.includes(\"code\") ||\n ((options.pkceS256 ?? true) && !metadata.code_challenge_methods_supported?.includes(\"S256\"))\n )\n return yield* ConfigurationError.make({ reason: \"metadata\" });\n const http = yield* Transport.capture;\n\n const context = yield* Layer.build(\n Jwks.layerRemote({\n url: metadata.jwks_uri,\n timeoutMs: options.timeoutMs,\n maxResponseBytes: options.maxResponseBytes,\n }).pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, http))),\n ).pipe(Effect.mapError(() => Unavailable.make({})));\n\n const keys = Context.get(context, Jwks.Jwks);\n const { use } = yield* Ownership.make;\n\n const verify = Effect.fnUntraced(function* (\n token: Redacted.Redacted<string>,\n input: VerificationInput,\n ): Effect.fn.Return<Verified, Rejected | Unavailable, VerificationRequirements> {\n // Capture caller policy and secret values before key lookup or cryptography.\n const policy = yield* V.decode(VerifyInput, input);\n const start = DateTime.toEpochMillis(policy.verificationStartedAt);\n const expectedNonce = yield* V.reveal(policy.nonce);\n\n const accessToken =\n policy.accessToken === undefined ? undefined : yield* V.reveal(policy.accessToken);\n\n const code = policy.code === undefined ? undefined : yield* V.reveal(policy.code);\n\n // Jwt verifies the signature before any registered/application claim checks.\n // Unauthenticated malformed/key/signature failures must never burn a receipt.\n const verified = yield* Jwt.verifyWithKeySet(JsonObject, token, {\n algorithms: advertised,\n issuer: metadata.issuer,\n audience: options.clientId,\n requiredClaims: [\"iss\", \"sub\", \"aud\", \"exp\", \"iat\"],\n clockTolerance: 0,\n }).pipe(\n Effect.provideService(Jwks.Jwks, keys),\n Effect.mapError((error) =>\n error._tag === \"JoseClaimValidationFailed\" ? claimFailure() : Unavailable.make({}),\n ),\n );\n\n const claims = yield* V.decode(IdClaims, verified.claims).pipe(Effect.mapError(claimFailure));\n\n const now = (yield* Clock.currentTimeMillis) / 1000;\n\n if (\n start < 0 ||\n start > now * 1000 ||\n (claims.azp !== undefined && claims.azp !== options.clientId) ||\n claims.exp <= now ||\n claims.iat > now ||\n (claims.nbf !== undefined && claims.nbf > now) ||\n (claims.auth_time !== undefined && claims.auth_time > now)\n )\n return yield* claimFailure();\n if (policy.previous === undefined) {\n if (\n claims.nonce !== expectedNonce ||\n (policy.maxAgeSeconds !== undefined &&\n (claims.auth_time === undefined ||\n claims.auth_time + policy.maxAgeSeconds < Math.floor(now)))\n )\n return yield* claimFailure();\n } else if (\n claims.sub !== policy.previous.subject ||\n (claims.nonce !== undefined && claims.nonce !== expectedNonce) ||\n (claims.auth_time !== undefined && claims.auth_time !== policy.previous.authTime)\n )\n return yield* claimFailure();\n const hashProfile = tokenHash(verified.protectedHeader.alg);\n\n if (hashProfile === undefined) return yield* claimFailure();\n\n for (const [hash, secret] of [\n [claims.at_hash, accessToken],\n [claims.c_hash, code],\n ] as const) {\n if (hash === undefined) continue;\n if (secret === undefined) return yield* claimFailure();\n const crypto = yield* Crypto.Crypto;\n\n const digest = yield* crypto\n .digest(hashProfile.digest, new TextEncoder().encode(secret))\n .pipe(Effect.mapError(() => Unavailable.make({})));\n\n if (digest.length !== hashProfile.octets * 2) return yield* Unavailable.make({});\n if (hash !== Base64Url.encode(digest.subarray(0, hashProfile.octets)))\n return yield* claimFailure();\n }\n // Re-read time after optional digest I/O so expired tokens cannot escape.\n const finished = (yield* Clock.currentTimeMillis) / 1000;\n\n if (\n claims.exp <= finished ||\n (policy.previous === undefined &&\n policy.maxAgeSeconds !== undefined &&\n claims.auth_time !== undefined &&\n claims.auth_time + policy.maxAgeSeconds < Math.floor(finished))\n )\n return yield* claimFailure();\n\n return {\n claims: Redacted.make(V.freeze(verified.claims)),\n subject: claims.sub,\n ...(claims.auth_time === undefined\n ? {}\n : {\n authTime: claims.auth_time,\n upstreamAuthenticatedAt: DateTime.makeUnsafe(Math.min(start, claims.auth_time * 1000)),\n }),\n };\n }, use);\n\n return { verify };\n});\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@yielded/oauth","version":"0.1.0-beta.
|
|
1
|
+
{"name":"@yielded/oauth","version":"0.1.0-beta.31","dependencies":{"@yielded/jose":"0.1.0-beta.31"},"devDependencies":{"@effect/platform-node":"4.0.0","@effect/vitest":"4.0.0","@types/node":"26.1.2","@yielded/crypto":"0.1.0-beta.31","effect":"4.0.0","jose":"6.2.12","typescript":"7.0.2","vite":"npm:@voidzero-dev/vite-plus-core@1.1.0","vite-plus":"1.1.0","vitest":"5.0.3"},"peerDependencies":{"effect":"^4.0.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./Errors":{"types":"./dist/Errors.d.mts","default":"./dist/Errors.mjs"},"./OAuth":{"types":"./dist/OAuth.d.mts","default":"./dist/OAuth.mjs"},"./Oidc":{"types":"./dist/Oidc.d.mts","default":"./dist/Oidc.mjs"},"./Pkce":{"types":"./dist/Pkce.d.mts","default":"./dist/Pkce.mjs"}},"description":"Native Effect OAuth and signed OpenID Connect protocols.","homepage":"https://yielded.dev/auth/","bugs":{"url":"https://github.com/yielded-dev/auth/issues"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/yielded-dev/auth.git","directory":"packages/oauth"},"files":["dist","src","THIRD_PARTY_NOTICES.md"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test"}}
|
package/src/OAuth.ts
CHANGED
|
@@ -91,15 +91,17 @@ export interface AuthorizationInput extends Parameters {
|
|
|
91
91
|
readonly redirectUri: string;
|
|
92
92
|
readonly scopes: ReadonlyArray<string>;
|
|
93
93
|
readonly state: Redacted.Redacted<string>;
|
|
94
|
-
readonly codeChallenge
|
|
94
|
+
readonly codeChallenge?: string;
|
|
95
95
|
readonly nonce?: Redacted.Redacted<string>;
|
|
96
96
|
readonly maxAgeSeconds?: number;
|
|
97
|
+
readonly prompt?: "none" | "login" | "consent" | "select_account";
|
|
98
|
+
readonly loginHint?: string;
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
export interface CodeGrantInput extends Parameters {
|
|
100
102
|
readonly code: Redacted.Redacted<string>;
|
|
101
103
|
readonly redirectUri: string;
|
|
102
|
-
readonly pkceVerifier
|
|
104
|
+
readonly pkceVerifier?: Redacted.Redacted<string>;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
export interface RefreshGrantInput extends Parameters {
|
|
@@ -152,16 +154,18 @@ const Authorization = Schema.Struct({
|
|
|
152
154
|
redirectUri: V.Callback,
|
|
153
155
|
scopes: V.Scopes,
|
|
154
156
|
state: V.secret(256),
|
|
155
|
-
codeChallenge: V.Challenge,
|
|
157
|
+
codeChallenge: Schema.optionalKey(V.Challenge),
|
|
156
158
|
nonce: Schema.optionalKey(V.secret(256)),
|
|
157
159
|
maxAgeSeconds: Schema.optionalKey(V.integer(0, 86400)),
|
|
160
|
+
prompt: Schema.optionalKey(Schema.Literals(["none", "login", "consent", "select_account"])),
|
|
161
|
+
loginHint: Schema.optionalKey(V.text(1024)),
|
|
158
162
|
});
|
|
159
163
|
|
|
160
164
|
const CodeGrant = Schema.Struct({
|
|
161
165
|
...additional,
|
|
162
166
|
code: V.secret(16384),
|
|
163
167
|
redirectUri: V.Callback,
|
|
164
|
-
pkceVerifier: V.Verifier,
|
|
168
|
+
pkceVerifier: Schema.optionalKey(V.Verifier),
|
|
165
169
|
});
|
|
166
170
|
|
|
167
171
|
const RefreshGrant = Schema.Struct({
|
|
@@ -371,10 +375,14 @@ export const make = Effect.fnUntraced(function* (
|
|
|
371
375
|
body.set("response_mode", "query");
|
|
372
376
|
body.set("scope", value.scopes.join(" "));
|
|
373
377
|
body.set("state", yield* V.reveal(value.state));
|
|
374
|
-
|
|
375
|
-
|
|
378
|
+
if (value.codeChallenge !== undefined) {
|
|
379
|
+
body.set("code_challenge", value.codeChallenge);
|
|
380
|
+
body.set("code_challenge_method", "S256");
|
|
381
|
+
}
|
|
376
382
|
if (value.nonce !== undefined) body.set("nonce", yield* V.reveal(value.nonce));
|
|
377
383
|
if (value.maxAgeSeconds !== undefined) body.set("max_age", String(value.maxAgeSeconds));
|
|
384
|
+
if (value.prompt !== undefined) body.set("prompt", value.prompt);
|
|
385
|
+
if (value.loginHint !== undefined) body.set("login_hint", value.loginHint);
|
|
378
386
|
for (const [key, item] of body) url.searchParams.append(key, item);
|
|
379
387
|
if (url.href.length > 16384) return yield* ConfigurationError.make({ reason: "parameters" });
|
|
380
388
|
|
|
@@ -388,7 +396,8 @@ export const make = Effect.fnUntraced(function* (
|
|
|
388
396
|
|
|
389
397
|
body.set("grant_type", "authorization_code");
|
|
390
398
|
body.set("code", yield* V.reveal(value.code));
|
|
391
|
-
|
|
399
|
+
if (value.pkceVerifier !== undefined)
|
|
400
|
+
body.set("code_verifier", yield* V.reveal(value.pkceVerifier));
|
|
392
401
|
body.set("redirect_uri", value.redirectUri);
|
|
393
402
|
|
|
394
403
|
return yield* send(body);
|
package/src/Oidc.ts
CHANGED
|
@@ -21,9 +21,25 @@ import * as Transport from "./internal/transport";
|
|
|
21
21
|
import * as V from "./internal/validation";
|
|
22
22
|
import { JsonObject, Metadata, type RequestOptions } from "./OAuth";
|
|
23
23
|
|
|
24
|
+
export const IdTokenAlgorithm = Schema.Literals(["RS256", "PS256", "ES256", "EdDSA"]);
|
|
25
|
+
export type IdTokenAlgorithm = typeof IdTokenAlgorithm.Type;
|
|
26
|
+
|
|
27
|
+
export const defaultIdTokenAlgorithms: readonly IdTokenAlgorithm[] = [
|
|
28
|
+
"RS256",
|
|
29
|
+
"PS256",
|
|
30
|
+
"ES256",
|
|
31
|
+
"EdDSA",
|
|
32
|
+
];
|
|
33
|
+
|
|
24
34
|
export interface VerifierOptions extends RequestOptions {
|
|
25
35
|
readonly metadata: Metadata;
|
|
26
36
|
readonly clientId: string;
|
|
37
|
+
/** Algorithms this verifier will accept. Discovery must advertise at least one.
|
|
38
|
+
* Defaults to RS256, PS256, ES256 and EdDSA. */
|
|
39
|
+
readonly algorithms?: ReadonlyArray<IdTokenAlgorithm>;
|
|
40
|
+
/** Require advertised S256 PKCE. Defaults to true. Set false only for issuers
|
|
41
|
+
* that cannot complete authorization-code + PKCE. */
|
|
42
|
+
readonly pkceS256?: boolean;
|
|
27
43
|
}
|
|
28
44
|
|
|
29
45
|
export interface VerificationInput {
|
|
@@ -58,6 +74,10 @@ const Configuration = Schema.Struct({
|
|
|
58
74
|
metadata: Metadata,
|
|
59
75
|
clientId: V.text(1024),
|
|
60
76
|
...V.RequestOptions.fields,
|
|
77
|
+
algorithms: Schema.optionalKey(
|
|
78
|
+
Schema.Array(IdTokenAlgorithm).check(Schema.isMinLength(1), Schema.isMaxLength(4)),
|
|
79
|
+
),
|
|
80
|
+
pkceS256: Schema.optionalKey(Schema.Boolean),
|
|
61
81
|
});
|
|
62
82
|
|
|
63
83
|
const VerifyInput = Schema.Struct({
|
|
@@ -84,11 +104,23 @@ const IdClaims = Schema.Struct({
|
|
|
84
104
|
nbf: Schema.optionalKey(V.NumericDate),
|
|
85
105
|
auth_time: Schema.optionalKey(V.NumericDate),
|
|
86
106
|
nonce: Schema.optionalKey(V.text(256)),
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
// Leftmost half of the signing-algorithm hash, unpadded base64url: 22
|
|
108
|
+
// characters for SHA-256 (RS256, PS256, ES256) and 43 for SHA-512 (Ed25519).
|
|
109
|
+
at_hash: Schema.optionalKey(
|
|
110
|
+
Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}(?:[A-Za-z0-9_-]{21})?$/u)),
|
|
111
|
+
),
|
|
112
|
+
c_hash: Schema.optionalKey(
|
|
113
|
+
Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}(?:[A-Za-z0-9_-]{21})?$/u)),
|
|
114
|
+
),
|
|
90
115
|
});
|
|
91
116
|
|
|
117
|
+
const tokenHash = (algorithm: string) =>
|
|
118
|
+
algorithm === "EdDSA"
|
|
119
|
+
? { digest: "SHA-512" as const, octets: 32 }
|
|
120
|
+
: algorithm === "RS256" || algorithm === "PS256" || algorithm === "ES256"
|
|
121
|
+
? { digest: "SHA-256" as const, octets: 16 }
|
|
122
|
+
: undefined;
|
|
123
|
+
|
|
92
124
|
/** OIDC discovery uses the trusted issuer identifier, retaining its exact
|
|
93
125
|
* spelling for claim comparison even if its network URL normalizes. */
|
|
94
126
|
export const discover = Effect.fnUntraced(function* (
|
|
@@ -121,10 +153,11 @@ export const discover = Effect.fnUntraced(function* (
|
|
|
121
153
|
|
|
122
154
|
const claimFailure = () => Rejected.make({ reason: "claims" });
|
|
123
155
|
|
|
124
|
-
/**
|
|
125
|
-
* to the caller's Scope. Owner closure cancels
|
|
126
|
-
* returning Unavailable; caller interruption joins
|
|
127
|
-
* the verifier. No process cache or automatic
|
|
156
|
+
/** Signed ID tokens using advertised RS256, PS256, ES256 or EdDSA. Verification
|
|
157
|
+
* and the bounded JWKS cache belong to the caller's Scope. Owner closure cancels
|
|
158
|
+
* and joins active verification, returning Unavailable; caller interruption joins
|
|
159
|
+
* its operation without closing the verifier. No process cache or automatic
|
|
160
|
+
* exchange retry is created. */
|
|
128
161
|
export const makeVerifier = Effect.fnUntraced(function* (
|
|
129
162
|
input: VerifierOptions,
|
|
130
163
|
): Effect.fn.Return<
|
|
@@ -136,11 +169,19 @@ export const makeVerifier = Effect.fnUntraced(function* (
|
|
|
136
169
|
|
|
137
170
|
const metadata = V.freeze(options.metadata);
|
|
138
171
|
|
|
172
|
+
const algorithms = defaultIdTokenAlgorithms.filter((algorithm) =>
|
|
173
|
+
(options.algorithms ?? defaultIdTokenAlgorithms).includes(algorithm),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const advertised = algorithms.filter((algorithm) =>
|
|
177
|
+
metadata.id_token_signing_alg_values_supported?.includes(algorithm),
|
|
178
|
+
);
|
|
179
|
+
|
|
139
180
|
if (
|
|
140
181
|
metadata.jwks_uri === undefined ||
|
|
141
|
-
|
|
182
|
+
advertised.length === 0 ||
|
|
142
183
|
!metadata.response_types_supported?.includes("code") ||
|
|
143
|
-
!metadata.
|
|
184
|
+
((options.pkceS256 ?? true) && !metadata.code_challenge_methods_supported?.includes("S256"))
|
|
144
185
|
)
|
|
145
186
|
return yield* ConfigurationError.make({ reason: "metadata" });
|
|
146
187
|
const http = yield* Transport.capture;
|
|
@@ -173,7 +214,7 @@ export const makeVerifier = Effect.fnUntraced(function* (
|
|
|
173
214
|
// Jwt verifies the signature before any registered/application claim checks.
|
|
174
215
|
// Unauthenticated malformed/key/signature failures must never burn a receipt.
|
|
175
216
|
const verified = yield* Jwt.verifyWithKeySet(JsonObject, token, {
|
|
176
|
-
algorithms:
|
|
217
|
+
algorithms: advertised,
|
|
177
218
|
issuer: metadata.issuer,
|
|
178
219
|
audience: options.clientId,
|
|
179
220
|
requiredClaims: ["iss", "sub", "aud", "exp", "iat"],
|
|
@@ -213,6 +254,10 @@ export const makeVerifier = Effect.fnUntraced(function* (
|
|
|
213
254
|
(claims.auth_time !== undefined && claims.auth_time !== policy.previous.authTime)
|
|
214
255
|
)
|
|
215
256
|
return yield* claimFailure();
|
|
257
|
+
const hashProfile = tokenHash(verified.protectedHeader.alg);
|
|
258
|
+
|
|
259
|
+
if (hashProfile === undefined) return yield* claimFailure();
|
|
260
|
+
|
|
216
261
|
for (const [hash, secret] of [
|
|
217
262
|
[claims.at_hash, accessToken],
|
|
218
263
|
[claims.c_hash, code],
|
|
@@ -222,11 +267,12 @@ export const makeVerifier = Effect.fnUntraced(function* (
|
|
|
222
267
|
const crypto = yield* Crypto.Crypto;
|
|
223
268
|
|
|
224
269
|
const digest = yield* crypto
|
|
225
|
-
.digest(
|
|
270
|
+
.digest(hashProfile.digest, new TextEncoder().encode(secret))
|
|
226
271
|
.pipe(Effect.mapError(() => Unavailable.make({})));
|
|
227
272
|
|
|
228
|
-
if (digest.length !==
|
|
229
|
-
if (hash !== Base64Url.encode(digest.subarray(0,
|
|
273
|
+
if (digest.length !== hashProfile.octets * 2) return yield* Unavailable.make({});
|
|
274
|
+
if (hash !== Base64Url.encode(digest.subarray(0, hashProfile.octets)))
|
|
275
|
+
return yield* claimFailure();
|
|
230
276
|
}
|
|
231
277
|
// Re-read time after optional digest I/O so expired tokens cannot escape.
|
|
232
278
|
const finished = (yield* Clock.currentTimeMillis) / 1000;
|