@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,162 @@
1
+ import { base64url } from "jose";
2
+
3
+ //#region src/pkce.ts
4
+ /**
5
+ * Performs a timing-safe comparison of two strings to prevent timing attacks.
6
+ * This implementation is platform-agnostic, uses a constant-time algorithm,
7
+ * and correctly handles all Unicode characters by operating on their UTF-8 byte representation.
8
+ * It always takes a time proportional to the length of the expected string,
9
+ * regardless of where the strings differ, making it safe for comparing sensitive values.
10
+ *
11
+ * @param a - The first string to compare (often the expected, secret value).
12
+ * @param b - The second string to compare (often the user-provided value).
13
+ * @returns True if the strings are identical, false otherwise.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // Safe for comparing sensitive values like PKCE verifiers or tokens
18
+ * const isValid = await timingSafeCompare(receivedVerifier, expectedChallenge);
19
+ *
20
+ * // Safe for password hash verification
21
+ * const isValidPassword = timingSafeCompare(hashedInput, storedHash);
22
+ *
23
+ * // Returns false for different types or lengths without leaking timing info
24
+ * timingSafeCompare("abc", 123 as any); // false
25
+ * timingSafeCompare("abc", "abcd"); // false
26
+ * ```
27
+ */
28
+ const timingSafeCompare = (a, b) => {
29
+ if (typeof a !== "string" || typeof b !== "string") return false;
30
+ const encoder = new TextEncoder();
31
+ const aBytes = encoder.encode(a);
32
+ const bBytes = encoder.encode(b);
33
+ let diff = aBytes.length ^ bBytes.length;
34
+ for (const [i, aByte] of aBytes.entries()) diff |= aByte ^ (bBytes[i] ?? 0);
35
+ return diff === 0;
36
+ };
37
+ /**
38
+ * Generates a cryptographically secure code verifier for PKCE.
39
+ * The verifier is a URL-safe base64-encoded string of random bytes.
40
+ *
41
+ * @param length - Length of the random buffer in bytes
42
+ * @returns Base64url-encoded verifier string
43
+ */
44
+ const generateVerifier = (length) => {
45
+ const buffer = new Uint8Array(length);
46
+ crypto.getRandomValues(buffer);
47
+ return base64url.encode(buffer);
48
+ };
49
+ /**
50
+ * Generates a code challenge from a verifier using the specified method.
51
+ * For 'S256', applies SHA-256 hash then base64url encoding.
52
+ * For 'plain', returns the verifier unchanged (not recommended for production).
53
+ *
54
+ * @param verifier - The code verifier string
55
+ * @param method - Challenge generation method
56
+ * @returns Promise resolving to the code challenge string
57
+ */
58
+ const generateChallenge = async (verifier, method) => {
59
+ if (method === "plain") return verifier;
60
+ const encoder = new TextEncoder();
61
+ const data = encoder.encode(verifier);
62
+ const hash = await crypto.subtle.digest("SHA-256", data);
63
+ return base64url.encode(new Uint8Array(hash));
64
+ };
65
+ /**
66
+ * Generates a complete PKCE challenge for OAuth authorization requests.
67
+ * Creates a cryptographically secure verifier and corresponding S256 challenge.
68
+ * Validates that the generated verifier meets standard requirements (43-128 characters).
69
+ *
70
+ * @param length - Length of the random buffer in bytes (32-96 range to generate 43-128 character verifier)
71
+ * @returns Promise resolving to PKCE challenge data
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * const pkce = await generatePKCE()
76
+ *
77
+ * // Use challenge in authorization URL
78
+ * authUrl.searchParams.set('code_challenge', pkce.challenge)
79
+ * authUrl.searchParams.set('code_challenge_method', pkce.method)
80
+ *
81
+ * // Store verifier for token exchange
82
+ * sessionStorage.setItem('code_verifier', pkce.verifier)
83
+ * ```
84
+ *
85
+ * @throws {RangeError} If length is outside valid range or generated verifier doesn't meet requirements
86
+ */
87
+ const generatePKCE = async (length = 48) => {
88
+ if (!Number.isInteger(length) || length < 32 || length > 96) throw new RangeError("Random buffer length must be between 32 and 96 bytes (generates 43-128 character verifier)");
89
+ const verifier = generateVerifier(length);
90
+ if (verifier.length < 43 || verifier.length > 128) throw new Error("Generated verifier does not meet requirements");
91
+ if (!/^[A-Za-z0-9_-]+$/.test(verifier)) throw new Error("Generated verifier is not valid base64url format");
92
+ const challenge = await generateChallenge(verifier, "S256");
93
+ return {
94
+ verifier,
95
+ challenge,
96
+ method: "S256"
97
+ };
98
+ };
99
+ /**
100
+ * Validates a PKCE code verifier against a previously generated challenge.
101
+ * Uses timing-safe comparison and timing normalization to prevent timing attacks.
102
+ * All validation paths take the same computational time regardless of input validity,
103
+ * making it resistant to timing-based side-channel attacks.
104
+ *
105
+ * @param verifier - The code verifier received from the client
106
+ * @param challenge - The code challenge stored during authorization
107
+ * @param method - The challenge method used during generation
108
+ * @returns Promise resolving to true if verifier matches challenge
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * // During token exchange
113
+ * const isValid = await validatePKCE(
114
+ * receivedVerifier,
115
+ * storedChallenge,
116
+ * 'S256'
117
+ * )
118
+ *
119
+ * if (!isValid) {
120
+ * throw new Error('Invalid PKCE verifier')
121
+ * }
122
+ * ```
123
+ */
124
+ const validatePKCE = async (verifier, challenge, method = "S256") => {
125
+ const MIN_PROCESSING_TIME = 50;
126
+ const RANDOM_JITTER_MAX = 20;
127
+ const startTime = performance.now();
128
+ let isValid = false;
129
+ let hasEarlyFailure = false;
130
+ const normalizedVerifier = String(verifier || "");
131
+ const normalizedChallenge = String(challenge || "");
132
+ const validations = [
133
+ typeof verifier === "string" && typeof challenge === "string" && verifier && challenge,
134
+ normalizedVerifier.length >= 43 && normalizedVerifier.length <= 128,
135
+ normalizedChallenge.length >= 43 && normalizedChallenge.length <= 128,
136
+ /^[A-Za-z0-9_-]+$/.test(normalizedVerifier),
137
+ /^[A-Za-z0-9_-]+$/.test(normalizedChallenge)
138
+ ];
139
+ hasEarlyFailure = !validations.every(Boolean);
140
+ const verifierToUse = hasEarlyFailure ? "dummyverifier_".repeat(6) : normalizedVerifier;
141
+ try {
142
+ const generatedChallenge = await generateChallenge(verifierToUse, method);
143
+ const challengeToCompare = hasEarlyFailure ? "dummychallenge_".repeat(6) : normalizedChallenge;
144
+ const comparisonResult = timingSafeCompare(generatedChallenge, challengeToCompare);
145
+ isValid = !hasEarlyFailure && comparisonResult;
146
+ } catch {
147
+ isValid = false;
148
+ }
149
+ const elapsed = performance.now() - startTime;
150
+ const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
151
+ if (remainingTime > 0 || elapsed < MIN_PROCESSING_TIME) {
152
+ const jitterArray = new Uint32Array(1);
153
+ crypto.getRandomValues(jitterArray);
154
+ const jitter = (jitterArray[0] ?? 0) / 4294967295 * RANDOM_JITTER_MAX;
155
+ const totalDelay = Math.max(remainingTime, MIN_PROCESSING_TIME - elapsed) + jitter;
156
+ await new Promise((resolve) => setTimeout(resolve, totalDelay));
157
+ }
158
+ return isValid;
159
+ };
160
+
161
+ //#endregion
162
+ export { generatePKCE, validatePKCE };
package/dist/pkce.d.ts ADDED
@@ -0,0 +1,72 @@
1
+ //#region src/pkce.d.ts
2
+ /**
3
+ * PKCE (Proof Key for Code Exchange) implementation for OAuth security.
4
+ * Provides protection against authorization code interception attacks by using
5
+ * dynamically generated code verifiers and challenges.
6
+ */
7
+ /**
8
+ * PKCE challenge methods supported by the implementation.
9
+ */
10
+ type PKCEMethod = "S256" | "plain";
11
+ /**
12
+ * Complete PKCE challenge data containing verifier, challenge, and method.
13
+ */
14
+ interface PKCEChallenge {
15
+ /** The code verifier to be sent to the token endpoint */
16
+ readonly verifier: string;
17
+ /** The code challenge to be sent to the authorization endpoint */
18
+ readonly challenge: string;
19
+ /** The challenge method used */
20
+ readonly method: "S256";
21
+ }
22
+ /**
23
+ * Generates a complete PKCE challenge for OAuth authorization requests.
24
+ * Creates a cryptographically secure verifier and corresponding S256 challenge.
25
+ * Validates that the generated verifier meets standard requirements (43-128 characters).
26
+ *
27
+ * @param length - Length of the random buffer in bytes (32-96 range to generate 43-128 character verifier)
28
+ * @returns Promise resolving to PKCE challenge data
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const pkce = await generatePKCE()
33
+ *
34
+ * // Use challenge in authorization URL
35
+ * authUrl.searchParams.set('code_challenge', pkce.challenge)
36
+ * authUrl.searchParams.set('code_challenge_method', pkce.method)
37
+ *
38
+ * // Store verifier for token exchange
39
+ * sessionStorage.setItem('code_verifier', pkce.verifier)
40
+ * ```
41
+ *
42
+ * @throws {RangeError} If length is outside valid range or generated verifier doesn't meet requirements
43
+ */
44
+ declare const generatePKCE: (length?: number) => Promise<PKCEChallenge>;
45
+ /**
46
+ * Validates a PKCE code verifier against a previously generated challenge.
47
+ * Uses timing-safe comparison and timing normalization to prevent timing attacks.
48
+ * All validation paths take the same computational time regardless of input validity,
49
+ * making it resistant to timing-based side-channel attacks.
50
+ *
51
+ * @param verifier - The code verifier received from the client
52
+ * @param challenge - The code challenge stored during authorization
53
+ * @param method - The challenge method used during generation
54
+ * @returns Promise resolving to true if verifier matches challenge
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * // During token exchange
59
+ * const isValid = await validatePKCE(
60
+ * receivedVerifier,
61
+ * storedChallenge,
62
+ * 'S256'
63
+ * )
64
+ *
65
+ * if (!isValid) {
66
+ * throw new Error('Invalid PKCE verifier')
67
+ * }
68
+ * ```
69
+ */
70
+ declare const validatePKCE: (verifier: string, challenge: string, method?: PKCEMethod) => Promise<boolean>;
71
+ //#endregion
72
+ export { generatePKCE, validatePKCE };
package/dist/pkce.js ADDED
@@ -0,0 +1,3 @@
1
+ import { generatePKCE, validatePKCE } from "./pkce-276Za_rZ.js";
2
+
3
+ export { generatePKCE, validatePKCE };
@@ -0,0 +1,4 @@
1
+ import "../storage-CxKerLlc.js";
2
+ import "../provider-CwWMG-1l.js";
3
+ import { CodeProvider, CodeProviderConfig, CodeProviderError, CodeProviderOptions, CodeProviderState, CodeUserData } from "../code-l_uvMR1j.js";
4
+ export { CodeProvider, CodeProviderConfig, CodeProviderError, CodeProviderOptions, CodeProviderState, CodeUserData };
@@ -0,0 +1,145 @@
1
+ import { generateUnbiasedDigits, timingSafeCompare } from "../random-SXMYlaVr.js";
2
+
3
+ //#region src/provider/code.ts
4
+ /**
5
+ * Creates a PIN code authentication provider.
6
+ * Implements a flexible claim-based authentication flow with PIN verification.
7
+ *
8
+ * @template Claims - Type of claims to collect (email, phone, username, etc.)
9
+ * @param config - PIN code provider configuration
10
+ * @returns Provider instance implementing PIN code authentication
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * // Email-based PIN authentication
15
+ * const emailCodeProvider = CodeProvider<{ email: string }>({
16
+ * length: 6,
17
+ * request: async (req, state, form, error) => {
18
+ * if (state.type === 'start') {
19
+ * return new Response(renderEmailForm(form?.get('email'), error))
20
+ * } else {
21
+ * return new Response(renderPinForm(state.claims.email, error, state.resend))
22
+ * }
23
+ * },
24
+ * sendCode: async (claims, code) => {
25
+ * if (!claims.email || !isValidEmail(claims.email)) {
26
+ * return {
27
+ * type: "invalid_claim",
28
+ * key: "email",
29
+ * value: "Invalid email address"
30
+ * }
31
+ * }
32
+ *
33
+ * await emailService.send(claims.email, `Your verification code: ${code}`)
34
+ * }
35
+ * })
36
+ *
37
+ * // Multi-channel PIN authentication (email or phone)
38
+ * const flexibleCodeProvider = CodeProvider<{ email?: string; phone?: string }>({
39
+ * length: 4,
40
+ * request: async (req, state, form, error) => {
41
+ * if (state.type === 'start') {
42
+ * return new Response(renderContactForm(form, error))
43
+ * } else {
44
+ * const contact = state.claims.email || state.claims.phone
45
+ * return new Response(renderPinForm(contact, error))
46
+ * }
47
+ * },
48
+ * sendCode: async (claims, code) => {
49
+ * if (claims.email) {
50
+ * await emailService.send(claims.email, `PIN: ${code}`)
51
+ * } else if (claims.phone) {
52
+ * await smsService.send(claims.phone, `PIN: ${code}`)
53
+ * } else {
54
+ * return {
55
+ * type: "invalid_claim",
56
+ * key: "contact",
57
+ * value: "Provide either email or phone number"
58
+ * }
59
+ * }
60
+ * }
61
+ * })
62
+ *
63
+ * // Usage in issuer
64
+ * export default issuer({
65
+ * providers: {
66
+ * email: emailCodeProvider,
67
+ * flexible: flexibleCodeProvider
68
+ * },
69
+ * success: async (ctx, value) => {
70
+ * if (value.provider === "code") {
71
+ * const email = value.claims.email
72
+ * const phone = value.claims.phone
73
+ *
74
+ * // Look up or create user based on verified claims
75
+ * const userId = await findOrCreateUser({ email, phone })
76
+ *
77
+ * return ctx.subject("user", { userId, email, phone })
78
+ * }
79
+ * }
80
+ * })
81
+ * ```
82
+ */
83
+ const CodeProvider = (config) => {
84
+ const codeLength = config.length || 6;
85
+ /**
86
+ * Generates a cryptographically secure PIN code.
87
+ */
88
+ const generateCode = () => {
89
+ return generateUnbiasedDigits(codeLength);
90
+ };
91
+ return {
92
+ type: "code",
93
+ init(routes, ctx) {
94
+ /**
95
+ * Transitions between authentication states and renders the appropriate UI.
96
+ */
97
+ const transition = async (c, nextState, formData, error) => {
98
+ await ctx.set(c, "provider", 60 * 60 * 24, nextState);
99
+ const response = await config.request(c.request, nextState, formData, error);
100
+ return ctx.forward(c, response);
101
+ };
102
+ /**
103
+ * GET /authorize - Display initial claim collection form
104
+ */
105
+ routes.get("/authorize", (c) => {
106
+ return transition(c, { type: "start" });
107
+ });
108
+ /**
109
+ * POST /authorize - Handle form submissions and state transitions
110
+ */
111
+ routes.post("/authorize", async (c) => {
112
+ const formData = await c.formData();
113
+ const currentState = await ctx.get(c, "provider");
114
+ const action = formData.get("action")?.toString();
115
+ if (action === "request" || action === "resend") {
116
+ const code = generateCode();
117
+ const formEntries = Object.fromEntries(formData);
118
+ const { action: _,...claims } = formEntries;
119
+ const sendError = await config.sendCode(claims, code);
120
+ if (sendError) return transition(c, { type: "start" }, formData, sendError);
121
+ return transition(c, {
122
+ type: "code",
123
+ resend: action === "resend",
124
+ claims,
125
+ code
126
+ }, formData);
127
+ }
128
+ if (action === "verify" && currentState?.type === "code") {
129
+ const enteredCode = formData.get("code")?.toString();
130
+ if (!(currentState.code && enteredCode && timingSafeCompare(currentState.code, enteredCode))) return transition(c, {
131
+ ...currentState,
132
+ resend: false
133
+ }, formData, { type: "invalid_code" });
134
+ await ctx.unset(c, "provider");
135
+ const successResponse = await ctx.success(c, { claims: currentState.claims });
136
+ return ctx.forward(c, successResponse);
137
+ }
138
+ return transition(c, { type: "start" }, formData);
139
+ });
140
+ }
141
+ };
142
+ };
143
+
144
+ //#endregion
145
+ export { CodeProvider };
@@ -0,0 +1,137 @@
1
+ import "../storage-CxKerLlc.js";
2
+ import { Provider } from "../provider-CwWMG-1l.js";
3
+ import { Oauth2UserData, Oauth2WrappedConfig } from "../oauth2-DtKwtl8p.js";
4
+
5
+ //#region src/provider/facebook.d.ts
6
+
7
+ /**
8
+ * Configuration options for Facebook OAuth 2.0 provider.
9
+ * Extends the base OAuth 2.0 configuration with Facebook-specific documentation.
10
+ */
11
+ interface FacebookConfig extends Oauth2WrappedConfig {
12
+ /**
13
+ * Facebook App ID from your Facebook App Dashboard.
14
+ * This is the public identifier for your Facebook application.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * {
19
+ * clientID: "1234567890123456"
20
+ * }
21
+ * ```
22
+ */
23
+ readonly clientID: string;
24
+ /**
25
+ * Facebook App Secret from your Facebook App Dashboard.
26
+ * Keep this secure and never expose it to client-side code.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * {
31
+ * clientSecret: process.env.FACEBOOK_APP_SECRET
32
+ * }
33
+ * ```
34
+ */
35
+ readonly clientSecret: string;
36
+ /**
37
+ * Facebook permissions to request during login.
38
+ * Determines what data your app can access from the user's Facebook account.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * {
43
+ * scopes: [
44
+ * "email", // User's email address
45
+ * "public_profile", // Basic profile info
46
+ * "user_friends", // User's friends list
47
+ * "user_posts" // User's timeline posts
48
+ * ]
49
+ * }
50
+ * ```
51
+ */
52
+ readonly scopes: string[];
53
+ /**
54
+ * Additional query parameters for Facebook OAuth authorization.
55
+ * Useful for Facebook-specific options like response type or display mode.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * {
60
+ * query: {
61
+ * display: "popup", // Show login in popup
62
+ * auth_type: "rerequest", // Force permission re-request
63
+ * state: "custom-state" // Custom state parameter
64
+ * }
65
+ * }
66
+ * ```
67
+ */
68
+ readonly query?: Record<string, string>;
69
+ }
70
+ /**
71
+ * Creates a Facebook OAuth 2.0 authentication provider.
72
+ * Use this when you need access tokens to call Facebook Graph API on behalf of the user.
73
+ *
74
+ * @param config - Facebook OAuth 2.0 configuration
75
+ * @returns OAuth 2.0 provider configured for Facebook
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * // Basic Facebook authentication
80
+ * const basicFacebook = FacebookProvider({
81
+ * clientID: process.env.FACEBOOK_APP_ID,
82
+ * clientSecret: process.env.FACEBOOK_APP_SECRET,
83
+ * scopes: ["email", "public_profile"]
84
+ * })
85
+ *
86
+ * // Facebook with extended permissions
87
+ * const extendedFacebook = FacebookProvider({
88
+ * clientID: process.env.FACEBOOK_APP_ID,
89
+ * clientSecret: process.env.FACEBOOK_APP_SECRET,
90
+ * scopes: [
91
+ * "email",
92
+ * "public_profile",
93
+ * "user_friends",
94
+ * "user_posts",
95
+ * "user_photos"
96
+ * ],
97
+ * query: {
98
+ * display: "popup",
99
+ * auth_type: "rerequest" // Force permission approval
100
+ * }
101
+ * })
102
+ *
103
+ * // Using the access token for Graph API calls
104
+ * export default issuer({
105
+ * providers: { facebook: extendedFacebook },
106
+ * success: async (ctx, value) => {
107
+ * if (value.provider === "facebook") {
108
+ * const token = value.tokenset.access
109
+ *
110
+ * // Get user profile with custom fields
111
+ * const profileRes = await fetch(
112
+ * `https://graph.facebook.com/me?fields=id,name,email,picture.width(200),friends&access_token=${token}`
113
+ * )
114
+ * const profile = await profileRes.json()
115
+ *
116
+ * // Get user's posts (if permission granted)
117
+ * const postsRes = await fetch(
118
+ * `https://graph.facebook.com/me/posts?access_token=${token}`
119
+ * )
120
+ * const posts = await postsRes.json()
121
+ *
122
+ * return ctx.subject("user", {
123
+ * facebookId: profile.id,
124
+ * name: profile.name,
125
+ * email: profile.email,
126
+ * picture: profile.picture?.data?.url,
127
+ * friendsCount: profile.friends?.summary?.total_count || 0,
128
+ * postsCount: posts.data?.length || 0
129
+ * })
130
+ * }
131
+ * }
132
+ * })
133
+ * ```
134
+ */
135
+ declare const FacebookProvider: (config: FacebookConfig) => Provider<Oauth2UserData>;
136
+ //#endregion
137
+ export { FacebookConfig, FacebookProvider };
@@ -0,0 +1,85 @@
1
+ import "../util-CSdHUFOo.js";
2
+ import "../error-DgAKK7b2.js";
3
+ import "../pkce-276Za_rZ.js";
4
+ import "../random-SXMYlaVr.js";
5
+ import { Oauth2Provider } from "../oauth2-B7-6Z7Lc.js";
6
+
7
+ //#region src/provider/facebook.ts
8
+ /**
9
+ * Creates a Facebook OAuth 2.0 authentication provider.
10
+ * Use this when you need access tokens to call Facebook Graph API on behalf of the user.
11
+ *
12
+ * @param config - Facebook OAuth 2.0 configuration
13
+ * @returns OAuth 2.0 provider configured for Facebook
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // Basic Facebook authentication
18
+ * const basicFacebook = FacebookProvider({
19
+ * clientID: process.env.FACEBOOK_APP_ID,
20
+ * clientSecret: process.env.FACEBOOK_APP_SECRET,
21
+ * scopes: ["email", "public_profile"]
22
+ * })
23
+ *
24
+ * // Facebook with extended permissions
25
+ * const extendedFacebook = FacebookProvider({
26
+ * clientID: process.env.FACEBOOK_APP_ID,
27
+ * clientSecret: process.env.FACEBOOK_APP_SECRET,
28
+ * scopes: [
29
+ * "email",
30
+ * "public_profile",
31
+ * "user_friends",
32
+ * "user_posts",
33
+ * "user_photos"
34
+ * ],
35
+ * query: {
36
+ * display: "popup",
37
+ * auth_type: "rerequest" // Force permission approval
38
+ * }
39
+ * })
40
+ *
41
+ * // Using the access token for Graph API calls
42
+ * export default issuer({
43
+ * providers: { facebook: extendedFacebook },
44
+ * success: async (ctx, value) => {
45
+ * if (value.provider === "facebook") {
46
+ * const token = value.tokenset.access
47
+ *
48
+ * // Get user profile with custom fields
49
+ * const profileRes = await fetch(
50
+ * `https://graph.facebook.com/me?fields=id,name,email,picture.width(200),friends&access_token=${token}`
51
+ * )
52
+ * const profile = await profileRes.json()
53
+ *
54
+ * // Get user's posts (if permission granted)
55
+ * const postsRes = await fetch(
56
+ * `https://graph.facebook.com/me/posts?access_token=${token}`
57
+ * )
58
+ * const posts = await postsRes.json()
59
+ *
60
+ * return ctx.subject("user", {
61
+ * facebookId: profile.id,
62
+ * name: profile.name,
63
+ * email: profile.email,
64
+ * picture: profile.picture?.data?.url,
65
+ * friendsCount: profile.friends?.summary?.total_count || 0,
66
+ * postsCount: posts.data?.length || 0
67
+ * })
68
+ * }
69
+ * }
70
+ * })
71
+ * ```
72
+ */
73
+ const FacebookProvider = (config) => {
74
+ return Oauth2Provider({
75
+ ...config,
76
+ type: "facebook",
77
+ endpoint: {
78
+ authorization: "https://www.facebook.com/v18.0/dialog/oauth",
79
+ token: "https://graph.facebook.com/v18.0/oauth/access_token"
80
+ }
81
+ });
82
+ };
83
+
84
+ //#endregion
85
+ export { FacebookProvider };