Skip to main content
← Back to list
01Issue
FeatureOpenSwamp ClubPublic
AssigneesNone

Relationships

#1821 SCIM 2.0 provisioning endpoint for swamp-club (Users + Groups, deprovision)

Opened by keeb · 8/25/2026

Problem

swamp-club has no SCIM 2.0 endpoint. rg -i scim across the repo returns nothing, and better-auth does not ship a SCIM plugin — the plugin export list in node_modules/better-auth/package.json:183-291 covers organization, sso, oidc-provider, device-authorization, jwt, bearer and admin, but nothing for SCIM. Any IdP configured to provision against swamp-club would 404 on every request and surface as a failed integration in the IdP admin console.

What exists today is just-in-time provisioning: provisionSsoMembership (lib/app/provision-sso-member.ts:37) upserts a member record on first SSO login, matched by email domain against the ssoProvider collection, tagged source: "sso".

Gap analysis against the four core SCIM operations

SCIM operation Today Status
User create JIT upsert at first SSO login Equivalent outcome, but pull not push
User update Nothing (see the overrideUserInfo issue) Gap
Group membership sync Written only during the SSO login hook Gap (see the idpGroups staleness issue)
Deprovision Nothing automatic Gap

removeMember (lib/app/collective-commands.ts:380) exists only as a manual admin action behind an API route. Nothing removes a member when they leave the organization.

Why the existing data model fits

The pieces SCIM needs are already in place:

  • user — the directory, with idpGroups declared as a custom field (lib/auth.ts:332)
  • member — collective membership, carrying a source field that already distinguishes "sso" from manual invites
  • ssoProvider — keyed by domain with an organizationId, so SSO is already scoped per-collective

That last point matters: SCIM would inherit the same per-collective scoping SSO already uses, which is the correct granularity. And the existing source field gives a clean ownership boundary — SCIM-created members get source: "scim", which delimits what a SCIM sync is permitted to deprovision. Without that, a sync could delete manually-invited members.

Proposed scope

  • /scim/v2/Users and /scim/v2/Groups with GET, POST, PUT, PATCH, DELETE
  • A SCIM bearer credential per ssoProvider record, verified in constant time
  • Okta-compatible behaviors: PATCH operations for group membership deltas, filter=userName eq "...", pagination via startIndex/count, soft-delete via active: false mapped onto removeMember
  • source: "scim" ownership rules governing what the sync may modify or remove

Landing SCIM Groups alone would substantially improve the existing chain: writes to idpGroups would flow through userinfo to serve idp-group: subjects on the next refresh tick, taking group staleness from "until next login" down to the refresh interval.

Open question

Self-hosted deployments running --auth-mode token never contact swamp-club, so a swamp-club SCIM integration does not serve them at all. If inbound provisioning for air-gapped installs is in scope, that is a separate integration against swamp serve rather than a variation of this one — worth deciding before design rather than after.

Context

Builds on the SSO and IdP group work in #1014, #1015, #1016, #1154, #1166, #1170, #1171, #1611.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

8/25/2026, 2:33:59 PM

No activity in this phase yet.

03Sludge Pulse
keeb marked as blocked8/26/2026, 6:49:33 PM
keeb unblocked8/26/2026, 6:49:34 PM
Editable. Press Enter to edit.

keeb commented 8/25/2026, 2:34:14 PM

Two independently filed defects found during the same investigation, both of which SCIM would mitigate: #1819 (idpGroups only written by the SSO login hook, so group membership is stale until next login) and #1820 (overrideUserInfo never set, so profile fields are frozen at first login). #1819 in particular is the reason SCIM Groups alone would be worth landing early.

webframp commented 8/25/2026, 11:43:24 PM

👀

Sign in to post a ripple.