import type { NextResponse } from "next/server" /** Ultimail session lifetime — independent of short-lived OIDC access tokens. */ export const SESSION_MAX_AGE_SEC = 60 * 60 * 24 * 365 export const SESSION_COOKIE_NAMES = { session: "ulti_session", accessToken: "ulti_access_token", refreshToken: "ulti_refresh_token", expiresAt: "ulti_expires_at", } as const export type TokenResponse = { access_token: string refresh_token?: string expires_in?: number id_token?: string token_type?: string } export function sessionCookieOptions() { return { httpOnly: true, sameSite: "lax" as const, path: "/", maxAge: SESSION_MAX_AGE_SEC, secure: process.env.NODE_ENV === "production", } } export function computeExpiresAt(expiresIn: number): number { return Date.now() + expiresIn * 1000 } export function isAccessTokenValid( accessToken: string | undefined, expiresAtRaw: string | undefined ): boolean { if (!accessToken || !expiresAtRaw) return false const expiresAt = Number(expiresAtRaw) return Number.isFinite(expiresAt) && Date.now() < expiresAt } type OidcTokenConfig = { tokenEndpoint: string clientId: string clientSecret: string } export async function exchangeRefreshToken( refreshToken: string, cfg: OidcTokenConfig ): Promise { const body = new URLSearchParams({ grant_type: "refresh_token", client_id: cfg.clientId, client_secret: cfg.clientSecret, refresh_token: refreshToken, }) const res = await fetch(cfg.tokenEndpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body, }) if (!res.ok) { throw new Error(`refresh_failed:${res.status}`) } return (await res.json()) as TokenResponse } export function resolveBearerToken(tokens: TokenResponse): string { const bearer = tokens.id_token ?? tokens.access_token if (!bearer) { throw new Error("no_token_in_response") } return bearer } export function applySessionCookies( response: NextResponse, tokens: TokenResponse, bearer?: string ) { const token = bearer ?? resolveBearerToken(tokens) const expiresIn = tokens.expires_in ?? 3600 const opts = sessionCookieOptions() response.cookies.set(SESSION_COOKIE_NAMES.session, "1", opts) response.cookies.set(SESSION_COOKIE_NAMES.accessToken, token, opts) if (tokens.refresh_token) { response.cookies.set( SESSION_COOKIE_NAMES.refreshToken, tokens.refresh_token, opts ) } response.cookies.set( SESSION_COOKIE_NAMES.expiresAt, String(computeExpiresAt(expiresIn)), opts ) }