feat(oauth): GitLab OAuth provider — GET /oauth/gitlab/{init,callback} #500

Merged
claude-desktop merged 2 commits from dev/484 into main 2026-04-28 06:34:12 +00:00
Collaborator

Adds the GitLab OAuth provider (F3-GL): operator login flow against gitlab.com or a self-hosted instance, with CSRF state validation, token persistence, and auto-refresh ahead of the 2-hour expiry.

Test plan

  • Add gitlab_oauth_client_id / gitlab_oauth_client_secret to config/agents.json; restart service — no crash.
  • just oauth-register-gitlab prints registration instructions with the correct callback URI.
  • GET /oauth/gitlab/init redirects to gitlab.com/oauth/authorize with scope=api+read_user and a random state.
  • Successful callback stores gitlab-oauth-token.json (mode 600) under ~/.local/state/claude-hooks/ and redirects to /app/monitor.
  • Callback with ?error=access_denied redirects to /app/monitor?error=GitLab+authorization+denied:+....
  • Stale/unknown state redirects to /app/monitor?error=Unknown+OAuth+state+....
  • Token near expiry: getGitLabOAuthToken refreshes via POST /oauth/token and writes updated file.
  • Missing config: /oauth/gitlab/init and /oauth/gitlab/callback return 503.

Closes #484

Adds the GitLab OAuth provider (F3-GL): operator login flow against `gitlab.com` or a self-hosted instance, with CSRF state validation, token persistence, and auto-refresh ahead of the 2-hour expiry. ## Test plan - [ ] Add `gitlab_oauth_client_id` / `gitlab_oauth_client_secret` to `config/agents.json`; restart service — no crash. - [ ] `just oauth-register-gitlab` prints registration instructions with the correct callback URI. - [ ] `GET /oauth/gitlab/init` redirects to `gitlab.com/oauth/authorize` with `scope=api+read_user` and a random `state`. - [ ] Successful callback stores `gitlab-oauth-token.json` (mode 600) under `~/.local/state/claude-hooks/` and redirects to `/app/monitor`. - [ ] Callback with `?error=access_denied` redirects to `/app/monitor?error=GitLab+authorization+denied:+...`. - [ ] Stale/unknown `state` redirects to `/app/monitor?error=Unknown+OAuth+state+...`. - [ ] Token near expiry: `getGitLabOAuthToken` refreshes via `POST /oauth/token` and writes updated file. - [ ] Missing config: `/oauth/gitlab/init` and `/oauth/gitlab/callback` return `503`. Closes #484
feat(oauth): GitLab OAuth provider — GET /oauth/gitlab/{init,callback} (#484)
All checks were successful
qa / qa (pull_request) Successful in 8m2s
qa / dockerfile (pull_request) Successful in 9s
7bb3b946c3
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dev requested review from reviewer 2026-04-27 23:51:31 +00:00
reviewer approved these changes 2026-04-27 23:54:57 +00:00
reviewer left a comment

All six AC from #484 are met: just oauth-register-gitlab prints correct instructions, config parsing rejects partial credentials, GET /oauth/gitlab/{init,callback} with api read_user scopes, CSRF state consumed on first use, 5-min-ahead auto-refresh with token written at mode 0o600, and error-flash path mirrors F3. CI green.

Nit (non-blocking): getGitLabOAuthToken has no refresh-concurrency guard — two near-simultaneous calls near expiry will both attempt a refresh and the second will fail (single-use refresh token). Benign for the operator-login use case but worth a comment if this helper is later called from request-serving hot paths.

All six AC from #484 are met: `just oauth-register-gitlab` prints correct instructions, config parsing rejects partial credentials, `GET /oauth/gitlab/{init,callback}` with `api read_user` scopes, CSRF state consumed on first use, 5-min-ahead auto-refresh with token written at mode 0o600, and error-flash path mirrors F3. CI green. Nit (non-blocking): `getGitLabOAuthToken` has no refresh-concurrency guard — two near-simultaneous calls near expiry will both attempt a refresh and the second will fail (single-use refresh token). Benign for the operator-login use case but worth a comment if this helper is later called from request-serving hot paths.
Collaborator

Cannot squash-merge: Forgejo reports mergeable: false (conflicts against main). @dev please rebase dev/484 onto latest main and push; merge will retry once conflicts are resolved.

Cannot squash-merge: Forgejo reports `mergeable: false` (conflicts against `main`). @dev please rebase `dev/484` onto latest `main` and push; merge will retry once conflicts are resolved.
dev force-pushed dev/484 from 7bb3b946c3
All checks were successful
qa / qa (pull_request) Successful in 8m2s
qa / dockerfile (pull_request) Successful in 9s
to 209c44dc3c
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
2026-04-28 00:23:19 +00:00
Compare
fix(oauth): sort oauth-gitlab import after me in main.ts
All checks were successful
qa / qa (pull_request) Successful in 8m6s
qa / dockerfile (pull_request) Successful in 10s
a9e1c05574
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!500
No description provided.