Commit Graph

333 Commits

Author SHA1 Message Date
Tom Moor ca36451e42 Improve handling of non-HD Google logins from root domain (#12615) 2026-06-07 13:16:25 -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 9ec6b8309d chore: Improve handling of 'expected' network errors from webhooks (#12599) 2026-06-05 18:00:37 -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 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
dependabot[bot] a20c8e5371 chore(deps): bump zod from 4.3.6 to 4.4.3 (#12563)
* chore(deps): bump zod from 4.3.6 to 4.4.3

Bumps [zod](https://github.com/colinhacks/zod) from 4.3.6 to 4.4.3.
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.3.6...v4.4.3)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.4.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* fix: Make files.create file param optional in schema for zod 4.4

zod 4.4 changed z.custom() to reject undefined. Since validate runs
before multipart injects the file, validation failed with 400 on all
files.create requests. Mark the field optional and guard in the handler.

Co-Authored-By: Claude Opus 4.8 <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.8 <noreply@anthropic.com>
2026-06-02 22:34:41 -04:00
Tom Moor 334b179048 fix: Prevent Linear unfurl errors from bubbling to error tracking (#12532)
Returning the unfurl promises without awaiting them inside the try
block meant rejections (e.g. "Entity not found: Issue") escaped the
catch and were reported to error tracking. Await them so they are
caught and returned as a handled { error } result.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 07:59:05 -04:00
Tom Moor 82743b1c0a feat: Allow http webhook urls when self-hosting (#12499) 2026-05-27 22:52:15 -04:00
Tom Moor 667bfe68c5 fix: Retry Notion API 5xx errors with exponential backoff (#12481)
The Notion API can return transient 5xx errors during imports. Retry these
up to 8 times with exponential backoff, tracked separately from the existing
timeout/rate-limit retry budget.
2026-05-26 20:38:12 -04:00
Tom Moor a23b04c8fa fix: Prevent ISE when tsquery tail interleaves operator and escape chars (#12475)
When a user query produces a pg-tsquery output ending in mixed `&` and `\`
characters (e.g. `"plugins"&\`), stripping them with separate single-char
regexes in a fixed order could leave a dangling `&` operator, causing
Postgres to reject the query with "no operand in tsquery".

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 20:10:49 -04:00
Tom Moor 6bab00b92e chore(deps): upgrade octokit to v5 and @octokit/auth-app to v8 (#12472)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:47:58 -04:00
Tom Moor a1b9f900c7 perf: Avoid correlated subquery in Slack hooks user lookup (#12432)
* perf: Avoid correlated subquery in Slack hooks user lookup

Query UserAuthentication directly by indexed providerId and load the
associated User and Team, instead of driving from User.findOne with a
required hasMany include — which Sequelize translates into a correlated
subquery that scans the users table.

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

* fix: Scope Slack fallback user lookup to matching AuthenticationProvider

The fallback in findUserForRequest matched any UserAuthentication with
the same providerId, which is only unique per (providerId, userId).
A colliding external user id from another workspace or provider could
resolve a user from the wrong team. Constrain via the AuthenticationProvider
join (name = "slack", providerId = serviceTeamId).

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

* test

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 14:15:22 -04:00
Tom Moor def9da6a12 fix: Empty Notion table crashes importer (#12421)
* fix: Empty Notion table crashes importer

* Add tests
2026-05-21 21:27:02 -04:00
Tom Moor 80d90e3201 fix: Slack notifications show "Untitled" for documents without titles (#12406)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 17:49:44 -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 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
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 091346dfe8 chore: Migrate to vitest (#12272)
* wip

* Remove obsolete snapshots

* simplify

* chore(test): Convert mocks to TypeScript and tighten fetch mock types

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

* Remove unneccessary patches

* Migrate to msw instead of custom fetch mock

* Address PR review comments

- Split chained vi.useFakeTimers().setSystemTime() into separate calls.
- Switch test setup to dynamic imports so EventEmitter.defaultMaxListeners
  assignment runs before module init (static imports were hoisted above it).
- Drop redundant NODE_ENV guard in monkeyPatchSequelizeErrorsForJest; its
  sole caller already gates on env.isTest.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 21:10:51 -04:00
Tom Moor 0139b91b5d chore: Replace lodash with es-toolkit (#12281)
* chore: Replace lodash with es-toolkit

Migrate all direct lodash imports to es-toolkit/compat for a smaller,
faster, lodash-compatible utility library. Transitive lodash usage from
other packages remains unchanged.

* fix: Restore isPlainObject semantics in CanCan policy

The lodash migration aliased `isObject` to `lodash/isPlainObject` and
the codemod incorrectly mapped the local name to es-toolkit's `isObject`,
which also returns true for arrays and functions. This caused condition
objects in policy definitions to be skipped, breaking authorization
checks across the codebase.

* fix: Restore unicode-aware length counting in validators

es-toolkit/compat's size() returns string.length, while lodash's _.size()
counts unicode code points. Switch to [...value].length to preserve the
previous behavior so multi-byte characters like emoji count as one.
2026-05-06 21:03:47 -04:00
Tom Moor f50bb00b29 Refactor of OAuth account linking flows (#12246)
* Refactor of OAuth account linking flows

* PR feedback
2026-05-02 18:54:38 -04:00
Tom Moor 1f097b0fdd chore: resolve no-explicit-any lint warnings in plugins (#12237)
* chore: resolve no-explicit-any lint warnings in plugins

Replaces uses of `any` in the plugins directory with concrete types,
`unknown`, or structured type assertions, addressing the remaining
typescript-eslint(no-explicit-any) warnings flagged by oxlint.

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

* chore: address review feedback in GitLabIssueProvider

Drop trailing semicolon from log string and add early return in
`destroyNamespace` when neither `user_id` nor `full_path` is present
to avoid an unnecessary full-scan transaction.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 08:29:58 -04:00
Tom Moor 1caf7f9221 chore: Increased default model creation rate limits from 10/m to 25/m (#12236) 2026-05-01 08:14:25 -04:00
Tom Moor 57308c46af chore: resolve lint warnings (no-explicit-any, no-redundant-type-constituents, no-base-to-string) (#12209)
* chore: resolve no-redundant-type-constituents and test/mock no-explicit-any warnings

Clears 36 lint warnings: all 5 no-redundant-type-constituents, 6
no-misused-spread (via narrowing getPartitionWhereClause's return type
to WhereAttributeHash), and 25 no-explicit-any in test/mock files.

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

* chore: resolve no-base-to-string warnings in tests

Convert userProvisioner try/catch error assertions to Jest's
.rejects.toThrow() idiom, and cast webhook test body to string.

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

* chore: resolve no-explicit-any warnings in cancan and tracing

Tighten types in the cancan policy framework and tracing decorators.
Constructor / generic-function upper bounds keep `any` where TypeScript
variance requires it, scoped to single-line oxlint-disable comments.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 22:55:30 -04:00
Tom Moor f8e70c2c39 chore: resolve mechanical react-hooks/exhaustive-deps warnings (#12207)
Adds missing stable dependencies (e.g. `t`, prop callbacks, store refs,
`setFocusedCommentId`) and removes unnecessary ones across hooks where the
fix is straightforward. For the two MobX-observed `.orderedData` deps in
`History.tsx`, keeps the original deps and silences the false positive
with `eslint-disable-next-line` so the memos still recompute when the
underlying observable arrays change.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 22:06:09 -04:00
Tom Moor 4c85c4d08d chore: resolve unbound-method lint warnings in tests (#12204)
Capture jest mock references in local variables instead of asserting
against unbound method references on mocked classes/instances.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 20:50:25 -04:00
Tom Moor e29c9102af chore: resolve unbound-method warnings in NotionConverter (#12205)
Convert rich_text and rich_text_to_plaintext from static methods to
static arrow-function fields so they can be passed as map callbacks
without tripping the unbound-method lint. Neither method accesses
`this`, so behavior is unchanged.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 20:49:58 -04:00
Tom Moor f9a2cbc1b3 chore: resolve remaining unbound-method lint warnings (#12206)
* chore: resolve remaining unbound-method lint warnings

Apply targeted fixes per call pattern: arrow wrappers when passing a
method as a callback, arrow-function class fields when the method
doesn't depend on `this`, and `.bind()` when capturing for later
invocation.

Also replaces the rfc6902 hasOwnProperty re-export with a small wrapper
function so callers don't reference an unbound prototype method.

* chore: memoize history.goBack callbacks

Stable identity prevents Button re-renders and avoids re-subscribing
the global keydown handler in RegisterKeyDown when the parent renders.
2026-04-28 20:49:35 -04:00
Tom Moor 87bb79250d chore: enable typescript/restrict-template-expressions lint rule (#12199)
* chore: enable typescript/restrict-template-expressions lint rule

Coerce values of unknown type with explicit String() and tighten typing
for template literal expressions across the codebase.

* fix: restore --line-height on Card for fadeOut ::after gradient
2026-04-28 20:11:15 -04:00
Tom Moor 321b232f17 Move "Webhook" settings to table (#12119)
* Move 'Webhook' settings to table

* Add tests
2026-04-19 19:27:32 -04:00
Tom Moor 69e8aac4f1 Move "Api Keys" listing to filterable table (#12117)
* Move 'Api Keys' listing to filterable table

* Add context menu
Allow copying new keys
2026-04-19 18:12:32 -04:00
Tom Moor 60562f4f6a fix: Handle GitLab Flavored Markdown (#11930)
* fix: Handle GitLab Flavored Markdown

* PR feedback

* Harden HTML comment stripping against overlapping patterns

Loop the replacement until stable to avoid CodeQL's incomplete
multi-character sanitization alert — a single pass could leave
`<!--` residue for inputs like `<!<!-- x -->-- -->`.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 10:53:50 -04:00
Tom Moor 505082b196 fix: Correctly validate uploaded file size using "local" storage option (#12095)
* fix: Correctly validate uploaded file size using local storage option

* fix: Normalize attachment size from BIGINT before comparison

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 23:25:46 -04:00
Tom Moor 347bdb10d4 fix: Ensure OTP is bound to workspace (#12096)
* fix: Ensure OTP is bound to teamId

* fix: Address review feedback on OTP tenant scoping

- Trim whitespace in VerificationCode Redis keys to match DB lookup
  normalization.
- Redirect with invalid-code (rather than leaking a backend error)
  when no user exists for the email in the resolved team.
- Correct retrieve() JSDoc to state undefined instead of null.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 23:22:58 -04:00
Tom Moor 26f5bb9784 fix: Unable to search drafts without a collection (#12079)
* fix: Unable to search drafts without a collection

* PR feedback
2026-04-16 17:37:25 -04:00
Tom Moor d4dec42bc5 fix: Validate host parameter stored in OAuth state on failure redirect (#11956)
* fix: Validate host parameter stored in OAuth state on auth failure path

* fix: Validate OAuth state host to prevent open redirect

Sanitize the host parameter from OAuth state before using it in error
redirects. Adds userinfo stripping to parseDomain's normalizeUrl to
prevent bypasses like "subdomain.base@evil.com", validates custom
domains against registered teams, and introduces Team.findByDomain
with input normalization for consistent domain lookups.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 16:13:54 -04:00
Tom Moor b2aad71cb4 chore: Move welcome email to processor (#11939)
* chore: Move welcome email to processor

* fix: Restore welcome email on invite acceptance
2026-04-02 20:16:47 -04:00
Tom Moor b91d9e9a72 feat: Extract search into pluggable provider system (#11448)
* feat: Extract search into pluggable provider system

Refactors the monolithic SearchHelper into a pluggable search provider
architecture, enabling alternative search backends (Elasticsearch,
Turbopuffer, etc.) while preserving PostgreSQL full-text search as the
default. The SEARCH_PROVIDER env var selects the active provider.

- Add BaseSearchProvider abstract class and SearchProviderManager
- Add Hook.SearchProvider to the plugin system
- Move PostgreSQL search logic into plugins/postgres-search/
- Add SearchIndexProcessor for event-driven index sync
- Update all callers to use the provider manager directly
- Keep SearchHelper as a deprecated thin wrapper for backwards compat

Closes #11347

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

* refactor: Remove deprecated SearchHelper wrapper

All callers now use SearchProviderManager directly, so the thin
delegation wrapper is no longer needed.

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

* refactor: Rename postgres-search plugin to search-postgres

Renames the plugin folder and id so that future search provider plugins
(e.g. search-elasticsearch, search-turbopuffer) will be colocated
alphabetically.

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

* refactor: Remove special-case plugin import from SearchProviderManager

Make PluginManager.loadPlugins resilient to individual plugin load
failures so SearchProviderManager can use the standard getHooks path
without needing to directly import the search-postgres plugin.

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

* test: Add missing search provider tests for full coverage parity

Adds all tests that existed in the old SearchHelper.test.ts but were missing
from PostgresSearchProvider.test.ts, including searchTitlesForUser status
filters, collection filtering, group memberships, and sorting tests.

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

* feedback

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 23:01:26 -04:00
Tom Moor c2ccdb6fd4 fix: Prevent registration of duplicate passkeys on the same device (#11870)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:54:43 -04:00
Tom Moor 0390f30e1d Restore enterprise translations 2026-03-22 21:56:11 -04:00
Tom Moor 0ba310e027 Remove unused files and dependencies (#11850)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 18:44:51 -04:00
Tom Moor 5693618de4 Add translation hooks to transactional emails (#11785)
* First pass

* fix: Missing translations

* fix: Missing translations

* welcome

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* translations

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-20 23:28:51 -04:00
Copilot 500c3f91b0 Support GitLab work_items URL structure in unfurl integration (#11795)
* Initial plan

* Support GitLab work_items URL structure in parseUrl

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

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>
2026-03-17 22:40:47 -04:00
Tom Moor 1a893b0e45 Group sync framework (#11684)
Adds group sync from external authentication providers, allowing team group memberships to be automatically managed based on provider data on sign-in in the future.
2026-03-14 23:02:20 -04:00
Liam Stanley b1a192c078 fix: don't force prompt for Discord OAuth2 (#11757)
Signed-off-by: Liam Stanley <liam@liam.sh>
2026-03-14 19:20:13 -04:00
Copilot 22138957ab Add Project unfurl support to GitLab plugin (#11752)
* Initial plan

* Add GitLab Project unfurl support

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

* Fix TypeScript errors: add explicit return type to parseUrl

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

* tweaks

* progress

* Remove log noise

---------

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-03-14 19:14:35 -04:00
Copilot d1203408b5 Add GitHub Project V2 unfurl support (#11753)
* Initial plan

* Add GitHub Project V2 unfurl support to the GitHub plugin

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

* Various fixes

---------

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-03-14 17:13:35 -04:00
Copilot 36d555f3fb Add Linear project unfurling support (#11525)
* Initial plan

* Add Project type and unfurl implementation for Linear projects

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

* Fix linter issues - remove unused import and rename unused parameter

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

* Make actor parameter optional in unfurl helper methods

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

* fix: Resolve type errors in Linear project unfurl

Use project.status (ProjectStatus object) instead of the deprecated
project.state (string) field, add satisfies constraint, and fix
exhaustive return in unfurl switch.

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

* Determine mention type

* styling

---------

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>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 11:03:04 -04:00
Copilot 83977f85bd Use filtered fetch in Figma and Linear plugins (#11701)
* Initial plan

* chore: use filtered fetch in Figma and Linear plugins

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

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>
2026-03-09 23:04:18 -04:00
Tom Moor 257d01af89 fix: Missing check for enabled passkeys in verification endpoint (#11689) 2026-03-08 18:46:13 -04:00