diff --git a/app/controllers/github/callbacks_controller.rb b/app/controllers/github/callbacks_controller.rb index 0bbd9b46c..b82a3ff2c 100644 --- a/app/controllers/github/callbacks_controller.rb +++ b/app/controllers/github/callbacks_controller.rb @@ -2,18 +2,78 @@ class Github::CallbacksController < ApplicationController include Github::IntegrationHelper def show + # Log all received parameters for debugging + Rails.logger.info("GitHub callback received parameters: #{params.to_unsafe_h}") + Rails.logger.info("installation_id present: #{params[:installation_id].present?}") + Rails.logger.info("code present: #{params[:code].present?}") + Rails.logger.info("setup_action: #{params[:setup_action]}") + Rails.logger.info("state present: #{params[:state].present?}") + + if params[:installation_id].present? && params[:code].present? + Rails.logger.info('Handling installation with OAuth') + # Both installation and OAuth code present - handle both + handle_installation_with_oauth + elsif params[:installation_id].present? + Rails.logger.info('Handling installation only') + # Only installation_id present - redirect to OAuth + handle_installation + else + Rails.logger.info('Handling authorization only') + # Only OAuth code present - handle authorization + handle_authorization + end + rescue StandardError => e + Rails.logger.error("Github callback error: #{e.message}") + redirect_to fallback_redirect_uri + end + + private + + def handle_installation_with_oauth + Rails.logger.info("Processing installation with OAuth - installation_id: #{params[:installation_id]}, code: #{params[:code]}") + # Handle both installation and OAuth in one go + installation_id = params[:installation_id] + + @response = oauth_client.auth_code.get_token( + params[:code], + redirect_uri: "#{base_url}/github/callback" + ) + + handle_response(installation_id) + end + + def handle_installation + Rails.logger.info("Processing installation only - setup_action: #{params[:setup_action]}, installation_id: #{params[:installation_id]}") + if params[:setup_action] == 'install' + installation_id = params[:installation_id] + + redirect_to build_oauth_url(installation_id) + else + Rails.logger.error("Unknown setup_action: #{params[:setup_action]}") + redirect_to github_integration_settings_url + end + end + + def handle_authorization + Rails.logger.info("Processing authorization only - code: #{params[:code]}") @response = oauth_client.auth_code.get_token( params[:code], redirect_uri: "#{base_url}/github/callback" ) handle_response - rescue StandardError => e - Rails.logger.error("Github callback error: #{e.message}") - redirect_to github_redirect_uri end - private + def build_oauth_url(installation_id) + GlobalConfigService.load('GITHUB_CLIENT_ID', nil) + + # Store installation_id in session for later use + session[:github_installation_id] = installation_id + + # For now, redirect to a page that will initiate OAuth with proper account context + # This is a temporary solution until we have a proper account-agnostic setup + "#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/1/settings/integrations/github?setup_action=install&installation_id=#{installation_id}" + end def oauth_client app_id = GlobalConfigService.load('GITHUB_CLIENT_ID', nil) @@ -30,21 +90,37 @@ class Github::CallbacksController < ApplicationController ) end - def handle_response - hook = account.hooks.new( + def handle_response(installation_id = nil) + settings = { + token_type: parsed_body['token_type'], + scope: parsed_body['scope'] + } + + # Add installation_id from parameter or session + if installation_id + settings[:installation_id] = installation_id + elsif session[:github_installation_id] + settings[:installation_id] = session[:github_installation_id] + end + + # Use account from state parameter - this should work for both flows now + target_account = account + + hook = target_account.hooks.new( access_token: parsed_body['access_token'], status: 'enabled', app_id: 'github', - settings: { - token_type: parsed_body['token_type'], - scope: parsed_body['scope'] - } + settings: settings ) hook.save! + + # Clear session data + session.delete(:github_installation_id) + redirect_to github_redirect_uri rescue StandardError => e Rails.logger.error("Github callback error: #{e.message}") - redirect_to github_redirect_uri + redirect_to fallback_redirect_uri end def account @@ -52,15 +128,30 @@ class Github::CallbacksController < ApplicationController end def account_id - return unless params[:state] + # First try to get from state parameter (OAuth flow) + return verify_github_token(params[:state]) if params[:state].present? - verify_github_token(params[:state]) + # Fallback to hardcoded account 1 for installation flow (temporary) + 1 end def github_redirect_uri "#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{account.id}/settings/integrations/github" end + def github_integration_settings_url + "#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/1/settings/integrations/github" + end + + def fallback_redirect_uri + if account_id + github_redirect_uri + else + # Fallback if no account context available + "#{ENV.fetch('FRONTEND_URL', nil)}/app/settings/integrations" + end + end + def parsed_body @parsed_body ||= @response.response.parsed end diff --git a/app/models/integrations/app.rb b/app/models/integrations/app.rb index ce897fbcb..3e0a73cb5 100644 --- a/app/models/integrations/app.rb +++ b/app/models/integrations/app.rb @@ -62,6 +62,8 @@ class Integrations::App GlobalConfigService.load('SLACK_CLIENT_SECRET', nil).present? when 'linear' GlobalConfigService.load('LINEAR_CLIENT_ID', nil).present? + when 'github' + GlobalConfigService.load('GITHUB_CLIENT_ID', nil).present? && GlobalConfigService.load('GITHUB_CLIENT_SECRET', nil).present? when 'shopify' shopify_enabled?(account) when 'leadsquared' @@ -87,13 +89,11 @@ class Integrations::App end def build_github_action - app_id = GlobalConfigService.load('GITHUB_CLIENT_ID', nil) - [ - "#{params[:action]}?client_id=#{app_id}", - "redirect_uri=#{self.class.github_integration_url}", - "state=#{encode_state}", - 'scope=repo,read:org' - ].join('&') + GlobalConfigService.load('GITHUB_CLIENT_ID', nil) + + # For GitHub Apps, we need to redirect to the installation page first + github_app_name = GlobalConfigService.load('GITHUB_APP_NAME', 'chatwoot-qa') + "https://github.com/apps/#{github_app_name}/installations/new" end def enabled?(account) diff --git a/config/installation_config.yml b/config/installation_config.yml index 1fb05874a..952f0fa3a 100644 --- a/config/installation_config.yml +++ b/config/installation_config.yml @@ -387,5 +387,10 @@ locked: false description: 'Github client secret' type: secret +- name: GITHUB_APP_NAME + display_title: 'Github App Name' + value: + locked: false + description: 'Github App name for installation URLs (e.g., "chatwoot-qa")' ## ------ End of Github Integration Config ------- #