@yielded/oauth 0.1.0-beta.19

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.
@@ -0,0 +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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"}
@@ -0,0 +1,57 @@
1
+ import { ConfigurationError, Rejected, Unavailable } from "./Errors.mjs";
2
+ import { JsonObject, Metadata, RequestOptions } from "./OAuth.mjs";
3
+ import { Crypto, DateTime, Effect, Redacted, Scope } from "effect";
4
+ import { HttpClient } from "effect/http";
5
+ import * as Jwks from "@yielded/jose/Jwks";
6
+ import * as Jws from "@yielded/jose/Jws";
7
+ //#region src/Oidc.d.ts
8
+ export interface VerifierOptions extends RequestOptions {
9
+ readonly metadata: Metadata;
10
+ readonly clientId: string;
11
+ }
12
+ export interface VerificationInput {
13
+ readonly verificationStartedAt: DateTime.Utc;
14
+ readonly nonce: Redacted.Redacted<string>;
15
+ readonly maxAgeSeconds?: number;
16
+ readonly accessToken?: Redacted.Redacted<string>;
17
+ readonly code?: Redacted.Redacted<string>;
18
+ /** Refresh continuation: same subject, optional nonce must match, auth_time cannot change. */
19
+ readonly previous?: {
20
+ readonly subject: string;
21
+ readonly authTime?: number;
22
+ };
23
+ }
24
+ export interface Verified {
25
+ readonly claims: Redacted.Redacted<JsonObject>;
26
+ readonly subject: string;
27
+ readonly authTime?: number;
28
+ readonly upstreamAuthenticatedAt?: DateTime.Utc;
29
+ }
30
+ type VerificationRequirements = Exclude<Effect.Services<ReturnType<typeof Jws.verifyWithKeySet>>, Jwks.Jwks> | Crypto.Crypto;
31
+ export interface Verifier {
32
+ readonly verify: (token: Redacted.Redacted<string>, input: VerificationInput) => Effect.Effect<Verified, Rejected | Unavailable, VerificationRequirements>;
33
+ }
34
+ /** OIDC discovery uses the trusted issuer identifier, retaining its exact
35
+ * spelling for claim comparison even if its network URL normalizes. */
36
+ export declare const discover: (input: string, inputOptions: RequestOptions) => Effect.Effect<{
37
+ readonly issuer: string;
38
+ readonly authorization_endpoint: string;
39
+ readonly token_endpoint: string;
40
+ readonly jwks_uri?: string;
41
+ readonly userinfo_endpoint?: string;
42
+ readonly revocation_endpoint?: string;
43
+ readonly code_challenge_methods_supported?: readonly string[];
44
+ readonly response_types_supported?: readonly string[];
45
+ readonly grant_types_supported?: readonly string[];
46
+ readonly id_token_signing_alg_values_supported?: readonly string[];
47
+ readonly token_endpoint_auth_methods_supported?: readonly string[];
48
+ readonly revocation_endpoint_auth_methods_supported?: readonly string[];
49
+ readonly authorization_response_iss_parameter_supported?: boolean;
50
+ }, 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. */
55
+ export declare const makeVerifier: (input: VerifierOptions) => Effect.Effect<Verifier, ConfigurationError | Unavailable, HttpClient.HttpClient | Scope.Scope>;
56
+ //#endregion
57
+ //# sourceMappingURL=Oidc.d.mts.map
package/dist/Oidc.mjs ADDED
@@ -0,0 +1,120 @@
1
+ import { ConfigurationError, Rejected, Unavailable } from "./Errors.mjs";
2
+ import { make } from "./internal/ownership.mjs";
3
+ import { Issuer, NumericDate, RequestOptions, configuration, decode, freeze, integer, reveal, secret, text } from "./internal/validation.mjs";
4
+ import { capture, json, request } from "./internal/transport.mjs";
5
+ import { JsonObject, Metadata } from "./OAuth.mjs";
6
+ import { Clock, Context, Crypto, DateTime, Effect, Layer, Redacted, Schema } from "effect";
7
+ import { Base64Url } from "effect/encoding";
8
+ import { HttpClient, HttpClientRequest } from "effect/http";
9
+ import * as Jwks from "@yielded/jose/Jwks";
10
+ import * as Jwt from "@yielded/jose/Jwt";
11
+ //#region src/Oidc.ts
12
+ const Configuration = Schema.Struct({
13
+ metadata: Metadata,
14
+ clientId: text(1024),
15
+ ...RequestOptions.fields
16
+ });
17
+ const VerifyInput = Schema.Struct({
18
+ verificationStartedAt: Schema.DateTimeUtc,
19
+ nonce: secret(256),
20
+ maxAgeSeconds: Schema.optional(integer(0, 86400)),
21
+ accessToken: Schema.optional(secret(16384)),
22
+ code: Schema.optional(secret(16384)),
23
+ previous: Schema.optional(Schema.Struct({
24
+ subject: text(1024),
25
+ authTime: Schema.optional(NumericDate)
26
+ }))
27
+ });
28
+ const IdClaims = Schema.Struct({
29
+ iss: text(2048),
30
+ sub: text(1024),
31
+ aud: Schema.Union([text(1024), Schema.Array(text(1024)).check(Schema.isMinLength(1), Schema.isMaxLength(1))]),
32
+ azp: Schema.optionalKey(text(1024)),
33
+ exp: NumericDate,
34
+ iat: NumericDate,
35
+ nbf: Schema.optionalKey(NumericDate),
36
+ auth_time: Schema.optionalKey(NumericDate),
37
+ 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)))
40
+ });
41
+ /** OIDC discovery uses the trusted issuer identifier, retaining its exact
42
+ * spelling for claim comparison even if its network URL normalizes. */
43
+ const discover = Effect.fnUntraced(function* (input, inputOptions) {
44
+ const issuer = yield* configuration(Issuer, input, "issuer");
45
+ const options = yield* configuration(RequestOptions, inputOptions);
46
+ const url = new URL(issuer);
47
+ url.pathname = `${url.pathname.replace(/\/$/u, "")}/.well-known/openid-configuration`;
48
+ const http = yield* capture;
49
+ const response = yield* request(http, HttpClientRequest.get(url.href).pipe(HttpClientRequest.acceptJson), options);
50
+ if (response.status !== 200) return yield* Unavailable.make({});
51
+ const document = yield* json(response);
52
+ const metadata = yield* configuration(Metadata, yield* reveal(document.body), "metadata");
53
+ if (metadata.issuer !== issuer) return yield* ConfigurationError.make({ reason: "issuer" });
54
+ return freeze(metadata);
55
+ });
56
+ 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. */
61
+ const makeVerifier = Effect.fnUntraced(function* (input) {
62
+ const options = yield* configuration(Configuration, input, "metadata");
63
+ 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" });
65
+ const http = yield* capture;
66
+ const context = yield* Layer.build(Jwks.layerRemote({
67
+ url: metadata.jwks_uri,
68
+ timeoutMs: options.timeoutMs,
69
+ maxResponseBytes: options.maxResponseBytes
70
+ }).pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, http)))).pipe(Effect.mapError(() => Unavailable.make({})));
71
+ const keys = Context.get(context, Jwks.Jwks);
72
+ const { use } = yield* make;
73
+ return { verify: Effect.fnUntraced(function* (token, input) {
74
+ const policy = yield* decode(VerifyInput, input);
75
+ const start = DateTime.toEpochMillis(policy.verificationStartedAt);
76
+ const expectedNonce = yield* reveal(policy.nonce);
77
+ const accessToken = policy.accessToken === void 0 ? void 0 : yield* reveal(policy.accessToken);
78
+ const code = policy.code === void 0 ? void 0 : yield* reveal(policy.code);
79
+ const verified = yield* Jwt.verifyWithKeySet(JsonObject, token, {
80
+ algorithms: ["RS256"],
81
+ issuer: metadata.issuer,
82
+ audience: options.clientId,
83
+ requiredClaims: [
84
+ "iss",
85
+ "sub",
86
+ "aud",
87
+ "exp",
88
+ "iat"
89
+ ],
90
+ clockTolerance: 0
91
+ }).pipe(Effect.provideService(Jwks.Jwks, keys), Effect.mapError((error) => error._tag === "JoseClaimValidationFailed" ? claimFailure() : Unavailable.make({})));
92
+ const claims = yield* decode(IdClaims, verified.claims).pipe(Effect.mapError(claimFailure));
93
+ const now = (yield* Clock.currentTimeMillis) / 1e3;
94
+ if (start < 0 || start > now * 1e3 || claims.azp !== void 0 && claims.azp !== options.clientId || claims.exp <= now || claims.iat > now || claims.nbf !== void 0 && claims.nbf > now || claims.auth_time !== void 0 && claims.auth_time > now) return yield* claimFailure();
95
+ if (policy.previous === void 0) {
96
+ 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
+ } 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();
98
+ for (const [hash, secret] of [[claims.at_hash, accessToken], [claims.c_hash, code]]) {
99
+ if (hash === void 0) continue;
100
+ 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();
104
+ }
105
+ const finished = (yield* Clock.currentTimeMillis) / 1e3;
106
+ 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();
107
+ return {
108
+ claims: Redacted.make(freeze(verified.claims)),
109
+ subject: claims.sub,
110
+ ...claims.auth_time === void 0 ? {} : {
111
+ authTime: claims.auth_time,
112
+ upstreamAuthenticatedAt: DateTime.makeUnsafe(Math.min(start, claims.auth_time * 1e3))
113
+ }
114
+ };
115
+ }, use) };
116
+ });
117
+ //#endregion
118
+ export { discover, makeVerifier };
119
+
120
+ //# sourceMappingURL=Oidc.mjs.map
@@ -0,0 +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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"}
@@ -0,0 +1,12 @@
1
+ import { Unavailable } from "./Errors.mjs";
2
+ import { Crypto, Effect, Redacted } from "effect";
3
+ //#region src/Pkce.d.ts
4
+ /** A fresh 256-bit state, nonce, or PKCE verifier; application policy owns storage. */
5
+ export declare const random: () => Effect.Effect<Redacted.Redacted<string>, Unavailable, Crypto.Crypto>;
6
+ export declare const challenge: (input: Redacted.Redacted<string>) => Effect.Effect<string, Unavailable, Crypto.Crypto>;
7
+ export declare const make: () => Effect.Effect<{
8
+ verifier: Redacted.Redacted<string>;
9
+ challenge: string;
10
+ }, Unavailable, Crypto.Crypto>;
11
+ //#endregion
12
+ //# sourceMappingURL=Pkce.d.mts.map
package/dist/Pkce.mjs ADDED
@@ -0,0 +1,28 @@
1
+ import { Unavailable } from "./Errors.mjs";
2
+ import { Verifier, decode, reveal } from "./internal/validation.mjs";
3
+ import { Crypto, Effect, Redacted } from "effect";
4
+ import { Base64Url } from "effect/encoding";
5
+ //#region src/Pkce.ts
6
+ /** A fresh 256-bit state, nonce, or PKCE verifier; application policy owns storage. */
7
+ const random = Effect.fnUntraced(function* () {
8
+ const bytes = yield* (yield* Crypto.Crypto).randomBytes(32);
9
+ if (bytes.length !== 32) return yield* Unavailable.make({});
10
+ return Redacted.make(Base64Url.encode(bytes));
11
+ }, Effect.mapError(() => Unavailable.make({})));
12
+ const challenge = Effect.fnUntraced(function* (input) {
13
+ const verifier = yield* reveal(yield* decode(Verifier, input));
14
+ const digest = yield* (yield* Crypto.Crypto).digest("SHA-256", new TextEncoder().encode(verifier));
15
+ if (digest.length !== 32) return yield* Unavailable.make({});
16
+ return Base64Url.encode(digest);
17
+ }, Effect.mapError(() => Unavailable.make({})));
18
+ const make = Effect.fnUntraced(function* () {
19
+ const verifier = yield* random();
20
+ return {
21
+ verifier,
22
+ challenge: yield* challenge(verifier)
23
+ };
24
+ });
25
+ //#endregion
26
+ export { challenge, make, random };
27
+
28
+ //# sourceMappingURL=Pkce.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pkce.mjs","names":["V.reveal","V.decode","V.Verifier"],"sources":["../src/Pkce.ts"],"sourcesContent":["import { Crypto, Effect, Redacted } from \"effect\";\nimport { Base64Url } from \"effect/encoding\";\n\nimport { Unavailable } from \"./Errors\";\nimport * as V from \"./internal/validation\";\n\n/** A fresh 256-bit state, nonce, or PKCE verifier; application policy owns storage. */\nexport const random = Effect.fnUntraced(\n function* () {\n const crypto = yield* Crypto.Crypto;\n const bytes = yield* crypto.randomBytes(32);\n\n if (bytes.length !== 32) return yield* Unavailable.make({});\n\n return Redacted.make(Base64Url.encode(bytes));\n },\n Effect.mapError(() => Unavailable.make({})),\n);\n\nexport const challenge = Effect.fnUntraced(\n function* (input: Redacted.Redacted<string>) {\n const verifier = yield* V.reveal(yield* V.decode(V.Verifier, input));\n const crypto = yield* Crypto.Crypto;\n const digest = yield* crypto.digest(\"SHA-256\", new TextEncoder().encode(verifier));\n\n if (digest.length !== 32) return yield* Unavailable.make({});\n\n return Base64Url.encode(digest);\n },\n Effect.mapError(() => Unavailable.make({})),\n);\n\nexport const make = Effect.fnUntraced(function* () {\n const verifier = yield* random();\n\n return { verifier, challenge: yield* challenge(verifier) };\n});\n"],"mappings":";;;;;;AAOA,MAAa,SAAS,OAAO,WAC3B,aAAa;CAEX,MAAM,QAAQ,QAAO,OADC,OAAO,OAAA,CACD,YAAY,EAAE;CAE1C,IAAI,MAAM,WAAW,IAAI,OAAO,OAAO,YAAY,KAAK,CAAC,CAAC;CAE1D,OAAO,SAAS,KAAK,UAAU,OAAO,KAAK,CAAC;AAC9C,GACA,OAAO,eAAe,YAAY,KAAK,CAAC,CAAC,CAAC,CAC5C;AAEA,MAAa,YAAY,OAAO,WAC9B,WAAW,OAAkC;CAC3C,MAAM,WAAW,OAAOA,OAAS,OAAOC,OAASC,UAAY,KAAK,CAAC;CAEnE,MAAM,SAAS,QAAO,OADA,OAAO,OAAA,CACA,OAAO,WAAW,IAAI,YAAY,CAAC,CAAC,OAAO,QAAQ,CAAC;CAEjF,IAAI,OAAO,WAAW,IAAI,OAAO,OAAO,YAAY,KAAK,CAAC,CAAC;CAE3D,OAAO,UAAU,OAAO,MAAM;AAChC,GACA,OAAO,eAAe,YAAY,KAAK,CAAC,CAAC,CAAC,CAC5C;AAEA,MAAa,OAAO,OAAO,WAAW,aAAa;CACjD,MAAM,WAAW,OAAO,OAAO;CAE/B,OAAO;EAAE;EAAU,WAAW,OAAO,UAAU,QAAQ;CAAE;AAC3D,CAAC"}
@@ -0,0 +1,5 @@
1
+ import * as Errors from "./Errors.mjs";
2
+ import * as OAuth from "./OAuth.mjs";
3
+ import * as Oidc from "./Oidc.mjs";
4
+ import * as Pkce from "./Pkce.mjs";
5
+ export { Errors, OAuth, Oidc, Pkce };
package/dist/index.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import * as Errors from "./Errors.mjs";
2
+ import * as OAuth from "./OAuth.mjs";
3
+ import * as Oidc from "./Oidc.mjs";
4
+ import * as Pkce from "./Pkce.mjs";
5
+ export { Errors, OAuth, Oidc, Pkce };
@@ -0,0 +1,21 @@
1
+ import { Unavailable } from "../Errors.mjs";
2
+ import { Effect, Fiber } from "effect";
3
+ //#region src/internal/ownership.ts
4
+ /** Each operation belongs to the factory's Scope and is joined by its caller. */
5
+ const make = Effect.gen(function* () {
6
+ const scope = yield* Effect.scope;
7
+ const available = Effect.suspend(() => scope.state._tag === "Closed" ? Effect.fail(Unavailable.make({})) : Effect.void);
8
+ const use = (effect) => Effect.acquireUseRelease(available.pipe(Effect.andThen(Effect.forkIn(effect, scope, { uninterruptible: false }))), (fiber) => Effect.gen(function* () {
9
+ const exit = yield* Fiber.await(fiber);
10
+ yield* available;
11
+ return yield* exit;
12
+ }), Fiber.interrupt);
13
+ return {
14
+ available,
15
+ use
16
+ };
17
+ });
18
+ //#endregion
19
+ export { make };
20
+
21
+ //# sourceMappingURL=ownership.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ownership.mjs","names":[],"sources":["../../src/internal/ownership.ts"],"sourcesContent":["import { Effect, Fiber } from \"effect\";\n\nimport { Unavailable } from \"../Errors\";\n\n/** Each operation belongs to the factory's Scope and is joined by its caller. */\nexport const make = Effect.gen(function* () {\n const scope = yield* Effect.scope;\n\n const available = Effect.suspend(() =>\n scope.state._tag === \"Closed\" ? Effect.fail(Unavailable.make({})) : Effect.void,\n );\n\n // Inspect the exit only after checking the owner: closure must stay unavailable,\n // even when an interrupted operation was about to return a definite rejection.\n const use = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n Effect.acquireUseRelease(\n available.pipe(Effect.andThen(Effect.forkIn(effect, scope, { uninterruptible: false }))),\n (fiber) =>\n Effect.gen(function* () {\n const exit = yield* Fiber.await(fiber);\n\n yield* available;\n\n return yield* exit;\n }),\n Fiber.interrupt,\n );\n\n return { available, use };\n});\n"],"mappings":";;;;AAKA,MAAa,OAAO,OAAO,IAAI,aAAa;CAC1C,MAAM,QAAQ,OAAO,OAAO;CAE5B,MAAM,YAAY,OAAO,cACvB,MAAM,MAAM,SAAS,WAAW,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,IAAI,OAAO,IAC7E;CAIA,MAAM,OAAgB,WACpB,OAAO,kBACL,UAAU,KAAK,OAAO,QAAQ,OAAO,OAAO,QAAQ,OAAO,EAAE,iBAAiB,MAAM,CAAC,CAAC,CAAC,IACtF,UACC,OAAO,IAAI,aAAa;EACtB,MAAM,OAAO,OAAO,MAAM,MAAM,KAAK;EAErC,OAAO;EAEP,OAAO,OAAO;CAChB,CAAC,GACH,MAAM,SACR;CAEF,OAAO;EAAE;EAAW;CAAI;AAC1B,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { Unavailable } from "../Errors.mjs";
2
+ import { JsonObject, decode, freeze, reveal } from "./validation.mjs";
3
+ import { Effect, Redacted, Schema, Stream, Tracer } from "effect";
4
+ import { FetchHttpClient, HttpClient } from "effect/http";
5
+ //#region src/internal/transport.ts
6
+ /** Caller supplies a nonretrying, nonredirecting HttpClient. Each body and socket
7
+ * belongs to this request's Scope; cancellation reaches the entire body read. */
8
+ const request = Effect.fnUntraced(function* (client, request, options) {
9
+ const response = yield* HttpClient.withScope(client).execute(request);
10
+ if (response.url !== request.url || response.status >= 300 && response.status < 400) return yield* Unavailable.make({});
11
+ const announced = response.headers["content-length"];
12
+ if (announced !== void 0 && (!/^\d+$/u.test(announced) || Number(announced) > options.maxResponseBytes)) return yield* Unavailable.make({});
13
+ const buffer = new Uint8Array(options.maxResponseBytes);
14
+ const length = yield* Stream.runFoldEffect(response.stream, () => 0, (length, chunk) => {
15
+ if (chunk.byteLength > buffer.length - length) return Effect.fail(Unavailable.make({}));
16
+ buffer.set(chunk, length);
17
+ return Effect.succeed(length + chunk.byteLength);
18
+ }).pipe(Effect.catchReason("HttpClientError", "EmptyBodyError", () => Effect.succeed(0)));
19
+ return {
20
+ status: response.status,
21
+ contentType: response.headers["content-type"] ?? null,
22
+ bytes: Redacted.make(buffer.subarray(0, length))
23
+ };
24
+ }, (effect, _client, _request, options) => effect.pipe(Effect.scoped, Effect.provideService(FetchHttpClient.RequestInit, {
25
+ redirect: "error",
26
+ credentials: "omit",
27
+ cache: "no-store"
28
+ }), Effect.provideService(Tracer.DisablePropagation, true), Effect.mapError(() => Unavailable.make({})), Effect.timeoutOrElse({
29
+ duration: options.timeoutMs,
30
+ orElse: () => Unavailable.make({})
31
+ })));
32
+ const isJson = (contentType) => contentType?.split(";", 1)[0]?.trim().toLowerCase() === "application/json";
33
+ const json = Effect.fnUntraced(function* (response) {
34
+ if (!isJson(response.contentType)) return yield* Unavailable.make({});
35
+ const bytes = yield* reveal(response.bytes);
36
+ const text = yield* Effect.try({
37
+ try: () => new TextDecoder("utf-8", { fatal: true }).decode(bytes),
38
+ catch: () => Unavailable.make({})
39
+ });
40
+ const body = yield* decode(Json, text);
41
+ return {
42
+ status: response.status,
43
+ contentType: response.contentType,
44
+ body: Redacted.make(freeze(body))
45
+ };
46
+ });
47
+ const Json = Schema.fromJsonString(JsonObject);
48
+ /** Capture Fetch's optional reference at the installed-client boundary too. */
49
+ const capture = Effect.gen(function* () {
50
+ const client = yield* HttpClient.HttpClient;
51
+ const fetch = yield* FetchHttpClient.Fetch;
52
+ return HttpClient.transformResponse(client, (effect) => effect.pipe(Effect.provideService(FetchHttpClient.Fetch, fetch), Effect.provideService(Tracer.DisablePropagation, true)));
53
+ });
54
+ //#endregion
55
+ export { capture, isJson, json, request };
56
+
57
+ //# sourceMappingURL=transport.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.mjs","names":["V.reveal","V.decode","V.freeze","V.JsonObject"],"sources":["../../src/internal/transport.ts"],"sourcesContent":["import { Effect, Redacted, Schema, Stream, Tracer } from \"effect\";\nimport { FetchHttpClient, HttpClient, type HttpClientRequest } from \"effect/http\";\n\nimport { Unavailable } from \"../Errors\";\nimport * as V from \"./validation\";\n\nexport interface Options {\n readonly timeoutMs: number;\n readonly maxResponseBytes: number;\n}\n\n/** Caller supplies a nonretrying, nonredirecting HttpClient. Each body and socket\n * belongs to this request's Scope; cancellation reaches the entire body read. */\nexport const request = Effect.fnUntraced(\n function* (\n client: HttpClient.HttpClient,\n request: HttpClientRequest.HttpClientRequest,\n options: Options,\n ) {\n const response = yield* HttpClient.withScope(client).execute(request);\n\n if (response.url !== request.url || (response.status >= 300 && response.status < 400))\n return yield* Unavailable.make({});\n const announced = response.headers[\"content-length\"];\n\n if (\n announced !== undefined &&\n (!/^\\d+$/u.test(announced) || Number(announced) > options.maxResponseBytes)\n )\n return yield* Unavailable.make({});\n const buffer = new Uint8Array(options.maxResponseBytes);\n\n const length = yield* Stream.runFoldEffect(\n response.stream,\n () => 0,\n (length, chunk) => {\n if (chunk.byteLength > buffer.length - length) return Effect.fail(Unavailable.make({}));\n buffer.set(chunk, length);\n\n return Effect.succeed(length + chunk.byteLength);\n },\n ).pipe(Effect.catchReason(\"HttpClientError\", \"EmptyBodyError\", () => Effect.succeed(0)));\n\n return {\n status: response.status,\n contentType: response.headers[\"content-type\"] ?? null,\n bytes: Redacted.make(buffer.subarray(0, length)),\n };\n },\n (effect, _client, _request, options) =>\n effect.pipe(\n Effect.scoped,\n Effect.provideService(FetchHttpClient.RequestInit, {\n redirect: \"error\",\n credentials: \"omit\",\n cache: \"no-store\",\n }),\n Effect.provideService(Tracer.DisablePropagation, true),\n Effect.mapError(() => Unavailable.make({})),\n Effect.timeoutOrElse({ duration: options.timeoutMs, orElse: () => Unavailable.make({}) }),\n ),\n);\n\nexport const isJson = (contentType: string | null) =>\n contentType?.split(\";\", 1)[0]?.trim().toLowerCase() === \"application/json\";\n\nexport const json = Effect.fnUntraced(function* (\n response: Effect.Success<ReturnType<typeof request>>,\n) {\n if (!isJson(response.contentType)) return yield* Unavailable.make({});\n const bytes = yield* V.reveal(response.bytes);\n\n const text = yield* Effect.try({\n try: () => new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes),\n catch: () => Unavailable.make({}),\n });\n\n const body = yield* V.decode(Json, text);\n\n return {\n status: response.status,\n contentType: response.contentType,\n body: Redacted.make(V.freeze(body)),\n };\n});\n\nconst Json = Schema.fromJsonString(V.JsonObject);\n\n/** Capture Fetch's optional reference at the installed-client boundary too. */\nexport const capture = Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient;\n const fetch = yield* FetchHttpClient.Fetch;\n\n return HttpClient.transformResponse(client, (effect) =>\n effect.pipe(\n Effect.provideService(FetchHttpClient.Fetch, fetch),\n Effect.provideService(Tracer.DisablePropagation, true),\n ),\n );\n});\n"],"mappings":";;;;;;;AAaA,MAAa,UAAU,OAAO,WAC5B,WACE,QACA,SACA,SACA;CACA,MAAM,WAAW,OAAO,WAAW,UAAU,MAAM,CAAC,CAAC,QAAQ,OAAO;CAEpE,IAAI,SAAS,QAAQ,QAAQ,OAAQ,SAAS,UAAU,OAAO,SAAS,SAAS,KAC/E,OAAO,OAAO,YAAY,KAAK,CAAC,CAAC;CACnC,MAAM,YAAY,SAAS,QAAQ;CAEnC,IACE,cAAc,KAAA,MACb,CAAC,SAAS,KAAK,SAAS,KAAK,OAAO,SAAS,IAAI,QAAQ,mBAE1D,OAAO,OAAO,YAAY,KAAK,CAAC,CAAC;CACnC,MAAM,SAAS,IAAI,WAAW,QAAQ,gBAAgB;CAEtD,MAAM,SAAS,OAAO,OAAO,cAC3B,SAAS,cACH,IACL,QAAQ,UAAU;EACjB,IAAI,MAAM,aAAa,OAAO,SAAS,QAAQ,OAAO,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC;EACtF,OAAO,IAAI,OAAO,MAAM;EAExB,OAAO,OAAO,QAAQ,SAAS,MAAM,UAAU;CACjD,CACF,CAAC,CAAC,KAAK,OAAO,YAAY,mBAAmB,wBAAwB,OAAO,QAAQ,CAAC,CAAC,CAAC;CAEvF,OAAO;EACL,QAAQ,SAAS;EACjB,aAAa,SAAS,QAAQ,mBAAmB;EACjD,OAAO,SAAS,KAAK,OAAO,SAAS,GAAG,MAAM,CAAC;CACjD;AACF,IACC,QAAQ,SAAS,UAAU,YAC1B,OAAO,KACL,OAAO,QACP,OAAO,eAAe,gBAAgB,aAAa;CACjD,UAAU;CACV,aAAa;CACb,OAAO;AACT,CAAC,GACD,OAAO,eAAe,OAAO,oBAAoB,IAAI,GACrD,OAAO,eAAe,YAAY,KAAK,CAAC,CAAC,CAAC,GAC1C,OAAO,cAAc;CAAE,UAAU,QAAQ;CAAW,cAAc,YAAY,KAAK,CAAC,CAAC;AAAE,CAAC,CAC1F,CACJ;AAEA,MAAa,UAAU,gBACrB,aAAa,MAAM,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,YAAY,MAAM;AAE1D,MAAa,OAAO,OAAO,WAAW,WACpC,UACA;CACA,IAAI,CAAC,OAAO,SAAS,WAAW,GAAG,OAAO,OAAO,YAAY,KAAK,CAAC,CAAC;CACpE,MAAM,QAAQ,OAAOA,OAAS,SAAS,KAAK;CAE5C,MAAM,OAAO,OAAO,OAAO,IAAI;EAC7B,WAAW,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK;EACjE,aAAa,YAAY,KAAK,CAAC,CAAC;CAClC,CAAC;CAED,MAAM,OAAO,OAAOC,OAAS,MAAM,IAAI;CAEvC,OAAO;EACL,QAAQ,SAAS;EACjB,aAAa,SAAS;EACtB,MAAM,SAAS,KAAKC,OAAS,IAAI,CAAC;CACpC;AACF,CAAC;AAED,MAAM,OAAO,OAAO,eAAeC,UAAY;;AAG/C,MAAa,UAAU,OAAO,IAAI,aAAa;CAC7C,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,QAAQ,OAAO,gBAAgB;CAErC,OAAO,WAAW,kBAAkB,SAAS,WAC3C,OAAO,KACL,OAAO,eAAe,gBAAgB,OAAO,KAAK,GAClD,OAAO,eAAe,OAAO,oBAAoB,IAAI,CACvD,CACF;AACF,CAAC"}
@@ -0,0 +1,146 @@
1
+ import { ConfigurationError, Unavailable } from "../Errors.mjs";
2
+ import { Effect, Predicate, Redacted, Schema } from "effect";
3
+ //#region src/internal/validation.ts
4
+ const text = (maximum) => Schema.NonEmptyString.check(Schema.isMaxLength(maximum));
5
+ const integer = (minimum, maximum) => Schema.Int.check(Schema.isBetween({
6
+ minimum,
7
+ maximum
8
+ }));
9
+ const secret = (maximum) => Schema.Redacted(text(maximum), { disallowJsonEncode: true });
10
+ /** At most 64 nested containers and 65,536 values, including the root object. */
11
+ const JsonObject = Schema.Record(Schema.String, Schema.Json).check(Schema.makeFilter((value) => {
12
+ const pending = [{
13
+ value,
14
+ depth: 1
15
+ }];
16
+ let values = 1;
17
+ for (let entry = pending.pop(); entry !== void 0; entry = pending.pop()) {
18
+ const { value, depth } = entry;
19
+ if (depth > 64) return false;
20
+ const children = Object.values(value);
21
+ values += children.length;
22
+ if (values > 65536) return false;
23
+ for (const child of children) if (child !== null && typeof child === "object") pending.push({
24
+ value: child,
25
+ depth: depth + 1
26
+ });
27
+ }
28
+ return true;
29
+ }));
30
+ const validUrl = (value, loopback) => {
31
+ try {
32
+ const url = new URL(value);
33
+ return (url.protocol === "https:" || loopback && url.protocol === "http:" && [
34
+ "localhost",
35
+ "127.0.0.1",
36
+ "[::1]"
37
+ ].includes(url.hostname)) && url.username === "" && url.password === "" && !value.includes("#") && !/[\s\\\u0000-\u001f\u007f]/u.test(value);
38
+ } catch {
39
+ return false;
40
+ }
41
+ };
42
+ const Endpoint = text(2048).check(Schema.makeFilter((value) => validUrl(value, false)));
43
+ const Issuer = Endpoint.check(Schema.makeFilter((value) => !value.includes("?") && !new URL(value).pathname.includes("/.well-known/")));
44
+ const Callback = text(2048).check(Schema.makeFilter((value) => validUrl(value, true) && new URL(value).href === value && !value.includes("?")));
45
+ const ScopeName = Schema.String.check(Schema.isPattern(/^[\x21\x23-\x5b\x5d-\x7e]{1,256}$/));
46
+ const Scopes = Schema.Array(ScopeName).check(Schema.isMaxLength(64), Schema.makeFilter((values) => new Set(values).size === values.length));
47
+ const Resource = text(2048).check(Schema.makeFilter((value) => {
48
+ try {
49
+ const url = new URL(value);
50
+ return url.username === "" && url.password === "" && !/[\s\\#\u0000-\u001f\u007f]/u.test(value);
51
+ } catch {
52
+ return false;
53
+ }
54
+ }));
55
+ const Resources = Schema.Array(Resource).check(Schema.isMaxLength(64));
56
+ const RequestOptions = Schema.Struct({
57
+ timeoutMs: Schema.Finite.check(Schema.isBetween({
58
+ minimum: 1,
59
+ maximum: 6e4
60
+ })),
61
+ maxResponseBytes: integer(1, 1048576).pipe(Schema.withDecodingDefaultKey(Effect.succeed(1048576)))
62
+ });
63
+ const Verifier = Schema.Redacted(Schema.String.check(Schema.isPattern(/^[A-Za-z0-9._~-]{43,128}$/)), { disallowJsonEncode: true });
64
+ const Challenge = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{43}$/));
65
+ const NumericDate = Schema.Finite.check(Schema.isBetween({
66
+ minimum: 0,
67
+ maximum: 864e10
68
+ }));
69
+ const reserved = /* @__PURE__ */ new Set([
70
+ "client_id",
71
+ "client_secret",
72
+ "client_assertion",
73
+ "client_assertion_type",
74
+ "redirect_uri",
75
+ "response_type",
76
+ "response_mode",
77
+ "state",
78
+ "code",
79
+ "code_verifier",
80
+ "code_challenge",
81
+ "code_challenge_method",
82
+ "nonce",
83
+ "scope",
84
+ "iss",
85
+ "grant_type",
86
+ "refresh_token",
87
+ "token",
88
+ "token_type_hint",
89
+ "max_age",
90
+ "request",
91
+ "request_uri",
92
+ "authorization_details",
93
+ "id_token_hint",
94
+ "login_hint",
95
+ "login_hint_token",
96
+ "subject_token",
97
+ "subject_token_type",
98
+ "actor_token",
99
+ "actor_token_type",
100
+ "requested_token_type"
101
+ ]);
102
+ const Parameters = Schema.Record(Schema.String.check(Schema.isPattern(/^[A-Za-z][A-Za-z0-9._~-]{0,63}$/)), Schema.String.check(Schema.isMaxLength(2048))).check(Schema.makeFilter((value) => Object.keys(value).length <= 16 && Object.keys(value).every((key) => !reserved.has(key.toLowerCase()))));
103
+ const forbiddenHeaders = /* @__PURE__ */ new Set([
104
+ "authorization",
105
+ "proxy-authorization",
106
+ "cookie",
107
+ "host",
108
+ "content-length",
109
+ "content-type",
110
+ "transfer-encoding",
111
+ "connection",
112
+ "upgrade",
113
+ "trailer",
114
+ "te",
115
+ "traceparent",
116
+ "tracestate",
117
+ "baggage"
118
+ ]);
119
+ const Headers = Schema.Record(Schema.String.check(Schema.isPattern(/^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,64}$/)), Schema.String.check(Schema.isMaxLength(2048))).check(Schema.makeFilter((value) => {
120
+ const names = Object.keys(value).map((name) => name.toLowerCase());
121
+ return names.length <= 16 && new Set(names).size === names.length && names.every((name) => !forbiddenHeaders.has(name)) && Object.values(value).every((value) => !/[\r\n]/u.test(value));
122
+ }));
123
+ const ProtocolEndpoint = Endpoint.check(Schema.makeFilter((value) => Array.from(new URL(value).searchParams.keys()).every((key) => !reserved.has(key.toLowerCase()))));
124
+ const configuration = (schema, input, reason = "parameters") => Effect.suspend(() => Schema.decodeUnknownEffect(schema)(input, { reportInput: false })).pipe(Effect.mapError(() => ConfigurationError.make({ reason })), Effect.catchDefect(() => ConfigurationError.make({ reason })));
125
+ const decode = (schema, input) => Effect.suspend(() => Schema.decodeUnknownEffect(schema)(input, { reportInput: false })).pipe(Effect.mapError(() => Unavailable.make({})), Effect.catchDefect(() => Unavailable.make({})));
126
+ const reveal = (value) => Effect.try({
127
+ try: () => Redacted.value(value),
128
+ catch: () => Unavailable.make({})
129
+ });
130
+ /** Freeze owned configuration snapshots or freshly parsed wire JSON, not caller-owned graphs. */
131
+ const freeze = (value) => {
132
+ const pending = [value];
133
+ const seen = /* @__PURE__ */ new WeakSet();
134
+ while (pending.length > 0) {
135
+ const item = pending.pop();
136
+ if (!Predicate.isObjectOrArray(item) || Redacted.isRedacted(item) || seen.has(item)) continue;
137
+ seen.add(item);
138
+ for (const child of Object.values(item)) pending.push(child);
139
+ Object.freeze(item);
140
+ }
141
+ return value;
142
+ };
143
+ //#endregion
144
+ export { Callback, Challenge, Endpoint, Headers, Issuer, JsonObject, NumericDate, Parameters, ProtocolEndpoint, RequestOptions, Resource, Resources, ScopeName, Scopes, Verifier, configuration, decode, freeze, integer, reserved, reveal, secret, text };
145
+
146
+ //# sourceMappingURL=validation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.mjs","names":[],"sources":["../../src/internal/validation.ts"],"sourcesContent":["import { Effect, Predicate, Redacted, Schema } from \"effect\";\n\nimport { ConfigurationError, Unavailable } from \"../Errors\";\n\nexport const text = (maximum: number) => Schema.NonEmptyString.check(Schema.isMaxLength(maximum));\n\nexport const integer = (minimum: number, maximum: number) =>\n Schema.Int.check(Schema.isBetween({ minimum, maximum }));\n\nexport const secret = (maximum: number) =>\n Schema.Redacted(text(maximum), { disallowJsonEncode: true });\n\n/** At most 64 nested containers and 65,536 values, including the root object. */\nexport const JsonObject = Schema.Record(Schema.String, Schema.Json).check(\n Schema.makeFilter((value) => {\n const pending: Array<{ value: Schema.JsonObject | Schema.JsonArray; depth: number }> = [\n { value, depth: 1 },\n ];\n\n let values = 1;\n\n for (let entry = pending.pop(); entry !== undefined; entry = pending.pop()) {\n const { value, depth } = entry;\n\n if (depth > 64) return false;\n const children = Object.values(value);\n\n values += children.length;\n if (values > 65536) return false;\n for (const child of children)\n if (child !== null && typeof child === \"object\")\n pending.push({ value: child, depth: depth + 1 });\n }\n\n return true;\n }),\n);\n\nconst validUrl = (value: string, loopback: boolean) => {\n try {\n const url = new URL(value);\n\n return (\n (url.protocol === \"https:\" ||\n (loopback &&\n url.protocol === \"http:\" &&\n [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname))) &&\n url.username === \"\" &&\n url.password === \"\" &&\n !value.includes(\"#\") &&\n // oxlint-disable-next-line no-control-regex -- Reject URL parser normalization of untrusted controls.\n !/[\\s\\\\\\u0000-\\u001f\\u007f]/u.test(value)\n );\n } catch {\n return false;\n }\n};\n\nexport const Endpoint = text(2048).check(Schema.makeFilter((value) => validUrl(value, false)));\n\nexport const Issuer = Endpoint.check(\n Schema.makeFilter(\n (value) => !value.includes(\"?\") && !new URL(value).pathname.includes(\"/.well-known/\"),\n ),\n);\n\nexport const Callback = text(2048).check(\n Schema.makeFilter(\n (value) => validUrl(value, true) && new URL(value).href === value && !value.includes(\"?\"),\n ),\n);\n\n// oxlint-disable-next-line no-control-regex -- RFC6749 scope-token permits these visible ASCII ranges.\nexport const ScopeName = Schema.String.check(Schema.isPattern(/^[\\x21\\x23-\\x5b\\x5d-\\x7e]{1,256}$/));\n\nexport const Scopes = Schema.Array(ScopeName).check(\n Schema.isMaxLength(64),\n Schema.makeFilter((values) => new Set(values).size === values.length),\n);\n\nexport const Resource = text(2048).check(\n Schema.makeFilter((value) => {\n try {\n const url = new URL(value);\n\n return (\n // oxlint-disable-next-line no-control-regex -- Preserve exact resource identifiers.\n url.username === \"\" && url.password === \"\" && !/[\\s\\\\#\\u0000-\\u001f\\u007f]/u.test(value)\n );\n } catch {\n return false;\n }\n }),\n);\n\nexport const Resources = Schema.Array(Resource).check(Schema.isMaxLength(64));\n\nexport const RequestOptions = Schema.Struct({\n timeoutMs: Schema.Finite.check(Schema.isBetween({ minimum: 1, maximum: 60000 })),\n maxResponseBytes: integer(1, 1048576).pipe(\n Schema.withDecodingDefaultKey(Effect.succeed(1048576)),\n ),\n});\n\nexport const Verifier = Schema.Redacted(\n Schema.String.check(Schema.isPattern(/^[A-Za-z0-9._~-]{43,128}$/)),\n { disallowJsonEncode: true },\n);\n\nexport const Challenge = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{43}$/));\n\nexport const NumericDate = Schema.Finite.check(\n Schema.isBetween({ minimum: 0, maximum: 8640000000000 }),\n);\n\nexport const reserved = new Set([\n \"client_id\",\n \"client_secret\",\n \"client_assertion\",\n \"client_assertion_type\",\n \"redirect_uri\",\n \"response_type\",\n \"response_mode\",\n \"state\",\n \"code\",\n \"code_verifier\",\n \"code_challenge\",\n \"code_challenge_method\",\n \"nonce\",\n \"scope\",\n \"iss\",\n \"grant_type\",\n \"refresh_token\",\n \"token\",\n \"token_type_hint\",\n \"max_age\",\n \"request\",\n \"request_uri\",\n \"authorization_details\",\n \"id_token_hint\",\n \"login_hint\",\n \"login_hint_token\",\n \"subject_token\",\n \"subject_token_type\",\n \"actor_token\",\n \"actor_token_type\",\n \"requested_token_type\",\n]);\n\nexport const Parameters = Schema.Record(\n Schema.String.check(Schema.isPattern(/^[A-Za-z][A-Za-z0-9._~-]{0,63}$/)),\n Schema.String.check(Schema.isMaxLength(2048)),\n).check(\n Schema.makeFilter(\n (value) =>\n Object.keys(value).length <= 16 &&\n Object.keys(value).every((key) => !reserved.has(key.toLowerCase())),\n ),\n);\n\nconst forbiddenHeaders = new Set([\n \"authorization\",\n \"proxy-authorization\",\n \"cookie\",\n \"host\",\n \"content-length\",\n \"content-type\",\n \"transfer-encoding\",\n \"connection\",\n \"upgrade\",\n \"trailer\",\n \"te\",\n \"traceparent\",\n \"tracestate\",\n \"baggage\",\n]);\n\nexport const Headers = Schema.Record(\n Schema.String.check(Schema.isPattern(/^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,64}$/)),\n Schema.String.check(Schema.isMaxLength(2048)),\n).check(\n Schema.makeFilter((value) => {\n const names = Object.keys(value).map((name) => name.toLowerCase());\n\n return (\n names.length <= 16 &&\n new Set(names).size === names.length &&\n names.every((name) => !forbiddenHeaders.has(name)) &&\n Object.values(value).every((value) => !/[\\r\\n]/u.test(value))\n );\n }),\n);\n\nexport const ProtocolEndpoint = Endpoint.check(\n Schema.makeFilter((value) =>\n Array.from(new URL(value).searchParams.keys()).every((key) => !reserved.has(key.toLowerCase())),\n ),\n);\n\n// Keep exceptions from hostile input access and erased Redacted values inside\n// schema decoding; service execution must remain outside these catches.\nexport const configuration = <S extends Schema.Constraint>(\n schema: S,\n input: unknown,\n reason: ConfigurationError[\"reason\"] = \"parameters\",\n) =>\n Effect.suspend(() => Schema.decodeUnknownEffect(schema)(input, { reportInput: false })).pipe(\n Effect.mapError(() => ConfigurationError.make({ reason })),\n Effect.catchDefect(() => ConfigurationError.make({ reason })),\n );\n\nexport const decode = <S extends Schema.Constraint>(schema: S, input: unknown) =>\n Effect.suspend(() => Schema.decodeUnknownEffect(schema)(input, { reportInput: false })).pipe(\n Effect.mapError(() => Unavailable.make({})),\n Effect.catchDefect(() => Unavailable.make({})),\n );\n\nexport const reveal = <A>(value: Redacted.Redacted<A>) =>\n Effect.try({ try: () => Redacted.value(value), catch: () => Unavailable.make({}) });\n\n/** Freeze owned configuration snapshots or freshly parsed wire JSON, not caller-owned graphs. */\nexport const freeze = <A>(value: A): A => {\n const pending: Array<unknown> = [value];\n const seen = new WeakSet<object>();\n\n while (pending.length > 0) {\n const item = pending.pop();\n\n if (!Predicate.isObjectOrArray(item) || Redacted.isRedacted(item) || seen.has(item)) continue;\n seen.add(item);\n for (const child of Object.values(item)) pending.push(child);\n Object.freeze(item);\n }\n\n return value;\n};\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1 @@
1
+ {"name":"@yielded/oauth","version":"0.1.0-beta.19","dependencies":{"@yielded/jose":"0.1.0-beta.19"},"devDependencies":{"@effect/platform-node":"4.0.0","@effect/vitest":"4.0.0","@types/node":"26.1.2","@yielded/crypto":"0.1.0-beta.19","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"}}