UNPKG

@dataroadinc/setup-auth

Version:

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

383 lines (349 loc) 11.5 kB
/** * Programmatic API for setup-auth * * This module provides a stable, versioned API that programs can use * to register callback URLs and perform OAuth setup operations without * relying on CLI commands. */ import { OAuthProvider, PlatformType, UpdateResult } from "../types/index.js" import { buildRedirectUriList } from "../utils/redirect-urls.js" import { GcpOAuthWebClientManager } from "../providers/gcp/oauth/client.js" import { SetupAuthError } from "../utils/error.js" /** * Configuration for callback URL registration */ export interface CallbackUrlConfig { /** The OAuth provider (gcp, github, azure, linkedin) */ provider: OAuthProvider /** The platform (vercel, opennext, netlify) */ platform: PlatformType /** The deployment URL to register */ deploymentUrl?: string /** Custom callback path (optional) */ callbackPath?: string /** Additional URLs to include */ additionalUrls?: string[] /** Wildcard patterns to include */ wildcardPatterns?: string[] /** Project-specific configuration */ projectConfig?: { /** GCP project ID (required for GCP provider) */ gcpProjectId?: string /** GCP organization ID (required for GCP provider) */ gcpOrganizationId?: string /** GitHub OAuth app name (required for GitHub provider) */ githubAppName?: string /** Azure tenant ID (required for Azure provider) */ azureTenantId?: string } } /** * Result of callback URL registration */ export interface CallbackUrlRegistrationResult { /** Whether the registration was successful */ success: boolean /** Error message if registration failed */ error?: string /** The registered callback URLs */ registeredUrls?: string[] /** The OAuth client ID (if applicable) */ clientId?: string /** Provider-specific details */ providerDetails?: Record<string, unknown> } /** * Main API class for setup-auth operations */ export class SetupAuthAPI { private static instance: SetupAuthAPI | null = null private constructor() {} /** * Get the singleton instance of SetupAuthAPI */ static getInstance(): SetupAuthAPI { if (!SetupAuthAPI.instance) { SetupAuthAPI.instance = new SetupAuthAPI() } return SetupAuthAPI.instance } /** * Register callback URLs for an OAuth provider * * @param config Configuration for callback URL registration * @returns Promise resolving to registration result */ async registerCallbackUrls( config: CallbackUrlConfig ): Promise<CallbackUrlRegistrationResult> { try { // Validate configuration this.validateCallbackUrlConfig(config) // Build redirect URI list const redirectUris = await this.buildRedirectUris(config) // Register with the appropriate provider switch (config.provider) { case "gcp": return await this.registerGcpCallbackUrls(config, redirectUris) case "github": return await this.registerGitHubCallbackUrls(config, redirectUris) case "azure": return await this.registerAzureCallbackUrls(config, redirectUris) case "linkedin": return await this.registerLinkedInCallbackUrls(config, redirectUris) default: throw new SetupAuthError( `Unsupported OAuth provider: ${config.provider}` ) } } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error), } } } /** * Update existing callback URLs for an OAuth provider * * @param config Configuration for callback URL update * @returns Promise resolving to update result */ async updateCallbackUrls(config: CallbackUrlConfig): Promise<UpdateResult> { try { // Validate configuration this.validateCallbackUrlConfig(config) // Build redirect URI list const redirectUris = await this.buildRedirectUris(config) // Update with the appropriate provider switch (config.provider) { case "gcp": return await this.updateGcpCallbackUrls(config, redirectUris) case "github": return await this.updateGitHubCallbackUrls(config, redirectUris) case "azure": return await this.updateAzureCallbackUrls(config, redirectUris) case "linkedin": return await this.updateLinkedInCallbackUrls(config, redirectUris) default: throw new SetupAuthError( `Unsupported OAuth provider: ${config.provider}` ) } } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error), } } } /** * Validate callback URL configuration */ private validateCallbackUrlConfig(config: CallbackUrlConfig): void { if (!config.provider) { throw new SetupAuthError("OAuth provider is required") } if (!config.platform) { throw new SetupAuthError("Platform is required") } // Provider-specific validation switch (config.provider) { case "gcp": if (!config.projectConfig?.gcpProjectId) { throw new SetupAuthError( "GCP project ID is required for GCP provider" ) } break case "github": if (!config.projectConfig?.githubAppName) { throw new SetupAuthError( "GitHub app name is required for GitHub provider" ) } break case "azure": if (!config.projectConfig?.azureTenantId) { throw new SetupAuthError( "Azure tenant ID is required for Azure provider" ) } break } } /** * Build redirect URIs based on configuration */ private async buildRedirectUris( config: CallbackUrlConfig ): Promise<string[]> { // Create options object for the redirect URL utilities const options = { platform: config.platform, oauthProvider: config.provider, deploymentUrl: config.deploymentUrl, callbackPath: config.callbackPath, redirectOptions: { gcpOauthProjectId: config.projectConfig?.gcpProjectId || "", clientId: "", // Will be filled by provider-specific logic additionalUrls: config.additionalUrls, wildcardPatterns: config.wildcardPatterns, }, } return buildRedirectUriList(options) } /** * Register callback URLs for GCP */ private async registerGcpCallbackUrls( config: CallbackUrlConfig, redirectUris: string[] ): Promise<CallbackUrlRegistrationResult> { if (!config.projectConfig?.gcpProjectId) { throw new SetupAuthError("GCP project ID is required") } const oauthClient = new GcpOAuthWebClientManager( config.projectConfig.gcpProjectId ) // Create or get OAuth client const displayName = `${config.platform.charAt(0).toUpperCase() + config.platform.slice(1)} OAuth Client` const { clientId, clientSecret } = await oauthClient.createClient( displayName, redirectUris, [] // No JavaScript origins for server-side auth ) return { success: true, registeredUrls: redirectUris, clientId, providerDetails: { clientSecret, projectId: config.projectConfig.gcpProjectId, }, } } /** * Update callback URLs for GCP */ private async updateGcpCallbackUrls( config: CallbackUrlConfig, redirectUris: string[] ): Promise<UpdateResult> { if (!config.projectConfig?.gcpProjectId) { throw new SetupAuthError("GCP project ID is required") } // Get client ID from environment or config const clientId = process.env.GCP_OAUTH_CLIENT_ID?.replace( /\.apps\.googleusercontent\.com$/, "" ) if (!clientId) { throw new SetupAuthError( "GCP OAuth client ID not found. Please run setup first." ) } const oauthClient = new GcpOAuthWebClientManager( config.projectConfig.gcpProjectId ) await oauthClient.updateRedirectUris(clientId, redirectUris) return { success: true, redirectUris, } } /** * Register callback URLs for GitHub */ private async registerGitHubCallbackUrls( // eslint-disable-next-line @typescript-eslint/no-unused-vars _config: CallbackUrlConfig, // eslint-disable-next-line @typescript-eslint/no-unused-vars _redirectUris: string[] ): Promise<CallbackUrlRegistrationResult> { // TODO: Implement GitHub OAuth app creation throw new SetupAuthError("GitHub OAuth app creation not yet implemented") } /** * Update callback URLs for GitHub */ private async updateGitHubCallbackUrls( // eslint-disable-next-line @typescript-eslint/no-unused-vars _config: CallbackUrlConfig, // eslint-disable-next-line @typescript-eslint/no-unused-vars _redirectUris: string[] ): Promise<UpdateResult> { // TODO: Implement GitHub OAuth app update throw new SetupAuthError("GitHub OAuth app update not yet implemented") } /** * Register callback URLs for Azure */ private async registerAzureCallbackUrls( // eslint-disable-next-line @typescript-eslint/no-unused-vars _config: CallbackUrlConfig, // eslint-disable-next-line @typescript-eslint/no-unused-vars _redirectUris: string[] ): Promise<CallbackUrlRegistrationResult> { // TODO: Implement Azure AD app registration throw new SetupAuthError("Azure AD app registration not yet implemented") } /** * Update callback URLs for Azure */ private async updateAzureCallbackUrls( // eslint-disable-next-line @typescript-eslint/no-unused-vars _config: CallbackUrlConfig, // eslint-disable-next-line @typescript-eslint/no-unused-vars _redirectUris: string[] ): Promise<UpdateResult> { // TODO: Implement Azure AD app update throw new SetupAuthError("Azure AD app update not yet implemented") } /** * Register callback URLs for LinkedIn */ private async registerLinkedInCallbackUrls( // eslint-disable-next-line @typescript-eslint/no-unused-vars _config: CallbackUrlConfig, // eslint-disable-next-line @typescript-eslint/no-unused-vars _redirectUris: string[] ): Promise<CallbackUrlRegistrationResult> { // TODO: Implement LinkedIn OAuth app creation throw new SetupAuthError("LinkedIn OAuth app creation not yet implemented") } /** * Update callback URLs for LinkedIn */ private async updateLinkedInCallbackUrls( // eslint-disable-next-line @typescript-eslint/no-unused-vars _config: CallbackUrlConfig, // eslint-disable-next-line @typescript-eslint/no-unused-vars _redirectUris: string[] ): Promise<UpdateResult> { // TODO: Implement LinkedIn OAuth app update throw new SetupAuthError("LinkedIn OAuth app update not yet implemented") } } /** * Convenience function to register callback URLs * * @param config Configuration for callback URL registration * @returns Promise resolving to registration result */ export async function registerCallbackUrls( config: CallbackUrlConfig ): Promise<CallbackUrlRegistrationResult> { return SetupAuthAPI.getInstance().registerCallbackUrls(config) } /** * Convenience function to update callback URLs * * @param config Configuration for callback URL update * @returns Promise resolving to update result */ export async function updateCallbackUrls( config: CallbackUrlConfig ): Promise<UpdateResult> { return SetupAuthAPI.getInstance().updateCallbackUrls(config) }