Skip to content

feat(scim): serve the User ResourceType and Schema - #2672

Draft
xlgmokha wants to merge 3 commits into
xlgmokha/auth-1368bfrom
xlgmokha/auth-1369
Draft

feat(scim): serve the User ResourceType and Schema#2672
xlgmokha wants to merge 3 commits into
xlgmokha/auth-1368bfrom
xlgmokha/auth-1369

Conversation

@xlgmokha

@xlgmokha xlgmokha commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Feature. Adds GET /scim/v2/ResourceTypes/{id} and GET /scim/v2/Schemas/{id}.

https://linear.app/supabase/issue/AUTH-1369/scim-user-resourcetype-and-schema-reflection-endpoints

What is the current behavior?

/ResourceTypes and /Schemas return an empty ListResponse and have no by-id route, so nothing on this server describes the User resource.
A conformant client discovers before it fetches, which makes /Users/{id} unreachable in practice.

The User resource itself publishes userName and emails.

What is the new behavior?

request response
GET /ResourceTypes 200 + ListResponse with one ResourceType
GET /ResourceTypes/User 200 + the ResourceType
GET /Schemas 200 + ListResponse with one Schema
GET /Schemas/urn:ietf:params:scim:schemas:core:2.0:User 200 + the Schema
モ curl -s http://localhost:9999/scim/v2/ResourceTypes/User | jq
{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
  ],
  "id": "User",
  "name": "User",
  "description": "User Account",
  "endpoint": "/Users",
  "schema": "urn:ietf:params:scim:schemas:core:2.0:User",
  "meta": {
    "resourceType": "ResourceType",
    "location": "http://localhost:9999/scim/v2/ResourceTypes/User"
  }
}
モ curl -s http://localhost:9999/scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User | jq
{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:Schema"
  ],
  "id": "urn:ietf:params:scim:schemas:core:2.0:User",
  "name": "User",
  "description": "User Account",
  "attributes": [
    {
      "name": "userName",
      "type": "string",
      "multiValued": false,
      "description": "Unique identifier for the User, typically used by the user to directly authenticate to the service provider.",
      "required": true,
      "caseExact": false,
      "mutability": "readWrite",
      "returned": "default",
      "uniqueness": "server"
    },
    {
      "name": "name",
      "type": "complex",
      "multiValued": false,
      "description": "The components of the user's real name.",
      "required": false,
      "caseExact": false,
      "mutability": "readWrite",
      "returned": "default",
      "uniqueness": "none",
      "subAttributes": [
        {
          "name": "formatted",
          "type": "string",
          "multiValued": false,
          "description": "The full name, including all middle names, titles, and suffixes as appropriate, formatted for display.",
          "required": false,
          "caseExact": false,
          "mutability": "readWrite",
          "returned": "default",
          "uniqueness": "none"
        },
        {
          "name": "familyName",
          "type": "string",
          "multiValued": false,
          "description": "The family name of the User, or last name in most Western languages.",
          "required": false,
          "caseExact": false,
          "mutability": "readWrite",
          "returned": "default",
          "uniqueness": "none"
        },
        {
          "name": "givenName",
          "type": "string",
          "multiValued": false,
          "description": "The given name of the User, or first name in most Western languages.",
          "required": false,
          "caseExact": false,
          "mutability": "readWrite",
          "returned": "default",
          "uniqueness": "none"
        }
      ]
    },
    {
      "name": "emails",
      "type": "complex",
      "multiValued": true,
      "description": "Email addresses for the User. Only the primary address is supported.",
      "required": false,
      "caseExact": false,
      "mutability": "readWrite",
      "returned": "default",
      "uniqueness": "none",
      "subAttributes": [
        {
          "name": "value",
          "type": "string",
          "multiValued": false,
          "description": "Email address for the User.",
          "required": false,
          "caseExact": false,
          "mutability": "readWrite",
          "returned": "default",
          "uniqueness": "none"
        },
        {
          "name": "primary",
          "type": "boolean",
          "multiValued": false,
          "description": "A Boolean value indicating the preferred email address.",
          "required": false,
          "caseExact": false,
          "mutability": "readWrite",
          "returned": "default",
          "uniqueness": "none"
        }
      ]
    },
    {
      "name": "active",
      "type": "boolean",
      "multiValued": false,
      "description": "A Boolean value indicating the User's administrative status.",
      "required": false,
      "caseExact": false,
      "mutability": "readWrite",
      "returned": "default",
      "uniqueness": "none"
    },
    {
      "name": "externalId",
      "type": "string",
      "multiValued": false,
      "description": "An identifier for the User as defined by the provisioning client.",
      "required": false,
      "caseExact": true,
      "mutability": "readWrite",
      "returned": "default",
      "uniqueness": "none"
    }
  ],
  "meta": {
    "resourceType": "Schema",
    "location": "http://localhost:9999/scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User"
  }
}
$ curl -s -H "Authorization: Bearer $SCIM_TOKEN" \
    http://localhost:9999/scim/v2/Users/1f9c1d5e-... | jq
{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:User"
  ],
  "id": "1f9c1d5e-...",
  "userName": "ada@example.com",
  "name": {
    "formatted": "Ada Lovelace",
    "familyName": "Lovelace",
    "givenName": "Ada"
  },
  "emails": [
    {
      "value": "ada@example.com",
      "primary": true
    }
  ],
  "active": true,
  "meta": {
    "resourceType": "User",
    "created": "2026-08-03T23:39:01Z",
    "lastModified": "2026-08-03T23:39:01Z",
    "location": "http://localhost:9999/scim/v2/Users/1f9c1d5e-..."
  }
}

Additional context

The RFC 7644 message URIs share the urn:ietf:params:scim:api:messages:2.0
namespace, so declare it once and derive Error, ListResponse and the Bulk,
PatchOp and SearchRequest URIs the upcoming endpoints will need.

scimType becomes a named type carrying the ten detail error keywords of
RFC 7644, Table 9. Nothing produces one yet -- filtering is the first real
producer -- but NewError no longer takes a bare string for it.
The schema this server advertises named five attributes while the mapper
populated two. Close the gap: name is mapped best effort from user
metadata, preferring the SCIM spelling over the snake_case fallback, and
active is derived from the ban state. externalId is carried on the
resource but stays empty until something writes it.

active is serialized unconditionally. Omitting it on false would report a
deactivated user as unknown, which a provisioning client reads as
"still provisioned".

Along the way core gains the vocabulary the resources are built from: the
schema URIs composed from one namespace, named types for the RFC 7643
keywords so a Mutability cannot be assigned where a Returned belongs, and
NewMeta so the location layout is stated once rather than per resource.
Adds GET /scim/v2/ResourceTypes/{id} and
GET /scim/v2/Schemas/{id}, and fills the list endpoints that until now
answered with an empty ListResponse. A conformant client discovers before
it fetches, so /Users was unreachable in practice without these.

The published User schema advertises userName, name, emails, active and
externalId. externalId is a deviation: RFC 7643 defines it as a common
attribute in Section 3.1 rather than a User attribute, so the spec's own
User schema omits it, but provisioning clients map it.

The ResourceType is built from the Schema rather than from its URI, which
is what makes Section 6's "schema MUST be equal to the id attribute of the
associated Schema resource" hold by construction.

https://linear.app/supabase/issue/AUTH-1369/scim-user-resourcetype-and-schema-reflection-endpoints
@xlgmokha xlgmokha self-assigned this Aug 4, 2026
@xlgmokha
xlgmokha changed the base branch from master to xlgmokha/auth-1368b August 4, 2026 01:32
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