LibreChat/api/server
Airam Hernández Hernández 277fdd2b43
🪪 feat: Optimized Entra ID Group Sync with Auto-Creation (#12606)
* feat: implement optimized Entra group sync with auto-creation

## Changes

### MUST FIX (Critical Issues) - RESOLVED

1. **BUG FIX: Prevent unintended user removal from existing groups**
   - ISSUE: db.syncUserEntraGroups() was called with only missing groups, causing removal
     from all existing Entra groups (full bidirectional sync behavior)
   - SOLUTION: Replaced with db.upsertGroupByExternalId() for each missing group followed
     by single bulkUpdateGroups() to add memberships (race-safe, idempotent)
   - BENEFIT: User memberships correctly maintained for mix of existing + new groups

2. **JSDoc @throws contradiction**
   - ISSUE: JSDoc declared function throws, but implementation catches all errors
   - SOLUTION: Removed @throws from JSDoc - function is best-effort
   - BENEFIT: Prevents unnecessary try/catch in caller code

3. **Missing test for group creation flow**
   - ISSUE: Auto-creating missing Entra groups had no test coverage
   - SOLUTION: Added regression test for mix of existing + new groups scenario
   - BENEFIT: Prevents future regressions on critical path

### SHOULD FIX (Important Improvements) - RESOLVED

4. **E11000 race condition handling**
   - SOLUTION: Upserts are idempotent and race-safe by design
   - BENEFIT: Concurrent logins no longer race each other

5. **Direct Mongoose access instead of db layer**
   - SOLUTION: Added findGroupsByExternalIds() helper to userGroup.ts
   - BENEFIT: Centralized data access, easier to add tenant scoping

6. **Serial DB round-trips on login path**
   - ISSUE: 40+ queries for user with 20 new groups
   - SOLUTION: Promise.all() for parallel upserts + single bulkUpdate
   - BENEFIT: ~10x performance improvement

7. **Graph API 429/503 throttling unhandled**
   - SOLUTION: Retry logic with exponential backoff (1s, 2s delays)
   - BENEFIT: Temporary API issues no longer cause permanent membership loss

8. **Sequential batch requests slow**
   - ISSUE: 200 groups = 10 batches × 200ms = ~2s sequential
   - SOLUTION: Promise.all() with concurrency limit (5 parallel batches)
   - BENEFIT: ~400ms total time

## Minor Fixes

- Removed dead code check
- PII removal: user._id instead of user.email in logs
- ES6 shorthand fixes
- Style consistency (blank lines)
- Projection optimization

## Verification

 npm run build - success
 npm run test:api - 61/61 passing (+ new regression test)
 npm run lint - no errors
 All feedback from danny-avila resolved

* docs: better JSDoc for the syncUserEntraGroupMemberships method

---------

Co-authored-by: Airam Hernández Hernández <airam.hernandez@intelequia.com>
2026-04-13 08:50:52 -04:00
..
controllers 📌 feat: Add Pin Support for Model Specs (#11219) 2026-04-09 18:37:25 -04:00
middleware refactor: Short-Circuit Config Override Resolution (#12553) 2026-04-07 22:38:08 -04:00
routes 🔐 fix: Add Tenant Context to Admin OAuth Callback Routes (#12579) 2026-04-08 20:04:15 -04:00
services 🪪 feat: Optimized Entra ID Group Sync with Auto-Creation (#12606) 2026-04-13 08:50:52 -04:00
utils 🏗️ refactor: Remove Redundant Caching, Migrate Config Services to TypeScript (#12466) 2026-03-30 16:49:48 -04:00
cleanup.js 🧹 refactor: Tighten Config Schema Typing and Remove Deprecated Fields (#12452) 2026-03-29 01:10:57 -04:00
experimental.js ⚖️ refactor: Split Config Route into Unauthenticated and Authenticated Paths (#12490) 2026-03-31 19:22:51 -04:00
index.js ⚖️ refactor: Split Config Route into Unauthenticated and Authenticated Paths (#12490) 2026-03-31 19:22:51 -04:00
index.spec.js 🚦 fix: 404 JSON Responses for Unmatched API Routes (#11976) 2026-02-27 22:49:54 -05:00
socialLogins.js 🔐 feat: Admin Auth Support for SAML and Social OAuth Providers (#12472) 2026-03-30 22:49:44 -04:00