Repository navigation
Add SCIM 2.0 provisioning for Organizations - #62
Merged
Merged
Conversation
An Organization's identity provider can invite and remove its members
(API Reference → SCIM):
- POST/GET/DELETE /v1/organizations/{id}/scim-token (org admin or
organizations.manage): one bearer token per Organization, secret shown
once, SHA-256 stored. Migration 0008_scim_tokens, with
organization.scim_token_created / _revoked audit actions.
- /scim/v2: ServiceProviderConfig, ResourceTypes, and Users (list with
userName eq filter and paging, create, get, put, patch, delete), in
SCIM's own response and error shapes.
- Same membership rules as org admins: provisioning invites (pending
until the person accepts, whether or not they had an account);
deprovisioning removes the membership with access.revoked and never
touches the person's account; the last-org_admin guard applies. Writes
are audited as actor system, id the token's sct_ id.
- The invite and removal logic is factored out of the member handlers
(inviteMember, insertPendingMember, removeMember) and shared, with no
behavior change.
Users only; no Groups, bulk, sort, or ETags.
Closes #34
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
terraform plan (production)This is exactly what merging will change in AWS. Plan |
Brings in the Substratal-Version header and root paths (#37), the plan-job secret fix (#59), and the versioning docs (#63). - /scim/v2 is exempt from Substratal-Version: identity providers (Okta, Entra ID, Google Workspace) can't add custom headers, and SCIM versions itself in the path. Without this every IdP request would be 400 api_version_required. The scim-token endpoints still need it. Conventions, SCIM, openapi.yaml's description, and the Changelog say so - scimCall sends no Substratal-Version, the way an IdP does, so the whole SCIM suite exercises the exemption; testenv skips contract validation for /scim/ (it follows RFC 7644, not openapi.yaml) - Conflicts in Organizations and Orders & Audit: this branch's rows, with root paths; plan-remaining.md is main's (this branch didn't change it). The Changelog puts this branch's entry above main's two entries - openapi.yaml 1.0.0-draft.5 (main took draft.4 for the header change) - Root paths in scim.md, scim.go, and scim_test.go; tools.json regenerated Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
terraform plan (production)This is exactly what merging will change in AWS. Plan |
This was referenced Oct 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #34.
An Organization's identity provider (Okta, Entra ID, Google Workspace, …) can now invite and remove the Organization's members with SCIM 2.0. Spec: the new page API Reference → SCIM.
Setup (in
openapi.yaml)POST /v1/organizations/{id}/scim-token: org admin ororganizations.manage, with a User token. Creates the token, or rotates it; the old one stops working at once. Returns thescim_…secret once, plusbase_url.GET: shows the token without the secret, includinglast_used_at.DELETE: turns SCIM off.0008_scim_tokens.organization.scim_token_createdandorganization.scim_token_revoked./scim/v2(RFC 7644, notopenapi.yaml, like/.well-known)ServiceProviderConfig,ResourceTypes, and Users: list (with auserName eq "…"filter andstartIndex/countpaging), create, get,PUT,PATCH, and delete. Responses useapplication/scim+jsonand SCIM's own error shape. API errors are mapped to SCIM error types (uniqueness,mutability,invalidValue). API Gateway's$defaultroute already sends/scim/v2to the API Lambda, so no infra change is needed.Design choices (written into the SCIM page)
POST /Usersis the same as an org admin inviting by email: apendingmembership plus an invitation. Organizations' consent model makes activating someone on their behalf a platform action, so an identity provider can't attach an existing account to an Organization without its owner agreeing.activeistruefor both pending and active memberships.Membershipextension reportsstatusandrole.displayNameappears only once the person has accepted. The member list already hides it the same way, so provisioning an address never reveals whether it already had an account.active: false(throughPATCHorPUT, including the string"False"that Entra sends) orDELETEbehaves likeDELETE …/members/{userId}.organization.member_removedandaccess.revokedfire.org_adminguard applies.systemand actor id the token'ssct_…id. No new actor type is needed.Refactor
The invite and removal logic is extracted from the member handlers into
inviteMember,insertPendingMember, andremoveMember, so SCIM uses exactly the same code paths. Behavior is unchanged; the existing Organization tests pass as they were.Tests
TestSCIMProvisioning:401; an unsupported resource returns404.409.PATCH active "False"removes the membership:access.revokedfires once,GETreturns404, and the person's account still works.DELETEremoves a pending invitation.409mutability.The full integration and contract suite, gen-check, builds, and terraform fmt pass locally.
Merge order
0008comes after0005(Build test-mode User expiry (expires_at) #58),0006(Open POST /applications to self-service registration #60), and0007(Add Audit Event exports (POST/GET /audit-exports) #61). Its action check already includes theiruser.expiredandaudit.exported.info.versiontodraft.4. Bump it again on each later merge, and stack the Changelog entries.🤖 Generated with Claude Code