From 5b7a257087c629d0b415b1828a51175874eee85d Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Fri, 13 Mar 2026 11:01:44 +0800 Subject: [PATCH] feat: Implement Supabase-backed authentication and entitlement checks for frontend-to-backend requests. --- frontend/lib/backend-auth.ts | 14 ++++++++++++++ web/app.py | 26 ++++++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/frontend/lib/backend-auth.ts b/frontend/lib/backend-auth.ts index bcacdd88..28e086a6 100644 --- a/frontend/lib/backend-auth.ts +++ b/frontend/lib/backend-auth.ts @@ -3,6 +3,8 @@ import { NextResponse } from "next/server"; import { createSupabaseRouteClient, hasSupabaseServerEnv } from "@/lib/supabase/server"; export const BACKEND_ENTITLEMENT_HEADER = "x-polyweather-entitlement"; +export const FORWARDED_SUPABASE_USER_ID_HEADER = "x-polyweather-auth-user-id"; +export const FORWARDED_SUPABASE_EMAIL_HEADER = "x-polyweather-auth-email"; type HeaderBuildResult = { headers: HeadersInit; @@ -36,6 +38,18 @@ export async function buildBackendRequestHeaders( const { data: { session }, } = await supabase.auth.getSession(); + const { + data: { user }, + } = await supabase.auth.getUser(); + + const forwardedUserId = String(user?.id || session?.user?.id || "").trim(); + const forwardedEmail = String(user?.email || session?.user?.email || "").trim(); + if (forwardedUserId) { + headers.set(FORWARDED_SUPABASE_USER_ID_HEADER, forwardedUserId); + } + if (forwardedEmail) { + headers.set(FORWARDED_SUPABASE_EMAIL_HEADER, forwardedEmail); + } const accessToken = session?.access_token || ""; if (accessToken) { diff --git a/web/app.py b/web/app.py index f61d2004..e12b9c14 100644 --- a/web/app.py +++ b/web/app.py @@ -95,6 +95,8 @@ def _env_bool(name: str, default: bool = False) -> bool: _ENTITLEMENT_GUARD_ENABLED = _env_bool("POLYWEATHER_REQUIRE_ENTITLEMENT", False) _ENTITLEMENT_HEADER = "x-polyweather-entitlement" _ENTITLEMENT_TOKEN = (os.getenv("POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN") or "").strip() +_FORWARDED_SUPABASE_USER_ID_HEADER = "x-polyweather-auth-user-id" +_FORWARDED_SUPABASE_EMAIL_HEADER = "x-polyweather-auth-email" _SUPABASE_AUTH_REQUIRED = _env_bool( "POLYWEATHER_AUTH_REQUIRED", SUPABASE_ENTITLEMENT.enabled, @@ -211,12 +213,24 @@ def _require_supabase_identity(request: Request) -> Dict[str, str]: detail="payment requires SUPABASE_URL and SUPABASE_ANON_KEY", ) token = extract_bearer_token(request.headers.get("authorization")) - if not token: - raise HTTPException(status_code=401, detail="Unauthorized") - identity = SUPABASE_ENTITLEMENT.get_identity(token) - if not identity: - raise HTTPException(status_code=401, detail="Unauthorized") - return {"user_id": identity.user_id, "email": identity.email} + if token: + identity = SUPABASE_ENTITLEMENT.get_identity(token) + if identity: + return {"user_id": identity.user_id, "email": identity.email} + + # Trusted fallback for server-to-server proxy calls: + # only when entitlement token is valid do we accept forwarded identity headers. + if _legacy_service_token_valid(request): + forwarded_user_id = str( + request.headers.get(_FORWARDED_SUPABASE_USER_ID_HEADER) or "" + ).strip() + if forwarded_user_id: + forwarded_email = str( + request.headers.get(_FORWARDED_SUPABASE_EMAIL_HEADER) or "" + ).strip() + return {"user_id": forwarded_user_id, "email": forwarded_email} + + raise HTTPException(status_code=401, detail="Unauthorized") class WalletChallengeRequest(BaseModel):