feat(publisher): integrate GitHub Client ID into device flow authentication
This commit is contained in:
parent
5d9171bdcc
commit
4dbf80562b
2 changed files with 48 additions and 36 deletions
|
|
@ -20,8 +20,7 @@ type Authentication struct {
|
||||||
// PublishRequest represents a request to publish a server to the registry
|
// PublishRequest represents a request to publish a server to the registry
|
||||||
type PublishRequest struct {
|
type PublishRequest struct {
|
||||||
ServerDetail `json:",inline"`
|
ServerDetail `json:",inline"`
|
||||||
Authentication Authentication `json:"-"` // Now provided via Authorization header
|
AuthStatusToken string `json:"-"` // Used internally for device flows
|
||||||
AuthStatusToken string `json:"-"` // Used internally for device flows
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repository represents a source code repository as defined in the spec
|
// Repository represents a source code repository as defined in the spec
|
||||||
|
|
@ -121,7 +120,7 @@ type Server struct {
|
||||||
|
|
||||||
// ServerDetail represents detailed server information as defined in the spec
|
// ServerDetail represents detailed server information as defined in the spec
|
||||||
type ServerDetail struct {
|
type ServerDetail struct {
|
||||||
Server `json:",inline" bson:",inline"`
|
Server `json:",inline" bson:",inline"`
|
||||||
Packages []Package `json:"packages,omitempty" bson:"packages,omitempty"`
|
Packages []Package `json:"packages,omitempty" bson:"packages,omitempty"`
|
||||||
Remotes []Remote `json:"remotes,omitempty" bson:"remotes,omitempty"`
|
Remotes []Remote `json:"remotes,omitempty" bson:"remotes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,6 @@ const (
|
||||||
// GitHub OAuth URLs
|
// GitHub OAuth URLs
|
||||||
GitHubDeviceCodeURL = "https://github.com/login/device/code"
|
GitHubDeviceCodeURL = "https://github.com/login/device/code"
|
||||||
GitHubAccessTokenURL = "https://github.com/login/oauth/access_token"
|
GitHubAccessTokenURL = "https://github.com/login/oauth/access_token"
|
||||||
|
|
||||||
// Environment variable for GitHub Client ID
|
|
||||||
EnvGithubClientID = "MCP_REGISTRY_GITHUB_CLIENT_ID"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeviceCodeResponse represents the response from GitHub's device code endpoint
|
// DeviceCodeResponse represents the response from GitHub's device code endpoint
|
||||||
|
|
@ -40,6 +37,11 @@ type AccessTokenResponse struct {
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServerHealthResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
GitHubClientId string `json:"github_client_id"`
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var registryURL string
|
var registryURL string
|
||||||
var mcpFilePath string
|
var mcpFilePath string
|
||||||
|
|
@ -58,18 +60,31 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for GitHub client ID in environment if we're going to need it for authentication
|
// get the clientID from the server's health endpoint
|
||||||
if providedToken == "" && os.Getenv(EnvGithubClientID) == "" {
|
healthURL := registryURL + "/v0/health"
|
||||||
fmt.Printf("Warning: Environment variable %s is not set. This is required for GitHub authentication.\n", EnvGithubClientID)
|
resp, err := http.Get(healthURL)
|
||||||
fmt.Println("You can set it with: export " + EnvGithubClientID + "=your_github_client_id")
|
if err != nil {
|
||||||
fmt.Println("Or provide a token directly with the --token flag.")
|
fmt.Printf("Error fetching health endpoint: %s\n", err.Error())
|
||||||
|
return
|
||||||
// Only return if we'll need to do GitHub auth
|
|
||||||
_, statErr := os.Stat(tokenFilePath)
|
|
||||||
if forceLogin || os.IsNotExist(statErr) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
fmt.Printf("Health endpoint returned status %d: %s\n", resp.StatusCode, body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var healthResponse ServerHealthResponse
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&healthResponse)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error decoding health response: %s\n", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if healthResponse.GitHubClientId == "" {
|
||||||
|
fmt.Println("GitHub Client ID is not set in the server's health response.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
githubClientID := healthResponse.GitHubClientId
|
||||||
|
|
||||||
var token string
|
var token string
|
||||||
|
|
||||||
|
|
@ -80,7 +95,7 @@ func main() {
|
||||||
// Check if token exists or force login is requested
|
// Check if token exists or force login is requested
|
||||||
_, statErr := os.Stat(tokenFilePath)
|
_, statErr := os.Stat(tokenFilePath)
|
||||||
if forceLogin || os.IsNotExist(statErr) {
|
if forceLogin || os.IsNotExist(statErr) {
|
||||||
err := performDeviceFlowLogin()
|
err := performDeviceFlowLogin(githubClientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to perform device flow login: %s\n", err.Error())
|
fmt.Printf("Failed to perform device flow login: %s\n", err.Error())
|
||||||
return
|
return
|
||||||
|
|
@ -113,15 +128,15 @@ func main() {
|
||||||
fmt.Println("Successfully published to registry!")
|
fmt.Println("Successfully published to registry!")
|
||||||
}
|
}
|
||||||
|
|
||||||
func performDeviceFlowLogin() error {
|
func performDeviceFlowLogin(githubClientID string) error {
|
||||||
// Check if the environment variable is set
|
|
||||||
if os.Getenv(EnvGithubClientID) == "" {
|
if githubClientID == "" {
|
||||||
return fmt.Errorf("environment variable %s must be set for GitHub authentication", EnvGithubClientID)
|
return fmt.Errorf("GitHub Client ID is required for device flow login")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Device flow login logic using GitHub's device flow
|
// Device flow login logic using GitHub's device flow
|
||||||
// First, request a device code
|
// First, request a device code
|
||||||
deviceCode, userCode, verificationURI, err := requestDeviceCode()
|
deviceCode, userCode, verificationURI, err := requestDeviceCode(githubClientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error requesting device code: %w", err)
|
return fmt.Errorf("error requesting device code: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +149,7 @@ func performDeviceFlowLogin() error {
|
||||||
|
|
||||||
// Poll for the token
|
// Poll for the token
|
||||||
fmt.Println("Waiting for authorization...")
|
fmt.Println("Waiting for authorization...")
|
||||||
token, err := pollForToken(deviceCode)
|
token, err := pollForToken(deviceCode, githubClientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error polling for token: %w", err)
|
return fmt.Errorf("error polling for token: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -150,14 +165,13 @@ func performDeviceFlowLogin() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// requestDeviceCode initiates the device authorization flow
|
// requestDeviceCode initiates the device authorization flow
|
||||||
func requestDeviceCode() (string, string, string, error) {
|
func requestDeviceCode(githubClientID string) (string, string, string, error) {
|
||||||
clientID := os.Getenv(EnvGithubClientID)
|
if githubClientID == "" {
|
||||||
if clientID == "" {
|
return "", "", "", fmt.Errorf("GitHub Client ID is required for device flow login")
|
||||||
return "", "", "", fmt.Errorf("environment variable %s is not set", EnvGithubClientID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := map[string]string{
|
payload := map[string]string{
|
||||||
"client_id": clientID,
|
"client_id": githubClientID,
|
||||||
"scope": "read:org read:user",
|
"scope": "read:org read:user",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,14 +213,13 @@ func requestDeviceCode() (string, string, string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// pollForToken polls for access token after user completes authorization
|
// pollForToken polls for access token after user completes authorization
|
||||||
func pollForToken(deviceCode string) (string, error) {
|
func pollForToken(deviceCode, githubClientID string) (string, error) {
|
||||||
clientID := os.Getenv(EnvGithubClientID)
|
if githubClientID == "" {
|
||||||
if clientID == "" {
|
return "", fmt.Errorf("GitHub Client ID is required for device flow login")
|
||||||
return "", fmt.Errorf("environment variable %s is not set", EnvGithubClientID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := map[string]string{
|
payload := map[string]string{
|
||||||
"client_id": clientID,
|
"client_id": githubClientID,
|
||||||
"device_code": deviceCode,
|
"device_code": deviceCode,
|
||||||
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue