Files
yutu/pkg/auth/verifier.go
T

70 lines
2.1 KiB
Go

// Copyright 2026 eat-pray-ai & OpenWaygate
// SPDX-License-Identifier: Apache-2.0
package auth
import (
"context"
"encoding/json/v2"
"fmt"
"io"
"net/http"
"strings"
"time"
sdkauth "github.com/modelcontextprotocol/go-sdk/auth"
)
const defaultTokenInfoURL = "https://oauth2.googleapis.com/tokeninfo"
// NewGoogleTokenVerifier returns a TokenVerifier that validates Google OAuth
// access tokens by calling the given tokeninfo endpoint. Use
// GoogleTokenVerifier for production (calls Google's endpoint directly).
func NewGoogleTokenVerifier(tokenInfoURL string) sdkauth.TokenVerifier {
return func(ctx context.Context, token string, req *http.Request) (*sdkauth.TokenInfo, error) {
url := fmt.Sprintf("%s?access_token=%s", tokenInfoURL, token)
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("%w: %w", sdkauth.ErrInvalidToken, err)
}
resp, err := http.DefaultClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("%w: %w", sdkauth.ErrInvalidToken, err)
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(
"%w: tokeninfo returned %d", sdkauth.ErrInvalidToken, resp.StatusCode,
)
}
var result struct {
ExpiresIn int64 `json:"expires_in,string"`
Scope string `json:"scope"`
Sub string `json:"sub"`
Error string `json:"error"`
}
if err := json.UnmarshalRead(resp.Body, &result); err != nil {
return nil, fmt.Errorf("%w: %w", sdkauth.ErrInvalidToken, err)
}
if result.Error != "" {
return nil, fmt.Errorf("%w: %s", sdkauth.ErrInvalidToken, result.Error)
}
return &sdkauth.TokenInfo{
Scopes: strings.Fields(result.Scope),
Expiration: time.Now().Add(time.Duration(result.ExpiresIn) * time.Second),
UserID: result.Sub,
Extra: map[string]any{"access_token": token},
}, nil
}
}
// GoogleTokenVerifier is a production TokenVerifier that validates tokens
// against Google's tokeninfo endpoint.
var GoogleTokenVerifier = NewGoogleTokenVerifier(defaultTokenInfoURL)