Repository navigation
feat(connectors): OAuth authorize/callback flow + auto-refresh resolver (#9019) - #10087
Merged
Merged
Conversation
Establishes the architectural decision that connector credentials (OAuth refresh tokens, API keys, client secrets) must be stored via SecretsService with Fernet encryption, not as plaintext in Redis connector config dicts. - docs/adr/007-connector-oauth-token-storage.md — new ADR - docs/adr/_index.md — register ADR-007 in the index - docs/connectors/writing-a-connector.md — credential storage section with auth type table, sensitive-field contract, and testing guidance Co-Authored-By: Paperclip <noreply@paperclip.ing>
…er (#9019) Extend the ADR-007 connector-credential gate with the generic OAuth plumbing its data-source connectors consume: - oauth_flow.py: provider registry (google/microsoft/gitlab), PKCE (S256), authorize-URL builder, code exchange, and refresh-token rotation. OAuth app client creds come from operator config (AuthConfig). - ConnectorCredentialStore.store_oauth + get_access_token: persist a self-contained token bundle via SecretsService and return a valid access token, refreshing + rotating the secret in place when within the expiry skew. - knowledge_connector_oauth.py: POST .../oauth/{provider}/authorize and GET .../oauth/callback. Security: state+PKCE in knowledge Redis (GETDEL single-use), host-allowlisted redirect, HttpOnly SameSite=Lax state cookie binding the completing browser (CSRF/token-attachment defense), and an HTML/JS-escaped result page (XSS-safe). Mounted in core_routers. - ssot_config: per-provider OAuth client_id/secret settings. - Frontend: KnowledgeRepository.startConnectorOAuth + reusable ConnectorOAuthButton.vue (popup + origin-checked postMessage) for connector issues (#9003/#9004/#9011) to drop into their config step. - ADR-007: document the flow, auto-refresh, and security properties. Tests: oauth_flow (12), credential_store oauth (8 new), endpoints (9 incl. CSRF-binding + XSS), frontend repo + component (7). Credentials never touch Redis or the frontend. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6 tasks
Contributor
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
This was referenced Jun 13, 2026
Applied Black, isort, and autoflake to match code-quality checks. Triggered by workflow auto-fix-formatting.yml. Co-Authored-By: Paperclip <noreply@paperclip.ing>
9 of 21 tasks
… label (#9019) Bandit B106 (hardcoded_password_funcarg) false-positives on kwargs whose name contains 'token'/'secret' with a string literal: token_url=<provider URL> and secret_type='connector_oauth_token'. These are endpoint URLs and a SecretType label, not credentials. Annotated inline per repo convention (cf. secret.py). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…th (#9019) CodeQL py/reflective-xss (HIGH) flagged the OAuth callback result page because the postMessage payload went through a custom _js_safe_json() sanitizer that the analyzer cannot verify. Restructure so every attacker-influenced value reaches the response ONLY through html.escape (a CodeQL-recognized XSS sanitizer): - visible status: html.escape (unchanged) - postMessage payload: json.dumps -> html.escape -> data-* attribute; the inline script reads it back via JSON.parse(getAttribute(...)). No user-influenced value is interpolated into the <script> body, so nothing can break out of the HTML or script context. Removes the now-unused _js_safe_json helper. Behavior unchanged; all 9 test_connector_oauth_api tests pass incl. test_callback_escapes_reflected_error_xss. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 16, 2026
Closed
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.
Thinking Path
#9019 is the connector-group architecture gate (OAuth tokens → secrets management). Investigation found the credential-storage core already merged (PR #9079:
ConnectorCredentialStore+ API CRUD wiring +SecretType.OAUTH_REFRESH_TOKEN+ sensitive-field registry). Two ACs remained un-met and were rescoped by the accepted ADR-007 into generic, provider-agnostic plumbing: an OAuth authorize/callback flow (AC#3) and access-token auto-refresh (AC#2). This PR delivers that plumbing so the data-source connectors (#9003/#9004/#9011) can consume it — credentials never touch Redis or the frontend.What Changed
Backend
knowledge/connectors/oauth_flow.py— provider registry (Google/Microsoft/GitLab), PKCE (S256), authorize-URL builder, code exchange, refresh-token rotation. OAuth app client creds from operator config.ConnectorCredentialStore.store_oauth+get_access_token— persist a self-contained token bundle viaSecretsService; return a valid access token, refreshing + rotating the secret in place within an expiry skew (honors refresh-token rotation, e.g. GitLab).api/knowledge_connector_oauth.py—POST .../oauth/{provider}/authorizeandGET .../oauth/callback, mounted incore_routers.ssot_configAuthConfig— per-provider*_oauth_client_id/secretsettings (empty ⇒ provider disabled).Security (both flagged by automated review, fixed here)
statecookie; callback requires it to matchstatebefore consuming it (GETDEL single-use), binding the completing browser to the initiator.errorand JS-escapes the embedded JSON (<,>,&,U+2028/9).AUTOBOT_SSO_CALLBACK_HOSTS; HTTPS enforced viaAUTOBOT_ENFORCE_HTTPS_CALLBACKS.Frontend
KnowledgeRepository.startConnectorOAuth+ reusableConnectorOAuthButton.vue(popup launcher; origin-checkedpostMessage; emitsconnectedwithsecret_id). Surfacing OAuth connector types in the create wizard belongs to plugin(connectors): Google Drive data source connector #9003/plugin(connectors): Microsoft OneDrive / SharePoint data source connector #9004/plugin(connectors): GitLab / Gitea / Forgejo connector — index repositories and issues from self-hosted git #9011, which drop this button into their config step — not wired into the modal here to avoid half-wired dead UI.Docs
Acceptance criteria (#9019)
SecretType.OAUTH_REFRESH_TOKENget_access_token(this PR)Verification
pytest knowledge/connectors/tests/{test_oauth_flow,test_credential_store,test_connector_oauth_api}.py→ 41 passed (incl. CSRF-binding + XSS escaping + refresh/rotate).vitestonConnectorOAuthButton.spec.ts+KnowledgeRepository.connectors.test.ts→ 17 passed.gdrive_test.py/registry_public_api_test.py.Model Used
Claude Opus 4.8 (1M context)