@dataroadinc/setup-auth
Version:
CLI tool and programmatic API for automated OAuth setup across cloud platforms
240 lines (239 loc) • 10.5 kB
JavaScript
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);
}
}