@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,101 @@
1
+ import {
2
+ PasskeyConfigurationError,
3
+ PasskeyProtocolRejected,
4
+ PasskeyProfile,
5
+ samePasskey,
6
+ snapshotPasskey,
7
+ } from "@yielded/auth/Passkey";
8
+ import { reportAuthFailure } from "@yielded/auth/Persistence";
9
+ import { Cause, Effect } from "effect";
10
+ import { parse } from "tldts";
11
+
12
+ import { SimpleWebAuthnPasskeyProtocolOptions } from "./models";
13
+
14
+ export const captureSimpleWebAuthnProfiles = Effect.fn("captureSimpleWebAuthnProfiles")(
15
+ function* (options: SimpleWebAuthnPasskeyProtocolOptions) {
16
+ const captured = yield* snapshotPasskey(SimpleWebAuthnPasskeyProtocolOptions, options).pipe(
17
+ Effect.mapError(() => PasskeyConfigurationError.make({})),
18
+ );
19
+
20
+ const keys = new Set<string>();
21
+
22
+ for (const profile of captured.profiles) {
23
+ const key = `${profile.rpId}/${profile.profileId}/${profile.generation}`;
24
+
25
+ if (
26
+ keys.has(key) ||
27
+ new Set(profile.origins).size !== profile.origins.length ||
28
+ new Set(profile.algorithms).size !== profile.algorithms.length ||
29
+ (profile.primarySignIn &&
30
+ (profile.residentKey !== "required" || profile.userVerification !== "required"))
31
+ ) {
32
+ return yield* PasskeyConfigurationError.make({});
33
+ }
34
+ keys.add(key);
35
+
36
+ const valid = yield* Effect.try({
37
+ try: () => {
38
+ const host = new URL(`https://${profile.rpId}`).hostname;
39
+
40
+ if (host !== profile.rpId) return false;
41
+
42
+ const domain = parse(host, {
43
+ allowIcannDomains: true,
44
+ allowPrivateDomains: true,
45
+ extractHostname: false,
46
+ validateHostname: true,
47
+ detectIp: true,
48
+ });
49
+
50
+ if (
51
+ host === "localhost"
52
+ ? !profile.developmentLocalhost
53
+ : domain.isIp || domain.domain === null || domain.publicSuffix === host
54
+ )
55
+ return false;
56
+
57
+ return profile.origins.every((origin) => {
58
+ const url = new URL(origin);
59
+
60
+ return (
61
+ url.origin === origin &&
62
+ url.username === "" &&
63
+ url.password === "" &&
64
+ (url.protocol === "https:" ||
65
+ (profile.developmentLocalhost &&
66
+ profile.rpId === "localhost" &&
67
+ url.hostname === "localhost" &&
68
+ url.protocol === "http:")) &&
69
+ (url.hostname === profile.rpId ||
70
+ (profile.rpId !== "localhost" && url.hostname.endsWith(`.${profile.rpId}`)))
71
+ );
72
+ });
73
+ },
74
+ catch: () => PasskeyConfigurationError.make({}),
75
+ });
76
+
77
+ if (!valid) return yield* PasskeyConfigurationError.make({});
78
+ }
79
+
80
+ return Effect.fn("selectSimpleWebAuthnProfile")(function* (input: PasskeyProfile) {
81
+ const profile = captured.profiles.find(
82
+ (value) =>
83
+ value.rpId === input.rpId &&
84
+ value.profileId === input.profileId &&
85
+ value.generation === input.generation,
86
+ );
87
+
88
+ if (profile === undefined || !(yield* samePasskey(PasskeyProfile, profile, input)))
89
+ return yield* PasskeyProtocolRejected.make({});
90
+
91
+ return profile;
92
+ });
93
+ },
94
+ Effect.catchCause((cause) =>
95
+ Cause.hasDies(cause)
96
+ ? reportAuthFailure("passkey-protocol", cause).pipe(
97
+ Effect.andThen(Effect.fail(PasskeyConfigurationError.make({}))),
98
+ )
99
+ : Effect.failCause(cause),
100
+ ),
101
+ );
@@ -0,0 +1,8 @@
1
+ import { PasskeyProfile } from "@yielded/auth/Passkey";
2
+ import { Schema } from "effect";
3
+
4
+ export const SimpleWebAuthnPasskeyProtocolOptions = Schema.Struct({
5
+ profiles: Schema.NonEmptyArray(PasskeyProfile).check(Schema.isMaxLength(64)),
6
+ });
7
+
8
+ export type SimpleWebAuthnPasskeyProtocolOptions = typeof SimpleWebAuthnPasskeyProtocolOptions.Type;