fix: Revoke Linear OAuth token when integration is deleted (#11838)

When users delete the Linear integration from their Chatwoot dashboard,
the access token remains valid in Linear's system. This causes the
integration to still appear as connected in Linear's UI, even though
it's been removed from Chatwoot. Users need to manually disconnect from
Linear's side to fully remove the integration.


https://www.loom.com/share/5c102cbdf02e49bcb7a6fa6d409b531a?sid=0c664250-c867-4fc8-b44d-e1c1165337a7
This commit is contained in:
Muhsin Keloth
2025-07-01 13:31:02 +05:30
committed by GitHub
parent 2573f37d90
commit 14ba73fc63
3 changed files with 27 additions and 0 deletions

View File

@@ -3,6 +3,7 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas
before_action :fetch_hook, only: [:destroy] before_action :fetch_hook, only: [:destroy]
def destroy def destroy
revoke_linear_token
@hook.destroy! @hook.destroy!
head :ok head :ok
end end
@@ -120,4 +121,15 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas
def fetch_hook def fetch_hook
@hook = Integrations::Hook.where(account: Current.account).find_by(app_id: 'linear') @hook = Integrations::Hook.where(account: Current.account).find_by(app_id: 'linear')
end end
def revoke_linear_token
return unless @hook&.access_token
begin
linear_client = Linear.new(@hook.access_token)
linear_client.revoke_token
rescue StandardError => e
Rails.logger.error "Failed to revoke Linear token: #{e.message}"
end
end
end end

View File

@@ -1,5 +1,6 @@
class Linear class Linear
BASE_URL = 'https://api.linear.app/graphql'.freeze BASE_URL = 'https://api.linear.app/graphql'.freeze
REVOKE_URL = 'https://api.linear.app/oauth/revoke'.freeze
PRIORITY_LEVELS = (0..4).to_a PRIORITY_LEVELS = (0..4).to_a
def initialize(access_token) def initialize(access_token)
@@ -86,6 +87,14 @@ class Linear
process_response(response) process_response(response)
end end
def revoke_token
response = HTTParty.post(
REVOKE_URL,
headers: { 'Authorization' => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
)
response.success?
end
private private
def validate_team_and_title(params) def validate_team_and_title(params)

View File

@@ -14,6 +14,12 @@ RSpec.describe 'Linear Integration API', type: :request do
describe 'DELETE /api/v1/accounts/:account_id/integrations/linear' do describe 'DELETE /api/v1/accounts/:account_id/integrations/linear' do
it 'deletes the linear integration' do it 'deletes the linear integration' do
# Stub the HTTP call to Linear's revoke endpoint
allow(HTTParty).to receive(:post).with(
'https://api.linear.app/oauth/revoke',
anything
).and_return(instance_double(HTTParty::Response, success?: true))
delete "/api/v1/accounts/#{account.id}/integrations/linear", delete "/api/v1/accounts/#{account.id}/integrations/linear",
headers: agent.create_new_auth_token, headers: agent.create_new_auth_token,
as: :json as: :json