@yielded/jose 0.1.0-beta.19 → 0.1.0-beta.21
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/dist/Jwks.mjs +1 -1
- package/dist/Jwks.mjs.map +1 -1
- package/dist/Jwt.mjs +3 -3
- package/dist/Jwt.mjs.map +1 -1
- package/dist/internal/keyInput.mjs +1 -1
- package/dist/internal/keyInput.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Jwks.ts +1 -1
- package/src/Jwt.ts +3 -3
- package/src/internal/keyInput.ts +1 -1
package/dist/Jwks.mjs
CHANGED
|
@@ -71,7 +71,7 @@ const layerRemote = (input) => {
|
|
|
71
71
|
...input
|
|
72
72
|
};
|
|
73
73
|
return Layer.effect(Jwks, Effect.gen(function* () {
|
|
74
|
-
const options = yield* Schema.
|
|
74
|
+
const options = yield* Schema.decodeEffect(RemoteConfiguration)(configuration, { reportInput: false }).pipe(Effect.mapError(() => JwksUnavailable.make({ reason: "configuration" })));
|
|
75
75
|
const client = HttpClient.withScope(yield* HttpClient.HttpClient);
|
|
76
76
|
const state = yield* SynchronizedRef.make({
|
|
77
77
|
_tag: "Open",
|
package/dist/Jwks.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Jwks.mjs","names":["KeyInput.keySet","KeyInput.detach","KeyInput.object"],"sources":["../src/Jwks.ts"],"sourcesContent":["import type { Signature } from \"@yielded/crypto/Signature\";\nimport {\n Clock,\n Context,\n Deferred,\n Effect,\n Exit,\n Layer,\n Option,\n Schema,\n Scope,\n Semaphore,\n Stream,\n SynchronizedRef,\n} from \"effect\";\nimport { FetchHttpClient, HttpClient } from \"effect/http\";\n\nimport { AmbiguousKey, InvalidKey, JwksUnavailable, KeyNotFound, type KeyError } from \"./Errors\";\nimport { json } from \"./internal/encoding\";\nimport * as KeyInput from \"./internal/keyInput\";\nimport { AsymmetricAlgorithm, importPublic, type PublicKey, PublicJwk } from \"./Jwk\";\n\nexport const KeySet = Schema.Struct({\n keys: Schema.Array(PublicJwk).check(Schema.isMaxLength(KeyInput.maxArrayLength)),\n});\n\nexport type KeySet = typeof KeySet.Type;\n\nexport const Selection = Schema.Struct({\n algorithm: AsymmetricAlgorithm,\n kid: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(256))),\n});\n\nexport type Selection = typeof Selection.Type;\n\nexport class Jwks extends Context.Service<\n Jwks,\n {\n readonly resolve: (selection: Selection) => Effect.Effect<PublicKey, KeyError, Signature>;\n }\n>()(\"@yielded/jose/Jwks\") {}\n\n/** One immutable public key set; local selection never requires HttpClient. */\nexport const layerLocal = (input: unknown): Layer.Layer<Jwks, InvalidKey> =>\n Layer.effect(\n Jwks,\n Effect.gen(function* () {\n const set = yield* readSet(input);\n\n return Jwks.of({\n resolve: Effect.fnUntraced(function* (input) {\n const selection = yield* readSelection(input);\n\n return yield* importPublic(yield* select(set, selection), selection.algorithm);\n }),\n });\n }),\n );\n\nexport interface RemoteOptions {\n /** Trusted application configuration, never a URL from a token. HTTPS only. */\n readonly url: string;\n readonly cacheTimeMs?: number;\n readonly cooldownMs?: number;\n readonly timeoutMs?: number;\n readonly maxResponseBytes?: number;\n readonly maxKeys?: number;\n readonly maxWaiters?: number;\n}\n\nconst readSet = (input: unknown) =>\n Effect.try({\n try: () => KeyInput.keySet(input),\n catch: () => InvalidKey.make({}),\n }).pipe(\n Effect.flatMap(Effect.fromResult),\n Effect.flatMap((captured) =>\n Schema.decodeUnknownEffect(KeySet)(captured, { reportInput: false }).pipe(\n Effect.mapError(() => InvalidKey.make({})),\n ),\n ),\n Effect.map((set) => {\n for (const key of set.keys) {\n KeyInput.detach(key);\n if (key.key_ops !== undefined) Object.freeze(key.key_ops);\n Object.freeze(key);\n }\n Object.freeze(set.keys);\n\n return Object.freeze(KeyInput.detach(set));\n }),\n );\n\nconst readSelection = (input: Selection) =>\n Effect.try({\n try: () => KeyInput.object(input),\n catch: () => InvalidKey.make({}),\n }).pipe(\n Effect.flatMap((captured) =>\n Schema.decodeUnknownEffect(Selection)(captured, { reportInput: false }).pipe(\n Effect.mapError(() => InvalidKey.make({})),\n ),\n ),\n Effect.map(KeyInput.detach),\n );\n\nconst select = Effect.fnUntraced(function* (set: KeySet, selection: Selection) {\n const candidates = set.keys.filter(\n (key) =>\n (selection.kid === undefined || key.kid === selection.kid) &&\n (key.alg === undefined || key.alg === selection.algorithm) &&\n (key.use === undefined || key.use === \"sig\") &&\n (key.key_ops === undefined || key.key_ops.includes(\"verify\")) &&\n (selection.algorithm === \"ES256\"\n ? key.kty === \"EC\"\n : selection.algorithm === \"EdDSA\"\n ? key.kty === \"OKP\"\n : key.kty === \"RSA\"),\n );\n\n if (candidates.length === 0) return yield* KeyNotFound.make({});\n if (candidates.length !== 1) return yield* AmbiguousKey.make({});\n\n return candidates[0];\n});\n\nconst bounded = (minimum: number, maximum: number) =>\n Schema.Int.check(Schema.isBetween({ minimum, maximum }));\n\nconst RemoteConfiguration = Schema.Struct({\n url: Schema.URLFromString.check(\n Schema.makeFilter(\n (url) =>\n url.protocol === \"https:\" && url.username === \"\" && url.password === \"\" && url.hash === \"\",\n ),\n ),\n cacheTimeMs: bounded(1, 86400000),\n cooldownMs: bounded(0, 3600000),\n timeoutMs: bounded(1, 60000),\n maxResponseBytes: bounded(1, 1048576),\n maxKeys: bounded(1, KeyInput.maxArrayLength),\n maxWaiters: bounded(1, 1024),\n});\n\ninterface Snapshot {\n readonly keys: KeySet;\n readonly expiresAt: number;\n}\n\ntype RemoteState =\n | { readonly _tag: \"Closed\" }\n | {\n readonly _tag: \"Open\";\n readonly cache: Snapshot | undefined;\n readonly pending: Deferred.Deferred<Snapshot, JwksUnavailable> | undefined;\n readonly lastAttempt: number;\n };\n\ntype Admission =\n | { readonly _tag: \"Rejected\"; readonly reason: \"closed\" | \"cooldown\" }\n | { readonly _tag: \"Cached\"; readonly snapshot: Snapshot }\n | {\n readonly _tag: \"Join\" | \"Start\";\n readonly result: Deferred.Deferred<Snapshot, JwksUnavailable>;\n };\n\n/**\n * One trusted HTTPS endpoint per Layer. The supplied client owns egress policy\n * and must not follow redirects; FetchHttpClient is configured to reject them.\n * Responses from another URL are rejected with every client. No stale-on-error\n * fallback, global cache, background polling, or token-supplied key URLs.\n */\nexport const layerRemote = (\n input: RemoteOptions,\n): Layer.Layer<Jwks, JwksUnavailable, HttpClient.HttpClient> => {\n const configuration = {\n cacheTimeMs: 600000,\n cooldownMs: 30000,\n timeoutMs: 5000,\n maxResponseBytes: 131072,\n maxKeys: 64,\n maxWaiters: 64,\n ...input,\n };\n\n return Layer.effect(\n Jwks,\n Effect.gen(function* () {\n const options = yield* Schema.decodeUnknownEffect(RemoteConfiguration)(configuration, {\n reportInput: false,\n }).pipe(Effect.mapError(() => JwksUnavailable.make({ reason: \"configuration\" })));\n\n const client = HttpClient.withScope(yield* HttpClient.HttpClient);\n\n const state = yield* SynchronizedRef.make<RemoteState>({\n _tag: \"Open\",\n cache: undefined,\n pending: undefined,\n lastAttempt: -Infinity,\n });\n\n const permits = yield* Semaphore.make(options.maxWaiters);\n\n const scope = yield* Effect.acquireRelease(Scope.make(), (scope) =>\n Effect.gen(function* () {\n const previous = yield* SynchronizedRef.getAndSet(state, { _tag: \"Closed\" });\n\n // A scoped fiber can be interrupted before its first instruction, so\n // its onExit handler alone cannot release pending lookup waiters.\n if (previous._tag === \"Open\" && previous.pending !== undefined)\n yield* Deferred.fail(previous.pending, JwksUnavailable.make({ reason: \"closed\" }));\n yield* Scope.close(scope, Exit.void);\n }),\n );\n\n const fetch = Effect.gen(function* () {\n const response = yield* client.get(options.url.href, {\n headers: { accept: \"application/json\" },\n });\n\n if (response.status !== 200 || response.url !== options.url.href)\n return yield* JwksUnavailable.make({ reason: \"response\" });\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)\n return Effect.fail(JwksUnavailable.make({ reason: \"response\" }));\n buffer.set(chunk, length);\n\n return Effect.succeed(length + chunk.length);\n },\n );\n\n const document = yield* json(buffer.subarray(0, length), \"payload\");\n const keys = yield* readSet(document);\n\n if (keys.keys.length > options.maxKeys)\n return yield* JwksUnavailable.make({ reason: \"response\" });\n\n return {\n keys,\n expiresAt: (yield* Clock.currentTimeMillis) + options.cacheTimeMs,\n } satisfies Snapshot;\n }).pipe(\n Effect.scoped,\n Effect.provideService(FetchHttpClient.RequestInit, {\n redirect: \"error\",\n credentials: \"omit\",\n }),\n Effect.catchTag(\"HttpClientError\", () => JwksUnavailable.make({ reason: \"transport\" })),\n Effect.catchTag([\"JoseInvalidToken\", \"JoseInvalidKey\"], () =>\n JwksUnavailable.make({ reason: \"response\" }),\n ),\n Effect.timeoutOrElse({\n duration: options.timeoutMs,\n orElse: () => JwksUnavailable.make({ reason: \"transport\" }),\n }),\n );\n\n const refresh = (missingFrom?: Snapshot) =>\n Effect.uninterruptibleMask((restore) =>\n Effect.gen(function* () {\n const now = yield* restore(Clock.currentTimeMillis);\n const result = yield* Deferred.make<Snapshot, JwksUnavailable>();\n\n // Recheck cache and ownership together after the caller's snapshot.\n // A missing key bypasses only the snapshot that failed selection.\n const admission = yield* SynchronizedRef.modify(\n state,\n (current): readonly [Admission, RemoteState] => {\n if (current._tag === \"Closed\")\n return [{ _tag: \"Rejected\", reason: \"closed\" }, current];\n if (\n current.cache !== undefined &&\n current.cache !== missingFrom &&\n current.cache.expiresAt > now\n )\n return [{ _tag: \"Cached\", snapshot: current.cache }, current];\n if (current.pending !== undefined)\n return [{ _tag: \"Join\", result: current.pending }, current];\n if (now - current.lastAttempt < options.cooldownMs)\n return [{ _tag: \"Rejected\", reason: \"cooldown\" }, current];\n\n return [\n { _tag: \"Start\", result },\n { ...current, pending: result, lastAttempt: now },\n ];\n },\n );\n\n if (admission._tag === \"Rejected\")\n return yield* JwksUnavailable.make({ reason: admission.reason });\n if (admission._tag === \"Cached\") return admission.snapshot;\n if (admission._tag === \"Join\") return yield* restore(Deferred.await(admission.result));\n\n // Admission through fork registration is uninterruptible. Network\n // work and waiter suspension never run under the state lock.\n yield* fetch.pipe(\n Effect.onExit((exit) =>\n SynchronizedRef.updateEffect(state, (current) =>\n current._tag === \"Closed\"\n ? Effect.succeed(current)\n : Deferred.done(result, exit).pipe(\n // Keep pending ownership until publication, serialized\n // with shutdown so a closed owner cannot publish success.\n Effect.as({\n ...current,\n cache: Exit.isSuccess(exit) ? exit.value : current.cache,\n pending: undefined,\n }),\n ),\n ),\n ),\n Effect.forkIn(scope, { uninterruptible: false }),\n );\n\n return yield* restore(Deferred.await(result));\n }),\n );\n\n const resolve = Effect.fnUntraced(function* (input: Selection) {\n if ((yield* SynchronizedRef.get(state))._tag === \"Closed\")\n return yield* JwksUnavailable.make({ reason: \"closed\" });\n const selection = yield* readSelection(input);\n const now = yield* Clock.currentTimeMillis;\n const snapshot = yield* SynchronizedRef.get(state);\n\n if (snapshot._tag === \"Closed\") return yield* JwksUnavailable.make({ reason: \"closed\" });\n\n const current =\n snapshot.cache !== undefined && snapshot.cache.expiresAt > now\n ? snapshot.cache\n : yield* refresh();\n\n const candidate = yield* select(current.keys, selection).pipe(\n Effect.catchTag(\"JoseKeyNotFound\", () =>\n refresh(current).pipe(\n Effect.mapError((error) =>\n error.reason === \"cooldown\" ? KeyNotFound.make({}) : error,\n ),\n Effect.flatMap((snapshot) => select(snapshot.keys, selection)),\n ),\n ),\n );\n\n return yield* importPublic(candidate, selection.algorithm);\n });\n\n return Jwks.of({\n resolve: (selection) =>\n resolve(selection).pipe(\n permits.withPermitsIfAvailable(1),\n Effect.flatMap(\n Option.match({\n onNone: () => JwksUnavailable.make({ reason: \"busy\" }),\n onSome: Effect.succeed,\n }),\n ),\n ),\n });\n }),\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Jwks.mjs","names":["KeyInput.keySet","KeyInput.detach","KeyInput.object"],"sources":["../src/Jwks.ts"],"sourcesContent":["import type { Signature } from \"@yielded/crypto/Signature\";\nimport {\n Clock,\n Context,\n Deferred,\n Effect,\n Exit,\n Layer,\n Option,\n Schema,\n Scope,\n Semaphore,\n Stream,\n SynchronizedRef,\n} from \"effect\";\nimport { FetchHttpClient, HttpClient } from \"effect/http\";\n\nimport { AmbiguousKey, InvalidKey, JwksUnavailable, KeyNotFound, type KeyError } from \"./Errors\";\nimport { json } from \"./internal/encoding\";\nimport * as KeyInput from \"./internal/keyInput\";\nimport { AsymmetricAlgorithm, importPublic, type PublicKey, PublicJwk } from \"./Jwk\";\n\nexport const KeySet = Schema.Struct({\n keys: Schema.Array(PublicJwk).check(Schema.isMaxLength(KeyInput.maxArrayLength)),\n});\n\nexport type KeySet = typeof KeySet.Type;\n\nexport const Selection = Schema.Struct({\n algorithm: AsymmetricAlgorithm,\n kid: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(256))),\n});\n\nexport type Selection = typeof Selection.Type;\n\nexport class Jwks extends Context.Service<\n Jwks,\n {\n readonly resolve: (selection: Selection) => Effect.Effect<PublicKey, KeyError, Signature>;\n }\n>()(\"@yielded/jose/Jwks\") {}\n\n/** One immutable public key set; local selection never requires HttpClient. */\nexport const layerLocal = (input: unknown): Layer.Layer<Jwks, InvalidKey> =>\n Layer.effect(\n Jwks,\n Effect.gen(function* () {\n const set = yield* readSet(input);\n\n return Jwks.of({\n resolve: Effect.fnUntraced(function* (input) {\n const selection = yield* readSelection(input);\n\n return yield* importPublic(yield* select(set, selection), selection.algorithm);\n }),\n });\n }),\n );\n\nexport interface RemoteOptions {\n /** Trusted application configuration, never a URL from a token. HTTPS only. */\n readonly url: string;\n readonly cacheTimeMs?: number;\n readonly cooldownMs?: number;\n readonly timeoutMs?: number;\n readonly maxResponseBytes?: number;\n readonly maxKeys?: number;\n readonly maxWaiters?: number;\n}\n\nconst readSet = (input: unknown) =>\n Effect.try({\n try: () => KeyInput.keySet(input),\n catch: () => InvalidKey.make({}),\n }).pipe(\n Effect.flatMap(Effect.fromResult),\n Effect.flatMap((captured) =>\n Schema.decodeUnknownEffect(KeySet)(captured, { reportInput: false }).pipe(\n Effect.mapError(() => InvalidKey.make({})),\n ),\n ),\n Effect.map((set) => {\n for (const key of set.keys) {\n KeyInput.detach(key);\n if (key.key_ops !== undefined) Object.freeze(key.key_ops);\n Object.freeze(key);\n }\n Object.freeze(set.keys);\n\n return Object.freeze(KeyInput.detach(set));\n }),\n );\n\nconst readSelection = (input: Selection) =>\n Effect.try({\n try: () => KeyInput.object(input),\n catch: () => InvalidKey.make({}),\n }).pipe(\n Effect.flatMap((captured) =>\n Schema.decodeUnknownEffect(Selection)(captured, { reportInput: false }).pipe(\n Effect.mapError(() => InvalidKey.make({})),\n ),\n ),\n Effect.map(KeyInput.detach),\n );\n\nconst select = Effect.fnUntraced(function* (set: KeySet, selection: Selection) {\n const candidates = set.keys.filter(\n (key) =>\n (selection.kid === undefined || key.kid === selection.kid) &&\n (key.alg === undefined || key.alg === selection.algorithm) &&\n (key.use === undefined || key.use === \"sig\") &&\n (key.key_ops === undefined || key.key_ops.includes(\"verify\")) &&\n (selection.algorithm === \"ES256\"\n ? key.kty === \"EC\"\n : selection.algorithm === \"EdDSA\"\n ? key.kty === \"OKP\"\n : key.kty === \"RSA\"),\n );\n\n if (candidates.length === 0) return yield* KeyNotFound.make({});\n if (candidates.length !== 1) return yield* AmbiguousKey.make({});\n\n return candidates[0];\n});\n\nconst bounded = (minimum: number, maximum: number) =>\n Schema.Int.check(Schema.isBetween({ minimum, maximum }));\n\nconst RemoteConfiguration = Schema.Struct({\n url: Schema.URLFromString.check(\n Schema.makeFilter(\n (url) =>\n url.protocol === \"https:\" && url.username === \"\" && url.password === \"\" && url.hash === \"\",\n ),\n ),\n cacheTimeMs: bounded(1, 86400000),\n cooldownMs: bounded(0, 3600000),\n timeoutMs: bounded(1, 60000),\n maxResponseBytes: bounded(1, 1048576),\n maxKeys: bounded(1, KeyInput.maxArrayLength),\n maxWaiters: bounded(1, 1024),\n});\n\ninterface Snapshot {\n readonly keys: KeySet;\n readonly expiresAt: number;\n}\n\ntype RemoteState =\n | { readonly _tag: \"Closed\" }\n | {\n readonly _tag: \"Open\";\n readonly cache: Snapshot | undefined;\n readonly pending: Deferred.Deferred<Snapshot, JwksUnavailable> | undefined;\n readonly lastAttempt: number;\n };\n\ntype Admission =\n | { readonly _tag: \"Rejected\"; readonly reason: \"closed\" | \"cooldown\" }\n | { readonly _tag: \"Cached\"; readonly snapshot: Snapshot }\n | {\n readonly _tag: \"Join\" | \"Start\";\n readonly result: Deferred.Deferred<Snapshot, JwksUnavailable>;\n };\n\n/**\n * One trusted HTTPS endpoint per Layer. The supplied client owns egress policy\n * and must not follow redirects; FetchHttpClient is configured to reject them.\n * Responses from another URL are rejected with every client. No stale-on-error\n * fallback, global cache, background polling, or token-supplied key URLs.\n */\nexport const layerRemote = (\n input: RemoteOptions,\n): Layer.Layer<Jwks, JwksUnavailable, HttpClient.HttpClient> => {\n const configuration = {\n cacheTimeMs: 600000,\n cooldownMs: 30000,\n timeoutMs: 5000,\n maxResponseBytes: 131072,\n maxKeys: 64,\n maxWaiters: 64,\n ...input,\n };\n\n return Layer.effect(\n Jwks,\n Effect.gen(function* () {\n const options = yield* Schema.decodeEffect(RemoteConfiguration)(configuration, {\n reportInput: false,\n }).pipe(Effect.mapError(() => JwksUnavailable.make({ reason: \"configuration\" })));\n\n const client = HttpClient.withScope(yield* HttpClient.HttpClient);\n\n const state = yield* SynchronizedRef.make<RemoteState>({\n _tag: \"Open\",\n cache: undefined,\n pending: undefined,\n lastAttempt: -Infinity,\n });\n\n const permits = yield* Semaphore.make(options.maxWaiters);\n\n const scope = yield* Effect.acquireRelease(Scope.make(), (scope) =>\n Effect.gen(function* () {\n const previous = yield* SynchronizedRef.getAndSet(state, { _tag: \"Closed\" });\n\n // A scoped fiber can be interrupted before its first instruction, so\n // its onExit handler alone cannot release pending lookup waiters.\n if (previous._tag === \"Open\" && previous.pending !== undefined)\n yield* Deferred.fail(previous.pending, JwksUnavailable.make({ reason: \"closed\" }));\n yield* Scope.close(scope, Exit.void);\n }),\n );\n\n const fetch = Effect.gen(function* () {\n const response = yield* client.get(options.url.href, {\n headers: { accept: \"application/json\" },\n });\n\n if (response.status !== 200 || response.url !== options.url.href)\n return yield* JwksUnavailable.make({ reason: \"response\" });\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)\n return Effect.fail(JwksUnavailable.make({ reason: \"response\" }));\n buffer.set(chunk, length);\n\n return Effect.succeed(length + chunk.length);\n },\n );\n\n const document = yield* json(buffer.subarray(0, length), \"payload\");\n const keys = yield* readSet(document);\n\n if (keys.keys.length > options.maxKeys)\n return yield* JwksUnavailable.make({ reason: \"response\" });\n\n return {\n keys,\n expiresAt: (yield* Clock.currentTimeMillis) + options.cacheTimeMs,\n } satisfies Snapshot;\n }).pipe(\n Effect.scoped,\n Effect.provideService(FetchHttpClient.RequestInit, {\n redirect: \"error\",\n credentials: \"omit\",\n }),\n Effect.catchTag(\"HttpClientError\", () => JwksUnavailable.make({ reason: \"transport\" })),\n Effect.catchTag([\"JoseInvalidToken\", \"JoseInvalidKey\"], () =>\n JwksUnavailable.make({ reason: \"response\" }),\n ),\n Effect.timeoutOrElse({\n duration: options.timeoutMs,\n orElse: () => JwksUnavailable.make({ reason: \"transport\" }),\n }),\n );\n\n const refresh = (missingFrom?: Snapshot) =>\n Effect.uninterruptibleMask((restore) =>\n Effect.gen(function* () {\n const now = yield* restore(Clock.currentTimeMillis);\n const result = yield* Deferred.make<Snapshot, JwksUnavailable>();\n\n // Recheck cache and ownership together after the caller's snapshot.\n // A missing key bypasses only the snapshot that failed selection.\n const admission = yield* SynchronizedRef.modify(\n state,\n (current): readonly [Admission, RemoteState] => {\n if (current._tag === \"Closed\")\n return [{ _tag: \"Rejected\", reason: \"closed\" }, current];\n if (\n current.cache !== undefined &&\n current.cache !== missingFrom &&\n current.cache.expiresAt > now\n )\n return [{ _tag: \"Cached\", snapshot: current.cache }, current];\n if (current.pending !== undefined)\n return [{ _tag: \"Join\", result: current.pending }, current];\n if (now - current.lastAttempt < options.cooldownMs)\n return [{ _tag: \"Rejected\", reason: \"cooldown\" }, current];\n\n return [\n { _tag: \"Start\", result },\n { ...current, pending: result, lastAttempt: now },\n ];\n },\n );\n\n if (admission._tag === \"Rejected\")\n return yield* JwksUnavailable.make({ reason: admission.reason });\n if (admission._tag === \"Cached\") return admission.snapshot;\n if (admission._tag === \"Join\") return yield* restore(Deferred.await(admission.result));\n\n // Admission through fork registration is uninterruptible. Network\n // work and waiter suspension never run under the state lock.\n yield* fetch.pipe(\n Effect.onExit((exit) =>\n SynchronizedRef.updateEffect(state, (current) =>\n current._tag === \"Closed\"\n ? Effect.succeed(current)\n : Deferred.done(result, exit).pipe(\n // Keep pending ownership until publication, serialized\n // with shutdown so a closed owner cannot publish success.\n Effect.as({\n ...current,\n cache: Exit.isSuccess(exit) ? exit.value : current.cache,\n pending: undefined,\n }),\n ),\n ),\n ),\n Effect.forkIn(scope, { uninterruptible: false }),\n );\n\n return yield* restore(Deferred.await(result));\n }),\n );\n\n const resolve = Effect.fnUntraced(function* (input: Selection) {\n if ((yield* SynchronizedRef.get(state))._tag === \"Closed\")\n return yield* JwksUnavailable.make({ reason: \"closed\" });\n const selection = yield* readSelection(input);\n const now = yield* Clock.currentTimeMillis;\n const snapshot = yield* SynchronizedRef.get(state);\n\n if (snapshot._tag === \"Closed\") return yield* JwksUnavailable.make({ reason: \"closed\" });\n\n const current =\n snapshot.cache !== undefined && snapshot.cache.expiresAt > now\n ? snapshot.cache\n : yield* refresh();\n\n const candidate = yield* select(current.keys, selection).pipe(\n Effect.catchTag(\"JoseKeyNotFound\", () =>\n refresh(current).pipe(\n Effect.mapError((error) =>\n error.reason === \"cooldown\" ? KeyNotFound.make({}) : error,\n ),\n Effect.flatMap((snapshot) => select(snapshot.keys, selection)),\n ),\n ),\n );\n\n return yield* importPublic(candidate, selection.algorithm);\n });\n\n return Jwks.of({\n resolve: (selection) =>\n resolve(selection).pipe(\n permits.withPermitsIfAvailable(1),\n Effect.flatMap(\n Option.match({\n onNone: () => JwksUnavailable.make({ reason: \"busy\" }),\n onSome: Effect.succeed,\n }),\n ),\n ),\n });\n }),\n );\n};\n"],"mappings":"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"}
|
package/dist/Jwt.mjs
CHANGED
|
@@ -33,7 +33,7 @@ const VerifyOptions = Schema.Struct({
|
|
|
33
33
|
])))
|
|
34
34
|
});
|
|
35
35
|
const checkClaims = Effect.fnUntraced(function* (claims, header, options) {
|
|
36
|
-
const registered = yield* Schema.
|
|
36
|
+
const registered = yield* Schema.decodeEffect(RegisteredClaims)(claims, { reportInput: false }).pipe(Effect.mapError(() => ClaimValidationFailed.make({
|
|
37
37
|
claim: "registered",
|
|
38
38
|
reason: "invalid"
|
|
39
39
|
})));
|
|
@@ -111,7 +111,7 @@ const decodeClaims = Effect.fnUntraced(function* (schema, verified, options) {
|
|
|
111
111
|
const wire = yield* json(yield* reveal(verified.payload), "payload");
|
|
112
112
|
const registeredClaims = yield* checkClaims(wire, verified.protectedHeader, options);
|
|
113
113
|
return {
|
|
114
|
-
claims: yield* Schema.
|
|
114
|
+
claims: yield* Schema.decodeEffect(schema)(wire, { reportInput: false }),
|
|
115
115
|
registeredClaims,
|
|
116
116
|
protectedHeader: verified.protectedHeader
|
|
117
117
|
};
|
|
@@ -119,7 +119,7 @@ const decodeClaims = Effect.fnUntraced(function* (schema, verified, options) {
|
|
|
119
119
|
const sign = Effect.fnUntraced(function* (schema, claims, key, header) {
|
|
120
120
|
const encoded = yield* Schema.encodeEffect(schema)(claims, { reportInput: false });
|
|
121
121
|
const wire = yield* Schema.decodeUnknownEffect(JsonObject)(encoded, { reportInput: false });
|
|
122
|
-
yield* Schema.
|
|
122
|
+
yield* Schema.decodeEffect(RegisteredClaims)(wire, { reportInput: false });
|
|
123
123
|
return yield* sign$1(Redacted.make(utf8(yield* stringify(wire))), key, header);
|
|
124
124
|
});
|
|
125
125
|
const verify = Effect.fnUntraced(function* (schema, token, key, options) {
|
package/dist/Jwt.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Jwt.mjs","names":["JwsVerifyOptions","Jws.sign","Jws.verify","Jws.verifyWithKeySet"],"sources":["../src/Jwt.ts"],"sourcesContent":["import type { Signature } from \"@yielded/crypto/Signature\";\nimport { Clock, Effect, Redacted, Schema } from \"effect\";\n\nimport { ClaimValidationFailed, type VerifyError } from \"./Errors\";\nimport {\n decode,\n json,\n JsonObject,\n parse,\n reveal,\n split,\n stringify,\n utf8,\n} from \"./internal/encoding\";\nimport type { SigningKey, VerificationKey } from \"./Jwk\";\nimport type { Jwks } from \"./Jwks\";\nimport { type Header, type Requirements, VerifyOptions as JwsVerifyOptions } from \"./Jws\";\nimport * as Jws from \"./Jws\";\n\nexport const RegisteredClaims = Schema.Struct({\n iss: Schema.optionalKey(Schema.String),\n sub: Schema.optionalKey(Schema.String),\n aud: Schema.optionalKey(Schema.Union([Schema.String, Schema.Array(Schema.String)])),\n exp: Schema.optionalKey(Schema.Finite),\n nbf: Schema.optionalKey(Schema.Finite),\n iat: Schema.optionalKey(Schema.Finite),\n jti: Schema.optionalKey(Schema.String),\n});\n\nexport type RegisteredClaims = typeof RegisteredClaims.Type;\n\nconst expected = Schema.Union([\n Schema.String,\n Schema.Array(Schema.String).check(Schema.isMinLength(1)),\n]);\n\nconst seconds = Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0));\n\nexport const VerifyOptions = Schema.Struct({\n ...JwsVerifyOptions.fields,\n issuer: Schema.optionalKey(expected),\n audience: Schema.optionalKey(expected),\n subject: Schema.optionalKey(Schema.String),\n typ: Schema.optionalKey(Schema.NonEmptyString),\n clockTolerance: Schema.optionalKey(seconds),\n maxTokenAge: Schema.optionalKey(seconds),\n requiredClaims: Schema.optionalKey(\n Schema.Array(Schema.Literals([\"iss\", \"sub\", \"aud\", \"exp\", \"nbf\", \"iat\", \"jti\"])),\n ),\n});\n\nexport type VerifyOptions = typeof VerifyOptions.Type;\n\nexport interface Verified<A> {\n readonly claims: A;\n readonly registeredClaims: RegisteredClaims;\n readonly protectedHeader: Header;\n}\n\nexport type JwtError = VerifyError | ClaimValidationFailed | Schema.SchemaError;\n\nconst checkClaims = Effect.fnUntraced(function* (\n claims: Readonly<Record<string, Schema.Json>>,\n header: Header,\n options: VerifyOptions,\n) {\n const registered = yield* Schema.decodeUnknownEffect(RegisteredClaims)(claims, {\n reportInput: false,\n }).pipe(\n Effect.mapError(() => ClaimValidationFailed.make({ claim: \"registered\", reason: \"invalid\" })),\n );\n\n for (const claim of options.requiredClaims ?? []) {\n if (registered[claim] === undefined)\n return yield* ClaimValidationFailed.make({ claim, reason: \"missing\" });\n }\n if (options.issuer !== undefined) {\n if (registered.iss === undefined)\n return yield* ClaimValidationFailed.make({ claim: \"iss\", reason: \"missing\" });\n const expected = typeof options.issuer === \"string\" ? [options.issuer] : options.issuer;\n\n if (!expected.includes(registered.iss))\n return yield* ClaimValidationFailed.make({ claim: \"iss\", reason: \"mismatch\" });\n }\n if (options.subject !== undefined) {\n if (registered.sub === undefined)\n return yield* ClaimValidationFailed.make({ claim: \"sub\", reason: \"missing\" });\n if (options.subject !== registered.sub)\n return yield* ClaimValidationFailed.make({ claim: \"sub\", reason: \"mismatch\" });\n }\n if (options.audience !== undefined) {\n if (registered.aud === undefined)\n return yield* ClaimValidationFailed.make({ claim: \"aud\", reason: \"missing\" });\n const expected = typeof options.audience === \"string\" ? [options.audience] : options.audience;\n const actual = typeof registered.aud === \"string\" ? [registered.aud] : registered.aud;\n\n if (!expected.some((audience) => actual.includes(audience)))\n return yield* ClaimValidationFailed.make({ claim: \"aud\", reason: \"mismatch\" });\n }\n\n const normalizedType = (value: string) => {\n const type = value.toLowerCase();\n\n return type.includes(\"/\") ? type : `application/${type}`;\n };\n\n if (\n options.typ !== undefined &&\n (header.typ === undefined || normalizedType(header.typ) !== normalizedType(options.typ))\n )\n return yield* ClaimValidationFailed.make({ claim: \"typ\", reason: \"mismatch\" });\n const now = Math.floor((yield* Clock.currentTimeMillis) / 1000);\n const tolerance = options.clockTolerance ?? 0;\n\n if (registered.exp !== undefined && registered.exp <= now - tolerance)\n return yield* ClaimValidationFailed.make({ claim: \"exp\", reason: \"expired\" });\n if (registered.nbf !== undefined && registered.nbf > now + tolerance)\n return yield* ClaimValidationFailed.make({ claim: \"nbf\", reason: \"notYetValid\" });\n if (options.maxTokenAge !== undefined) {\n if (registered.iat === undefined)\n return yield* ClaimValidationFailed.make({ claim: \"iat\", reason: \"missing\" });\n if (registered.iat > now + tolerance)\n return yield* ClaimValidationFailed.make({ claim: \"iat\", reason: \"future\" });\n if (now - registered.iat - tolerance > options.maxTokenAge)\n return yield* ClaimValidationFailed.make({ claim: \"iat\", reason: \"tooOld\" });\n }\n\n return registered;\n});\n\nconst decodeClaims = Effect.fnUntraced(function* <S extends Schema.Constraint>(\n schema: S,\n verified: Jws.Verified,\n options: VerifyOptions,\n) {\n const wire = yield* json(yield* reveal(verified.payload), \"payload\");\n const registeredClaims = yield* checkClaims(wire, verified.protectedHeader, options);\n const claims = yield* Schema.decodeUnknownEffect(schema)(wire, { reportInput: false });\n\n return { claims, registeredClaims, protectedHeader: verified.protectedHeader } satisfies Verified<\n S[\"Type\"]\n >;\n});\n\nexport const sign = Effect.fnUntraced(function* <S extends Schema.Constraint>(\n schema: S,\n claims: S[\"Type\"],\n key: SigningKey,\n header: Header,\n): Effect.fn.Return<Redacted.Redacted<string>, JwtError, Requirements | S[\"EncodingServices\"]> {\n const encoded = yield* Schema.encodeEffect(schema)(claims, { reportInput: false });\n const wire = yield* Schema.decodeUnknownEffect(JsonObject)(encoded, { reportInput: false });\n\n yield* Schema.decodeUnknownEffect(RegisteredClaims)(wire, { reportInput: false });\n\n return yield* Jws.sign(Redacted.make(utf8(yield* stringify(wire))), key, header);\n});\n\nexport const verify = Effect.fnUntraced(function* <S extends Schema.Constraint>(\n schema: S,\n token: Redacted.Redacted<string>,\n key: VerificationKey,\n options: VerifyOptions,\n): Effect.fn.Return<Verified<S[\"Type\"]>, JwtError, Requirements | S[\"DecodingServices\"]> {\n // Capture all policy before asynchronous cryptography or key resolution.\n const policy = yield* parse(VerifyOptions, options, \"parameters\");\n\n return yield* decodeClaims(schema, yield* Jws.verify(token, key, policy), policy);\n});\n\nexport const verifyWithKeySet = Effect.fnUntraced(function* <S extends Schema.Constraint>(\n schema: S,\n token: Redacted.Redacted<string>,\n options: VerifyOptions,\n): Effect.fn.Return<Verified<S[\"Type\"]>, JwtError, Signature | Jwks | S[\"DecodingServices\"]> {\n const policy = yield* parse(VerifyOptions, options, \"parameters\");\n\n return yield* decodeClaims(schema, yield* Jws.verifyWithKeySet(token, policy), policy);\n});\n\n/** Parsing only. This result does not establish authenticity or validate claims. */\nexport const decodeUnverified = Effect.fnUntraced(function* (token: Redacted.Redacted<string>) {\n const [, payload] = yield* split(token, 3);\n const claims = yield* json(yield* decode(payload), \"payload\");\n\n return { _tag: \"UnverifiedJwt\", claims: Redacted.make(claims) } as const;\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Jwt.mjs","names":["JwsVerifyOptions","Jws.sign","Jws.verify","Jws.verifyWithKeySet"],"sources":["../src/Jwt.ts"],"sourcesContent":["import type { Signature } from \"@yielded/crypto/Signature\";\nimport { Clock, Effect, Redacted, Schema } from \"effect\";\n\nimport { ClaimValidationFailed, type VerifyError } from \"./Errors\";\nimport {\n decode,\n json,\n JsonObject,\n parse,\n reveal,\n split,\n stringify,\n utf8,\n} from \"./internal/encoding\";\nimport type { SigningKey, VerificationKey } from \"./Jwk\";\nimport type { Jwks } from \"./Jwks\";\nimport { type Header, type Requirements, VerifyOptions as JwsVerifyOptions } from \"./Jws\";\nimport * as Jws from \"./Jws\";\n\nexport const RegisteredClaims = Schema.Struct({\n iss: Schema.optionalKey(Schema.String),\n sub: Schema.optionalKey(Schema.String),\n aud: Schema.optionalKey(Schema.Union([Schema.String, Schema.Array(Schema.String)])),\n exp: Schema.optionalKey(Schema.Finite),\n nbf: Schema.optionalKey(Schema.Finite),\n iat: Schema.optionalKey(Schema.Finite),\n jti: Schema.optionalKey(Schema.String),\n});\n\nexport type RegisteredClaims = typeof RegisteredClaims.Type;\n\nconst expected = Schema.Union([\n Schema.String,\n Schema.Array(Schema.String).check(Schema.isMinLength(1)),\n]);\n\nconst seconds = Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0));\n\nexport const VerifyOptions = Schema.Struct({\n ...JwsVerifyOptions.fields,\n issuer: Schema.optionalKey(expected),\n audience: Schema.optionalKey(expected),\n subject: Schema.optionalKey(Schema.String),\n typ: Schema.optionalKey(Schema.NonEmptyString),\n clockTolerance: Schema.optionalKey(seconds),\n maxTokenAge: Schema.optionalKey(seconds),\n requiredClaims: Schema.optionalKey(\n Schema.Array(Schema.Literals([\"iss\", \"sub\", \"aud\", \"exp\", \"nbf\", \"iat\", \"jti\"])),\n ),\n});\n\nexport type VerifyOptions = typeof VerifyOptions.Type;\n\nexport interface Verified<A> {\n readonly claims: A;\n readonly registeredClaims: RegisteredClaims;\n readonly protectedHeader: Header;\n}\n\nexport type JwtError = VerifyError | ClaimValidationFailed | Schema.SchemaError;\n\nconst checkClaims = Effect.fnUntraced(function* (\n claims: Readonly<Record<string, Schema.Json>>,\n header: Header,\n options: VerifyOptions,\n) {\n const registered = yield* Schema.decodeEffect(RegisteredClaims)(claims, {\n reportInput: false,\n }).pipe(\n Effect.mapError(() => ClaimValidationFailed.make({ claim: \"registered\", reason: \"invalid\" })),\n );\n\n for (const claim of options.requiredClaims ?? []) {\n if (registered[claim] === undefined)\n return yield* ClaimValidationFailed.make({ claim, reason: \"missing\" });\n }\n if (options.issuer !== undefined) {\n if (registered.iss === undefined)\n return yield* ClaimValidationFailed.make({ claim: \"iss\", reason: \"missing\" });\n const expected = typeof options.issuer === \"string\" ? [options.issuer] : options.issuer;\n\n if (!expected.includes(registered.iss))\n return yield* ClaimValidationFailed.make({ claim: \"iss\", reason: \"mismatch\" });\n }\n if (options.subject !== undefined) {\n if (registered.sub === undefined)\n return yield* ClaimValidationFailed.make({ claim: \"sub\", reason: \"missing\" });\n if (options.subject !== registered.sub)\n return yield* ClaimValidationFailed.make({ claim: \"sub\", reason: \"mismatch\" });\n }\n if (options.audience !== undefined) {\n if (registered.aud === undefined)\n return yield* ClaimValidationFailed.make({ claim: \"aud\", reason: \"missing\" });\n const expected = typeof options.audience === \"string\" ? [options.audience] : options.audience;\n const actual = typeof registered.aud === \"string\" ? [registered.aud] : registered.aud;\n\n if (!expected.some((audience) => actual.includes(audience)))\n return yield* ClaimValidationFailed.make({ claim: \"aud\", reason: \"mismatch\" });\n }\n\n const normalizedType = (value: string) => {\n const type = value.toLowerCase();\n\n return type.includes(\"/\") ? type : `application/${type}`;\n };\n\n if (\n options.typ !== undefined &&\n (header.typ === undefined || normalizedType(header.typ) !== normalizedType(options.typ))\n )\n return yield* ClaimValidationFailed.make({ claim: \"typ\", reason: \"mismatch\" });\n const now = Math.floor((yield* Clock.currentTimeMillis) / 1000);\n const tolerance = options.clockTolerance ?? 0;\n\n if (registered.exp !== undefined && registered.exp <= now - tolerance)\n return yield* ClaimValidationFailed.make({ claim: \"exp\", reason: \"expired\" });\n if (registered.nbf !== undefined && registered.nbf > now + tolerance)\n return yield* ClaimValidationFailed.make({ claim: \"nbf\", reason: \"notYetValid\" });\n if (options.maxTokenAge !== undefined) {\n if (registered.iat === undefined)\n return yield* ClaimValidationFailed.make({ claim: \"iat\", reason: \"missing\" });\n if (registered.iat > now + tolerance)\n return yield* ClaimValidationFailed.make({ claim: \"iat\", reason: \"future\" });\n if (now - registered.iat - tolerance > options.maxTokenAge)\n return yield* ClaimValidationFailed.make({ claim: \"iat\", reason: \"tooOld\" });\n }\n\n return registered;\n});\n\nconst decodeClaims = Effect.fnUntraced(function* <S extends Schema.Constraint>(\n schema: S,\n verified: Jws.Verified,\n options: VerifyOptions,\n) {\n const wire = yield* json(yield* reveal(verified.payload), \"payload\");\n const registeredClaims = yield* checkClaims(wire, verified.protectedHeader, options);\n const claims = yield* Schema.decodeEffect(schema)(wire, { reportInput: false });\n\n return { claims, registeredClaims, protectedHeader: verified.protectedHeader } satisfies Verified<\n S[\"Type\"]\n >;\n});\n\nexport const sign = Effect.fnUntraced(function* <S extends Schema.Constraint>(\n schema: S,\n claims: S[\"Type\"],\n key: SigningKey,\n header: Header,\n): Effect.fn.Return<Redacted.Redacted<string>, JwtError, Requirements | S[\"EncodingServices\"]> {\n const encoded = yield* Schema.encodeEffect(schema)(claims, { reportInput: false });\n const wire = yield* Schema.decodeUnknownEffect(JsonObject)(encoded, { reportInput: false });\n\n yield* Schema.decodeEffect(RegisteredClaims)(wire, { reportInput: false });\n\n return yield* Jws.sign(Redacted.make(utf8(yield* stringify(wire))), key, header);\n});\n\nexport const verify = Effect.fnUntraced(function* <S extends Schema.Constraint>(\n schema: S,\n token: Redacted.Redacted<string>,\n key: VerificationKey,\n options: VerifyOptions,\n): Effect.fn.Return<Verified<S[\"Type\"]>, JwtError, Requirements | S[\"DecodingServices\"]> {\n // Capture all policy before asynchronous cryptography or key resolution.\n const policy = yield* parse(VerifyOptions, options, \"parameters\");\n\n return yield* decodeClaims(schema, yield* Jws.verify(token, key, policy), policy);\n});\n\nexport const verifyWithKeySet = Effect.fnUntraced(function* <S extends Schema.Constraint>(\n schema: S,\n token: Redacted.Redacted<string>,\n options: VerifyOptions,\n): Effect.fn.Return<Verified<S[\"Type\"]>, JwtError, Signature | Jwks | S[\"DecodingServices\"]> {\n const policy = yield* parse(VerifyOptions, options, \"parameters\");\n\n return yield* decodeClaims(schema, yield* Jws.verifyWithKeySet(token, policy), policy);\n});\n\n/** Parsing only. This result does not establish authenticity or validate claims. */\nexport const decodeUnverified = Effect.fnUntraced(function* (token: Redacted.Redacted<string>) {\n const [, payload] = yield* split(token, 3);\n const claims = yield* json(yield* decode(payload), \"payload\");\n\n return { _tag: \"UnverifiedJwt\", claims: Redacted.make(claims) } as const;\n});\n"],"mappings":"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"}
|
|
@@ -17,7 +17,7 @@ const object = (input) => {
|
|
|
17
17
|
return copy;
|
|
18
18
|
};
|
|
19
19
|
const array = (input) => Result.gen(function* () {
|
|
20
|
-
const length = yield* Schema.
|
|
20
|
+
const length = yield* Schema.decodeResult(ArrayLength)(input.length).pipe(Result.mapError(() => InvalidKey.make({})));
|
|
21
21
|
return Array.from({ length }, (_, index) => Object.getOwnPropertyDescriptor(input, index)).map((descriptor) => descriptor === void 0 ? void 0 : Object.hasOwn(descriptor, "value") ? descriptor.value : descriptor.get?.call(input));
|
|
22
22
|
});
|
|
23
23
|
const jwk = (input) => Result.gen(function* () {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyInput.mjs","names":[],"sources":["../../src/internal/keyInput.ts"],"sourcesContent":["import { Predicate, Result, Schema } from \"effect\";\n\nimport { InvalidKey } from \"../Errors\";\n\nexport const maxArrayLength = 256;\n\nconst ArrayLength = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: maxArrayLength }));\n\n/** Copy only own fields, evaluating accessors once before Schema validation. */\nexport const object = (input: unknown): unknown => {\n if (!Predicate.isObject(input)) return input;\n const descriptors = Object.getOwnPropertyDescriptors(input);\n const copy: Record<string, unknown> = {};\n\n Object.setPrototypeOf(copy, null);\n for (const name of Object.getOwnPropertyNames(descriptors)) {\n const descriptor = descriptors[name];\n\n copy[name] = Object.hasOwn(descriptor, \"value\")\n ? descriptor.value\n : descriptor.get?.call(input);\n }\n\n return copy;\n};\n\nconst array = (input: ReadonlyArray<unknown>) =>\n Result.gen(function* () {\n const length = yield* Schema.
|
|
1
|
+
{"version":3,"file":"keyInput.mjs","names":[],"sources":["../../src/internal/keyInput.ts"],"sourcesContent":["import { Predicate, Result, Schema } from \"effect\";\n\nimport { InvalidKey } from \"../Errors\";\n\nexport const maxArrayLength = 256;\n\nconst ArrayLength = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: maxArrayLength }));\n\n/** Copy only own fields, evaluating accessors once before Schema validation. */\nexport const object = (input: unknown): unknown => {\n if (!Predicate.isObject(input)) return input;\n const descriptors = Object.getOwnPropertyDescriptors(input);\n const copy: Record<string, unknown> = {};\n\n Object.setPrototypeOf(copy, null);\n for (const name of Object.getOwnPropertyNames(descriptors)) {\n const descriptor = descriptors[name];\n\n copy[name] = Object.hasOwn(descriptor, \"value\")\n ? descriptor.value\n : descriptor.get?.call(input);\n }\n\n return copy;\n};\n\nconst array = (input: ReadonlyArray<unknown>) =>\n Result.gen(function* () {\n const length = yield* Schema.decodeResult(ArrayLength)(input.length).pipe(\n Result.mapError(() => InvalidKey.make({})),\n );\n\n // Capture bounded own slots before any accessor can mutate the array.\n const descriptors = Array.from({ length }, (_, index) =>\n Object.getOwnPropertyDescriptor(input, index),\n );\n\n return descriptors.map((descriptor) =>\n descriptor === undefined\n ? undefined\n : Object.hasOwn(descriptor, \"value\")\n ? descriptor.value\n : descriptor.get?.call(input),\n );\n });\n\nexport const jwk = (input: unknown) =>\n Result.gen(function* () {\n const copy = object(input);\n\n if (Predicate.isObject(copy) && Array.isArray(copy.key_ops))\n copy.key_ops = yield* array(copy.key_ops);\n\n return copy;\n });\n\nexport const keySet = (input: unknown) =>\n Result.gen(function* () {\n const copy = object(input);\n\n if (Predicate.isObject(copy) && Array.isArray(copy.keys)) {\n const keys = yield* array(copy.keys);\n\n copy.keys = yield* Result.all(keys.map(jwk));\n }\n\n return copy;\n });\n\n/** Struct decoding creates ordinary objects; detach owned output as well. */\nexport const detach = <A>(value: A): A => {\n if (Predicate.isObject(value)) Object.setPrototypeOf(value, null);\n\n return value;\n};\n"],"mappings":";;AAMA,MAAM,cAAc,OAAO,IAAI,MAAM,OAAO,UAAU;CAAE,SAAS;CAAG,SAAA;AAAwB,CAAC,CAAC;;AAG9F,MAAa,UAAU,UAA4B;CACjD,IAAI,CAAC,UAAU,SAAS,KAAK,GAAG,OAAO;CACvC,MAAM,cAAc,OAAO,0BAA0B,KAAK;CAC1D,MAAM,OAAgC,CAAC;CAEvC,OAAO,eAAe,MAAM,IAAI;CAChC,KAAK,MAAM,QAAQ,OAAO,oBAAoB,WAAW,GAAG;EAC1D,MAAM,aAAa,YAAY;EAE/B,KAAK,QAAQ,OAAO,OAAO,YAAY,OAAO,IAC1C,WAAW,QACX,WAAW,KAAK,KAAK,KAAK;CAChC;CAEA,OAAO;AACT;AAEA,MAAM,SAAS,UACb,OAAO,IAAI,aAAa;CACtB,MAAM,SAAS,OAAO,OAAO,aAAa,WAAW,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,KACnE,OAAO,eAAe,WAAW,KAAK,CAAC,CAAC,CAAC,CAC3C;CAOA,OAJoB,MAAM,KAAK,EAAE,OAAO,IAAI,GAAG,UAC7C,OAAO,yBAAyB,OAAO,KAAK,CAG7B,CAAC,CAAC,KAAK,eACtB,eAAe,KAAA,IACX,KAAA,IACA,OAAO,OAAO,YAAY,OAAO,IAC/B,WAAW,QACX,WAAW,KAAK,KAAK,KAAK,CAClC;AACF,CAAC;AAEH,MAAa,OAAO,UAClB,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,KAAK;CAEzB,IAAI,UAAU,SAAS,IAAI,KAAK,MAAM,QAAQ,KAAK,OAAO,GACxD,KAAK,UAAU,OAAO,MAAM,KAAK,OAAO;CAE1C,OAAO;AACT,CAAC;AAEH,MAAa,UAAU,UACrB,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,KAAK;CAEzB,IAAI,UAAU,SAAS,IAAI,KAAK,MAAM,QAAQ,KAAK,IAAI,GAAG;EACxD,MAAM,OAAO,OAAO,MAAM,KAAK,IAAI;EAEnC,KAAK,OAAO,OAAO,OAAO,IAAI,KAAK,IAAI,GAAG,CAAC;CAC7C;CAEA,OAAO;AACT,CAAC;;AAGH,MAAa,UAAa,UAAgB;CACxC,IAAI,UAAU,SAAS,KAAK,GAAG,OAAO,eAAe,OAAO,IAAI;CAEhE,OAAO;AACT"}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@yielded/jose","version":"0.1.0-beta.
|
|
1
|
+
{"name":"@yielded/jose","version":"0.1.0-beta.21","dependencies":{"@yielded/crypto":"0.1.0-beta.21"},"devDependencies":{"@effect/platform-node":"4.0.0","@effect/vitest":"4.0.0","@types/node":"26.1.2","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"},"./Jwk":{"types":"./dist/Jwk.d.mts","default":"./dist/Jwk.mjs"},"./Jwks":{"types":"./dist/Jwks.d.mts","default":"./dist/Jwks.mjs"},"./Jws":{"types":"./dist/Jws.d.mts","default":"./dist/Jws.mjs"},"./Jwt":{"types":"./dist/Jwt.d.mts","default":"./dist/Jwt.mjs"},"./Jwe":{"types":"./dist/Jwe.d.mts","default":"./dist/Jwe.mjs"}},"description":"Schema-typed Effect JOSE with explicit keys, cryptography, and remote JWKS trust.","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/jose"},"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/Jwks.ts
CHANGED
|
@@ -186,7 +186,7 @@ export const layerRemote = (
|
|
|
186
186
|
return Layer.effect(
|
|
187
187
|
Jwks,
|
|
188
188
|
Effect.gen(function* () {
|
|
189
|
-
const options = yield* Schema.
|
|
189
|
+
const options = yield* Schema.decodeEffect(RemoteConfiguration)(configuration, {
|
|
190
190
|
reportInput: false,
|
|
191
191
|
}).pipe(Effect.mapError(() => JwksUnavailable.make({ reason: "configuration" })));
|
|
192
192
|
|
package/src/Jwt.ts
CHANGED
|
@@ -64,7 +64,7 @@ const checkClaims = Effect.fnUntraced(function* (
|
|
|
64
64
|
header: Header,
|
|
65
65
|
options: VerifyOptions,
|
|
66
66
|
) {
|
|
67
|
-
const registered = yield* Schema.
|
|
67
|
+
const registered = yield* Schema.decodeEffect(RegisteredClaims)(claims, {
|
|
68
68
|
reportInput: false,
|
|
69
69
|
}).pipe(
|
|
70
70
|
Effect.mapError(() => ClaimValidationFailed.make({ claim: "registered", reason: "invalid" })),
|
|
@@ -135,7 +135,7 @@ const decodeClaims = Effect.fnUntraced(function* <S extends Schema.Constraint>(
|
|
|
135
135
|
) {
|
|
136
136
|
const wire = yield* json(yield* reveal(verified.payload), "payload");
|
|
137
137
|
const registeredClaims = yield* checkClaims(wire, verified.protectedHeader, options);
|
|
138
|
-
const claims = yield* Schema.
|
|
138
|
+
const claims = yield* Schema.decodeEffect(schema)(wire, { reportInput: false });
|
|
139
139
|
|
|
140
140
|
return { claims, registeredClaims, protectedHeader: verified.protectedHeader } satisfies Verified<
|
|
141
141
|
S["Type"]
|
|
@@ -151,7 +151,7 @@ export const sign = Effect.fnUntraced(function* <S extends Schema.Constraint>(
|
|
|
151
151
|
const encoded = yield* Schema.encodeEffect(schema)(claims, { reportInput: false });
|
|
152
152
|
const wire = yield* Schema.decodeUnknownEffect(JsonObject)(encoded, { reportInput: false });
|
|
153
153
|
|
|
154
|
-
yield* Schema.
|
|
154
|
+
yield* Schema.decodeEffect(RegisteredClaims)(wire, { reportInput: false });
|
|
155
155
|
|
|
156
156
|
return yield* Jws.sign(Redacted.make(utf8(yield* stringify(wire))), key, header);
|
|
157
157
|
});
|
package/src/internal/keyInput.ts
CHANGED
|
@@ -26,7 +26,7 @@ export const object = (input: unknown): unknown => {
|
|
|
26
26
|
|
|
27
27
|
const array = (input: ReadonlyArray<unknown>) =>
|
|
28
28
|
Result.gen(function* () {
|
|
29
|
-
const length = yield* Schema.
|
|
29
|
+
const length = yield* Schema.decodeResult(ArrayLength)(input.length).pipe(
|
|
30
30
|
Result.mapError(() => InvalidKey.make({})),
|
|
31
31
|
);
|
|
32
32
|
|