UNPKG

@dataroadinc/setup-auth

Version:

CLI tool and programmatic API for automated OAuth setup across cloud platforms

240 lines (239 loc) 10.5 kB
import fs from "fs/promises"; import { GCP_OAUTH_APPLICATION_CREDENTIALS } from "../../../utils/env-handler.js"; import { SetupAuthError } from "../../../utils/error.js"; import axios from "axios"; import { GoogleAuth as AuthLibGoogleAuth, } from "google-auth-library"; import { GoogleAuth as GaxGoogleAuth } from "google-gax"; export class GcpAuthenticatedIdentity { constructor(options) { this.options = options; this.authLibGoogleAuth = new AuthLibGoogleAuth(options); this.gaxGoogleAuth = new GaxGoogleAuth({ ...options, }); } async getAccessToken() { console.log("Getting access token..."); try { const client = await this.authLibGoogleAuth.getClient(); if (!client) { throw new Error("Failed to get client object from authLibGoogleAuth"); } const response = await client.getAccessToken(); if (!response || !response.token) { throw new Error("Failed to get access token from auth library client"); } return response.token; } catch (error) { throw new SetupAuthError("Failed to get access token", { cause: error }); } } async getAuthClient() { return this.authLibGoogleAuth; } async getGaxAuthClient() { return this.gaxGoogleAuth; } async getAuthClientForResourceManager() { return this.authLibGoogleAuth; } async getAuthClientForIAM() { return this.authLibGoogleAuth; } async getAuthClientForOAuth2() { return this.authLibGoogleAuth; } } export class GcpUserAccountIdentity extends GcpAuthenticatedIdentity { constructor() { super({ scopes: [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/userinfo.email", ], clientOptions: { universeDomain: "googleapis.com", }, }); console.log("Using user account authentication"); } async getCurrentUserEmail() { console.log("Attempting to retrieve user email via User Account Identity..."); try { const token = await this.getAccessToken(); if (!token) { throw new Error("No access token available for user account"); } const response = await axios.get(`https://oauth2.googleapis.com/tokeninfo?access_token=${token}`); if (response.data && response.data.email) { console.log("User email retrieved via tokeninfo:", response.data.email); return response.data.email; } throw new SetupAuthError("Could not determine user's email from tokeninfo response."); } catch (error) { console.warn("Tokeninfo approach failed for User Account:", error instanceof Error ? error.message : error); function isGaxiosReauthError(err) { if (err && typeof err === "object") { if ("response" in err && typeof err.response === "object" && err.response && "data" in err.response) { const data = err.response.data; const descriptionIncludesReauth = data?.error_description?.includes("reauth") ?? false; return (data?.error_subtype === "invalid_rapt" || (data?.error === "invalid_grant" && descriptionIncludesReauth)); } } return false; } let rootCause = error; while (rootCause instanceof SetupAuthError && rootCause.originalError) { rootCause = rootCause.originalError; } if (isGaxiosReauthError(rootCause)) { throw new SetupAuthError(`Failed to retrieve user email.\n\n⚠️ Reauthentication required. Please log in again using:\n\n gcloud auth login\n`, { cause: error }); } throw new SetupAuthError("Failed to retrieve email for User Account. Ensure you are logged in (`gcloud auth login`) and have permissions.", { cause: error, }); } } } export class GcpAdcIdentity extends GcpAuthenticatedIdentity { constructor() { super({ scopes: [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/userinfo.email", ], clientOptions: { universeDomain: "googleapis.com", }, }); console.log("Using Application Default Credentials"); } async getCurrentUserEmail() { console.log("Attempting to retrieve user email via ADC..."); try { const token = await this.getAccessToken(); const response = await axios.get(`https://oauth2.googleapis.com/tokeninfo?access_token=${token}`); if (response.data && response.data.email) { console.log("ADC email retrieved via tokeninfo:", response.data.email); return response.data.email; } throw new SetupAuthError("Could not determine email associated with ADC."); } catch (error) { console.warn("Failed to retrieve email via ADC:", error instanceof Error ? error.message : error); function isGaxiosReauthError(err) { if (err && typeof err === "object") { if ("response" in err && typeof err.response === "object" && err.response && "data" in err.response) { const data = err.response.data; const descriptionIncludesReauth = data?.error_description?.includes("reauth") ?? false; return (data?.error_subtype === "invalid_rapt" || (data?.error === "invalid_grant" && descriptionIncludesReauth)); } } return false; } let rootCause = error; while (rootCause instanceof SetupAuthError && rootCause.originalError) { rootCause = rootCause.originalError; } if (isGaxiosReauthError(rootCause)) { throw new SetupAuthError(`Failed to retrieve ADC email.\n\n⚠️ Reauthentication required for Application Default Credentials. Please log in again using:\n\n gcloud auth application-default login\n`, { cause: error }); } throw new SetupAuthError("Failed to retrieve email using Application Default Credentials. Ensure ADC are configured correctly (`gcloud auth application-default login`) or provide a service account key.", { cause: error }); } } } export class GcpServiceAccountIdentity extends GcpAuthenticatedIdentity { constructor(keyFilePath) { super({ keyFile: keyFilePath, scopes: [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/userinfo.email", ], clientOptions: { universeDomain: "googleapis.com", }, }); console.log(`Using service account authentication with key file: ${keyFilePath}`); } async getCurrentUserEmail() { console.log("Attempting to retrieve email address of the service account..."); try { const credentials = await this.authLibGoogleAuth.getCredentials(); if (credentials.client_email) { console.log("Service Account email from credentials:", credentials.client_email); return credentials.client_email; } else { if (this.options.keyFile && typeof this.options.keyFile === "string") { try { const keyFileContent = await fs.readFile(this.options.keyFile, "utf-8"); const keyData = JSON.parse(keyFileContent); if (keyData.client_email) { console.log("Service Account email from key file:", keyData.client_email); return keyData.client_email; } } catch (parseError) { console.warn("Could not parse key file to get service account email:", parseError); } } throw new SetupAuthError("Could not determine Service Account email from credentials or key file."); } } catch (error) { throw new SetupAuthError("Failed to retrieve Service Account email.", { cause: error, }); } } } export function detectAuthenticationType(context) { if (context?.forceAuthType) { return context.forceAuthType; } if (process.env.GOOGLE_APPLICATION_CREDENTIALS || context?.serviceAccountPath) { return "Service Account"; } return "ADC"; } export class GcpIdentityFactory { static createIdentity(context) { const authType = detectAuthenticationType(context); switch (authType) { case "Service Account": return GcpIdentityFactory.createServiceAccountIdentity(context?.serviceAccountPath); case "ADC": return GcpIdentityFactory.createAdcIdentity(); case "User Account": return GcpIdentityFactory.createUserIdentity(); default: throw new Error(`Unsupported authentication type detected: ${authType}`); } } static createAdcIdentity() { return new GcpAdcIdentity(); } static createUserIdentity() { return new GcpUserAccountIdentity(); } static createServiceAccountIdentity(keyFilePath) { const keyFilePath2 = keyFilePath || process.env[GCP_OAUTH_APPLICATION_CREDENTIALS]; if (!keyFilePath2) { throw new SetupAuthError(`Service account key path not found in environment variable ${GCP_OAUTH_APPLICATION_CREDENTIALS}. ` + `Please run the 'gcp-setup-service-account' command first.`); } return new GcpServiceAccountIdentity(keyFilePath2); } }