diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..6a63f5d --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,9 @@ +## Describe the Change + +This PR ..... + +## Checklist + +- [ ] Open Source License list updated? Use `make opensource` to update the list. + +- [ ] License header updated? Use `make license` to update the header. diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 97ea143..b280021 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -60,7 +60,7 @@ jobs: - name: Run golangci-lint uses: golangci/golangci-lint-action@v6.0.1 with: - args: -v + args: --verbose --timeout=2m compile: name: Compile validation diff --git a/cmd/login.go b/cmd/login.go deleted file mode 100644 index f41bbc9..0000000 --- a/cmd/login.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) karl-cardenas-coding -// SPDX-License-Identifier: Apache-2.0 - -package cmd - -import ( - "log/slog" - - "github.com/karl-cardenas-coding/mywhoop/internal" - "github.com/spf13/cobra" -) - -// loginCmd represents the login command -var loginCmd = &cobra.Command{ - Use: "login", - Short: "Authenticate with Whoop API and get an access token", - Long: "Authenticate with Whoop API and get an access token", - RunE: func(cmd *cobra.Command, args []string) error { - return login() - }, -} - -func init() { - rootCmd.AddCommand(loginCmd) -} - -// login authenticates with Whoop API and gets an access token -func login() error { - err := InitLogger(&Configuration) - if err != nil { - return err - } - client := internal.CreateHTTPClient() - - _, err = internal.GetToken(Configuration.Credentials.CredentialsFile, client) - if err != nil { - slog.Error("Error getting access token", "msg", err) - return err - } - return nil -} diff --git a/internal/auth.go b/internal/auth.go index b3b8bff..45a0117 100644 --- a/internal/auth.go +++ b/internal/auth.go @@ -11,7 +11,6 @@ import ( "io" "log/slog" "net/http" - "net/url" "os" "strings" "time" @@ -83,157 +82,6 @@ func RefreshToken(ctx context.Context, auth AuthRequest) (oauth2.Token, error) { } -// GetToken is a function that triggers an Oauth flow that endusers can use to aquire a Whoop autentication token using their Whoop client and secret ID. -// The function logic is mostly copied from https://github.com/marekq/go-whoop with some minor modifications. -func GetToken(tokenFilePath string, client *http.Client) (string, error) { - - // Set accessToken variable - accessToken := "" - clientID := os.Getenv("WHOOP_CLIENT_ID") - clientSecret := os.Getenv("WHOOP_CLIENT_SECRET") - - if clientID == "" || clientSecret == "" { - return "", fmt.Errorf("ClientID and ClientSecret environment variables not set") - - } - - // Set token file path default - if tokenFilePath == "" { - tokenFilePath = DEFAULT_CREDENTIALS_FILE - } - - config := &oauth2.Config{ - ClientID: clientID, - ClientSecret: clientSecret, - RedirectURL: DEFAULT_REDIRECT_URL, - Scopes: []string{ - "offline", - "read:recovery", - "read:cycles", - "read:workout", - "read:sleep", - "read:profile", - "read:body_measurement", - }, - Endpoint: getEndpoint(), - } - - // Check if token.json file exists - if _, err := os.Stat(tokenFilePath); err == nil { - - _, localToken, err := VerfyToken(tokenFilePath) - if err != nil { - slog.Error("Error reading local token", "msg", err) - return "", err - } - - if !localToken.Valid() { - - fmt.Println("Local token expired at " + localToken.Expiry.String() + " , refreshing...") - - form := url.Values{} - form.Add("grant_type", "refresh_token") - form.Add("refresh_token", localToken.RefreshToken) - form.Add("client_id", clientID) - form.Add("client_secret", clientSecret) - form.Add("scope", "offline") - - body := strings.NewReader(form.Encode()) - req, err := http.NewRequest("POST", DEFAULT_ACCESS_TOKEN_URL, body) - if err != nil { - return "", err - } - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - resp, err := client.Do(req) - if err != nil { - return "", err - } - - if resp == nil { - return "", errors.New("empty response body from HTTP requests") - } - - defer resp.Body.Close() - - // Decode JSON - var tokenResponse AuthCredentials - err = json.NewDecoder(resp.Body).Decode(&tokenResponse) - if err != nil { - return "", err - } - - // Marshal JSON - newToken := &oauth2.Token{ - AccessToken: tokenResponse.AccessToken, - TokenType: tokenResponse.TokenType, - RefreshToken: tokenResponse.RefreshToken, - Expiry: time.Now().Local().Add(time.Second * time.Duration(tokenResponse.ExpiresIn)), - } - - // Write token to file - err = writeLocalToken(tokenFilePath, newToken) - if err != nil { - return "", err - } - - accessToken = tokenResponse.AccessToken - - } else { - - // Token is valid, use it without refresh - slog.Info("Local token valid till " + localToken.Expiry.String() + ", reused without refresh") - accessToken = localToken.AccessToken - - } - - } else { - - // If token.json not present, start browser authentication flow - slog.Info("No token.json found, starting OAuth2 flow") - - // Redirect user to consent page to ask for permission - authUrl := config.AuthCodeURL("stateidentifier", oauth2.AccessTypeOffline) - slog.Info("Visit the following URL for the auth dialog: \n\n" + authUrl + "\n") - slog.Info("Enter the response URL: ") - - // Wait for user to paste in the response URL - var respUrl string - if _, err := fmt.Scan(&respUrl); err != nil { - return "", err - } - - // Get response code from response URL string - parseUrl, err := url.Parse(respUrl) - if err != nil { - return "", errors.New("unable to parse URL value provided") - } - - urlQuery := parseUrl.Query() - if urlQuery == nil { - return "", errors.New("unable to determine query parameters") - } - - code := urlQuery.Get("code") - - // Exchange response code for token - accessToken, err := config.Exchange(context.Background(), code) - if err != nil { - return "", err - } - - // Write token to file - err = writeLocalToken(tokenFilePath, accessToken) - if err != nil { - return "", err - } - - } - - return accessToken, nil - -} - // writeLocalToken creates file containing the Whoop authentication token func writeLocalToken(filePath string, token *oauth2.Token) error {