@dcode-dev/dcode-cli 1.0.0
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.
- package/NPM_README.md +78 -0
- package/README.md +341 -0
- package/bin/dcode-bin +0 -0
- package/bin/dcode.js +44 -0
- package/cmd/agent_v2.go +448 -0
- package/cmd/analyze.go +97 -0
- package/cmd/auth.go +338 -0
- package/cmd/compose.go +284 -0
- package/cmd/context.go +111 -0
- package/cmd/edit.go +116 -0
- package/cmd/env.go +10 -0
- package/cmd/fix.go +145 -0
- package/cmd/gemini.go +20 -0
- package/cmd/generate.go +47 -0
- package/cmd/interactive.go +33 -0
- package/cmd/mcp.go +196 -0
- package/cmd/patch.go +19 -0
- package/cmd/providers.go +67 -0
- package/cmd/root.go +41 -0
- package/cmd/search.go +61 -0
- package/cmd/server.go +36 -0
- package/cmd/switch.go +122 -0
- package/cmd/terminal.go +277 -0
- package/go.mod +42 -0
- package/go.sum +86 -0
- package/internal/agent/agent.go +332 -0
- package/internal/agent/parse.go +25 -0
- package/internal/agents/base.go +154 -0
- package/internal/agents/documenter.go +77 -0
- package/internal/agents/generalist.go +266 -0
- package/internal/agents/investigator.go +60 -0
- package/internal/agents/registry.go +34 -0
- package/internal/agents/reviewer.go +67 -0
- package/internal/agents/tester.go +73 -0
- package/internal/ai/client.go +634 -0
- package/internal/ai/tools.go +332 -0
- package/internal/auth/adc.go +108 -0
- package/internal/auth/apikey.go +67 -0
- package/internal/auth/factory.go +145 -0
- package/internal/auth/oauth2.go +227 -0
- package/internal/auth/store.go +216 -0
- package/internal/auth/types.go +79 -0
- package/internal/auth/vertex.go +138 -0
- package/internal/config/config.go +428 -0
- package/internal/config/policy.go +251 -0
- package/internal/context/builder.go +312 -0
- package/internal/detector/detector.go +204 -0
- package/internal/diffutil/diffutil.go +30 -0
- package/internal/fsutil/fsutil.go +35 -0
- package/internal/mcp/client.go +314 -0
- package/internal/mcp/manager.go +221 -0
- package/internal/policy/policy.go +89 -0
- package/internal/prompt/interactive.go +338 -0
- package/internal/registry/agent.go +201 -0
- package/internal/registry/tool.go +181 -0
- package/internal/scheduler/scheduler.go +250 -0
- package/internal/server/server.go +167 -0
- package/internal/tools/file.go +183 -0
- package/internal/tools/filesystem.go +286 -0
- package/internal/tools/git.go +355 -0
- package/internal/tools/memory.go +269 -0
- package/internal/tools/registry.go +49 -0
- package/internal/tools/search.go +230 -0
- package/internal/tools/shell.go +84 -0
- package/internal/websearch/search.go +40 -0
- package/internal/websearch/tavily.go +79 -0
- package/main.go +19 -0
- package/package.json +57 -0
- package/scripts/install.js +59 -0
- package/scripts/uninstall.js +28 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
package auth
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"net/http"
|
|
7
|
+
"time"
|
|
8
|
+
|
|
9
|
+
"golang.org/x/oauth2"
|
|
10
|
+
"golang.org/x/oauth2/google"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
// OAuth2Auth implements OAuth2 authentication
|
|
14
|
+
type OAuth2Auth struct {
|
|
15
|
+
provider string
|
|
16
|
+
config *oauth2.Config
|
|
17
|
+
token *oauth2.Token
|
|
18
|
+
store CredentialStore
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// NewOAuth2Auth creates a new OAuth2 authenticator
|
|
22
|
+
func NewOAuth2Auth(provider string, clientID, clientSecret string, scopes []string, store CredentialStore) *OAuth2Auth {
|
|
23
|
+
config := &oauth2.Config{
|
|
24
|
+
ClientID: clientID,
|
|
25
|
+
ClientSecret: clientSecret,
|
|
26
|
+
Scopes: scopes,
|
|
27
|
+
Endpoint: google.Endpoint,
|
|
28
|
+
RedirectURL: "http://localhost:8085/callback",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return &OAuth2Auth{
|
|
32
|
+
provider: provider,
|
|
33
|
+
config: config,
|
|
34
|
+
store: store,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// StartAuthFlow initiates the OAuth2 flow
|
|
39
|
+
func (a *OAuth2Auth) StartAuthFlow(ctx context.Context) (string, error) {
|
|
40
|
+
// Generate auth URL
|
|
41
|
+
authURL := a.config.AuthCodeURL("state", oauth2.AccessTypeOffline)
|
|
42
|
+
return authURL, nil
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// CompleteAuthFlow completes the OAuth2 flow with the authorization code
|
|
46
|
+
func (a *OAuth2Auth) CompleteAuthFlow(ctx context.Context, code string) error {
|
|
47
|
+
// Exchange code for token
|
|
48
|
+
token, err := a.config.Exchange(ctx, code)
|
|
49
|
+
if err != nil {
|
|
50
|
+
return fmt.Errorf("failed to exchange code: %w", err)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
a.token = token
|
|
54
|
+
|
|
55
|
+
// Store credentials
|
|
56
|
+
if a.store != nil {
|
|
57
|
+
creds := &Credentials{
|
|
58
|
+
Type: AuthTypeOAuth2,
|
|
59
|
+
Provider: a.provider,
|
|
60
|
+
Token: token.AccessToken,
|
|
61
|
+
RefreshToken: token.RefreshToken,
|
|
62
|
+
ExpiresAt: token.Expiry,
|
|
63
|
+
Metadata: make(map[string]string),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if err := a.store.Save(a.provider, creds); err != nil {
|
|
67
|
+
return fmt.Errorf("failed to store credentials: %w", err)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return nil
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// GetToken returns a valid access token, refreshing if necessary
|
|
75
|
+
func (a *OAuth2Auth) GetToken(ctx context.Context) (string, error) {
|
|
76
|
+
// Load token from store if not in memory
|
|
77
|
+
if a.token == nil {
|
|
78
|
+
if err := a.loadToken(); err != nil {
|
|
79
|
+
return "", fmt.Errorf("no valid token, please login: %w", err)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check if token needs refresh
|
|
84
|
+
if a.token.Expiry.Before(time.Now().Add(5 * time.Minute)) {
|
|
85
|
+
if err := a.Refresh(ctx); err != nil {
|
|
86
|
+
return "", fmt.Errorf("failed to refresh token: %w", err)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return a.token.AccessToken, nil
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Refresh refreshes the access token
|
|
94
|
+
func (a *OAuth2Auth) Refresh(ctx context.Context) error {
|
|
95
|
+
if a.token == nil || a.token.RefreshToken == "" {
|
|
96
|
+
return fmt.Errorf("no refresh token available")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Create token source and get fresh token
|
|
100
|
+
tokenSource := a.config.TokenSource(ctx, a.token)
|
|
101
|
+
newToken, err := tokenSource.Token()
|
|
102
|
+
if err != nil {
|
|
103
|
+
return fmt.Errorf("failed to refresh token: %w", err)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
a.token = newToken
|
|
107
|
+
|
|
108
|
+
// Update stored credentials
|
|
109
|
+
if a.store != nil {
|
|
110
|
+
creds := &Credentials{
|
|
111
|
+
Type: AuthTypeOAuth2,
|
|
112
|
+
Provider: a.provider,
|
|
113
|
+
Token: newToken.AccessToken,
|
|
114
|
+
RefreshToken: newToken.RefreshToken,
|
|
115
|
+
ExpiresAt: newToken.Expiry,
|
|
116
|
+
Metadata: make(map[string]string),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if err := a.store.Save(a.provider, creds); err != nil {
|
|
120
|
+
return fmt.Errorf("failed to update stored credentials: %w", err)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return nil
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Validate checks if credentials are valid
|
|
128
|
+
func (a *OAuth2Auth) Validate(ctx context.Context) error {
|
|
129
|
+
token, err := a.GetToken(ctx)
|
|
130
|
+
if err != nil {
|
|
131
|
+
return err
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if token == "" {
|
|
135
|
+
return fmt.Errorf("invalid token")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Test token with a simple API call
|
|
139
|
+
client := a.config.Client(ctx, a.token)
|
|
140
|
+
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
|
|
141
|
+
if err != nil {
|
|
142
|
+
return fmt.Errorf("token validation failed: %w", err)
|
|
143
|
+
}
|
|
144
|
+
defer resp.Body.Close()
|
|
145
|
+
|
|
146
|
+
if resp.StatusCode != http.StatusOK {
|
|
147
|
+
return fmt.Errorf("token validation failed with status: %d", resp.StatusCode)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return nil
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// GetType returns the auth type
|
|
154
|
+
func (a *OAuth2Auth) GetType() AuthType {
|
|
155
|
+
return AuthTypeOAuth2
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// GetProvider returns the provider name
|
|
159
|
+
func (a *OAuth2Auth) GetProvider() string {
|
|
160
|
+
return a.provider
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Revoke revokes the OAuth2 token
|
|
164
|
+
func (a *OAuth2Auth) Revoke(ctx context.Context) error {
|
|
165
|
+
if a.token == nil {
|
|
166
|
+
return nil
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Revoke token with Google
|
|
170
|
+
revokeURL := fmt.Sprintf("https://oauth2.googleapis.com/revoke?token=%s", a.token.AccessToken)
|
|
171
|
+
resp, err := http.Post(revokeURL, "application/x-www-form-urlencoded", nil)
|
|
172
|
+
if err != nil {
|
|
173
|
+
return fmt.Errorf("failed to revoke token: %w", err)
|
|
174
|
+
}
|
|
175
|
+
defer resp.Body.Close()
|
|
176
|
+
|
|
177
|
+
// Delete from store
|
|
178
|
+
if a.store != nil {
|
|
179
|
+
if err := a.store.Delete(a.provider); err != nil {
|
|
180
|
+
return fmt.Errorf("failed to delete stored credentials: %w", err)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
a.token = nil
|
|
185
|
+
|
|
186
|
+
return nil
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// loadToken loads token from store
|
|
190
|
+
func (a *OAuth2Auth) loadToken() error {
|
|
191
|
+
if a.store == nil {
|
|
192
|
+
return fmt.Errorf("no credential store available")
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
creds, err := a.store.Load(a.provider)
|
|
196
|
+
if err != nil {
|
|
197
|
+
return err
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
a.token = &oauth2.Token{
|
|
201
|
+
AccessToken: creds.Token,
|
|
202
|
+
RefreshToken: creds.RefreshToken,
|
|
203
|
+
Expiry: creds.ExpiresAt,
|
|
204
|
+
TokenType: "Bearer",
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return nil
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// GetTokenInfo returns token information
|
|
211
|
+
func (a *OAuth2Auth) GetTokenInfo(ctx context.Context) (*TokenInfo, error) {
|
|
212
|
+
if a.token == nil {
|
|
213
|
+
if err := a.loadToken(); err != nil {
|
|
214
|
+
return &TokenInfo{Valid: false, Provider: a.provider}, nil
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
expiresIn := time.Until(a.token.Expiry)
|
|
219
|
+
valid := a.token.Valid()
|
|
220
|
+
|
|
221
|
+
return &TokenInfo{
|
|
222
|
+
Valid: valid,
|
|
223
|
+
ExpiresAt: a.token.Expiry,
|
|
224
|
+
ExpiresIn: expiresIn,
|
|
225
|
+
Provider: a.provider,
|
|
226
|
+
}, nil
|
|
227
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
package auth
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"sync"
|
|
9
|
+
|
|
10
|
+
"github.com/zalando/go-keyring"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const (
|
|
14
|
+
keyringService = "dcode"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// KeyringStore stores credentials in the OS keyring
|
|
18
|
+
type KeyringStore struct {
|
|
19
|
+
mu sync.RWMutex
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// NewKeyringStore creates a new keyring-based credential store
|
|
23
|
+
func NewKeyringStore() *KeyringStore {
|
|
24
|
+
return &KeyringStore{}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Save stores credentials in the keyring
|
|
28
|
+
func (s *KeyringStore) Save(provider string, creds *Credentials) error {
|
|
29
|
+
s.mu.Lock()
|
|
30
|
+
defer s.mu.Unlock()
|
|
31
|
+
|
|
32
|
+
// Marshal credentials to JSON
|
|
33
|
+
data, err := json.Marshal(creds)
|
|
34
|
+
if err != nil {
|
|
35
|
+
return fmt.Errorf("failed to marshal credentials: %w", err)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Store in keyring
|
|
39
|
+
err = keyring.Set(keyringService, provider, string(data))
|
|
40
|
+
if err != nil {
|
|
41
|
+
// Fall back to file-based storage if keyring fails
|
|
42
|
+
return s.saveToFile(provider, creds)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return nil
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Load retrieves credentials from the keyring
|
|
49
|
+
func (s *KeyringStore) Load(provider string) (*Credentials, error) {
|
|
50
|
+
s.mu.RLock()
|
|
51
|
+
defer s.mu.RUnlock()
|
|
52
|
+
|
|
53
|
+
// Get from keyring
|
|
54
|
+
data, err := keyring.Get(keyringService, provider)
|
|
55
|
+
if err != nil {
|
|
56
|
+
// Fall back to file-based storage
|
|
57
|
+
return s.loadFromFile(provider)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Unmarshal credentials
|
|
61
|
+
var creds Credentials
|
|
62
|
+
if err := json.Unmarshal([]byte(data), &creds); err != nil {
|
|
63
|
+
return nil, fmt.Errorf("failed to unmarshal credentials: %w", err)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return &creds, nil
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Delete removes credentials from the keyring
|
|
70
|
+
func (s *KeyringStore) Delete(provider string) error {
|
|
71
|
+
s.mu.Lock()
|
|
72
|
+
defer s.mu.Unlock()
|
|
73
|
+
|
|
74
|
+
// Delete from keyring
|
|
75
|
+
err := keyring.Delete(keyringService, provider)
|
|
76
|
+
if err != nil {
|
|
77
|
+
// Try to delete from file as well
|
|
78
|
+
_ = s.deleteFromFile(provider)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return nil
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// List returns all stored provider names
|
|
85
|
+
func (s *KeyringStore) List() ([]string, error) {
|
|
86
|
+
// Keyring doesn't support listing, so we fall back to file-based listing
|
|
87
|
+
return s.listFromFiles()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// File-based fallback methods
|
|
91
|
+
|
|
92
|
+
func (s *KeyringStore) getCredsDir() (string, error) {
|
|
93
|
+
homeDir, err := os.UserHomeDir()
|
|
94
|
+
if err != nil {
|
|
95
|
+
return "", fmt.Errorf("failed to get home directory: %w", err)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
credsDir := filepath.Join(homeDir, ".dcode", "credentials")
|
|
99
|
+
if err := os.MkdirAll(credsDir, 0700); err != nil {
|
|
100
|
+
return "", fmt.Errorf("failed to create credentials directory: %w", err)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return credsDir, nil
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func (s *KeyringStore) saveToFile(provider string, creds *Credentials) error {
|
|
107
|
+
credsDir, err := s.getCredsDir()
|
|
108
|
+
if err != nil {
|
|
109
|
+
return err
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
data, err := json.MarshalIndent(creds, "", " ")
|
|
113
|
+
if err != nil {
|
|
114
|
+
return fmt.Errorf("failed to marshal credentials: %w", err)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
filePath := filepath.Join(credsDir, provider+".json")
|
|
118
|
+
if err := os.WriteFile(filePath, data, 0600); err != nil {
|
|
119
|
+
return fmt.Errorf("failed to write credentials file: %w", err)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return nil
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func (s *KeyringStore) loadFromFile(provider string) (*Credentials, error) {
|
|
126
|
+
credsDir, err := s.getCredsDir()
|
|
127
|
+
if err != nil {
|
|
128
|
+
return nil, err
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
filePath := filepath.Join(credsDir, provider+".json")
|
|
132
|
+
data, err := os.ReadFile(filePath)
|
|
133
|
+
if err != nil {
|
|
134
|
+
return nil, fmt.Errorf("credentials not found for provider: %s", provider)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
var creds Credentials
|
|
138
|
+
if err := json.Unmarshal(data, &creds); err != nil {
|
|
139
|
+
return nil, fmt.Errorf("failed to unmarshal credentials: %w", err)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return &creds, nil
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
func (s *KeyringStore) deleteFromFile(provider string) error {
|
|
146
|
+
credsDir, err := s.getCredsDir()
|
|
147
|
+
if err != nil {
|
|
148
|
+
return err
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
filePath := filepath.Join(credsDir, provider+".json")
|
|
152
|
+
return os.Remove(filePath)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
func (s *KeyringStore) listFromFiles() ([]string, error) {
|
|
156
|
+
credsDir, err := s.getCredsDir()
|
|
157
|
+
if err != nil {
|
|
158
|
+
return nil, err
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
entries, err := os.ReadDir(credsDir)
|
|
162
|
+
if err != nil {
|
|
163
|
+
if os.IsNotExist(err) {
|
|
164
|
+
return []string{}, nil
|
|
165
|
+
}
|
|
166
|
+
return nil, err
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
providers := make([]string, 0, len(entries))
|
|
170
|
+
for _, entry := range entries {
|
|
171
|
+
if !entry.IsDir() && filepath.Ext(entry.Name()) == ".json" {
|
|
172
|
+
provider := entry.Name()[:len(entry.Name())-5] // Remove .json
|
|
173
|
+
providers = append(providers, provider)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return providers, nil
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// FileStore is a simple file-based credential store (no keyring)
|
|
181
|
+
type FileStore struct {
|
|
182
|
+
KeyringStore // Reuse keyring store's file methods
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// NewFileStore creates a new file-based credential store
|
|
186
|
+
func NewFileStore() *FileStore {
|
|
187
|
+
return &FileStore{
|
|
188
|
+
KeyringStore: KeyringStore{},
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Save stores credentials in a file
|
|
193
|
+
func (s *FileStore) Save(provider string, creds *Credentials) error {
|
|
194
|
+
s.mu.Lock()
|
|
195
|
+
defer s.mu.Unlock()
|
|
196
|
+
return s.saveToFile(provider, creds)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Load retrieves credentials from a file
|
|
200
|
+
func (s *FileStore) Load(provider string) (*Credentials, error) {
|
|
201
|
+
s.mu.RLock()
|
|
202
|
+
defer s.mu.RUnlock()
|
|
203
|
+
return s.loadFromFile(provider)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Delete removes credentials file
|
|
207
|
+
func (s *FileStore) Delete(provider string) error {
|
|
208
|
+
s.mu.Lock()
|
|
209
|
+
defer s.mu.Unlock()
|
|
210
|
+
return s.deleteFromFile(provider)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// List returns all stored provider names
|
|
214
|
+
func (s *FileStore) List() ([]string, error) {
|
|
215
|
+
return s.listFromFiles()
|
|
216
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
package auth
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"time"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// AuthType represents the type of authentication
|
|
9
|
+
type AuthType string
|
|
10
|
+
|
|
11
|
+
const (
|
|
12
|
+
// AuthTypeAPIKey uses a simple API key
|
|
13
|
+
AuthTypeAPIKey AuthType = "api_key"
|
|
14
|
+
|
|
15
|
+
// AuthTypeOAuth2 uses OAuth2 flow
|
|
16
|
+
AuthTypeOAuth2 AuthType = "oauth2"
|
|
17
|
+
|
|
18
|
+
// AuthTypeADC uses Application Default Credentials
|
|
19
|
+
AuthTypeADC AuthType = "adc"
|
|
20
|
+
|
|
21
|
+
// AuthTypeVertexAI uses Vertex AI authentication
|
|
22
|
+
AuthTypeVertexAI AuthType = "vertex_ai"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
// Credentials represents authentication credentials
|
|
26
|
+
type Credentials struct {
|
|
27
|
+
Type AuthType
|
|
28
|
+
Provider string
|
|
29
|
+
Token string
|
|
30
|
+
RefreshToken string
|
|
31
|
+
ExpiresAt time.Time
|
|
32
|
+
Metadata map[string]string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Authenticator interface for all auth types
|
|
36
|
+
type Authenticator interface {
|
|
37
|
+
// GetToken returns a valid access token
|
|
38
|
+
GetToken(ctx context.Context) (string, error)
|
|
39
|
+
|
|
40
|
+
// Refresh refreshes the token if needed
|
|
41
|
+
Refresh(ctx context.Context) error
|
|
42
|
+
|
|
43
|
+
// Validate checks if credentials are valid
|
|
44
|
+
Validate(ctx context.Context) error
|
|
45
|
+
|
|
46
|
+
// GetType returns the auth type
|
|
47
|
+
GetType() AuthType
|
|
48
|
+
|
|
49
|
+
// GetProvider returns the provider name
|
|
50
|
+
GetProvider() string
|
|
51
|
+
|
|
52
|
+
// Revoke revokes/logs out the credentials
|
|
53
|
+
Revoke(ctx context.Context) error
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// CredentialStore interface for storing credentials securely
|
|
57
|
+
type CredentialStore interface {
|
|
58
|
+
// Save stores credentials
|
|
59
|
+
Save(provider string, creds *Credentials) error
|
|
60
|
+
|
|
61
|
+
// Load retrieves credentials
|
|
62
|
+
Load(provider string) (*Credentials, error)
|
|
63
|
+
|
|
64
|
+
// Delete removes credentials
|
|
65
|
+
Delete(provider string) error
|
|
66
|
+
|
|
67
|
+
// List returns all stored provider names
|
|
68
|
+
List() ([]string, error)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// TokenInfo represents token metadata
|
|
72
|
+
type TokenInfo struct {
|
|
73
|
+
Valid bool
|
|
74
|
+
ExpiresAt time.Time
|
|
75
|
+
ExpiresIn time.Duration
|
|
76
|
+
Provider string
|
|
77
|
+
User string
|
|
78
|
+
Scopes []string
|
|
79
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
package auth
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"time"
|
|
7
|
+
|
|
8
|
+
"cloud.google.com/go/compute/metadata"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// VertexAIAuth implements Vertex AI authentication
|
|
12
|
+
type VertexAIAuth struct {
|
|
13
|
+
provider string
|
|
14
|
+
project string
|
|
15
|
+
location string
|
|
16
|
+
apiKey string
|
|
17
|
+
store CredentialStore
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// NewVertexAIAuth creates a new Vertex AI authenticator
|
|
21
|
+
func NewVertexAIAuth(provider, project, location, apiKey string, store CredentialStore) *VertexAIAuth {
|
|
22
|
+
return &VertexAIAuth{
|
|
23
|
+
provider: provider,
|
|
24
|
+
project: project,
|
|
25
|
+
location: location,
|
|
26
|
+
apiKey: apiKey,
|
|
27
|
+
store: store,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// GetToken returns a valid access token for Vertex AI
|
|
32
|
+
func (a *VertexAIAuth) GetToken(ctx context.Context) (string, error) {
|
|
33
|
+
// If API key is provided, use it directly
|
|
34
|
+
if a.apiKey != "" {
|
|
35
|
+
return a.apiKey, nil
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Otherwise, use ADC
|
|
39
|
+
adcAuth := NewADCAuth(a.provider, []string{
|
|
40
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
41
|
+
"https://www.googleapis.com/auth/generative-language",
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return adcAuth.GetToken(ctx)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Refresh refreshes the token if using ADC
|
|
48
|
+
func (a *VertexAIAuth) Refresh(ctx context.Context) error {
|
|
49
|
+
if a.apiKey != "" {
|
|
50
|
+
return nil // API key doesn't need refresh
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ADC handles refresh automatically
|
|
54
|
+
return nil
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Validate checks if Vertex AI credentials are valid
|
|
58
|
+
func (a *VertexAIAuth) Validate(ctx context.Context) error {
|
|
59
|
+
if a.project == "" {
|
|
60
|
+
return fmt.Errorf("project ID is required for Vertex AI")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if a.location == "" {
|
|
64
|
+
a.location = "us-central1" // Default location
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Try to get token
|
|
68
|
+
_, err := a.GetToken(ctx)
|
|
69
|
+
if err != nil {
|
|
70
|
+
return fmt.Errorf("failed to get Vertex AI token: %w", err)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// For Vertex AI, just check that we can get a token
|
|
74
|
+
// Full validation would require actual API calls which we skip for now
|
|
75
|
+
return nil
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// GetType returns the auth type
|
|
79
|
+
func (a *VertexAIAuth) GetType() AuthType {
|
|
80
|
+
return AuthTypeVertexAI
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// GetProvider returns the provider name
|
|
84
|
+
func (a *VertexAIAuth) GetProvider() string {
|
|
85
|
+
return a.provider
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Revoke revokes the credentials
|
|
89
|
+
func (a *VertexAIAuth) Revoke(ctx context.Context) error {
|
|
90
|
+
if a.apiKey != "" {
|
|
91
|
+
a.apiKey = ""
|
|
92
|
+
return nil
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// For ADC, we can't revoke
|
|
96
|
+
return fmt.Errorf("cannot revoke Vertex AI ADC credentials")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// GetTokenInfo returns token information
|
|
100
|
+
func (a *VertexAIAuth) GetTokenInfo(ctx context.Context) (*TokenInfo, error) {
|
|
101
|
+
if a.apiKey != "" {
|
|
102
|
+
return &TokenInfo{
|
|
103
|
+
Valid: true,
|
|
104
|
+
Provider: a.provider,
|
|
105
|
+
ExpiresAt: time.Time{}, // API keys don't expire
|
|
106
|
+
}, nil
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Use ADC for token info
|
|
110
|
+
adcAuth := NewADCAuth(a.provider, []string{
|
|
111
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return adcAuth.GetTokenInfo(ctx)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// GetProject returns the GCP project ID
|
|
118
|
+
func (a *VertexAIAuth) GetProject() string {
|
|
119
|
+
if a.project != "" {
|
|
120
|
+
return a.project
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Try to get from metadata server
|
|
124
|
+
if metadata.OnGCE() {
|
|
125
|
+
project, _ := metadata.ProjectID()
|
|
126
|
+
return project
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return ""
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// GetLocation returns the GCP location
|
|
133
|
+
func (a *VertexAIAuth) GetLocation() string {
|
|
134
|
+
if a.location != "" {
|
|
135
|
+
return a.location
|
|
136
|
+
}
|
|
137
|
+
return "us-central1" // Default
|
|
138
|
+
}
|