feat(auth): enhance token validation to ensure association with GitHub application and improve error handling
This commit is contained in:
parent
9c6eebbed0
commit
6191066b2e
1 changed files with 64 additions and 3 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -65,14 +66,74 @@ func NewGitHubDeviceAuth(config GitHubOAuthConfig) *GitHubDeviceAuth {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateToken checks if a token has write:packages access to the required repository
|
// ValidateToken validates if a GitHub token has the necessary permissions to access the required repository.
|
||||||
// and verifies that the user matches the owner in the required repository
|
// It verifies the token owner matches the repository owner or is a member of the owning organization.
|
||||||
|
// It also verifies that the token was created for the same ClientID used to set up the authentication.
|
||||||
|
// Returns true if valid, false otherwise along with an error explaining the validation failure.
|
||||||
func (g *GitHubDeviceAuth) ValidateToken(token string, requiredRepo string) (bool, error) {
|
func (g *GitHubDeviceAuth) ValidateToken(token string, requiredRepo string) (bool, error) {
|
||||||
// If no repo is required, we can't validate properly
|
// If no repo is required, we can't validate properly
|
||||||
if requiredRepo == "" {
|
if requiredRepo == "" {
|
||||||
return false, fmt.Errorf("repository reference is required for token validation")
|
return false, fmt.Errorf("repository reference is required for token validation")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First, validate that the token is associated with our ClientID
|
||||||
|
tokenReq, err := http.NewRequest("GET", "https://api.github.com/applications/"+g.config.ClientID+"/token", nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The applications endpoint requires basic auth with client ID and secret
|
||||||
|
tokenReq.SetBasicAuth(g.config.ClientID, g.config.ClientSecret)
|
||||||
|
tokenReq.Header.Set("Accept", "application/vnd.github+json")
|
||||||
|
|
||||||
|
// Create request body with the token
|
||||||
|
type tokenCheck struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
checkBody, err := json.Marshal(tokenCheck{AccessToken: token})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST instead of GET for security reasons per GitHub API
|
||||||
|
tokenReq, err = http.NewRequest("POST", "https://api.github.com/applications/"+g.config.ClientID+"/token", io.NopCloser(bytes.NewReader(checkBody)))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenReq.SetBasicAuth(g.config.ClientID, g.config.ClientSecret)
|
||||||
|
tokenReq.Header.Set("Accept", "application/vnd.github+json")
|
||||||
|
tokenReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
tokenResp, err := client.Do(tokenReq)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer tokenResp.Body.Close()
|
||||||
|
|
||||||
|
// Check response - 200 means token is valid and associated with our app
|
||||||
|
// 404 means token is not associated with our app
|
||||||
|
if tokenResp.StatusCode != http.StatusOK {
|
||||||
|
return false, fmt.Errorf("token is not associated with this application (status: %d)", tokenResp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenInfo TokenValidationResponse
|
||||||
|
tokenRespBody, err := io.ReadAll(tokenResp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(tokenRespBody, &tokenInfo); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there's an error in the response
|
||||||
|
if tokenInfo.Error != "" {
|
||||||
|
return false, fmt.Errorf("token validation error: %s", tokenInfo.Error)
|
||||||
|
}
|
||||||
|
|
||||||
// Get the authenticated user
|
// Get the authenticated user
|
||||||
userReq, err := http.NewRequest("GET", "https://api.github.com/user", nil)
|
userReq, err := http.NewRequest("GET", "https://api.github.com/user", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -81,7 +142,7 @@ func (g *GitHubDeviceAuth) ValidateToken(token string, requiredRepo string) (boo
|
||||||
|
|
||||||
userReq.Header.Set("Accept", "application/vnd.github+json")
|
userReq.Header.Set("Accept", "application/vnd.github+json")
|
||||||
userReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
userReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
client := &http.Client{}
|
client = &http.Client{}
|
||||||
userResp, err := client.Do(userReq)
|
userResp, err := client.Do(userReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue