Files
PolyWeather/frontend/lib/backend-auth.ts
T
2569718930@qq.com 20c8395c0b 全局配置更新:OAuth 回调修复、支付安全加固、站点 URL 工具
- 新增 NEXT_PUBLIC_SITE_URL 支持及 site-url.ts 工具模块
- 修复 OAuth 回调域名:import.meta.env 统一读取站点 URL
- 支付 API 路由新增收款地址校验
- 后端支付服务更新
- middleware 清理
- 新增 paymentSecurity 测试
2026-05-24 18:33:47 +08:00

107 lines
3.5 KiB
TypeScript

import type { NextRequest } from "next/server";
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;
response: NextResponse | null;
authUserId?: string | null;
authEmail?: string | null;
};
type HeaderBuildOptions = {
includeSupabaseIdentity?: boolean;
};
function extractBearerToken(headerValue: string | null) {
if (!headerValue) return "";
const parts = headerValue.trim().split(/\s+/);
if (parts.length === 2 && parts[0].toLowerCase() === "bearer") {
return parts[1];
}
return "";
}
export async function buildBackendRequestHeaders(
request: NextRequest,
options?: HeaderBuildOptions,
): Promise<HeaderBuildResult> {
const headers = new Headers({
Accept: "application/json",
});
const backendToken = process.env.POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN?.trim();
if (backendToken) {
headers.set(BACKEND_ENTITLEMENT_HEADER, backendToken);
}
const incomingAuth = extractBearerToken(request.headers.get("authorization"));
const includeSupabaseIdentity = options?.includeSupabaseIdentity !== false;
if (hasSupabaseServerEnv() && includeSupabaseIdentity) {
const supabase = createSupabaseRouteClient(request, new NextResponse(null, { status: 200 }));
const {
data: { session },
} = await supabase.auth.getSession();
let user = session?.user ?? null;
if (session) {
const {
data: { user: validated },
} = await supabase.auth.getUser();
user = validated ?? session.user;
}
const passthroughResponse = new NextResponse(null, { status: 200 });
const forwardedUserId = String(user?.id || "").trim();
const forwardedEmail = String(user?.email || "").trim();
if (forwardedUserId) {
headers.set(FORWARDED_SUPABASE_USER_ID_HEADER, forwardedUserId);
}
if (forwardedEmail) {
headers.set(FORWARDED_SUPABASE_EMAIL_HEADER, forwardedEmail);
}
if (incomingAuth) {
headers.set("Authorization", `Bearer ${incomingAuth}`);
return { headers, response: passthroughResponse, authUserId: forwardedUserId || null, authEmail: forwardedEmail || null };
}
const accessToken = session?.access_token || "";
if (accessToken) {
// Fallback to cookie-backed session when request does not carry bearer.
headers.set("Authorization", `Bearer ${accessToken}`);
}
return { headers, response: passthroughResponse, authUserId: forwardedUserId || null, authEmail: forwardedEmail || null };
}
if (incomingAuth) {
headers.set("Authorization", `Bearer ${incomingAuth}`);
}
return { headers, response: null, authUserId: null, authEmail: null };
}
export function applyAuthResponseCookies(
target: NextResponse,
source: NextResponse | null,
) {
if (!source) return target;
for (const [name, value] of source.headers.entries()) {
if (name.toLowerCase() === "set-cookie") {
target.headers.append(name, value);
}
}
return target;
}
export function requireBackendAuthUser(auth: HeaderBuildResult) {
if (auth.authUserId) return null;
return applyAuthResponseCookies(
NextResponse.json(
{ error: "Authentication required", detail: "Supabase user required" },
{ status: 401 },
),
auth.response,
);
}