90 Commits

Author SHA1 Message Date
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 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 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 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 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 46dd13fc7f Update integrations directory to color icons (#11516) 2026-02-22 02:37:04 +00:00
Tom Moor c758f0d93a chore: Upgrade Zod to version 4 (#11465) 2026-02-15 22:54:50 -05:00
Tom Moor 00fb4d1af7 chore: Update node style imports (#11277)
- crypto → node:crypto
  - fs → node:fs
  - fs/promises → node:fs/promises
  - path → node:path
  - http → node:http
  - https → node:https
  - stream → node:stream
  - buffer → node:buffer
  - url → node:url
  - os → node:os
  - net → node:net
  - dns → node:dns
  - events → node:events
  - readline → node:readline
  - querystring → node:querystring
  - util → node:util
2026-01-26 20:51:50 -05:00
Hemachandar 5584089441 feat: Figma integration (#11044)
* OAuth

* store logo

* unfurl support

* refresh token

* support for list

* embed list

* mention menu for all embeds in a list

* multi-level list

* logo

* account level connection

* tsc

* Update Icon.tsx

* coderabbit feedback

* RFC 6749 suggestion

---------

Co-authored-by: Tom Moor <tom@getoutline.com>
2026-01-15 20:27:00 -05:00
Tom Moor b123762e86 chore: Remove collection.url, fix post-signin redirect (#11139) 2026-01-10 16:19:23 +00:00
Tom Moor fbd4ded5b4 feat: Add authentication provider management (#10997)
* Gemini first-pass

* Prevent post-connect login

* stash

* stash

* Add OIDC logo

* Separate security page

* test

* Update icon

* test

* ui

* Add extra guards for disabling auth provider

* refactor

* test
2025-12-24 09:09:24 -05:00
Tom Moor bf45e97641 chore: Enforce type import consistency (#10968)
* Update types

* fix circular dep

* type imports

* lint type imports and --fix
2025-12-19 23:07:02 -05:00
Tom Moor 8fc44ca681 fix: Move Slack credentials to Authorization header in token exchange (#10898) 2025-12-13 12:59:38 -05:00
Tom Moor 4777a90fa9 fix: hooks.unfurl check (#10884) 2025-12-12 22:54:56 -05:00
Tom Moor 55f21bfbb3 fix: Rapid retry behavior (#10776) 2025-12-03 20:25:12 -05:00
Hemachandar 142985c6d7 Move Document event writing to model layer (#9790)
* documents.restore, documents.unarchive

* documents.templatize

* documents.archive

* documents.unpublish

* documents.create, documents.update

* documents.title_change event

* documents.move

* documents.delete

* tsc, tests

* tsc

* Copilot feedback

---------

Co-authored-by: Tom Moor <tom@getoutline.com>
2025-11-23 20:40:45 +01:00
Tom Moor dcd7a050bd chore: Formatting (#9939) 2025-08-14 22:49:07 -04:00
Hemachandar d3eb3db7ba feat: Public sharing of collections (#9529)
* shares.info, collections.info, documents.info

* shares.list, shares.create, shares.update

* shares.sitemap

* parity with existing document shared screen

* collection share popover

* parent share and table

* collection scene

* collection link in sidebar

* sidebar and breadcrumb collection link click

* collection link click in editor

* meta

* more meta + 404 page

* map internal link, remove showLastUpdated option

* fix shares.list pagination

* show last updated

* shareLoader tests

* lint

* sidebar context for collection link

* badge in shares table

* fix existing tests

* tsc

* update failing test snapshot

* env

* signed url for collection attachments

* include collection content in SSR for screen readers

* search

* drafts can be shared

* review

* tsc, remove old shared-doc scene

* tweaks

* DRY

* refactor loader

* Remove share/collection urls

* fix: Collection overview should not be editable when viewing shared link and logged in

* Tweak public breadcrumb

* fix: Deleted documents should never be exposed through share

* empty sharedTree array where includeChildDocuments is false

* revert includeChildDocs guard for logical correctness + SSR bug fix

* fix: check document is part of share

---------

Co-authored-by: Tom Moor <tom@getoutline.com>
2025-08-03 13:07:39 -04:00
Tom Moor cc591ebc66 Conversion of User to event system (#9741)
* Conversion of User to event system

* fix

* warning

* fixes

* Skip lastActiveAt in changeset

* fix: Skip count in view changeset

* refactor: Remove userDestroyer

* refactor: Remove userSuspender

* refactor: Remove userUnsuspender

* tests
2025-07-27 13:15:21 -04:00
Neha Prasad 27d0fe86d1 fix: extract domain from user email in Slack authentication (#9742)
* fix: extract domain from user email in Slack authentication

Fixes #9641 - Slack auth fails when OIDC is enabled

* Added tests for domain extraction functionality
Added parseEmail import

* Fix Slack OIDC authentication to retain original subdomain logic (profile.team.domain) while adding domain extraction from user email

* Update slack.ts

---------

Co-authored-by: Tom Moor <tom@getoutline.com>
2025-07-27 12:24:33 -04:00
Tom Moor 772eb2f1d4 fix: randomstring dep does not work in browser with rolldown-vite (#9624)
* fix: randomstring dep does not work in browser with rolldown-vite

* fix: Last usage of randomstring, docs

* feedback
2025-07-13 09:33:16 -04:00
Tom Moor 928ec064ee fix: Viewers unable to connect personal Slack (#9614) 2025-07-11 17:41:59 -04:00
Hemachandar 29c07d6dee Migrate remaining popovers to Radix (#9555)
* reaction picker

* api key expiry date picker

* find and replace popover

* slack list item
2025-07-06 08:53:47 -04:00
codegen-sh[bot] 879c568a2c Upgrade Prettier to v3.6.2 (#9500)
* Upgrade Prettier to v3.6.2 and eslint-plugin-prettier to v5.5.1

- Upgraded prettier from ^2.8.8 to ^3.6.2 (latest version)
- Upgraded eslint-plugin-prettier from ^4.2.1 to ^5.5.1 for compatibility
- Applied automatic formatting changes from new Prettier version
- All existing ESLint and Prettier configurations remain compatible

* Applied automatic fixes

* Trigger CI

---------

Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
2025-06-28 10:22:28 -04:00
codegen-sh[bot] f3b4640c7a Update Switch component onChange handler to use boolean callback (#9385)
* Update Switch component onChange handler to use boolean callback

- Remove synthetic event handling from Switch component
- Update onChange signature to (checked: boolean) => void
- Update all call points across the codebase:
  - PublicAccess.tsx: Updated handlers for indexing, showLastModified, and published switches
  - Security.tsx: Created individual handlers for all security preferences
  - Preferences.tsx: Created individual handlers for user preferences
  - CollectionForm.tsx: Added createSwitchRegister helper for react-hook-form compatibility
  - TemplatizeDialog: Updated publish handler
  - DocumentMenu.tsx: Added handlers for embeds and full-width toggles
  - Search.tsx: Updated SearchTitlesFilter handler
  - Application.tsx: Added createSwitchRegister helper
  - Details.tsx: Updated public branding handler
  - Features.tsx: Created individual handlers for seamless edit and commenting
  - OAuthClientForm.tsx: Added createSwitchRegister helper

- Maintained backward compatibility with react-hook-form
- Improved type safety and code clarity

* Fix ESLint floating promise errors

- Add void operator to onChange call in CollectionForm.tsx
- Add void operator to document.save call in DocumentMenu.tsx

These changes fix the @typescript-eslint/no-floating-promises errors
while maintaining the existing functionality.

* Fix Switch component onChange handlers in remaining files

- Updated DocumentCopy.tsx handlers to use boolean parameter
- Updated Notifications.tsx to use closure pattern for event types
- Updated SlackListItem.tsx to use closure pattern for event names
- All TypeScript errors resolved

* Refactor createSwitchRegister into utils/forms

- Created shared utility function in app/utils/forms.ts
- Removed duplicate implementations from CollectionForm, Application, and OAuthClientForm
- Updated all usage points to use the shared utility
- Improved TypeScript typing with Record<string, unknown>
- Fixed imports and removed unused variables
- Maintained existing functionality while reducing code duplication

* Fix TypeScript errors in createSwitchRegister utility

- Updated generic constraint from Record<string, unknown> to FieldValues
- Added Path import from react-hook-form for proper type safety
- Fixed parameter type from keyof TFormData to Path<TFormData>
- Improved type compatibility with react-hook-form's UseFormRegister

Resolves TypeScript compilation errors in CI pipeline.

* Remove unnecessary handlePublishChange callbacks

- Removed handlePublishChange wrapper in DocumentCopy.tsx
- Removed handlePublishChange wrapper in TemplatizeDialog/index.tsx
- Updated Switch components to use setPublish directly
- Simplified code by leveraging boolean callback from Switch component

Since Switch now passes boolean directly, no need for intermediate callbacks.

* Address review feedback: simplify callbacks and fix fullWidth behavior

1. DocumentCopy.tsx:
   - Remove handleRecursiveChange callback wrapper
   - Use setRecursive directly with Switch component

2. DocumentMenu.tsx:
   - Add void user.save() to persist user preference
   - Add document.fullWidth = checked for optimistic update behavior

Both changes leverage the boolean callback from Switch component properly.

* Update Security.tsx

* i18n

---------

Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
2025-06-08 10:59:16 -04:00
codegen-sh[bot] 758d4edbb9 Upgrade @typescript-eslint dependencies to v8.33.0 (#9363)
* Upgrade @typescript-eslint dependencies from v6.21.0 to v8.33.0

- Updated @typescript-eslint/eslint-plugin from ^6.21.0 to ^8.33.0
- Updated @typescript-eslint/parser from ^6.21.0 to ^8.33.0
- Tested linting functionality to ensure compatibility
- This brings the latest TypeScript ESLint features and bug fixes

* lint

* tsc

---------

Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
2025-06-01 11:01:15 -04:00
Tom Moor 08b7c11461 Normalize Collection.findByPk (#9193) 2025-05-14 00:05:45 -04:00
Tom Moor d392149860 fix: Non-integration plugins missing in settings (#9167)
Other minor refactors
2025-05-10 12:45:06 -04:00
Tom Moor d0bd2baa9f Add integrations page (#9155)
* update useSettings

* Integration page skeleton

* add descriptions

* update design

* Integration page style update

* clean up

* update integration card

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Update integration icon size

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Update all integrations menu item

* update IntegrationCard to use the `Text` component

* update card status

* fix: Google analytics never shows as installed
fix: Styling tweaks
Move webhooks out of integrations

* Add breadcrumbs

* Add filtering

* refactor

* Add hover state, tweak descriptions

---------

Co-authored-by: Tess99854 <tesnimesb@gmail.com>
Co-authored-by: Mahmoud Mohammed Ali <ibn.el4ai5@gmail.com>
Co-authored-by: Mahmoud Ali <mahmoud.ali.khallaf@gmail.com>
2025-05-10 09:59:41 -04:00
Tom Moor fd984774d0 Add smart preloading of settings screens to reduce flicker (#9165) 2025-05-10 09:17:43 -04:00
Tom Moor e2dd6221f8 Extract subdomain auth redirect (#9070)
* Extract subdomain auth redirect

* docs
2025-04-27 10:55:05 -04:00
Tom Moor 7e05fc1017 Revert "Add recency boost to search results (#9038)" (#9065)
This reverts commit 2bc47cfcef.
2025-04-26 16:44:49 +00:00
Tom Moor 2bc47cfcef Add recency boost to search results (#9038)
* Add recency boost to search helpers

* Restore tests

* Use boost
2025-04-26 08:27:45 -04:00
Hemachandar c1c20f1ff9 feat: allow search without a search term (#7765)
* feat: allow search without a search term

* tests

* conditional filter visibility

* add icon to collection filter
2024-11-03 14:59:48 -08:00
Tom Moor 9680e57849 chore: Remove suppressImplicitAnyIndexErrors TS rule (#7760) 2024-10-11 12:46:46 -07:00
Tom Moor 7c9a2bbcf6 fix: Team is required for user queries from slack hooks 2024-09-12 12:39:24 -04:00
Tom Moor ff11a3c667 fix: Include plugin tests on CI (#7528)
* fix: Include plugin tests on CI
2024-09-03 19:13:42 -07:00
Apoorv Mishra 5d85a3a093 Specify time conversion unit (#7458)
* fix: specificity in time units

* fix: milliseconds -> ms
2024-08-25 18:57:45 +05:30
Tom Moor 2a502e43ef perf: Avoid count query in search where possible (#7426)
* Remove unused replacements

* perf: Avoid count query where possible

* logic
2024-08-20 18:59:32 -07:00
Tom Moor 1451f70b9e Changes plugin interface from 'roles' to 'enabled' method for more flexibility 2024-06-24 08:33:48 -04:00
Tom Moor 3d0160463c chore: Refactor client plugin management (#7053)
* Update clientside plugin management to work as server

* docs

* tsc

* Rebase main
2024-06-16 08:11:26 -07:00
Tom Moor df3cd22aee Matomo integration (#7009) 2024-06-12 05:03:38 -07:00
Tom Moor 24e4dd663b fix: Clarify functionality of personal Slack connection 2024-05-27 13:10:58 -04:00
Tom Moor 9622452b5d fix: Infinite redirect loop on return from Slack auth without logged in session 2024-05-15 20:13:32 -04:00
Tom Moor 3298a1cd7d fix: Connecting Slack channel sometimes fails with 404 2024-04-30 08:54:23 -04:00