fix(auth): fail clearly on a missing keyring token

Description

When a host has use_keyring: "true" but its keyring entry is gone, glab sent requests without a token. Every command ended in a bare 401 Unauthorized, with no hint about the missing credential. Now glab stops before the request, with the same kind of error OAuth hosts already get:

the token for "gitlab.example.com" is missing from the operating system keyring; run `glab auth login --hostname gitlab.example.com` to re-authenticate

Decisions, by their effect on users:

  1. A token from GITLAB_TOKEN, the plaintext config, or a CI job token still wins. The error only shows when there is no token at all.
  2. Only use_keyring in the host's own config entry counts. A top-level use_keyring (from glab config set use_keyring true --global) and GLAB_USE_KEYRING do not, the same as when glab reads the token. Otherwise, they would make every tokenless host fail.
  3. glab auth login --web and --device save use_keyring before any token exists, so the first login would hit the new error. That login step now does not read or send a token, so a locked keyring does not block it either. It keeps the host's proxy, CA, client certificate, skip_tls_verify and api_host settings.
  4. This also fixes a re-login loop. If an OAuth host's keyring entries are gone, glab auth login --device on main fails and tells you to run glab auth login. On this branch, the login starts.

Behavior changes on a host with use_keyring: "true" and no keyring entry:

  • Anonymous reads (for example on public projects) now fail early with the error above.
  • glab auth docker-helper still works for registries on other hosts, because it does not need the default host's token. A registry on this host still fails, and for an Artifact Registry the error is the one above.

Related to #8552

I didn't use Closes. I fixed the bug the maintainer described in the issue:

when a host is configured with use_keyring: true and the keyring entry is missing, glab treats the token as unset and builds an unauthenticated client, so the result is a bare 401 with nothing pointing back at the credential. The OAuth2 path already fails with an actionable message in that situation; the personal access token path does not.

The reporter's own 401 may come from how glab api and glab auth status pick the host. That question is still open, so I left the issue open.

How has this been tested?

I built glab from main and from this branch. I used a temporary config with use_keyring: "true" for a local stub server, and no keyring entry.

$ glab api user        # built from main
{"message": "401 Unauthorized"}glab: 401 Unauthorized (HTTP 401)

$ glab api user        # built from this branch
  The token for "127.0.0.1:18552" is missing from the operating system keyring; run `glab auth login --hostname
  127.0.0.1:18552` to re-authenticate.

Then I removed use_keyring from the host entry and ran glab config set use_keyring true --global. The previous commit of this branch stopped with the keyring error here. Now the request goes out:

$ glab api user        # built from this branch
{"message": "401 Unauthorized"}glab: 401 Unauthorized (HTTP 401)

$ GITLAB_TOKEN=glpat-stub glab api user        # built from this branch
{"id": 1, "username": "stub-user", "name": "Stub"}

For decision 3, I ran glab auth login --device for a new host. Both builds reached the device endpoint. A branch build with the old login code stopped with the new error instead. For decision 4, main failed with Oauth2 authentication is configured ... run glab auth login, and the branch reached the device endpoint.

I added unit tests for the new error, an environment token, a job token, GLAB_USE_KEYRING, and the login step keeping TLS and header settings. After the review, I added tests for --device with no stored token, a locked keyring when the login client skips the token, the Docker helper with a tokenless default host, and a tokenless host under a top-level use_keyring. Each of these fails on the code before the fix.

$ go test -count=1 ./internal/api/ ./internal/config/ ./internal/commands/auth/docker/ ./internal/commands/auth/login/ ./internal/commands/auth/status/
ok  	gitlab.com/gitlab-org/cli/internal/api	0.419s
ok  	gitlab.com/gitlab-org/cli/internal/config	2.242s
ok  	gitlab.com/gitlab-org/cli/internal/commands/auth/docker	0.261s
ok  	gitlab.com/gitlab-org/cli/internal/commands/auth/login	38.528s
ok  	gitlab.com/gitlab-org/cli/internal/commands/auth/status	0.030s

gofmt -s -l, gofumpt -l and go vet printed nothing. golangci-lint run --new-from-rev on the changed packages reported 0 issues. The rest runs in the pipeline.

Screenshots (if appropriate):

Not applicable, the change is in CLI output. The new error text is above.

Implemented with AI assistance; I reviewed and tested the changes.

Edited by Hamza Hassanain

Merge request reports

Loading
Loading