Commit Graph

3069 Commits

Author SHA1 Message Date
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
Tom Moor cf488508b7 fix: Ignore "Premature close" stream errors in Sentry (#12424)
Client disconnects mid-response surface as "Premature close" errors from
Node's stream end-of-stream helper. These are expected and add noise to
Sentry, similar to the EPIPE/ECONNRESET errors already filtered.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 21:26:27 -04:00
Tom Moor df117ebad5 fix: Updating collection description via MCP does not take (#12410) 2026-05-21 17:41:37 -04:00
Copilot 841ab022a6 Sanitize Windows-invalid characters in exported filenames (#12407)
* Sanitize Windows-invalid ZIP filename characters

Agent-Logs-Url: https://github.com/outline/outline/sessions/539082bc-597f-463d-b77c-6eb1bcf9bffa

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* Refine Windows filename sanitization regex handling

Agent-Logs-Url: https://github.com/outline/outline/sessions/539082bc-597f-463d-b77c-6eb1bcf9bffa

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* PR feedback

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
2026-05-20 17:49:30 -04:00
Tom Moor 597b6d801c fix: Allow deleting failed and canceled imports (#12379)
* fix: Allow deleting failed and canceled imports

The delete policy only permitted imports in the Completed state, so the
overflow menu for Errored or Canceled imports rendered with no items.

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

* test: Cover Errored and Canceled in imports.delete

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 21:37:16 -04:00
Tom Moor ee5164290d perf: Move Markdown importer to zip stream (#12372)
* perf: Move Markdown importer to zip stream

* refactor

* refactor: Extract zip walk + tree builder into ZipHelper

Adds `ZipHelper.walk` and `ZipHelper.toFileTree` so other importers can
stream zip contents without extracting to disk. Tree construction uses
an O(1) path → node map; `./`-prefixed entries are normalized, while
dotfiles, `__MACOSX`, and `..` segments are filtered.

* PR feedback
2026-05-18 18:32:58 -04:00
Tom Moor 77cee2806c chore: getJWTToken -> getSessionToken (#12371)
* getJWTToken -> getSessionToken

Ensure expiry is included in payload

* Refactor test harness to avoid direct usage of getSessionToken
2026-05-17 16:58:52 -04:00
Tom Moor 4774fa4fd0 Weekly insights rollup (#12113)
* Weekly insights rollup

* fix: Avoid eager db instance creation in DocumentInsight model

Importing sequelize at the top level triggered createDatabaseInstance
during module load, which caused unrelated test suites that transitively
require the model to fail. Use the instance-bound this.sequelize in the
static method instead.

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

* fix: Skip soft-deleted documents in weekly insights rollup

The weekly task was deleting daily rows for soft-deleted documents
without creating a weekly replacement, since rollupPeriod filters them
out. Join to documents in both the week-discovery query and the DELETE
to keep behavior consistent — historical daily rows for deleted docs are
left for the cleanup task to remove at the retention boundary.

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

* refactor: Bind cutoff days param and add date predicate in weekly rollup

Moves CUTOFF_DAYS from string interpolation to a bound parameter and
adds a plain `date <` predicate so the planner can use the
(documentId, date, period) index before evaluating date_trunc.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 09:48:30 -04:00
Tom Moor 879d2b8198 fix: Allow connecting additional auth providers on custom domain (#12364)
* fix: Unable to link secondary auth provider on custom domain

* doc

* chore: Custom -> Apex transfer token

* Refactor, address security concerns

* Ensure OAuth intent is single-use

* Secure OAuth state actor binding

* Use scrypt for OAuth actor session binding
2026-05-16 19:56:21 -04:00
Tom Moor 82d7041b6b chore: Refactor Markdown importer to use new import pipeline (#12361)
* chore: Refactor Markdown importer to use new import pipeline

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 14:10:15 -04:00
Tom Moor a5c22bbb09 feat(mcp): Add commentCount to document info response (#12355)
* Add commentsCount to MCP document info response

* fix: refine MCP document comment counts
2026-05-16 08:42:48 -04:00
Tom Moor 2fb34a400d fix: Improve resilience of markdown importer (#12357) 2026-05-14 23:37:55 -04:00
Tom Moor 9db539dfce fix: Disabling of authorization providers with env (#12349)
* fix: Disabling of authorization providers with env

* fix: type error in authenticationProviders delete test

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 09:08:58 -04:00
Tom Moor 4a324784be Refactor MCP tests (#12347) 2026-05-13 20:57:55 -04:00
Tor Anders Johansen e325867716 feat(mcp): add fullWidth parameter to create_document and update_document tools (#12338)
The fullWidth field is already persisted on the Document model and
supported in the REST API schemas (DocumentsCreateSchema, DocumentsUpdateSchema),
but the built-in MCP server did not expose it.

This patch adds fullWidth as an optional boolean parameter to both
create_document and update_document MCP tools, passing it through to
documentCreator and documentUpdater respectively.
2026-05-13 08:32:40 -04:00
Tom Moor 7c070df942 fix: Correct locking in comment anchor for update (#12332)
* fix: Lock error on MCP anchorText comment creation

* refactor
2026-05-12 22:22:52 -04:00
Tom Moor 925a43bd36 feat: Inline comment support (#12322)
* feat: Inline comment support

* fix: wrap inline comment mark and creation in transaction with row lock

* fix: lock document row when anchoring, error on failed anchor, use uuid import
2026-05-12 21:22:38 -04:00
Tom Moor 6bd775eb84 fix: Batch document deletes when emptying trash (#12328)
* fix: Batch document deletes when emptying trash

Splits the final parentDocumentId clear and destroy in documentPermanentDeleter
into chunks of 100 to keep the exclusive lock window on the documents table
short, preventing concurrent web SELECTs from queueing behind a single large
DELETE.

* fix: Skip parentDocumentId clear for documents restored mid-flight

Re-checks deletedAt in the database before clearing parentDocumentId on
children, so a parent restored between the caller's query and now keeps its
children attached.
2026-05-12 20:31:12 -04:00
Tom Moor 42a0958322 test: Fix flaky comments.list ordering assertion (#12329)
Two comments built back-to-back could share a millisecond-precision
createdAt, leaving the DESC-ordered result non-deterministic. Pass
explicit createdAt values so the assertion on body.data[1] is stable.
2026-05-12 20:03:54 -04:00
Tom Moor 935e0bb7b9 chore: Fix all no-misused-spread lint warnings (#12327) 2026-05-12 17:30:08 -04:00
Tom Moor 871cb52a23 fix: Print includes extra blank page (#12326)
closes #12324
2026-05-12 12:15:25 +00:00
Tom Moor 58f031c7e9 fix: Crash on misconfigured file storage env (#12325)
closes #12323
2026-05-12 12:07:08 +00:00
dependabot[bot] fc01deeefd chore(deps-dev): bump oxlint-tsgolint from 0.14.2 to 0.22.1 (#12320)
* chore(deps-dev): bump oxlint-tsgolint from 0.14.2 to 0.22.1

Bumps [oxlint-tsgolint](https://github.com/oxc-project/tsgolint) from 0.14.2 to 0.22.1.
- [Release notes](https://github.com/oxc-project/tsgolint/releases)
- [Commits](https://github.com/oxc-project/tsgolint/compare/v0.14.2...v0.22.1)

---
updated-dependencies:
- dependency-name: oxlint-tsgolint
  dependency-version: 0.22.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: Switch tsconfig to bundler resolution for tsgolint 0.22.1

oxlint-tsgolint 0.22.1 removed support for moduleResolution=node10
(the alias for "node"). Switch to "bundler" with resolvePackageJsonExports
disabled so packages whose exports field omits a types condition still
resolve. Update markdown-it type imports to sub-paths since the package's
.d.mts entry only re-exports a subset of named types.

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

* fix: Resolve type-aware lint errors caught by tsgolint 0.22.1

oxlint-tsgolint 0.22.1 catches several await-thenable, no-floating-promises,
and no-meaningless-void-operator cases the prior 0.14.2 missed:

- Drop redundant inner `await` from Promise.all([await x, await y]) call sites
  so the array entries are real Promises rather than already-resolved values.
- Replace Promise.all wrappers around synchronous presenters (presentEvent,
  presentTemplate, presentPublicTeam) with plain map / direct calls.
- Wrap non-promise branches of ternaries inside Promise.all with
  Promise.resolve so the array remains thenable across both arms.
- Add `void` to the unawaited provider.connect() in the auth-failed retry
  chain, and remove `void` from the disconnect() call which returns void.

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 07:59:13 -04:00
Tom Moor 3109f49b40 feat: Allow MCP to access signed attachment urls through fetch tool (#12315)
* feat: Add ability for MCP to access signed attachment urls through fetch tool

* Potential fix for pull request finding

* fix: non-admin cannot fetch attachments
2026-05-11 22:16:20 -04:00
Tom Moor ff3b3ce552 fix: Allow empty string in optional MCP fields (#12310)
* fix: Allow empty string in optional fields

* fix: Preserve empty strings for content fields in MCP tools

Address review feedback by reverting content/text fields (description, document
text, comment text) back to z.string().optional() so callers can intentionally
clear values via "". optionalString() is reserved for identifier and query
fields where "" is not a meaningful input.
2026-05-10 10:47:24 -04:00
Tom Moor 7ff1c84530 chore: Short-circuit common scanner/crawler routes (#12306)
* Shortcircuit common scanner/crawler routes

* PR feedback, remove query strings
2026-05-09 11:32:17 -04:00
Salihu b4cbb39f17 feat: request document access (#10825)
* feat: Request document access

Allow users without permission to a document to request access. Notifies
document managers via in-app notification and email; managers can grant
or dismiss the request.

- Adds AccessRequest model, migration, policy, presenter
- Adds accessRequests.create/info/approve/dismiss endpoints
- Adds DocumentAccessRequestNotificationsTask + email
- Adds Error403 request flow with loading state and pending indicator
- Auto-opens notifications popover via ?notifications=true (used in email)
- Adds SplitButton primitive for permission selection in notifications
- Refactors useConsumeQueryParam hook

* refactor

* fix: Make approve/dismiss idempotent on access requests

Return success when the access request has already been dismissed, or
when the user already has document membership at approve time, instead
of throwing 400. Avoids racy double-clicks on notification actions
producing user-visible errors.

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

* Minor fixes

---------

Co-authored-by: Tom Moor <tom@getoutline.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 08:42:47 -04:00
Tom Moor 56c3267186 Update popularity scoring to use document_insights table as data source (#12103)
* Update popularity scoring to use document_insights as data source

* Use UTC dates and guard against future-dated insights

Derive threshold/today as UTC day boundaries to match how document_insights.date is written, and add an upper bound to prevent future-dated rollups from collapsing the decay denominator.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 23:22:00 -04:00
Tom Moor 3670a918bb chore: Catch other types of client aborted error (#12303)
* chore: Catch other types of client aborted error

* Add EPIPE
2026-05-08 22:32:54 -04:00
Mark Steward fa990a33c0 Only preconnect to S3 if it's being used (#12298) 2026-05-08 14:41:32 -04:00