2025-11-23 10:55:04 +08:00
|
|
|
|
import { createServerComponentClient, createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
|
|
|
|
|
|
import { getDecodedCookies } from "@/lib/server-cookies";
|
2026-01-15 20:54:21 +08:00
|
|
|
|
import { getMnoteRuntimeConfig } from "@/lib/runtime-config";
|
|
|
|
|
|
|
|
|
|
|
|
const getAuthStorageKey = (supabaseUrl: string) => {
|
|
|
|
|
|
// 说明:supabase-js 默认用 “项目 ref(hostname 第一个片段)” 作为 storageKey,
|
|
|
|
|
|
// 同时 auth-helpers 会把该 storageKey 作为 cookie 名(sb-<ref>-auth-token)。
|
|
|
|
|
|
// 我们服务端为了绕过 FRP 自签证书,会把 supabaseUrl 指向内网/本机(例如 127.0.0.1),
|
|
|
|
|
|
// 但 cookie 名必须仍然按“公网 supabaseUrl”计算,否则会读不到浏览器写入的 cookie。
|
|
|
|
|
|
const hostname = new URL(supabaseUrl).hostname;
|
|
|
|
|
|
const ref = hostname.split(".")[0] || hostname;
|
|
|
|
|
|
return `sb-${ref}-auth-token`;
|
|
|
|
|
|
};
|
2025-11-23 10:55:04 +08:00
|
|
|
|
|
|
|
|
|
|
export const createSupabaseServerClient = async () => {
|
|
|
|
|
|
const cookieStore = await getDecodedCookies();
|
2026-01-15 20:54:21 +08:00
|
|
|
|
const cfg = getMnoteRuntimeConfig();
|
|
|
|
|
|
const publicSupabaseUrl = cfg.supabaseUrl ?? process.env.NEXT_PUBLIC_SUPABASE_URL ?? "";
|
|
|
|
|
|
const internalSupabaseUrl =
|
|
|
|
|
|
cfg.supabaseInternalUrl || process.env.SUPABASE_INTERNAL_URL || publicSupabaseUrl;
|
|
|
|
|
|
const supabaseAnonKey =
|
|
|
|
|
|
cfg.supabaseAnonKey ?? process.env.SUPABASE_ANON_KEY ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
2026-01-10 23:08:56 +08:00
|
|
|
|
|
2026-01-15 20:54:21 +08:00
|
|
|
|
if (!publicSupabaseUrl || !internalSupabaseUrl || !supabaseAnonKey) {
|
2026-01-10 23:08:56 +08:00
|
|
|
|
throw new Error(
|
|
|
|
|
|
"缺少 Supabase 环境变量,请配置 NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY(可选:SUPABASE_INTERNAL_URL / SUPABASE_ANON_KEY 作为服务端内网地址)",
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-01-15 20:54:21 +08:00
|
|
|
|
|
|
|
|
|
|
// 关键:服务端请求尽量走内网/本机(HTTP),避免 FRP Auto HTTPS 的证书导致 Node 侧校验失败。
|
|
|
|
|
|
// 但 storageKey/cookie 名称必须与浏览器端一致(使用 publicSupabaseUrl 计算),否则会读不到会话。
|
|
|
|
|
|
const storageKey = getAuthStorageKey(publicSupabaseUrl);
|
|
|
|
|
|
// 注意:@supabase/auth-helpers-nextjs 的 createServerComponentClient 签名是 (context, options)。
|
|
|
|
|
|
// 如果把 supabaseUrl/supabaseKey 放进第一个参数,会被当成 context 字段而忽略,导致仍使用默认
|
|
|
|
|
|
// NEXT_PUBLIC_SUPABASE_URL(HTTPS),从而触发 Node 端自签证书报错。
|
|
|
|
|
|
return createServerComponentClient(
|
|
|
|
|
|
{ cookies: () => cookieStore as any },
|
|
|
|
|
|
{
|
|
|
|
|
|
supabaseUrl: internalSupabaseUrl,
|
|
|
|
|
|
supabaseKey: supabaseAnonKey,
|
|
|
|
|
|
// 关键:显式覆盖 storageKey,让 supabase-js 读取 sb-<公网 ref>-auth-token,
|
|
|
|
|
|
// 而不是 sb-<127>-auth-token。
|
|
|
|
|
|
options: { auth: { storageKey } } as any,
|
|
|
|
|
|
} as any,
|
|
|
|
|
|
);
|
2025-11-23 10:55:04 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const createSupabaseRouteClient = async () => {
|
|
|
|
|
|
const cookieStore = await getDecodedCookies();
|
2026-01-15 20:54:21 +08:00
|
|
|
|
const cfg = getMnoteRuntimeConfig();
|
|
|
|
|
|
const publicSupabaseUrl = cfg.supabaseUrl ?? process.env.NEXT_PUBLIC_SUPABASE_URL ?? "";
|
|
|
|
|
|
const internalSupabaseUrl =
|
|
|
|
|
|
cfg.supabaseInternalUrl || process.env.SUPABASE_INTERNAL_URL || publicSupabaseUrl;
|
|
|
|
|
|
const supabaseAnonKey =
|
|
|
|
|
|
cfg.supabaseAnonKey ?? process.env.SUPABASE_ANON_KEY ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
2026-01-10 23:08:56 +08:00
|
|
|
|
|
2026-01-15 20:54:21 +08:00
|
|
|
|
if (!publicSupabaseUrl || !internalSupabaseUrl || !supabaseAnonKey) {
|
2026-01-10 23:08:56 +08:00
|
|
|
|
throw new Error(
|
|
|
|
|
|
"缺少 Supabase 环境变量,请配置 NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY(可选:SUPABASE_INTERNAL_URL / SUPABASE_ANON_KEY 作为服务端内网地址)",
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-01-15 20:54:21 +08:00
|
|
|
|
const storageKey = getAuthStorageKey(publicSupabaseUrl);
|
|
|
|
|
|
return createRouteHandlerClient(
|
|
|
|
|
|
{ cookies: () => cookieStore as any },
|
|
|
|
|
|
{
|
|
|
|
|
|
supabaseUrl: internalSupabaseUrl,
|
|
|
|
|
|
supabaseKey: supabaseAnonKey,
|
|
|
|
|
|
options: { auth: { storageKey } } as any,
|
|
|
|
|
|
} as any,
|
|
|
|
|
|
);
|
2025-11-23 10:55:04 +08:00
|
|
|
|
};
|