@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 CHANGED
@@ -1,60 +1,33 @@
1
1
  # @yielded/oauth
2
2
 
3
- Native Effect OAuth authorization-code and refresh flows, PKCE S256, and signed
4
- RS256 OpenID Connect. Runtime dependencies are Effect and `@yielded/jose`.
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` and Effect `Crypto`. Runtime Layers belong
10
- to the application. `Oidc.discover` retrieves metadata from the exact trusted
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
- Inspect `Redacted.value(receipt.body)` for provider-specific receipt rules, then
19
- call `OAuth.tokens`. Its tokens remain redacted. Raw expiry extensions and scope
20
- strings are retained unchanged, including provider-specific comma scopes.
21
- `OAuth.tokens` accepts `expires_in` as a finite nonnegative number or a whole
22
- decimal string such as `"60.5"`; whitespace, numeric-prefix junk, exponent syntax,
23
- `NaN` and infinity are rejected. Provider checks can still require a raw number.
24
- A returned receipt has not established grant success or an authenticated identity.
25
- For OIDC, require an ID token on initial exchange and call `verifier.verify` with
26
- the captured `verificationStartedAt`, nonce, access token and authorization code.
27
- It verifies the signature before claims, checks present `at_hash`/`c_hash`, and
28
- returns redacted verified claims for application-owned identity/profile mapping.
29
- On refresh, verify any returned ID token with `previous` subject/authentication
30
- time; an omitted refresh ID token is an application-owned continuation.
31
-
32
- The application owns registered callbacks, expected state and response issuer,
33
- provider generations, permissions, identity callbacks, grant retention and durable
34
- receipts. This package makes one request per exchange, refresh or revocation.
35
- `OAuthRejected` identifies a complete HTTP 400 `invalid_grant` or an authenticated
36
- claim rejection. Malformed responses and expected transport, key and signature
37
- failures are `OAuthUnavailable`; they do not authorize repeating a possibly
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: string;
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: Redacted.Redacted<string>;
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$1 = yield* authenticated(new URL(metadata.token_endpoint).href, options.clientId, authentication, body);
187
- return yield* json(yield* request(http, request$1, options));
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
- body.set("code_challenge", value.codeChallenge);
203
- body.set("code_challenge_method", "S256");
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$2 = HttpClientRequest.get(new URL(profile.url).href).pipe(HttpClientRequest.acceptJson, HttpClientRequest.setHeaders(profile.headers ?? {}), HttpClientRequest.setHeader("authorization", `Bearer ${token}`));
234
- const response = yield* request(http, request$2, options);
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),
@@ -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":"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"}
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
- /** RS256-only signed ID tokens. Verification and the bounded JWKS cache belong
52
- * to the caller's Scope. Owner closure cancels and joins active verification,
53
- * returning Unavailable; caller interruption joins its operation without closing
54
- * the verifier. No process cache or automatic exchange retry is created. */
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}$/u))),
39
- c_hash: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}$/u)))
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
- /** RS256-only signed ID tokens. Verification and the bounded JWKS cache belong
58
- * to the caller's Scope. Owner closure cancels and joins active verification,
59
- * returning Unavailable; caller interruption joins its operation without closing
60
- * the verifier. No process cache or automatic exchange retry is created. */
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
- if (metadata.jwks_uri === void 0 || !metadata.code_challenge_methods_supported?.includes("S256") || !metadata.response_types_supported?.includes("code") || !metadata.id_token_signing_alg_values_supported?.includes("RS256")) return yield* ConfigurationError.make({ reason: "metadata" });
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: ["RS256"],
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("SHA-256", new TextEncoder().encode(secret)).pipe(Effect.mapError(() => Unavailable.make({})));
102
- if (digest.length !== 32) return yield* Unavailable.make({});
103
- if (hash !== Base64Url.encode(digest.subarray(0, 16))) return yield* claimFailure();
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.30","dependencies":{"@yielded/jose":"0.1.0-beta.30"},"devDependencies":{"@effect/platform-node":"4.0.0","@effect/vitest":"4.0.0","@types/node":"26.1.2","@yielded/crypto":"0.1.0-beta.30","effect":"4.0.0","jose":"6.2.12","typescript":"7.0.2","vite-plus":"0.3.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"}}
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: string;
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: Redacted.Redacted<string>;
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
- body.set("code_challenge", value.codeChallenge);
375
- body.set("code_challenge_method", "S256");
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
- body.set("code_verifier", yield* V.reveal(value.pkceVerifier));
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
- // The RS256 half digest is 128 bits (22 base64url characters).
88
- at_hash: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}$/u))),
89
- c_hash: Schema.optionalKey(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{22}$/u))),
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
- /** RS256-only signed ID tokens. Verification and the bounded JWKS cache belong
125
- * to the caller's Scope. Owner closure cancels and joins active verification,
126
- * returning Unavailable; caller interruption joins its operation without closing
127
- * the verifier. No process cache or automatic exchange retry is created. */
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
- !metadata.code_challenge_methods_supported?.includes("S256") ||
182
+ advertised.length === 0 ||
142
183
  !metadata.response_types_supported?.includes("code") ||
143
- !metadata.id_token_signing_alg_values_supported?.includes("RS256")
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: ["RS256"],
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("SHA-256", new TextEncoder().encode(secret))
270
+ .digest(hashProfile.digest, new TextEncoder().encode(secret))
226
271
  .pipe(Effect.mapError(() => Unavailable.make({})));
227
272
 
228
- if (digest.length !== 32) return yield* Unavailable.make({});
229
- if (hash !== Base64Url.encode(digest.subarray(0, 16))) return yield* claimFailure();
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;