/** * Briven Auth M2M (machine-to-machine) — server only. * SuperTokens-style client_credentials via briven-engine. * @see https://supertokens.com/docs/authentication/m2m/client-credentials * @see Briven docs/HANDOFF-AUTH-M2M-FOR-ALL-PROJECTS.md * * Never import this from client components. Never put m2ms_ secrets in NEXT_PUBLIC_*. */ type M2mTokenCache = { accessToken: string; expiresAtMs: number }; let cache: M2mTokenCache | null = null; function apiOrigin(): string { return ( process.env.BRIVEN_API_ORIGIN?.trim() || process.env.NEXT_PUBLIC_BRIVEN_API_ORIGIN?.trim() || "https://api.briven.tech" ); } export function isM2mConfigured(): boolean { const id = process.env.BRIVEN_M2M_CLIENT_ID?.trim() ?? ""; const secret = process.env.BRIVEN_M2M_CLIENT_SECRET?.trim() ?? ""; return id.startsWith("m2m_") && secret.startsWith("m2ms_"); } /** * Mint (or reuse cached) M2M access token. * Token TTL is ~3600s; refresh 60s early. */ export async function getBrivenM2mToken(): Promise { if (!isM2mConfigured()) { throw new Error( "M2M not configured. Create a machine client in Briven Auth → Keys, then set BRIVEN_M2M_CLIENT_ID + BRIVEN_M2M_CLIENT_SECRET.", ); } const now = Date.now(); if (cache && cache.expiresAtMs > now + 60_000) { return cache.accessToken; } const res = await fetch(`${apiOrigin()}/v1/auth-core/oauth/token`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ grant_type: "client_credentials", client_id: process.env.BRIVEN_M2M_CLIENT_ID, client_secret: process.env.BRIVEN_M2M_CLIENT_SECRET, }), cache: "no-store", }); if (!res.ok) { const text = await res.text(); throw new Error(`m2m token failed: ${res.status} ${text.slice(0, 200)}`); } const data = (await res.json()) as { access_token: string; expires_in: number; project_id?: string; role?: string; }; cache = { accessToken: data.access_token, expiresAtMs: now + (data.expires_in ?? 3600) * 1000, }; return cache.accessToken; } /** Example: call project info with M2M bearer. */ export async function brivenProjectInfoWithM2m(): Promise { const token = await getBrivenM2mToken(); const projectId = process.env.BRIVEN_PROJECT_ID?.trim() || process.env.NEXT_PUBLIC_BRIVEN_PROJECT_ID?.trim(); if (!projectId) throw new Error("BRIVEN_PROJECT_ID missing"); const res = await fetch(`${apiOrigin()}/v1/projects/${projectId}/info`, { headers: { authorization: `Bearer ${token}` }, cache: "no-store", }); if (!res.ok) { throw new Error(`project info ${res.status}: ${(await res.text()).slice(0, 200)}`); } return res.json(); }