Improve terminal entitlement resilience
This commit is contained in:
@@ -11,6 +11,12 @@ import {
|
||||
getLocalDevAuthPayload,
|
||||
isLocalFullAccessHost,
|
||||
} from "@/lib/local-dev-access";
|
||||
import {
|
||||
applyEntitlementSnapshotCookie,
|
||||
clearEntitlementSnapshotCookie,
|
||||
entitlementSnapshotToAuthPayload,
|
||||
readEntitlementSnapshot,
|
||||
} from "@/lib/entitlement-snapshot";
|
||||
import { hasSupabaseServerEnv } from "@/lib/supabase/server";
|
||||
|
||||
const API_BASE = process.env.POLYWEATHER_API_BASE_URL;
|
||||
@@ -68,14 +74,28 @@ async function getVerifiedBearerIdentity(
|
||||
function degradedAuthProfileResponse({
|
||||
email,
|
||||
reason,
|
||||
req,
|
||||
response,
|
||||
userId,
|
||||
}: {
|
||||
email: string | null;
|
||||
reason: string;
|
||||
req: NextRequest;
|
||||
response: NextResponse | null;
|
||||
userId: string;
|
||||
}) {
|
||||
const snapshotPayload = entitlementSnapshotToAuthPayload(
|
||||
readEntitlementSnapshot(req, userId),
|
||||
);
|
||||
if (snapshotPayload) {
|
||||
const snapshotResponse = NextResponse.json({
|
||||
...snapshotPayload,
|
||||
email: snapshotPayload.email || email,
|
||||
entitlement_snapshot_reason: reason,
|
||||
});
|
||||
return applyAuthResponseCookies(snapshotResponse, response);
|
||||
}
|
||||
|
||||
const degraded = NextResponse.json({
|
||||
authenticated: true,
|
||||
user_id: userId,
|
||||
@@ -93,6 +113,44 @@ function degradedAuthProfileResponse({
|
||||
return applyAuthResponseCookies(degraded, response);
|
||||
}
|
||||
|
||||
function snapshotAuthProfileResponse({
|
||||
email,
|
||||
reason,
|
||||
req,
|
||||
response,
|
||||
userId,
|
||||
}: {
|
||||
email: string | null;
|
||||
reason: string;
|
||||
req: NextRequest;
|
||||
response: NextResponse | null;
|
||||
userId: string;
|
||||
}) {
|
||||
const snapshotPayload = entitlementSnapshotToAuthPayload(
|
||||
readEntitlementSnapshot(req, userId),
|
||||
);
|
||||
if (!snapshotPayload) return null;
|
||||
const snapshotResponse = NextResponse.json({
|
||||
...snapshotPayload,
|
||||
email: snapshotPayload.email || email,
|
||||
entitlement_snapshot_reason: reason,
|
||||
});
|
||||
return applyAuthResponseCookies(snapshotResponse, response);
|
||||
}
|
||||
|
||||
function applyEntitlementSnapshotFromAuthPayload(
|
||||
response: NextResponse,
|
||||
data: Record<string, unknown>,
|
||||
) {
|
||||
if (data.authenticated === true && data.subscription_active === true) {
|
||||
return applyEntitlementSnapshotCookie(response, data);
|
||||
}
|
||||
if (data.authenticated === false || data.subscription_active === false) {
|
||||
return clearEntitlementSnapshotCookie(response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const requestHost =
|
||||
req.headers.get("x-forwarded-host") || req.headers.get("host") || req.nextUrl.host;
|
||||
@@ -113,6 +171,13 @@ export async function GET(req: NextRequest) {
|
||||
}
|
||||
|
||||
let auth: Awaited<ReturnType<typeof buildBackendRequestHeaders>> | null = null;
|
||||
let bearerIdentity: VerifiedBearerIdentity | null | undefined;
|
||||
const preferSnapshot = req.nextUrl.searchParams.get("prefer_snapshot") === "1";
|
||||
const getBearerIdentityOnce = async () => {
|
||||
if (bearerIdentity !== undefined) return bearerIdentity;
|
||||
bearerIdentity = await getVerifiedBearerIdentity(req);
|
||||
return bearerIdentity;
|
||||
};
|
||||
try {
|
||||
auth = await buildBackendRequestHeaders(req);
|
||||
if (
|
||||
@@ -125,9 +190,27 @@ export async function GET(req: NextRequest) {
|
||||
subscription_active: false,
|
||||
points: 0,
|
||||
});
|
||||
if (!preferSnapshot) clearEntitlementSnapshotCookie(response);
|
||||
return applyAuthResponseCookies(response, auth.response);
|
||||
}
|
||||
|
||||
if (preferSnapshot) {
|
||||
const identity =
|
||||
auth.authUserId
|
||||
? { email: auth.authEmail || null, userId: auth.authUserId }
|
||||
: await getBearerIdentityOnce();
|
||||
if (identity?.userId) {
|
||||
const snapshotResponse = snapshotAuthProfileResponse({
|
||||
email: identity.email,
|
||||
reason: "prefer_snapshot",
|
||||
req,
|
||||
response: auth.response,
|
||||
userId: identity.userId,
|
||||
});
|
||||
if (snapshotResponse) return snapshotResponse;
|
||||
}
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 6000);
|
||||
let res: Response;
|
||||
@@ -144,18 +227,20 @@ export async function GET(req: NextRequest) {
|
||||
return degradedAuthProfileResponse({
|
||||
email: auth.authEmail || null,
|
||||
reason: `backend_${res.status}`,
|
||||
req,
|
||||
response: auth.response,
|
||||
userId: auth.authUserId,
|
||||
});
|
||||
}
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
const bearerIdentity = await getVerifiedBearerIdentity(req);
|
||||
if (bearerIdentity) {
|
||||
const identity = await getBearerIdentityOnce();
|
||||
if (identity) {
|
||||
return degradedAuthProfileResponse({
|
||||
email: bearerIdentity.email,
|
||||
email: identity.email,
|
||||
reason: `backend_${res.status}`,
|
||||
req,
|
||||
response: auth.response,
|
||||
userId: bearerIdentity.userId,
|
||||
userId: identity.userId,
|
||||
});
|
||||
}
|
||||
const response = NextResponse.json({
|
||||
@@ -163,6 +248,7 @@ export async function GET(req: NextRequest) {
|
||||
subscription_active: false,
|
||||
points: 0,
|
||||
});
|
||||
clearEntitlementSnapshotCookie(response);
|
||||
return applyAuthResponseCookies(response, auth.response);
|
||||
}
|
||||
if (!res.ok) {
|
||||
@@ -171,41 +257,59 @@ export async function GET(req: NextRequest) {
|
||||
return degradedAuthProfileResponse({
|
||||
email: auth.authEmail || null,
|
||||
reason: `backend_${res.status}`,
|
||||
req,
|
||||
response: auth.response,
|
||||
userId: auth.authUserId,
|
||||
});
|
||||
}
|
||||
const bearerIdentity = await getVerifiedBearerIdentity(req);
|
||||
if (bearerIdentity) {
|
||||
const identity = await getBearerIdentityOnce();
|
||||
if (identity) {
|
||||
return degradedAuthProfileResponse({
|
||||
email: bearerIdentity.email,
|
||||
email: identity.email,
|
||||
reason: `backend_${res.status}`,
|
||||
req,
|
||||
response: auth.response,
|
||||
userId: bearerIdentity.userId,
|
||||
userId: identity.userId,
|
||||
});
|
||||
}
|
||||
const response = buildUpstreamErrorResponse(res.status, raw);
|
||||
return applyAuthResponseCookies(response, auth.response);
|
||||
}
|
||||
const data = await res.json();
|
||||
if (data?.authenticated === true && data?.subscription_active == null) {
|
||||
const userId = String(data.user_id || auth.authUserId || "").trim();
|
||||
if (userId) {
|
||||
const snapshotResponse = snapshotAuthProfileResponse({
|
||||
email: String(data.email || auth.authEmail || "").trim() || null,
|
||||
reason: "subscription_unknown",
|
||||
req,
|
||||
response: auth.response,
|
||||
userId,
|
||||
});
|
||||
if (snapshotResponse) return snapshotResponse;
|
||||
}
|
||||
}
|
||||
const response = NextResponse.json(data);
|
||||
applyEntitlementSnapshotFromAuthPayload(response, data);
|
||||
return applyAuthResponseCookies(response, auth.response);
|
||||
} catch (error) {
|
||||
if (auth?.authUserId) {
|
||||
return degradedAuthProfileResponse({
|
||||
email: auth.authEmail || null,
|
||||
reason: String(error),
|
||||
req,
|
||||
response: auth.response,
|
||||
userId: auth.authUserId,
|
||||
});
|
||||
}
|
||||
const bearerIdentity = await getVerifiedBearerIdentity(req);
|
||||
if (bearerIdentity) {
|
||||
const identity = await getBearerIdentityOnce();
|
||||
if (identity) {
|
||||
return degradedAuthProfileResponse({
|
||||
email: bearerIdentity.email,
|
||||
email: identity.email,
|
||||
reason: String(error),
|
||||
req,
|
||||
response: auth?.response || null,
|
||||
userId: bearerIdentity.userId,
|
||||
userId: identity.userId,
|
||||
});
|
||||
}
|
||||
return buildProxyExceptionResponse(error, {
|
||||
|
||||
Reference in New Issue
Block a user