web/lib/server-fetch.tsblame
View source
bc1b2ba1// SSR-side fetch helpers that forward the user's auth token to the API.
bc1b2ba2//
bc1b2ba3// Auth lives in `grove_hub_token` (issued by hub-api, JWT verified by both
bc1b2ba4// hub-api and the grove api with the shared JWT_SECRET). On the client it
bc1b2ba5// also lives in localStorage but Next.js server components can only see
bc1b2ba6// the cookie. Without forwarding it, every SSR fetch is anonymous, which
bc1b2ba7// makes private repos look like 404s to their actual owners.
bc1b2ba8
bc1b2ba9import { cookies } from "next/headers";
bc1b2ba10import { groveApiUrl } from "./utils";
bc1b2ba11
bc1b2ba12const TOKEN_COOKIE = "grove_hub_token";
bc1b2ba13
bc1b2ba14/** Read the user's bearer token from cookies. Returns null if missing. */
bc1b2ba15export async function readAuthToken(): Promise<string | null> {
bc1b2ba16 const store = await cookies();
bc1b2ba17 return store.get(TOKEN_COOKIE)?.value ?? null;
bc1b2ba18}
bc1b2ba19
bc1b2ba20/** Build auth headers for a server-side fetch. Empty if no token. */
bc1b2ba21export async function authHeaders(): Promise<Record<string, string>> {
bc1b2ba22 const token = await readAuthToken();
bc1b2ba23 return token ? { authorization: `Bearer ${token}` } : {};
bc1b2ba24}
bc1b2ba25
bc1b2ba26/** Fetch a Grove API path forwarding the user's auth token. Defaults to
bc1b2ba27 * no-store so per-user data isn't cached across requests. */
bc1b2ba28export async function serverFetch(
bc1b2ba29 path: string,
bc1b2ba30 init: RequestInit = {},
bc1b2ba31): Promise<Response> {
bc1b2ba32 const headers = {
bc1b2ba33 ...(init.headers ?? {}),
bc1b2ba34 ...(await authHeaders()),
bc1b2ba35 };
bc1b2ba36 return fetch(`${groveApiUrl}${path}`, {
bc1b2ba37 cache: "no-store",
bc1b2ba38 ...init,
bc1b2ba39 headers,
bc1b2ba40 });
bc1b2ba41}