-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Token scopes context #1997
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Token scopes context #1997
Changes from all commits
beb728a
2022dee
f1d372e
48eb4cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,10 +12,8 @@ type tokenCtx string | |
| var tokenCtxKey tokenCtx = "tokenctx" | ||
|
|
||
| type TokenInfo struct { | ||
| Token string | ||
| TokenType utils.TokenType | ||
| ScopesFetched bool | ||
| Scopes []string | ||
| Token string | ||
| TokenType utils.TokenType | ||
| } | ||
|
|
||
| // WithTokenInfo adds TokenInfo to the context | ||
|
|
@@ -30,3 +28,20 @@ func GetTokenInfo(ctx context.Context) (*TokenInfo, bool) { | |
| } | ||
| return nil, false | ||
| } | ||
|
|
||
| type TokenScopesKey tokenCtx | ||
|
|
||
| var tokenScopesKey TokenScopesKey = "tokenscopesctx" | ||
|
|
||
| // WithTokenScopes adds token scopes to the context | ||
| func WithTokenScopes(ctx context.Context, scopes []string) context.Context { | ||
| return context.WithValue(ctx, tokenScopesKey, scopes) | ||
| } | ||
|
Comment on lines
+32
to
+39
|
||
|
|
||
| // GetTokenScopes retrieves token scopes from the context | ||
| func GetTokenScopes(ctx context.Context) ([]string, bool) { | ||
| if scopes, ok := ctx.Value(tokenScopesKey).([]string); ok { | ||
| return scopes, true | ||
| } | ||
| return nil, false | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -26,18 +26,22 @@ func WithPATScopes(logger *slog.Logger, scopeFetcher scopes.FetcherInterface) fu | |||||
| // Only classic PATs (ghp_ prefix) return OAuth scopes via X-OAuth-Scopes header. | ||||||
| // Fine-grained PATs and other token types don't support this, so we skip filtering. | ||||||
| if tokenInfo.TokenType == utils.TokenTypePersonalAccessToken { | ||||||
| existingScopes, ok := ghcontext.GetTokenScopes(ctx) | ||||||
| if ok { | ||||||
| logger.Debug("using existing scopes from context", "scopes", existingScopes) | ||||||
|
||||||
| logger.Debug("using existing scopes from context", "scopes", existingScopes) | |
| logger.Debug("using existing scopes from context", "scope_count", len(existingScopes)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,17 +94,19 @@ func WithScopeChallenge(oauthCfg *oauth.Config, scopeFetcher scopes.FetcherInter | |
| return | ||
| } | ||
|
|
||
| // Get OAuth scopes from GitHub API | ||
| activeScopes, err := scopeFetcher.FetchTokenScopes(ctx, tokenInfo.Token) | ||
| if err != nil { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| // Get OAuth scopes for Token. First check if scopes are already in context, then fetch from GitHub if not present. | ||
| // This allows Remote Server to pass scope info to avoid redundant GitHub API calls. | ||
| activeScopes, ok := ghcontext.GetTokenScopes(ctx) | ||
| if !ok { | ||
| activeScopes, err = scopeFetcher.FetchTokenScopes(ctx, tokenInfo.Token) | ||
| if err != nil { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
| } | ||
|
|
||
| // Store active scopes in context for downstream use | ||
| tokenInfo.Scopes = activeScopes | ||
| tokenInfo.ScopesFetched = true | ||
| ctx = ghcontext.WithTokenInfo(ctx, tokenInfo) | ||
| ctx = ghcontext.WithTokenScopes(ctx, activeScopes) | ||
| r = r.WithContext(ctx) | ||
|
Comment on lines
+97
to
110
|
||
|
|
||
| // Check if user has the required scopes | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,16 @@ import ( | |
| func ExtractUserToken(oauthCfg *oauth.Config) func(next http.Handler) http.Handler { | ||
| return func(next http.Handler) http.Handler { | ||
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| ctx := r.Context() | ||
|
|
||
| // Check if token info already exists in context, if it does, skip extraction. | ||
| // In remote setup, we may have already extracted token info earlier. | ||
| if _, ok := ghcontext.GetTokenInfo(ctx); ok { | ||
| // Token info already exists in context, skip extraction | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
Comment on lines
+18
to
+24
|
||
|
|
||
| tokenType, token, err := utils.ParseAuthorizationHeader(r) | ||
| if err != nil { | ||
| // For missing Authorization header, return 401 with WWW-Authenticate header per MCP spec | ||
|
|
@@ -25,7 +35,6 @@ func ExtractUserToken(oauthCfg *oauth.Config) func(next http.Handler) http.Handl | |
| return | ||
| } | ||
|
|
||
| ctx := r.Context() | ||
| ctx = ghcontext.WithTokenInfo(ctx, &ghcontext.TokenInfo{ | ||
| Token: token, | ||
| TokenType: tokenType, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TokenInfois an exported struct in a public package (pkg/context). Removing theScopes/ScopesFetchedfields is a breaking API change for any external consumers importing this module. Consider keeping the fields (possibly deprecated) or introducing a new context value/type for scopes while maintaining backward compatibility.