Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
bnema committed Sep 3, 2024
2 parents aaa6632 + 5637d74 commit b849f6c
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 24 deletions.
113 changes: 113 additions & 0 deletions internal/cli/auth/device_flow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package auth

import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"

"github.com/bnema/gordon/internal/common"
)

func DeviceFlowAuth(config *common.Config) error {
fmt.Println("Starting device flow authentication...")

// Request device code
deviceCode, err := requestDeviceCode(config)
if err != nil {
return fmt.Errorf("error requesting device code: %w", err)
}

fmt.Printf("Please go to %v and enter the code: %v\n", deviceCode["verification_uri"], deviceCode["user_code"])

// Poll for access token
interval := 5 // Default to 5 seconds if not provided
if i, ok := deviceCode["interval"].(float64); ok {
interval = int(i)
}

token, err := pollForAccessToken(config, deviceCode["device_code"].(string), interval)
if err != nil {
return fmt.Errorf("error polling for access token: %w", err)
}

// Save the token
config.SetToken(token)

fmt.Println("Authentication successful!")
return nil
}

func requestDeviceCode(config *common.Config) (map[string]interface{}, error) {
resp, err := http.PostForm(config.GetBackendURL()+"/api/device/code", url.Values{})
if err != nil {
return nil, fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %w", err)
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("server returned non-200 status code: %d. Body: %s", resp.StatusCode, string(body))
}

var deviceCode map[string]interface{}
if err := json.Unmarshal(body, &deviceCode); err != nil {
return nil, fmt.Errorf("error parsing JSON response: %w", err)
}

// Check for required fields
requiredFields := []string{"verification_uri", "user_code", "device_code", "interval"}
for _, field := range requiredFields {
if _, ok := deviceCode[field]; !ok {
return nil, fmt.Errorf("missing required field in response: %s", field)
}
}

return deviceCode, nil
}

func pollForAccessToken(config *common.Config, deviceCode string, interval int) (string, error) {
maxAttempts := 60 // Maximum number of attempts (5 minutes with 5-second intervals)
for attempt := 0; attempt < maxAttempts; attempt++ {
time.Sleep(time.Duration(interval) * time.Second)

resp, err := http.PostForm(config.GetBackendURL()+"/api/device/token", url.Values{
"device_code": {deviceCode},
})
if err != nil {
continue
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
continue
}

var tokenResp map[string]interface{}
if err := json.Unmarshal(body, &tokenResp); err != nil {
continue
}

if accessToken, ok := tokenResp["access_token"].(string); ok && accessToken != "" {
return accessToken, nil
}

if tokenResp["error"] == "authorization_pending" {
// Optionally, you can print a dot to show progress without cluttering the output
fmt.Print(".")
continue
}

// If we get here, there's an unexpected response but we can continue to try
// fmt.Printf("Unexpected response from server (attempt %d): %v\n", attempt+1, tokenResp)
}

return "", fmt.Errorf("max attempts reached, failed to obtain access token")
}
2 changes: 1 addition & 1 deletion internal/cli/cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func NewDeployCommand(a *cli.App) *cobra.Command {
log.Error("Deployment failed",
"status_code", deployErr.StatusCode,
"message", deployErr.Message,
"raw_response", deployErr.RawResponse)
)
} else {
log.Error("Deployment failed", "error", err)
}
Expand Down
17 changes: 10 additions & 7 deletions internal/cli/handler/fieldcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/bnema/gordon/internal/cli"
"github.com/bnema/gordon/internal/cli/auth"
"github.com/bnema/gordon/internal/common"
)

Expand Down Expand Up @@ -31,15 +32,17 @@ func FieldCheck(a *cli.App) error {
return err
}

wasTokenEmpty, err := checkAndSetField(&a.Config.General.Token,
"Enter the token (check your backend config.yml):",
"Token is not set in config.yml")
if err != nil {
return err
if a.Config.General.Token == "" {
fmt.Println("Token is not set. Starting OAuth device flow...")
err := auth.DeviceFlowAuth(&a.Config)
if err != nil {
fmt.Printf("Device flow authentication error: %v\n", err)
return fmt.Errorf("failed to complete device flow authentication: %w", err)
}
}

// Save config if one of the fields was empty
if wasBackendURLEmpty || wasTokenEmpty {
// Save config if BackendURL was empty or if a new token was set
if wasBackendURLEmpty || a.Config.General.Token != "" {
err = a.Config.SaveConfig()
if err != nil {
return err
Expand Down
3 changes: 2 additions & 1 deletion internal/cli/handler/pingrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ func PerformPingRequest(a *cli.App) (PingResponse, error) {
defer resp.Http.Body.Close()

if resp.StatusCode != http.StatusOK {
return PingResponse{}, fmt.Errorf("expected status code 200, got %d", resp.StatusCode)
// Return the error message from the server
return PingResponse{}, fmt.Errorf("server returned status %d: %s", resp.StatusCode, string(resp.Body))
}

// Unmarshal the JSON response into the PingResponse struct
Expand Down
8 changes: 8 additions & 0 deletions internal/common/initconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,11 @@ func (config *Config) GetVersion() string {
func (config *Config) GetStorageDir() string {
return config.General.StorageDir
}

func (c *Config) GetBackendURL() string {
return c.Http.BackendURL
}

func (c *Config) SetToken(token string) {
c.General.Token = token
}
48 changes: 48 additions & 0 deletions internal/httpserve/handler/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"time"

"github.com/bnema/gordon/internal/db"
Expand All @@ -20,6 +21,7 @@ import (

var urlToken string

// ClLI :
// compareGordonToken compares the token from the URL query parameter with the one from the config.yml
func CompareGordonToken(c echo.Context, a *server.App) error {
configToken, err := a.Config.GetToken()
Expand Down Expand Up @@ -328,3 +330,49 @@ func githubGetUserDetails(c echo.Context, a *server.App) (userInfo *queries.Gith

return userInfo, nil
}

func DeviceCodeRequest(c echo.Context, a *server.App) error {
proxyURL := a.Config.Build.ProxyURL
resp, err := http.Post(proxyURL+"/github/device/code", "application/json", nil)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return c.JSON(resp.StatusCode, map[string]string{"error": string(body)})
}

var deviceCode map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&deviceCode); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to decode response"})
}

return c.JSON(http.StatusOK, deviceCode)
}

func DeviceTokenRequest(c echo.Context, a *server.App) error {
proxyURL := a.Config.Build.ProxyURL
deviceCode := c.FormValue("device_code")

resp, err := http.PostForm(proxyURL+"/github/device/token", url.Values{
"device_code": {deviceCode},
})
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return c.JSON(resp.StatusCode, map[string]string{"error": string(body)})
}

var tokenResp map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to decode response"})
}

return c.JSON(http.StatusOK, tokenResp)
}
Loading

0 comments on commit b849f6c

Please sign in to comment.