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