Skip to content

Route auth API calls through the backend to avoid CORS issues#13302

Open
mtsgrd wants to merge 6 commits intomasterfrom
backend-auth
Open

Route auth API calls through the backend to avoid CORS issues#13302
mtsgrd wants to merge 6 commits intomasterfrom
backend-auth

Conversation

@mtsgrd
Copy link
Copy Markdown
Contributor

@mtsgrd mtsgrd commented Apr 14, 2026

Summary

When running in but remote mode, browser security blocks cross-origin requests from the frontend to app.gitbutler.com. Rather than adding CORS headers to the staging/production servers, we route all authenticated API calls through the Rust backend. This keeps the API client in gitbutler-user::api reusable for a future but auth CLI command without any web framework dependency.

What's routed through the backend

Endpoint Command Used by
POST /api/login/token.json get_login_token Login flow
GET /api/login/whoami login_with_token Token validation
GET /api/user.json get_user_profile Settings page (refresh profile)
PUT /api/user.json update_user_profile Settings page (update name/avatar)

Key changes

  • gitbutler-user::api — new auth API client with sync public API (async HTTP runs on a dedicated thread with a short-lived Tokio runtime, following the but-forge pattern)
  • default_api_url() — compile-time AppChannel determines the API URL, with a GITBUTLER_API_URL runtime env var override for local development
  • but_api commandsget_login_token, login_with_token, get_user_profile, update_user_profile exposed via the #[but_api] macro so both Tauri and but-server use the same code path
  • but-server routes — all four commands routed through but_post for consistent error envelopes
  • FrontenduserService.ts now calls backend.invoke(...) instead of httpClient.get/put(...) for all user API calls; avatar uploads are base64-encoded in the browser and re-encoded as multipart form data in Rust
  • auth.rs simplified — delegates token validation to gitbutler_user::api::validate_token_owner, which returns Ok(false) for 401/403 (not the owner) and Err for everything else
  • CSP fix — removed invalid ws://[::1]:{port} from connect-src (brackets are not valid in CSP source expressions; ws://localhost covers both IPv4 and IPv6)
  • HTTP timeouts — 10s connect / 30s request timeout on all API calls to prevent hangs in the auth middleware path
  • Typed errorsApiHttpError used consistently across all fetch functions for status-code matching

Security notes

  • Access tokens now travel backend-to-API only, never as cross-origin browser requests
  • api_url is compile-time by default — the runtime override (GITBUTLER_API_URL) is opt-in and not derived from untrusted input, so there is no SSRF risk
  • In remote mode, the new commands sit behind the existing auth middleware; the initial login flow uses the separate public /auth/login + /auth/callback routes
  • In local mode, the auth middleware is a no-op (localhost only, matching the existing trust model)

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
gitbutler-web Ignored Ignored Preview Apr 14, 2026 2:38pm

Request Review

@github-actions github-actions bot added rust Pull requests that update Rust code @gitbutler/desktop CLI The command-line program `but` labels Apr 14, 2026
@mtsgrd mtsgrd requested a review from estib-vega April 14, 2026 09:28
Copy link
Copy Markdown
Contributor

@estib-vega estib-vega left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a great idea, thanks! Just added a small doc comment + request

@mtsgrd mtsgrd requested review from Byron and krlvi April 14, 2026 09:48
@mtsgrd mtsgrd marked this pull request as ready for review April 14, 2026 09:49
Copilot AI review requested due to automatic review settings April 14, 2026 09:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Routes GitButler authentication API calls through the Rust backend to avoid cross-origin browser requests (CORS) when running in but remote mode, while keeping the auth API client reusable for future CLI commands.

Changes:

  • Added a new gitbutler-user::api module implementing the auth API client and default API URL resolution.
  • Added backend commands/endpoints for get_login_token and login_with_token and updated the desktop frontend to use them.
  • Removed the --dev/dev staging toggle in favor of CHANNEL (compile-time) plus GITBUTLER_API_URL (runtime override), and adjusted CSP WebSocket sources.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
crates/gitbutler-user/src/lib.rs Exposes the new api module from the user crate.
crates/gitbutler-user/src/api.rs Implements server-side HTTP client helpers for auth token issuance and validation.
crates/gitbutler-user/Cargo.toml Adds reqwest dependency needed for the new API client.
crates/gitbutler-tauri/src/main.rs Registers new Tauri commands for login token and token-based login.
crates/gitbutler-tauri/permissions/default.toml Allows the new Tauri commands (get_login_token, login_with_token).
crates/but-api/src/legacy/users.rs Adds but_api commands that call into gitbutler_user::api.
crates/but/src/lib.rs Removes passing the deprecated dev flag into but-server config.
crates/but/src/args/mod.rs Removes the --dev CLI flag from but remote.
crates/but-server/src/main.rs Removes the --dev CLI flag wiring.
crates/but-server/src/lib.rs Uses default_api_url(), adds /get_login_token + /login_with_token routes, and tweaks CSP WS sources.
crates/but-server/src/auth.rs Delegates token validation to gitbutler_user::api and changes caching shape.
crates/but-server/Cargo.toml Drops direct reqwest dependency (now pulled via gitbutler-user).
apps/desktop/src/lib/user/userService.ts Switches login token + whoami calls to backend invokes to avoid CORS.
Cargo.lock Moves reqwest usage from but-server to gitbutler-user in the lockfile.

Copilot AI review requested due to automatic review settings April 14, 2026 13:55
@mtsgrd
Copy link
Copy Markdown
Contributor Author

mtsgrd commented Apr 14, 2026

Thanks guys! I just pushed cad9237 addressing the feedback. *edit: I'll squash and push before any merge.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 7 comments.

Copilot AI review requested due to automatic review settings April 14, 2026 14:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

mtsgrd added 4 commits April 15, 2026 12:25
When running in `but remote` mode, browser security blocks cross-origin
requests from the frontend to app.gitbutler.com. Rather than adding CORS
headers to the staging/production servers, we route auth calls through
the Rust backend. This keeps the auth API client in `gitbutler-user::api`
reusable for a future `but auth` CLI command (not yet implemented)
without any web framework dependency.

- Extract auth API client into `gitbutler-user::api` with
  `default_api_url()`, `fetch_login_token`, `fetch_user_by_token`,
  and `validate_token_owner`
- Add `get_login_token` and `login_with_token` as `but_api` commands
  so both Tauri and but-server use the same backend code path
- Determine API URL at compile time via `CHANNEL` env var, with a
  `GITBUTLER_API_URL` runtime env var override for local development
- Simplify `auth.rs` to delegate token validation to `gitbutler_user::api`
- Return `Ok(false)` for 4xx responses in `validate_token_owner` so
  invalid/expired tokens are treated as unauthorized rather than 500s
- Remove `ws://[::1]:{port}` from CSP `connect-src` — square brackets
  are invalid in CSP source expressions (RFC 7230 §2.7.1), so the
  browser ignored the entry and logged a console error. IPv6 loopback
  WebSocket connections are already covered by `ws://localhost:{port}`.

Security notes:
- Access tokens now travel backend→API only, never as cross-origin
  browser requests, reducing the attack surface compared to the
  previous Tauri path (which sent tokens through the webview network
  stack via httpClient)
- `api_url` is compile-time by default, with an opt-in runtime override
  (`GITBUTLER_API_URL`) — not derived from untrusted user input, so
  there is no SSRF risk
- In but-server remote mode, `/get_login_token` and `/login_with_token`
  sit behind the auth middleware; the initial login flow uses the
  separate public `/auth/login` + `/auth/callback` routes
- In but-server local mode, the auth middleware is a no-op (localhost
  only, matching the existing trust model)
The carbon-based reviewers have kindly pointed out several areas where
our code could better conform to their expectations. We are happy to
oblige.

- Make the public API synchronous as the humans prefer, wrapping async
  HTTP in a dedicated thread+runtime (the "but-forge" way)
- Use AppChannel from but-path instead of raw option_env!, because
  apparently we already have a perfectly good type for this
- Stop leaking serde_json::Value from the crate boundary, as politely
  requested
- Call validate_token_owner and fetch_login_token via spawn_blocking
  in the async but-server context, since they are now blocking
- Return Ok(false) for 4xx in validate_token_owner so the auth
  middleware doesn't 500 on expired tokens (good catch, Copilot)
- Await forgetUserCredentials() so it doesn't race (also Copilot)
- Update LoginToken doc comment to reflect reality
Skip token from tracing instrumentation to avoid logging secrets.
Route but-server auth handlers through `but_post` for consistent error
envelopes instead of hand-rolling Response variants. Narrow
`validate_token_owner` to only treat 401/403 as "not the owner" —
other client errors like 429 rate-limit should surface as real errors.
Prevents indefinite hangs when the GitButler API is unreachable,
particularly in the remote-auth middleware path where a stalled
request would block authentication checks.
fetch_login_token now returns ApiHttpError on non-2xx responses,
matching fetch_user_by_token. This allows callers to pattern-match
on HTTP status codes uniformly across all API functions.
Copilot AI review requested due to automatic review settings April 15, 2026 10:51
@mtsgrd mtsgrd requested a review from Caleb-T-Owens as a code owner April 15, 2026 10:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.

Route the user profile GET and PUT requests through the Rust backend
instead of making direct browser-to-API calls, which are blocked by
CORS in remote mode. The avatar file upload is passed as base64 from
the frontend and re-encoded as multipart form data in Rust.
Remove explicit token parameter for user endpoints

Stop passing the auth token through frontend/backend calls by reading
the stored access token server-side. This simplifies calls to
get_user_profile and update_user_profile: the token is no longer fetched
from the Svelte store and forwarded, and the Rust API handlers no longer
accept a token parameter. The gitbutler-user crate now reads the stored
access token internally before making API requests.

- Frontend: remove get() calls to tokenMemoryService and stop sending token in backend.invoke.
- Backend: remove token parameters from but-api handlers and update instrumentation.
- Library: add stored_access_token() helper and use it in fetch_user_profile and update_user_profile to obtain the token internally.
Copilot AI review requested due to automatic review settings April 15, 2026 13:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLI The command-line program `but` @gitbutler/desktop rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants