lex-github 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/CLAUDE.md +50 -17
- data/lib/legion/extensions/github/cli/auth.rb +2 -2
- data/lib/legion/extensions/github/cli/runner.rb +70 -59
- data/lib/legion/extensions/github/helpers/browser_auth.rb +1 -1
- data/lib/legion/extensions/github/helpers/client.rb +18 -0
- data/lib/legion/extensions/github/oauth/runners/auth.rb +10 -12
- data/lib/legion/extensions/github/runners/auth.rb +126 -0
- data/lib/legion/extensions/github/version.rb +1 -1
- data/lib/legion/extensions/github.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 61ef9dc50273e5c0700962c3864a8d3ae24276621ed29215d35551f52fb4573d
|
|
4
|
+
data.tar.gz: b5a4de4b921f743c86320e2c95c99c2773ebcf56b02df788cf2a62323929ae87
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c06065ce5d893b0a4008c7195155669985c3e44cb4b4eb8817c74e2faa99912fdbadb0a8b74fb49be445e95696293e750f0f945ab05576b08cc5d02ebaa645a9
|
|
7
|
+
data.tar.gz: f3d6915a975c4b35c669b3cf0aaf0d9e443a6333136e019df460638574a7f58362904560cc6c366093a4c797ee2518d2c62f742fd9e2a339a5c194bd06c4b11d
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.3.4] - 2026-04-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `resolve_broker_app` to `CREDENTIAL_RESOLVERS` for Broker integration (Phase 8 Wave 3)
|
|
9
|
+
- Stable `installation_id` fingerprint for consistent credential caching across GitHub App installations
|
|
10
|
+
|
|
11
|
+
## [0.3.3] - 2026-03-31
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- CLI runner output: `status` and `login` commands now print JSON results to stdout
|
|
15
|
+
- CLI runner errors print to stderr via `warn`
|
|
16
|
+
|
|
5
17
|
## [0.3.2] - 2026-03-31
|
|
6
18
|
|
|
7
19
|
### Added
|
data/CLAUDE.md
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
## Purpose
|
|
8
8
|
|
|
9
|
-
Legion Extension that connects LegionIO to GitHub. Provides runners for interacting with the GitHub REST API covering repositories, issues, pull requests, users, organizations, gists, search, labels, comments, commits, branches, file contents, GitHub App authentication, OAuth delegated auth, and
|
|
9
|
+
Legion Extension that connects LegionIO to GitHub. Provides runners for interacting with the GitHub REST API covering repositories, issues, pull requests, users, organizations, gists, search, labels, comments, commits, branches, file contents, Actions workflows, checks, releases, deployments, repository webhooks, GitHub App authentication, OAuth delegated auth, and credential storage.
|
|
10
10
|
|
|
11
11
|
**GitHub**: https://github.com/LegionIO/lex-github
|
|
12
12
|
**License**: MIT
|
|
13
|
-
**Version**: 0.3.
|
|
13
|
+
**Version**: 0.3.3
|
|
14
14
|
|
|
15
15
|
## Architecture
|
|
16
16
|
|
|
@@ -28,21 +28,45 @@ Legion::Extensions::Github
|
|
|
28
28
|
│ ├── Comments # CRUD issue/PR comments
|
|
29
29
|
│ ├── Commits # List, get, compare commits
|
|
30
30
|
│ ├── Branches # Create branches via Git Data API
|
|
31
|
-
│
|
|
31
|
+
│ ├── Contents # Commit multiple files via Git Data API
|
|
32
|
+
│ ├── Actions # Workflows, runs, jobs, artifacts, logs
|
|
33
|
+
│ ├── Checks # Check runs, check suites, annotations
|
|
34
|
+
│ ├── Releases # CRUD releases, release assets
|
|
35
|
+
│ ├── Deployments # CRUD deployments and deployment statuses
|
|
36
|
+
│ ├── RepositoryWebhooks # CRUD repo webhooks, ping, test, deliveries
|
|
37
|
+
│ └── Auth # Composite runner: delegates to App, CredentialStore, OAuth auth modules
|
|
32
38
|
├── App/
|
|
33
|
-
│
|
|
34
|
-
│
|
|
35
|
-
│
|
|
36
|
-
│
|
|
37
|
-
│
|
|
39
|
+
│ ├── Runners/
|
|
40
|
+
│ │ ├── Auth # JWT generation, installation token exchange, list/get installations
|
|
41
|
+
│ │ ├── Webhooks # HMAC signature verification, event parsing
|
|
42
|
+
│ │ ├── Manifest # GitHub App manifest flow (generate, exchange code, manifest URL)
|
|
43
|
+
│ │ ├── Installations # Full installation management (list repos, suspend, delete)
|
|
44
|
+
│ │ └── CredentialStore # Store app credentials and OAuth tokens in Vault
|
|
45
|
+
│ ├── Actors/
|
|
46
|
+
│ │ ├── TokenRefresh # Periodic App installation token refresh
|
|
47
|
+
│ │ └── WebhookPoller # Polls GitHub webhook deliveries
|
|
48
|
+
│ └── Transport/ # AMQP transport (exchanges/queues/messages)
|
|
38
49
|
├── OAuth/
|
|
39
|
-
│
|
|
40
|
-
│
|
|
50
|
+
│ ├── Runners/
|
|
51
|
+
│ │ └── Auth # PKCE + Authorization Code, device code, refresh, revoke
|
|
52
|
+
│ ├── Actors/
|
|
53
|
+
│ │ └── TokenRefresh # Periodic OAuth delegated token refresh
|
|
54
|
+
│ └── Transport/ # AMQP transport (exchanges/queues)
|
|
55
|
+
├── Middleware/
|
|
56
|
+
│ ├── RateLimit # Tracks rate-limit headers, skips exhausted credentials
|
|
57
|
+
│ ├── ScopeProbe # Detects scope-denied 403s for specific owner/repo
|
|
58
|
+
│ └── CredentialFallback # Triggers fallback to next credential source on auth failure
|
|
41
59
|
├── Helpers/
|
|
42
60
|
│ ├── Client # 8-source scope-aware credential resolution chain + Faraday builder
|
|
43
61
|
│ ├── Cache # Two-tier read-through/write-through API response caching
|
|
44
62
|
│ ├── TokenCache # Token lifecycle management (store, fetch, expiry, rate limits)
|
|
45
|
-
│
|
|
63
|
+
│ ├── ScopeRegistry # Credential-to-scope authorization cache (org/repo level)
|
|
64
|
+
│ ├── BrowserAuth # Delegated OAuth orchestrator (PKCE, headless detection, browser launch)
|
|
65
|
+
│ └── CallbackServer # Ephemeral TCP server for OAuth redirect callback
|
|
66
|
+
├── CLI/
|
|
67
|
+
│ ├── Auth # `legion lex exec github auth login/status`
|
|
68
|
+
│ ├── App # `legion lex exec github app setup/complete_setup`
|
|
69
|
+
│ └── Runner # CLI dispatch registration
|
|
46
70
|
└── Client # Standalone client class (includes all runners)
|
|
47
71
|
```
|
|
48
72
|
|
|
@@ -63,12 +87,16 @@ Rate-limited credentials are skipped. Scope-denied credentials (for a given owne
|
|
|
63
87
|
|
|
64
88
|
| Gem | Purpose |
|
|
65
89
|
|-----|---------|
|
|
66
|
-
| `faraday` | HTTP client for GitHub REST API |
|
|
67
|
-
| `jwt` (
|
|
90
|
+
| `faraday` (>= 2.0) | HTTP client for GitHub REST API |
|
|
91
|
+
| `jwt` (>= 2.7) | RS256 JWT generation for GitHub App authentication |
|
|
68
92
|
| `base64` (>= 0.1) | PKCE code challenge computation |
|
|
69
|
-
| `legion-cache` | Two-tier caching (global Redis + local in-memory) |
|
|
70
|
-
| `legion-crypt` | Vault secret resolution for credentials |
|
|
71
|
-
| `legion-
|
|
93
|
+
| `legion-cache` (>= 1.3.11) | Two-tier caching (global Redis + local in-memory) |
|
|
94
|
+
| `legion-crypt` (>= 1.4.9) | Vault secret resolution for credentials |
|
|
95
|
+
| `legion-data` (>= 1.4.17) | Data persistence |
|
|
96
|
+
| `legion-json` (>= 1.2.1) | JSON serialization |
|
|
97
|
+
| `legion-logging` (>= 1.3.2) | Logging |
|
|
98
|
+
| `legion-settings` (>= 1.3.14) | Settings-based credential resolution |
|
|
99
|
+
| `legion-transport` (>= 1.3.9) | AMQP transport for actors |
|
|
72
100
|
|
|
73
101
|
## Key Files
|
|
74
102
|
|
|
@@ -80,15 +108,20 @@ Rate-limited credentials are skipped. Scope-denied credentials (for a given owne
|
|
|
80
108
|
| `lib/legion/extensions/github/helpers/cache.rb` | Two-tier API response caching |
|
|
81
109
|
| `lib/legion/extensions/github/helpers/token_cache.rb` | Token lifecycle + rate limit tracking |
|
|
82
110
|
| `lib/legion/extensions/github/helpers/scope_registry.rb` | Credential-to-scope authorization cache |
|
|
111
|
+
| `lib/legion/extensions/github/helpers/browser_auth.rb` | OAuth PKCE browser launch + headless detection |
|
|
112
|
+
| `lib/legion/extensions/github/helpers/callback_server.rb` | Ephemeral TCP server for OAuth redirect |
|
|
83
113
|
| `lib/legion/extensions/github/app/runners/auth.rb` | JWT generation, installation tokens |
|
|
84
114
|
| `lib/legion/extensions/github/app/runners/webhooks.rb` | Webhook signature verification, event parsing |
|
|
85
115
|
| `lib/legion/extensions/github/app/runners/manifest.rb` | GitHub App manifest registration flow |
|
|
86
116
|
| `lib/legion/extensions/github/app/runners/installations.rb` | Installation management |
|
|
117
|
+
| `lib/legion/extensions/github/app/runners/credential_store.rb` | Store app/OAuth credentials in Vault |
|
|
87
118
|
| `lib/legion/extensions/github/oauth/runners/auth.rb` | OAuth PKCE, device code, token refresh/revoke |
|
|
119
|
+
| `lib/legion/extensions/github/runners/auth.rb` | Composite auth runner (delegates to app + oauth + credential_store) |
|
|
120
|
+
| `lib/lex/github.rb` | Redirect shim for `require 'lex/github'` |
|
|
88
121
|
|
|
89
122
|
## Testing
|
|
90
123
|
|
|
91
|
-
|
|
124
|
+
234 specs across 38 spec files.
|
|
92
125
|
|
|
93
126
|
```bash
|
|
94
127
|
bundle install
|
|
@@ -15,9 +15,9 @@ module Legion
|
|
|
15
15
|
csec = client_secret || settings_client_secret
|
|
16
16
|
sc = scopes || settings_scopes
|
|
17
17
|
|
|
18
|
-
unless cid
|
|
18
|
+
unless cid
|
|
19
19
|
return { error: 'missing_config',
|
|
20
|
-
description: 'Set github.oauth.client_id or github.app.client_id
|
|
20
|
+
description: 'Set github.oauth.client_id or github.app.client_id in settings' }
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
browser = Helpers::BrowserAuth.new(client_id: cid, client_secret: csec, scopes: sc)
|
|
@@ -1,90 +1,101 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
4
|
-
require '
|
|
5
|
-
require '
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'rbconfig'
|
|
6
7
|
|
|
7
8
|
module Legion
|
|
8
9
|
module Extensions
|
|
9
10
|
module Github
|
|
10
11
|
module CLI
|
|
11
|
-
|
|
12
|
-
include Legion::Logging::Helper if defined?(Legion::Logging::Helper)
|
|
13
|
-
include Github::CLI::Auth
|
|
14
|
-
include Github::App::Runners::CredentialStore
|
|
15
|
-
|
|
16
|
-
def credential_fingerprint(auth_type:, identifier:)
|
|
17
|
-
"#{auth_type}:#{identifier}"
|
|
18
|
-
end
|
|
12
|
+
DAEMON_URL = ENV.fetch('LEGION_API_URL', 'http://127.0.0.1:4567')
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
module DaemonApi
|
|
15
|
+
private
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
def api_post(path, body = {})
|
|
18
|
+
uri = URI("#{DAEMON_URL}#{path}")
|
|
19
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
20
|
+
http.open_timeout = 5
|
|
21
|
+
http.read_timeout = 30
|
|
22
|
+
request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
|
|
23
|
+
request.body = ::JSON.generate(body)
|
|
24
|
+
parse_response(http.request(request))
|
|
25
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET => _e
|
|
26
|
+
{ error: 'daemon_unavailable', description: "Legion daemon not running at #{DAEMON_URL}. Start it with: legionio start" }
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
def api_get(path)
|
|
30
|
+
uri = URI("#{DAEMON_URL}#{path}")
|
|
31
|
+
parse_response(Net::HTTP.get_response(uri))
|
|
32
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET => _e
|
|
33
|
+
{ error: 'daemon_unavailable', description: "Legion daemon not running at #{DAEMON_URL}. Start it with: legionio start" }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
rescue
|
|
39
|
-
|
|
40
|
-
false
|
|
36
|
+
def parse_response(response)
|
|
37
|
+
::JSON.parse(response.body, symbolize_names: true)
|
|
38
|
+
rescue ::JSON::ParserError => _e
|
|
39
|
+
{ error: "http_#{response.code}", description: response.body&.strip }
|
|
41
40
|
end
|
|
42
41
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
def print_json(result)
|
|
43
|
+
if result.is_a?(Hash) && result[:error]
|
|
44
|
+
warn "Error: #{result[:error]}"
|
|
45
|
+
warn " #{result[:description]}" if result[:description]
|
|
46
|
+
else
|
|
47
|
+
puts ::JSON.pretty_generate(result)
|
|
48
|
+
end
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
def
|
|
51
|
-
::
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
def open_browser(url)
|
|
52
|
+
cmd = case RbConfig::CONFIG['host_os']
|
|
53
|
+
when /darwin/ then 'open'
|
|
54
|
+
when /linux/ then 'xdg-open'
|
|
55
|
+
when /mswin|mingw/ then 'start'
|
|
56
|
+
end
|
|
57
|
+
system(cmd, url) if cmd
|
|
55
58
|
end
|
|
59
|
+
end
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
class AuthRunner
|
|
62
|
+
include DaemonApi
|
|
63
|
+
|
|
64
|
+
def status
|
|
65
|
+
print_json(api_post('/api/extensions/github/runners/auth/status'))
|
|
62
66
|
end
|
|
63
67
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
rescue StandardError => e
|
|
67
|
-
log.debug("[lex-github] local_cache_set failed: #{e.message}")
|
|
68
|
-
nil
|
|
68
|
+
def login
|
|
69
|
+
print_json(api_post('/api/extensions/github/runners/auth/login'))
|
|
69
70
|
end
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
class AppRunner
|
|
73
|
-
include
|
|
74
|
-
include Github::CLI::App
|
|
75
|
-
include Github::App::Runners::CredentialStore
|
|
74
|
+
include DaemonApi
|
|
76
75
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
end
|
|
76
|
+
def setup
|
|
77
|
+
result = api_post('/api/extensions/github/cli/app/setup')
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
if result[:error]
|
|
80
|
+
print_json(result)
|
|
81
|
+
return
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
url = result.dig(:data, :manifest_url)
|
|
85
|
+
if url
|
|
86
|
+
warn 'Opening browser to create GitHub App...'
|
|
87
|
+
open_browser(url)
|
|
88
|
+
warn 'Waiting for callback...'
|
|
89
|
+
poll = api_post('/api/extensions/github/cli/app/await_callback',
|
|
90
|
+
{ timeout: 300 })
|
|
91
|
+
print_json(poll)
|
|
92
|
+
else
|
|
93
|
+
print_json(result)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
83
96
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
log.warn("[lex-github] vault_get failed: #{e.message}")
|
|
87
|
-
nil
|
|
97
|
+
def complete_setup
|
|
98
|
+
print_json(api_post('/api/extensions/github/cli/app/complete_setup'))
|
|
88
99
|
end
|
|
89
100
|
end
|
|
90
101
|
end
|
|
@@ -14,7 +14,7 @@ module Legion
|
|
|
14
14
|
|
|
15
15
|
attr_reader :client_id, :client_secret, :scopes
|
|
16
16
|
|
|
17
|
-
def initialize(client_id:, client_secret
|
|
17
|
+
def initialize(client_id:, client_secret: nil, scopes: DEFAULT_SCOPES, auth: nil, **)
|
|
18
18
|
@client_id = client_id
|
|
19
19
|
@client_secret = client_secret
|
|
20
20
|
@scopes = scopes
|
|
@@ -15,6 +15,7 @@ module Legion
|
|
|
15
15
|
|
|
16
16
|
CREDENTIAL_RESOLVERS = %i[
|
|
17
17
|
resolve_vault_delegated resolve_settings_delegated
|
|
18
|
+
resolve_broker_app
|
|
18
19
|
resolve_vault_app resolve_settings_app
|
|
19
20
|
resolve_vault_pat resolve_settings_pat
|
|
20
21
|
resolve_gh_cli resolve_env
|
|
@@ -139,6 +140,23 @@ module Legion
|
|
|
139
140
|
nil
|
|
140
141
|
end
|
|
141
142
|
|
|
143
|
+
def resolve_broker_app
|
|
144
|
+
return nil unless defined?(Legion::Identity::Broker)
|
|
145
|
+
|
|
146
|
+
token = Legion::Identity::Broker.token_for(:github)
|
|
147
|
+
return nil unless token
|
|
148
|
+
|
|
149
|
+
lease = Legion::Identity::Broker.lease_for(:github)
|
|
150
|
+
installation_id = lease&.metadata&.dig(:installation_id) || 'unknown'
|
|
151
|
+
fp = credential_fingerprint(auth_type: :app_installation,
|
|
152
|
+
identifier: "broker_app_#{installation_id}")
|
|
153
|
+
{ token: token, auth_type: :app_installation,
|
|
154
|
+
metadata: { source: :broker, credential_type: :installation_token,
|
|
155
|
+
credential_fingerprint: fp } }
|
|
156
|
+
rescue StandardError => _e
|
|
157
|
+
nil
|
|
158
|
+
end
|
|
159
|
+
|
|
142
160
|
def resolve_vault_app
|
|
143
161
|
return nil unless defined?(Legion::Crypt)
|
|
144
162
|
|
|
@@ -33,21 +33,19 @@ module Legion
|
|
|
33
33
|
{ result: "https://github.com/login/oauth/authorize?#{params}" }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def exchange_code(client_id:,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})
|
|
36
|
+
def exchange_code(client_id:, code:, redirect_uri:, code_verifier:, client_secret: nil, **)
|
|
37
|
+
body = { client_id: client_id, code: code,
|
|
38
|
+
redirect_uri: redirect_uri, code_verifier: code_verifier }
|
|
39
|
+
body[:client_secret] = client_secret if client_secret
|
|
40
|
+
response = oauth_connection.post('/login/oauth/access_token', body)
|
|
42
41
|
{ result: response.body }
|
|
43
42
|
end
|
|
44
43
|
|
|
45
|
-
def refresh_token(client_id:,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
})
|
|
44
|
+
def refresh_token(client_id:, refresh_token:, client_secret: nil, **)
|
|
45
|
+
body = { client_id: client_id, refresh_token: refresh_token,
|
|
46
|
+
grant_type: 'refresh_token' }
|
|
47
|
+
body[:client_secret] = client_secret if client_secret
|
|
48
|
+
response = oauth_connection.post('/login/oauth/access_token', body)
|
|
51
49
|
{ result: response.body }
|
|
52
50
|
end
|
|
53
51
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/github/helpers/client'
|
|
4
|
+
require 'legion/extensions/github/helpers/browser_auth'
|
|
5
|
+
require 'legion/extensions/github/app/runners/auth'
|
|
6
|
+
require 'legion/extensions/github/app/runners/credential_store'
|
|
7
|
+
require 'legion/extensions/github/oauth/runners/auth'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module Github
|
|
12
|
+
module Runners
|
|
13
|
+
module Auth
|
|
14
|
+
include Legion::Extensions::Github::Helpers::Client
|
|
15
|
+
include Legion::Extensions::Github::App::Runners::Auth
|
|
16
|
+
include Legion::Extensions::Github::App::Runners::CredentialStore
|
|
17
|
+
include Legion::Extensions::Github::OAuth::Runners::Auth
|
|
18
|
+
|
|
19
|
+
def self.remote_invocable?
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def status(**)
|
|
24
|
+
cred = resolve_credential
|
|
25
|
+
unless cred
|
|
26
|
+
log.warn('[lex-github] auth status: no credential found across all sources')
|
|
27
|
+
return { result: { authenticated: false } }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
log.info("[lex-github] auth status: credential found via #{cred[:auth_type]}")
|
|
31
|
+
|
|
32
|
+
user_info = {}
|
|
33
|
+
scopes = nil
|
|
34
|
+
begin
|
|
35
|
+
response = connection(token: cred[:token]).get('/user')
|
|
36
|
+
user_info = response.body || {}
|
|
37
|
+
headers = response.respond_to?(:headers) ? response.headers : {}
|
|
38
|
+
scopes_header = headers['X-OAuth-Scopes'] || headers['x-oauth-scopes']
|
|
39
|
+
scopes = scopes_header&.split(',')&.map(&:strip)
|
|
40
|
+
log.info("[lex-github] auth status: authenticated as #{user_info['login']} (#{cred[:auth_type]})")
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
log.warn("[lex-github] auth status: credential found but /user request failed: #{e.message}")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
{ result: { authenticated: true, auth_type: cred[:auth_type],
|
|
46
|
+
user: user_info['login'], scopes: scopes } }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def login(client_id: nil, scopes: nil, **)
|
|
50
|
+
cid = client_id || settings_client_id
|
|
51
|
+
unless cid
|
|
52
|
+
log.error('[lex-github] auth login: no client_id configured — set github.app.client_id in settings')
|
|
53
|
+
return { error: 'missing_config', description: 'Set github.app.client_id in settings' }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
log.info("[lex-github] auth login: starting OAuth flow with client_id=#{cid[0..7]}...")
|
|
57
|
+
|
|
58
|
+
sc = scopes || settings_scopes
|
|
59
|
+
browser = Helpers::BrowserAuth.new(client_id: cid, scopes: sc)
|
|
60
|
+
result = browser.authenticate
|
|
61
|
+
|
|
62
|
+
if result[:error]
|
|
63
|
+
log.error("[lex-github] auth login failed: #{result[:error]} — #{result[:description]}")
|
|
64
|
+
return { result: nil, error: result[:error], description: result[:description] }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if result[:result]&.dig('access_token')
|
|
68
|
+
user = begin
|
|
69
|
+
current_user(token: result[:result]['access_token'])
|
|
70
|
+
rescue StandardError => e
|
|
71
|
+
log.warn("[lex-github] auth login: token obtained but /user lookup failed: #{e.message}")
|
|
72
|
+
'default'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
log.info("[lex-github] auth login: authenticated as #{user}")
|
|
76
|
+
|
|
77
|
+
if respond_to?(:store_oauth_token, true)
|
|
78
|
+
store_oauth_token(
|
|
79
|
+
user: user,
|
|
80
|
+
access_token: result[:result]['access_token'],
|
|
81
|
+
refresh_token: result[:result]['refresh_token'],
|
|
82
|
+
expires_in: result[:result]['expires_in']
|
|
83
|
+
)
|
|
84
|
+
log.info("[lex-github] auth login: token stored for user=#{user}")
|
|
85
|
+
else
|
|
86
|
+
log.warn('[lex-github] auth login: store_oauth_token not available — token not persisted')
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
log.warn('[lex-github] auth login: OAuth completed but no access_token in response')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
result
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def installations(**)
|
|
96
|
+
log.info('[lex-github] listing app installations')
|
|
97
|
+
list_installations(**)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def current_user(token:)
|
|
103
|
+
connection(token: token).get('/user').body['login']
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def settings_client_id
|
|
107
|
+
defined?(Legion::Settings) &&
|
|
108
|
+
(Legion::Settings.dig(:github, :oauth, :client_id) ||
|
|
109
|
+
Legion::Settings.dig(:github, :app, :client_id))
|
|
110
|
+
rescue StandardError => _e
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def settings_scopes
|
|
115
|
+
defined?(Legion::Settings) && Legion::Settings.dig(:github, :oauth, :scopes)
|
|
116
|
+
rescue StandardError => _e
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
121
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -35,6 +35,7 @@ require 'legion/extensions/github/runners/actions'
|
|
|
35
35
|
require 'legion/extensions/github/runners/checks'
|
|
36
36
|
require 'legion/extensions/github/runners/releases'
|
|
37
37
|
require 'legion/extensions/github/runners/deployments'
|
|
38
|
+
require 'legion/extensions/github/runners/auth'
|
|
38
39
|
require 'legion/extensions/github/runners/repository_webhooks'
|
|
39
40
|
require 'legion/extensions/github/client'
|
|
40
41
|
require 'legion/extensions/github/cli/runner'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-github
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -203,6 +203,7 @@ files:
|
|
|
203
203
|
- lib/legion/extensions/github/oauth/transport/exchanges/oauth.rb
|
|
204
204
|
- lib/legion/extensions/github/oauth/transport/queues/auth.rb
|
|
205
205
|
- lib/legion/extensions/github/runners/actions.rb
|
|
206
|
+
- lib/legion/extensions/github/runners/auth.rb
|
|
206
207
|
- lib/legion/extensions/github/runners/branches.rb
|
|
207
208
|
- lib/legion/extensions/github/runners/checks.rb
|
|
208
209
|
- lib/legion/extensions/github/runners/comments.rb
|