Skip to content

fix(scim): make POST /Users and /Groups idempotent on external_id#6

Merged
mxhash merged 2 commits into
mainfrom
fix/scim-idempotent-post
May 19, 2026
Merged

fix(scim): make POST /Users and /Groups idempotent on external_id#6
mxhash merged 2 commits into
mainfrom
fix/scim-idempotent-post

Conversation

@mxhash
Copy link
Copy Markdown
Member

@mxhash mxhash commented May 19, 2026

Summary

  • POST /scim/v2/Users now adopts an existing user when the incoming externalId already exists in struudel: applies the payload as an update and returns 200 OK + Location instead of 409 uniqueness. Same logic for POST /scim/v2/Groups.
  • IntegrityError handler now logs constraint name and Postgres message_detail (e.g. Key (preferred_username)=(mopolka) already exists.) plus the request body, so future SCIM 409s are diagnosable from the app log alone.

Why

When the SCIM provider in Authentik is deleted and recreated, Authentik loses its scim_id mapping for every user/group and POSTs them all on the next sync. Without idempotency on external_id, struudel rejects every POST with 409 (UNIQUE on users.external_id / groups.external_id) and re-provisioning is impossible without dropping all SCIM-managed data first.

With this fix Authentik re-discovers each resource on first POST, learns the correct current struudel id, and switches to PUT-based updates from then on.

Test plan

  • CI green (lint, ty)
  • Pull the PR image on the live host and trigger a fresh Authentik SCIM sync; verify users and groups previously yielding 409 now log SCIM POST /Users adopted existing user id=... and the sync completes
  • Verify that the user.id values returned in Location headers are stable across repeat syncs (i.e. Authentik is now mapped to the right ids)
  • Force an artificial 409 (e.g. PUT user with someone else's username) and confirm the new log line contains constraint= and detail=

🤖 Generated with Claude Code

mxhash added 2 commits May 19, 2026 13:01
When Authentik's SCIM provider is recreated, it loses its remote-id
mappings and re-POSTs every user/group on the next sync. Without
idempotency, every POST hits the `external_id` UNIQUE constraint and
fails with 409, blocking re-provisioning.

POST now looks up by `external_id` first; if a resource already exists,
update it from the payload, return 200 + Location instead of 201. This
lets Authentik adopt the existing struudel resource and store the
current struudel id for future PUT/PATCH calls.

Also log constraint name and Postgres detail (e.g. which key collided)
on any remaining SCIM 409, so future unique-constraint violations are
diagnosable from the app log without psql access.
Authentik's outgoing SCIM PATCH on a group sometimes omits the
top-level `schemas: [PatchOp]` array (RFC 7644 §3.5.2 requires it) and
puts the Group schema inside the operation's `value` instead.
Additionally, the `value` dict carries SCIM-meta keys like `id`,
`schemas` and `meta` that have no direct mapping to a column.

Drop the top-level PatchOp schema requirement (Operations being a list
is enough to identify the request), and silently skip the meta keys in
group replace operations. Matches the existing Authentik-lenience
pattern from fbcbe02 (members remove without value-eq filter).
@mxhash mxhash merged commit ef112ec into main May 19, 2026
1 of 2 checks passed
@mxhash mxhash deleted the fix/scim-idempotent-post branch May 19, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant