{"version":3,"file":"gcp-auth.cjs","names":["VERTEX_AI_AUTH_SCOPES","iife","AuthError"],"sources":["../../src/utils/gcp-auth.ts"],"sourcesContent":["/**\n * GCP authentication utilities.\n *\n * Adapted from: https://github.com/kriasoft/web-auth-library\n * Original source: web-auth-library by Kriasoft\n * License: MIT (https://github.com/kriasoft/web-auth-library/blob/main/LICENSE)\n */\n\nimport { AuthError } from \"../utils/errors.js\";\nimport { iife } from \"../utils/misc.js\";\nimport { VERTEX_AI_AUTH_SCOPES } from \"../const.js\";\n\ntype JoseModule = typeof import(\"jose\");\n\nlet joseModulePromise: Promise<JoseModule> | undefined;\n\nasync function getJose(): Promise<JoseModule> {\n  joseModulePromise ??= import(\"jose\");\n  return joseModulePromise;\n}\n\n/**\n * Google Cloud Platform service account credentials interface.\n *\n * This interface represents the structure of a GCP service account key file,\n * which is used for authenticating with Google Cloud APIs. Service account\n * keys can be downloaded from the Google Cloud Console and contain all the\n * information needed to authenticate as a service account.\n *\n * @see https://cloud.google.com/iam/docs/creating-managing-service-account-keys\n */\nexport interface GCPCredentials {\n  /** The type of credential, typically \"service_account\" */\n  type: string;\n  /** The GCP project ID associated with this service account */\n  project_id: string;\n  /** The unique identifier for the private key */\n  private_key_id: string;\n  /** The RSA private key in PEM format used for signing JWTs */\n  private_key: string;\n  /** The unique client ID for this service account */\n  client_id: string;\n  /** The email address of the service account */\n  client_email: string;\n  /** The OAuth2 authorization endpoint URI */\n  auth_uri: string;\n  /** The OAuth2 token endpoint URI where access tokens are requested */\n  token_uri: string;\n  /** The URL for the OAuth2 provider's x509 certificate */\n  auth_provider_x509_cert_url: string;\n  /** The URL for this service account's x509 certificate */\n  client_x509_cert_url: string;\n}\n\ninterface CacheValue {\n  /** Unix timestamp (in seconds) when this cache entry was created */\n  created: number;\n  /** Promise that resolves to the token and its expiration time */\n  promise: Promise<{ token: string; expires: number }>;\n}\n\nconst cache = new Map<string, CacheValue>();\nconst CACHE_MAX_AGE = 60 * 60; // 1 hour\n\n/**\n * Normalizes GCP credentials to a frozen GCPCredentials object.\n *\n * This function accepts credentials in either string (JSON) or object format\n * and ensures the returned value is a frozen (immutable) GCPCredentials object.\n * Freezing the credentials prevents accidental modification and ensures\n * consistency throughout the application lifecycle.\n *\n * @param credentials - The GCP service account credentials, either as a JSON string\n *                      or as a GCPCredentials object\n * @returns A frozen GCPCredentials object that cannot be modified\n *\n * @example\n * ```typescript\n * // From JSON string\n * const creds = normalizeGCPCredentials('{\"type\":\"service_account\",...}');\n *\n * // From object\n * const creds = normalizeGCPCredentials({\n *   type: \"service_account\",\n *   project_id: \"my-project\",\n *   // ... other fields\n * });\n * ```\n */\nexport function normalizeGCPCredentials(\n  credentials: string | GCPCredentials\n): GCPCredentials {\n  return typeof credentials === \"string\"\n    ? Object.freeze(JSON.parse(credentials))\n    : Object.isFrozen(credentials)\n      ? credentials\n      : Object.freeze(credentials);\n}\n\n/**\n * Imports and returns the private key from GCP credentials for JWT signing.\n *\n * This function extracts the RSA private key from the service account credentials\n * and imports it using the PKCS#8 format with the RS256 algorithm. The returned\n * key can be used to sign JWTs for authentication with Google Cloud services.\n *\n * @param credentials - The GCP service account credentials containing the private key\n * @returns A Promise that resolves to the imported private key suitable for JWT signing\n *\n * @throws {Error} If the private key format is invalid or cannot be imported\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const privateKey = await getGCPPrivateKey(credentials);\n * // Use privateKey for signing operations\n * ```\n */\nexport async function getGCPPrivateKey(credentials: GCPCredentials) {\n  const { importPKCS8 } = await getJose();\n  return importPKCS8(credentials.private_key, \"RS256\");\n}\n\n/**\n * Generates a custom JWT token for authenticating with Google Cloud services.\n *\n * This function creates a signed JWT (JSON Web Token) using the service account's\n * private key. The token includes standard claims such as issuer, audience, subject,\n * issued-at time, and expiration time. This custom token can be exchanged for an\n * access token or ID token through Google's OAuth2 token endpoint.\n *\n * The generated JWT is valid for 1 hour and follows the OAuth 2.0 JWT Bearer\n * Token flow as specified by RFC 7523.\n *\n * @param credentials - The GCP service account credentials used to sign the JWT\n * @param scopes - The OAuth scopes the resulting access token should carry.\n *                 Google requires these on the assertion itself: the claim set\n *                 for an access token request must include `scope`, \"a\n *                 space-delimited list of the permissions that the application\n *                 requests\". Omitting it makes the exchange fail with\n *                 `Invalid OAuth scope or ID token audience provided`.\n * @returns A Promise that resolves to the signed JWT string\n *\n * @throws {Error} If the private key cannot be imported or the JWT cannot be signed\n *\n * @see https://datatracker.ietf.org/doc/html/rfc7523\n * @see https://developers.google.com/identity/protocols/oauth2/service-account\n * @see https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const customToken = await getGCPCustomToken(credentials, [\n *   \"https://www.googleapis.com/auth/cloud-platform\",\n * ]);\n * // Use customToken to request an access token\n * ```\n */\nexport async function getGCPCustomToken(\n  credentials: GCPCredentials,\n  scopes: string[] = VERTEX_AI_AUTH_SCOPES\n) {\n  const { SignJWT } = await getJose();\n  const privateKey = await getGCPPrivateKey(credentials);\n  const customToken = await new SignJWT({ scope: scopes.join(\" \") })\n    .setIssuer(credentials.client_email)\n    .setAudience(credentials.token_uri)\n    .setSubject(credentials.client_email)\n    .setProtectedHeader({ alg: \"RS256\" })\n    .setIssuedAt()\n    .setExpirationTime(\"1h\")\n    .sign(privateKey);\n  return customToken;\n}\n\n/**\n * Retrieves an access token for authenticating with Google Cloud APIs.\n *\n * - Tokens are cached based on the token URI and private key ID\n * - Cache entries are valid for up to 1 hour\n * - Expired tokens trigger automatic refresh\n * - Multiple concurrent requests for the same token are deduplicated\n *\n * The function handles both access tokens and ID tokens, automatically detecting\n * which type is returned by the token endpoint and extracting the appropriate\n * expiration time.\n *\n * @param credentials - The GCP service account credentials used to request the token\n * @returns A Promise that resolves to the access token string\n *\n * @throws {AuthError} If the token request fails (e.g., invalid credentials,\n *                     network error, or API error)\n *\n * @see https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials\n * @see https://datatracker.ietf.org/doc/html/rfc7523\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const accessToken = await getGCPCredentialsAccessToken(credentials);\n *\n * // Use the access token in API requests\n * const response = await fetch('https://api.google.com/...', {\n *   headers: {\n *     'Authorization': `Bearer ${accessToken}`\n *   }\n * });\n * ```\n *\n * @remarks\n * The caching mechanism uses a two-level validation:\n * 1. Cache entry age (must be less than CACHE_MAX_AGE)\n * 2. Token expiration time (must not be expired)\n *\n * This ensures that even if a cache entry is fresh, an expired token\n * will trigger a refresh request.\n */\nexport async function getGCPCredentialsAccessToken(\n  credentials: GCPCredentials,\n  scopes: string[] = VERTEX_AI_AUTH_SCOPES\n): Promise<string> {\n  const tokenUrl = credentials.token_uri;\n\n  const cacheKeyUrl = new URL(tokenUrl);\n  cacheKeyUrl.searchParams.set(\"key\", credentials.private_key_id);\n  // The scopes are baked into the token that comes back, so a token minted for\n  // one scope set must not be handed to a caller that asked for another.\n  cacheKeyUrl.searchParams.set(\"scope\", [...scopes].sort().join(\" \"));\n  const cacheKey = cacheKeyUrl.toString();\n\n  // Attempt to retrieve the token from the cache\n  const cacheValue = cache.get(cacheKey);\n  let now = Math.floor(Date.now() / 1000);\n\n  if (cacheValue) {\n    if (cacheValue.created > now - CACHE_MAX_AGE) {\n      let token = await cacheValue.promise;\n\n      if (token.expires > now) {\n        return token.token;\n      } else {\n        const nextValue = cache.get(cacheKey);\n\n        if (nextValue && nextValue !== cacheValue) {\n          token = await nextValue.promise;\n          if (token.expires > now) {\n            return token.token;\n          } else {\n            cache.delete(cacheKey);\n          }\n        }\n      }\n    } else {\n      cache.delete(cacheKey);\n    }\n  }\n\n  const promise = iife(async () => {\n    now = Math.floor(Date.now() / 1000);\n\n    // Request a new token from the Google Cloud API\n    const jwt = await getGCPCustomToken(credentials, scopes);\n    const body = new URLSearchParams();\n    body.append(\"grant_type\", \"urn:ietf:params:oauth:grant-type:jwt-bearer\");\n    body.append(\"assertion\", jwt);\n    const res = await fetch(tokenUrl, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n      body,\n    });\n\n    if (!res.ok) {\n      throw await AuthError.fromResponse(res);\n    }\n\n    const data = await res.json();\n\n    if (\"id_token\" in data) {\n      const { decodeJwt } = await getJose();\n      const claims = decodeJwt(data.id_token);\n      return { token: data.id_token, expires: claims.exp as number };\n    }\n\n    const lastModified = res.headers.get(\"last-modified\");\n    const expires = lastModified\n      ? Math.floor(new Date(lastModified).valueOf() / 1000) + data.expires_in\n      : now + data.expires_in;\n\n    return { expires, token: data.access_token };\n  });\n\n  cache.set(cacheKey, { created: now, promise });\n  return await promise.then((data) => data.token);\n}\n"],"mappings":"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"}