@burdenoff/microfe-auth 2026.601.2 → 2026.601.3
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.
|
@@ -32,7 +32,20 @@ function a() {
|
|
|
32
32
|
let e = new Uint8Array(32);
|
|
33
33
|
return crypto.getRandomValues(e), Array.from(e, (e) => e.toString(16).padStart(2, "0")).join("");
|
|
34
34
|
}
|
|
35
|
-
function o() {
|
|
35
|
+
function o(e) {
|
|
36
|
+
try {
|
|
37
|
+
return {
|
|
38
|
+
BASE_URL: "/",
|
|
39
|
+
DEV: !1,
|
|
40
|
+
MODE: "production",
|
|
41
|
+
PROD: !0,
|
|
42
|
+
SSR: !1
|
|
43
|
+
}?.[e];
|
|
44
|
+
} catch {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function s() {
|
|
36
49
|
if (typeof window > "u") return !1;
|
|
37
50
|
let e = window;
|
|
38
51
|
if (typeof e.Capacitor?.isNativePlatform == "function") try {
|
|
@@ -40,8 +53,8 @@ function o() {
|
|
|
40
53
|
} catch {}
|
|
41
54
|
return !!(typeof navigator < "u" && /Electron/i.test(navigator.userAgent || ""));
|
|
42
55
|
}
|
|
43
|
-
var
|
|
44
|
-
async function
|
|
56
|
+
var c = "vibecontrols://app/auth/oauth/callback";
|
|
57
|
+
async function l(e) {
|
|
45
58
|
if (typeof window > "u") return;
|
|
46
59
|
let t = window.__burdenoffOpenExternal;
|
|
47
60
|
if (typeof t == "function") try {
|
|
@@ -49,31 +62,27 @@ async function c(e) {
|
|
|
49
62
|
} catch {}
|
|
50
63
|
window.location.href = e;
|
|
51
64
|
}
|
|
52
|
-
function
|
|
53
|
-
let
|
|
54
|
-
let t = i[e], n =
|
|
55
|
-
VITE_GOOGLE_CLIENT_ID: void 0,
|
|
56
|
-
VITE_GITHUB_CLIENT_ID: void 0,
|
|
57
|
-
VITE_MICROSOFT_CLIENT_ID: void 0
|
|
58
|
-
}[t.clientIdEnvKey];
|
|
65
|
+
function u() {
|
|
66
|
+
let u = e(), [d, { loading: f }] = r(n), p = (e) => u.oauthClientIds?.[e] || o(i[e].clientIdEnvKey), m = (e) => {
|
|
67
|
+
let t = i[e], n = p(e);
|
|
59
68
|
if (!n) throw console.error(`OAuth client ID not configured for ${e}`), Error(`${e} OAuth is not configured. Please set ${t.clientIdEnvKey} environment variable.`);
|
|
60
69
|
let r = a();
|
|
61
70
|
localStorage.setItem("oauth_state", r), localStorage.setItem("oauth_provider", e);
|
|
62
|
-
let
|
|
63
|
-
localStorage.setItem("oauth_redirect_uri",
|
|
71
|
+
let o = s() ? c : `${window.location.origin}/auth/oauth/callback`;
|
|
72
|
+
localStorage.setItem("oauth_redirect_uri", o);
|
|
64
73
|
let u = new URLSearchParams({
|
|
65
74
|
client_id: n,
|
|
66
|
-
redirect_uri:
|
|
75
|
+
redirect_uri: o,
|
|
67
76
|
response_type: "code",
|
|
68
77
|
scope: t.scopes.join(" "),
|
|
69
78
|
state: r,
|
|
70
79
|
...e === "google" && { prompt: "select_account" },
|
|
71
80
|
...e === "google" && { access_type: "offline" }
|
|
72
81
|
});
|
|
73
|
-
|
|
82
|
+
l(`${t.authorizationUrl}?${u.toString()}`);
|
|
74
83
|
};
|
|
75
84
|
return {
|
|
76
|
-
initiateOAuth:
|
|
85
|
+
initiateOAuth: m,
|
|
77
86
|
completeOAuth: async (e, n) => {
|
|
78
87
|
let r = t.getState(), i = localStorage.getItem("oauth_state");
|
|
79
88
|
if (!i || i !== n) throw console.error("[OAuth Complete] State mismatch!", {
|
|
@@ -85,7 +94,7 @@ function l() {
|
|
|
85
94
|
localStorage.removeItem("oauth_state"), localStorage.removeItem("oauth_provider"), localStorage.removeItem("oauth_redirect_uri");
|
|
86
95
|
try {
|
|
87
96
|
r.setLoading(!0), r.setError(null);
|
|
88
|
-
let { data: t, error: n } = await
|
|
97
|
+
let { data: t, error: n } = await d({ variables: { input: {
|
|
89
98
|
provider: a.toUpperCase(),
|
|
90
99
|
code: e,
|
|
91
100
|
redirectUri: o
|
|
@@ -95,7 +104,7 @@ function l() {
|
|
|
95
104
|
throw Error(e);
|
|
96
105
|
}
|
|
97
106
|
let i = t.authenticateOAuth;
|
|
98
|
-
return i.requiresMFA ? (r.setMfaRequired(!0, i.mfaChallengeId || void 0), i) : (i.success && i.user && i.token ? (r.setUser(i.user), r.setAuthTokens(i.token, i.refreshToken || ""), r.setAuthenticated(!0), r.setMfaRequired(!1),
|
|
107
|
+
return i.requiresMFA ? (r.setMfaRequired(!0, i.mfaChallengeId || void 0), i) : (i.success && i.user && i.token ? (r.setUser(i.user), r.setAuthTokens(i.token, i.refreshToken || ""), r.setAuthenticated(!0), r.setMfaRequired(!1), u.onAuthSuccess?.(i.user, i.token, i.refreshToken || void 0)) : i.error && r.setError(i.error), i);
|
|
99
108
|
} catch (e) {
|
|
100
109
|
let t = e instanceof Error ? e.message : "OAuth authentication failed";
|
|
101
110
|
throw r.setError(t), e;
|
|
@@ -103,18 +112,14 @@ function l() {
|
|
|
103
112
|
r.setLoading(!1);
|
|
104
113
|
}
|
|
105
114
|
},
|
|
106
|
-
initiateGoogleOAuth: () =>
|
|
107
|
-
initiateGitHubOAuth: () =>
|
|
108
|
-
initiateMicrosoftOAuth: () =>
|
|
109
|
-
isProviderConfigured: (e) => !!
|
|
110
|
-
|
|
111
|
-
VITE_GITHUB_CLIENT_ID: void 0,
|
|
112
|
-
VITE_MICROSOFT_CLIENT_ID: void 0
|
|
113
|
-
}[i[e].clientIdEnvKey],
|
|
114
|
-
isLoading: d
|
|
115
|
+
initiateGoogleOAuth: () => m("google"),
|
|
116
|
+
initiateGitHubOAuth: () => m("github"),
|
|
117
|
+
initiateMicrosoftOAuth: () => m("microsoft"),
|
|
118
|
+
isProviderConfigured: (e) => !!p(e),
|
|
119
|
+
isLoading: f
|
|
115
120
|
};
|
|
116
121
|
}
|
|
117
122
|
//#endregion
|
|
118
|
-
export {
|
|
123
|
+
export { u as useOAuthOperations };
|
|
119
124
|
|
|
120
125
|
//# sourceMappingURL=useOAuthOperations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOAuthOperations.js","names":[],"sources":["../../../src/auth/hooks/useOAuthOperations.ts"],"sourcesContent":["import { useMutation } from \"@apollo/client/react\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport type { AuthResult, OAuthInput } from \"../types\";\n\nimport { AuthenticateOAuthDocument } from \"../../generated/global-operations\";\n\n// =============================================================================\n// GRAPHQL RESPONSE INTERFACES\n// =============================================================================\n\ninterface AuthenticateOAuthResponse {\n authenticateOAuth: AuthResult;\n}\n\ninterface AuthenticateOAuthVariables {\n input: OAuthInput;\n}\n\n/**\n * Supported OAuth providers\n */\nexport type OAuthProvider = \"google\" | \"github\" | \"microsoft\";\n\n/**\n * OAuth configuration for each provider\n */\ninterface OAuthConfig {\n authorizationUrl: string;\n scopes: string[];\n clientIdEnvKey: string;\n}\n\nconst OAUTH_CONFIGS: Record<OAuthProvider, OAuthConfig> = {\n google: {\n authorizationUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n scopes: [\"openid\", \"email\", \"profile\"],\n clientIdEnvKey: \"VITE_GOOGLE_CLIENT_ID\",\n },\n github: {\n authorizationUrl: \"https://github.com/login/oauth/authorize\",\n scopes: [\"user:email\", \"read:user\"],\n clientIdEnvKey: \"VITE_GITHUB_CLIENT_ID\",\n },\n microsoft: {\n authorizationUrl:\n \"https://login.microsoftonline.com/common/oauth2/v2.0/authorize\",\n scopes: [\"openid\", \"email\", \"profile\"],\n clientIdEnvKey: \"VITE_MICROSOFT_CLIENT_ID\",\n },\n};\n\n/**\n * Generate a random state parameter for CSRF protection\n */\nfunction generateState(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return Array.from(array, (byte) => byte.toString(16).padStart(2, \"0\")).join(\n \"\",\n );\n}\n\n/**\n * Detect whether the bundle is running inside a native shell (Capacitor on\n * iOS/Android, or Electron). Mirrors the helper in LoginPage so OAuth\n * initiation can swap to a custom-scheme redirect_uri + system-browser\n * hand-off — the only flow OAuth providers will accept from a single-device\n * install.\n */\nfunction isNativeShell(): boolean {\n if (typeof window === \"undefined\") return false;\n const w = window as typeof window & {\n Capacitor?: { isNativePlatform?: () => boolean };\n };\n if (typeof w.Capacitor?.isNativePlatform === \"function\") {\n try {\n if (w.Capacitor.isNativePlatform()) return true;\n } catch {\n /* fall through */\n }\n }\n if (\n typeof navigator !== \"undefined\" &&\n /Electron/i.test(navigator.userAgent || \"\")\n ) {\n return true;\n }\n return false;\n}\n\n/**\n * Custom-scheme redirect URI used in native shells. The scheme is registered\n * by the host shell (Electron `setAsDefaultProtocolClient`, iOS Info.plist\n * CFBundleURLTypes, Android intent-filter). The OS routes the provider's\n * callback back to the running app, which forwards it to React Router via\n * the host's deep-link handler.\n *\n * IMPORTANT: This redirect URI MUST be registered for the OAuth client in\n * each provider's console:\n * - Google Cloud Console → Credentials → OAuth client → add\n * `vibecontrols://app/auth/oauth/callback` (Installed App).\n * - Microsoft Entra App registration → Authentication → register the\n * same custom-scheme redirect URI as a mobile/desktop redirect.\n * The backend `authenticateOAuth` mutation must also accept this redirectUri\n * verbatim during the code exchange.\n */\nconst NATIVE_OAUTH_REDIRECT_URI = \"vibecontrols://app/auth/oauth/callback\";\n\nasync function openOAuthAuthorizationUrl(authUrl: string): Promise<void> {\n if (typeof window === \"undefined\") return;\n const opener = (\n window as typeof window & {\n __burdenoffOpenExternal?: (url: string) => Promise<boolean>;\n }\n ).__burdenoffOpenExternal;\n if (typeof opener === \"function\") {\n try {\n const ok = await opener(authUrl);\n if (ok) return;\n } catch {\n /* fall through to legacy hand-off */\n }\n }\n // Browser fallback: same-window navigation (current production behavior).\n window.location.href = authUrl;\n}\n\n/**\n * Hook for OAuth authentication operations\n */\nexport function useOAuthOperations() {\n const context = useAuth();\n const [authenticateOAuthMutation, { loading }] = useMutation<\n AuthenticateOAuthResponse,\n AuthenticateOAuthVariables\n >(AuthenticateOAuthDocument);\n\n /**\n * Initiate OAuth flow by redirecting to the provider\n * @param provider - The OAuth provider to use\n */\n const initiateOAuth = (provider: OAuthProvider) => {\n const config = OAUTH_CONFIGS[provider];\n\n // Get client ID from environment\n // In Vite, environment variables are accessed via import.meta.env\n const envVars: Record<string, string | undefined> = {\n VITE_GOOGLE_CLIENT_ID: import.meta.env.VITE_GOOGLE_CLIENT_ID,\n VITE_GITHUB_CLIENT_ID: import.meta.env.VITE_GITHUB_CLIENT_ID,\n VITE_MICROSOFT_CLIENT_ID: import.meta.env.VITE_MICROSOFT_CLIENT_ID,\n };\n const clientId = envVars[config.clientIdEnvKey];\n\n if (!clientId) {\n console.error(`OAuth client ID not configured for ${provider}`);\n throw new Error(\n `${provider} OAuth is not configured. Please set ${config.clientIdEnvKey} environment variable.`,\n );\n }\n\n // Generate state for CSRF protection and store it\n // Use localStorage instead of sessionStorage for better persistence across redirects\n const state = generateState();\n localStorage.setItem(\"oauth_state\", state);\n localStorage.setItem(\"oauth_provider\", provider);\n\n // Native shells (Electron, Capacitor) can't use the current origin as the\n // redirect_uri — `vibecontrols://app` / `https://localhost` aren't valid\n // registered URIs at OAuth providers. Use a custom-scheme URL that the\n // host shell has registered as a deep-link target; the OS will route the\n // provider's callback back into the running app.\n const redirectUri = isNativeShell()\n ? NATIVE_OAUTH_REDIRECT_URI\n : `${window.location.origin}/auth/oauth/callback`;\n localStorage.setItem(\"oauth_redirect_uri\", redirectUri);\n\n // Build authorization URL\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: config.scopes.join(\" \"),\n state: state,\n // Google-specific: force account selection\n ...(provider === \"google\" && { prompt: \"select_account\" }),\n // Google-specific: enable offline access for refresh token\n ...(provider === \"google\" && { access_type: \"offline\" }),\n });\n\n const authUrl = `${config.authorizationUrl}?${params.toString()}`;\n\n // Hand off to the system browser in native shells (keeps the embedded\n // webview in place so the deep-link callback can return cleanly). On the\n // web this falls back to same-window navigation.\n void openOAuthAuthorizationUrl(authUrl);\n };\n\n /**\n * Complete OAuth flow by exchanging authorization code for tokens\n * Called from the callback page after provider redirects back\n * @param code - Authorization code from OAuth provider\n * @param state - State parameter for CSRF validation\n */\n const completeOAuth = async (\n code: string,\n state: string,\n ): Promise<AuthResult> => {\n const store = useAuthStore.getState();\n\n // Validate state parameter\n const storedState = localStorage.getItem(\"oauth_state\");\n if (!storedState || storedState !== state) {\n console.error(\"[OAuth Complete] State mismatch!\", {\n storedState,\n urlState: state,\n });\n throw new Error(\"Invalid OAuth state. Please try again.\");\n }\n\n // Get provider and redirect URI from localStorage\n const provider = localStorage.getItem(\"oauth_provider\") as OAuthProvider;\n const redirectUri = localStorage.getItem(\"oauth_redirect_uri\");\n\n if (!provider || !redirectUri) {\n console.error(\"[OAuth Complete] Missing provider or redirectUri\");\n throw new Error(\"OAuth session data missing. Please try again.\");\n }\n\n // Clear localStorage\n localStorage.removeItem(\"oauth_state\");\n localStorage.removeItem(\"oauth_provider\");\n localStorage.removeItem(\"oauth_redirect_uri\");\n\n try {\n store.setLoading(true);\n store.setError(null);\n\n // Call backend mutation to exchange code for tokens\n const { data, error } = await authenticateOAuthMutation({\n variables: {\n input: {\n provider: provider.toUpperCase(),\n code,\n redirectUri,\n },\n },\n });\n\n if (!data?.authenticateOAuth) {\n const errMsg =\n error?.message ?? \"OAuth authentication failed. Please try again.\";\n throw new Error(errMsg);\n }\n\n const result = data.authenticateOAuth;\n\n if (result.requiresMFA) {\n store.setMfaRequired(true, result.mfaChallengeId || undefined);\n return result;\n }\n\n if (result.success && result.user && result.token) {\n store.setUser(result.user);\n store.setAuthTokens(result.token, result.refreshToken || \"\");\n store.setAuthenticated(true);\n store.setMfaRequired(false);\n\n context.onAuthSuccess?.(\n result.user,\n result.token,\n result.refreshToken || undefined,\n );\n } else if (result.error) {\n store.setError(result.error);\n }\n\n return result;\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"OAuth authentication failed\";\n store.setError(message);\n throw err;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Convenience method to initiate Google OAuth\n */\n const initiateGoogleOAuth = () => initiateOAuth(\"google\");\n\n /**\n * Convenience method to initiate GitHub OAuth\n */\n const initiateGitHubOAuth = () => initiateOAuth(\"github\");\n\n /**\n * Convenience method to initiate Microsoft OAuth\n */\n const initiateMicrosoftOAuth = () => initiateOAuth(\"microsoft\");\n\n /**\n * Check if a provider is configured (has client ID)\n */\n const isProviderConfigured = (provider: OAuthProvider): boolean => {\n const envVars: Record<string, string | undefined> = {\n VITE_GOOGLE_CLIENT_ID: import.meta.env.VITE_GOOGLE_CLIENT_ID,\n VITE_GITHUB_CLIENT_ID: import.meta.env.VITE_GITHUB_CLIENT_ID,\n VITE_MICROSOFT_CLIENT_ID: import.meta.env.VITE_MICROSOFT_CLIENT_ID,\n };\n const config = OAUTH_CONFIGS[provider];\n const clientId = envVars[config.clientIdEnvKey];\n return !!clientId;\n };\n\n return {\n initiateOAuth,\n completeOAuth,\n initiateGoogleOAuth,\n initiateGitHubOAuth,\n initiateMicrosoftOAuth,\n isProviderConfigured,\n isLoading: loading,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useOAuthOperations.js","names":[],"sources":["../../../src/auth/hooks/useOAuthOperations.ts"],"sourcesContent":["import { useMutation } from \"@apollo/client/react\";\nimport { useAuthStore } from \"../store/authStore\";\nimport { useAuth } from \"../providers/AuthProvider\";\nimport type { AuthResult, OAuthInput } from \"../types\";\n\nimport { AuthenticateOAuthDocument } from \"../../generated/global-operations\";\n\n// =============================================================================\n// GRAPHQL RESPONSE INTERFACES\n// =============================================================================\n\ninterface AuthenticateOAuthResponse {\n authenticateOAuth: AuthResult;\n}\n\ninterface AuthenticateOAuthVariables {\n input: OAuthInput;\n}\n\n/**\n * Supported OAuth providers\n */\nexport type OAuthProvider = \"google\" | \"github\" | \"microsoft\";\n\n/**\n * OAuth configuration for each provider\n */\ninterface OAuthConfig {\n authorizationUrl: string;\n scopes: string[];\n clientIdEnvKey: string;\n}\n\nconst OAUTH_CONFIGS: Record<OAuthProvider, OAuthConfig> = {\n google: {\n authorizationUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n scopes: [\"openid\", \"email\", \"profile\"],\n clientIdEnvKey: \"VITE_GOOGLE_CLIENT_ID\",\n },\n github: {\n authorizationUrl: \"https://github.com/login/oauth/authorize\",\n scopes: [\"user:email\", \"read:user\"],\n clientIdEnvKey: \"VITE_GITHUB_CLIENT_ID\",\n },\n microsoft: {\n authorizationUrl:\n \"https://login.microsoftonline.com/common/oauth2/v2.0/authorize\",\n scopes: [\"openid\", \"email\", \"profile\"],\n clientIdEnvKey: \"VITE_MICROSOFT_CLIENT_ID\",\n },\n};\n\n/**\n * Generate a random state parameter for CSRF protection\n */\nfunction generateState(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return Array.from(array, (byte) => byte.toString(16).padStart(2, \"0\")).join(\n \"\",\n );\n}\n\n/**\n * Read a Vite env var by key via a dynamic (non-statically-analyzable) lookup.\n *\n * Build-time composition compiles THIS package into its own npm dist first —\n * where `VITE_GOOGLE_CLIENT_ID` / `VITE_MICROSOFT_CLIENT_ID` are unset — and\n * only later bundles that dist into a host app shell. A direct\n * `import.meta.env.VITE_GOOGLE_CLIENT_ID` member read is statically replaced\n * with `undefined` during this package's build, so it ships dead and the host\n * shell's build value can never reach it (this is exactly why the SSO buttons\n * rendered permanently disabled).\n *\n * Reading the whole `import.meta.env` object and indexing it dynamically leaves\n * the lookup to runtime, so the value baked into `import.meta.env` by the HOST\n * app shell's build wins. Same pattern fe-libs uses for `VITE_GATEWAY_TARGET`.\n */\nfunction readViteEnv(key: string): string | undefined {\n try {\n const env = (import.meta as { env?: Record<string, string | undefined> })\n .env;\n return env?.[key];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Detect whether the bundle is running inside a native shell (Capacitor on\n * iOS/Android, or Electron). Mirrors the helper in LoginPage so OAuth\n * initiation can swap to a custom-scheme redirect_uri + system-browser\n * hand-off — the only flow OAuth providers will accept from a single-device\n * install.\n */\nfunction isNativeShell(): boolean {\n if (typeof window === \"undefined\") return false;\n const w = window as typeof window & {\n Capacitor?: { isNativePlatform?: () => boolean };\n };\n if (typeof w.Capacitor?.isNativePlatform === \"function\") {\n try {\n if (w.Capacitor.isNativePlatform()) return true;\n } catch {\n /* fall through */\n }\n }\n if (\n typeof navigator !== \"undefined\" &&\n /Electron/i.test(navigator.userAgent || \"\")\n ) {\n return true;\n }\n return false;\n}\n\n/**\n * Custom-scheme redirect URI used in native shells. The scheme is registered\n * by the host shell (Electron `setAsDefaultProtocolClient`, iOS Info.plist\n * CFBundleURLTypes, Android intent-filter). The OS routes the provider's\n * callback back to the running app, which forwards it to React Router via\n * the host's deep-link handler.\n *\n * IMPORTANT: This redirect URI MUST be registered for the OAuth client in\n * each provider's console:\n * - Google Cloud Console → Credentials → OAuth client → add\n * `vibecontrols://app/auth/oauth/callback` (Installed App).\n * - Microsoft Entra App registration → Authentication → register the\n * same custom-scheme redirect URI as a mobile/desktop redirect.\n * The backend `authenticateOAuth` mutation must also accept this redirectUri\n * verbatim during the code exchange.\n */\nconst NATIVE_OAUTH_REDIRECT_URI = \"vibecontrols://app/auth/oauth/callback\";\n\nasync function openOAuthAuthorizationUrl(authUrl: string): Promise<void> {\n if (typeof window === \"undefined\") return;\n const opener = (\n window as typeof window & {\n __burdenoffOpenExternal?: (url: string) => Promise<boolean>;\n }\n ).__burdenoffOpenExternal;\n if (typeof opener === \"function\") {\n try {\n const ok = await opener(authUrl);\n if (ok) return;\n } catch {\n /* fall through to legacy hand-off */\n }\n }\n // Browser fallback: same-window navigation (current production behavior).\n window.location.href = authUrl;\n}\n\n/**\n * Hook for OAuth authentication operations\n */\nexport function useOAuthOperations() {\n const context = useAuth();\n const [authenticateOAuthMutation, { loading }] = useMutation<\n AuthenticateOAuthResponse,\n AuthenticateOAuthVariables\n >(AuthenticateOAuthDocument);\n\n /**\n * Resolve a provider's OAuth client ID.\n *\n * Priority:\n * 1. `oauthClientIds` injected by the host app shell — the only source that\n * works in published builds (see AuthRootProps.oauthClientIds for why a\n * direct `import.meta.env` read is baked dead at this package's build).\n * 2. `import.meta.env.VITE_*_CLIENT_ID` — fallback for when this package is\n * consumed from source in local dev (where the host's Vite build inlines\n * it into the same bundle).\n */\n const resolveClientId = (provider: OAuthProvider): string | undefined => {\n const fromHost = context.oauthClientIds?.[provider];\n if (fromHost) return fromHost;\n return readViteEnv(OAUTH_CONFIGS[provider].clientIdEnvKey);\n };\n\n /**\n * Initiate OAuth flow by redirecting to the provider\n * @param provider - The OAuth provider to use\n */\n const initiateOAuth = (provider: OAuthProvider) => {\n const config = OAUTH_CONFIGS[provider];\n\n // Client ID comes from the host app shell (see resolveClientId).\n const clientId = resolveClientId(provider);\n\n if (!clientId) {\n console.error(`OAuth client ID not configured for ${provider}`);\n throw new Error(\n `${provider} OAuth is not configured. Please set ${config.clientIdEnvKey} environment variable.`,\n );\n }\n\n // Generate state for CSRF protection and store it\n // Use localStorage instead of sessionStorage for better persistence across redirects\n const state = generateState();\n localStorage.setItem(\"oauth_state\", state);\n localStorage.setItem(\"oauth_provider\", provider);\n\n // Native shells (Electron, Capacitor) can't use the current origin as the\n // redirect_uri — `vibecontrols://app` / `https://localhost` aren't valid\n // registered URIs at OAuth providers. Use a custom-scheme URL that the\n // host shell has registered as a deep-link target; the OS will route the\n // provider's callback back into the running app.\n const redirectUri = isNativeShell()\n ? NATIVE_OAUTH_REDIRECT_URI\n : `${window.location.origin}/auth/oauth/callback`;\n localStorage.setItem(\"oauth_redirect_uri\", redirectUri);\n\n // Build authorization URL\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: config.scopes.join(\" \"),\n state: state,\n // Google-specific: force account selection\n ...(provider === \"google\" && { prompt: \"select_account\" }),\n // Google-specific: enable offline access for refresh token\n ...(provider === \"google\" && { access_type: \"offline\" }),\n });\n\n const authUrl = `${config.authorizationUrl}?${params.toString()}`;\n\n // Hand off to the system browser in native shells (keeps the embedded\n // webview in place so the deep-link callback can return cleanly). On the\n // web this falls back to same-window navigation.\n void openOAuthAuthorizationUrl(authUrl);\n };\n\n /**\n * Complete OAuth flow by exchanging authorization code for tokens\n * Called from the callback page after provider redirects back\n * @param code - Authorization code from OAuth provider\n * @param state - State parameter for CSRF validation\n */\n const completeOAuth = async (\n code: string,\n state: string,\n ): Promise<AuthResult> => {\n const store = useAuthStore.getState();\n\n // Validate state parameter\n const storedState = localStorage.getItem(\"oauth_state\");\n if (!storedState || storedState !== state) {\n console.error(\"[OAuth Complete] State mismatch!\", {\n storedState,\n urlState: state,\n });\n throw new Error(\"Invalid OAuth state. Please try again.\");\n }\n\n // Get provider and redirect URI from localStorage\n const provider = localStorage.getItem(\"oauth_provider\") as OAuthProvider;\n const redirectUri = localStorage.getItem(\"oauth_redirect_uri\");\n\n if (!provider || !redirectUri) {\n console.error(\"[OAuth Complete] Missing provider or redirectUri\");\n throw new Error(\"OAuth session data missing. Please try again.\");\n }\n\n // Clear localStorage\n localStorage.removeItem(\"oauth_state\");\n localStorage.removeItem(\"oauth_provider\");\n localStorage.removeItem(\"oauth_redirect_uri\");\n\n try {\n store.setLoading(true);\n store.setError(null);\n\n // Call backend mutation to exchange code for tokens\n const { data, error } = await authenticateOAuthMutation({\n variables: {\n input: {\n provider: provider.toUpperCase(),\n code,\n redirectUri,\n },\n },\n });\n\n if (!data?.authenticateOAuth) {\n const errMsg =\n error?.message ?? \"OAuth authentication failed. Please try again.\";\n throw new Error(errMsg);\n }\n\n const result = data.authenticateOAuth;\n\n if (result.requiresMFA) {\n store.setMfaRequired(true, result.mfaChallengeId || undefined);\n return result;\n }\n\n if (result.success && result.user && result.token) {\n store.setUser(result.user);\n store.setAuthTokens(result.token, result.refreshToken || \"\");\n store.setAuthenticated(true);\n store.setMfaRequired(false);\n\n context.onAuthSuccess?.(\n result.user,\n result.token,\n result.refreshToken || undefined,\n );\n } else if (result.error) {\n store.setError(result.error);\n }\n\n return result;\n } catch (err) {\n const message =\n err instanceof Error ? err.message : \"OAuth authentication failed\";\n store.setError(message);\n throw err;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Convenience method to initiate Google OAuth\n */\n const initiateGoogleOAuth = () => initiateOAuth(\"google\");\n\n /**\n * Convenience method to initiate GitHub OAuth\n */\n const initiateGitHubOAuth = () => initiateOAuth(\"github\");\n\n /**\n * Convenience method to initiate Microsoft OAuth\n */\n const initiateMicrosoftOAuth = () => initiateOAuth(\"microsoft\");\n\n /**\n * Check if a provider is configured (has client ID)\n */\n const isProviderConfigured = (provider: OAuthProvider): boolean => {\n return !!resolveClientId(provider);\n };\n\n return {\n initiateOAuth,\n completeOAuth,\n initiateGoogleOAuth,\n initiateGitHubOAuth,\n initiateMicrosoftOAuth,\n isProviderConfigured,\n isLoading: loading,\n };\n}\n"],"mappings":"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"}
|