ui: add OAuth2 consent page and MCP server auth mode settings - #4510
Conversation
|
|
|
Warning Review limit reached
More reviews will be available in 7 minutes and 15 seconds. Learn how PR review limits work. To continue reviewing without waiting, enable usage-based billing in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds a new ChangesOAuth2 Consent Page
MCP Server Authentication Configuration
Sequence Diagram(s)sequenceDiagram
participant Browser
participant OAuth2ConsentPage
participant sessionStorage
participant oauth2ConsentApi as RTK Query (oauth2ConsentApi)
Browser->>OAuth2ConsentPage: GET /oauth/consent?flow=flowId
OAuth2ConsentPage->>sessionStorage: restore temp token keyed by flowId
OAuth2ConsentPage->>oauth2ConsentApi: getOAuth2ConsentFlow(flowId)
oauth2ConsentApi-->>OAuth2ConsentPage: OAuth2ConsentFlowDetail (modes, expires_at)
OAuth2ConsentPage->>OAuth2ConsentPage: render user / vk / session mode selection
OAuth2ConsentPage->>oauth2ConsentApi: submitOAuth2ConsentFlow({ mode, value })
oauth2ConsentApi-->>OAuth2ConsentPage: { redirect_url }
OAuth2ConsentPage->>Browser: window.location = redirect_url
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Confidence Score: 5/5Safe to merge; the consent page and MCP auth-mode config are self-contained UI additions with no regressions on existing flows. All previously raised concerns have been addressed: sessionStorage restore runs inside the useState initializer, the sessionStorage write is a useEffect, redirect URLs are validated with isSafeRedirect, TTL inputs are controlled, TTL validation is guarded by auth mode, and the clientConfigToSave merge ensures TTL values are always included in the save payload. No new correctness or security issues were found. No files require special attention. Important Files Changed
Reviews (28): Last reviewed commit: "feat: adds ui for mcp oauth consent scre..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ui/app/oauth/consent/page.tsx`:
- Around line 275-283: The formatExpiry function does not validate that the
parsed date is valid before using it in calculations. When an invalid ISO string
is passed, new Date(iso).getTime() returns NaN, which then gets propagated
through the arithmetic operations and renders as "in NaN minutes". Add a check
immediately after calculating diffMs to verify it is not NaN, and if it is NaN,
return a safe default value such as "soon" before proceeding with the remaining
logic.
- Around line 290-292: The inner div with the className containing "bg-card
w-1/3 rounded-sm border p-8 shadow-sm" uses a fixed width of w-1/3 which is not
responsive on mobile screens. Replace the w-1/3 class with a responsive width
approach that uses full width (w-full) on small screens and constrains the width
on larger screens. Consider using a combination of w-full with a max-width
constraint (such as max-w-sm or max-w-md) and add responsive breakpoint
modifiers (like md:w-1/3) to achieve the desired layout on different screen
sizes.
- Around line 149-264: Add data-testid attributes to all new interactive
controls in the consent flow to enable stable Playwright E2E test selectors.
Specifically, add data-testid to: the "Continue as" button that calls
handleSubmit("user"), the login link in the sign-in section, the virtual key
input field (id="vk-input"), the "Connect with key" button that calls
handleSubmit("vk"), and the "Continue without an identity" button that calls
handleSubmit("session"). Use descriptive, stable identifiers that clearly
indicate the purpose of each element to support E2E test coverage.
- Around line 134-265: The consent page currently renders nothing when no
authentication modes are available (when all conditions like hasUser, hasVK, and
hasSession evaluate to false). Add an empty state UI that displays when none of
the consent modes can be shown. This should check if no modes are available and
render a message informing the user that no authentication options are currently
available. Place this empty state within the space-y-3 div container,
conditional on the absence of all available modes (user mode, VK mode, and
session mode).
In `@ui/app/workspace/config/views/mcpView.tsx`:
- Around line 171-189: The validation in handleAuthCodeTTLChange and
handleAccessTokenTTLChange currently only checks if the parsed number is greater
than 0, but the input fields declare a minimum of 60 seconds. Update both
handlers to also enforce the minimum of 60 seconds by changing the condition
from num > 0 to num >= 60 when validating auth_code_ttl and access_token_ttl in
the oauth2_server_config. Additionally, add validation logic in the handleSave
function to check both TTL values against the 60-second minimum and display a
toast notification to the user if either value falls below the required minimum,
providing explicit feedback about the constraint.
- Around line 91-99: The issuer URL comparison only checks the .value property
of the EnvVar, missing changes to env_var and from_env fields. This prevents the
Save Changes button from being enabled when switching between literal values and
environment variables. Replace the direct comparison of issuer_url?.value with a
call to the envVarEquals function (reusing the same approach used for the
clientURLChanged comparison earlier in the condition chain) to properly compare
the full EnvVar object including all fields.
In `@ui/lib/store/apis/oauth2ConsentApi.ts`:
- Around line 22-30: The flowId parameter is being directly interpolated into
API endpoint paths without URL encoding in both the getOAuth2ConsentFlow and
submitOAuth2ConsentFlow methods. This allows reserved characters in the
user-controlled flowId to alter the requested route. Wrap the flowId with
encodeURIComponent() before inserting it into the URL paths for both the query
and mutation endpoints to ensure proper URL encoding.
In `@ui/lib/utils/loginGoto.ts`:
- Line 29: The value.startsWith("/oauth/consent") check in the loginGoto.ts file
is too permissive because it matches unintended paths like
"/oauth/consent-anything". Examine how the /workspace path checks are
implemented in the same file to understand the proper restriction pattern, then
apply the same approach to the /oauth/consent check to ensure it only matches
the intended paths (likely by checking for an exact match or verifying a path
separator follows the prefix).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 3c974628-a163-403f-b346-da529229f20f
📒 Files selected for processing (7)
ui/app/oauth/consent/layout.tsxui/app/oauth/consent/page.tsxui/app/workspace/config/views/mcpView.tsxui/lib/store/apis/index.tsui/lib/store/apis/oauth2ConsentApi.tsui/lib/types/config.tsui/lib/utils/loginGoto.ts
98169a4 to
be01ec8
Compare
3d76e6a to
e5a9a8f
Compare
be01ec8 to
9282613
Compare
e5a9a8f to
47cce08
Compare
47cce08 to
577e9c3
Compare
9282613 to
2a42dc9
Compare
577e9c3 to
87046c1
Compare
2a42dc9 to
1c19dfb
Compare
87046c1 to
c6c30c4
Compare
1c19dfb to
0ecdc90
Compare
6b64567 to
cc3e020
Compare
da29389 to
bcdf0a4
Compare
bcdf0a4 to
91ef58c
Compare
cc3e020 to
347b1b0
Compare
91ef58c to
90b5a4b
Compare
347b1b0 to
8362a9e
Compare
90b5a4b to
ce55915
Compare
594fa59 to
ec3bbac
Compare
ce55915 to
520b193
Compare
Merge activity
|
The base branch was changed.
520b193 to
47ea665
Compare
## Summary Adds an OAuth2 consent page and supporting configuration to allow MCP clients (e.g. `claude mcp add`) to authenticate via a browser-based OAuth flow. Anonymous visitors arrive at `/oauth/consent?flow=<id>` with a short-lived temp token embedded in the URL fragment, choose how they want to identify themselves (signed-in user, virtual key, or anonymous session), and are redirected back to the MCP client with an authorization code. ## Changes - **`/oauth/consent` route** — New standalone page (`layout.tsx` + `page.tsx`) that renders outside the dashboard chrome. It wraps itself with `ThemeProvider`, `ReduxProvider`, `NuqsAdapter`, and `Toaster` directly since it does not inherit them from `ClientLayout`. `TempTokenScope` extracts the `#t=…` fragment and attaches it as `X-Bifrost-Temp-Token` on every API call so the consent APIs can authenticate the anonymous visitor. - **Consent UI** — `ConsentView` fetches the flow details and presents up to three authentication options: continuing as the signed-in user, entering a virtual key, or proceeding anonymously. It preserves the temp token across the login redirect via `sessionStorage` so users who choose to sign in are returned to the correct flow. - **`oauth2ConsentApi`** — New RTK Query endpoints (`GET` and `PUT` `/oauth2/consent/flows/:flowId`) for fetching flow details and submitting the chosen identity mode. - **MCP Server Auth Mode setting** — The MCP config view gains a `mcp_server_auth_mode` selector (`headers` / `both` / `oauth`) with contextual warnings: switching to `oauth` disables VK/header access; downgrading back to `headers` revokes all existing OAuth JWTs. OAuth2 server settings (issuer URL, auth code TTL, access token TTL) are revealed when the mode is `both` or `oauth`. - **`CoreConfig` types** — Added `mcp_server_auth_mode` and `oauth2_server_config` (issuer URL, auth code TTL, access token TTL) to the config type and dirty-check logic. - **`loginGoto`** — `/oauth/consent` is now treated as a valid post-login redirect destination. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [ ] Plugins - [x] UI (React) - [ ] Docs ## How to test 1. Start Bifrost with an MCP server configured. 2. Run `claude mcp add --transport sse <bifrost-url>/mcp` (or equivalent). The client should redirect to `/oauth/consent?flow=<id>#t=<token>`. 3. Verify the consent page loads without requiring a dashboard login. 4. Test each identity mode: - **User** — sign in via the login redirect and confirm you are returned to the consent page and redirected back to the MCP client. - **Virtual key** — enter a valid `sk-bf-…` key and confirm a successful redirect. - **Anonymous** — click "Continue without an identity" and confirm a successful redirect. 5. In the MCP config view, toggle `mcp_server_auth_mode` between `headers`, `both`, and `oauth` and verify the correct warnings appear and the OAuth2 server settings section shows/hides accordingly. ```sh cd ui pnpm i pnpm build ``` ## Breaking changes - [x] Yes - [ ] No Setting `mcp_server_auth_mode` to `oauth` disables virtual key and header-based MCP authentication immediately. All existing MCP integrations using VK, api-key, or session headers will stop working until clients re-authenticate via the OAuth consent flow. Setting the mode back to `headers` from `both` or `oauth` invalidates all previously issued OAuth JWTs and refresh tokens. ## Security considerations - The temp token (`#t=…`) is passed in the URL fragment and never sent to the server by the browser directly; it is extracted client-side and attached as a custom header (`X-Bifrost-Temp-Token`), limiting exposure in server logs. - `setSuppressGlobal401` is called when restoring a temp token from `sessionStorage` after a login redirect to prevent the global 401 handler from clearing the session prematurely during the consent flow. - The consent page is intentionally accessible without a dashboard session; all authorization is enforced server-side via the flow ID and temp token. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [ ] I added/updated tests where appropriate - [ ] I updated documentation where needed - [ ] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable

Summary
Adds an OAuth2 consent page and supporting configuration to allow MCP clients (e.g.
claude mcp add) to authenticate via a browser-based OAuth flow. Anonymous visitors arrive at/oauth/consent?flow=<id>with a short-lived temp token embedded in the URL fragment, choose how they want to identify themselves (signed-in user, virtual key, or anonymous session), and are redirected back to the MCP client with an authorization code.Changes
/oauth/consentroute — New standalone page (layout.tsx+page.tsx) that renders outside the dashboard chrome. It wraps itself withThemeProvider,ReduxProvider,NuqsAdapter, andToasterdirectly since it does not inherit them fromClientLayout.TempTokenScopeextracts the#t=…fragment and attaches it asX-Bifrost-Temp-Tokenon every API call so the consent APIs can authenticate the anonymous visitor.ConsentViewfetches the flow details and presents up to three authentication options: continuing as the signed-in user, entering a virtual key, or proceeding anonymously. It preserves the temp token across the login redirect viasessionStorageso users who choose to sign in are returned to the correct flow.oauth2ConsentApi— New RTK Query endpoints (GETandPUT/oauth2/consent/flows/:flowId) for fetching flow details and submitting the chosen identity mode.mcp_server_auth_modeselector (headers/both/oauth) with contextual warnings: switching tooauthdisables VK/header access; downgrading back toheadersrevokes all existing OAuth JWTs. OAuth2 server settings (issuer URL, auth code TTL, access token TTL) are revealed when the mode isbothoroauth.CoreConfigtypes — Addedmcp_server_auth_modeandoauth2_server_config(issuer URL, auth code TTL, access token TTL) to the config type and dirty-check logic.loginGoto—/oauth/consentis now treated as a valid post-login redirect destination.Type of change
Affected areas
How to test
claude mcp add --transport sse <bifrost-url>/mcp(or equivalent). The client should redirect to/oauth/consent?flow=<id>#t=<token>.sk-bf-…key and confirm a successful redirect.mcp_server_auth_modebetweenheaders,both, andoauthand verify the correct warnings appear and the OAuth2 server settings section shows/hides accordingly.cd ui pnpm i pnpm buildBreaking changes
Setting
mcp_server_auth_modetooauthdisables virtual key and header-based MCP authentication immediately. All existing MCP integrations using VK, api-key, or session headers will stop working until clients re-authenticate via the OAuth consent flow. Setting the mode back toheadersfrombothoroauthinvalidates all previously issued OAuth JWTs and refresh tokens.Security considerations
#t=…) is passed in the URL fragment and never sent to the server by the browser directly; it is extracted client-side and attached as a custom header (X-Bifrost-Temp-Token), limiting exposure in server logs.setSuppressGlobal401is called when restoring a temp token fromsessionStorageafter a login redirect to prevent the global 401 handler from clearing the session prematurely during the consent flow.Checklist
docs/contributing/README.mdand followed the guidelines