Commit Graph

3098 Commits

Author SHA1 Message Date
Tom Moor b1bf7c488b chore: Drop dead collaborativeEditing column from teams (#12669)
The collaborativeEditing toggle has been unused since collaborative
editing became always-on. The column is no longer defined in the Team
model nor referenced anywhere in the codebase, so this drops it.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 22:16:08 -04:00
Tom Moor c65b020655 fix: Reject collections.update requests that include both description and data (#12648) 2026-06-11 21:30:47 -04:00
Tom Moor 3c2e9a9723 fix: Default collections created through MCP to private (#12644) 2026-06-09 08:38:34 -04:00
Tom Moor bd01a62fc1 feat: MCP template support (#12639) 2026-06-09 07:43:21 -04:00
Tom Moor a2f9962958 fix: Update validateUrlNotPrivate to match implementation in SSRF (#12636) 2026-06-08 19:35:46 -04:00
Tom Moor 053693b9d5 fix: Spurious post-import edits (#12620) 2026-06-07 21:13:23 -04:00
Tom Moor 9b8acf3efb Remove unnecessary default parameter values from function signatures (#12617)
* Fix remaining no-useless-default-assignment lint warnings

* Promote no-useless-default-assignment lint rule to error
2026-06-07 15:46:01 -04:00
Tom Moor 0d198294eb fix: Improve patch merging of links in table cells (#12614)
* fix: Improve merging of links in table cells through patch

* PR feedback
2026-06-07 12:09:44 -04:00
Tom Moor 492af6683b Add document restore functionality to MCP tools (#12575)
* Add restore_document MCP tool and archived/trashed listing

Closes the delete/restore asymmetry in the MCP server: previously documents
could be archived or trashed via delete_document but never recovered.

- Add restore_document tool to recover archived or trashed documents,
  optionally into a different collection.
- Add a status option ("archived" | "trashed") to list_documents so agents
  can discover what to restore.
- Extract the documents.restore route logic into a shared documentRestorer
  command, used by both the REST endpoint and the MCP tool.

https://claude.ai/code/session_01HpFcYtgEZJ96iaFMuGGCmc

* Use type-only import for Document in documentRestorer

https://claude.ai/code/session_01HpFcYtgEZJ96iaFMuGGCmc

* Revert archived/trashed status option on list_documents

Keeps the restore_document tool and shared documentRestorer command;
removes the list_documents status filter and its tests.

https://claude.ai/code/session_01HpFcYtgEZJ96iaFMuGGCmc

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-06 15:08:52 -04:00
Tom Moor b23a39bd39 Add email verification check during sign-in flow (#12605)
* Add email verification check during sign-in flow

* Add support for Entra External ID with OIDC standard verification claim
2026-06-06 08:01:26 -04:00
Tom Moor f329b56d0e fix: Hard break serialization for commonMark (#12603)
* fix: Hard break serialization for commonMark

* tests
2026-06-06 07:24:16 -04:00
Tom Moor 997d38a6ac fix: document.collaboratorIds iterable error (#12602) 2026-06-05 23:16:45 -04:00
Tom Moor 0c0facc2a1 perf: Avoid empty webhook processor work via cached subscription lookup (#12593)
* Avoid empty webhook processor work via cached subscription lookup

WebhookProcessor ran for every event but most teams have no matching
webhook subscription, costing an empty processor job and a database query
per event.

Cache a team's enabled subscriptions ({ id, events }) in Redis, invalidated
by model lifecycle hooks, and add an optional BaseProcessor.shouldQueue hook
consulted by the global event queue so the webhook processor only enqueues a
job when a matching subscription exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feedback

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:53:40 -04:00
Tom Moor e864684d56 fix: Private IP lookup should be invalid request rather than internal error (#12592) 2026-06-05 09:19:05 -04:00
Tom Moor bfddf4bb4c fix: Unhandled server error in MCP route (#12586) 2026-06-04 23:31:16 -04:00
Tom Moor 1cc10f5fff fix: Increase valid user-supplied URL length to 1024 (#12585)
* fix: Increase valid user-supplied URL length to 1024

* fix: Wrap URL length migration in a transaction

Wrap the multi-column changeColumn operations in a transaction so a
failure on any column rolls back the whole migration rather than leaving
the database partially migrated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 23:30:55 -04:00
Tom Moor e9e565dc2b fix: Access request logic for collection managers (#12579)
* fix: Access request logic for collection managers

* test: Exercise collection-manager path in access request regression tests

Grant the non-workspace-admin manager a collection-level Admin membership
instead of a direct document-level membership, so authorization flows
through the collection-manager path being tested for #12567.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 08:24:51 -04:00
Tom Moor a21ddc4999 Add tagging of outgoing emails (#12570)
* Add tagging of outgoing emails

* Detect SES configured via well-known service key

The isSES check only matched "amazonaws" in the host, so SES configured
through SMTP_SERVICE (e.g. "SES" or "SES-US-EAST-1") was not detected and
tagging headers were not applied.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 23:51:09 -04:00
Tom Moor 7e252f0892 fix: Add missing safeEqual to notification unsubscribe endpoints (#12551) 2026-06-01 22:07:09 -04:00
Tom Moor 224230eaa0 perf: Remove N+1 query in documents.search (#12540) 2026-05-30 18:11:00 -04:00
Tom Moor d0ede882c6 perf: More memory improvements (#12539)
* perf: Lazy import mailparser, @fast-csv, and franc deps

Moves heavy dependencies off the startup path into the narrow async code
paths that actually use them, mirroring the mammoth lazy-import change:

- mailparser: only needed for Confluence Word imports (confluenceToHtml)
- @fast-csv/parse: only needed for CSV imports (csvToMarkdown)
- franc / iso-639-3: only needed by the DocumentUpdateText worker task

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf: Lazy import jsdom dep

jsdom is one of the heaviest server dependencies but is only needed for
HTML export (ProsemirrorHelper.toHTML) and HTML import
(DocumentConverter.htmlToProsemirror). Move it to a lazy `await import`
inside those methods so its dependency tree stays off the startup path.

Both methods become async; all callers were already in async contexts.
The type-only usage in patchGlobalEnv is now an `import type`.
2026-05-30 17:31:04 -04:00
Tom Moor b189c308e5 perf: Avoid loading unused services (#12537)
* fix: Run single process when only the worker service is enabled

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf: Improve memory consumption through lazy service loading

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 16:48:31 -04:00
Tom Moor cecc9ef576 perf: Lazy import Mammoth dep (#12538) 2026-05-30 16:48:19 -04:00
Tom Moor a37bb13956 perf: Avoid redundant import lookup when presenting documents (#12529)
The FileOperation import association was fetched for every non-public
document but only used when sourceMetadata is present. Move the lookup
inside that branch to eliminate an N+1 query for documents that are not
imports, benefiting every endpoint that presents documents.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 22:46:43 -04:00
Tom Moor 60bf47ede0 fix: Prevent foreign key violation when permanently deleting a team (#12527)
The attachment cleanup loop used findAllInBatches, which advances an
OFFSET each iteration. Because the callback deletes each batch, the
remaining rows shift backwards and the advancing offset skips over them,
leaving attachments that still reference the team. team.destroy() then
failed with attachments_teamId_fkey.

Page from offset 0 until no attachments remain, and remove the now
redundant per-user attachment delete so the loop is the single
authoritative cleanup.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 20:47:18 -04:00
Tom Moor e044014cea fix: Disable webhooks when deleting associated user (#12524)
* fix: Disable webhooks created by deleted users

* Delete -> disable
2026-05-29 17:44:29 -04:00
Tom Moor fecca544f9 chore: Normalize permission logic between API/MCP doc creation (#12517) 2026-05-28 22:42:40 -04:00
Tom Moor ae5cd6a159 fix: Allow service worker to load on custom domains (#12502)
* fix: Allow service worker to load on custom domains

Add explicit worker-src 'self' so the service worker can register on
team custom domains. Without it, browsers fall back to script-src which
only lists env.URL and env.CDN_URL, blocking /static/sw.js on hosts
like docs.getoutline.com.

* fix: Switch worker-src approach to script-src 'self' for type safety

The @types/koa-helmet definitions don't include workerSrc. Add 'self'
to script-src instead — worker-src falls back to script-src per spec,
and 'self' matches the document origin on custom domains.

* fix: Properly add worker-src directive without script-src widening

Extract the CSP directives to a local variable so workerSrc can be
included despite koa-helmet's outdated type definitions missing it
(the underlying helmet supports it). Also drop @types/koa-helmet
since the package now ships its own (equivalent) types.
2026-05-28 09:07:05 -04:00
Tom Moor 6366859935 chore: Remove Koa middleware span reporting (#12501) 2026-05-27 23:18:37 -04:00
Tom Moor 76a3ba4e83 fix: Normalize IP addresses to avoid validation errors (#12500)
* fix: Normalize IP addresses to avoid validation errors on audit columns

Koa's `ctx.request.ip` can yield values that fail Sequelize's `isIP`
validation (X-Forwarded-For chains, IPv6 zone identifiers, "unknown"
from misconfigured proxies). This drops the IP metadata silently
instead of raising a 500 on Event/User writes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test: Cover IP normalization on User setters

Reviewer feedback. Also switches the column-options `set` to TypeScript
get/set accessors — the original approach was shadowed by the class
field declaration and never actually fired, which the new tests would
have caught.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 22:52:05 -04:00
Tom Moor 09e99ac98d fix: Graceful exit when import is canceled beneath import task (#12497) 2026-05-27 22:37:54 -04:00
Tom Moor 7473d5b437 fix: Allow reordering subdocuments with document-only access (#12493)
* fix: Allow reordering subdocuments with document-only access

When a user has "Manage" (or any move-eligible) permission on a parent
document but no access to its collection, the sidebar drop cursors were
hidden because they gated on collection.isManualSort, and the move
handler bailed out because it built the payload from collection.id.
Fall back to the document's own collectionId and the move policy so the
reorder UX works for sourced document memberships.

* fix: Structure not refetched
parentDocumentId not provided
2026-05-27 21:33:33 -04:00
Tom Moor a4a67f2cdd fix: Upgrade yauzl, improve stream close handling 2026-05-27 20:33:33 -04:00
Tom Moor e9e13c4819 Another rev on transaction statement timeout (#12483)
* Another rev on transaction statement timeout

* docs

* PR feedback
2026-05-27 20:28:03 -04:00
Tom Moor 0f2513346a Hardening of scope validation (#12490) 2026-05-27 18:27:34 -04:00
Tom Moor 1186ddd3c0 fix: Enable import into document with write permissions only (#12485) 2026-05-27 08:32:09 -04:00
Tom Moor 70c55e4a42 feat: Add support for code blocks in comments (#12480)
* feat: Add support for code blocks in comments

* Add code_block
2026-05-26 20:38:46 -04:00
Tom Moor 84c00cfae7 fix: Distinguish rate limiter error (#12479) 2026-05-26 20:29:56 -04:00
Tom Moor 2c3e736eb3 fix: Avoid logging error when team not found in apex auth redirect (#12478)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 20:29:18 -04:00
Tom Moor b9addda229 perf: Reduce deletion batch size (#12474) 2026-05-26 20:12:26 -04:00
Tom Moor 15213bbeb0 fix: Skip export attachments with malformed key (#12470)
Attachments whose key ends in a trailing slash have no filename
component and cause yazl to throw, aborting the entire export. Skip
them with a warning and continue the export instead.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:57:36 -04:00
Tom Moor 03950af3b7 fix: TypeError when document.collaboratorIds is null (#12471)
Guard against null collaboratorIds when persisting collaborative
updates; the DB column has no default and can be NULL on older rows.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:56:56 -04:00
Tom Moor 8989287e8a perf: Add missing indexes on foreign keys referencing documents (#12473)
* perf: Add missing indexes on foreign keys referencing documents

Cascade deletes on the documents table were forced into sequential scans
on file_operations, share_subscriptions, and access_requests because
their documentId columns lacked a usable single-column index.

Also fixes lint-staged to skip oxlint when every staged file matches an
.oxlintrc.json ignore pattern, since oxlint exits 1 in that case.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Handle oxlint no-files exit instead of mirroring ignorePatterns

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:56:46 -04:00
Tom Moor f3da1bc79e fix: Remapping internal links on import (#12461)
* fix: No remapping of internal links

closes #9584

* PR feedback, testing

* tsc
2026-05-26 07:06:56 -04:00
Tom Moor ecafd5f32a chore: Update JSON importer to use zip streaming (#12380)
* chore: Update JSON importer to use zip streaming, new importer flow

* chore: Drop teamId from import urlId collision check and remove unused internal-id scaffolding

urlId is globally unique on Document/Collection so the team scope was wrong.
Also removes leftover internal-id generation in JSONAPIImportTask that was
never used in task input/output.

* Restore classes used upstream
2026-05-25 17:03:02 -04:00
Tom Moor f9dc1a3983 fix: documents.list with Draft status filter throws database error (#12426)
* fix: documents.list with Draft status filter throws database error

The count() query referenced $memberships.id$ in WHERE but had no
membership include, causing "missing FROM-clause entry for table
memberships". The findAll path was also silently dropping drafts because
withMembershipScope defaulted to defaultScope (which filters publishedAt
!= null). Pre-fetch the user's UserMembership document IDs and filter by
id IN (...) on both find and count, and pass includeDrafts: true when
the Draft filter is active.

* Preserve template/trial filters when including drafts

* Move template/trial filters into withDrafts scope

* Revert withDrafts scope filters, apply at call site instead

Adding template/trial filters to withDrafts broke includes in places
like Share's withCollectionPermissions where the document include must
remain optional (LEFT JOIN) — adding a where promoted it to INNER JOIN
and dropped shares without a documentId.
2026-05-25 17:02:46 -04:00
Tom Moor 8d44a0fd92 chore: Migrate from JSZip to Yazl (#12408)
* chore: Migrate from JSZip to Yazl

* Add koa stream helper, PR feedback
2026-05-21 23:27:23 -04:00
Tom Moor bf62bd04b0 fix: pg_bouncer statement timeout error (#12428) 2026-05-21 23:25:51 -04:00
Tom Moor 5309e8bb01 fix: Don't report upstream OAuth provider errors to Sentry (#12425)
* fix: Don't report upstream OAuth provider errors to Sentry

TokenError and AuthorizationError from passport-oauth2 represent
input problems from the upstream provider (expired or already-redeemed
codes, access_denied, etc) rather than server bugs. Log them at warn
level and redirect with the standard auth-error notice instead of
sending to the error reporter.

* Warn-only for OAuth provider errors, keep redirect flow shared

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 22:09:20 -04:00
Tom Moor 63a6ed7f8d fix: Apply statement_timeout on request-handling processes (#12422)
* fix: Apply Postgres statement_timeout on request-handling processes

Sets `statement_timeout` to REQUEST_TIMEOUT on the Sequelize connection
pool when the process handles HTTP requests (web/api/collaboration/
websockets/admin) and does not also run worker/cron. Prevents a single
runaway query from saturating the shared Postgres instance and cascading
into timeouts across all endpoints.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Drop dead `api` service check

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Only apply statement_timeout in forked cluster workers

Skips the timeout in the master process so startup migrations driven
from `checkPendingMigrations` are not cancelled mid-execution.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 21:26:30 -04:00