Connections own their credentials

Migration 0038 moves every stored secret (OAuth tokens, MCP OAuth
tokens and client registrations, API keys) into the connection's
encrypted envelope, links API-key tools to one connection per distinct
credential, allows several accounts per provider, and adds
credential_mode to sources and tools. OAuth MCP tools keep resolving
each member's own token, as they did before.

docsgpt.connectors.service is now the only reader of OAuth tokens:
get_valid_token_info refreshes under a row lock and persists rotated
refresh tokens, and a revoked grant flags the connection, pauses its
sources and notifies the owner. Loaders build from a connection
(BaseConnectorLoader.from_connection), so scheduled sync covers Drive,
SharePoint and Confluence sources with no browser. S3 and Reddit keys
stay on the connection instead of in remote_data.

New endpoints: POST /api/connections, /setup, /reconnect,
/picker-token, /claim, DELETE /api/connections/<id>, per-action
permissions and MCP refresh-tools. Upload, file listing, sync and
validate-session take a connection_id; session tokens keep working for
this release. The tool executor reads credentials from the resolved
connection (owner or member mode) and pauses on a Connect card when a
connection needs signing in. docsgpt connectors reencrypt rewrites
stored credentials after a key rotation.
This commit is contained in:
arc53-machine committed 2026-09-28 17:03:09 +01:00
1 parent 667d4bbab0
commit 3511004f50
54 files changed
+4007 -754

No files matched your search

@@ -66,7 +66,8 @@ class TestSharePointLoaderLoadFileById:
"""Test _load_file_by_id method."""
@patch("docsgpt.parser.connectors.share_point.loader.requests.get")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.get_token_info_from_session")
@patch("docsgpt.connectors.service.connection_id_for_session_token", new=lambda token: "conn-1")
@patch("docsgpt.connectors.service.get_valid_token_info")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.__init__", return_value=None)
@patch("docsgpt.parser.connectors.share_point.loader.SharePointLoader._ensure_valid_token")
def test_load_file_by_id_includes_size_in_select(self, mock_ensure_token, mock_auth_init, mock_get_token, mock_get):
@@ -97,7 +98,8 @@ class TestSharePointLoaderLoadFileById:
assert "size" in params["$select"]
@patch("docsgpt.parser.connectors.share_point.loader.requests.get")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.get_token_info_from_session")
@patch("docsgpt.connectors.service.connection_id_for_session_token", new=lambda token: "conn-1")
@patch("docsgpt.connectors.service.get_valid_token_info")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.__init__", return_value=None)
@patch("docsgpt.parser.connectors.share_point.loader.SharePointLoader._ensure_valid_token")
def test_load_file_by_id_returns_document_with_size(self, mock_ensure_token, mock_auth_init, mock_get_token, mock_get):
@@ -134,7 +136,8 @@ class TestSharePointLoaderListItems:
"""Test _list_items_in_parent method."""
@patch("docsgpt.parser.connectors.share_point.loader.requests.get")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.get_token_info_from_session")
@patch("docsgpt.connectors.service.connection_id_for_session_token", new=lambda token: "conn-1")
@patch("docsgpt.connectors.service.get_valid_token_info")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.__init__", return_value=None)
@patch("docsgpt.parser.connectors.share_point.loader.SharePointLoader._ensure_valid_token")
def test_list_items_includes_size_in_select(self, mock_ensure_token, mock_auth_init, mock_get_token, mock_get):
@@ -169,7 +172,8 @@ class TestSharePointLoaderListItems:
assert "size" in params["$select"]
@patch("docsgpt.parser.connectors.share_point.loader.requests.get")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.get_token_info_from_session")
@patch("docsgpt.connectors.service.connection_id_for_session_token", new=lambda token: "conn-1")
@patch("docsgpt.connectors.service.get_valid_token_info")
@patch("docsgpt.parser.connectors.share_point.loader.SharePointAuth.__init__", return_value=None)
@patch("docsgpt.parser.connectors.share_point.loader.SharePointLoader._ensure_valid_token")
def test_list_items_folders_include_size(self, mock_ensure_token, mock_auth_init, mock_get_token, mock_get):