import { headers } from "next/headers"; import { redirect } from "next/navigation"; export type ChatGPTUser = { displayName: string; email: string; fullName: string | null; }; const USER_EMAIL_HEADER = "oai-authenticated-user-email"; const USER_FULL_NAME_HEADER = "oai-authenticated-user-full-name"; const USER_FULL_NAME_ENCODING_HEADER = "oai-authenticated-user-full-name-encoding"; const PERCENT_ENCODED_UTF8 = "percent-encoded-utf-8"; const SIGN_IN_PATH = "/signin-with-chatgpt"; const SIGN_OUT_PATH = "/signout-with-chatgpt"; const CALLBACK_PATH = "/callback"; export async function getChatGPTUser(): Promise { const requestHeaders = await headers(); const email = requestHeaders.get(USER_EMAIL_HEADER); if (!email) return null; const encodedFullName = requestHeaders.get(USER_FULL_NAME_HEADER); const fullName = encodedFullName && requestHeaders.get(USER_FULL_NAME_ENCODING_HEADER) === PERCENT_ENCODED_UTF8 ? safeDecodeURIComponent(encodedFullName) : null; return { displayName: fullName ?? email, email, fullName, }; } export async function requireChatGPTUser( returnTo: string, ): Promise { const user = await getChatGPTUser(); if (user) return user; redirect(chatGPTSignInPath(returnTo)); } export function chatGPTSignInPath(returnTo: string): string { const safeReturnTo = safeRelativeReturnPath(returnTo); return `${SIGN_IN_PATH}?return_to=${encodeURIComponent(safeReturnTo)}`; } export function chatGPTSignOutPath(returnTo = "/"): string { const safeReturnTo = safeRelativeReturnPath(returnTo); return `${SIGN_OUT_PATH}?return_to=${encodeURIComponent(safeReturnTo)}`; } function safeRelativeReturnPath(value: string): string { if (!value.startsWith("/") || value.startsWith("//")) return "/"; let url: URL; try { url = new URL(value, "https://app.local"); } catch { return "/"; } if (url.origin !== "https://app.local") return "/"; if (isReservedAuthPath(url.pathname)) return "/"; return `${url.pathname}${url.search}${url.hash}`; } function isReservedAuthPath(pathname: string): boolean { return ( pathname === SIGN_IN_PATH || pathname === SIGN_OUT_PATH || pathname === CALLBACK_PATH ); } function safeDecodeURIComponent(value: string): string | null { try { return decodeURIComponent(value); } catch { return null; } }