feat(auth): guide self-managed OAuth setup interactively

What does this MR do and why?

glab auth login --hostname <self-managed> currently fails with a bare ERROR set 'client_id' first with ... message when no OAuth application is configured for the host, forcing a context switch to docs, a separate glab config set client_id command, and a re-run of auth login.

This MR replaces that dead end with a guided interactive prompt. When the session is a TTY and no client_id is already resolvable (config file or GITLAB_CLIENT_ID), glab asks how to continue:

No OAuth application ID is configured for self.hosted.gitlab.com.

How do you want to continue?
> I have an Application ID to paste
  Show me what an admin needs to create
  Cancel
  • Paste: prompts for the Application ID, then continues straight into login. The value is persisted exactly the way glab config set client_id <id> -g --host <hostname> would, but only once the OAuth round trip that used it actually succeeds (see below).
  • Show me: prints the redirect URI, scopes, and confidential setting an admin needs to register an application, plus a link to the docs, then exits with today's error unchanged.
  • Cancel, or Ctrl+C at either step: also exits with today's error unchanged.

Non-interactive sessions (CI, scripts, piped stdin) are unaffected: they keep today's error message pointing at glab config set client_id, and so does a client_id already resolvable from config or GITLAB_CLIENT_ID (no prompt at all in that case).

Why the value isn't persisted on paste

An earlier version of this MR wrote the pasted client_id to config immediately. Review caught that this makes a bad paste sticky: a typo, an ID from another instance, or the application Secret pasted in place of the Application ID (both shown together on the same admin page) would be persisted right away. Once client_id is non-empty, the guided prompt never fires again, and StartFlow waits on the OAuth callback with no timeout, so glab hangs silently instead of failing visibly, the exact symptom cli#1338 describes.

resolveClientID now returns a persist function alongside the client_id instead of writing it itself. StartFlow/StartDeviceFlow call it only after their OAuth round trip actually succeeds, so a pasted value is only committed once it's been proven to work. An already-configured or GITLAB_CLIENT_ID-supplied value is unaffected: its persist function is a no-op.

Where this lives

internal/oauth2/config.go:

  • oauthClientID looks up what's already configured (config file or GITLAB_CLIENT_ID, via cfg.Get), without prompting.
  • resolveClientID is the only place that decides what to do when nothing is configured yet: prompts if io is interactive, otherwise returns the standard non-interactive error.
  • promptClientIDChoice / promptClientID / printClientIDRegistrationInstructions implement the paste/show/cancel flow above.

StartFlow (oauth2.go, Web flow) and StartDeviceFlow (device.go, Device flow) both call resolveClientID, then persist the returned client_id only after their respective OAuth exchange succeeds. NewConfigTokenSource's background token-refresh path (token_source.go) calls oauthClientID directly and never prompts.

References

Fixes #8525 (closed)

Related to cli#1338 (closed in 2024 by making client_id itself configurable via glab config set client_id), but distinct from it: that issue's fix didn't add a timeout to the OAuth callback wait, so a wrong client_id (however it got there) still hangs glab silently instead of failing with an error. This MR's guided prompt makes it easier to end up with a client_id in the first place, which is exactly why the deferred-persist fix above matters, it keeps this MR from reintroducing that same silent-hang symptom through the new prompt.

Screenshots or screen recordings

N/A (terminal-only prompt change)

How to set up and validate locally

  1. Run glab auth login --hostname <a self-managed instance with no client_id configured> from an interactive terminal.
  2. Pick "Show me what an admin needs to create": confirm the redirect URI, scopes, confidential setting, and docs link print, then confirm the command exits with the standard set 'client_id' first error and nothing was written (glab config get client_id --host <hostname> stays empty).
  3. Re-run, pick "Cancel": confirm the same standard error, nothing written.
  4. Re-run, pick "I have an Application ID to paste" and paste a valid Application ID: confirm login continues, and confirm glab config get client_id --host <hostname> reflects the pasted value only after login succeeds.
  5. Re-run non-interactively (e.g. with stdin redirected from /dev/null) and confirm the original error message is unchanged.
  6. With GITLAB_CLIENT_ID set (or client_id already configured for the host), confirm no prompt appears at all.
Edited by Radek Antoniuk

Merge request reports

Loading
Loading