UNPKG

@dataroadinc/setup-auth

Version:

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

186 lines (185 loc) 8.29 kB
import { IdentityAwareProxyOAuthServiceClient } from "@google-cloud/iap"; import { backOff } from "exponential-backoff"; import { EKG_PROJECT_LONG, GCP_OAUTH_BRAND_NAME, } from "../../utils/env-handler.js"; import { SetupAuthError } from "../../utils/error.js"; import { BACKOFF_OPTIONS } from "./iam/base-iam.js"; export class GcpOAuthBrandClient { constructor(projectId, identity) { this.initialized = false; this.projectId = projectId; this.identity = identity; } async initialize() { if (this.initialized) return; try { const gaxAuth = await this.identity.getGaxAuthClient(); this.iapClient = new IdentityAwareProxyOAuthServiceClient({ auth: gaxAuth, }); this.initialized = true; } catch (error) { throw new SetupAuthError("Failed to initialize GcpOAuthBrandClient", { cause: error, }); } } formatProjectName() { return `projects/${this.projectId}`; } formatBrandName(brandId) { return `projects/${this.projectId}/brands/${brandId}`; } async findExistingBrand() { await this.initialize(); if (!this.iapClient) throw new SetupAuthError("IAP Client not initialized"); const parent = this.formatProjectName(); console.log(`Listing brands for project ${parent}...`); try { const [response] = await backOff(() => this.iapClient.listBrands({ parent }), { ...BACKOFF_OPTIONS, retry: (e) => { let code; if (e && typeof e === "object" && "code" in e) { code = e.code; } const retryable = code === 8 || code === 13 || code === 14; if (retryable) console.warn(`Retrying listBrands due to error code ${code}`); return retryable; }, }); const brandsList = response.brands; if (brandsList && brandsList.length > 0) { console.log(`Found existing brand: ${brandsList[0].name}`); return brandsList[0]; } else { console.log("No existing brand found for this project."); return null; } } catch (error) { console.error("Error listing existing brands:", error); const message = error instanceof Error ? error.message : String(error); throw new SetupAuthError(`Failed to list OAuth Brands for project ${this.projectId}. ` + `Ensure the user/SA has 'clientauthconfig.brands.list' permission. Original Error: ${message}`, { cause: error instanceof Error ? error : new Error(message) }); } } async createBrand(applicationTitle) { await this.initialize(); if (!this.iapClient) throw new SetupAuthError("IAP Client not initialized"); const parent = this.formatProjectName(); console.log(`Attempting to create a new brand for project ${parent} with title '${applicationTitle}'...`); try { const userEmail = await this.identity.getCurrentUserEmail(); if (!userEmail) throw new SetupAuthError("Could not get user email for brand creation."); const [brand] = await backOff(() => this.iapClient.createBrand({ parent, brand: { applicationTitle: applicationTitle, supportEmail: userEmail, }, }), BACKOFF_OPTIONS); console.log(`Successfully created brand: ${brand.name}`); return brand; } catch (error) { console.error("DEBUG: Raw error during createBrand SDK call:", error); let code; let details; let message = "Unknown error during brand creation."; if (error instanceof Error) { message = error.message; if (typeof error === "object" && error !== null) { if ("code" in error) code = error.code; if ("details" in error) details = error.details; } } else { message = String(error); } if (message.includes("permission denied") || code === 7) { throw new SetupAuthError(`Permission denied creating OAuth Brand for project ${this.projectId}. ` + `Ensure the user/SA has 'clientauthconfig.brands.create' permission. Original Error: ${message}`, { cause: error instanceof Error ? error : new Error(message) }); } if (message.includes("Brand already exists")) { console.warn("Brand creation failed because it already exists (unexpected). Attempting to find it again."); const existing = await this.findExistingBrand(); if (existing) return existing; throw new SetupAuthError("Brand already exists but could not be retrieved after creation attempt.", { cause: error instanceof Error ? error : new Error(message), }); } throw new SetupAuthError(`Failed to create OAuth Brand for project ${this.projectId}. Code: ${code ?? "N/A"}, Details: ${details ?? "N/A"}, Message: ${message}`, { cause: error instanceof Error ? error : new Error(message) }); } } async createOrGetBrand(applicationTitle) { await this.initialize(); const existingBrand = await this.findExistingBrand(); if (existingBrand?.name) { console.log(`Using existing brand: ${existingBrand.name}`); return existingBrand.name; } console.log("No existing brand found, creating a new one..."); const newBrand = await this.createBrand(applicationTitle); if (!newBrand?.name) { throw new SetupAuthError("Brand creation process did not return a valid brand name."); } return newBrand.name; } async getBrandByName(name) { await this.initialize(); if (!this.iapClient) throw new SetupAuthError("IAP Client not initialized"); console.warn("getBrandByName might be unreliable, prefer findExistingBrand"); try { const [brand] = await this.iapClient.getBrand({ name: this.formatBrandName(name), }); return brand; } catch (error) { console.error(`Error getting brand by name ${name}:`, error); return null; } } } async function gcpGetOauthBrandName(options) { if (options.oauthBrandName) { const oauthBrandName = options.oauthBrandName; console.log(`Using explicitly provided GCP OAuth brand name: ${oauthBrandName}`); return { success: true, oauthBrandName: oauthBrandName }; } if (process.env[GCP_OAUTH_BRAND_NAME]) { options.oauthBrandName = process.env[GCP_OAUTH_BRAND_NAME]; console.log(`Using GCP OAuth Brand Name from environment: ${options.oauthBrandName}`); return { success: true, oauthBrandName: options.oauthBrandName }; } if (process.env[EKG_PROJECT_LONG]) { options.oauthBrandName = process.env.EKG_PROJECT_LONG; console.log(`Found brand name in environment variable ${EKG_PROJECT_LONG}: ${options.oauthBrandName}`); return { success: true, oauthBrandName: options.oauthBrandName }; } return { success: false, error: "Could not determine brand name.\n" + `Please set ${GCP_OAUTH_BRAND_NAME} or ${EKG_PROJECT_LONG} ` + "in .env.local or environment variables.", }; } export async function gcpCheckOauthBrandName(options) { const { success, oauthBrandName, error } = await gcpGetOauthBrandName(options); if (!success) return { success: false, error }; process.env.GCP_OAUTH_BRAND_NAME = oauthBrandName; options.oauthBrandName = oauthBrandName; return { success: true }; }