Class: Ace::Git::Secrets::Atoms::ServiceApiClient

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/git/secrets/atoms/service_api_client.rb

Overview

HTTP API client for token revocation services Builds requests for GitHub, Anthropic, OpenAI credential revocation APIs

Constant Summary collapse

DEFAULT_GITHUB_REVOKE_URL =

Default GitHub Credential Revocation API (unauthenticated, rate limited)

"https://api.github.com/credentials/revoke"
DEFAULT_USER_AGENT =

Default user agent for API requests

"ace-git-secrets/#{Ace::Git::Secrets::VERSION}"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(timeout: 30, retry_count: 3, github_api_url: nil, user_agent: nil) ⇒ ServiceApiClient

Returns a new instance of ServiceApiClient.

Parameters:

  • timeout (Integer) (defaults to: 30)

    Request timeout in seconds

  • retry_count (Integer) (defaults to: 3)

    Number of retries

  • github_api_url (String, nil) (defaults to: nil)

    Custom GitHub API URL (for GitHub Enterprise)

  • user_agent (String, nil) (defaults to: nil)

    Custom User-Agent header



26
27
28
29
30
31
32
# File 'lib/ace/git/secrets/atoms/service_api_client.rb', line 26

def initialize(timeout: 30, retry_count: 3, github_api_url: nil, user_agent: nil)
  @timeout = timeout
  @retry_count = retry_count
  @github_api_base_url = github_api_url&.chomp("/") || "https://api.github.com"
  @github_revoke_url = "#{@github_api_base_url}/credentials/revoke"
  @user_agent = user_agent || DEFAULT_USER_AGENT
end

Instance Attribute Details

#github_api_base_urlObject (readonly)

Returns the value of attribute github_api_base_url.



20
21
22
# File 'lib/ace/git/secrets/atoms/service_api_client.rb', line 20

def github_api_base_url
  @github_api_base_url
end

#github_revoke_urlObject (readonly)

Returns the value of attribute github_revoke_url.



20
21
22
# File 'lib/ace/git/secrets/atoms/service_api_client.rb', line 20

def github_revoke_url
  @github_revoke_url
end

#retry_countObject (readonly)

Returns the value of attribute retry_count.



20
21
22
# File 'lib/ace/git/secrets/atoms/service_api_client.rb', line 20

def retry_count
  @retry_count
end

#timeoutObject (readonly)

Returns the value of attribute timeout.



20
21
22
# File 'lib/ace/git/secrets/atoms/service_api_client.rb', line 20

def timeout
  @timeout
end

#user_agentObject (readonly)

Returns the value of attribute user_agent.



20
21
22
# File 'lib/ace/git/secrets/atoms/service_api_client.rb', line 20

def user_agent
  @user_agent
end

Instance Method Details

#build_revocation_request(service, token) ⇒ Hash

Build revocation request for a service

Parameters:

  • service (String)

    Service name (github, anthropic, openai)

  • token (String)

    Token to revoke

Returns:

  • (Hash)

    Request details for manual revocation if API not available



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/ace/git/secrets/atoms/service_api_client.rb', line 76

def build_revocation_request(service, token)
  case service
  when "github"
    {
      method: :post,
      url: github_revoke_url,
      headers: {"Content-Type" => "application/json"},
      body: {credential: token},
      notes: "GitHub tokens can be revoked via API without authentication"
    }
  when "anthropic"
    {
      method: :manual,
      url: "https://console.anthropic.com/settings/keys",
      notes: "Anthropic API keys must be revoked manually via the console"
    }
  when "openai"
    {
      method: :manual,
      url: "https://platform.openai.com/api-keys",
      notes: "OpenAI API keys must be revoked manually via the platform"
    }
  when "aws"
    {
      method: :manual,
      url: "https://console.aws.amazon.com/iam/home#/security_credentials",
      notes: "AWS credentials must be rotated/deleted via IAM console"
    }
  else
    {
      method: :unsupported,
      url: nil,
      notes: "Automatic revocation not supported for #{service}"
    }
  end
end

#github_rate_limitHash

Check API rate limit status for GitHub Uses configured GitHub API base URL (supports GitHub Enterprise)

Returns:

  • (Hash)

    Rate limit info



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/ace/git/secrets/atoms/service_api_client.rb', line 116

def github_rate_limit
  conn = Faraday.new(url: github_api_base_url) do |f|
    f.options.timeout = timeout
    if retry_count > 0
      f.request :retry, max: retry_count
    end
    f.adapter Faraday.default_adapter
  end

  response = conn.get("/rate_limit")

  if response.success?
    data = JSON.parse(response.body)
    resources = data["resources"]["core"]
    {
      limit: resources["limit"],
      remaining: resources["remaining"],
      reset_at: Time.at(resources["reset"])
    }
  else
    {limit: 60, remaining: "unknown", reset_at: nil}
  end
rescue
  {limit: 60, remaining: "unknown", reset_at: nil}
end

#github_revoke_connectionFaraday::Connection

Get cached GitHub revoke connection for bulk operations

Returns:

  • (Faraday::Connection)


68
69
70
# File 'lib/ace/git/secrets/atoms/service_api_client.rb', line 68

def github_revoke_connection
  @github_revoke_connection ||= build_connection(github_revoke_url)
end

#revoke_github_token(token, check_rate_limit: false) ⇒ Hash

Revoke a GitHub token Uses GitHub’s Credential Revocation API (unauthenticated) Connection is reused for bulk revocation efficiency

Parameters:

  • token (String)

    The token to revoke

  • check_rate_limit (Boolean) (defaults to: false)

    Whether to check rate limit before request

Returns:

  • (Hash)

    Result with :success, :message, :response keys



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/ace/git/secrets/atoms/service_api_client.rb', line 40

def revoke_github_token(token, check_rate_limit: false)
  # Optionally check rate limit before attempting revocation
  if check_rate_limit
    rate_info = github_rate_limit
    if rate_info[:remaining].is_a?(Integer) && rate_info[:remaining] == 0
      reset_msg = rate_info[:reset_at] ? " Reset at #{rate_info[:reset_at]}" : ""
      return {
        success: false,
        message: "GitHub API rate limit exceeded.#{reset_msg}",
        response: nil,
        rate_limited: true
      }
    end
  end

  response = github_revoke_connection.post do |req|
    req.headers["Content-Type"] = "application/json"
    req.headers["Accept"] = "application/json"
    req.body = JSON.generate({credential: token})
  end

  parse_github_response(response)
rescue Faraday::Error => e
  {success: false, message: "GitHub API error: #{e.message}", response: nil}
end