Class: Kward::GithubOAuth

Inherits:
Object
  • Object
show all
Defined in:
lib/kward/auth/github_oauth.rb

Constant Summary collapse

DEVICE_CODE_URL =
URI("https://github.com/login/device/code")
TOKEN_URL =
URI("https://github.com/login/oauth/access_token")
COPILOT_TOKEN_URL =
URI("https://api.github.com/copilot_internal/v2/token")
DEFAULT_SCOPE =
"read:user"
DEFAULT_CLIENT_ID =
"Iv1.b507a08c87ecfe98"
COPILOT_HEADERS =
{
  "User-Agent" => "GitHubCopilotChat/0.35.0",
  "Editor-Version" => "vscode/1.107.0",
  "Editor-Plugin-Version" => "copilot-chat/0.35.0",
  "Copilot-Integration-Id" => "vscode-chat"
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(auth_path: GithubOAuth.default_auth_path, config_path: ConfigFiles.config_path) ⇒ GithubOAuth

Returns a new instance of GithubOAuth.



24
25
26
27
# File 'lib/kward/auth/github_oauth.rb', line 24

def initialize(auth_path: GithubOAuth.default_auth_path, config_path: ConfigFiles.config_path)
  @auth_path = File.expand_path(auth_path)
  @config_path = File.expand_path(config_path)
end

Instance Attribute Details

#auth_pathObject (readonly)

Returns the value of attribute auth_path.



22
23
24
# File 'lib/kward/auth/github_oauth.rb', line 22

def auth_path
  @auth_path
end

Class Method Details

.default_auth_pathObject



29
30
31
# File 'lib/kward/auth/github_oauth.rb', line 29

def self.default_auth_path
  File.expand_path(ENV["KWARD_GITHUB_AUTH_PATH"] || "~/.kward/github_auth.json")
end

Instance Method Details

#access_tokenObject



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/kward/auth/github_oauth.rb', line 33

def access_token
  env_token = ENV["COPILOT_GITHUB_TOKEN"].to_s
  return env_token unless env_token.empty?

  auth = load_auth
  return nil unless auth

  token = auth.dig("tokens", "copilot_access_token") || auth.dig("tokens", "access")
  return token if token && !token_expired?(auth)

  github_token = auth.dig("tokens", "github_access_token") || auth.dig("tokens", "refresh") || auth.dig("tokens", "access_token")
  return token unless github_token

  refreshed = refresh_copilot_token(github_token)
  save_auth(tokens: auth.fetch("tokens", {}).merge(refreshed))
  refreshed["copilot_access_token"]
end

#base_urlObject



51
52
53
54
55
56
57
# File 'lib/kward/auth/github_oauth.rb', line 51

def base_url
  token = access_token.to_s
  match = token.match(/proxy-ep=([^;]+)/)
  return "https://#{match[1].sub(/\Aproxy\./, "api.")}" if match

  "https://api.individual.githubcopilot.com"
end

#logged_in?Boolean

Returns:

  • (Boolean)


59
60
61
# File 'lib/kward/auth/github_oauth.rb', line 59

def logged_in?
  !access_token.to_s.empty?
end

#login(prompt:, timeout_seconds: 900) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/kward/auth/github_oauth.rb', line 63

def (prompt:, timeout_seconds: 900)
  device = request_device_code
  prompt.say("GitHub login URL: #{device.fetch("verification_uri")}")
  prompt.say("GitHub device code: #{device.fetch("user_code")}")
  prompt.say("Authorize Kward in your browser, then wait for login to complete.")

  github_tokens = poll_for_token(
    device_code: device.fetch("device_code"),
    interval: positive_integer(device["interval"]) || 5,
    timeout_seconds: timeout_seconds
  )
  copilot_tokens = refresh_copilot_token(github_tokens.fetch("access_token"))
  tokens = github_tokens.merge("github_access_token" => github_tokens.fetch("access_token")).merge(copilot_tokens)
  save_auth(tokens: tokens)
  auth_path
end

#save_auth(tokens: {}) ⇒ Object



80
81
82
83
84
85
86
87
88
89
# File 'lib/kward/auth/github_oauth.rb', line 80

def save_auth(tokens: {})
  data = {
    "auth_mode" => "github_oauth",
    "tokens" => tokens,
    "saved_at" => Time.now.utc.iso8601,
    "expires_at" => expires_at_for(tokens)
  }.compact

  AuthFile.write_json(@auth_path, data)
end