Skip to content

feat: SCIM 2.0 user provisioning#35

Merged
TerrifiedBug merged 12 commits intomainfrom
feat/scim
Mar 7, 2026
Merged

feat: SCIM 2.0 user provisioning#35
TerrifiedBug merged 12 commits intomainfrom
feat/scim

Conversation

@TerrifiedBug
Copy link
Copy Markdown
Owner

Summary

  • Add SCIM 2.0 protocol support for automated user lifecycle management from identity providers (Okta, Entra ID, OneLogin, etc.)
  • Prisma migration adds scimExternalId to User and scimEnabled/scimBearerToken to SystemSettings
  • SCIM service layer handles user CRUD with proper mapping to VectorFlow's lockedAt pattern (no hard deletes)
  • API routes at /api/scim/v2/Users and /api/scim/v2/Groups with encrypted bearer token auth
  • Groups endpoints map SCIM Groups to VectorFlow Teams with member add/remove
  • Settings UI adds SCIM tab with enable/disable toggle, base URL display, token generation with one-time-show modal
  • Documentation: new SCIM guide with IdP-specific instructions, updated auth docs and SUMMARY

Test plan

  • Run npx prisma migrate dev to apply the migration
  • Navigate to Settings > SCIM as a Super Admin; verify toggle, URL display, and token generation work
  • Copy generated token; verify it cannot be retrieved after closing the dialog
  • Test SCIM endpoints with curl: GET /api/scim/v2/Users with valid/invalid bearer tokens
  • Test user creation via POST /api/scim/v2/Users with SCIM JSON payload
  • Test user deactivation via PATCH /api/scim/v2/Users/:id with active: false
  • Test group listing via GET /api/scim/v2/Groups and member management via PATCH
  • Verify disabling SCIM clears the token and rejects subsequent requests
  • Verify TypeScript compiles cleanly (npx tsc --noEmit)

@github-actions github-actions bot added documentation Improvements or additions to documentation feature and removed documentation Improvements or additions to documentation labels Mar 7, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This PR adds a complete SCIM 2.0 user provisioning integration, covering a Prisma migration, a service layer, four route files (/api/scim/v2/Users and /api/scim/v2/Groups), a tRPC settings router extension, and a settings UI tab. The implementation fits naturally into VectorFlow's existing patterns: token storage uses the project's AES-256-GCM encrypt/decrypt helpers, account deactivation maps to the lockedAt/lockedBy pattern used elsewhere, and all mutations write to the audit log.

All implementation details are correct:

  • Authentication: Bearer token validation uses crypto.timingSafeEqual for constant-time comparison, preventing timing attacks
  • User operations: Type-narrowed patch operations with case-normalized op.op field; audit logging on all mutations; lockedBy: "SCIM" recorded for both inactive creation and deactivation
  • Group operations: Azure AD value-array member removal is supported; PUT handler wrapped in atomic prisma.$transaction preventing member data loss on failure; audit logging present
  • Pagination: NaN-safe parameter handling with Number.isFinite() checks on both Users and Groups endpoints
  • Error handling: Differentiated 409 responses for email vs. externalId unique constraint violations

The implementation is production-ready.

Confidence Score: 5/5

  • All security, correctness, and data-integrity concerns have been properly addressed.
  • The SCIM implementation is complete and correct. All cryptographic operations use constant-time comparison, all mutations are audit-logged, all user state transitions properly track the lock source (lockedBy), all database operations are atomic where needed, and all external inputs are validated with proper type narrowing and bounds checking. No issues remain.
  • No files require special attention.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[IdP sends SCIM request] --> B[authenticateScim]
    B --> C{scimEnabled AND<br/>token valid?}
    C -- No --> D[401 Unauthorized]
    C -- Yes --> E{HTTP Method + Path}

    E --> F["POST /Users"]
    F --> G[scimCreateUser]
    G --> G1[user.create in DB]
    G1 --> G2[writeAuditLog<br/>scim.user_created]
    G2 --> G3[201 Created]

    E --> H["PATCH /Users/:id"]
    H --> I[scimPatchUser<br/>normalize op.op case]
    I --> I1[user.update in DB]
    I1 --> I2[writeAuditLog<br/>scim.user_patched]
    I2 --> I3[200 OK]

    E --> J["DELETE /Users/:id"]
    J --> K[scimDeleteUser<br/>soft-lock only]
    K --> K1[user.update<br/>lockedAt + lockedBy=SCIM]
    K1 --> K2[writeAuditLog<br/>scim.user_deactivated]
    K2 --> K3[204 No Content]

    E --> L["PATCH /Groups/:id"]
    L --> M[prisma transaction<br/>add or remove TeamMember]
    M --> N[writeAuditLog<br/>scim.group_patched]
    N --> O[200 OK]

    E --> P["PUT /Groups/:id"]
    P --> Q[prisma transaction<br/>deleteMany + create loop]
    Q --> R[writeAuditLog<br/>scim.group_updated]
    R --> S[200 OK]
Loading

Last reviewed commit: 46f1085

Add SCIM 2.0 protocol support for automated user lifecycle management
from identity providers (Okta, Entra ID, etc.):

- Prisma migration: scimExternalId on User, scimEnabled/scimBearerToken
  on SystemSettings
- SCIM service layer with list/get/create/update/patch/delete user ops
- API routes: /api/scim/v2/Users and /api/scim/v2/Groups with bearer
  token auth
- Groups endpoints map SCIM Groups to VectorFlow Teams with member
  add/remove via PATCH
- Settings router: enable/disable SCIM toggle, generate bearer token
- Settings UI: SCIM tab with enable toggle, base URL display, token
  generation with copy-to-clipboard modal, and IdP setup instructions
- Create docs/public/operations/scim.md with setup guide, IdP-specific
  instructions (Okta, Entra ID, OneLogin), endpoint reference, filtering
  examples, security notes, and troubleshooting table
- Update docs/public/operations/authentication.md with SCIM section
  linking to the new guide
- Add SCIM page to docs/public/SUMMARY.md under Operations
- Use crypto.timingSafeEqual for bearer token comparison to prevent
  timing attacks
- Extract shared authenticateScim into src/app/api/scim/v2/auth.ts
- Wrap PUT /Groups/:id member replacement in Prisma transaction for
  atomicity
- Add type validation on SCIM PATCH operation values before DB writes
- Add audit logging to all SCIM mutations (user create, update, patch,
  deactivate)
When IdP asserts active:true, only clear locks that were SCIM-originated
(lockedBy is "SCIM" or null). Admin-initiated locks are now preserved,
preventing SCIM sync from silently re-activating accounts locked for
security reasons.

Applied to scimUpdateUser and both code paths in scimPatchUser.
@TerrifiedBug
Copy link
Copy Markdown
Owner Author

@greptile review

@TerrifiedBug TerrifiedBug merged commit 5c15a58 into main Mar 7, 2026
3 checks passed
@TerrifiedBug TerrifiedBug deleted the feat/scim branch March 7, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant