UNPKG

@dataroadinc/setup-auth

Version:

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

202 lines (201 loc) 8.7 kB
import { GcpCloudCliClient } from "../providers/gcp/cloud-cli-client.js"; import { config } from "dotenv"; import * as fs from "fs/promises"; import * as path from "path"; import { SetupAuthError } from "./error.js"; import { fileExists } from "./file.js"; export const AZURE_AD_CLIENT_ID = "AZURE_AD_CLIENT_ID"; export const AZURE_AD_CLIENT_SECRET = "AZURE_AD_CLIENT_SECRET"; export const AZURE_AD_TENANT_ID = "AZURE_AD_TENANT_ID"; export const EKG_BASE_EXTERNAL = "EKG_BASE_EXTERNAL"; export const EKG_ORG_LONG = "EKG_ORG_LONG"; export const EKG_ORG_SHORT = "EKG_ORG_SHORT"; export const EKG_PROJECT_DESCRIPTION = "EKG_PROJECT_DESCRIPTION"; export const EKG_PROJECT_LABEL = "EKG_PROJECT_LABEL"; export const EKG_PROJECT_LONG = "EKG_PROJECT_LONG"; export const EKG_PROJECT_NAME = "EKG_PROJECT_NAME"; export const EKG_PROJECT_SHORT = "EKG_PROJECT_SHORT"; export const GCP_OAUTH_ALLOWED_DOMAINS = "GCP_OAUTH_ALLOWED_DOMAINS"; export const GCP_OAUTH_CLIENT_ID = "GCP_OAUTH_CLIENT_ID"; export const GCP_OAUTH_CLIENT_SECRET = "GCP_OAUTH_CLIENT_SECRET"; export const GCP_OAUTH_ORGANIZATION_ID = "GCP_OAUTH_ORGANIZATION_ID"; export const GCP_OAUTH_ORIGINS = "GCP_OAUTH_ORIGINS"; export const GCP_OAUTH_PROJECT_ID = "GCP_OAUTH_PROJECT_ID"; export const GCP_OAUTH_QUOTA_PROJECT_ID = "GCP_OAUTH_QUOTA_PROJECT_ID"; export const GCP_OAUTH_BRAND_NAME = "GCP_OAUTH_BRAND_NAME"; export const GCP_OAUTH_REDIRECT_URIS = "GCP_OAUTH_REDIRECT_URIS"; export const GCP_OAUTH_APPLICATION_CREDENTIALS = "GCP_OAUTH_APPLICATION_CREDENTIALS"; export const GCP_OAUTH_BRAND_RESOURCE_NAME = "GCP_OAUTH_BRAND_RESOURCE_NAME"; export const GITHUB_OAUTH_ID = "GITHUB_OAUTH_ID"; export const GITHUB_OAUTH_ID_PROD = "GITHUB_OAUTH_ID_PROD"; export const GITHUB_OAUTH_SECRET = "GITHUB_OAUTH_SECRET"; export const GITHUB_OAUTH_SECRET_PROD = "GITHUB_OAUTH_SECRET_PROD"; export const NEXTAUTH_URL = "NEXTAUTH_URL"; export const VERCEL_ACCESS_TOKEN = "VERCEL_ACCESS_TOKEN"; export const VERCEL_PROJECT_NAME = "VERCEL_PROJECT_NAME"; export const VERCEL_TEAM_ID = "VERCEL_TEAM_ID"; export const VERCEL_PROJECT_ID = "VERCEL_PROJECT_ID"; export function validateRequiredEnvVars() { const isSetupOAuthCommand = process.argv.includes("gcp-setup-oauth"); const requiredVars = [ GCP_OAUTH_ALLOWED_DOMAINS, GCP_OAUTH_ORGANIZATION_ID, GCP_OAUTH_PROJECT_ID, GCP_OAUTH_APPLICATION_CREDENTIALS, VERCEL_TEAM_ID, VERCEL_PROJECT_ID, ]; if (!isSetupOAuthCommand) { requiredVars.push(GCP_OAUTH_CLIENT_ID, GCP_OAUTH_CLIENT_SECRET); } const missing = requiredVars.filter(v => !process.env[v] || process.env[v]?.trim() === ""); if (missing.length > 0) { throw new SetupAuthError(`Missing required environment variables: ${missing.join(", ")}.\n` + "Please set these in your .env.local file before running setup-auth.\n" + "See the documentation for details on required variables."); } } export async function loadEnvVariables() { const envFilePath = getEnvFilePath(); if (await fileExists(envFilePath)) { config({ path: envFilePath }); validateRequiredEnvVars(); } else { console.error("Root .env.local file not found. Some features may not work correctly."); process.exit(1); } } export async function gcpStoreOAuthProjectID(gcpOauthProjectId) { try { await updateOrAddEnvVariable(GCP_OAUTH_PROJECT_ID, gcpOauthProjectId); console.log(`✅ Stored GCP project ID in root .env.local: ${gcpOauthProjectId}`); } catch (error) { throw new SetupAuthError(`❌ Failed to store GCP project ID in root .env.local:`, { cause: error }); } } export async function gcpStoreOAuthOrganizationID(organizationId) { try { await updateOrAddEnvVariable(GCP_OAUTH_ORGANIZATION_ID, organizationId); console.log(`✅ Stored GCP organization ID in root .env.local: ${GCP_OAUTH_ORGANIZATION_ID}=${organizationId}`); } catch (error) { throw new SetupAuthError(`❌ Failed to store GCP organization ID in root .env.local:`, { cause: error }); } } export async function updateOrAddEnvVariable(key, value) { if (Array.isArray(value)) { value = value.join(","); } const envFilePath = getEnvFilePath(); let envContent = ""; let updatedKey = false; let valueToUse = value || ""; if (await fileExists(envFilePath)) { const lines = (await fs.readFile(envFilePath, "utf8")).split("\n"); for (let i = 0; i < lines.length; i++) { if (lines[i].startsWith(`${key}=`)) { const existingValue = lines[i].replace(`${key}=`, "").trim(); if (existingValue === valueToUse) { return; } lines[i] = `${key}=${valueToUse}`; updatedKey = true; break; } } if (!updatedKey) { lines.push(`${key}=${valueToUse}`); } envContent = lines.join("\n"); } else { envContent = `${key}=${value}\n`; } await fs.writeFile(envFilePath, envContent); } function getEnvFilePath() { return path.resolve(process.cwd(), ".env.local"); } export async function updateEnvFileWithOAuth(clientId, clientSecret, allowedDomains) { try { await updateOrAddEnvVariable(GCP_OAUTH_CLIENT_ID, clientId); await updateOrAddEnvVariable(GCP_OAUTH_CLIENT_SECRET, clientSecret); await updateOrAddEnvVariable(GCP_OAUTH_ALLOWED_DOMAINS, allowedDomains); console.log(`✅ Updated root .env.local with OAuth credentials`); } catch (error) { throw new SetupAuthError("❌ Failed to update .env.local file:", { cause: error, }); } } export function gcpGetOAuthProjectID(baseProjectName) { if (process.env[GCP_OAUTH_PROJECT_ID]) { return process.env[GCP_OAUTH_PROJECT_ID].trim(); } if (!baseProjectName) { if (process.env.EKG_PROJECT_NAME) { baseProjectName = process.env.EKG_PROJECT_NAME; } else { throw new SetupAuthError("No base project name provided"); } } if (baseProjectName && baseProjectName.match(/^[a-z][-a-z0-9]{4,28}[a-z0-9]$/)) { console.log(`Using provided name as GCP project ID: ${baseProjectName}`); process.env[GCP_OAUTH_PROJECT_ID] = baseProjectName; return baseProjectName; } const randomSuffix = Math.floor(10000 + Math.random() * 90000); const sanitizedBase = baseProjectName .toLowerCase() .replace(/[^a-z0-9-]/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, ""); const maxBaseLength = 23; const truncatedBase = sanitizedBase.substring(0, maxBaseLength); const projectID = `${truncatedBase}-${randomSuffix}`; console.log(`Generated GCP project ID with random suffix: ${projectID}`); process.env[GCP_OAUTH_PROJECT_ID] = projectID; return projectID; } export async function getAdcEmailOrNull() { try { const cli = new GcpCloudCliClient(); return await cli.getAdcEmail(); } catch { return null; } } export function enforceUserDomainOrFail(userEmail) { const expectedDomain = process.env.EKG_ORG_PRIMARY_DOMAIN; if (!expectedDomain) { throw new SetupAuthError("Missing required environment variable: EKG_ORG_PRIMARY_DOMAIN. This tool enforces a fail-fast approach and requires this variable to be set in your .env.local (e.g., EKG_ORG_PRIMARY_DOMAIN=your-domain.com).", { cause: new Error("EKG_ORG_PRIMARY_DOMAIN not set") }); } const userDomain = userEmail?.split("@")[1] || ""; if (userDomain.toLowerCase() !== expectedDomain.toLowerCase()) { throw new SetupAuthError(`Authenticated user (${userEmail}) does not match the required organization domain (${expectedDomain}).\n` + `Please run 'gcloud auth login <your-user>@${expectedDomain}' and try again.`, { cause: new Error(`User domain mismatch: got ${userDomain}, expected ${expectedDomain}`), }); } } export async function hookSaveEnvironmentVariables() { console.log("hookSaveEnvironmentVariables"); } export async function printGcloudAndAdcAccounts() { try { const cli = new GcpCloudCliClient(); const gcloudAccount = await cli.getActiveAccount(); const adcEmail = await cli.getAdcEmail(); console.log(`gcloud active account: ${gcloudAccount || "(not set)"}`); console.log(`ADC (Application Default Credentials) account: ${adcEmail || "(not set)"}`); } catch { console.warn("Could not print gcloud/ADC account info."); } }