Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/api/src/auth/better-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ export function createAuth(env: Env, db?: ReturnType<typeof createDatabase>) {
}
: {};

// 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",
Expand Down
22 changes: 20 additions & 2 deletions packages/api/src/hono/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -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
Expand Down
20 changes: 12 additions & 8 deletions packages/app/src/lib/auth-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 29 additions & 4 deletions packages/app/src/lib/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,36 @@ const navigateAfterAuth = async (
router: ReturnType<typeof useRouter>
): Promise<void> => {
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";
}
Expand All @@ -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) {
Expand All @@ -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);
Expand Down
9 changes: 8 additions & 1 deletion packages/app/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,15 @@ function RootErrorComponent({ error }: { error: Error }) {
export const Route = createRootRouteWithContext<RouterContext>()({
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) {
Expand Down Expand Up @@ -132,7 +139,7 @@ export const Route = createRootRouteWithContext<RouterContext>()({

// 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,
Expand Down
Loading