mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-25 15:34:55 +00:00
chore: add oauth flow
This commit is contained in:
71
app/controllers/github/callbacks_controller.rb
Normal file
71
app/controllers/github/callbacks_controller.rb
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
class Github::CallbacksController < ApplicationController
|
||||||
|
include Github::IntegrationHelper
|
||||||
|
|
||||||
|
def show
|
||||||
|
@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 oauth_client
|
||||||
|
app_id = GlobalConfigService.load('GITHUB_CLIENT_ID', nil)
|
||||||
|
app_secret = GlobalConfigService.load('GITHUB_CLIENT_SECRET', nil)
|
||||||
|
|
||||||
|
OAuth2::Client.new(
|
||||||
|
app_id,
|
||||||
|
app_secret,
|
||||||
|
{
|
||||||
|
site: 'https://github.com',
|
||||||
|
token_url: '/login/oauth/access_token',
|
||||||
|
authorize_url: '/login/oauth/authorize'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_response
|
||||||
|
hook = 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']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
hook.save!
|
||||||
|
redirect_to github_redirect_uri
|
||||||
|
rescue StandardError => e
|
||||||
|
Rails.logger.error("Github callback error: #{e.message}")
|
||||||
|
redirect_to github_redirect_uri
|
||||||
|
end
|
||||||
|
|
||||||
|
def account
|
||||||
|
@account ||= Account.find(account_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_id
|
||||||
|
return unless params[:state]
|
||||||
|
|
||||||
|
verify_github_token(params[:state])
|
||||||
|
end
|
||||||
|
|
||||||
|
def github_redirect_uri
|
||||||
|
"#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{account.id}/settings/integrations/github"
|
||||||
|
end
|
||||||
|
|
||||||
|
def parsed_body
|
||||||
|
@parsed_body ||= @response.response.parsed
|
||||||
|
end
|
||||||
|
|
||||||
|
def base_url
|
||||||
|
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
||||||
|
end
|
||||||
|
end
|
||||||
47
app/helpers/github/integration_helper.rb
Normal file
47
app/helpers/github/integration_helper.rb
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
module Github::IntegrationHelper
|
||||||
|
# Generates a signed JWT token for Github integration
|
||||||
|
#
|
||||||
|
# @param account_id [Integer] The account ID to encode in the token
|
||||||
|
# @return [String, nil] The encoded JWT token or nil if client secret is missing
|
||||||
|
def generate_github_token(account_id)
|
||||||
|
return if client_secret.blank?
|
||||||
|
|
||||||
|
JWT.encode(token_payload(account_id), client_secret, 'HS256')
|
||||||
|
rescue StandardError => e
|
||||||
|
Rails.logger.error("Failed to generate Github token: #{e.message}")
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def token_payload(account_id)
|
||||||
|
{
|
||||||
|
sub: account_id,
|
||||||
|
iat: Time.current.to_i
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Verifies and decodes a Github JWT token
|
||||||
|
#
|
||||||
|
# @param token [String] The JWT token to verify
|
||||||
|
# @return [Integer, nil] The account ID from the token or nil if invalid
|
||||||
|
def verify_github_token(token)
|
||||||
|
return if token.blank? || client_secret.blank?
|
||||||
|
|
||||||
|
decode_token(token, client_secret)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def client_secret
|
||||||
|
@client_secret ||= GlobalConfigService.load('GITHUB_CLIENT_SECRET', nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def decode_token(token, secret)
|
||||||
|
JWT.decode(token, secret, true, {
|
||||||
|
algorithm: 'HS256',
|
||||||
|
verify_expiration: true
|
||||||
|
}).first['sub']
|
||||||
|
rescue StandardError => e
|
||||||
|
Rails.logger.error("Unexpected error verifying Github token: #{e.message}")
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
class Integrations::App
|
class Integrations::App
|
||||||
include Linear::IntegrationHelper
|
include Linear::IntegrationHelper
|
||||||
|
include Github::IntegrationHelper
|
||||||
attr_accessor :params
|
attr_accessor :params
|
||||||
|
|
||||||
def initialize(params)
|
def initialize(params)
|
||||||
@@ -29,7 +30,12 @@ class Integrations::App
|
|||||||
# There is no way to get the account_id from the linear callback
|
# There is no way to get the account_id from the linear callback
|
||||||
# so we are using the generate_linear_token method to generate a token and encode it in the state parameter
|
# so we are using the generate_linear_token method to generate a token and encode it in the state parameter
|
||||||
def encode_state
|
def encode_state
|
||||||
|
case params[:id]
|
||||||
|
when 'linear'
|
||||||
generate_linear_token(Current.account.id)
|
generate_linear_token(Current.account.id)
|
||||||
|
when 'github'
|
||||||
|
generate_github_token(Current.account.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def action
|
def action
|
||||||
@@ -38,6 +44,8 @@ class Integrations::App
|
|||||||
"#{params[:action]}&client_id=#{ENV.fetch('SLACK_CLIENT_ID', nil)}&redirect_uri=#{self.class.slack_integration_url}"
|
"#{params[:action]}&client_id=#{ENV.fetch('SLACK_CLIENT_ID', nil)}&redirect_uri=#{self.class.slack_integration_url}"
|
||||||
when 'linear'
|
when 'linear'
|
||||||
build_linear_action
|
build_linear_action
|
||||||
|
when 'github'
|
||||||
|
build_github_action
|
||||||
else
|
else
|
||||||
params[:action]
|
params[:action]
|
||||||
end
|
end
|
||||||
@@ -70,6 +78,17 @@ class Integrations::App
|
|||||||
].join('&')
|
].join('&')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_github_action
|
||||||
|
app_id = GlobalConfigService.load('GITHUB_CLIENT_ID', nil)
|
||||||
|
[
|
||||||
|
"#{params[:action]}?response_type=code",
|
||||||
|
"client_id=#{app_id}",
|
||||||
|
"redirect_uri=#{self.class.github_integration_url}",
|
||||||
|
"state=#{encode_state}",
|
||||||
|
'scope=repo'
|
||||||
|
].join('&')
|
||||||
|
end
|
||||||
|
|
||||||
def enabled?(account)
|
def enabled?(account)
|
||||||
case params[:id]
|
case params[:id]
|
||||||
when 'webhook'
|
when 'webhook'
|
||||||
@@ -93,6 +112,10 @@ class Integrations::App
|
|||||||
"#{ENV.fetch('FRONTEND_URL', nil)}/linear/callback"
|
"#{ENV.fetch('FRONTEND_URL', nil)}/linear/callback"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.github_integration_url
|
||||||
|
"#{ENV.fetch('FRONTEND_URL', nil)}/github/callback"
|
||||||
|
end
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def apps
|
def apps
|
||||||
Hashie::Mash.new(APPS_CONFIG)
|
Hashie::Mash.new(APPS_CONFIG)
|
||||||
|
|||||||
@@ -190,6 +190,6 @@ github:
|
|||||||
id: github
|
id: github
|
||||||
logo: github.png
|
logo: github.png
|
||||||
i18n_key: github
|
i18n_key: github
|
||||||
action: /github
|
action: https://github.com/login/oauth/authorize
|
||||||
hook_type: account
|
hook_type: account
|
||||||
allow_multiple_hooks: false
|
allow_multiple_hooks: false
|
||||||
|
|||||||
@@ -479,6 +479,10 @@ Rails.application.routes.draw do
|
|||||||
resources :delivery_status, only: [:create]
|
resources :delivery_status, only: [:create]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
namespace :github do
|
||||||
|
get :callback, to: 'callbacks#show'
|
||||||
|
end
|
||||||
|
|
||||||
get 'microsoft/callback', to: 'microsoft/callbacks#show'
|
get 'microsoft/callback', to: 'microsoft/callbacks#show'
|
||||||
get 'google/callback', to: 'google/callbacks#show'
|
get 'google/callback', to: 'google/callbacks#show'
|
||||||
get 'instagram/callback', to: 'instagram/callbacks#show'
|
get 'instagram/callback', to: 'instagram/callbacks#show'
|
||||||
|
|||||||
Reference in New Issue
Block a user