feat: Implement Supabase authentication, account management UI, and entitlement services.

This commit is contained in:
2569718930@qq.com
2026-03-13 02:23:01 +08:00
parent 987aec2fa6
commit 0a869459c4
34 changed files with 2318 additions and 68 deletions
+60 -2
View File
@@ -1,6 +1,14 @@
import { NextRequest, NextResponse } from "next/server";
import {
createSupabaseMiddlewareClient,
hasSupabaseServerEnv,
} from "@/lib/supabase/server";
const SESSION_COOKIE = "polyweather_entitlement";
const SUPABASE_AUTH_ENABLED =
String(process.env.POLYWEATHER_AUTH_ENABLED || "")
.trim()
.toLowerCase() === "true";
function isStaticAsset(pathname: string) {
return (
@@ -19,10 +27,14 @@ function isStaticAsset(pathname: string) {
}
function isPublicPage(pathname: string) {
return pathname === "/entitlement-required";
return (
pathname === "/entitlement-required" ||
pathname.startsWith("/auth/login") ||
pathname.startsWith("/auth/callback")
);
}
export function middleware(request: NextRequest) {
function handleLegacyTokenGate(request: NextRequest) {
const requiredToken = process.env.POLYWEATHER_DASHBOARD_ACCESS_TOKEN?.trim();
if (!requiredToken) {
return NextResponse.next();
@@ -68,6 +80,52 @@ export function middleware(request: NextRequest) {
return NextResponse.redirect(deniedUrl);
}
async function handleSupabaseAuthGate(request: NextRequest) {
const { pathname } = request.nextUrl;
if (isPublicPage(pathname)) {
return NextResponse.next();
}
const response = NextResponse.next({
request: {
headers: request.headers,
},
});
const supabase = createSupabaseMiddlewareClient(request, response);
const {
data: { user },
} = await supabase.auth.getUser();
if (user) {
return response;
}
if (pathname.startsWith("/api/")) {
return NextResponse.json(
{ error: "Unauthorized", detail: "Supabase session required" },
{ status: 401 },
);
}
const loginUrl = request.nextUrl.clone();
loginUrl.pathname = "/auth/login";
loginUrl.search = "";
loginUrl.searchParams.set("next", pathname);
return NextResponse.redirect(loginUrl);
}
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (isStaticAsset(pathname)) {
return NextResponse.next();
}
if (SUPABASE_AUTH_ENABLED && hasSupabaseServerEnv()) {
return handleSupabaseAuthGate(request);
}
return handleLegacyTokenGate(request);
}
export const config = {
matcher: ["/((?!_next/static|_next/image).*)"],
};