diff --git a/packages/api/src/auth/better-auth.ts b/packages/api/src/auth/better-auth.ts index 2849a1b..ff19028 100644 --- a/packages/api/src/auth/better-auth.ts +++ b/packages/api/src/auth/better-auth.ts @@ -86,6 +86,16 @@ export function createAuth(env: Env, db?: ReturnType) { } : {}; + // Log Better Auth configuration (for debugging cross-domain issues) + console.log("🔧 Better Auth Configuration:", { + apiUrl, + frontendUrl, + trustedOrigins, + hasCookieDomain: !!env.COOKIE_DOMAIN, + cookieDomain: env.COOKIE_DOMAIN || "(not set)", + crossSubDomainEnabled: !!env.COOKIE_DOMAIN, + }); + return betterAuth({ database: drizzleAdapter(database, { provider: "sqlite", diff --git a/packages/api/src/hono/app.ts b/packages/api/src/hono/app.ts index dcbec41..0228ccd 100644 --- a/packages/api/src/hono/app.ts +++ b/packages/api/src/hono/app.ts @@ -39,10 +39,15 @@ export function createHonoApp(config: HonoAppConfig) { }); // CORS middleware (must be before routes) + const corsOrigins = getCorsOrigins(config.env); + console.log("🔧 CORS Configuration:", { + origins: corsOrigins, + credentials: true, + }); app.use( "*", cors({ - origin: getCorsOrigins(config.env), + origin: corsOrigins, credentials: true, allowMethods: ["GET", "POST", "OPTIONS"], allowHeaders: [ @@ -155,9 +160,22 @@ export function createHonoApp(config: HonoAppConfig) { // BetterAuth routes app.on(["POST", "GET"], "/api/auth/*", async (c) => { + const path = c.req.path; + const origin = c.req.header("origin"); + console.log(`[Auth] 📥 ${c.req.method} ${path} from origin: ${origin}`); + const { createAuth } = await import("../auth/better-auth"); const auth = createAuth(c.get("env")); - return auth.handler(c.req.raw); + const response = await auth.handler(c.req.raw); + + // Log response headers (especially Set-Cookie) + const setCookieHeader = response.headers.get("set-cookie"); + if (setCookieHeader) { + console.log(`[Auth] 🍪 Setting cookies:`, setCookieHeader); + } + + console.log(`[Auth] 📤 Response status: ${response.status}`); + return response; }); // tRPC routes using @hono/trpc-server middleware diff --git a/packages/app/src/lib/auth-client.ts b/packages/app/src/lib/auth-client.ts index b702ef2..41bb817 100644 --- a/packages/app/src/lib/auth-client.ts +++ b/packages/app/src/lib/auth-client.ts @@ -16,17 +16,21 @@ const baseURL = viteApiUrl ? new URL(viteApiUrl).origin : "http://localhost:3001"; // Default to API server in development -// Debug logging in development -if (import.meta.env.DEV) { - console.log("🔧 Better Auth Client Configuration:", { - VITE_API_URL: viteApiUrl, - baseURL, - mode: import.meta.env.MODE, - }); -} +// Debug logging (always enabled for debugging cross-domain issues) +console.log("🔧 Better Auth Client Configuration:", { + VITE_API_URL: viteApiUrl, + baseURL, + mode: import.meta.env.MODE, + environment: import.meta.env.VITE_SENTRY_ENVIRONMENT || "development", +}); export const authClient = createAuthClient({ baseURL, + // CRITICAL: Include credentials (cookies) in cross-origin requests + // Required when API and frontend are on different domains (e.g., staging.tuvix.app vs tuvix-api-staging.cf-93e.workers.dev) + fetchOptions: { + credentials: "include", + }, plugins: [ // Custom session plugin for type inference // This ensures TypeScript knows about the banned field we added via customSession diff --git a/packages/app/src/lib/hooks/useAuth.ts b/packages/app/src/lib/hooks/useAuth.ts index 554b5eb..226d84f 100644 --- a/packages/app/src/lib/hooks/useAuth.ts +++ b/packages/app/src/lib/hooks/useAuth.ts @@ -26,20 +26,36 @@ const navigateAfterAuth = async ( router: ReturnType ): Promise => { try { + console.log("[Auth] 🔄 Starting post-login navigation..."); + // Get fresh session from Better Auth - await authClient.getSession(); + console.log("[Auth] 📡 Fetching fresh session from API..."); + const sessionResult = await authClient.getSession(); + console.log("[Auth] ✅ Session fetch result:", { + hasData: !!sessionResult?.data, + hasUser: !!sessionResult?.data?.user, + userId: sessionResult?.data?.user?.id, + hasSession: !!sessionResult?.data?.session, + }); + + // Log cookies for debugging + console.log("[Auth] 🍪 Current cookies:", document.cookie); // Invalidate router to force root beforeLoad to re-run with fresh session + console.log("[Auth] 🔄 Invalidating router..."); await router.invalidate({ sync: true }); // Navigate to articles page // Email verification can be checked on protected routes + console.log("[Auth] 🚀 Navigating to /app/articles..."); await router.navigate({ to: "/app/articles", search: { category_id: undefined, subscription_id: undefined }, }); + console.log("[Auth] ✅ Navigation completed successfully"); } catch (error) { - console.error("Navigation failed:", error); + console.error("[Auth] ❌ Navigation failed:", error); + console.log("[Auth] 🔄 Falling back to hard navigation..."); // Fallback to hard navigation window.location.href = "/app/articles"; } @@ -55,8 +71,10 @@ export const useLogin = () => { const mutate = async (values: { username: string; password: string }) => { setIsPending(true); try { + console.log("[Auth] 🔐 Starting login..."); // Detect if input is email or username const isEmail = values.username.includes("@"); + console.log("[Auth] 📧 Login type:", isEmail ? "email" : "username"); let result; if (isEmail) { @@ -71,17 +89,24 @@ export const useLogin = () => { }); } + console.log("[Auth] 📥 Login result:", { + hasError: !!result.error, + hasData: !!result.data, + dataKeys: result.data ? Object.keys(result.data) : [], + }); + if (result.error) { - console.error("Login error:", result.error); + console.error("[Auth] ❌ Login error:", result.error); toast.error(result.error.message || "Invalid credentials"); return; } + console.log("[Auth] ✅ Login successful!"); toast.success("Welcome back!"); await queryClient.invalidateQueries(); await navigateAfterAuth(router); } catch (error) { - console.error("Login error:", error); + console.error("[Auth] ❌ Login exception:", error); toast.error((error as Error).message || "Invalid credentials"); } finally { setIsPending(false); diff --git a/packages/app/src/routes/__root.tsx b/packages/app/src/routes/__root.tsx index 881d0a7..8924d89 100644 --- a/packages/app/src/routes/__root.tsx +++ b/packages/app/src/routes/__root.tsx @@ -99,8 +99,15 @@ function RootErrorComponent({ error }: { error: Error }) { export const Route = createRootRouteWithContext()({ beforeLoad: async () => { // Fetch session once at root level + console.log("[Root] 🔄 beforeLoad: Fetching session..."); try { const sessionResult = await authClient.getSession(); + console.log("[Root] ✅ Session result:", { + hasData: !!sessionResult?.data, + hasUser: !!sessionResult?.data?.user, + userId: sessionResult?.data?.user?.id, + hasSession: !!sessionResult?.data?.session, + }); // Set Sentry user context for Session Replay identification (non-PII) if (sessionResult?.data?.user?.id) { @@ -132,7 +139,7 @@ export const Route = createRootRouteWithContext()({ // Fail open - allow navigation without session // The individual route guards will handle redirects - console.warn("Failed to fetch session at root level:", error); + console.warn("[Root] ❌ Failed to fetch session at root level:", error); return { auth: { session: null,