Skip to content

feat: Workspace OAuth connector overrides + UI#2040

Open
edwinjosechittilappilly wants to merge 3 commits into
mainfrom
spike-connector-settings
Open

feat: Workspace OAuth connector overrides + UI#2040
edwinjosechittilappilly wants to merge 3 commits into
mainfrom
spike-connector-settings

Conversation

@edwinjosechittilappilly

@edwinjosechittilappilly edwinjosechittilappilly commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator
Screen.Recording.2026-07-07.at.1.41.33.PM.mov

Introduce workspace-level OAuth client credential overrides and admin UI. Adds a new service (src/services/connector_oauth_config_service.py) that stores/decrypts overrides, maintains an in-process cache warmed at startup, and exposes helper functions for BaseConnector resolution. Registers new internal API routes (/connectors/oauth-config GET/PUT/DELETE) and updates startup to warm the cache.

BaseConnector gains OAUTH_CREDENTIAL_KEY and resolution logic for client_id/secret (override -> workspace override -> env). OneDrive/SharePoint share a microsoft_graph key; GoogleDrive/connector now resolves creds via the centralized lookup. AuthService now supports a "test" purpose and a _handle_test_auth flow so admins can run Test Connection without persisting connections.

Frontend: add query/mutation hooks for connector OAuth config, update Connector Settings UI to manage credential groups, save/clear overrides, and run Test Connection; update auth callback and connect mutation to handle the test purpose and redirect back with UX to surface results. Also extend StatusBadge with new statuses for credential visibility.

Summary by CodeRabbit

  • New Features

    • Added support for configuring OAuth credentials at the workspace level for eligible connector groups.
    • Introduced a test-connection flow for OAuth connectors, including success/failure feedback and guided return to settings.
    • Updated connector settings navigation labels for clarity.
  • Bug Fixes

    • Improved OAuth callback handling so test connections clean up temporary state correctly and route users back to the right place.
    • Expanded connector credential status indicators to show clearer setup states.

Introduce workspace-level OAuth client credential overrides and admin UI. Adds a new service (src/services/connector_oauth_config_service.py) that stores/decrypts overrides, maintains an in-process cache warmed at startup, and exposes helper functions for BaseConnector resolution. Registers new internal API routes (/connectors/oauth-config GET/PUT/DELETE) and updates startup to warm the cache.

BaseConnector gains OAUTH_CREDENTIAL_KEY and resolution logic for client_id/secret (override -> workspace override -> env). OneDrive/SharePoint share a microsoft_graph key; GoogleDrive/connector now resolves creds via the centralized lookup. AuthService now supports a "test" purpose and a _handle_test_auth flow so admins can run Test Connection without persisting connections.

Frontend: add query/mutation hooks for connector OAuth config, update Connector Settings UI to manage credential groups, save/clear overrides, and run Test Connection; update auth callback and connect mutation to handle the test purpose and redirect back with UX to surface results. Also extend StatusBadge with new statuses for credential visibility.
@github-actions github-actions Bot added frontend 🟨 Issues related to the UI/UX backend 🔷 Issues related to backend services (OpenSearch, Langflow, APIs) labels Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

React Doctor found 5 new issues in 2 files · 5 warnings · score 83 / 100 (Needs work) · 1 fixed · vs main

5 warnings

app/auth/callback/page.tsx

  • ⚠️ L18 Large component is hard to read and change no-giant-component
  • ⚠️ L181 Client-side redirect for navigation nextjs-no-client-side-redirect

app/settings/_components/connector-access-section.tsx

  • ⚠️ L79 Large component is hard to read and change no-giant-component
  • ⚠️ L117 Client-side redirect for navigation nextjs-no-client-side-redirect
  • ⚠️ L120 Missing effect dependencies exhaustive-deps

Reviewed by React Doctor for commit 48ac2a6. See inline comments for fixes.

@github-actions github-actions Bot added the enhancement 🔵 New feature or request label Jul 7, 2026
@github-actions github-actions Bot added enhancement 🔵 New feature or request and removed enhancement 🔵 New feature or request labels Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds workspace-level OAuth client credential overrides for OAuth-kind connectors, including a backend cache/service, CRUD API endpoints, connector credential resolution changes, a new "test" OAuth purpose/flow, and frontend UI (accordion, status badges, query/mutation hooks) for configuring and testing overrides.

Changes

OAuth credential override feature

Layer / File(s) Summary
OAuth config cache/service and startup warm-up
src/services/connector_oauth_config_service.py, src/services/startup_orchestrator.py
New service module caches, warms, reads, sets, and clears workspace OAuth credential overrides; startup orchestrator warms the cache on boot.
Connector OAuth config API endpoints and routing
src/api/connectors.py, src/app/routes/internal.py
New request body model plus GET/PUT/DELETE endpoints for OAuth credential overrides, registered under /connectors/oauth-config with permission checks.
Connector credential resolution via overrides
src/connectors/base.py, src/connectors/google_drive/connector.py, src/connectors/onedrive/connector.py, src/connectors/sharepoint/connector.py
BaseConnector resolves client id/secret via config, cached override, then env var; Google Drive uses the new resolution methods; OneDrive/SharePoint share an microsoft_graph override key.
Test-connection OAuth purpose in auth service
src/services/auth_service.py
init_oauth accepts a "test" purpose and resolves credentials via connector instances; handle_oauth_callback routes test purpose to a new handler that deletes the temp connection without persisting.
Frontend connect mutation and OAuth callback page test handling
frontend/app/api/mutations/useConnectConnectorMutation.ts, frontend/app/auth/callback/page.tsx
Connect mutation sends a caller-supplied purpose and manages a test-return-tab key; callback page branches on test purpose for redirects, titles, and back-button navigation.
Frontend OAuth config query and mutation hooks
frontend/app/api/queries/useConnectorOAuthConfigQuery.ts, frontend/app/api/mutations/useConnectorOAuthConfigMutation.ts
New query hook fetches OAuth config status; new mutation hooks save/clear credential overrides and invalidate the query cache.
Connector access settings UI for OAuth credential groups
frontend/app/settings/_components/connector-access-section.tsx, frontend/app/settings/_components/settings-nav.tsx, frontend/components/ui/status-badge.tsx
Settings UI renders an accordion of OAuth credential groups with status badges, client id/secret inputs, Save/Clear/Test actions, and toast handling from URL params; nav label updated and Status type extended.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ConnectorAccessSection
  participant AuthCallbackPage
  participant AuthService
  participant ConnectorOAuthConfigService

  User->>ConnectorAccessSection: Click "Test Connection"
  ConnectorAccessSection->>AuthService: init_oauth(purpose="test")
  AuthService->>ConnectorOAuthConfigService: get_cached_client_id/secret
  AuthService-->>ConnectorAccessSection: OAuth redirect URL
  User->>AuthCallbackPage: OAuth provider redirects back
  AuthCallbackPage->>AuthService: handle_oauth_callback(purpose="test")
  AuthService->>AuthService: _handle_test_auth (delete connection)
  AuthService-->>AuthCallbackPage: test_success
  AuthCallbackPage-->>ConnectorAccessSection: redirect with oauth_test=success
  ConnectorAccessSection-->>User: show success toast
Loading

Possibly related PRs

  • langflow-ai/openrag#1654: Connector registry/plugin refactor that the new connector_oauth_config_service credential-key derivation and BaseConnector credential methods build on.
  • langflow-ai/openrag#1657: Also modifies useConnectConnectorMutation.ts's OAuth auth/init request flow.
  • langflow-ai/openrag#1782: Introduced the connector-access settings UI/component extended here with OAuth credential override and test-connection features.

Suggested reviewers: lucaseduoli, mfortman11, Wallgau

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: workspace OAuth connector overrides and the supporting UI.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch spike-connector-settings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added enhancement 🔵 New feature or request and removed enhancement 🔵 New feature or request labels Jul 7, 2026
if (!cancelled) {
redirectTimeoutId = setTimeout(() => {
if (!cancelled)
router.push(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/nextjs-no-client-side-redirect (warning)

router.push() in useEffect flashes the wrong page before redirecting.

Fix → Avoid redirects inside useEffect. Use an event handler, middleware, or server-side redirect (App Router: redirect() from next/navigation; Pages Router: getServerSideProps redirect)

Docs

return "not-configured";
}

export function ConnectorAccessSection() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/no-giant-component (warning)

Component "ConnectorAccessSection" is 385 lines long, which is hard to read & change. Split it into a few smaller components.

Fix → Pull each section into its own component so the parent is easier to read, test, and change.

Docs

} else {
toast.error("Test connection failed");
}
router.replace("/settings/connector-access");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/nextjs-no-client-side-redirect (warning)

router.replace() in useEffect flashes the wrong page before redirecting.

Fix → Avoid redirects inside useEffect. Use an event handler, middleware, or server-side redirect (App Router: redirect() from next/navigation; Pages Router: getServerSideProps redirect)

Docs

router.replace("/settings/connector-access");
// Only re-run when the query params themselves change.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

React Doctor · react-doctor/exhaustive-deps (warning)

useEffect can run with a stale router.replace & show your users old data.

Fix → Don't blindly add missing dependencies. Read the hook callback first.

Bad:
useEffect(() => {
setCount(count + 1);
}, [count]);

Better:
useEffect(() => {
setCount((currentCount) => currentCount + 1);
}, []);

If the missing value is recreated every render, move it inside the hook or stabilize it before adding it to deps.

Docs

@github-actions github-actions Bot added enhancement 🔵 New feature or request and removed enhancement 🔵 New feature or request labels Jul 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/connectors/base.py (1)

121-159: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Env-var access outside config/settings.py.

get_client_id()/get_client_secret() read credentials via os.getenv(self.CLIENT_ID_ENV_VAR) / os.getenv(self.CLIENT_SECRET_ENV_VAR) (lines 127, 155). Per repo convention, config/settings.py is the only place that should read os.environ. Since this is a dynamic, per-connector env-var name, routing it through settings is non-trivial, so please confirm whether this should be centralized or is an accepted exception for connector credential resolution.

As per path instructions: "Config values must come from config/settings.py (the only place os.environ is read); never access os.environ elsewhere in the codebase."

🤖 Prompt for 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.

In `@src/connectors/base.py` around lines 121 - 159, The OAuth credential lookup
in get_client_id() and get_client_secret() is reading environment variables
directly via os.getenv, which violates the repo rule that only
config/settings.py may access os.environ. Update BaseConnector so these methods
obtain the env-backed values through a settings/config helper instead of reading
os.getenv themselves, and preserve the current override precedence used with
get_cached_client_id() and get_cached_client_secret(). If dynamic env-var names
make centralization impossible, refactor the resolution path so the env access
still happens only in config/settings.py.

Source: Path instructions

🤖 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 `@frontend/app/settings/_components/connector-access-section.tsx`:
- Around line 231-234: The client_id input in connector-access-section stays
bound to status?.client_id whenever draft.client_id is empty, so clearing the
field visually snaps back to the saved value. Update the value logic in the
connector access form so the input uses the draft state as the source of truth
in edit mode, and only falls back to status?.client_id when no draft exists yet;
use the credentialDrafts[group.credentialKey] draft object and the client_id
input handler to keep an explicit empty string.

In `@src/services/auth_service.py`:
- Around line 142-149: The oauth credential validation in auth_service’s
connector setup assigns client_secret but never uses it, triggering Ruff F841.
Keep the get_client_secret() call in the try block for validation, but stop
binding its return value in the oauth_config/connector_instance flow so only
client_id is stored, and leave the error handling in place for
connector_class_any and the credential fetches.

In `@src/services/connector_oauth_config_service.py`:
- Around line 122-124: The `connector_oauth_config_service` is reading
environment variables directly via `os.getenv` for `CLIENT_ID_ENV_VAR` and
`CLIENT_SECRET_ENV_VAR`, which violates the repo rule that all env access must
go through `config/settings.py`. Move these lookups into `config/settings.py`
(or a settings helper there) and update the service to consume the values from
that settings interface in the `cls`/`_representative_connector_class` flow
instead of calling `os.getenv` directly.

---

Nitpick comments:
In `@src/connectors/base.py`:
- Around line 121-159: The OAuth credential lookup in get_client_id() and
get_client_secret() is reading environment variables directly via os.getenv,
which violates the repo rule that only config/settings.py may access os.environ.
Update BaseConnector so these methods obtain the env-backed values through a
settings/config helper instead of reading os.getenv themselves, and preserve the
current override precedence used with get_cached_client_id() and
get_cached_client_secret(). If dynamic env-var names make centralization
impossible, refactor the resolution path so the env access still happens only in
config/settings.py.
🪄 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

Run ID: 242ecf33-d183-42d1-b38d-596064c07e35

📥 Commits

Reviewing files that changed from the base of the PR and between 23a01d6 and 48ac2a6.

📒 Files selected for processing (16)
  • frontend/app/api/mutations/useConnectConnectorMutation.ts
  • frontend/app/api/mutations/useConnectorOAuthConfigMutation.ts
  • frontend/app/api/queries/useConnectorOAuthConfigQuery.ts
  • frontend/app/auth/callback/page.tsx
  • frontend/app/settings/_components/connector-access-section.tsx
  • frontend/app/settings/_components/settings-nav.tsx
  • frontend/components/ui/status-badge.tsx
  • src/api/connectors.py
  • src/app/routes/internal.py
  • src/connectors/base.py
  • src/connectors/google_drive/connector.py
  • src/connectors/onedrive/connector.py
  • src/connectors/sharepoint/connector.py
  • src/services/auth_service.py
  • src/services/connector_oauth_config_service.py
  • src/services/startup_orchestrator.py

Comment on lines +231 to +234
const draft = credentialDrafts[group.credentialKey] ?? {
client_id: "",
client_secret: "",
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Client ID field cannot be visually cleared while editing.

draft.client_id defaults to "", and the input falls back to status?.client_id whenever draft.client_id is falsy (line 267). Once a user backspaces the field to empty, the displayed value snaps back to the previously-saved client ID instead of staying blank, making it look like the edit didn't register.

🐛 Proposed fix
                 const draft = credentialDrafts[group.credentialKey] ?? {
-                  client_id: "",
+                  client_id: status?.client_id ?? "",
                   client_secret: "",
                 };
                           <Input
                             id={`${group.credentialKey}-client-id`}
-                            value={draft.client_id || status?.client_id || ""}
+                            value={draft.client_id}
                             onChange={(e) =>
                               setDraft({ client_id: e.target.value })
                             }

Also applies to: 265-274

🤖 Prompt for 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.

In `@frontend/app/settings/_components/connector-access-section.tsx` around lines
231 - 234, The client_id input in connector-access-section stays bound to
status?.client_id whenever draft.client_id is empty, so clearing the field
visually snaps back to the saved value. Update the value logic in the connector
access form so the input uses the draft state as the source of truth in edit
mode, and only falls back to status?.client_id when no draft exists yet; use the
credentialDrafts[group.credentialKey] draft object and the client_id input
handler to keep an explicit empty string.

Comment on lines +142 to +149
try:
connector_instance = connector_class_any({})
client_id = connector_instance.get_client_id()
client_secret = connector_instance.get_client_secret()
except (ValueError, NotImplementedError, RuntimeError) as e:
raise RuntimeError(
f"Missing OAuth env vars for {connector_class.__name__}. "
f"Set {client_key} and {secret_key} in the environment."
)
f"Missing OAuth credentials for {connector_class.__name__}: {e}"
) from e

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Unused client_secret fails CI (Ruff F841).

client_secret is assigned but never read (only client_id is returned in oauth_config). The get_client_secret() call is still useful as an existence check, so drop the binding to satisfy the linter while keeping validation.

🔧 Proposed fix
         try:
             connector_instance = connector_class_any({})
             client_id = connector_instance.get_client_id()
-            client_secret = connector_instance.get_client_secret()
+            # Resolve the secret to validate it is configured; it is not
+            # returned to the frontend (only client_id is).
+            connector_instance.get_client_secret()
         except (ValueError, NotImplementedError, RuntimeError) as e:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
connector_instance = connector_class_any({})
client_id = connector_instance.get_client_id()
client_secret = connector_instance.get_client_secret()
except (ValueError, NotImplementedError, RuntimeError) as e:
raise RuntimeError(
f"Missing OAuth env vars for {connector_class.__name__}. "
f"Set {client_key} and {secret_key} in the environment."
)
f"Missing OAuth credentials for {connector_class.__name__}: {e}"
) from e
try:
connector_instance = connector_class_any({})
client_id = connector_instance.get_client_id()
# Resolve the secret to validate it is configured; it is not
# returned to the frontend (only client_id is).
connector_instance.get_client_secret()
except (ValueError, NotImplementedError, RuntimeError) as e:
raise RuntimeError(
f"Missing OAuth credentials for {connector_class.__name__}: {e}"
) from e
🧰 Tools
🪛 GitHub Actions: autofix.ci / 0_ruff autofix.txt

[error] 145-145: ruff check failed (F841): Local variable client_secret is assigned to but never used. Remove assignment to unused variable client_secret.

🪛 GitHub Actions: autofix.ci / ruff autofix

[error] 145-145: ruff check failed: F841 Local variable client_secret is assigned to but never used. Help: Remove assignment to unused variable client_secret.

🪛 GitHub Actions: Lint Backend / 0_Ruff and mypy on changed files.txt

[error] 145-145: ruff check failed (F841): Local variable client_secret is assigned to but never used.

🪛 GitHub Actions: Lint Backend / Ruff and mypy on changed files

[error] 145-145: Ruff check failed (F841): Local variable client_secret is assigned to but never used.


[error] 145-145: Command failed: uv run ruff check --no-fix --output-format=github src/api/connectors.py src/app/routes/internal.py src/connectors/base.py src/connectors/google_drive/connector.py src/connectors/onedrive/connector.py src/connectors/sharepoint/connector.py src/services/auth_service.py src/services/connector_oauth_config_service.py src/services/startup_orchestrator.py

🪛 GitHub Check: Ruff and mypy on changed files

[failure] 145-145: ruff (F841)
src/services/auth_service.py:145:13: F841 Local variable client_secret is assigned to but never used
help: Remove assignment to unused variable client_secret

🤖 Prompt for 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.

In `@src/services/auth_service.py` around lines 142 - 149, The oauth credential
validation in auth_service’s connector setup assigns client_secret but never
uses it, triggering Ruff F841. Keep the get_client_secret() call in the try
block for validation, but stop binding its return value in the
oauth_config/connector_instance flow so only client_id is stored, and leave the
error handling in place for connector_class_any and the credential fetches.

Source: Linters/SAST tools

Comment on lines +122 to +124
cls = _representative_connector_class(key)
env_client_id_set = bool(cls and os.getenv(cls.CLIENT_ID_ENV_VAR))
env_client_secret_set = bool(cls and os.getenv(cls.CLIENT_SECRET_ENV_VAR))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Env access must route through config/settings.py.

os.getenv(cls.CLIENT_ID_ENV_VAR) / os.getenv(cls.CLIENT_SECRET_ENV_VAR) read os.environ directly inside a service module. Per repo convention, config/settings.py is the only place os.environ is read; expose these values there (or via a settings helper) and import them here instead.

As per path instructions: "Config values must come from config/settings.py (the only place os.environ is read); never access os.environ elsewhere in the codebase."

🤖 Prompt for 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.

In `@src/services/connector_oauth_config_service.py` around lines 122 - 124, The
`connector_oauth_config_service` is reading environment variables directly via
`os.getenv` for `CLIENT_ID_ENV_VAR` and `CLIENT_SECRET_ENV_VAR`, which violates
the repo rule that all env access must go through `config/settings.py`. Move
these lookups into `config/settings.py` (or a settings helper there) and update
the service to consume the values from that settings interface in the
`cls`/`_representative_connector_class` flow instead of calling `os.getenv`
directly.

Source: Path instructions

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

Labels

backend 🔷 Issues related to backend services (OpenSearch, Langflow, APIs) enhancement 🔵 New feature or request frontend 🟨 Issues related to the UI/UX

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant