@yielded/auth-simplewebauthn 0.1.0-beta.0

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":"simplewebauthn.mjs","names":[],"sources":["../../../src/internal/browser/simplewebauthn.ts"],"sourcesContent":["import {\n base64URLStringToBuffer,\n browserSupportsWebAuthn,\n browserSupportsWebAuthnAutofill,\n bufferToBase64URLString,\n platformAuthenticatorIsAvailable,\n} from \"@simplewebauthn/browser\";\nimport {\n PasskeyAuthenticationStarted,\n PasskeyProtocolCredentialId,\n PasskeyRegistrationStarted,\n snapshotPasskey,\n} from \"@yielded/auth/Passkey\";\nimport type { Context } from \"effect\";\nimport { DateTime, Effect, Layer, Predicate, Redacted, Schema } from \"effect\";\n\nimport {\n PasskeyBrowserAuthentication,\n PasskeyBrowserBusy,\n PasskeyBrowserCapabilities,\n type PasskeyBrowserFailure,\n PasskeyBrowserInputRejected,\n PasskeyBrowserNotCompleted,\n PasskeyBrowserRegistration,\n PasskeyBrowserUnavailable,\n PasskeyBrowserUnsupported,\n} from \"./models\";\nimport { PasskeyBrowser } from \"./PasskeyBrowser\";\n\ntype Browser = Context.Service.Shape<typeof PasskeyBrowser>;\nconst unavailable = () => PasskeyBrowserUnavailable.make({});\nconst rejected = () => PasskeyBrowserInputRejected.make({});\nconst incomplete = () => PasskeyBrowserNotCompleted.make({});\n\nconst unexpected = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n effect.pipe(Effect.catchDefect(() => Effect.fail(unavailable())));\n\nconst decode = <S extends Schema.Codec<unknown, unknown, never, never>>(\n schema: S,\n value: unknown,\n) =>\n // Native browser objects and maintained capability helpers are untyped boundaries.\n // eslint-disable-next-line no-restricted-properties\n Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(unavailable));\n\nconst capture = <S extends Schema.Codec<unknown, unknown, never, never>>(\n schema: S,\n value: S[\"Type\"],\n) => snapshotPasskey(schema, value).pipe(Effect.mapError(rejected));\n\nconst maintained = <A>(call: () => PromiseLike<A>) =>\n Effect.tryPromise({ try: call, catch: unavailable });\n\nconst timeout = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 300000 }));\n\nconst registrationInput = Schema.Struct({\n ...PasskeyRegistrationStarted.fields,\n options: Schema.Struct({ ...PasskeyRegistrationStarted.fields.options.fields, timeout }),\n});\n\nconst authenticationInput = Schema.Struct({\n started: Schema.Struct({\n ...PasskeyAuthenticationStarted.fields,\n options: Schema.Struct({ ...PasskeyAuthenticationStarted.fields.options.fields, timeout }),\n }),\n mediation: Schema.Literals([\"required\", \"conditional\"]),\n});\n\nconst nativeCapabilities = Schema.Struct({\n conditionalGet: Schema.UndefinedOr(Schema.Boolean),\n userVerifyingPlatformAuthenticator: Schema.UndefinedOr(Schema.Boolean),\n hybridTransport: Schema.UndefinedOr(Schema.Boolean),\n passkeyPlatformAuthenticator: Schema.UndefinedOr(Schema.Boolean),\n});\n\nconst support = (value: boolean | undefined) =>\n value === undefined\n ? (\"unknown\" as const)\n : value\n ? (\"supported\" as const)\n : (\"unsupported\" as const);\n\nconst readCapabilities = Effect.gen(function* () {\n if (typeof globalThis.PublicKeyCredential.getClientCapabilities !== \"function\")\n return {\n conditionalGet: undefined,\n userVerifyingPlatformAuthenticator: undefined,\n hybridTransport: undefined,\n passkeyPlatformAuthenticator: undefined,\n };\n const raw = yield* maintained(() => globalThis.PublicKeyCredential.getClientCapabilities());\n\n const fields = yield* Effect.try({\n try: () => {\n if (!Predicate.isObject(raw)) throw unavailable();\n\n return {\n conditionalGet: raw.conditionalGet,\n userVerifyingPlatformAuthenticator: raw.userVerifyingPlatformAuthenticator,\n hybridTransport: raw.hybridTransport,\n passkeyPlatformAuthenticator: raw.passkeyPlatformAuthenticator,\n };\n },\n catch: unavailable,\n });\n\n return yield* decode(nativeCapabilities, fields);\n});\n\nconst booleanCapability = Effect.fn(\"passkeyBrowserBooleanCapability\")(function* (\n call: () => PromiseLike<boolean>,\n) {\n const value = yield* decode(Schema.Boolean, yield* maintained(call));\n\n return value ? (\"supported\" as const) : (\"unsupported\" as const);\n});\n\nconst environment = Effect.try({\n try: () => {\n const secureContext = globalThis.isSecureContext === true;\n const container = globalThis.navigator?.credentials;\n\n const webAuthn =\n secureContext &&\n browserSupportsWebAuthn() &&\n container !== undefined &&\n typeof container.get === \"function\" &&\n typeof container.create === \"function\" &&\n typeof globalThis.AbortController === \"function\";\n\n return { secureContext, webAuthn, container };\n },\n catch: unavailable,\n});\n\nconst capabilities = Effect.gen(function* () {\n const env = yield* environment;\n\n if (!env.webAuthn)\n return yield* snapshotPasskey(PasskeyBrowserCapabilities, {\n secureContext: env.secureContext,\n webAuthn: false,\n conditionalGet: \"unsupported\",\n userVerifyingPlatformAuthenticator: \"unsupported\",\n hybridTransport: \"unsupported\",\n passkeyPlatformAuthenticator: \"unsupported\",\n }).pipe(Effect.mapError(unavailable));\n const raw = yield* readCapabilities;\n\n const values = {\n conditionalGet: support(raw.conditionalGet),\n userVerifyingPlatformAuthenticator: support(raw.userVerifyingPlatformAuthenticator),\n hybridTransport: support(raw.hybridTransport),\n passkeyPlatformAuthenticator: support(raw.passkeyPlatformAuthenticator),\n };\n\n const conditionalGet =\n values.conditionalGet === \"unknown\"\n ? typeof globalThis.PublicKeyCredential.isConditionalMediationAvailable === \"function\"\n ? yield* booleanCapability(browserSupportsWebAuthnAutofill)\n : (\"unsupported\" as const)\n : values.conditionalGet;\n\n const platform =\n values.userVerifyingPlatformAuthenticator === \"unknown\" &&\n typeof globalThis.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable ===\n \"function\"\n ? yield* booleanCapability(platformAuthenticatorIsAvailable)\n : values.userVerifyingPlatformAuthenticator;\n\n return yield* snapshotPasskey(PasskeyBrowserCapabilities, {\n secureContext: env.secureContext,\n webAuthn: true,\n ...values,\n conditionalGet,\n userVerifyingPlatformAuthenticator: platform,\n }).pipe(Effect.mapError(unavailable));\n}).pipe(\n Effect.timeoutOrElse({ duration: 5000, orElse: () => Effect.fail(unavailable()) }),\n unexpected,\n);\n\ninterface Lease {\n readonly container: CredentialsContainer;\n readonly controller: AbortController;\n phase: \"pre-native\" | \"native\" | \"settled\";\n}\n// One installed module coordinates its instances. No third-party/global cancel.\nconst leases = new WeakMap<CredentialsContainer, Lease>();\n\nconst settle = (lease: Lease): void => {\n lease.phase = \"settled\";\n if (leases.get(lease.container) === lease) leases.delete(lease.container);\n};\n\nconst acquire = Effect.gen(function* () {\n const env = yield* environment;\n\n if (!env.webAuthn || env.container === undefined)\n return yield* PasskeyBrowserUnsupported.make({});\n if (leases.has(env.container)) return yield* PasskeyBrowserBusy.make({});\n\n const lease: Lease = {\n container: env.container,\n controller: new AbortController(),\n phase: \"pre-native\",\n };\n\n leases.set(env.container, lease);\n\n return lease;\n});\n\nconst release = (lease: Lease) =>\n Effect.sync(() => {\n if (lease.phase === \"settled\") return;\n // Abort requests cancellation; only raw native settlement releases a started call.\n try {\n lease.controller.abort();\n } catch {\n /* Cleanup cannot replace interruption with a defect. */\n }\n if (lease.phase === \"pre-native\") settle(lease);\n });\n\nconst nativeFailure = (value: unknown): PasskeyBrowserNotCompleted | PasskeyBrowserUnavailable => {\n try {\n if (!Predicate.isObject(value)) return unavailable();\n // Only the native error's standardized name is inspected; no message or cause.\n // eslint-disable-next-line no-restricted-properties\n const parsed = Schema.decodeUnknownOption(Schema.String)(value.name);\n\n if (\n parsed._tag === \"Some\" &&\n [\n \"NotAllowedError\",\n \"InvalidStateError\",\n \"ConstraintError\",\n \"NotSupportedError\",\n \"SecurityError\",\n \"AbortError\",\n ].includes(parsed.value)\n )\n return incomplete();\n } catch {\n /* Throwing extension error properties are unavailable. */\n }\n\n return unavailable();\n};\n\ntype NativeRequest =\n | {\n readonly _tag: \"Create\";\n readonly publicKey: PublicKeyCredentialCreationOptions;\n }\n | {\n readonly _tag: \"Get\";\n readonly publicKey: PublicKeyCredentialRequestOptions;\n readonly mediation: \"required\" | \"conditional\";\n };\n\nconst native = (lease: Lease, request: NativeRequest) =>\n Effect.callback<unknown, PasskeyBrowserNotCompleted | PasskeyBrowserUnavailable>(\n (resume, signal) => {\n if (\n signal.aborted ||\n lease.controller.signal.aborted ||\n lease.phase !== \"pre-native\" ||\n leases.get(lease.container) !== lease\n ) {\n resume(Effect.fail(incomplete()));\n\n return;\n }\n lease.phase = \"native\";\n try {\n const promise =\n request._tag === \"Create\"\n ? lease.container.create({\n publicKey: request.publicKey,\n signal: lease.controller.signal,\n })\n : lease.container.get({\n publicKey: request.publicKey,\n mediation: request.mediation,\n signal: lease.controller.signal,\n });\n\n // Both handlers always settle and never throw, including after interruption.\n void Promise.resolve(promise).then(\n (value) => {\n try {\n settle(lease);\n if (!signal.aborted && !lease.controller.signal.aborted)\n resume(Effect.succeed(value));\n } catch {\n /* Detached handlers cannot reject. */\n }\n },\n (error) => {\n try {\n settle(lease);\n if (!signal.aborted && !lease.controller.signal.aborted)\n resume(Effect.fail(nativeFailure(error)));\n } catch {\n /* Detached handlers cannot reject. */\n }\n },\n );\n } catch (error) {\n settle(lease);\n resume(Effect.fail(nativeFailure(error)));\n }\n },\n );\n\nconst remaining = Effect.fn(\"passkeyBrowserRemaining\")(function* (deadline: number) {\n const duration = deadline - DateTime.toEpochMillis(yield* DateTime.now);\n\n if (duration <= 0) return yield* incomplete();\n\n return Math.max(1, Math.floor(duration));\n});\n\nconst runCeremony = Effect.fn(\"passkeyBrowserRunCeremony\")(function* <A>(\n started: { readonly expiresAtMillis: number; readonly options: { readonly timeout: number } },\n use: (lease: Lease, deadline: number) => Effect.Effect<A, PasskeyBrowserFailure>,\n) {\n const now = DateTime.toEpochMillis(yield* DateTime.now);\n\n if (started.expiresAtMillis <= now) return yield* rejected();\n const deadline = Math.min(started.expiresAtMillis, now + started.options.timeout);\n\n return yield* Effect.acquireUseRelease(\n acquire,\n (lease) =>\n Effect.gen(function* () {\n const duration = yield* remaining(deadline);\n\n return yield* use(lease, deadline).pipe(\n Effect.timeoutOrElse({ duration, orElse: () => Effect.fail(incomplete()) }),\n );\n }),\n release,\n );\n});\n\nconst nativeEnvelope = {\n id: PasskeyProtocolCredentialId,\n rawId: Schema.instanceOf(ArrayBuffer),\n type: Schema.Literal(\"public-key\"),\n};\n\nconst nativeRegistration = Schema.Struct({\n ...nativeEnvelope,\n response: Schema.Struct({\n attestationObject: Schema.instanceOf(ArrayBuffer),\n clientDataJSON: Schema.instanceOf(ArrayBuffer),\n }),\n});\n\nconst nativeAuthentication = Schema.Struct({\n ...nativeEnvelope,\n response: Schema.Struct({\n authenticatorData: Schema.instanceOf(ArrayBuffer),\n clientDataJSON: Schema.instanceOf(ArrayBuffer),\n signature: Schema.instanceOf(ArrayBuffer),\n userHandle: Schema.optionalKey(Schema.NullOr(Schema.instanceOf(ArrayBuffer))),\n }),\n});\n\nconst buffer = Effect.fn(\"passkeyBrowserBuffer\")(function* (\n value: ArrayBuffer,\n minimum: number,\n maximum: number,\n) {\n // Slice with an upper bound before conversion, ignoring instance overrides.\n const copied = yield* Effect.try({\n try: () => ArrayBuffer.prototype.slice.call(value, 0, maximum + 1),\n catch: unavailable,\n });\n\n if (copied.byteLength < minimum || copied.byteLength > maximum) return yield* unavailable();\n\n return yield* Effect.try({ try: () => bufferToBase64URLString(copied), catch: unavailable });\n});\n\nconst registrationWire = Schema.fromJsonString(\n Schema.Struct({\n id: Schema.String,\n rawId: Schema.String,\n type: Schema.Literal(\"public-key\"),\n response: Schema.Struct({ attestationObject: Schema.String, clientDataJSON: Schema.String }),\n clientExtensionResults: Schema.Struct({}),\n }),\n);\n\nconst authenticationWire = Schema.fromJsonString(\n Schema.Struct({\n id: Schema.String,\n rawId: Schema.String,\n type: Schema.Literal(\"public-key\"),\n response: Schema.Struct({\n authenticatorData: Schema.String,\n clientDataJSON: Schema.String,\n signature: Schema.String,\n userHandle: Schema.optionalKey(Schema.String),\n }),\n clientExtensionResults: Schema.Struct({}),\n }),\n);\n\nconst registrationResponse = Effect.fn(\"passkeyBrowserRegistrationResponse\")(function* (\n value: unknown,\n) {\n if (value === null) return yield* incomplete();\n\n const fields = yield* Effect.try({\n try: () => {\n if (!Predicate.isObject(value)) throw unavailable();\n const response = value.response;\n\n if (!Predicate.isObject(response)) throw unavailable();\n\n return {\n id: value.id,\n rawId: value.rawId,\n type: value.type,\n response: {\n attestationObject: response.attestationObject,\n clientDataJSON: response.clientDataJSON,\n },\n };\n },\n catch: unavailable,\n });\n\n const credential = yield* decode(nativeRegistration, fields);\n const rawId = yield* buffer(credential.rawId, 1, 1023);\n\n if (credential.id !== rawId) return yield* unavailable();\n\n return yield* Schema.encodeEffect(registrationWire)({\n id: credential.id,\n rawId,\n type: \"public-key\",\n response: {\n attestationObject: yield* buffer(credential.response.attestationObject, 1, 65536),\n clientDataJSON: yield* buffer(credential.response.clientDataJSON, 1, 8192),\n },\n clientExtensionResults: {},\n }).pipe(Effect.mapError(unavailable));\n});\n\nconst authenticationResponse = Effect.fn(\"passkeyBrowserAuthenticationResponse\")(function* (\n value: unknown,\n) {\n if (value === null) return yield* incomplete();\n\n const fields = yield* Effect.try({\n try: () => {\n if (!Predicate.isObject(value)) throw unavailable();\n const response = value.response;\n\n if (!Predicate.isObject(response)) throw unavailable();\n const userHandle = response.userHandle;\n\n return {\n id: value.id,\n rawId: value.rawId,\n type: value.type,\n response: {\n authenticatorData: response.authenticatorData,\n clientDataJSON: response.clientDataJSON,\n signature: response.signature,\n ...(userHandle === undefined ? {} : { userHandle }),\n },\n };\n },\n catch: unavailable,\n });\n\n const credential = yield* decode(nativeAuthentication, fields);\n const rawId = yield* buffer(credential.rawId, 1, 1023);\n\n if (credential.id !== rawId) return yield* unavailable();\n const handle = credential.response.userHandle;\n\n return yield* Schema.encodeEffect(authenticationWire)({\n id: credential.id,\n rawId,\n type: \"public-key\",\n response: {\n authenticatorData: yield* buffer(credential.response.authenticatorData, 37, 16384),\n clientDataJSON: yield* buffer(credential.response.clientDataJSON, 1, 8192),\n signature: yield* buffer(credential.response.signature, 1, 1024),\n ...(handle === null || handle === undefined\n ? {}\n : { userHandle: yield* buffer(handle, 1, 64) }),\n },\n clientExtensionResults: {},\n }).pipe(Effect.mapError(unavailable));\n});\n\nconst conditional = Effect.gen(function* () {\n if (typeof globalThis.PublicKeyCredential.isConditionalMediationAvailable !== \"function\")\n return yield* PasskeyBrowserUnsupported.make({});\n if ((yield* booleanCapability(browserSupportsWebAuthnAutofill)) !== \"supported\")\n return yield* PasskeyBrowserUnsupported.make({});\n\n const present = yield* Effect.try({\n try: () =>\n Array.from(globalThis.document.querySelectorAll(\"input[autocomplete]\")).some(\n (input) =>\n input\n .getAttribute(\"autocomplete\")\n ?.split(/[\\t\\n\\f\\r ]+/)\n .filter((token) => token.length > 0)\n .at(-1)\n ?.toLowerCase() === \"webauthn\",\n ),\n catch: unavailable,\n });\n\n if (!present) return yield* rejected();\n}).pipe(Effect.timeoutOrElse({ duration: 5000, orElse: () => Effect.fail(unavailable()) }));\n\nexport const makeSimpleWebAuthnPasskeyBrowser = Effect.fn(\"makeSimpleWebAuthnPasskeyBrowser\")(\n (): Effect.Effect<Browser> =>\n Effect.sync(() => {\n const register = Effect.fn(\"PasskeyBrowser.register\")(function* (\n input: Parameters<Browser[\"register\"]>[0],\n ) {\n const started = yield* capture(registrationInput, input);\n\n return yield* runCeremony(started, (lease, deadline) =>\n Effect.gen(function* () {\n const timeoutMillis = yield* remaining(deadline);\n const options = started.options;\n\n const publicKey: PublicKeyCredentialCreationOptions = {\n challenge: base64URLStringToBuffer(options.challenge),\n rp: { ...options.rp },\n user: { ...options.user, id: base64URLStringToBuffer(options.user.id) },\n pubKeyCredParams: options.pubKeyCredParams.map((value) => ({ ...value })),\n timeout: timeoutMillis,\n attestation: options.attestation,\n authenticatorSelection: { ...options.authenticatorSelection },\n excludeCredentials: options.excludeCredentials.map((value) => ({\n type: value.type,\n id: base64URLStringToBuffer(value.id),\n })),\n };\n\n yield* remaining(deadline);\n\n const response = yield* registrationResponse(\n yield* native(lease, { _tag: \"Create\", publicKey }),\n );\n\n yield* remaining(deadline);\n\n return yield* snapshotPasskey(PasskeyBrowserRegistration, {\n flowId: started.flowId,\n response: Redacted.make(response),\n }).pipe(Effect.mapError(unavailable));\n }),\n );\n }, unexpected);\n\n const authenticate = Effect.fn(\"PasskeyBrowser.authenticate\")(function* (\n input: Parameters<Browser[\"authenticate\"]>[0],\n ) {\n const fixed = yield* capture(authenticationInput, input);\n const { started, mediation } = fixed;\n\n if (mediation === \"conditional\" && started.options.allowCredentials.length !== 0)\n return yield* rejected();\n\n return yield* runCeremony(started, (lease, deadline) =>\n Effect.gen(function* () {\n if (mediation === \"conditional\") yield* conditional;\n const timeoutMillis = yield* remaining(deadline);\n const options = started.options;\n\n const publicKey: PublicKeyCredentialRequestOptions = {\n challenge: base64URLStringToBuffer(options.challenge),\n rpId: options.rpId,\n userVerification: options.userVerification,\n timeout: timeoutMillis,\n allowCredentials: options.allowCredentials.map((value) => ({\n type: value.type,\n id: base64URLStringToBuffer(value.id),\n })),\n };\n\n yield* remaining(deadline);\n\n const response = yield* authenticationResponse(\n yield* native(lease, { _tag: \"Get\", publicKey, mediation }),\n );\n\n yield* remaining(deadline);\n\n return yield* snapshotPasskey(PasskeyBrowserAuthentication, {\n flowId: started.flowId,\n response: Redacted.make(response),\n }).pipe(Effect.mapError(unavailable));\n }),\n );\n }, unexpected);\n\n return PasskeyBrowser.of({ capabilities, register, authenticate });\n }),\n);\n\nexport const layerSimpleWebAuthnPasskeyBrowser: Layer.Layer<PasskeyBrowser> = Layer.effect(\n PasskeyBrowser,\n makeSimpleWebAuthnPasskeyBrowser(),\n);\n"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { SimpleWebAuthnPasskeyProtocolOptions } from "./models.mjs";
2
+ import { PasskeyConfigurationError, PasskeyProfile, PasskeyProtocolRejected, samePasskey, snapshotPasskey } from "@yielded/auth/Passkey";
3
+ import { Cause, Effect } from "effect";
4
+ import { reportAuthFailure } from "@yielded/auth/Persistence";
5
+ import { parse } from "tldts";
6
+ //#region src/internal/server/configuration.ts
7
+ const captureSimpleWebAuthnProfiles = Effect.fn("captureSimpleWebAuthnProfiles")(function* (options) {
8
+ const captured = yield* snapshotPasskey(SimpleWebAuthnPasskeyProtocolOptions, options).pipe(Effect.mapError(() => PasskeyConfigurationError.make({})));
9
+ const keys = /* @__PURE__ */ new Set();
10
+ for (const profile of captured.profiles) {
11
+ const key = `${profile.rpId}/${profile.profileId}/${profile.generation}`;
12
+ if (keys.has(key) || new Set(profile.origins).size !== profile.origins.length || new Set(profile.algorithms).size !== profile.algorithms.length || profile.primarySignIn && (profile.residentKey !== "required" || profile.userVerification !== "required")) return yield* PasskeyConfigurationError.make({});
13
+ keys.add(key);
14
+ if (!(yield* Effect.try({
15
+ try: () => {
16
+ const host = new URL(`https://${profile.rpId}`).hostname;
17
+ if (host !== profile.rpId) return false;
18
+ const domain = parse(host, {
19
+ allowIcannDomains: true,
20
+ allowPrivateDomains: true,
21
+ extractHostname: false,
22
+ validateHostname: true,
23
+ detectIp: true
24
+ });
25
+ if (host === "localhost" ? !profile.developmentLocalhost : domain.isIp || domain.domain === null || domain.publicSuffix === host) return false;
26
+ return profile.origins.every((origin) => {
27
+ const url = new URL(origin);
28
+ return url.origin === origin && url.username === "" && url.password === "" && (url.protocol === "https:" || profile.developmentLocalhost && profile.rpId === "localhost" && url.hostname === "localhost" && url.protocol === "http:") && (url.hostname === profile.rpId || profile.rpId !== "localhost" && url.hostname.endsWith(`.${profile.rpId}`));
29
+ });
30
+ },
31
+ catch: () => PasskeyConfigurationError.make({})
32
+ }))) return yield* PasskeyConfigurationError.make({});
33
+ }
34
+ return Effect.fn("selectSimpleWebAuthnProfile")(function* (input) {
35
+ const profile = captured.profiles.find((value) => value.rpId === input.rpId && value.profileId === input.profileId && value.generation === input.generation);
36
+ if (profile === void 0 || !(yield* samePasskey(PasskeyProfile, profile, input))) return yield* PasskeyProtocolRejected.make({});
37
+ return profile;
38
+ });
39
+ }, Effect.catchCause((cause) => Cause.hasDies(cause) ? reportAuthFailure("passkey-protocol", cause).pipe(Effect.andThen(Effect.fail(PasskeyConfigurationError.make({})))) : Effect.failCause(cause)));
40
+ //#endregion
41
+ export { captureSimpleWebAuthnProfiles };
42
+
43
+ //# sourceMappingURL=configuration.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configuration.mjs","names":[],"sources":["../../../src/internal/server/configuration.ts"],"sourcesContent":["import {\n PasskeyConfigurationError,\n PasskeyProtocolRejected,\n PasskeyProfile,\n samePasskey,\n snapshotPasskey,\n} from \"@yielded/auth/Passkey\";\nimport { reportAuthFailure } from \"@yielded/auth/Persistence\";\nimport { Cause, Effect } from \"effect\";\nimport { parse } from \"tldts\";\n\nimport { SimpleWebAuthnPasskeyProtocolOptions } from \"./models\";\n\nexport const captureSimpleWebAuthnProfiles = Effect.fn(\"captureSimpleWebAuthnProfiles\")(\n function* (options: SimpleWebAuthnPasskeyProtocolOptions) {\n const captured = yield* snapshotPasskey(SimpleWebAuthnPasskeyProtocolOptions, options).pipe(\n Effect.mapError(() => PasskeyConfigurationError.make({})),\n );\n\n const keys = new Set<string>();\n\n for (const profile of captured.profiles) {\n const key = `${profile.rpId}/${profile.profileId}/${profile.generation}`;\n\n if (\n keys.has(key) ||\n new Set(profile.origins).size !== profile.origins.length ||\n new Set(profile.algorithms).size !== profile.algorithms.length ||\n (profile.primarySignIn &&\n (profile.residentKey !== \"required\" || profile.userVerification !== \"required\"))\n ) {\n return yield* PasskeyConfigurationError.make({});\n }\n keys.add(key);\n\n const valid = yield* Effect.try({\n try: () => {\n const host = new URL(`https://${profile.rpId}`).hostname;\n\n if (host !== profile.rpId) return false;\n\n const domain = parse(host, {\n allowIcannDomains: true,\n allowPrivateDomains: true,\n extractHostname: false,\n validateHostname: true,\n detectIp: true,\n });\n\n if (\n host === \"localhost\"\n ? !profile.developmentLocalhost\n : domain.isIp || domain.domain === null || domain.publicSuffix === host\n )\n return false;\n\n return profile.origins.every((origin) => {\n const url = new URL(origin);\n\n return (\n url.origin === origin &&\n url.username === \"\" &&\n url.password === \"\" &&\n (url.protocol === \"https:\" ||\n (profile.developmentLocalhost &&\n profile.rpId === \"localhost\" &&\n url.hostname === \"localhost\" &&\n url.protocol === \"http:\")) &&\n (url.hostname === profile.rpId ||\n (profile.rpId !== \"localhost\" && url.hostname.endsWith(`.${profile.rpId}`)))\n );\n });\n },\n catch: () => PasskeyConfigurationError.make({}),\n });\n\n if (!valid) return yield* PasskeyConfigurationError.make({});\n }\n\n return Effect.fn(\"selectSimpleWebAuthnProfile\")(function* (input: PasskeyProfile) {\n const profile = captured.profiles.find(\n (value) =>\n value.rpId === input.rpId &&\n value.profileId === input.profileId &&\n value.generation === input.generation,\n );\n\n if (profile === undefined || !(yield* samePasskey(PasskeyProfile, profile, input)))\n return yield* PasskeyProtocolRejected.make({});\n\n return profile;\n });\n },\n Effect.catchCause((cause) =>\n Cause.hasDies(cause)\n ? reportAuthFailure(\"passkey-protocol\", cause).pipe(\n Effect.andThen(Effect.fail(PasskeyConfigurationError.make({}))),\n )\n : Effect.failCause(cause),\n ),\n);\n"],"mappings":";;;;;;AAaA,MAAa,gCAAgC,OAAO,GAAG,+BAA+B,CAAC,CACrF,WAAW,SAA+C;CACxD,MAAM,WAAW,OAAO,gBAAgB,sCAAsC,OAAO,CAAC,CAAC,KACrF,OAAO,eAAe,0BAA0B,KAAK,CAAC,CAAC,CAAC,CAC1D;CAEA,MAAM,uBAAO,IAAI,IAAY;CAE7B,KAAK,MAAM,WAAW,SAAS,UAAU;EACvC,MAAM,MAAM,GAAG,QAAQ,KAAK,GAAG,QAAQ,UAAU,GAAG,QAAQ;EAE5D,IACE,KAAK,IAAI,GAAG,KACZ,IAAI,IAAI,QAAQ,OAAO,CAAC,CAAC,SAAS,QAAQ,QAAQ,UAClD,IAAI,IAAI,QAAQ,UAAU,CAAC,CAAC,SAAS,QAAQ,WAAW,UACvD,QAAQ,kBACN,QAAQ,gBAAgB,cAAc,QAAQ,qBAAqB,aAEtE,OAAO,OAAO,0BAA0B,KAAK,CAAC,CAAC;EAEjD,KAAK,IAAI,GAAG;EA2CZ,IAAI,EAAC,OAzCgB,OAAO,IAAI;GAC9B,WAAW;IACT,MAAM,OAAO,IAAI,IAAI,WAAW,QAAQ,MAAM,CAAC,CAAC;IAEhD,IAAI,SAAS,QAAQ,MAAM,OAAO;IAElC,MAAM,SAAS,MAAM,MAAM;KACzB,mBAAmB;KACnB,qBAAqB;KACrB,iBAAiB;KACjB,kBAAkB;KAClB,UAAU;IACZ,CAAC;IAED,IACE,SAAS,cACL,CAAC,QAAQ,uBACT,OAAO,QAAQ,OAAO,WAAW,QAAQ,OAAO,iBAAiB,MAErE,OAAO;IAET,OAAO,QAAQ,QAAQ,OAAO,WAAW;KACvC,MAAM,MAAM,IAAI,IAAI,MAAM;KAE1B,OACE,IAAI,WAAW,UACf,IAAI,aAAa,MACjB,IAAI,aAAa,OAChB,IAAI,aAAa,YACf,QAAQ,wBACP,QAAQ,SAAS,eACjB,IAAI,aAAa,eACjB,IAAI,aAAa,aACpB,IAAI,aAAa,QAAQ,QACvB,QAAQ,SAAS,eAAe,IAAI,SAAS,SAAS,IAAI,QAAQ,MAAM;IAE/E,CAAC;GACH;GACA,aAAa,0BAA0B,KAAK,CAAC,CAAC;EAChD,CAAC,IAEW,OAAO,OAAO,0BAA0B,KAAK,CAAC,CAAC;CAC7D;CAEA,OAAO,OAAO,GAAG,6BAA6B,CAAC,CAAC,WAAW,OAAuB;EAChF,MAAM,UAAU,SAAS,SAAS,MAC/B,UACC,MAAM,SAAS,MAAM,QACrB,MAAM,cAAc,MAAM,aAC1B,MAAM,eAAe,MAAM,UAC/B;EAEA,IAAI,YAAY,KAAA,KAAa,EAAE,OAAO,YAAY,gBAAgB,SAAS,KAAK,IAC9E,OAAO,OAAO,wBAAwB,KAAK,CAAC,CAAC;EAE/C,OAAO;CACT,CAAC;AACH,GACA,OAAO,YAAY,UACjB,MAAM,QAAQ,KAAK,IACf,kBAAkB,oBAAoB,KAAK,CAAC,CAAC,KAC3C,OAAO,QAAQ,OAAO,KAAK,0BAA0B,KAAK,CAAC,CAAC,CAAC,CAAC,CAChE,IACA,OAAO,UAAU,KAAK,CAC5B,CACF"}
@@ -0,0 +1,20 @@
1
+ import { Schema } from "effect";
2
+ //#region src/internal/server/models.d.ts
3
+ export declare const SimpleWebAuthnPasskeyProtocolOptions: Schema.Struct<{
4
+ readonly profiles: Schema.NonEmptyArray<Schema.Struct<{
5
+ readonly profileId: Schema.String;
6
+ readonly generation: Schema.Int;
7
+ readonly rpId: Schema.String;
8
+ readonly rpName: Schema.NonEmptyString;
9
+ readonly origins: Schema.NonEmptyArray<Schema.NonEmptyString>;
10
+ readonly developmentLocalhost: Schema.Boolean;
11
+ readonly residentKey: Schema.Literals<readonly ["required", "preferred", "discouraged"]>;
12
+ readonly userVerification: Schema.Literals<readonly ["required", "preferred"]>;
13
+ readonly primarySignIn: Schema.Boolean;
14
+ readonly attestation: Schema.Literal<"none">;
15
+ readonly algorithms: Schema.NonEmptyArray<Schema.Literals<readonly [-7, -257]>>;
16
+ }>>;
17
+ }>;
18
+ export type SimpleWebAuthnPasskeyProtocolOptions = typeof SimpleWebAuthnPasskeyProtocolOptions.Type;
19
+ //#endregion
20
+ //# sourceMappingURL=models.d.mts.map
@@ -0,0 +1,8 @@
1
+ import { PasskeyProfile } from "@yielded/auth/Passkey";
2
+ import { Schema } from "effect";
3
+ //#region src/internal/server/models.ts
4
+ const SimpleWebAuthnPasskeyProtocolOptions = Schema.Struct({ profiles: Schema.NonEmptyArray(PasskeyProfile).check(Schema.isMaxLength(64)) });
5
+ //#endregion
6
+ export { SimpleWebAuthnPasskeyProtocolOptions };
7
+
8
+ //# sourceMappingURL=models.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.mjs","names":[],"sources":["../../../src/internal/server/models.ts"],"sourcesContent":["import { PasskeyProfile } from \"@yielded/auth/Passkey\";\nimport { Schema } from \"effect\";\n\nexport const SimpleWebAuthnPasskeyProtocolOptions = Schema.Struct({\n profiles: Schema.NonEmptyArray(PasskeyProfile).check(Schema.isMaxLength(64)),\n});\n\nexport type SimpleWebAuthnPasskeyProtocolOptions = typeof SimpleWebAuthnPasskeyProtocolOptions.Type;\n"],"mappings":";;;AAGA,MAAa,uCAAuC,OAAO,OAAO,EAChE,UAAU,OAAO,cAAc,cAAc,CAAC,CAAC,MAAM,OAAO,YAAY,EAAE,CAAC,EAC7E,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { PasskeyAssertionVerified, PasskeyAuthenticationOptions, PasskeyCeremony, PasskeyConfig, PasskeyConfigurationError, PasskeyCredential, PasskeyDescriptor, PasskeyProfile, PasskeyProtocol, PasskeyProtocolRejected, PasskeyRegistrationOptions, PasskeyRegistrationVerified, PasskeyUnavailable, PasskeyUserHandle } from "@yielded/auth/Passkey";
2
+ import { Effect, Layer, Redacted } from "effect";
3
+ //#region src/internal/server/protocol.d.ts
4
+ export declare const makeSimpleWebAuthnPasskeyProtocol: (options: {
5
+ readonly profiles: readonly [{
6
+ readonly profileId: string;
7
+ readonly generation: number;
8
+ readonly rpId: string;
9
+ readonly rpName: string;
10
+ readonly origins: readonly [string, ...string[]];
11
+ readonly developmentLocalhost: boolean;
12
+ readonly residentKey: "discouraged" | "preferred" | "required";
13
+ readonly userVerification: "preferred" | "required";
14
+ readonly primarySignIn: boolean;
15
+ readonly attestation: "none";
16
+ readonly algorithms: readonly [-257 | -7, ...(-257 | -7)[]];
17
+ }, ...{
18
+ readonly profileId: string;
19
+ readonly generation: number;
20
+ readonly rpId: string;
21
+ readonly rpName: string;
22
+ readonly origins: readonly [string, ...string[]];
23
+ readonly developmentLocalhost: boolean;
24
+ readonly residentKey: "discouraged" | "preferred" | "required";
25
+ readonly userVerification: "preferred" | "required";
26
+ readonly primarySignIn: boolean;
27
+ readonly attestation: "none";
28
+ readonly algorithms: readonly [-257 | -7, ...(-257 | -7)[]];
29
+ }[]];
30
+ }) => Effect.Effect<{
31
+ readonly prepareAuthentication: (input: {
32
+ readonly profile: PasskeyProfile;
33
+ readonly challenge: string;
34
+ readonly timeoutMillis: number;
35
+ readonly allowedCredentials: ReadonlyArray<typeof PasskeyDescriptor.Type>;
36
+ }) => Effect.Effect<PasskeyAuthenticationOptions, PasskeyProtocolRejected | PasskeyUnavailable>;
37
+ readonly prepareRegistration: (input: {
38
+ readonly profile: PasskeyProfile;
39
+ readonly challenge: string;
40
+ readonly timeoutMillis: number;
41
+ readonly userHandle: PasskeyUserHandle;
42
+ readonly name: string;
43
+ readonly displayName: string;
44
+ readonly excludedCredentials: ReadonlyArray<typeof PasskeyDescriptor.Type>;
45
+ }) => Effect.Effect<PasskeyRegistrationOptions, PasskeyProtocolRejected | PasskeyUnavailable>;
46
+ readonly verifyAuthentication: (input: {
47
+ readonly ceremony: PasskeyCeremony;
48
+ readonly credential: PasskeyCredential;
49
+ readonly response: Redacted.Redacted<string>;
50
+ }) => Effect.Effect<PasskeyAssertionVerified, PasskeyProtocolRejected | PasskeyUnavailable>;
51
+ readonly verifyRegistration: (input: {
52
+ readonly ceremony: PasskeyCeremony;
53
+ readonly response: Redacted.Redacted<string>;
54
+ }) => Effect.Effect<PasskeyRegistrationVerified, PasskeyProtocolRejected | PasskeyUnavailable>;
55
+ }, PasskeyConfigurationError, never>;
56
+ /** Requires the same host profiles as the strategy. */
57
+ export declare const layerSimpleWebAuthnPasskeyProtocol: Layer.Layer<PasskeyProtocol, PasskeyConfigurationError, PasskeyConfig>;
58
+ //#endregion
59
+ //# sourceMappingURL=protocol.d.mts.map
@@ -0,0 +1,400 @@
1
+ import { captureSimpleWebAuthnProfiles } from "./configuration.mjs";
2
+ import { PasskeyAssertion, PasskeyAssertionVerified, PasskeyAttestation, PasskeyAuthenticationOptions, PasskeyCeremony, PasskeyChallenge, PasskeyConfig, PasskeyCounter, PasskeyCredential, PasskeyDescriptor, PasskeyLabel, PasskeyProfile, PasskeyProtocol, PasskeyProtocolCredentialId, PasskeyProtocolRejected, PasskeyRegistrationOptions, PasskeyRegistrationVerified, PasskeyUnavailable, PasskeyUserHandle, samePasskey, snapshotPasskey } from "@yielded/auth/Passkey";
3
+ import { Cause, DateTime, Effect, Encoding, Layer, Redacted, Schema } from "effect";
4
+ import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse } from "@simplewebauthn/server";
5
+ import { decodeAttestationObject, decodeClientDataJSON, decodeCredentialPublicKey, isoCBOR, parseAuthenticatorData } from "@simplewebauthn/server/helpers";
6
+ import { reportAuthFailure } from "@yielded/auth/Persistence";
7
+ //#region src/internal/server/protocol.ts
8
+ const rejected = () => PasskeyProtocolRejected.make({});
9
+ const unavailable = () => PasskeyUnavailable.make({});
10
+ const unexpected = (effect) => effect.pipe(Effect.catchCause((cause) => Cause.hasDies(cause) ? reportAuthFailure("passkey-protocol", cause).pipe(Effect.andThen(Effect.fail(unavailable()))) : Effect.failCause(cause)));
11
+ const maintained = (call) => Effect.tryPromise(call).pipe(Effect.onError((cause) => reportAuthFailure("passkey-protocol", cause)), Effect.mapError(unavailable));
12
+ const parsed = (call) => Effect.try({
13
+ try: call,
14
+ catch: rejected
15
+ });
16
+ const decode = (schema, input) => Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError(rejected));
17
+ const capture = (schema, input) => snapshotPasskey(schema, input).pipe(Effect.mapError(rejected));
18
+ const sameBytes = (left, right) => left.length === right.length && left.every((byte, index) => byte === right[index]);
19
+ const binary = (minimum, maximum) => Schema.String.check(Schema.isMaxLength(Math.ceil(maximum * 4 / 3)), Schema.makeFilter((value) => {
20
+ const result = Encoding.decodeBase64Url(value);
21
+ return result._tag === "Success" && result.success.length >= minimum && result.success.length <= maximum && Encoding.encodeBase64Url(result.success) === value;
22
+ }));
23
+ const bytes = Effect.fn("simpleWebAuthnBytes")(function* (value) {
24
+ const result = Encoding.decodeBase64Url(value);
25
+ if (result._tag === "Failure") return yield* rejected();
26
+ return new Uint8Array(result.success);
27
+ });
28
+ const duration = Schema.Int.check(Schema.isBetween({
29
+ minimum: 1,
30
+ maximum: 3e5
31
+ }));
32
+ const prepareAuthenticationInput = Schema.Struct({
33
+ profile: PasskeyProfile,
34
+ challenge: PasskeyChallenge,
35
+ timeoutMillis: duration,
36
+ allowedCredentials: Schema.Array(PasskeyDescriptor).check(Schema.isMaxLength(64))
37
+ });
38
+ const prepareRegistrationInput = Schema.Struct({
39
+ profile: PasskeyProfile,
40
+ challenge: PasskeyChallenge,
41
+ timeoutMillis: duration,
42
+ userHandle: PasskeyUserHandle,
43
+ name: PasskeyLabel,
44
+ displayName: PasskeyLabel,
45
+ excludedCredentials: Schema.Array(PasskeyDescriptor).check(Schema.isMaxLength(64))
46
+ });
47
+ const authenticationInput = Schema.Struct({
48
+ ceremony: PasskeyCeremony,
49
+ credential: PasskeyCredential,
50
+ response: PasskeyAssertion
51
+ });
52
+ const registrationInput = Schema.Struct({
53
+ ceremony: PasskeyCeremony,
54
+ response: PasskeyAttestation
55
+ });
56
+ const envelope = {
57
+ id: PasskeyProtocolCredentialId,
58
+ rawId: PasskeyProtocolCredentialId,
59
+ type: Schema.Literal("public-key")
60
+ };
61
+ const authenticationResponse = Schema.Struct({
62
+ ...envelope,
63
+ response: Schema.Struct({
64
+ clientDataJSON: binary(1, 8192),
65
+ authenticatorData: binary(37, 16384),
66
+ signature: binary(1, 1024),
67
+ userHandle: Schema.optionalKey(Schema.NullOr(PasskeyUserHandle))
68
+ })
69
+ });
70
+ const registrationResponse = Schema.Struct({
71
+ ...envelope,
72
+ response: Schema.Struct({
73
+ clientDataJSON: binary(1, 8192),
74
+ attestationObject: binary(1, 65536)
75
+ })
76
+ });
77
+ const clientData = Schema.Struct({
78
+ type: Schema.String,
79
+ challenge: PasskeyChallenge,
80
+ origin: Schema.String.check(Schema.isMaxLength(2048)),
81
+ crossOrigin: Schema.optionalKey(Schema.Boolean),
82
+ topOrigin: Schema.optionalKey(Schema.Unknown)
83
+ });
84
+ const cborLeaf = Schema.Union([
85
+ Schema.String.check(Schema.isMaxLength(8192)),
86
+ Schema.Number.check(Schema.isFinite()),
87
+ Schema.Boolean,
88
+ Schema.Null,
89
+ Schema.Uint8Array.check(Schema.makeFilter((value) => value.length <= 16384))
90
+ ]);
91
+ const cborValue = (depth) => {
92
+ if (depth === 0) return cborLeaf;
93
+ const child = cborValue(depth - 1);
94
+ return Schema.Union([
95
+ cborLeaf,
96
+ Schema.Array(child).check(Schema.isMaxLength(64)),
97
+ Schema.ReadonlyMap(Schema.Union([Schema.String.check(Schema.isMaxLength(128)), Schema.Int]), child).check(Schema.makeFilter((value) => value.size <= 32))
98
+ ]);
99
+ };
100
+ const cborMap = Schema.ReadonlyMap(Schema.Union([Schema.String.check(Schema.isMaxLength(128)), Schema.Int]), cborValue(4)).check(Schema.makeFilter((value) => value.size <= 32));
101
+ const encodeCbor = (value) => {
102
+ return isoCBOR.encode(value);
103
+ };
104
+ const exactMap = Effect.fn("simpleWebAuthnExactMap")(function* (input, value) {
105
+ const result = yield* decode(cborMap, value);
106
+ const encoded = yield* parsed(() => encodeCbor(result));
107
+ if (!sameBytes(input, encoded)) return yield* rejected();
108
+ return result;
109
+ });
110
+ const byteValue = (minimum, maximum) => Schema.Uint8Array.check(Schema.makeFilter((value) => value.length >= minimum && value.length <= maximum));
111
+ const fresh = Effect.fn("simpleWebAuthnFresh")(function* (ceremony) {
112
+ const now = DateTime.toEpochMillis(yield* DateTime.now);
113
+ if (ceremony.issuedAtMillis > now || now >= ceremony.expiresAtMillis || now >= ceremony.requestBindingExpiresAtMillis || ceremony.expiresAtMillis <= ceremony.issuedAtMillis) return yield* rejected();
114
+ });
115
+ const checkClient = Effect.fn("simpleWebAuthnClientData")(function* (value, ceremony, type) {
116
+ const client = yield* decode(clientData, yield* parsed(() => decodeClientDataJSON(value)));
117
+ if (client.type !== type || client.challenge !== ceremony.challenge || !ceremony.profile.origins.includes(client.origin) || client.crossOrigin === true || "topOrigin" in client) return yield* rejected();
118
+ return client;
119
+ });
120
+ const checkAuthenticator = Effect.fn("simpleWebAuthnAuthenticatorData")(function* (original, registration, profile) {
121
+ if (original.length < 37 || original.length > 16384 || Boolean(original[32] & 64) !== registration) return yield* rejected();
122
+ let prefix = 37;
123
+ if (registration) {
124
+ if (original.length < 55) return yield* rejected();
125
+ const length = new DataView(original.buffer, original.byteOffset, original.byteLength).getUint16(53, false);
126
+ if (length < 1 || length > 1023 || original.length < 55 + length) return yield* rejected();
127
+ prefix = 55 + length;
128
+ }
129
+ const result = yield* parsed(() => parseAuthenticatorData(new Uint8Array(original)));
130
+ if (!result.flags.up || profile.userVerification === "required" && !result.flags.uv || result.flags.bs && !result.flags.be || result.flags.at !== registration || result.flags.flagsInt !== original[32]) return yield* rejected();
131
+ yield* decode(PasskeyCounter, result.counter);
132
+ const chunks = [original.slice(0, prefix)];
133
+ if (registration) {
134
+ const id = yield* decode(byteValue(1, 1023), result.credentialID);
135
+ const key = yield* decode(byteValue(1, 8192), result.credentialPublicKey);
136
+ if (!sameBytes(id, original.slice(55, prefix))) return yield* rejected();
137
+ chunks.push(key);
138
+ }
139
+ if (result.flags.ed) {
140
+ const extension = yield* decode(byteValue(1, 16384), result.extensionsDataBuffer);
141
+ yield* exactMap(extension, yield* parsed(() => isoCBOR.decodeFirst(new Uint8Array(extension))));
142
+ chunks.push(extension);
143
+ } else if (result.extensionsDataBuffer !== void 0) return yield* rejected();
144
+ if (chunks.reduce((sum, chunk) => sum + chunk.length, 0) !== original.length) return yield* rejected();
145
+ let offset = 0;
146
+ for (const chunk of chunks) {
147
+ if (!sameBytes(chunk, original.slice(offset, offset + chunk.length))) return yield* rejected();
148
+ offset += chunk.length;
149
+ }
150
+ const rpHash = new Uint8Array(yield* maintained(() => globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(profile.rpId))));
151
+ if (!sameBytes(rpHash, original.slice(0, 32))) return yield* rejected();
152
+ return result;
153
+ });
154
+ const checkKey = Effect.fn("simpleWebAuthnPublicKey")(function* (input, allowed, expected) {
155
+ const map = yield* exactMap(input, yield* parsed(() => decodeCredentialPublicKey(new Uint8Array(input))));
156
+ const algorithmId = yield* decode(Schema.Literals([-7, -257]), map.get(3));
157
+ if (!allowed.includes(algorithmId) || expected !== void 0 && algorithmId !== expected) return yield* rejected();
158
+ let jwk;
159
+ let algorithm;
160
+ if (algorithmId === -7) {
161
+ if (map.size !== 5 || ![
162
+ 1,
163
+ 3,
164
+ -1,
165
+ -2,
166
+ -3
167
+ ].every((key) => map.has(key)) || map.get(1) !== 2 || map.get(-1) !== 1) return yield* rejected();
168
+ const x = yield* decode(byteValue(32, 32), map.get(-2));
169
+ const y = yield* decode(byteValue(32, 32), map.get(-3));
170
+ jwk = {
171
+ kty: "EC",
172
+ crv: "P-256",
173
+ x: Encoding.encodeBase64Url(x),
174
+ y: Encoding.encodeBase64Url(y),
175
+ ext: false
176
+ };
177
+ algorithm = {
178
+ name: "ECDSA",
179
+ namedCurve: "P-256"
180
+ };
181
+ } else {
182
+ if (map.size !== 4 || ![
183
+ 1,
184
+ 3,
185
+ -1,
186
+ -2
187
+ ].every((key) => map.has(key)) || map.get(1) !== 3) return yield* rejected();
188
+ const n = yield* decode(byteValue(256, 512), map.get(-1));
189
+ const e = yield* decode(byteValue(1, 4), map.get(-2));
190
+ const exponent = e.reduce((value, byte) => value * 256 + byte, 0);
191
+ if (n[0] < 128 || (n[n.length - 1] & 1) === 0 || e[0] === 0 || exponent < 3 || exponent % 2 !== 1) return yield* rejected();
192
+ jwk = {
193
+ kty: "RSA",
194
+ alg: "RS256",
195
+ n: Encoding.encodeBase64Url(n),
196
+ e: Encoding.encodeBase64Url(e),
197
+ ext: false
198
+ };
199
+ algorithm = {
200
+ name: "RSASSA-PKCS1-v1_5",
201
+ hash: "SHA-256"
202
+ };
203
+ }
204
+ yield* maintained(() => globalThis.crypto.subtle.importKey("jwk", jwk, algorithm, false, ["verify"]));
205
+ return algorithmId;
206
+ });
207
+ const makeSimpleWebAuthnPasskeyProtocol = Effect.fn("makeSimpleWebAuthnPasskeyProtocol")(function* (options) {
208
+ const select = yield* captureSimpleWebAuthnProfiles(options);
209
+ const prepareAuthentication = Effect.fn("SimpleWebAuthnPasskeyProtocol.prepareAuthentication")(function* (input) {
210
+ const fixed = yield* capture(prepareAuthenticationInput, input);
211
+ const profile = yield* select(fixed.profile);
212
+ const challenge = yield* bytes(fixed.challenge);
213
+ const result = yield* maintained(() => generateAuthenticationOptions({
214
+ rpID: profile.rpId,
215
+ challenge,
216
+ timeout: fixed.timeoutMillis,
217
+ userVerification: profile.userVerification,
218
+ allowCredentials: fixed.allowedCredentials.map((value) => ({ ...value }))
219
+ }));
220
+ const value = yield* decode(PasskeyAuthenticationOptions, {
221
+ challenge: result.challenge,
222
+ rpId: result.rpId,
223
+ timeout: result.timeout,
224
+ userVerification: result.userVerification,
225
+ allowCredentials: result.allowCredentials ?? []
226
+ });
227
+ if (!(yield* samePasskey(PasskeyAuthenticationOptions, value, {
228
+ challenge: fixed.challenge,
229
+ rpId: profile.rpId,
230
+ timeout: fixed.timeoutMillis,
231
+ userVerification: profile.userVerification,
232
+ allowCredentials: fixed.allowedCredentials
233
+ }))) return yield* unavailable();
234
+ return yield* capture(PasskeyAuthenticationOptions, value);
235
+ }, unexpected);
236
+ const prepareRegistration = Effect.fn("SimpleWebAuthnPasskeyProtocol.prepareRegistration")(function* (input) {
237
+ const fixed = yield* capture(prepareRegistrationInput, input);
238
+ const profile = yield* select(fixed.profile);
239
+ const challenge = yield* bytes(fixed.challenge), userID = yield* bytes(fixed.userHandle);
240
+ const result = yield* maintained(() => generateRegistrationOptions({
241
+ rpName: profile.rpName,
242
+ rpID: profile.rpId,
243
+ userName: fixed.name,
244
+ userDisplayName: fixed.displayName,
245
+ userID,
246
+ challenge,
247
+ timeout: fixed.timeoutMillis,
248
+ attestationType: "none",
249
+ supportedAlgorithmIDs: [...profile.algorithms],
250
+ authenticatorSelection: {
251
+ residentKey: profile.residentKey,
252
+ userVerification: profile.userVerification
253
+ },
254
+ excludeCredentials: fixed.excludedCredentials.map((value) => ({ ...value }))
255
+ }));
256
+ const value = yield* decode(PasskeyRegistrationOptions, {
257
+ challenge: result.challenge,
258
+ rp: result.rp,
259
+ user: result.user,
260
+ pubKeyCredParams: result.pubKeyCredParams,
261
+ timeout: result.timeout,
262
+ attestation: result.attestation,
263
+ authenticatorSelection: result.authenticatorSelection,
264
+ excludeCredentials: result.excludeCredentials ?? []
265
+ });
266
+ const expected = {
267
+ challenge: fixed.challenge,
268
+ rp: {
269
+ id: profile.rpId,
270
+ name: profile.rpName
271
+ },
272
+ user: {
273
+ id: fixed.userHandle,
274
+ name: fixed.name,
275
+ displayName: fixed.displayName
276
+ },
277
+ pubKeyCredParams: profile.algorithms.map((alg) => ({
278
+ type: "public-key",
279
+ alg
280
+ })),
281
+ timeout: fixed.timeoutMillis,
282
+ attestation: "none",
283
+ authenticatorSelection: {
284
+ residentKey: profile.residentKey,
285
+ userVerification: profile.userVerification
286
+ },
287
+ excludeCredentials: fixed.excludedCredentials
288
+ };
289
+ if (!(yield* samePasskey(PasskeyRegistrationOptions, value, yield* decode(PasskeyRegistrationOptions, expected)))) return yield* unavailable();
290
+ return yield* capture(PasskeyRegistrationOptions, value);
291
+ }, unexpected);
292
+ const verifyRegistration = Effect.fn("SimpleWebAuthnPasskeyProtocol.verifyRegistration")(function* (input) {
293
+ const fixed = yield* capture(registrationInput, input), ceremony = fixed.ceremony;
294
+ const profile = yield* select(ceremony.profile);
295
+ if ((ceremony.purpose !== "registration" || ceremony.context._tag !== "Registration") && (ceremony.purpose !== "enrollment" || ceremony.context._tag !== "Enrollment")) return yield* rejected();
296
+ yield* fresh(ceremony);
297
+ const wire = yield* decode(Schema.fromJsonString(registrationResponse), Redacted.value(fixed.response));
298
+ if (wire.id !== wire.rawId) return yield* rejected();
299
+ const client = yield* checkClient(wire.response.clientDataJSON, ceremony, "webauthn.create");
300
+ const attestationBytes = yield* bytes(wire.response.attestationObject);
301
+ const attestation = yield* exactMap(attestationBytes, yield* parsed(() => decodeAttestationObject(new Uint8Array(attestationBytes))));
302
+ if (attestation.size !== 3 || attestation.get("fmt") !== "none" || !attestation.has("attStmt") || !attestation.has("authData")) return yield* rejected();
303
+ if ((yield* decode(cborMap, attestation.get("attStmt"))).size !== 0) return yield* rejected();
304
+ const authenticatorBytes = yield* decode(byteValue(37, 16384), attestation.get("authData"));
305
+ const auth = yield* checkAuthenticator(authenticatorBytes, true, profile);
306
+ const id = yield* decode(byteValue(1, 1023), auth.credentialID);
307
+ const key = yield* decode(byteValue(1, 8192), auth.credentialPublicKey);
308
+ if (Encoding.encodeBase64Url(id) !== wire.id) return yield* rejected();
309
+ const algorithm = yield* checkKey(key, profile.algorithms);
310
+ const result = yield* maintained(() => verifyRegistrationResponse({
311
+ response: {
312
+ ...wire,
313
+ clientExtensionResults: {}
314
+ },
315
+ expectedChallenge: ceremony.challenge,
316
+ expectedOrigin: [...profile.origins],
317
+ expectedRPID: profile.rpId,
318
+ expectedType: "webauthn.create",
319
+ requireUserPresence: true,
320
+ requireUserVerification: profile.userVerification === "required",
321
+ supportedAlgorithmIDs: [...profile.algorithms]
322
+ }));
323
+ if (!result.verified || result.registrationInfo === void 0) return yield* rejected();
324
+ const info = result.registrationInfo;
325
+ if (info.fmt !== "none" || info.origin !== client.origin || info.rpID !== profile.rpId || info.credential.id !== wire.id || !sameBytes(info.credential.publicKey, key) || info.credential.counter !== auth.counter || info.userVerified !== auth.flags.uv || info.credentialDeviceType === "multiDevice" !== auth.flags.be || info.credentialBackedUp !== auth.flags.bs) return yield* rejected();
326
+ yield* fresh(ceremony);
327
+ return yield* capture(PasskeyRegistrationVerified, yield* decode(PasskeyRegistrationVerified, {
328
+ protocolCredentialId: info.credential.id,
329
+ publicKey: Encoding.encodeBase64Url(info.credential.publicKey),
330
+ algorithm,
331
+ counter: info.credential.counter,
332
+ userVerified: info.userVerified,
333
+ backupEligible: info.credentialDeviceType === "multiDevice",
334
+ backupState: info.credentialBackedUp
335
+ }));
336
+ }, unexpected);
337
+ const verifyAuthentication = Effect.fn("SimpleWebAuthnPasskeyProtocol.verifyAuthentication")(function* (input) {
338
+ const fixed = yield* capture(authenticationInput, input), ceremony = fixed.ceremony, credential = fixed.credential;
339
+ const profile = yield* select(ceremony.profile), original = yield* select(credential.profile);
340
+ if (ceremony.purpose === "registration" || ceremony.purpose === "enrollment" || {
341
+ "sign-in": "SignIn",
342
+ pending: "Pending",
343
+ "step-up": "StepUp",
344
+ action: "Action"
345
+ }[ceremony.purpose] !== ceremony.context._tag || !credential.active || credential.rpId !== profile.rpId || original.rpId !== profile.rpId || !original.algorithms.includes(credential.algorithm) || !profile.algorithms.includes(credential.algorithm)) return yield* rejected();
346
+ yield* fresh(ceremony);
347
+ const wire = yield* decode(Schema.fromJsonString(authenticationResponse), Redacted.value(fixed.response));
348
+ const userHandle = wire.response.userHandle ?? void 0;
349
+ if (wire.id !== wire.rawId || wire.id !== credential.protocolCredentialId || userHandle !== void 0 && userHandle !== credential.userHandle || ceremony.context._tag === "SignIn" && userHandle === void 0 || ceremony.allowedCredentials.length > 0 && !ceremony.allowedCredentials.some((value) => value.id === wire.id)) return yield* rejected();
350
+ const client = yield* checkClient(wire.response.clientDataJSON, ceremony, "webauthn.get");
351
+ const auth = yield* checkAuthenticator(yield* bytes(wire.response.authenticatorData), false, profile);
352
+ if (auth.flags.be !== credential.backupEligible || !credential.backupEligible && (credential.counter > 0 || auth.counter > 0) && auth.counter <= credential.counter) return yield* rejected();
353
+ const publicKey = yield* bytes(credential.publicKey);
354
+ yield* checkKey(publicKey, profile.algorithms, credential.algorithm);
355
+ const result = yield* maintained(() => verifyAuthenticationResponse({
356
+ response: {
357
+ ...wire,
358
+ response: {
359
+ ...wire.response,
360
+ userHandle
361
+ },
362
+ clientExtensionResults: {}
363
+ },
364
+ expectedChallenge: ceremony.challenge,
365
+ expectedOrigin: [...profile.origins],
366
+ expectedRPID: profile.rpId,
367
+ expectedType: "webauthn.get",
368
+ requireUserVerification: profile.userVerification === "required",
369
+ credential: {
370
+ id: credential.protocolCredentialId,
371
+ publicKey,
372
+ counter: credential.backupEligible ? 0 : credential.counter
373
+ }
374
+ }));
375
+ if (!result.verified) return yield* rejected();
376
+ const info = result.authenticationInfo;
377
+ if (info.credentialID !== wire.id || info.origin !== client.origin || info.rpID !== profile.rpId || info.newCounter !== auth.counter || info.userVerified !== auth.flags.uv || info.credentialDeviceType === "multiDevice" !== credential.backupEligible || info.credentialBackedUp !== auth.flags.bs) return yield* rejected();
378
+ yield* fresh(ceremony);
379
+ return yield* capture(PasskeyAssertionVerified, yield* decode(PasskeyAssertionVerified, {
380
+ protocolCredentialId: info.credentialID,
381
+ ...userHandle === void 0 ? {} : { userHandle },
382
+ counter: info.newCounter,
383
+ userVerified: info.userVerified,
384
+ backupEligible: info.credentialDeviceType === "multiDevice",
385
+ backupState: info.credentialBackedUp
386
+ }));
387
+ }, unexpected);
388
+ return PasskeyProtocol.of({
389
+ prepareAuthentication,
390
+ prepareRegistration,
391
+ verifyRegistration,
392
+ verifyAuthentication
393
+ });
394
+ });
395
+ /** Requires the same host profiles as the strategy. */
396
+ const layerSimpleWebAuthnPasskeyProtocol = Layer.effect(PasskeyProtocol, Effect.flatMap(PasskeyConfig, makeSimpleWebAuthnPasskeyProtocol));
397
+ //#endregion
398
+ export { layerSimpleWebAuthnPasskeyProtocol, makeSimpleWebAuthnPasskeyProtocol };
399
+
400
+ //# sourceMappingURL=protocol.mjs.map