feat: DMS connectors — iManage and NetDocuments behind a pluggable interface - #44
Open
amal66 wants to merge 1 commit into
Open
feat: DMS connectors — iManage and NetDocuments behind a pluggable interface#44amal66 wants to merge 1 commit into
amal66 wants to merge 1 commit into
Conversation
…terface Native document-management-system connectors: a small DMSConnector interface (lib/dms/adapter.ts) with a kind-keyed registry (lib/dms/index.ts) and three implementations — a deterministic in-memory FakeDMSAdapter plus cloud iManage (Work REST /api/v2) and NetDocuments (/v2 cabinets) adapters. Persistence, secrets, and OAuth mirror the user MCP connector machinery: dms_connectors / dms_connector_oauth_tokens / _oauth_states tables mirror the user_mcp_* tables, secrets reuse the exact AES-256-GCM scheme from lib/mcp/client.ts, every egress goes through the SSRF-guarded guardedFetch, and imports land through the existing upload pipeline as a documents row + V1 document_versions row (source "dms_import") with a dms_document_links row recording external provenance for round-trip export. Mechanical port of the fork feature (b3166dd) onto the upstream layout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Law firms keep their documents in a document management system — iManage or NetDocuments — and forcing every file through a manual upload breaks how they actually work. This PR teaches Mike to read documents where they live: a user connects their DMS once (OAuth), then browses/searches it and pulls a document straight into a project. The imported file flows through the exact same upload pipeline as a drag-and-drop (documents row + V1 version, Office→PDF rendition), is tagged
source: "dms_import", and remembers where it came from so its edited version can be pushed back to the DMS as a new version.The vendors sit behind a small pluggable
DMSConnectorinterface with a kind-keyed registry — the DMS analog of the existing MCP connector machinery — so a new vendor is one adapter class + oneregisterDmsAdaptercall, with no caller changes. A deterministic in-memoryFakeDMSAdapterbacks the test suite and doubles as an egress-free demo backend.Changes
backend/src/lib/dms/adapter.ts— theDMSConnectorcontract:authenticate/listFolders/search/fetchDocument(bytes + metadata + version) /exportDocument(with versioning) /checkReady.backend/src/lib/dms/index.ts— kind-keyed adapter registry (getDmsAdapter/registerDmsAdapter/listDmsAdapters), cloud-kind classification.backend/src/lib/dms/fake.ts— in-memoryFakeDMSAdapter(deterministic, no egress).backend/src/lib/dms/imanage.ts,netdocuments.ts— cloud adapters (iManage Work REST/api/v2, NetDocuments/v2cabinets), isolated behind the interface.backend/src/lib/dms/http.ts— shared egress helpers; every outbound request goes through the MCPguardedFetch(HTTPS-only, private-IP SSRF guard,redirect: "manual"), downloads capped at the upload size ceiling.backend/src/lib/dms/oauth.ts— auth-code + refresh OAuth (PKCE S256, hashed one-time state, 60s refresh skew), structurally mirroringlib/mcp/oauth.ts; credentials fromIMANAGE_OAUTH_*/NETDOCS_OAUTH_*env vars.backend/src/lib/dms/crypto.ts— re-exports the exact MCPencryptString/decryptString; no new crypto.backend/src/lib/dms/servers.ts— connector CRUD, adapter resolution, sync/search/import/export orchestration, air-gap gating, project authorization viacheckProjectAccess, SSRF validation of the tenant base URL on save (same ascreateUserMcpConnector).backend/src/lib/dms/import.ts— import/export wiring into the upload pipeline +dms_document_linksprovenance rows.backend/src/lib/dms/types.ts,backend/src/lib/dmsConnectors.ts— row/summary types and the stable barrel export (mirrorslib/mcpConnectors.ts).backend/src/lib/airgap.ts— 8-lineisAirgapped()helper; cloud DMS kinds are refused whenAIRGAPPED=true(the Fake stays usable).backend/src/routes/user.ts—/user/dms-connectorsroutes (list/get/create/patch/delete, oauth start/callback, sync, search, import), same auth posture and error idiom as the neighboring/user/mcp-connectorsroutes (requireAuthon reads, +requireMfaIfEnrolledon writes, unauthenticated state-validated OAuth callback).backend/src/routes/documents.ts— exportedcreateDocumentFromUpload: the initial-upload pipeline (same steps ashandleDocumentUpload) callable outside an Express handler, parameterized on the versionsourceso DMS imports land asdms_import. Existing handlers untouched.backend/src/index.ts— startup alias:DMS_CONNECTORS_ENCRYPTION_SECRET→MCP_CONNECTORS_ENCRYPTION_SECRETwhen the latter is unset, so operators can name the secret after the feature without a second crypto path.backend/migrations/20260717_01_dms_connectors.sql—dms_connectors,dms_connector_oauth_tokens,dms_connector_oauth_states(column-for-column mirrors of theuser_mcp_*tables),dms_document_links,'dms_import'added to thedocument_versions.sourcecheck. RLS enabled, anon/authenticated revoked, guarded/re-runnable.backend/schema.sql— same tables + source-check value, kept in lockstep with the migration.backend/src/lib/dms/__tests__/— 5 deterministic Vitest suites (36 tests) + an in-memory Supabase stand-in (fakeDb.ts).backend/tsconfig.json— exclude__tests__/*.test.tsfrom the production build (same two-line change as the other test-bearing PRs).Why
lib/mcp/client.tscrypto + guarded fetch,lib/storage,lib/convert,lib/access, nodecrypto).backend/package.jsonis untouched./user/dms-connectorsendpoints exist but are inert until a user creates a connector; the in-memoryfakekind works without any env (it is the test/demo backend and has no egress); the cloud kinds additionally require per-vendor OAuth env credentials (IMANAGE_OAUTH_*/NETDOCS_OAUTH_*) before they can do anything. The migration is additive (new tables + one additional allowed enum value in thedocument_versions.sourcecheck).Testing
cd backend && npm install && npm run build— tsc green as committed.npx vitest run src/lib/dms— 5 files, 36/36 passed (Fake adapter contract + registry swap; iManage and NetDocuments against mockeddns/promises+fetch— auth, folders, search, fetch-with-metadata/version, export-new-version, redirect:"manual"/SSRF private-IP rejection; OAuth start/callback/refresh with encrypted-at-rest assertions; air-gap refusal per kind).Provenance
All added lines are mechanical ports of amal66/mike@origin/main (commit b3166dd):
apps/api/src/lib/dms/**andapps/api/src/lib/dmsConnectors.ts→backend/src/lib/(14 files byte-identical), fork migration20260701000004_dms_connectors.sql→backend/migrations/20260717_01_dms_connectors.sql(statements verbatim; header comment re-pointed at this repo's migration filenames), route hunks from the fork'suser.routes.ts/user.dms.tsre-expressed in this repo's monolithicroutes/user.tsidiom (inline try/catch +console.error, matching the adjacent MCP routes), and the fork'screateDocumentFromUpload(modules/documents/documents.upload.ts) →routes/documents.ts. Exceptions, all mechanical adaptations to what upstream lacks: forkloggercalls →console.error; forklib/env.tszod schema → directprocess.envreads + the startup alias insrc/index.ts;org_idcolumn reference and org resolution stripped (no organizations feature here); fork-only queue-deferral/embedding-enqueue calls and thedocuments.filenamecolumn write stripped fromcreateDocumentFromUpload;lib/airgap.tscopied verbatim from the fork (8 lines, required by the air-gap gating and its tests); one fork test fixture (fakeEnv.ts, a stub of the fork-onlylib/env) dropped along with itsvi.mock. The fork's route-level Supabase integration test was not ported (no integration harness in this repo; consistent with the other feature PRs).Credits & prior art
lib/dms/crypto.tsre-exports the MCPencryptString/decryptString) descends from the HKDF + per-record-salt scheme fix: replace SHA-256 with HKDF + per-row salt for user API key encryption Open-Legal-Products/mike#76 introduced for user API keys; the fork's HKDF commit records fix: replace SHA-256 with HKDF + per-row salt for user API key encryption Open-Legal-Products/mike#76 as its precedent.🤖 Generated with Claude Code
https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC