Skip to content

feat(core): add GitLab OAuth (PKCE) login to the built-in provider - #1

Closed
vglafirov wants to merge 1 commit into
restore-gitlab-discoveryfrom
gitlab-oauth-core
Closed

vglafirov wants to merge 1 commit into
restore-gitlab-discoveryfrom
gitlab-oauth-core

Conversation

@vglafirov

Copy link
Copy Markdown
Owner

Issue for this PR

Part of the GitLab Duo provider work. Stacked on anomalyco#50422.

Note

This PR is stacked on anomalyco#50422 (restore-gitlab-discovery) and targets that
branch, because both change packages/core/src/plugin/provider/gitlab.ts.
Please merge anomalyco#50422 first; this PR can then be retargeted to v2.
The diff below is only the OAuth commit on top of that branch.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds a PKCE OAuth method to the built-in gitlab integration, so "Login with
GitLab" works out of the box. Until now the only in-core auth methods were the
generic key (personal access token) and env (GITLAB_TOKEN) methods that
ModelsDevPlugin derives from the models.dev catalog; the OAuth path required
the external opencode-gitlab-auth
plugin, which this makes unnecessary.

Core already owns credential storage, refresh scheduling, and merging
instanceUrl/apiKey into provider settings. This only supplies the pieces
core cannot infer: authorize, refresh, label, and a self-managed instance
URL form field. Implementation follows the existing in-core OAuth providers
(poe.ts, digitalocean.ts, snowflake-cortex.ts): crypto.subtle for PKCE,
a loopback node:http listener, OauthCallbackPage for the browser response,
and HttpClient for the token exchange.

GitLab specifics worth calling out:

  • Fixed callback port. GitLab matches redirect_uri exactly, so the
    listener cannot use an ephemeral port like poe.ts does. It is pinned to
    http://127.0.0.1:8080/callback; EADDRINUSE reports an actionable message
    (same approach as digitalocean.ts, which pins 1456).
  • redirect_uri on the refresh grant. GitLab requires it there too, unlike
    most providers.
  • Generic invalid_grant. GitLab returns one opaque error for a reused or
    expired code, a PKCE verifier mismatch, a redirect-URI mismatch, and a
    confidential-client misconfiguration alike, so failures are annotated with the
    redirect URI and client ID actually sent (never the code or verifier).
  • Client ID. The bundled application is instance-owned and trusted, and only
    exists on gitlab.com. GITLAB_OAUTH_CLIENT_ID overrides it for self-managed
    instances, and the docs explain registering one.
  • methodID is pkce, matching the credentials the external plugin already
    issued, so existing logins keep resolving and refreshing.

Discovery from anomalyco#50422 now prefers the instance an OAuth credential was issued
against over ambient defaults, so a self-managed login discovers workflow models
from the right host instead of GITLAB_INSTANCE_URL/gitlab.com.

How did you verify your code works?

  • packages/core: bun typecheck clean.
  • bun test test/plugin/ — 322 pass, 0 fail across 42 files. That includes the
    4 discovery tests added by feat(core): restore GitLab workflow discovery and add OAuth login anomalyco/opencode#50422, which still pass unchanged.
  • New test/plugin/provider-gitlab-oauth.test.ts (15 tests): method registration
    shape, full authorize -> callback -> exchange against gitlab.com and a
    self-managed URL, credential labelling, refresh (asserting redirect_uri and
    the stored instance), invalid/denied callbacks, failed exchanges preserving the
    previously active connection, EADDRINUSE, and the GITLAB_OAUTH_CLIENT_ID
    override.

One integration detail the tests surfaced: discovery subscribes to
Credential.Event.Switched and resolves the credential, so it legitimately races
the explicit connection.resolve to refresh the same expired token. The refresh
test asserts on the matching token request rather than on a single queued reply.

Screenshots / recordings

Not a UI change; the new method appears as "Login with GitLab (OAuth)" in the
existing /connect flow.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Registers a PKCE OAuth method on the `gitlab` integration so browser login
works without the external opencode-gitlab-auth plugin. Token storage and
refresh scheduling are already owned by core; this supplies the authorize,
refresh, and label implementations plus a self-managed instance URL field.

The bundled client ID is an instance-owned, trusted application registered
for the loopback redirect; GITLAB_OAUTH_CLIENT_ID overrides it for
self-managed instances. GitLab matches redirect_uri exactly and also
requires it on the refresh grant, so the callback port is fixed and
EADDRINUSE reports a clear message.

Workflow discovery now prefers the instance an OAuth credential was issued
against over ambient defaults.
@vglafirov

Copy link
Copy Markdown
Owner Author

Folding this into anomalyco#50422 instead of keeping a stacked PR — the OAuth commit is being pushed directly onto restore-gitlab-discovery.

@vglafirov vglafirov closed this Sep 21, 2026
@vglafirov
vglafirov deleted the gitlab-oauth-core branch September 21, 2026 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant