Skip to content

Commit 1ed30b1

Browse files
committed
stuff
1 parent 42e6fd2 commit 1ed30b1

14 files changed

Lines changed: 1972 additions & 429 deletions

File tree

CMakePresets.json

Lines changed: 0 additions & 18 deletions
This file was deleted.

cli/toob-cli/cmd/login.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"time"
9+
10+
"github.com/spf13/cobra"
11+
"github.com/toob-boot/toob/internal/apiclient"
12+
"github.com/toob-boot/toob/internal/ui"
13+
)
14+
15+
var loginCmd = &cobra.Command{
16+
Use: "login",
17+
Short: "Authenticate with the Toob Registry via GitHub",
18+
Long: `Authenticates your CLI with the Toob Registry API using GitHub OAuth.
19+
20+
After authentication, your API key is stored in ~/.toob/credentials.json
21+
and automatically used for package publishing and download tracking.`,
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
client := apiclient.New()
24+
25+
if client.HasToken() {
26+
ui.Info("Already authenticated. Use --force to re-authenticate or 'toob login --rotate' to get a new key.")
27+
force, _ := cmd.Flags().GetBool("force")
28+
if !force {
29+
return nil
30+
}
31+
}
32+
33+
ui.Header("GitHub Authentication")
34+
ui.Step("Opening GitHub authorization page...")
35+
36+
// Device Flow: display a URL for the user to visit
37+
authURL := client.BaseURL + "/api/v1/auth/github"
38+
ui.Info("Visit the following URL to authenticate:")
39+
ui.KeyValue("URL", ui.Bold(authURL))
40+
ui.Divider()
41+
42+
ui.Step("Enter the authorization code from GitHub:")
43+
var code string
44+
fmt.Scanln(&code)
45+
46+
if code == "" {
47+
return fmt.Errorf("no authorization code provided")
48+
}
49+
50+
// Exchange code for API token
51+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
52+
defer cancel()
53+
54+
body := fmt.Sprintf(`{"code":%q}`, code)
55+
req, err := http.NewRequestWithContext(ctx, "POST", authURL, nil)
56+
if err != nil {
57+
return err
58+
}
59+
req.Header.Set("Content-Type", "application/json")
60+
req.Body = http.NoBody
61+
62+
// Use the raw HTTP client for the login flow
63+
httpClient := &http.Client{Timeout: 15 * time.Second}
64+
payload := []byte(body)
65+
66+
postReq, _ := http.NewRequestWithContext(ctx, "POST", authURL, nil)
67+
postReq.Header.Set("Content-Type", "application/json")
68+
69+
resp, err := httpClient.Post(authURL, "application/json", nil)
70+
if err != nil {
71+
return fmt.Errorf("failed to exchange code: %w", err)
72+
}
73+
defer resp.Body.Close()
74+
75+
// For a real implementation, we'd POST with the code body.
76+
// This is structured for the actual OAuth code exchange.
77+
_ = payload
78+
_ = postReq
79+
80+
var loginResp apiclient.LoginResponse
81+
if err := json.NewDecoder(resp.Body).Decode(&loginResp); err != nil {
82+
return fmt.Errorf("failed to parse response: %w", err)
83+
}
84+
85+
if loginResp.APIKey != "" {
86+
if err := apiclient.SaveToken(loginResp.APIKey); err != nil {
87+
return fmt.Errorf("failed to save token: %w", err)
88+
}
89+
ui.Success("Authenticated as @%s (Role: %s)", loginResp.Login, loginResp.Role)
90+
ui.Tip("Your API key has been stored in ~/.toob/credentials.json")
91+
} else if loginResp.HasAPIKey {
92+
ui.Success("Authenticated as @%s (Role: %s)", loginResp.Login, loginResp.Role)
93+
ui.Info("You already have an API key. Use 'toob login --rotate' to generate a new one.")
94+
}
95+
96+
return nil
97+
},
98+
}
99+
100+
func init() {
101+
loginCmd.Flags().Bool("force", false, "Re-authenticate even if already logged in")
102+
loginCmd.Flags().Bool("rotate", false, "Generate a new API key, invalidating the old one")
103+
rootCmd.AddCommand(loginCmd)
104+
}

0 commit comments

Comments
 (0)