Commit graph

3 commits

Author SHA1 Message Date
Danny Avila
428ef2eb15
🏢 feat: Multi-Tenant Data Isolation Infrastructure (#12091)
* chore: imports

* chore: optional chaining in `spendTokens.spec.ts`

* feat: Add tenantId field to all MongoDB schemas for multi-tenant isolation

  - Add AsyncLocalStorage-based tenant context (`tenantContext.ts`) for
    request-scoped tenantId propagation without modifying method signatures
  - Add Mongoose `applyTenantIsolation` plugin that injects `{ tenantId }`
    into all query filters when tenant context is present, with
    `TENANT_ISOLATION_STRICT` env var for fail-closed production mode
  - Add optional `tenantId` field to all 28 collection schemas
  - Update all compound unique indexes to include tenantId (email, OAuth IDs,
    role names, serverName, conversationId+user, messageId+user, etc.)
  - Apply tenant isolation plugin in all 28 model factories
  - Add `tenantId?: string` to all TypeScript document interfaces

  Behaviorally inert — transitional mode (default) passes through all queries
  unchanged. No migration required for existing deployments.

* refactor: Update tenant context and enhance tenant isolation plugin

- Changed `tenantId` in `TenantContext` to be optional, allowing for more flexible usage.
- Refactored `runAsSystem` function to accept synchronous functions, improving usability.
- Introduced comprehensive tests for the `applyTenantIsolation` plugin, ensuring correct tenant filtering in various query scenarios.
- Enhanced the plugin to handle aggregate queries and save operations with tenant context, improving data isolation capabilities.

* docs: tenant context documentation and improve tenant isolation tests

- Added detailed documentation for the `tenantStorage` AsyncLocalStorage instance in `tenantContext.ts`, clarifying its usage for async tenant context propagation.
- Updated tests in `tenantIsolation.spec.ts` to improve clarity and coverage, including new tests for strict mode behavior and tenant context propagation through await boundaries.
- Refactored existing test cases for better readability and consistency, ensuring robust validation of tenant isolation functionality.

* feat: Enhance tenant isolation by preventing tenantId mutations in update operations

- Added a new function to assert that tenantId cannot be modified through update operators in Mongoose queries.
- Implemented middleware to enforce this restriction during findOneAndUpdate, updateOne, and updateMany operations.
- Updated documentation to reflect the new behavior regarding tenantId modifications, ensuring clarity on tenant isolation rules.

* feat: Enhance tenant isolation tests and enforce tenantId restrictions

- Updated existing tests to clarify behavior regarding tenantId preservation during save and insertMany operations.
- Introduced new tests to validate that tenantId cannot be modified through update operations, ensuring strict adherence to tenant isolation rules.
- Added checks for mismatched tenantId scenarios, reinforcing the integrity of tenant context propagation.
- Enhanced test coverage for async context propagation and mutation guards, improving overall robustness of tenant isolation functionality.

* fix: Remove duplicate re-exports in utils/index.ts

Merge artifact caused `string` and `tempChatRetention` to be exported
twice, which produces TypeScript compile errors for duplicate bindings.

* fix: Resolve admin capability gap in multi-tenant mode (TODO #12091)

- hasCapabilityForPrincipals now queries both tenant-scoped AND
  platform-level grants when tenantId is set, so seeded ADMIN grants
  remain effective in tenant mode.
- Add applyTenantIsolation to SystemGrant model factory.

* fix: Harden tenant isolation plugin

- Add replaceGuard for replaceOne/findOneAndReplace to prevent
  cross-tenant document reassignment via replacement documents.
- Cache isStrict() result to avoid process.env reads on every query.
  Export _resetStrictCache() for test teardown.
- Replace console.warn with project logger (winston).
- Add 5 new tests for replace guard behavior (46 total).

* style: Fix import ordering in convo.ts and message.ts

Move type imports after value imports per project style guide.

* fix: Remove tenant isolation from SystemGrant, stamp tenantId in replaceGuard

- SystemGrant is a cross-tenant control plane whose methods handle
  tenantId conditions explicitly. Applying the isolation plugin
  injects a hard equality filter that overrides the $and/$or logic
  in hasCapabilityForPrincipals, making platform-level ADMIN grants
  invisible in tenant mode.
- replaceGuard now stamps tenantId into replacement documents when
  absent, preventing replaceOne from silently stripping tenant
  context. Replacements with a matching tenantId are allowed;
  mismatched tenantId still throws.

* test: Add multi-tenant unique constraint and replace stamping tests

- Verify same name/email can exist in different tenants (compound
  unique index allows it).
- Verify duplicate within same tenant is rejected (E11000).
- Verify tenant-scoped query returns only the correct document.
- Update replaceOne test to assert tenantId is stamped into
  replacement document.
- Add test for replacement with matching tenantId.

* style: Reorder imports in message.ts to align with project style guide

* feat: Add migration to drop superseded unique indexes for multi-tenancy

Existing deployments have single-field unique indexes (e.g. { email: 1 })
that block multi-tenant operation — same email in different tenants
triggers E11000. Mongoose autoIndex creates the new compound indexes
but never drops the old ones.

dropSupersededTenantIndexes() drops all 19 superseded indexes across 11
collections. It is idempotent, skips missing indexes/collections, and
is a no-op on fresh databases.

Must be called before enabling multi-tenant middleware on an existing
deployment. Single-tenant deployments are unaffected (old indexes
coexist harmlessly until migration runs).

Includes 11 tests covering:
- Full upgrade simulation (create old indexes, drop them, verify gone)
- Multi-tenant writes work after migration (same email, different tenant)
- Intra-tenant uniqueness preserved (duplicate within tenant rejected)
- Fresh database (no-op, no errors)
- Partial migration (some collections exist, some don't)
- SUPERSEDED_INDEXES coverage validation

* fix: Update systemGrant test — platform grants now satisfy tenant queries

The TODO #12091 fix intentionally changed hasCapabilityForPrincipals to
match both tenant-scoped AND platform-level grants. The test expected
the old behavior (platform grant invisible to tenant query). Updated
test name and expectation to match the new semantics.

* fix: Align getCapabilitiesForPrincipal with hasCapabilityForPrincipals tenant query

getCapabilitiesForPrincipal used a hard tenantId equality filter while
hasCapabilityForPrincipals uses $and/$or to match both tenant-scoped
and platform-level grants. This caused the two functions to disagree
on what grants a principal holds in tenant mode.

Apply the same $or pattern: when tenantId is provided, match both
{ tenantId } and { tenantId: { $exists: false } }.

Adds test verifying platform-level ADMIN grants appear in
getCapabilitiesForPrincipal when called with a tenantId.

* fix: Remove categories from tenant index migration

categoriesSchema is exported but never used to create a Mongoose model.
No Category model factory exists, no code constructs a model from it,
and no categories collection exists in production databases. Including
it in the migration would attempt to drop indexes from a non-existent
collection (harmlessly skipped) but implies the collection is managed.

* fix: Restrict runAsSystem to async callbacks only

Sync callbacks returning Mongoose thenables silently lose ALS context —
the system bypass does nothing and strict mode throws with no indication
runAsSystem was involved. Narrowing to () => Promise<T> makes the wrong
pattern a compile error. All existing call sites already use async.

* fix: Use next(err) consistently in insertMany pre-hook

The hook accepted a next callback but used throw for errors. Standardize
on next(err) for all error paths so the hook speaks one language —
callback-style throughout.

* fix: Replace optional chaining with explicit null assertions in spendTokens tests

Optional chaining on test assertions masks failures with unintelligible
error messages. Add expect(result).not.toBeNull() before accessing
properties, so a null result produces a clear diagnosis instead of
"received value must be a number".
2026-03-10 23:15:54 -04:00
Danny Avila
a2fc7d312a
🏗️ refactor: Extract DB layers to data-schemas for shared use (#7650)
* refactor: move model definitions and database-related methods to packages/data-schemas

* ci: update tests due to new DB structure

fix: disable mocking `librechat-data-provider`

feat: Add schema exports to data-schemas package

- Introduced a new schema module that exports various schemas including action, agent, and user schemas.
- Updated index.ts to include the new schema exports for better modularity and organization.

ci: fix appleStrategy tests

fix: Agent.spec.js

ci: refactor handleTools tests to use MongoMemoryServer for in-memory database

fix: getLogStores imports

ci: update banViolation tests to use MongoMemoryServer and improve session mocking

test: refactor samlStrategy tests to improve mock configurations and user handling

ci: fix crypto mock in handleText tests for improved accuracy

ci: refactor spendTokens tests to improve model imports and setup

ci: refactor Message model tests to use MongoMemoryServer and improve database interactions

* refactor: streamline IMessage interface and move feedback properties to types/message.ts

* refactor: use exported initializeRoles from `data-schemas`, remove api workspace version (this serves as an example of future migrations that still need to happen)

* refactor: update model imports to use destructuring from `~/db/models` for consistency and clarity

* refactor: remove unused mongoose imports from model files for cleaner code

* refactor: remove unused mongoose imports from Share, Prompt, and Transaction model files for cleaner code

* refactor: remove unused import in Transaction model for cleaner code

* ci: update deploy workflow to reference new Docker Dev Branch Images Build and add new workflow for building Docker images on dev branch

* chore: cleanup imports
2025-05-30 22:18:13 -04:00
Ruben Talstra
b51cd21b3c
📦 refactor: Move DB Models to @librechat/data-schemas (#6210)
* 🚀 feat: Introduce data schemas and refactor models to use @librechat/data-schemas

* 🚀 feat: Add installation step for Data Schemas Package in backend review workflow

* chore: Add `data-schemas` package to update/rebuild packages scripts

* chore: Update Dockerfile to include data-schemas package build process

* fix: add missing @rollup/plugin-typescript package

* chore: Add GitHub Actions workflow for publishing data-schemas package

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2025-03-07 11:55:44 -05:00