From 10a218087324a65c34c72c7b96c00dcf00188524 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Fri, 19 Dec 2025 12:49:40 -0800 Subject: [PATCH] hotfix(try): OAUTH_INVALID_STATE --- surfsense_backend/app/app.py | 25 ++++++++++++++- .../app/(home)/login/GoogleLoginButton.tsx | 32 ++++++++----------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/surfsense_backend/app/app.py b/surfsense_backend/app/app.py index 8d525d659..3285b846d 100644 --- a/surfsense_backend/app/app.py +++ b/surfsense_backend/app/app.py @@ -1,7 +1,8 @@ from contextlib import asynccontextmanager -from fastapi import Depends, FastAPI, HTTPException, status +from fastapi import Depends, FastAPI, HTTPException, Request, status from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import RedirectResponse from sqlalchemy.ext.asyncio import AsyncSession from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware @@ -89,6 +90,28 @@ if config.AUTH_TYPE == "GOOGLE": ], # blocks OAuth registration when disabled ) + # Convenience endpoint for browser-based login: + # - FastAPI Users `/auth/google/authorize` returns JSON {authorization_url: ...} + # - In production, it's more reliable to do a top-level navigation that + # immediately redirects to Google (avoids cross-site XHR/CORS pitfalls). + from fastapi import Query + from fastapi_users.router.oauth import generate_state_token + + @app.get("/auth/google/start", include_in_schema=False) + async def google_oauth_start( + request: Request, scopes: list[str] = Query(None) + ) -> RedirectResponse: + base = (config.BACKEND_URL or str(request.base_url)).rstrip("/") + callback_url = f"{base}/auth/google/callback" + + state = generate_state_token({}, SECRET) + authorization_url = await google_oauth_client.get_authorization_url( + callback_url, + state, + scopes, + ) + return RedirectResponse(authorization_url, status_code=302) + app.include_router(crud_router, prefix="/api/v1", tags=["crud"]) diff --git a/surfsense_web/app/(home)/login/GoogleLoginButton.tsx b/surfsense_web/app/(home)/login/GoogleLoginButton.tsx index 52d79d72b..012906b37 100644 --- a/surfsense_web/app/(home)/login/GoogleLoginButton.tsx +++ b/surfsense_web/app/(home)/login/GoogleLoginButton.tsx @@ -9,24 +9,20 @@ export function GoogleLoginButton() { const t = useTranslations("auth"); const handleGoogleLogin = () => { - // Redirect to Google OAuth authorization URL - fetch(`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/auth/google/authorize`) - .then((response) => { - if (!response.ok) { - throw new Error("Failed to get authorization URL"); - } - return response.json(); - }) - .then((data) => { - if (data.authorization_url) { - window.location.href = data.authorization_url; - } else { - console.error("No authorization URL received"); - } - }) - .catch((error) => { - console.error("Error during Google login:", error); - }); + // IMPORTANT: + // FastAPI Users OAuth stores the "state" in a cookie. + // Doing a cross-origin fetch() (www.surfsense.com -> backend.ssbacktemp.xyz) + // will NOT persist Set-Cookie unless you use credentials + non-wildcard CORS. + // The simplest/most reliable approach is a top-level navigation to the backend + // authorize endpoint so the cookie is set as first-party. + const backendUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL; + if (!backendUrl) { + console.error("Missing NEXT_PUBLIC_FASTAPI_BACKEND_URL"); + return; + } + + // This endpoint performs a 302 to Google (more reliable than fetching JSON). + window.location.href = `${backendUrl}/auth/google/start`; }; return (