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