@draftlab/auth 0.0.1

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.
Files changed (86) hide show
  1. package/dist/adapters/node.d.ts +18 -0
  2. package/dist/adapters/node.js +71 -0
  3. package/dist/allow-CixonwTW.d.ts +59 -0
  4. package/dist/allow-DX5cehSc.js +63 -0
  5. package/dist/allow.d.ts +2 -0
  6. package/dist/allow.js +4 -0
  7. package/dist/base-DRutbxgL.js +422 -0
  8. package/dist/client.d.ts +413 -0
  9. package/dist/client.js +209 -0
  10. package/dist/code-l_uvMR1j.d.ts +212 -0
  11. package/dist/core-8WTqfnb4.d.ts +129 -0
  12. package/dist/core-CncE5rPg.js +498 -0
  13. package/dist/core.d.ts +9 -0
  14. package/dist/core.js +14 -0
  15. package/dist/error-CWAdNAzm.d.ts +243 -0
  16. package/dist/error-DgAKK7b2.js +237 -0
  17. package/dist/error.d.ts +2 -0
  18. package/dist/error.js +3 -0
  19. package/dist/form-6XKM_cOk.js +61 -0
  20. package/dist/icon-Ci5uqGB_.js +192 -0
  21. package/dist/index.d.ts +9 -0
  22. package/dist/index.js +14 -0
  23. package/dist/keys-EEfxEGfO.js +140 -0
  24. package/dist/keys.d.ts +67 -0
  25. package/dist/keys.js +5 -0
  26. package/dist/oauth2-B7-6Z7Lc.js +155 -0
  27. package/dist/oauth2-DtKwtl8p.d.ts +176 -0
  28. package/dist/password-Cm0dRMwa.d.ts +385 -0
  29. package/dist/pkce-276Za_rZ.js +162 -0
  30. package/dist/pkce.d.ts +72 -0
  31. package/dist/pkce.js +3 -0
  32. package/dist/provider/code.d.ts +4 -0
  33. package/dist/provider/code.js +145 -0
  34. package/dist/provider/facebook.d.ts +137 -0
  35. package/dist/provider/facebook.js +85 -0
  36. package/dist/provider/github.d.ts +141 -0
  37. package/dist/provider/github.js +88 -0
  38. package/dist/provider/google.d.ts +113 -0
  39. package/dist/provider/google.js +62 -0
  40. package/dist/provider/oauth2.d.ts +4 -0
  41. package/dist/provider/oauth2.js +7 -0
  42. package/dist/provider/password.d.ts +4 -0
  43. package/dist/provider/password.js +366 -0
  44. package/dist/provider/provider.d.ts +3 -0
  45. package/dist/provider/provider.js +44 -0
  46. package/dist/provider-CwWMG-1l.d.ts +227 -0
  47. package/dist/random-SXMYlaVr.js +87 -0
  48. package/dist/random.d.ts +66 -0
  49. package/dist/random.js +3 -0
  50. package/dist/select-BjySLL8I.js +280 -0
  51. package/dist/storage/memory.d.ts +82 -0
  52. package/dist/storage/memory.js +127 -0
  53. package/dist/storage/storage.d.ts +2 -0
  54. package/dist/storage/storage.js +3 -0
  55. package/dist/storage/turso.d.ts +31 -0
  56. package/dist/storage/turso.js +117 -0
  57. package/dist/storage/unstorage.d.ts +38 -0
  58. package/dist/storage/unstorage.js +97 -0
  59. package/dist/storage-BEaqEPNQ.js +62 -0
  60. package/dist/storage-CxKerLlc.d.ts +162 -0
  61. package/dist/subject-DiQdRWGt.d.ts +62 -0
  62. package/dist/subject.d.ts +3 -0
  63. package/dist/subject.js +36 -0
  64. package/dist/theme-C9by7VXf.d.ts +209 -0
  65. package/dist/theme-CswaLtbW.js +120 -0
  66. package/dist/themes/theme.d.ts +2 -0
  67. package/dist/themes/theme.js +3 -0
  68. package/dist/types.d.ts +94 -0
  69. package/dist/types.js +0 -0
  70. package/dist/ui/base.d.ts +43 -0
  71. package/dist/ui/base.js +4 -0
  72. package/dist/ui/code.d.ts +158 -0
  73. package/dist/ui/code.js +197 -0
  74. package/dist/ui/form.d.ts +31 -0
  75. package/dist/ui/form.js +3 -0
  76. package/dist/ui/icon.d.ts +98 -0
  77. package/dist/ui/icon.js +3 -0
  78. package/dist/ui/password.d.ts +54 -0
  79. package/dist/ui/password.js +300 -0
  80. package/dist/ui/select.d.ts +233 -0
  81. package/dist/ui/select.js +6 -0
  82. package/dist/util-CSdHUFOo.js +108 -0
  83. package/dist/util-ChlgVqPN.d.ts +72 -0
  84. package/dist/util.d.ts +2 -0
  85. package/dist/util.js +3 -0
  86. package/package.json +63 -0
@@ -0,0 +1,87 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+
3
+ //#region src/random.ts
4
+ /**
5
+ * Cryptographic utilities for secure random generation and comparison operations.
6
+ * These functions are designed to prevent timing attacks and provide unbiased randomness.
7
+ */
8
+ /**
9
+ * Generates a cryptographically secure token with enhanced entropy.
10
+ * Uses Web Crypto API and provides 256 bits of entropy
11
+ *
12
+ * @param length - Length of random data in bytes (default: 32 for 256-bit security)
13
+ * @returns Base64url-encoded secure token
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const authCode = generateSecureToken()
18
+ * // Returns: "7B8kJ9mN3pQ2rS5tU8vW0xY1zA3bC6dE9fG2hI5jK8lM" (example)
19
+ *
20
+ * const refreshToken = generateSecureToken(24) // 192-bit token
21
+ * // Returns: "4A7bC9dF2gH5iJ8kL1mN4pQ7rS0tU" (example)
22
+ * ```
23
+ */
24
+ const generateSecureToken = (length = 32) => {
25
+ if (length <= 0 || !Number.isInteger(length)) throw new RangeError("Token length must be a positive integer");
26
+ const randomBytes$1 = new Uint8Array(length);
27
+ crypto.getRandomValues(randomBytes$1);
28
+ const base64 = btoa(String.fromCharCode.apply(null, Array.from(randomBytes$1)));
29
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
30
+ };
31
+ /**
32
+ * Generates a cryptographically secure string of random digits without modulo bias.
33
+ * Uses rejection sampling to ensure each digit (0-9) has an equal probability of being selected.
34
+ *
35
+ * @param length - Number of digits to generate (must be positive)
36
+ * @returns String containing exactly the specified number of random digits
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * const pinCode = generateUnbiasedDigits(6)
41
+ * // Returns: "492847" (example - actual result is random)
42
+ *
43
+ * const shortCode = generateUnbiasedDigits(4)
44
+ * // Returns: "7291" (example - actual result is random)
45
+ * ```
46
+ *
47
+ * @throws {RangeError} If length is not a positive number
48
+ */
49
+ const generateUnbiasedDigits = (length) => {
50
+ if (length <= 0 || !Number.isInteger(length)) throw new RangeError("Length must be a positive integer");
51
+ const result = [];
52
+ while (result.length < length) {
53
+ const buffer = crypto.getRandomValues(new Uint8Array(length * 2));
54
+ for (const byte of buffer) if (byte < 250 && result.length < length) result.push(byte % 10);
55
+ }
56
+ return result.join("");
57
+ };
58
+ /**
59
+ * Performs a timing-safe comparison of two strings to prevent timing attacks.
60
+ * Always takes the same amount of time regardless of where the strings differ,
61
+ * making it safe for comparing sensitive values like tokens or passwords.
62
+ *
63
+ * @param a - First string to compare
64
+ * @param b - Second string to compare
65
+ * @returns True if strings are identical, false otherwise
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * // Safe for comparing sensitive values
70
+ * const isValidToken = timingSafeCompare(userToken, expectedToken)
71
+ *
72
+ * // Safe for password verification
73
+ * const isValidPassword = timingSafeCompare(hashedInput, storedHash)
74
+ *
75
+ * // Returns false for different types or lengths
76
+ * timingSafeCompare("abc", 123 as any) // false
77
+ * timingSafeCompare("abc", "abcd") // false
78
+ * ```
79
+ */
80
+ const timingSafeCompare = (a, b) => {
81
+ if (typeof a !== "string" || typeof b !== "string") return false;
82
+ if (a.length !== b.length) return false;
83
+ return timingSafeEqual(Buffer.from(a), Buffer.from(b));
84
+ };
85
+
86
+ //#endregion
87
+ export { generateSecureToken, generateUnbiasedDigits, timingSafeCompare };
@@ -0,0 +1,66 @@
1
+ //#region src/random.d.ts
2
+ /**
3
+ * Cryptographic utilities for secure random generation and comparison operations.
4
+ * These functions are designed to prevent timing attacks and provide unbiased randomness.
5
+ */
6
+ /**
7
+ * Generates a cryptographically secure token with enhanced entropy.
8
+ * Uses Web Crypto API and provides 256 bits of entropy
9
+ *
10
+ * @param length - Length of random data in bytes (default: 32 for 256-bit security)
11
+ * @returns Base64url-encoded secure token
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const authCode = generateSecureToken()
16
+ * // Returns: "7B8kJ9mN3pQ2rS5tU8vW0xY1zA3bC6dE9fG2hI5jK8lM" (example)
17
+ *
18
+ * const refreshToken = generateSecureToken(24) // 192-bit token
19
+ * // Returns: "4A7bC9dF2gH5iJ8kL1mN4pQ7rS0tU" (example)
20
+ * ```
21
+ */
22
+ declare const generateSecureToken: (length?: number) => string;
23
+ /**
24
+ * Generates a cryptographically secure string of random digits without modulo bias.
25
+ * Uses rejection sampling to ensure each digit (0-9) has an equal probability of being selected.
26
+ *
27
+ * @param length - Number of digits to generate (must be positive)
28
+ * @returns String containing exactly the specified number of random digits
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const pinCode = generateUnbiasedDigits(6)
33
+ * // Returns: "492847" (example - actual result is random)
34
+ *
35
+ * const shortCode = generateUnbiasedDigits(4)
36
+ * // Returns: "7291" (example - actual result is random)
37
+ * ```
38
+ *
39
+ * @throws {RangeError} If length is not a positive number
40
+ */
41
+ declare const generateUnbiasedDigits: (length: number) => string;
42
+ /**
43
+ * Performs a timing-safe comparison of two strings to prevent timing attacks.
44
+ * Always takes the same amount of time regardless of where the strings differ,
45
+ * making it safe for comparing sensitive values like tokens or passwords.
46
+ *
47
+ * @param a - First string to compare
48
+ * @param b - Second string to compare
49
+ * @returns True if strings are identical, false otherwise
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * // Safe for comparing sensitive values
54
+ * const isValidToken = timingSafeCompare(userToken, expectedToken)
55
+ *
56
+ * // Safe for password verification
57
+ * const isValidPassword = timingSafeCompare(hashedInput, storedHash)
58
+ *
59
+ * // Returns false for different types or lengths
60
+ * timingSafeCompare("abc", 123 as any) // false
61
+ * timingSafeCompare("abc", "abcd") // false
62
+ * ```
63
+ */
64
+ declare const timingSafeCompare: (a: string, b: string) => boolean;
65
+ //#endregion
66
+ export { generateSecureToken, generateUnbiasedDigits, timingSafeCompare };
package/dist/random.js ADDED
@@ -0,0 +1,3 @@
1
+ import { generateSecureToken, generateUnbiasedDigits, timingSafeCompare } from "./random-SXMYlaVr.js";
2
+
3
+ export { generateSecureToken, generateUnbiasedDigits, timingSafeCompare };
@@ -0,0 +1,280 @@
1
+ import { Layout } from "./base-DRutbxgL.js";
2
+ import { ICON_GITHUB, ICON_GOOGLE } from "./icon-Ci5uqGB_.js";
3
+
4
+ //#region src/ui/select.ts
5
+ /**
6
+ * Default copy text used throughout the select UI.
7
+ * These values are used when no custom copy is provided.
8
+ */
9
+ const DEFAULT_COPY = { button_provider: "Continue with" };
10
+ /**
11
+ * Default display names for all known provider types.
12
+ * These provide consistent naming across the application and serve as fallbacks
13
+ * when no custom display name is configured.
14
+ */
15
+ const DEFAULT_DISPLAY = {
16
+ steam: "Steam",
17
+ twitch: "Twitch",
18
+ google: "Google",
19
+ github: "GitHub",
20
+ apple: "Apple",
21
+ code: "Code",
22
+ x: "X",
23
+ facebook: "Facebook",
24
+ microsoft: "Microsoft",
25
+ slack: "Slack",
26
+ password: "Password"
27
+ };
28
+ /**
29
+ * Comprehensive icon mapping for all supported authentication providers.
30
+ * Each icon is an optimized SVG component with proper accessibility attributes.
31
+ *
32
+ * Icons are designed to:
33
+ * - Scale properly at different sizes
34
+ * - Inherit text color for theming
35
+ * - Include proper ARIA attributes
36
+ * - Work with screen readers
37
+ */
38
+ const ICON = {
39
+ steam: `
40
+ <svg
41
+ aria-hidden="true"
42
+ class="bi bi-steam"
43
+ fill="currentColor"
44
+ height="16"
45
+ viewBox="0 0 16 16"
46
+ width="16"
47
+ xmlns="http://www.w3.org/2000/svg"
48
+ >
49
+ <path d="M.329 10.333A8.01 8.01 0 0 0 7.99 16C12.414 16 16 12.418 16 8s-3.586-8-8.009-8A8.006 8.006 0 0 0 0 7.468l.003.006 4.304 1.769A2.2 2.2 0 0 1 5.62 8.88l1.96-2.844-.001-.04a3.046 3.046 0 0 1 3.042-3.043 3.046 3.046 0 0 1 3.042 3.043 3.047 3.047 0 0 1-3.111 3.044l-2.804 2a2.223 2.223 0 0 1-3.075 2.11 2.22 2.22 0 0 1-1.312-1.568L.33 10.333Z" />
50
+ <path d="M4.868 12.683a1.715 1.715 0 0 0 1.318-3.165 1.7 1.7 0 0 0-1.263-.02l1.023.424a1.261 1.261 0 1 1-.97 2.33l-.99-.41a1.7 1.7 0 0 0 .882.84Zm3.726-6.687a2.03 2.03 0 0 0 2.027 2.029 2.03 2.03 0 0 0 2.027-2.029 2.03 2.03 0 0 0-2.027-2.027 2.03 2.03 0 0 0-2.027 2.027m2.03-1.527a1.524 1.524 0 1 1-.002 3.048 1.524 1.524 0 0 1 .002-3.048" />
51
+ </svg>
52
+ `,
53
+ code: `
54
+ <svg
55
+ aria-hidden="true"
56
+ data-name="Layer 1"
57
+ fill="currentColor"
58
+ viewBox="0 0 52 52"
59
+ xmlns="http://www.w3.org/2000/svg"
60
+ >
61
+ <path
62
+ d="M8.55,36.91A6.55,6.55,0,1,1,2,43.45,6.54,6.54,0,0,1,8.55,36.91Zm17.45,0a6.55,6.55,0,1,1-6.55,6.54A6.55,6.55,0,0,1,26,36.91Zm17.45,0a6.55,6.55,0,1,1-6.54,6.54A6.54,6.54,0,0,1,43.45,36.91ZM8.55,19.45A6.55,6.55,0,1,1,2,26,6.55,6.55,0,0,1,8.55,19.45Zm17.45,0A6.55,6.55,0,1,1,19.45,26,6.56,6.56,0,0,1,26,19.45Zm17.45,0A6.55,6.55,0,1,1,36.91,26,6.55,6.55,0,0,1,43.45,19.45ZM8.55,2A6.55,6.55,0,1,1,2,8.55,6.54,6.54,0,0,1,8.55,2ZM26,2a6.55,6.55,0,1,1-6.55,6.55A6.55,6.55,0,0,1,26,2ZM43.45,2a6.55,6.55,0,1,1-6.54,6.55A6.55,6.55,0,0,1,43.45,2Z"
63
+ fill-rule="evenodd"
64
+ />
65
+ </svg>
66
+ `,
67
+ password: `
68
+ <svg
69
+ aria-hidden="true"
70
+ fill="currentColor"
71
+ viewBox="0 0 24 24"
72
+ xmlns="http://www.w3.org/2000/svg"
73
+ >
74
+ <path
75
+ clip-rule="evenodd"
76
+ d="M12 1.5a5.25 5.25 0 0 0-5.25 5.25v3a3 3 0 0 0-3 3v6.75a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3v-6.75a3 3 0 0 0-3-3v-3c0-2.9-2.35-5.25-5.25-5.25Zm3.75 8.25v-3a3.75 3.75 0 1 0-7.5 0v3h7.5Z"
77
+ fill-rule="evenodd"
78
+ />
79
+ </svg>
80
+ `,
81
+ twitch: `
82
+ <svg
83
+ aria-hidden="true"
84
+ role="img"
85
+ viewBox="0 0 448 512"
86
+ xmlns="http://www.w3.org/2000/svg"
87
+ >
88
+ <path
89
+ d="M40.1 32L10 108.9v314.3h107V480h60.2l56.8-56.8h87l117-117V32H40.1zm357.8 254.1L331 353H224l-56.8 56.8V353H76.9V72.1h321v214zM331 149v116.9h-40.1V149H331zm-107 0v116.9h-40.1V149H224z"
90
+ fill="currentColor"
91
+ />
92
+ </svg>
93
+ `,
94
+ google: ICON_GOOGLE,
95
+ github: ICON_GITHUB,
96
+ apple: `
97
+ <svg
98
+ aria-hidden="true"
99
+ role="img"
100
+ viewBox="0 0 814 1000"
101
+ xmlns="http://www.w3.org/2000/svg"
102
+ >
103
+ <path
104
+ d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2zm-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3z "
105
+ fill="currentColor"
106
+ />
107
+ </svg>
108
+ `,
109
+ x: `
110
+ <svg
111
+ aria-hidden="true"
112
+ role="img"
113
+ viewBox="0 0 1200 1227"
114
+ xmlns="http://www.w3.org/2000/svg"
115
+ >
116
+ <path
117
+ d="M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z"
118
+ fill="currentColor"
119
+ />
120
+ </svg>
121
+ `,
122
+ microsoft: `
123
+ <svg
124
+ aria-hidden="true"
125
+ preserveAspectRatio="xMidYMid"
126
+ role="img"
127
+ viewBox="0 0 256 256"
128
+ xmlns="http://www.w3.org/2000/svg"
129
+ >
130
+ <path d="M121.666 121.666H0V0h121.666z" fill="#F1511B" />
131
+ <path d="M256 121.666H134.335V0H256z" fill="#80CC28" />
132
+ <path d="M121.663 256.002H0V134.336h121.663z" fill="#00ADEF" />
133
+ <path d="M256 256.002H134.335V134.336H256z" fill="#FBBC09" />
134
+ </svg>
135
+ `,
136
+ facebook: `
137
+ <svg
138
+ aria-hidden="true"
139
+ fill="url(#a)"
140
+ role="img"
141
+ viewBox="0 0 36 36"
142
+ xmlns="http://www.w3.org/2000/svg"
143
+ >
144
+ <defs>
145
+ <linearGradient id="a" x1="50%" x2="50%" y1="97.078%" y2="0%">
146
+ <stop offset="0%" stop-color="#0062E0" />
147
+ <stop offset="100%" stop-color="#19AFFF" />
148
+ </linearGradient>
149
+ </defs>
150
+ <path d="M15 35.8C6.5 34.3 0 26.9 0 18 0 8.1 8.1 0 18 0s18 8.1 18 18c0 8.9-6.5 16.3-15 17.8l-1-.8h-4l-1 .8z" />
151
+ <path
152
+ d="m25 23 .8-5H21v-3.5c0-1.4.5-2.5 2.7-2.5H26V7.4c-1.3-.2-2.7-.4-4-.4-4.1 0-7 2.5-7 7v4h-4.5v5H15v12.7c1 .2 2 .3 3 .3s2-.1 3-.3V23h4z"
153
+ fill="#FFF"
154
+ />
155
+ </svg>
156
+ `,
157
+ slack: `
158
+ <svg
159
+ aria-hidden="true"
160
+ enable-background="new 0 0 2447.6 2452.5"
161
+ role="img"
162
+ viewBox="0 0 2447.6 2452.5"
163
+ xmlns="http://www.w3.org/2000/svg"
164
+ >
165
+ <g clip-rule="evenodd" fill-rule="evenodd">
166
+ <path
167
+ d="m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z"
168
+ fill="#36c5f0"
169
+ />
170
+ <path
171
+ d="m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z"
172
+ fill="#2eb67d"
173
+ />
174
+ <path
175
+ d="m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z"
176
+ fill="#ecb22e"
177
+ />
178
+ <path
179
+ d="m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0"
180
+ fill="#e01e5a"
181
+ />
182
+ </g>
183
+ </svg>
184
+ `
185
+ };
186
+ /**
187
+ * Creates a provider selection UI component for OAuth authentication.
188
+ *
189
+ * This component generates a complete authentication provider selection interface that:
190
+ * - Displays available OAuth providers as clickable buttons
191
+ * - Includes appropriate icons for recognized providers
192
+ * - Supports custom theming and internationalization
193
+ * - Provides accessible markup with proper ARIA attributes
194
+ * - Handles provider visibility and custom display names
195
+ *
196
+ * @param props - Configuration options for customizing the select UI
197
+ * @returns An async function that generates the HTML response for the selection interface
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * // Basic usage with defaults
202
+ * const selectUI = Select()
203
+ *
204
+ * // Customized with provider configuration
205
+ * const selectUI = Select({
206
+ * copy: {
207
+ * button_provider: "Sign in with"
208
+ * },
209
+ * displays: {
210
+ * github: "GitHub Account",
211
+ * code: "Email Code"
212
+ * },
213
+ * providers: {
214
+ * facebook: { hide: true },
215
+ * google: { display: "Google Workspace" }
216
+ * }
217
+ * })
218
+ *
219
+ * // Use in issuer configuration
220
+ * export default issuer({
221
+ * select: selectUI,
222
+ * // ... other configuration
223
+ * })
224
+ * ```
225
+ *
226
+ * ## Provider Resolution Logic
227
+ *
228
+ * Display names are resolved in the following priority order:
229
+ * 1. Provider-specific `display` property
230
+ * 2. Global `displays` type override
231
+ * 3. Default display name from `DEFAULT_DISPLAY`
232
+ * 4. Provider type string as final fallback
233
+ *
234
+ * ## Accessibility Features
235
+ *
236
+ * The generated UI includes:
237
+ * - Semantic HTML structure
238
+ * - Proper button roles and keyboard navigation
239
+ * - Icon accessibility with `aria-hidden="true"`
240
+ * - Screen reader friendly provider names
241
+ */
242
+ const Select = (props) => {
243
+ return async (providers, _req) => {
244
+ const copy = {
245
+ ...DEFAULT_COPY,
246
+ ...props?.copy
247
+ };
248
+ const displays = {
249
+ ...DEFAULT_DISPLAY,
250
+ ...props?.displays
251
+ };
252
+ const providerButtons = Object.entries(providers).map(([providerKey, providerType]) => {
253
+ const providerConfig = props?.providers?.[providerKey];
254
+ if (providerConfig?.hide) return "";
255
+ const displayName = providerConfig?.display || displays[providerType] || providerType;
256
+ const icon = ICON[providerKey];
257
+ return `
258
+ <a
259
+ aria-label="${copy.button_provider} ${displayName}"
260
+ data-color="ghost"
261
+ data-component="button"
262
+ href="./${providerKey}/authorize"
263
+ >
264
+ ${icon ? `<i data-slot="icon">${icon}</i>` : ""}
265
+ ${copy.button_provider} ${displayName}
266
+ </a>
267
+ `;
268
+ }).filter((button) => button !== "").join("");
269
+ const formContent = `
270
+ <div data-component="form">
271
+ ${providerButtons}
272
+ </div>
273
+ `;
274
+ const html = Layout({ children: formContent });
275
+ return new Response(html, { headers: { "Content-Type": "text/html" } });
276
+ };
277
+ };
278
+
279
+ //#endregion
280
+ export { Select };
@@ -0,0 +1,82 @@
1
+ import { StorageAdapter } from "../storage-CxKerLlc.js";
2
+
3
+ //#region src/storage/memory.d.ts
4
+
5
+ /**
6
+ * In-memory storage adapter for Draft Auth with optional file persistence.
7
+ *
8
+ * ## Usage
9
+ *
10
+ * ### Basic in-memory storage (development only)
11
+ * ```ts
12
+ * import { MemoryStorage } from "@draftauth/core/storage/memory"
13
+ *
14
+ * const storage = MemoryStorage()
15
+ *
16
+ * export default issuer({
17
+ * storage,
18
+ * // ...
19
+ * })
20
+ * ```
21
+ *
22
+ * ### With file persistence
23
+ * ```ts
24
+ * const storage = MemoryStorage({
25
+ * persist: "./data/auth-storage.json"
26
+ * })
27
+ * ```
28
+ *
29
+ * ## Important Notes
30
+ *
31
+ * - **Not for production**: Use proper databases in production environments
32
+ * - **Development friendly**: Great for testing and local development
33
+ * - **Optional persistence**: Can backup to file to survive restarts
34
+ * - **Automatic cleanup**: Expired entries are removed automatically
35
+ *
36
+ * @packageDocumentation
37
+ */
38
+ /**
39
+ * Configuration options for the memory storage adapter.
40
+ */
41
+ interface MemoryStorageOptions {
42
+ /**
43
+ * File path for persisting the in-memory store to disk.
44
+ * When specified, the store will be saved to this file on changes
45
+ * and loaded from it on startup if it exists.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * {
50
+ * persist: "./data/auth-storage.json"
51
+ * }
52
+ * ```
53
+ */
54
+ readonly persist?: string;
55
+ }
56
+ /**
57
+ * Creates an in-memory storage adapter with optional file persistence.
58
+ * Uses binary search for efficient key lookups and maintains sorted order.
59
+ *
60
+ * @param options - Configuration options for the memory storage
61
+ * @returns Storage adapter implementing the StorageAdapter interface
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * // Development storage (data lost on restart)
66
+ * const devStorage = MemoryStorage()
67
+ *
68
+ * // Persistent storage (survives restarts)
69
+ * const persistentStorage = MemoryStorage({
70
+ * persist: "./auth-data.json"
71
+ * })
72
+ *
73
+ * // Use with issuer
74
+ * export default issuer({
75
+ * storage: persistentStorage,
76
+ * providers: { ... }
77
+ * })
78
+ * ```
79
+ */
80
+ declare const MemoryStorage: (options?: MemoryStorageOptions) => StorageAdapter;
81
+ //#endregion
82
+ export { MemoryStorage, MemoryStorageOptions };
@@ -0,0 +1,127 @@
1
+ import { joinKey, splitKey } from "../storage-BEaqEPNQ.js";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { writeFile } from "node:fs/promises";
4
+
5
+ //#region src/storage/memory.ts
6
+ /**
7
+ * Creates an in-memory storage adapter with optional file persistence.
8
+ * Uses binary search for efficient key lookups and maintains sorted order.
9
+ *
10
+ * @param options - Configuration options for the memory storage
11
+ * @returns Storage adapter implementing the StorageAdapter interface
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // Development storage (data lost on restart)
16
+ * const devStorage = MemoryStorage()
17
+ *
18
+ * // Persistent storage (survives restarts)
19
+ * const persistentStorage = MemoryStorage({
20
+ * persist: "./auth-data.json"
21
+ * })
22
+ *
23
+ * // Use with issuer
24
+ * export default issuer({
25
+ * storage: persistentStorage,
26
+ * providers: { ... }
27
+ * })
28
+ * ```
29
+ */
30
+ const MemoryStorage = (options) => {
31
+ const store = [];
32
+ /**
33
+ * Type guard to validate loaded store data structure.
34
+ */
35
+ const isValidStoreData = (data) => {
36
+ return Array.isArray(data) && data.every((item) => Array.isArray(item) && item.length === 2 && typeof item[0] === "string" && typeof item[1] === "object" && item[1] !== null && "value" in item[1]);
37
+ };
38
+ if (options?.persist && existsSync(options.persist)) try {
39
+ const fileContent = readFileSync(options.persist, "utf8");
40
+ const parsed = JSON.parse(fileContent);
41
+ if (isValidStoreData(parsed)) store.push(...parsed);
42
+ } catch {}
43
+ /**
44
+ * Saves the current store state to the persistence file if configured.
45
+ */
46
+ const save = async () => {
47
+ if (!options?.persist) return;
48
+ try {
49
+ const serialized = JSON.stringify(store, null, 2);
50
+ await writeFile(options.persist, serialized, "utf8");
51
+ } catch {}
52
+ };
53
+ /**
54
+ * Performs binary search to find a key in the sorted store.
55
+ * Returns both whether the key was found and the insertion index.
56
+ */
57
+ const search = (key) => {
58
+ let left = 0;
59
+ let right = store.length - 1;
60
+ while (left <= right) {
61
+ const mid = Math.floor((left + right) / 2);
62
+ const midEntry = store[mid];
63
+ if (!midEntry) return {
64
+ found: false,
65
+ index: left
66
+ };
67
+ const comparison = key.localeCompare(midEntry[0]);
68
+ if (comparison === 0) return {
69
+ found: true,
70
+ index: mid
71
+ };
72
+ if (comparison < 0) right = mid - 1;
73
+ else left = mid + 1;
74
+ }
75
+ return {
76
+ found: false,
77
+ index: left
78
+ };
79
+ };
80
+ return {
81
+ async get(key) {
82
+ const searchKey = joinKey(key);
83
+ const match = search(searchKey);
84
+ if (!match.found) return;
85
+ const storeEntry = store[match.index];
86
+ if (!storeEntry) return;
87
+ const entry = storeEntry[1];
88
+ if (entry.expiry && Date.now() >= entry.expiry) {
89
+ store.splice(match.index, 1);
90
+ await save();
91
+ return;
92
+ }
93
+ return entry.value;
94
+ },
95
+ async set(key, value, expiry) {
96
+ const searchKey = joinKey(key);
97
+ const match = search(searchKey);
98
+ const entry = [searchKey, {
99
+ value,
100
+ expiry: expiry?.getTime()
101
+ }];
102
+ if (match.found) store[match.index] = entry;
103
+ else store.splice(match.index, 0, entry);
104
+ await save();
105
+ },
106
+ async remove(key) {
107
+ const searchKey = joinKey(key);
108
+ const match = search(searchKey);
109
+ if (match.found) {
110
+ store.splice(match.index, 1);
111
+ await save();
112
+ }
113
+ },
114
+ async *scan(prefix) {
115
+ const now = Date.now();
116
+ const prefixStr = joinKey(prefix);
117
+ for (const [key, entry] of store) {
118
+ if (!key.startsWith(prefixStr)) continue;
119
+ if (entry.expiry && now >= entry.expiry) continue;
120
+ yield [splitKey(key), entry.value];
121
+ }
122
+ }
123
+ };
124
+ };
125
+
126
+ //#endregion
127
+ export { MemoryStorage };
@@ -0,0 +1,2 @@
1
+ import { Storage, StorageAdapter, joinKey, splitKey } from "../storage-CxKerLlc.js";
2
+ export { Storage, StorageAdapter, joinKey, splitKey };
@@ -0,0 +1,3 @@
1
+ import { Storage, joinKey, splitKey } from "../storage-BEaqEPNQ.js";
2
+
3
+ export { Storage, joinKey, splitKey };
@@ -0,0 +1,31 @@
1
+ import { StorageAdapter } from "../storage-CxKerLlc.js";
2
+ import { Client } from "@libsql/client";
3
+
4
+ //#region src/storage/turso.d.ts
5
+
6
+ /**
7
+ * Creates a Turso storage adapter using the provided LibSQL client.
8
+ * Automatically initializes the required database table and implements
9
+ * the StorageAdapter interface with efficient SQL operations.
10
+ *
11
+ * @param client - Configured LibSQL client for database operations
12
+ * @returns Storage adapter implementing the StorageAdapter interface
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { createClient } from "@libsql/client"
17
+ *
18
+ * const client = createClient({
19
+ * url: process.env.TURSO_DATABASE_URL,
20
+ * authToken: process.env.TURSO_AUTH_TOKEN
21
+ * })
22
+ *
23
+ * const storage = TursoStorage(client)
24
+ *
25
+ * // Now ready to use with Draft Auth
26
+ * const app = issuer({ storage, ... })
27
+ * ```
28
+ */
29
+ declare const TursoStorage: (client: Client) => StorageAdapter;
30
+ //#endregion
31
+ export { TursoStorage };