Route auth API calls through the backend to avoid CORS issues#13302
Open
Route auth API calls through the backend to avoid CORS issues#13302
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
estib-vega
approved these changes
Apr 14, 2026
Contributor
estib-vega
left a comment
There was a problem hiding this comment.
It's a great idea, thanks! Just added a small doc comment + request
Contributor
There was a problem hiding this comment.
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::apimodule implementing the auth API client and default API URL resolution. - Added backend commands/endpoints for
get_login_tokenandlogin_with_tokenand updated the desktop frontend to use them. - Removed the
--dev/devstaging toggle in favor ofCHANNEL(compile-time) plusGITBUTLER_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. |
krlvi
reviewed
Apr 14, 2026
krlvi
reviewed
Apr 14, 2026
krlvi
requested changes
Apr 14, 2026
Contributor
Author
|
Thanks guys! I just pushed cad9237 addressing the feedback. *edit: I'll squash and push before any merge. |
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When running in
but remotemode, browser security blocks cross-origin requests from the frontend toapp.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 ingitbutler-user::apireusable for a futurebut authCLI command without any web framework dependency.What's routed through the backend
POST /api/login/token.jsonget_login_tokenGET /api/login/whoamilogin_with_tokenGET /api/user.jsonget_user_profilePUT /api/user.jsonupdate_user_profileKey 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 thebut-forgepattern)default_api_url()— compile-timeAppChanneldetermines the API URL, with aGITBUTLER_API_URLruntime env var override for local developmentbut_apicommands —get_login_token,login_with_token,get_user_profile,update_user_profileexposed via the#[but_api]macro so both Tauri and but-server use the same code pathbut-serverroutes — all four commands routed throughbut_postfor consistent error envelopesuserService.tsnow callsbackend.invoke(...)instead ofhttpClient.get/put(...)for all user API calls; avatar uploads are base64-encoded in the browser and re-encoded as multipart form data in Rustauth.rssimplified — delegates token validation togitbutler_user::api::validate_token_owner, which returnsOk(false)for 401/403 (not the owner) andErrfor everything elsews://[::1]:{port}fromconnect-src(brackets are not valid in CSP source expressions;ws://localhostcovers both IPv4 and IPv6)ApiHttpErrorused consistently across all fetch functions for status-code matchingSecurity notes
api_urlis 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/auth/login+/auth/callbackroutes