package utils //nolint:revive //TODO: figure out a better name for this package
import (
"fmt"
"net/http"
"regexp"
"strings"
httpheaders "github.com/github/github-mcp-server/pkg/http/headers"
"github.com/github/github-mcp-server/pkg/http/mark"
)
type TokenType int
const (
TokenTypeUnknown TokenType = iota
TokenTypePersonalAccessToken
TokenTypeFineGrainedPersonalAccessToken
TokenTypeOAuthAccessToken
TokenTypeUserToServerGitHubAppToken
TokenTypeServerToServerGitHubAppToken
)
var supportedGitHubPrefixes = map[string]TokenType{
"ghp_": TokenTypePersonalAccessToken, // Personal access token (classic)
"github_pat_": TokenTypeFineGrainedPersonalAccessToken, // Fine-grained personal access token
"gho_": TokenTypeOAuthAccessToken, // OAuth access token
"ghu_": TokenTypeUserToServerGitHubAppToken, // User access token for a GitHub App
"ghs_": TokenTypeServerToServerGitHubAppToken, // Installation access token for a GitHub App (a.k.a. server-to-server token)
}
var (
ErrMissingAuthorizationHeader = fmt.Errorf("%w: missing required Authorization header", mark.ErrBadRequest)
ErrBadAuthorizationHeader = fmt.Errorf("%w: Authorization header is badly formatted", mark.ErrBadRequest)
ErrUnsupportedAuthorizationHeader = fmt.Errorf("%w: unsupported Authorization header", mark.ErrBadRequest)
)
// oldPatternRegexp is the regular expression for the old pattern of the token.
// Until 2021, GitHub API tokens did not have an identifiable prefix. They
// were 40 characters long and only contained the characters a-f and 0-9.
var oldPatternRegexp = regexp.MustCompile(`\A[a-f0-9]{40}\z`)
// ParseAuthorizationHeader parses the Authorization header from the HTTP request
func ParseAuthorizationHeader(req *http.Request) (tokenType TokenType, token string, _ error) {
authHeader := req.Header.Get(httpheaders.AuthorizationHeader)
if authHeader == "" {
return 0, "", ErrMissingAuthorizationHeader
}
switch {
// decrypt dotcom token and set it as token
case strings.HasPrefix(authHeader, "GitHub-Bearer "):
return 0, "", ErrUnsupportedAuthorizationHeader
default:
// support both "Bearer" and "bearer" to conform to api.github.com
if len(authHeader) > 7 && strings.EqualFold(authHeader[:7], "Bearer ") {
token = authHeader[7:]
} else {
token = authHeader
}
}
for prefix, tokenType := range supportedGitHubPrefixes {
if strings.HasPrefix(token, prefix) {
return tokenType, token, nil
}
}
matchesOldTokenPattern := oldPatternRegexp.MatchString(token)
if matchesOldTokenPattern {
return TokenTypePersonalAccessToken, token, nil
}
return 0, "", ErrBadAuthorizationHeader
}