mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-01 20:03:30 +02:00
commit
ae1c3f954e
2 changed files with 78 additions and 25 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
from contextlib import asynccontextmanager
|
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.middleware.cors import CORSMiddleware
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
|
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
|
||||||
|
|
@ -107,6 +107,8 @@ app.include_router(
|
||||||
)
|
)
|
||||||
|
|
||||||
if config.AUTH_TYPE == "GOOGLE":
|
if config.AUTH_TYPE == "GOOGLE":
|
||||||
|
from fastapi.responses import RedirectResponse
|
||||||
|
|
||||||
from app.users import google_oauth_client
|
from app.users import google_oauth_client
|
||||||
|
|
||||||
# Determine if we're in a secure context (HTTPS) or local development (HTTP)
|
# Determine if we're in a secure context (HTTPS) or local development (HTTP)
|
||||||
|
|
@ -119,6 +121,15 @@ if config.AUTH_TYPE == "GOOGLE":
|
||||||
# For same-origin or local development, use SameSite=Lax (default)
|
# For same-origin or local development, use SameSite=Lax (default)
|
||||||
csrf_cookie_samesite = "none" if is_secure_context else "lax"
|
csrf_cookie_samesite = "none" if is_secure_context else "lax"
|
||||||
|
|
||||||
|
# Extract the domain from BACKEND_URL for cookie domain setting
|
||||||
|
# This helps with cross-site cookie issues in Firefox/Safari
|
||||||
|
csrf_cookie_domain = None
|
||||||
|
if config.BACKEND_URL:
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
parsed_url = urlparse(config.BACKEND_URL)
|
||||||
|
csrf_cookie_domain = parsed_url.hostname
|
||||||
|
|
||||||
app.include_router(
|
app.include_router(
|
||||||
fastapi_users.get_oauth_router(
|
fastapi_users.get_oauth_router(
|
||||||
google_oauth_client,
|
google_oauth_client,
|
||||||
|
|
@ -127,6 +138,7 @@ if config.AUTH_TYPE == "GOOGLE":
|
||||||
is_verified_by_default=True,
|
is_verified_by_default=True,
|
||||||
csrf_token_cookie_secure=is_secure_context,
|
csrf_token_cookie_secure=is_secure_context,
|
||||||
csrf_token_cookie_samesite=csrf_cookie_samesite,
|
csrf_token_cookie_samesite=csrf_cookie_samesite,
|
||||||
|
csrf_token_cookie_httponly=False, # Required for cross-site OAuth in Firefox/Safari
|
||||||
)
|
)
|
||||||
if not config.BACKEND_URL
|
if not config.BACKEND_URL
|
||||||
else fastapi_users.get_oauth_router(
|
else fastapi_users.get_oauth_router(
|
||||||
|
|
@ -137,6 +149,8 @@ if config.AUTH_TYPE == "GOOGLE":
|
||||||
redirect_url=f"{config.BACKEND_URL}/auth/google/callback",
|
redirect_url=f"{config.BACKEND_URL}/auth/google/callback",
|
||||||
csrf_token_cookie_secure=is_secure_context,
|
csrf_token_cookie_secure=is_secure_context,
|
||||||
csrf_token_cookie_samesite=csrf_cookie_samesite,
|
csrf_token_cookie_samesite=csrf_cookie_samesite,
|
||||||
|
csrf_token_cookie_httponly=False, # Required for cross-site OAuth in Firefox/Safari
|
||||||
|
csrf_token_cookie_domain=csrf_cookie_domain, # Explicitly set cookie domain
|
||||||
),
|
),
|
||||||
prefix="/auth/google",
|
prefix="/auth/google",
|
||||||
tags=["auth"],
|
tags=["auth"],
|
||||||
|
|
@ -145,6 +159,62 @@ if config.AUTH_TYPE == "GOOGLE":
|
||||||
], # blocks OAuth registration when disabled
|
], # blocks OAuth registration when disabled
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add a redirect-based authorize endpoint for Firefox/Safari compatibility
|
||||||
|
# This endpoint performs a server-side redirect instead of returning JSON
|
||||||
|
# which fixes cross-site cookie issues where browsers don't send cookies
|
||||||
|
# set via cross-origin fetch requests on subsequent redirects
|
||||||
|
@app.get("/auth/google/authorize-redirect", tags=["auth"])
|
||||||
|
async def google_authorize_redirect(
|
||||||
|
request: Request,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Redirect-based OAuth authorization endpoint.
|
||||||
|
|
||||||
|
Unlike the standard /auth/google/authorize endpoint that returns JSON,
|
||||||
|
this endpoint directly redirects the browser to Google's OAuth page.
|
||||||
|
This fixes CSRF cookie issues in Firefox and Safari where cookies set
|
||||||
|
via cross-origin fetch requests are not sent on subsequent redirects.
|
||||||
|
"""
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
from fastapi_users.router.oauth import generate_state_token
|
||||||
|
|
||||||
|
# Generate CSRF token
|
||||||
|
csrf_token = secrets.token_urlsafe(32)
|
||||||
|
|
||||||
|
# Build state token
|
||||||
|
state_data = {"csrftoken": csrf_token}
|
||||||
|
state = generate_state_token(state_data, SECRET, lifetime_seconds=3600)
|
||||||
|
|
||||||
|
# Get the callback URL
|
||||||
|
if config.BACKEND_URL:
|
||||||
|
redirect_url = f"{config.BACKEND_URL}/auth/google/callback"
|
||||||
|
else:
|
||||||
|
redirect_url = str(request.url_for("oauth:google.jwt.callback"))
|
||||||
|
|
||||||
|
# Get authorization URL from Google
|
||||||
|
authorization_url = await google_oauth_client.get_authorization_url(
|
||||||
|
redirect_url,
|
||||||
|
state,
|
||||||
|
scope=["openid", "email", "profile"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create redirect response and set CSRF cookie
|
||||||
|
response = RedirectResponse(url=authorization_url, status_code=302)
|
||||||
|
response.set_cookie(
|
||||||
|
key="fastapiusersoauthcsrf",
|
||||||
|
value=csrf_token,
|
||||||
|
max_age=3600,
|
||||||
|
path="/",
|
||||||
|
domain=csrf_cookie_domain,
|
||||||
|
secure=is_secure_context,
|
||||||
|
httponly=False, # Required for cross-site OAuth in Firefox/Safari
|
||||||
|
samesite=csrf_cookie_samesite,
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
app.include_router(crud_router, prefix="/api/v1", tags=["crud"])
|
app.include_router(crud_router, prefix="/api/v1", tags=["crud"])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { IconBrandGoogleFilled } from "@tabler/icons-react";
|
||||||
import { motion } from "motion/react";
|
import { motion } from "motion/react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { Logo } from "@/components/Logo";
|
import { Logo } from "@/components/Logo";
|
||||||
import { trackLoginAttempt, trackLoginFailure } from "@/lib/posthog/events";
|
import { trackLoginAttempt } from "@/lib/posthog/events";
|
||||||
import { AmbientBackground } from "./AmbientBackground";
|
import { AmbientBackground } from "./AmbientBackground";
|
||||||
|
|
||||||
export function GoogleLoginButton() {
|
export function GoogleLoginButton() {
|
||||||
|
|
@ -13,29 +13,12 @@ export function GoogleLoginButton() {
|
||||||
// Track Google login attempt
|
// Track Google login attempt
|
||||||
trackLoginAttempt("google");
|
trackLoginAttempt("google");
|
||||||
|
|
||||||
// Redirect to Google OAuth authorization URL
|
// IMPORTANT: Use the redirect-based authorize endpoint for cross-origin OAuth
|
||||||
// credentials: 'include' is required to accept the CSRF cookie from cross-origin response
|
// This fixes CSRF cookie issues in Firefox/Safari where cookies set via
|
||||||
fetch(`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/auth/google/authorize`, {
|
// cross-origin fetch requests may not be sent on subsequent redirects.
|
||||||
credentials: "include",
|
// The authorize-redirect endpoint does a server-side redirect to Google
|
||||||
})
|
// and sets the CSRF cookie properly for same-site context.
|
||||||
.then((response) => {
|
window.location.href = `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/auth/google/authorize-redirect`;
|
||||||
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 {
|
|
||||||
trackLoginFailure("google", "No authorization URL received");
|
|
||||||
console.error("No authorization URL received");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
trackLoginFailure("google", error?.message || "Unknown error");
|
|
||||||
console.error("Error during Google login:", error);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full overflow-hidden">
|
<div className="relative w-full overflow-hidden">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue