Module: Legion::Extensions::Github::OAuth::Runners::Auth

Includes:
Helpers::Client, Helpers::Lex
Included in:
Client, Runners::Auth
Defined in:
lib/legion/extensions/github/oauth/runners/auth.rb

Constant Summary

Constants included from Helpers::Client

Helpers::Client::CREDENTIAL_RESOLVERS

Constants included from Helpers::TokenCache

Helpers::TokenCache::TOKEN_BUFFER_SECONDS

Instance Method Summary collapse

Methods included from Helpers::Client

#connection, #gh_cli_token_output, #max_fallback_retries, #on_rate_limit, #on_scope_authorized, #on_scope_denied, #resolve_broker_app, #resolve_credential, #resolve_env, #resolve_gh_cli, #resolve_next_credential, #resolve_settings_app, #resolve_settings_delegated, #resolve_settings_pat, #resolve_vault_app, #resolve_vault_delegated, #resolve_vault_pat

Methods included from Helpers::ScopeRegistry

#credential_fingerprint, #invalidate_scope, #mark_rate_limited, #rate_limited?, #register_scope, #scope_status

Methods included from Helpers::TokenCache

#fetch_token, #mark_rate_limited, #rate_limited?, #store_token

Instance Method Details

#authorize_url(client_id:, redirect_uri:, scope:, state:, code_challenge:, code_challenge_method: 'S256') ⇒ Object



25
26
27
28
29
30
31
32
33
34
# File 'lib/legion/extensions/github/oauth/runners/auth.rb', line 25

def authorize_url(client_id:, redirect_uri:, scope:, state:,
                  code_challenge:, code_challenge_method: 'S256', **)
  params = URI.encode_www_form(
    client_id: client_id, redirect_uri: redirect_uri,
    scope: scope, state: state,
    code_challenge: code_challenge,
    code_challenge_method: code_challenge_method
  )
  { result: "https://github.com/login/oauth/authorize?#{params}" }
end

#exchange_code(client_id:, code:, redirect_uri:, code_verifier:, client_secret: nil) ⇒ Object



36
37
38
39
40
41
42
# File 'lib/legion/extensions/github/oauth/runners/auth.rb', line 36

def exchange_code(client_id:, code:, redirect_uri:, code_verifier:, client_secret: nil, **)
  body = { client_id: client_id, code: code,
           redirect_uri: redirect_uri, code_verifier: code_verifier }
  body[:client_secret] = client_secret if client_secret
  response = oauth_connection.post('/login/oauth/access_token', body)
  { result: response.body }
end

#generate_pkceObject



17
18
19
20
21
22
23
# File 'lib/legion/extensions/github/oauth/runners/auth.rb', line 17

def generate_pkce(**)
  verifier = SecureRandom.urlsafe_base64(32)
  challenge = ::Base64.urlsafe_encode64(
    OpenSSL::Digest::SHA256.digest(verifier), padding: false
  )
  { result: { verifier: verifier, challenge: challenge, challenge_method: 'S256' } }
end

#oauth_connection(client_id: nil, client_secret: nil) ⇒ Object



93
94
95
96
97
98
99
100
# File 'lib/legion/extensions/github/oauth/runners/auth.rb', line 93

def oauth_connection(client_id: nil, client_secret: nil, **)
  Faraday.new(url: 'https://github.com') do |conn|
    conn.request :json
    conn.response :json, content_type: /\bjson$/
    conn.headers['Accept'] = 'application/json'
    conn.request :authorization, :basic, client_id, client_secret if client_id && client_secret
  end
end

#poll_device_code(client_id:, device_code:, interval: 5, timeout: 300) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/legion/extensions/github/oauth/runners/auth.rb', line 59

def poll_device_code(client_id:, device_code:, interval: 5, timeout: 300, **)
  deadline = Time.now + timeout
  current_interval = interval

  loop do
    response = oauth_connection.post('/login/oauth/access_token', {
                                       client_id:   client_id,
                                       device_code: device_code,
                                       grant_type:  'urn:ietf:params:oauth:grant-type:device_code'
                                     })
    body = response.body
    return { result: body } if body[:access_token]

    error_key = body[:error]
    case error_key
    when 'authorization_pending'
      return { error: 'timeout', description: "Device code flow timed out after #{timeout}s" } if Time.now > deadline

      sleep(current_interval) unless current_interval.zero?
    when 'slow_down'
      current_interval += 5
      sleep(current_interval) unless current_interval.zero?
    else
      return { error: error_key, description: body[:error_description] }
    end
  end
end

#refresh_token(client_id:, refresh_token:, client_secret: nil) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/legion/extensions/github/oauth/runners/auth.rb', line 44

def refresh_token(client_id:, refresh_token:, client_secret: nil, **)
  body = { client_id: client_id, refresh_token: refresh_token,
           grant_type: 'refresh_token' }
  body[:client_secret] = client_secret if client_secret
  response = oauth_connection.post('/login/oauth/access_token', body)
  { result: response.body }
end

#request_device_code(client_id:, scope: 'repo') ⇒ Object



52
53
54
55
56
57
# File 'lib/legion/extensions/github/oauth/runners/auth.rb', line 52

def request_device_code(client_id:, scope: 'repo', **)
  response = oauth_connection.post('/login/device/code', {
                                     client_id: client_id, scope: scope
                                   })
  { result: response.body }
end

#revoke_token(client_id:, client_secret:, access_token:) ⇒ Object



87
88
89
90
91
# File 'lib/legion/extensions/github/oauth/runners/auth.rb', line 87

def revoke_token(client_id:, client_secret:, access_token:, **)
  conn = oauth_connection(client_id: client_id, client_secret: client_secret)
  response = conn.delete("/applications/#{client_id}/token", { access_token: access_token })
  { result: response.status == 204 }
end