OAuth2 token refresh ignores configured subfolder

Checklist

  • I'm using the latest version of the extension (Run glab --version)
    • Extension version:
  • Operating system and version: AlmaLinux 10
  • Gitlab.com or self-managed instance? self-managed instance
  • GitLab version (if self-managed) v19.2.4
  • I have performed glab auth status to check for authentication issues
  • Run the command in debug mode and attach any useful output

Summary

When refreshing an expired OAuth2 access token for a self-managed GitLab instance installed under a subfolder, glab ignores hosts.<hostname>.subfolder.

The refresh request is sent to:

https://gitlab.example.com/oauth/token

instead of: Related: #8399 (closed) fixed the equivalent problem for the OAuth authorization URL, but the token-refresh path still does not use subfolder.

Environment

  • SHELL: bash
  • TERM: xterm-256color
  • GLAB: glab 1.116.0 (e8436ca8)

Other:

  • GitLab instance URL: https://gitlab.example.com/<subfolder>
  • hosts.gitlab.example.com.subfolder: <subfolder>

Steps to reproduce

  1. Use a self-managed GitLab instance installed under a subfolder, for example https://gitlab.example.com/gitlab.

  2. Authenticate with OAuth2 using glab auth login, so the host configuration includes:

    hosts:
      gitlab.example.com:
        subfolder: gitlab
        is_oauth2: "true"
    
  3. Wait for the OAuth2 access token to expire.

  4. Run a command that makes an authenticated API request, for example:

    GLAB_DEBUG=true glab repo list --hostname gitlab.example.com

What is the current bug behavior?

glab attempts to refresh the OAuth2 token through /oauth/token at the host root, without the configured subfolder. The refresh request therefore reaches the wrong endpoint and fails.

What is the expected correct behavior?

glab should include the configured subfolder when constructing the token endpoint URL. For the example above, it should refresh through:

https://gitlab.example.com/gitlab/oauth/token

Relevant logs and/or screenshots

GLAB_DEBUG=true glab auth status
gitlab.example.com
  x gitlab.example.com: API call failed: oauth2: cannot fetch token: 404 Not Found
  ✓ Git operations for gitlab.example.com configured to use https protocol.
  ✓ API calls for gitlab.example.com are made over https protocol.
  ✓ REST API Endpoint: https://gitlab.example.com/git/api/v4/
  ✓ GraphQL Endpoint: https://gitlab.example.com/git/api/graphql/
  ✓ Subfolder: git
  ✓ Token found in configuration file (plaintext): **************************
  ! To store this token more securely, run glab auth login --hostname gitlab.example.com to move it into the operating system keyring.
          
   ERROR  
          
                                                                                                                      
 could not authenticate to one or more of the configured GitLab instances.    
GLAB_DEBUG=true glab repo list
          
   ERROR  
          
  Oauth2: cannot fetch token: 404 Not Found                                                                           
  Response: <!DOCTYPE html>                                                                                           
  <html>                                                                                                              
  <head>                                                                                                              
    <meta content="width=device-width, initial-scale=1" name="viewport">                                              
    <title>The page you're looking for could not be found (404)</title>                                               
    <style>                                                                                                           
      body {                                                                                                          
        color: #333238;                                                                                               
        text-align: center;                                                                                           
        font-family: "Nunito Sans", -apple-system, ".SFNSText-Regular", "San Francisco", BlinkMacSystemFont, "Segoe   
  UI", "Helvetica Neue", Helvetica, Arial, sans-serif;                                                                
      }                                                                                                               
                                                                                                                      
      h1 {                                                                                                            
        font-size: 1.75rem;                                                                                           
        line-height: 2.25rem;                                                                                         
        margin: 1rem 0;                                                                                               
      }                                                                                                               
                                                                                                                      
      p {                                                                                                             
        margin-bottom: .5rem;                                                                                         
      }                                                                                                               
                                                                                                                      
      img {                                                                                                           
        display: block;                                                                                               
        margin: 0 auto;                                                                                               
      }                                                                                                               
                                                                                                                      
      a {                                                                                                             
        text-decoration: none;                                                                                        
        color: #1068bf;                                                                                               
      }                                                                                                               
                                                                                                                      
      a:hover {                                                                                                       
        text-decoration: underline;                                                                                   
      }                                                                                                               
                                                                                                                      
      .error-container {                                                                                              
        max-width: 65ch;                                                                                              
        margin: auto;                                                                                                 
        padding: 1rem 0;                                                                                              
      }                                                                                                               
                                                                                                                      
      .action-container {                                                                                             
        margin-top: 1.5rem;                                                                                           
      }                                                                                                               
                                                                                                                      
      .go-back {                                                                                                      
        display: none;                                                                                                
      }                                                                                                               
    </style>                                                                                                          
  </head>                                                                                                             
                                                                                                                      
  <body>                                                                                                              
    <div class="error-container">                                                                                     
      <img src='/-/error-illustrations/error-404-lg.svg' alt="404 error"/>                                            
      <h1>404: Page not found</h1>                                                                                    
      <p>Make sure the address is correct and the page has not moved.</p>                                             
      <p>Please contact your GitLab administrator if you think this is a mistake.</p>                                 
      <div class="action-container">                                                                                  
        <a href="javascript:history.back()" class="js-go-back go-back">Go back</a>                                    
      </div>                                                                                                          
    </div>                                                                                                            
    <script>                                                                                                          
      (function () {                                                                                                  
        var goBack = document.querySelector('.js-go-back');                                                           
                                                                                                                      
        if (history.length > 1) {                                                                                     
          goBack.style.display = 'inline';                                                                            
        }                                                                                                             
      })();                                                                                                           
    </script>                                                                                                         
  </body>                                                                                                             
  </html>                                                                                                             
  .                   

Possible fixes

oauthBaseURL already reads subfolder and builds the correct authentication base URL for the authorization flow:

https://gitlab.com/gitlab-org/cli/-/blob/main/internal/oauth2/oauth2.go

However, NewConfigTokenSource constructs the OAuth2 base URL directly from protocol and hostname, without reading subfolder:

https://gitlab.com/gitlab-org/cli/-/blob/main/internal/oauth2/token_source.go

Using the same subfolder-aware base URL construction for token refresh should address the issue.