briven-m2m.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. /**
  2. * Briven Auth M2M (machine-to-machine) — server only.
  3. * SuperTokens-style client_credentials via briven-engine.
  4. * @see https://supertokens.com/docs/authentication/m2m/client-credentials
  5. * @see Briven docs/HANDOFF-AUTH-M2M-FOR-ALL-PROJECTS.md
  6. *
  7. * Never import this from client components. Never put m2ms_ secrets in NEXT_PUBLIC_*.
  8. */
  9. type M2mTokenCache = { accessToken: string; expiresAtMs: number };
  10. let cache: M2mTokenCache | null = null;
  11. function apiOrigin(): string {
  12. return (
  13. process.env.BRIVEN_API_ORIGIN?.trim() ||
  14. process.env.NEXT_PUBLIC_BRIVEN_API_ORIGIN?.trim() ||
  15. "https://api.briven.tech"
  16. );
  17. }
  18. export function isM2mConfigured(): boolean {
  19. const id = process.env.BRIVEN_M2M_CLIENT_ID?.trim() ?? "";
  20. const secret = process.env.BRIVEN_M2M_CLIENT_SECRET?.trim() ?? "";
  21. return id.startsWith("m2m_") && secret.startsWith("m2ms_");
  22. }
  23. /**
  24. * Mint (or reuse cached) M2M access token.
  25. * Token TTL is ~3600s; refresh 60s early.
  26. */
  27. export async function getBrivenM2mToken(): Promise<string> {
  28. if (!isM2mConfigured()) {
  29. throw new Error(
  30. "M2M not configured. Create a machine client in Briven Auth → Keys, then set BRIVEN_M2M_CLIENT_ID + BRIVEN_M2M_CLIENT_SECRET.",
  31. );
  32. }
  33. const now = Date.now();
  34. if (cache && cache.expiresAtMs > now + 60_000) {
  35. return cache.accessToken;
  36. }
  37. const res = await fetch(`${apiOrigin()}/v1/auth-core/oauth/token`, {
  38. method: "POST",
  39. headers: { "content-type": "application/json" },
  40. body: JSON.stringify({
  41. grant_type: "client_credentials",
  42. client_id: process.env.BRIVEN_M2M_CLIENT_ID,
  43. client_secret: process.env.BRIVEN_M2M_CLIENT_SECRET,
  44. }),
  45. cache: "no-store",
  46. });
  47. if (!res.ok) {
  48. const text = await res.text();
  49. throw new Error(`m2m token failed: ${res.status} ${text.slice(0, 200)}`);
  50. }
  51. const data = (await res.json()) as {
  52. access_token: string;
  53. expires_in: number;
  54. project_id?: string;
  55. role?: string;
  56. };
  57. cache = {
  58. accessToken: data.access_token,
  59. expiresAtMs: now + (data.expires_in ?? 3600) * 1000,
  60. };
  61. return cache.accessToken;
  62. }
  63. /** Example: call project info with M2M bearer. */
  64. export async function brivenProjectInfoWithM2m(): Promise<unknown> {
  65. const token = await getBrivenM2mToken();
  66. const projectId =
  67. process.env.BRIVEN_PROJECT_ID?.trim() ||
  68. process.env.NEXT_PUBLIC_BRIVEN_PROJECT_ID?.trim();
  69. if (!projectId) throw new Error("BRIVEN_PROJECT_ID missing");
  70. const res = await fetch(`${apiOrigin()}/v1/projects/${projectId}/info`, {
  71. headers: { authorization: `Bearer ${token}` },
  72. cache: "no-store",
  73. });
  74. if (!res.ok) {
  75. throw new Error(`project info ${res.status}: ${(await res.text()).slice(0, 200)}`);
  76. }
  77. return res.json();
  78. }