UNPKG

@dataroadinc/setup-auth

Version:

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

237 lines (215 loc) 7.84 kB
import { GCP_OAUTH_ORGANIZATION_ID } from "../../utils/env-handler.js" import { SetupAuthError } from "../../utils/error.js" import { OrganizationsClient, protos } from "@google-cloud/resource-manager" import { GcpAuthenticatedIdentity } from "./creds/identity.js" import { GcpOrganizationIamManager } from "./iam/organization-iam.js" export type GcpOrganization = protos.google.cloud.resourcemanager.v3.IOrganization type IamBinding = protos.google.iam.v1.IBinding interface IamPolicy { bindings: IamBinding[] version?: number etag?: string } export class GcpOrganizationManager { private initialized = false private client!: OrganizationsClient private identity: GcpAuthenticatedIdentity private organizationId: string private iamManager: GcpOrganizationIamManager | undefined = undefined constructor(identity: GcpAuthenticatedIdentity, organizationId: string) { this.organizationId = organizationId this.identity = identity } async initialize(): Promise<void> { if (this.initialized) { return } try { console.log("OrganizationManager: Initializing GAX auth client...") const auth = await this.identity.getGaxAuthClient() console.log("OrganizationManager: Creating OrganizationsClient...") this.client = new OrganizationsClient({ auth }) console.log("OrganizationManager: OrganizationsClient created.") // Create and initialize IAM Manager *after* client is created, passing the client console.log( "OrganizationManager: Initializing IAM Manager and passing client..." ) this.iamManager = new GcpOrganizationIamManager( this.identity, this.organizationId, this.client ) await this.iamManager.initialize() // Initialize the IAM manager itself (might do further setup) console.log("OrganizationManager: IAM Manager initialized.") this.initialized = true // Set initialized flag only after everything succeeds } catch (error) { console.error( "Error during GcpOrganizationManager initialization:", error ) if (error instanceof Error) { throw new SetupAuthError( `Failed to initialize client: ${error.message}`, { cause: error } ) } throw new SetupAuthError("Failed to initialize client: Unknown error") } } async getOrganization(): Promise<GcpOrganization> { try { const [organization] = await this.client.getOrganization({ name: `organizations/${this.organizationId}`, }) return organization } catch (error) { if (error instanceof Error) { throw new SetupAuthError( `Failed to get organization: ${error.message}`, { cause: error } ) } throw new SetupAuthError("Failed to get organization: Unknown error") } } /** * Get the IAM policy for the organization * * TODO: This duplicates the functionality of the GcpOrganizationIamManager.getIamPolicy() method. * We should refactor this to use the GcpOrganizationIamManager.getIamPolicy() method. */ async getIamPolicy(): Promise<IamPolicy> { try { const [policy] = await this.client.getIamPolicy({ resource: `organizations/${this.organizationId}`, }) return policy as IamPolicy } catch (error) { if (error instanceof Error) { throw new SetupAuthError(`Failed to get IAM policy: ${error.message}`, { cause: error, }) } throw new SetupAuthError("Failed to get IAM policy: Unknown error") } } /** * Get all IAM roles and their members for the organization * * TODO: This duplicates the functionality of the GcpOrganizationIamManager.getIamRoles() method. * We should refactor this to use the GcpOrganizationIamManager.getIamRoles() method. */ async getIamRoles(): Promise<{ role: string; members: string[] }[]> { try { const [policy] = await this.client.getIamPolicy({ resource: `organizations/${this.organizationId}`, }) return (policy.bindings || []).map((binding: IamBinding) => ({ role: binding.role || "", members: binding.members || [], })) } catch (error) { if (error instanceof Error) { throw new SetupAuthError(`Failed to get IAM roles: ${error.message}`, { cause: error, }) } throw new SetupAuthError("Failed to get IAM roles: Unknown error") } } async ensurePermissions(): Promise<void> { await this.initialize() if (!this.iamManager) { throw new SetupAuthError("IAM Manager was not initialized correctly.") } await this.iamManager.ensurePermissions() } } export async function gcpSetOauthOrganizationId(options: { gcpOauthOrganizationId?: string }): Promise<void> { // Validate project ID format if (!options.gcpOauthOrganizationId) { throw new SetupAuthError("Organization ID cannot be empty") } if ( options.gcpOauthOrganizationId.length < 6 || options.gcpOauthOrganizationId.length > 30 ) { throw new SetupAuthError( `Organization ID must be between 6 and 30 characters long. Got ${options.gcpOauthOrganizationId.length} characters: "${options.gcpOauthOrganizationId}"` ) } // If a GCP organization ID is provided, set it in the environment process.env.GCP_OAUTH_ORGANIZATION_ID = options.gcpOauthOrganizationId } export async function gcpGetOauthOrganizationId(options: { gcpOauthOrganizationId?: string }): Promise<{ success: boolean gcpOauthOrganizationId?: string error?: string }> { // If the GCP organization ID is explicitly provided, use it if (options.gcpOauthOrganizationId) { const gcpOauthOrganizationId = options.gcpOauthOrganizationId console.log( `Using explicitly provided GCP organization ID: ${gcpOauthOrganizationId}` ) return { success: true, gcpOauthOrganizationId: gcpOauthOrganizationId } } // If the GCP organization ID is provided in the environment, use it. // (../../.env.local has been loaded into process.env) if (process.env[GCP_OAUTH_ORGANIZATION_ID]) { options.gcpOauthOrganizationId = process.env[GCP_OAUTH_ORGANIZATION_ID] console.log( `Using GCP organization ID from environment: ${options.gcpOauthOrganizationId}` ) return { success: true, gcpOauthOrganizationId: options.gcpOauthOrganizationId, } } // Fall back to other environment variables if (process.env.EKG_ORG_SHORT) { options.gcpOauthOrganizationId = process.env.EKG_ORG_SHORT console.log( `Found organization name in environment variable EKG_ORG_SHORT: ${options.gcpOauthOrganizationId}` ) return { success: true, gcpOauthOrganizationId: options.gcpOauthOrganizationId, } } return { success: false, error: "Could not determine organization name.\n" + `Please set ${GCP_OAUTH_ORGANIZATION_ID} or EKG_ORG_SHORT `, } } export async function gcpCheckOauthOrganizationId(options: { gcpOauthOrganizationId?: string }): Promise<{ success: boolean; error?: string }> { const { success, gcpOauthOrganizationId, error } = await gcpGetOauthOrganizationId(options) if (!success) return { success: false, error } try { // Validate and set the project ID in environment variables await gcpSetOauthOrganizationId({ gcpOauthOrganizationId: gcpOauthOrganizationId!, }) // Update the options with the validated project ID options.gcpOauthOrganizationId = gcpOauthOrganizationId! return { success: true } } catch (error) { if (error instanceof SetupAuthError) { return { success: false, error: error.message } } return { success: false, error: `Failed to validate organization ID: ${error}`, } } }