@buildrbets/react 0.1.0 → 0.1.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/index.cjs +35 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +35 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -67,12 +67,29 @@ function clearStoredOAuthParams() {
|
|
|
67
67
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
68
68
|
var DEFAULT_BASE_URL = "https://buildr.bet";
|
|
69
69
|
var DEFAULT_SCOPES = "openid email profile";
|
|
70
|
+
var defaultStyle = {
|
|
71
|
+
backgroundColor: "#5C3FBD",
|
|
72
|
+
color: "#FFFFFF",
|
|
73
|
+
fontSize: "0.875rem",
|
|
74
|
+
fontWeight: 500,
|
|
75
|
+
fontFamily: "Geist, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
|
|
76
|
+
height: "2.5rem",
|
|
77
|
+
padding: "0 1rem",
|
|
78
|
+
borderRadius: "0.75rem",
|
|
79
|
+
border: "none",
|
|
80
|
+
cursor: "pointer",
|
|
81
|
+
display: "inline-flex",
|
|
82
|
+
alignItems: "center",
|
|
83
|
+
justifyContent: "center",
|
|
84
|
+
transition: "background-color 0.15s ease"
|
|
85
|
+
};
|
|
70
86
|
function LoginWithBuildr({
|
|
71
87
|
clientId,
|
|
72
88
|
redirectUri,
|
|
73
89
|
onError,
|
|
74
90
|
baseUrl = DEFAULT_BASE_URL,
|
|
75
91
|
children,
|
|
92
|
+
style,
|
|
76
93
|
...buttonProps
|
|
77
94
|
}) {
|
|
78
95
|
const handleClick = (0, import_react.useCallback)(async () => {
|
|
@@ -95,7 +112,24 @@ function LoginWithBuildr({
|
|
|
95
112
|
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
96
113
|
}
|
|
97
114
|
}, [clientId, redirectUri, baseUrl, onError]);
|
|
98
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
115
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
116
|
+
"button",
|
|
117
|
+
{
|
|
118
|
+
onClick: handleClick,
|
|
119
|
+
type: "button",
|
|
120
|
+
style: { ...defaultStyle, ...style },
|
|
121
|
+
onMouseEnter: (e) => {
|
|
122
|
+
e.currentTarget.style.backgroundColor = style?.backgroundColor ?? "#4A2A9B";
|
|
123
|
+
buttonProps.onMouseEnter?.(e);
|
|
124
|
+
},
|
|
125
|
+
onMouseLeave: (e) => {
|
|
126
|
+
e.currentTarget.style.backgroundColor = style?.backgroundColor ?? "#5C3FBD";
|
|
127
|
+
buttonProps.onMouseLeave?.(e);
|
|
128
|
+
},
|
|
129
|
+
...buttonProps,
|
|
130
|
+
children: children ?? "Login with Buildr"
|
|
131
|
+
}
|
|
132
|
+
);
|
|
99
133
|
}
|
|
100
134
|
|
|
101
135
|
// src/BuildrCallback.tsx
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/LoginWithBuildr.tsx","../src/pkce.ts","../src/BuildrCallback.tsx"],"sourcesContent":["export { LoginWithBuildr } from \"./LoginWithBuildr\";\nexport { BuildrCallback } from \"./BuildrCallback\";\nexport type {\n BuildrUser,\n LoginWithBuildrProps,\n BuildrCallbackProps,\n} from \"./types\";\n","import { useCallback } from \"react\";\nimport type { LoginWithBuildrProps } from \"./types\";\nimport {\n generateCodeVerifier,\n generateCodeChallenge,\n generateState,\n storeOAuthParams,\n} from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\nconst DEFAULT_SCOPES = \"openid email profile\";\n\nexport function LoginWithBuildr({\n clientId,\n redirectUri,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n ...buttonProps\n}: LoginWithBuildrProps) {\n const handleClick = useCallback(async () => {\n try {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n storeOAuthParams(state, codeVerifier);\n\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: DEFAULT_SCOPES,\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n state,\n });\n\n window.location.href = `${baseUrl}/api/oidc/auth?${params}`;\n } catch (err) {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n }, [clientId, redirectUri, baseUrl, onError]);\n\n return (\n <button onClick={handleClick} type=\"button\" {...buttonProps}>\n {children ?? \"Login with Buildr\"}\n </button>\n );\n}\n","const STORAGE_PREFIX = \"buildr_oauth_\";\n\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const data = new TextEncoder().encode(verifier);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return base64UrlEncode(new Uint8Array(hash));\n}\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport function generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport function storeOAuthParams(state: string, codeVerifier: string): void {\n sessionStorage.setItem(`${STORAGE_PREFIX}state`, state);\n sessionStorage.setItem(`${STORAGE_PREFIX}code_verifier`, codeVerifier);\n}\n\nexport function getStoredOAuthParams(): {\n state: string | null;\n codeVerifier: string | null;\n} {\n return {\n state: sessionStorage.getItem(`${STORAGE_PREFIX}state`),\n codeVerifier: sessionStorage.getItem(`${STORAGE_PREFIX}code_verifier`),\n };\n}\n\nexport function clearStoredOAuthParams(): void {\n sessionStorage.removeItem(`${STORAGE_PREFIX}state`);\n sessionStorage.removeItem(`${STORAGE_PREFIX}code_verifier`);\n}\n","import { useEffect, useRef } from \"react\";\nimport type { BuildrCallbackProps, BuildrUser } from \"./types\";\nimport { getStoredOAuthParams, clearStoredOAuthParams } from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\n\nexport function BuildrCallback({\n clientId,\n redirectUri,\n onSuccess,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n}: BuildrCallbackProps) {\n const processedRef = useRef(false);\n\n useEffect(() => {\n if (processedRef.current) return;\n processedRef.current = true;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get(\"code\");\n const returnedState = params.get(\"state\");\n const error = params.get(\"error\");\n\n if (error) {\n const description = params.get(\"error_description\") ?? error;\n onError?.(new Error(description));\n return;\n }\n\n if (!code) {\n onError?.(new Error(\"No authorization code in callback URL\"));\n return;\n }\n\n const stored = getStoredOAuthParams();\n\n if (!stored.state || stored.state !== returnedState) {\n onError?.(new Error(\"Invalid state parameter — possible CSRF attack\"));\n return;\n }\n\n if (!stored.codeVerifier) {\n onError?.(new Error(\"No code verifier found — session may have expired\"));\n return;\n }\n\n const exchangeToken = async () => {\n try {\n // Exchange code for tokens (public client — no client_secret)\n const tokenRes = await fetch(`${baseUrl}/api/oidc/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: stored.codeVerifier!,\n }),\n });\n\n if (!tokenRes.ok) {\n const err = await tokenRes.json().catch(() => ({}));\n throw new Error(\n err.error_description ??\n err.error ??\n `Token exchange failed (${tokenRes.status})`,\n );\n }\n\n const tokens = await tokenRes.json();\n\n // Fetch user info\n const userinfoRes = await fetch(`${baseUrl}/api/oidc/me`, {\n headers: { Authorization: `Bearer ${tokens.access_token}` },\n });\n\n if (!userinfoRes.ok) {\n throw new Error(`Failed to fetch user info (${userinfoRes.status})`);\n }\n\n const user: BuildrUser = await userinfoRes.json();\n clearStoredOAuthParams();\n onSuccess(user);\n } catch (err) {\n clearStoredOAuthParams();\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n };\n\n exchangeToken();\n }, [clientId, redirectUri, baseUrl, onSuccess, onError]);\n\n return <>{children ?? <p>Completing login...</p>}</>;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime","DEFAULT_BASE_URL"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/LoginWithBuildr.tsx","../src/pkce.ts","../src/BuildrCallback.tsx"],"sourcesContent":["export { LoginWithBuildr } from \"./LoginWithBuildr\";\nexport { BuildrCallback } from \"./BuildrCallback\";\nexport type {\n BuildrUser,\n LoginWithBuildrProps,\n BuildrCallbackProps,\n} from \"./types\";\n","import { useCallback } from \"react\";\nimport type { LoginWithBuildrProps } from \"./types\";\nimport {\n generateCodeVerifier,\n generateCodeChallenge,\n generateState,\n storeOAuthParams,\n} from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\nconst DEFAULT_SCOPES = \"openid email profile\";\n\nconst defaultStyle: React.CSSProperties = {\n backgroundColor: \"#5C3FBD\",\n color: \"#FFFFFF\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n fontFamily:\n \"Geist, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif\",\n height: \"2.5rem\",\n padding: \"0 1rem\",\n borderRadius: \"0.75rem\",\n border: \"none\",\n cursor: \"pointer\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n transition: \"background-color 0.15s ease\",\n};\n\nexport function LoginWithBuildr({\n clientId,\n redirectUri,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n style,\n ...buttonProps\n}: LoginWithBuildrProps) {\n const handleClick = useCallback(async () => {\n try {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n storeOAuthParams(state, codeVerifier);\n\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: DEFAULT_SCOPES,\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n state,\n });\n\n window.location.href = `${baseUrl}/api/oidc/auth?${params}`;\n } catch (err) {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n }, [clientId, redirectUri, baseUrl, onError]);\n\n return (\n <button\n onClick={handleClick}\n type=\"button\"\n style={{ ...defaultStyle, ...style }}\n onMouseEnter={(e) => {\n (e.currentTarget.style.backgroundColor =\n style?.backgroundColor ?? \"#4A2A9B\");\n buttonProps.onMouseEnter?.(e);\n }}\n onMouseLeave={(e) => {\n (e.currentTarget.style.backgroundColor =\n style?.backgroundColor ?? \"#5C3FBD\");\n buttonProps.onMouseLeave?.(e);\n }}\n {...buttonProps}\n >\n {children ?? \"Login with Buildr\"}\n </button>\n );\n}\n","const STORAGE_PREFIX = \"buildr_oauth_\";\n\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const data = new TextEncoder().encode(verifier);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return base64UrlEncode(new Uint8Array(hash));\n}\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport function generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport function storeOAuthParams(state: string, codeVerifier: string): void {\n sessionStorage.setItem(`${STORAGE_PREFIX}state`, state);\n sessionStorage.setItem(`${STORAGE_PREFIX}code_verifier`, codeVerifier);\n}\n\nexport function getStoredOAuthParams(): {\n state: string | null;\n codeVerifier: string | null;\n} {\n return {\n state: sessionStorage.getItem(`${STORAGE_PREFIX}state`),\n codeVerifier: sessionStorage.getItem(`${STORAGE_PREFIX}code_verifier`),\n };\n}\n\nexport function clearStoredOAuthParams(): void {\n sessionStorage.removeItem(`${STORAGE_PREFIX}state`);\n sessionStorage.removeItem(`${STORAGE_PREFIX}code_verifier`);\n}\n","import { useEffect, useRef } from \"react\";\nimport type { BuildrCallbackProps, BuildrUser } from \"./types\";\nimport { getStoredOAuthParams, clearStoredOAuthParams } from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\n\nexport function BuildrCallback({\n clientId,\n redirectUri,\n onSuccess,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n}: BuildrCallbackProps) {\n const processedRef = useRef(false);\n\n useEffect(() => {\n if (processedRef.current) return;\n processedRef.current = true;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get(\"code\");\n const returnedState = params.get(\"state\");\n const error = params.get(\"error\");\n\n if (error) {\n const description = params.get(\"error_description\") ?? error;\n onError?.(new Error(description));\n return;\n }\n\n if (!code) {\n onError?.(new Error(\"No authorization code in callback URL\"));\n return;\n }\n\n const stored = getStoredOAuthParams();\n\n if (!stored.state || stored.state !== returnedState) {\n onError?.(new Error(\"Invalid state parameter — possible CSRF attack\"));\n return;\n }\n\n if (!stored.codeVerifier) {\n onError?.(new Error(\"No code verifier found — session may have expired\"));\n return;\n }\n\n const exchangeToken = async () => {\n try {\n // Exchange code for tokens (public client — no client_secret)\n const tokenRes = await fetch(`${baseUrl}/api/oidc/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: stored.codeVerifier!,\n }),\n });\n\n if (!tokenRes.ok) {\n const err = await tokenRes.json().catch(() => ({}));\n throw new Error(\n err.error_description ??\n err.error ??\n `Token exchange failed (${tokenRes.status})`,\n );\n }\n\n const tokens = await tokenRes.json();\n\n // Fetch user info\n const userinfoRes = await fetch(`${baseUrl}/api/oidc/me`, {\n headers: { Authorization: `Bearer ${tokens.access_token}` },\n });\n\n if (!userinfoRes.ok) {\n throw new Error(`Failed to fetch user info (${userinfoRes.status})`);\n }\n\n const user: BuildrUser = await userinfoRes.json();\n clearStoredOAuthParams();\n onSuccess(user);\n } catch (err) {\n clearStoredOAuthParams();\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n };\n\n exchangeToken();\n }, [clientId, redirectUri, baseUrl, onSuccess, onError]);\n\n return <>{children ?? <p>Completing login...</p>}</>;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime","DEFAULT_BASE_URL"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -34,7 +34,7 @@ interface BuildrCallbackProps {
|
|
|
34
34
|
children?: React.ReactNode;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
declare function LoginWithBuildr({ clientId, redirectUri, onError, baseUrl, children, ...buttonProps }: LoginWithBuildrProps): react_jsx_runtime.JSX.Element;
|
|
37
|
+
declare function LoginWithBuildr({ clientId, redirectUri, onError, baseUrl, children, style, ...buttonProps }: LoginWithBuildrProps): react_jsx_runtime.JSX.Element;
|
|
38
38
|
|
|
39
39
|
declare function BuildrCallback({ clientId, redirectUri, onSuccess, onError, baseUrl, children, }: BuildrCallbackProps): react_jsx_runtime.JSX.Element;
|
|
40
40
|
|
package/dist/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ interface BuildrCallbackProps {
|
|
|
34
34
|
children?: React.ReactNode;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
declare function LoginWithBuildr({ clientId, redirectUri, onError, baseUrl, children, ...buttonProps }: LoginWithBuildrProps): react_jsx_runtime.JSX.Element;
|
|
37
|
+
declare function LoginWithBuildr({ clientId, redirectUri, onError, baseUrl, children, style, ...buttonProps }: LoginWithBuildrProps): react_jsx_runtime.JSX.Element;
|
|
38
38
|
|
|
39
39
|
declare function BuildrCallback({ clientId, redirectUri, onSuccess, onError, baseUrl, children, }: BuildrCallbackProps): react_jsx_runtime.JSX.Element;
|
|
40
40
|
|
package/dist/index.js
CHANGED
|
@@ -40,12 +40,29 @@ function clearStoredOAuthParams() {
|
|
|
40
40
|
import { jsx } from "react/jsx-runtime";
|
|
41
41
|
var DEFAULT_BASE_URL = "https://buildr.bet";
|
|
42
42
|
var DEFAULT_SCOPES = "openid email profile";
|
|
43
|
+
var defaultStyle = {
|
|
44
|
+
backgroundColor: "#5C3FBD",
|
|
45
|
+
color: "#FFFFFF",
|
|
46
|
+
fontSize: "0.875rem",
|
|
47
|
+
fontWeight: 500,
|
|
48
|
+
fontFamily: "Geist, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif",
|
|
49
|
+
height: "2.5rem",
|
|
50
|
+
padding: "0 1rem",
|
|
51
|
+
borderRadius: "0.75rem",
|
|
52
|
+
border: "none",
|
|
53
|
+
cursor: "pointer",
|
|
54
|
+
display: "inline-flex",
|
|
55
|
+
alignItems: "center",
|
|
56
|
+
justifyContent: "center",
|
|
57
|
+
transition: "background-color 0.15s ease"
|
|
58
|
+
};
|
|
43
59
|
function LoginWithBuildr({
|
|
44
60
|
clientId,
|
|
45
61
|
redirectUri,
|
|
46
62
|
onError,
|
|
47
63
|
baseUrl = DEFAULT_BASE_URL,
|
|
48
64
|
children,
|
|
65
|
+
style,
|
|
49
66
|
...buttonProps
|
|
50
67
|
}) {
|
|
51
68
|
const handleClick = useCallback(async () => {
|
|
@@ -68,7 +85,24 @@ function LoginWithBuildr({
|
|
|
68
85
|
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
69
86
|
}
|
|
70
87
|
}, [clientId, redirectUri, baseUrl, onError]);
|
|
71
|
-
return /* @__PURE__ */ jsx(
|
|
88
|
+
return /* @__PURE__ */ jsx(
|
|
89
|
+
"button",
|
|
90
|
+
{
|
|
91
|
+
onClick: handleClick,
|
|
92
|
+
type: "button",
|
|
93
|
+
style: { ...defaultStyle, ...style },
|
|
94
|
+
onMouseEnter: (e) => {
|
|
95
|
+
e.currentTarget.style.backgroundColor = style?.backgroundColor ?? "#4A2A9B";
|
|
96
|
+
buttonProps.onMouseEnter?.(e);
|
|
97
|
+
},
|
|
98
|
+
onMouseLeave: (e) => {
|
|
99
|
+
e.currentTarget.style.backgroundColor = style?.backgroundColor ?? "#5C3FBD";
|
|
100
|
+
buttonProps.onMouseLeave?.(e);
|
|
101
|
+
},
|
|
102
|
+
...buttonProps,
|
|
103
|
+
children: children ?? "Login with Buildr"
|
|
104
|
+
}
|
|
105
|
+
);
|
|
72
106
|
}
|
|
73
107
|
|
|
74
108
|
// src/BuildrCallback.tsx
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/LoginWithBuildr.tsx","../src/pkce.ts","../src/BuildrCallback.tsx"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { LoginWithBuildrProps } from \"./types\";\nimport {\n generateCodeVerifier,\n generateCodeChallenge,\n generateState,\n storeOAuthParams,\n} from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\nconst DEFAULT_SCOPES = \"openid email profile\";\n\nexport function LoginWithBuildr({\n clientId,\n redirectUri,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n ...buttonProps\n}: LoginWithBuildrProps) {\n const handleClick = useCallback(async () => {\n try {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n storeOAuthParams(state, codeVerifier);\n\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: DEFAULT_SCOPES,\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n state,\n });\n\n window.location.href = `${baseUrl}/api/oidc/auth?${params}`;\n } catch (err) {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n }, [clientId, redirectUri, baseUrl, onError]);\n\n return (\n <button onClick={handleClick} type=\"button\" {...buttonProps}>\n {children ?? \"Login with Buildr\"}\n </button>\n );\n}\n","const STORAGE_PREFIX = \"buildr_oauth_\";\n\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const data = new TextEncoder().encode(verifier);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return base64UrlEncode(new Uint8Array(hash));\n}\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport function generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport function storeOAuthParams(state: string, codeVerifier: string): void {\n sessionStorage.setItem(`${STORAGE_PREFIX}state`, state);\n sessionStorage.setItem(`${STORAGE_PREFIX}code_verifier`, codeVerifier);\n}\n\nexport function getStoredOAuthParams(): {\n state: string | null;\n codeVerifier: string | null;\n} {\n return {\n state: sessionStorage.getItem(`${STORAGE_PREFIX}state`),\n codeVerifier: sessionStorage.getItem(`${STORAGE_PREFIX}code_verifier`),\n };\n}\n\nexport function clearStoredOAuthParams(): void {\n sessionStorage.removeItem(`${STORAGE_PREFIX}state`);\n sessionStorage.removeItem(`${STORAGE_PREFIX}code_verifier`);\n}\n","import { useEffect, useRef } from \"react\";\nimport type { BuildrCallbackProps, BuildrUser } from \"./types\";\nimport { getStoredOAuthParams, clearStoredOAuthParams } from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\n\nexport function BuildrCallback({\n clientId,\n redirectUri,\n onSuccess,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n}: BuildrCallbackProps) {\n const processedRef = useRef(false);\n\n useEffect(() => {\n if (processedRef.current) return;\n processedRef.current = true;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get(\"code\");\n const returnedState = params.get(\"state\");\n const error = params.get(\"error\");\n\n if (error) {\n const description = params.get(\"error_description\") ?? error;\n onError?.(new Error(description));\n return;\n }\n\n if (!code) {\n onError?.(new Error(\"No authorization code in callback URL\"));\n return;\n }\n\n const stored = getStoredOAuthParams();\n\n if (!stored.state || stored.state !== returnedState) {\n onError?.(new Error(\"Invalid state parameter — possible CSRF attack\"));\n return;\n }\n\n if (!stored.codeVerifier) {\n onError?.(new Error(\"No code verifier found — session may have expired\"));\n return;\n }\n\n const exchangeToken = async () => {\n try {\n // Exchange code for tokens (public client — no client_secret)\n const tokenRes = await fetch(`${baseUrl}/api/oidc/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: stored.codeVerifier!,\n }),\n });\n\n if (!tokenRes.ok) {\n const err = await tokenRes.json().catch(() => ({}));\n throw new Error(\n err.error_description ??\n err.error ??\n `Token exchange failed (${tokenRes.status})`,\n );\n }\n\n const tokens = await tokenRes.json();\n\n // Fetch user info\n const userinfoRes = await fetch(`${baseUrl}/api/oidc/me`, {\n headers: { Authorization: `Bearer ${tokens.access_token}` },\n });\n\n if (!userinfoRes.ok) {\n throw new Error(`Failed to fetch user info (${userinfoRes.status})`);\n }\n\n const user: BuildrUser = await userinfoRes.json();\n clearStoredOAuthParams();\n onSuccess(user);\n } catch (err) {\n clearStoredOAuthParams();\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n };\n\n exchangeToken();\n }, [clientId, redirectUri, baseUrl, onSuccess, onError]);\n\n return <>{children ?? <p>Completing login...</p>}</>;\n}\n"],"mappings":"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","names":["jsx","DEFAULT_BASE_URL"]}
|
|
1
|
+
{"version":3,"sources":["../src/LoginWithBuildr.tsx","../src/pkce.ts","../src/BuildrCallback.tsx"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { LoginWithBuildrProps } from \"./types\";\nimport {\n generateCodeVerifier,\n generateCodeChallenge,\n generateState,\n storeOAuthParams,\n} from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\nconst DEFAULT_SCOPES = \"openid email profile\";\n\nconst defaultStyle: React.CSSProperties = {\n backgroundColor: \"#5C3FBD\",\n color: \"#FFFFFF\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n fontFamily:\n \"Geist, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif\",\n height: \"2.5rem\",\n padding: \"0 1rem\",\n borderRadius: \"0.75rem\",\n border: \"none\",\n cursor: \"pointer\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n transition: \"background-color 0.15s ease\",\n};\n\nexport function LoginWithBuildr({\n clientId,\n redirectUri,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n style,\n ...buttonProps\n}: LoginWithBuildrProps) {\n const handleClick = useCallback(async () => {\n try {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n storeOAuthParams(state, codeVerifier);\n\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: DEFAULT_SCOPES,\n code_challenge: codeChallenge,\n code_challenge_method: \"S256\",\n state,\n });\n\n window.location.href = `${baseUrl}/api/oidc/auth?${params}`;\n } catch (err) {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n }, [clientId, redirectUri, baseUrl, onError]);\n\n return (\n <button\n onClick={handleClick}\n type=\"button\"\n style={{ ...defaultStyle, ...style }}\n onMouseEnter={(e) => {\n (e.currentTarget.style.backgroundColor =\n style?.backgroundColor ?? \"#4A2A9B\");\n buttonProps.onMouseEnter?.(e);\n }}\n onMouseLeave={(e) => {\n (e.currentTarget.style.backgroundColor =\n style?.backgroundColor ?? \"#5C3FBD\");\n buttonProps.onMouseLeave?.(e);\n }}\n {...buttonProps}\n >\n {children ?? \"Login with Buildr\"}\n </button>\n );\n}\n","const STORAGE_PREFIX = \"buildr_oauth_\";\n\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const data = new TextEncoder().encode(verifier);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return base64UrlEncode(new Uint8Array(hash));\n}\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nexport function generateState(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\nexport function storeOAuthParams(state: string, codeVerifier: string): void {\n sessionStorage.setItem(`${STORAGE_PREFIX}state`, state);\n sessionStorage.setItem(`${STORAGE_PREFIX}code_verifier`, codeVerifier);\n}\n\nexport function getStoredOAuthParams(): {\n state: string | null;\n codeVerifier: string | null;\n} {\n return {\n state: sessionStorage.getItem(`${STORAGE_PREFIX}state`),\n codeVerifier: sessionStorage.getItem(`${STORAGE_PREFIX}code_verifier`),\n };\n}\n\nexport function clearStoredOAuthParams(): void {\n sessionStorage.removeItem(`${STORAGE_PREFIX}state`);\n sessionStorage.removeItem(`${STORAGE_PREFIX}code_verifier`);\n}\n","import { useEffect, useRef } from \"react\";\nimport type { BuildrCallbackProps, BuildrUser } from \"./types\";\nimport { getStoredOAuthParams, clearStoredOAuthParams } from \"./pkce\";\n\nconst DEFAULT_BASE_URL = \"https://buildr.bet\";\n\nexport function BuildrCallback({\n clientId,\n redirectUri,\n onSuccess,\n onError,\n baseUrl = DEFAULT_BASE_URL,\n children,\n}: BuildrCallbackProps) {\n const processedRef = useRef(false);\n\n useEffect(() => {\n if (processedRef.current) return;\n processedRef.current = true;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get(\"code\");\n const returnedState = params.get(\"state\");\n const error = params.get(\"error\");\n\n if (error) {\n const description = params.get(\"error_description\") ?? error;\n onError?.(new Error(description));\n return;\n }\n\n if (!code) {\n onError?.(new Error(\"No authorization code in callback URL\"));\n return;\n }\n\n const stored = getStoredOAuthParams();\n\n if (!stored.state || stored.state !== returnedState) {\n onError?.(new Error(\"Invalid state parameter — possible CSRF attack\"));\n return;\n }\n\n if (!stored.codeVerifier) {\n onError?.(new Error(\"No code verifier found — session may have expired\"));\n return;\n }\n\n const exchangeToken = async () => {\n try {\n // Exchange code for tokens (public client — no client_secret)\n const tokenRes = await fetch(`${baseUrl}/api/oidc/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: stored.codeVerifier!,\n }),\n });\n\n if (!tokenRes.ok) {\n const err = await tokenRes.json().catch(() => ({}));\n throw new Error(\n err.error_description ??\n err.error ??\n `Token exchange failed (${tokenRes.status})`,\n );\n }\n\n const tokens = await tokenRes.json();\n\n // Fetch user info\n const userinfoRes = await fetch(`${baseUrl}/api/oidc/me`, {\n headers: { Authorization: `Bearer ${tokens.access_token}` },\n });\n\n if (!userinfoRes.ok) {\n throw new Error(`Failed to fetch user info (${userinfoRes.status})`);\n }\n\n const user: BuildrUser = await userinfoRes.json();\n clearStoredOAuthParams();\n onSuccess(user);\n } catch (err) {\n clearStoredOAuthParams();\n onError?.(err instanceof Error ? err : new Error(String(err)));\n }\n };\n\n exchangeToken();\n }, [clientId, redirectUri, baseUrl, onSuccess, onError]);\n\n return <>{children ?? <p>Completing login...</p>}</>;\n}\n"],"mappings":"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","names":["jsx","DEFAULT_BASE_URL"]}
|