@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.
- package/dist/adapters/node.d.ts +18 -0
- package/dist/adapters/node.js +71 -0
- package/dist/allow-CixonwTW.d.ts +59 -0
- package/dist/allow-DX5cehSc.js +63 -0
- package/dist/allow.d.ts +2 -0
- package/dist/allow.js +4 -0
- package/dist/base-DRutbxgL.js +422 -0
- package/dist/client.d.ts +413 -0
- package/dist/client.js +209 -0
- package/dist/code-l_uvMR1j.d.ts +212 -0
- package/dist/core-8WTqfnb4.d.ts +129 -0
- package/dist/core-CncE5rPg.js +498 -0
- package/dist/core.d.ts +9 -0
- package/dist/core.js +14 -0
- package/dist/error-CWAdNAzm.d.ts +243 -0
- package/dist/error-DgAKK7b2.js +237 -0
- package/dist/error.d.ts +2 -0
- package/dist/error.js +3 -0
- package/dist/form-6XKM_cOk.js +61 -0
- package/dist/icon-Ci5uqGB_.js +192 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +14 -0
- package/dist/keys-EEfxEGfO.js +140 -0
- package/dist/keys.d.ts +67 -0
- package/dist/keys.js +5 -0
- package/dist/oauth2-B7-6Z7Lc.js +155 -0
- package/dist/oauth2-DtKwtl8p.d.ts +176 -0
- package/dist/password-Cm0dRMwa.d.ts +385 -0
- package/dist/pkce-276Za_rZ.js +162 -0
- package/dist/pkce.d.ts +72 -0
- package/dist/pkce.js +3 -0
- package/dist/provider/code.d.ts +4 -0
- package/dist/provider/code.js +145 -0
- package/dist/provider/facebook.d.ts +137 -0
- package/dist/provider/facebook.js +85 -0
- package/dist/provider/github.d.ts +141 -0
- package/dist/provider/github.js +88 -0
- package/dist/provider/google.d.ts +113 -0
- package/dist/provider/google.js +62 -0
- package/dist/provider/oauth2.d.ts +4 -0
- package/dist/provider/oauth2.js +7 -0
- package/dist/provider/password.d.ts +4 -0
- package/dist/provider/password.js +366 -0
- package/dist/provider/provider.d.ts +3 -0
- package/dist/provider/provider.js +44 -0
- package/dist/provider-CwWMG-1l.d.ts +227 -0
- package/dist/random-SXMYlaVr.js +87 -0
- package/dist/random.d.ts +66 -0
- package/dist/random.js +3 -0
- package/dist/select-BjySLL8I.js +280 -0
- package/dist/storage/memory.d.ts +82 -0
- package/dist/storage/memory.js +127 -0
- package/dist/storage/storage.d.ts +2 -0
- package/dist/storage/storage.js +3 -0
- package/dist/storage/turso.d.ts +31 -0
- package/dist/storage/turso.js +117 -0
- package/dist/storage/unstorage.d.ts +38 -0
- package/dist/storage/unstorage.js +97 -0
- package/dist/storage-BEaqEPNQ.js +62 -0
- package/dist/storage-CxKerLlc.d.ts +162 -0
- package/dist/subject-DiQdRWGt.d.ts +62 -0
- package/dist/subject.d.ts +3 -0
- package/dist/subject.js +36 -0
- package/dist/theme-C9by7VXf.d.ts +209 -0
- package/dist/theme-CswaLtbW.js +120 -0
- package/dist/themes/theme.d.ts +2 -0
- package/dist/themes/theme.js +3 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +0 -0
- package/dist/ui/base.d.ts +43 -0
- package/dist/ui/base.js +4 -0
- package/dist/ui/code.d.ts +158 -0
- package/dist/ui/code.js +197 -0
- package/dist/ui/form.d.ts +31 -0
- package/dist/ui/form.js +3 -0
- package/dist/ui/icon.d.ts +98 -0
- package/dist/ui/icon.js +3 -0
- package/dist/ui/password.d.ts +54 -0
- package/dist/ui/password.js +300 -0
- package/dist/ui/select.d.ts +233 -0
- package/dist/ui/select.js +6 -0
- package/dist/util-CSdHUFOo.js +108 -0
- package/dist/util-ChlgVqPN.d.ts +72 -0
- package/dist/util.d.ts +2 -0
- package/dist/util.js +3 -0
- 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 };
|
package/dist/random.d.ts
ADDED
|
@@ -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,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,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 };
|