Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
086b28f
feat: add scim to sso providers, banner reason to users, and add scim…
Bewinxed Dec 10, 2025
742aa80
feat: add SCIM fields and methods to models
Bewinxed Dec 10, 2025
d9e25cb
feat: add scim error codes
Bewinxed Dec 10, 2025
4d38667
fix: add findUserByProvider to user model
Bewinxed Dec 10, 2025
4efc01d
chore: update Ban() calls in admin.go
Bewinxed Dec 10, 2025
210ebd1
feat: Add SCIM v2 endpoints
Bewinxed Dec 10, 2025
0659822
feat: register SCIM endpoints
Bewinxed Dec 10, 2025
00317da
fix: reuse existing helpers for user creation, add audit logging
Bewinxed Dec 10, 2025
8ea1ec0
feat: Add Admin SCIM management endpoints
Bewinxed Dec 10, 2025
8f8aa1b
chore: make SCIM Token prefixed
Bewinxed Dec 10, 2025
e977276
fix: several bugfixes, add db-pagination
Bewinxed Dec 10, 2025
b3676aa
fix: restore is_super_admin to the User model
Bewinxed Dec 10, 2025
cac75ef
fix: RFC 7644 compliance
Bewinxed Dec 11, 2025
adb3125
chore: extract scim_parser out, minimal impl
Bewinxed Dec 11, 2025
ae537dc
chore: extract scim types out
Bewinxed Dec 24, 2025
5d14e16
chore: add scim2/filter-parser as SCIM query parser
Bewinxed Dec 24, 2025
9183e45
chore: add SCIM errors
Bewinxed Dec 24, 2025
1cc3ec3
feat: add SCIM filter support to user and group queries
Bewinxed Dec 24, 2025
4731c5a
chore: refactor SCIM to use extracted types/helpers.
Bewinxed Dec 24, 2025
090dbf2
feat: add scim2/filter-parser dependency
Bewinxed Dec 24, 2025
d890c41
feat: add scim filters with RFC 7644 support
Bewinxed Dec 24, 2025
548f909
fix: group schema migration fix
Bewinxed Dec 24, 2025
d6ee008
chore: rename scim parser to scim helper after dep was added
Bewinxed Dec 24, 2025
4a96798
chore: extract UserNotInSSOProviderError as typed error
Bewinxed Jan 18, 2026
3ebe331
chore(scim): remove unused group query functions
Bewinxed Jan 18, 2026
5bb5172
chore: consolidate userBelongsToProvider implementations
Bewinxed Jan 18, 2026
7d38d1a
chore: consolidate filter clause types
Bewinxed Jan 18, 2026
cc91093
fix(scim): remove duplicate types and fix error handling consistency
Bewinxed Jan 18, 2026
96ed466
chore: add scim test infrastructure
Bewinxed Jan 19, 2026
fc330c3
chore: add SCIM user filtering tests
Bewinxed Jan 19, 2026
3f04db3
chore: add SCIM user PATCH tests
Bewinxed Jan 19, 2026
0ea17fb
chore: add SCIM group CRUD tests
Bewinxed Jan 19, 2026
1c69b1c
chore: add SCIM group filtering tests
Bewinxed Jan 19, 2026
ff5c1dc
chore: add SCIM group membership PATCH tests
Bewinxed Jan 19, 2026
a431f22
chore: add SCIM authentication and error tests
Bewinxed Jan 19, 2026
c007f3f
chore: centralize test fixtures
Bewinxed Jan 19, 2026
184e3b4
chore: add nosec for false positive token error code
Bewinxed Jan 19, 2026
b278e1d
fix: use sendSCIMJSON for SCIM error responses
Bewinxed Feb 5, 2026
68d75d8
fix: use schema-qualified table names in SCIM queries
Bewinxed Feb 5, 2026
161eafa
fix: scimReplaceUser now validates, updates email, and fully replaces…
Bewinxed Feb 5, 2026
3b43ce4
fix: handle count=0 in SCIM pagination per RFC 7644
Bewinxed Feb 5, 2026
d64ac57
fix: make SCIM user delete idempotent
Bewinxed Feb 5, 2026
fca20d3
fix: sanitize JSON errors and use API_EXTERNAL_URL for SCIM base URL
Bewinxed Feb 5, 2026
076683d
fix: use FlexBool for SCIM user Active field
Bewinxed Feb 5, 2026
d91aeb5
fix: add ESCAPE clause to LIKE filters and use SCIM error types consi…
Bewinxed Feb 5, 2026
75480a8
chore: remove unused SCIM error code constants
Bewinxed Feb 5, 2026
6024d8d
fix: optimize SCIM token lookup and add partial index
Bewinxed Feb 5, 2026
c6afbc8
fix: batch user loading in SetMembers to avoid N+1 queries
Bewinxed Feb 5, 2026
13cd615
fix: use API_EXTERNAL_URL for SCIM base URL in admin endpoints
Bewinxed Feb 5, 2026
58a14f7
chore: add SCIM PUT replace and cross-provider isolation tests
Bewinxed Feb 5, 2026
313c57c
fix: add request size limits and validation for SCIM endpoints
Bewinxed Feb 5, 2026
4fe20cc
fix: use SHA-256 instead of bcrypt for SCIM token lookup
Bewinxed Feb 5, 2026
7241f9d
fix: harden SCIM cross-provider isolation, error handling, and batch …
Bewinxed Feb 6, 2026
c0482dd
refactor: route all SCIM PATCH paths through filter.ParsePath
Bewinxed Feb 6, 2026
51282de
fix: pass ResponseWriter to MaxBytesReader instead of nil
Bewinxed Feb 6, 2026
fc66e83
fix: wrap all raw DB/model errors in SCIMHTTPError types
Bewinxed Feb 6, 2026
75204ed
fix: map uniqueness violations to 409 in group patch and replace
Bewinxed Feb 6, 2026
c10e61e
fix: validate all member IDs in SetMembers before replacing
Bewinxed Feb 6, 2026
ce05abb
fix: preserve non-not-found errors in SetMembers validation and lock …
Bewinxed Feb 6, 2026
f2035ab
fix: remove DISTINCT from FOR SHARE query and de-duplicate in Go
Bewinxed Feb 6, 2026
1f4e920
fix: enable SCIM user reactivation for SSO users
Bewinxed Feb 6, 2026
e855455
fix: scope SCIM reactivation lookup by provider to prevent cross-prov…
Bewinxed Feb 6, 2026
e8b22b9
fix: make SCIM reactivation deterministic by querying all matching SS…
Bewinxed Feb 6, 2026
09a8d17
fix: return 400 for unsupported SCIM PATCH paths and value types
Bewinxed Feb 6, 2026
332b8e4
fix: log SCIM 5xx errors at Error level and 429 at Warn level
Bewinxed Feb 6, 2026
650abd9
fix: reject ambiguous reactivation when multiple deprovisioned users …
Bewinxed Feb 6, 2026
504bad4
fix: support SCIM PATCH add with explicit path for user attributes
Bewinxed Feb 6, 2026
630f38e
fix: cap startIndex, use SetEmail in PATCH, map externalId uniqueness…
Bewinxed Feb 6, 2026
e310393
fix: use SetEmail consistently in SCIM PATCH/PUT and map email unique…
Bewinxed Feb 6, 2026
a9259b5
fix: enforce provider-scoped email uniqueness in SCIM PUT and PATCH p…
Bewinxed Feb 6, 2026
0484837
test: add SCIM email uniqueness regression tests for PUT and PATCH
Bewinxed Feb 6, 2026
1bab6ed
fix: fix group member pointer aliasing, group create race, and FlexBo…
Bewinxed Feb 6, 2026
b7a9348
fix: preserve non-SCIM metadata in PUT and pass IP to audit logs
Bewinxed Feb 6, 2026
050e64b
fix: stop clearing provider_id on externalId removal and batch group …
Bewinxed Feb 6, 2026
5bfb196
fix: normalize active parsing, derive externalId from identity data, …
Bewinxed Feb 6, 2026
f123bd3
fix: use identity_data for externalId filter and add row locking to A…
Bewinxed Feb 6, 2026
ed379b2
fix: lock identity rows alongside user rows in group membership valid…
Bewinxed Feb 6, 2026
97c5d34
fix: honor active attribute on SCIM user create
Bewinxed Feb 6, 2026
8c2b44f
fix: check non-SSO email collisions, sync sub on externalId change, g…
Bewinxed Feb 6, 2026
270db7f
fix: default members in group list, avoid eager loading, make timesta…
Bewinxed Feb 6, 2026
407d495
chore: consolidate SCIM migrations into single file
Bewinxed Feb 7, 2026
f22a0b7
fix: use correct audit action when reprovisioning inactive user
Bewinxed Feb 7, 2026
f38e226
fix: update identity data and merge metadata on user reactivation
Bewinxed Feb 7, 2026
0873bf8
fix: deduplicate member IDs before validation in AddMembers/SetMembers
Bewinxed Feb 7, 2026
a6e6ce5
refactor: extract identity update helpers to reduce SCIM patch comple…
Bewinxed Feb 7, 2026
0474ed1
feat: gate SCIM routes behind GOTRUE_SCIM_ENABLED config flag
Bewinxed Feb 7, 2026
6b96df9
fix: validate schemas field in SCIM request bodies per RFC 7644
Bewinxed Feb 7, 2026
c28221f
fix: use NULLIF in COALESCE to skip empty userName in filter queries
Bewinxed Feb 7, 2026
1459b26
fix: add safety LIMIT to GetMembers query
Bewinxed Feb 7, 2026
767dabb
refactor: deduplicate schema and resource type definitions
Bewinxed Feb 7, 2026
10ca92c
style: fix indentation of SCIM route registration block
Bewinxed Feb 7, 2026
6965e3c
fix: reset ProviderID and sub when externalId is omitted in PUT and r…
Bewinxed Feb 7, 2026
a53392e
fix: check cross-provider email collisions to return 409 instead of 500
Bewinxed Feb 7, 2026
1db2a53
fix: reset ProviderID and sub when removing externalId via PATCH
Bewinxed Feb 7, 2026
941b0e6
refactor: extract shared helpers and remove duplication across SCIM f…
Bewinxed Feb 7, 2026
17797dc
fix: remove IsSuperAdmin field re-added against upstream removal
Bewinxed Feb 7, 2026
60a56f7
cleanup: remove dead code, fix PR description, add SCIM test coverage
Bewinxed Feb 7, 2026
85aab09
fix: update SCIM delete user response to return 404 for deprovisioned…
Bewinxed Feb 7, 2026
a9cb049
fix: remove unused identityID assignment in scimCreateUser function
Bewinxed Feb 7, 2026
e021d97
final cleanup
Bewinxed Feb 7, 2026
dff166d
refactor: extract SCIM error messages to constants and normalize wording
Bewinxed Feb 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ require (
github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/di-wu/parser v0.2.2 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect
github.com/ethereum/go-verkle v0.2.2 // indirect
Expand All @@ -64,6 +65,7 @@ require (
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/scim2/filter-parser/v2 v2.2.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect
github.com/supranational/blst v0.3.14 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5il
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/di-wu/parser v0.2.2 h1:I9oHJ8spBXOeL7Wps0ffkFFFiXJf/pk7NX9lcAMqRMU=
github.com/di-wu/parser v0.2.2/go.mod h1:SLp58pW6WamdmznrVRrw2NTyn4wAvT9rrEFynKX7nYo=
github.com/didip/tollbooth/v5 v5.1.1 h1:QpKFg56jsbNuQ6FFj++Z1gn2fbBsvAc1ZPLUaDOYW5k=
github.com/didip/tollbooth/v5 v5.1.1/go.mod h1:d9rzwOULswrD3YIrAQmP3bfjxab32Df4IaO6+D25l9g=
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
Expand Down Expand Up @@ -430,6 +432,8 @@ github.com/russellhaering/goxmldsig v1.3.0 h1:DllIWUgMy0cRUMfGiASiYEa35nsieyD3ci
github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/scim2/filter-parser/v2 v2.2.0 h1:QGadEcsmypxg8gYChRSM2j1edLyE/2j72j+hdmI4BJM=
github.com/scim2/filter-parser/v2 v2.2.0/go.mod h1:jWnkDToqX/Y0ugz0P5VvpVEUKcWcyHHj+X+je9ce5JA=
github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35 h1:eajwn6K3weW5cd1ZXLu2sJ4pvwlBiCWY4uDejOr73gM=
github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
Expand Down
1 change: 1 addition & 0 deletions hack/test.env
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ GOTRUE_SECURITY_CAPTCHA_PROVIDER="hcaptcha"
GOTRUE_SECURITY_CAPTCHA_SECRET="0x0000000000000000000000000000000000000000"
GOTRUE_SECURITY_CAPTCHA_TIMEOUT="10s"
GOTRUE_SAML_ENABLED="true"
GOTRUE_SCIM_ENABLED="true"
GOTRUE_SAML_PRIVATE_KEY="MIIEowIBAAKCAQEAszrVveMQcSsa0Y+zN1ZFb19cRS0jn4UgIHTprW2tVBmO2PABzjY3XFCfx6vPirMAPWBYpsKmXrvm1tr0A6DZYmA8YmJd937VUQ67fa6DMyppBYTjNgGEkEhmKuszvF3MARsIKCGtZqUrmS7UG4404wYxVppnr2EYm3RGtHlkYsXu20MBqSDXP47bQP+PkJqC3BuNGk3xt5UHl2FSFpTHelkI6lBynw16B+lUT1F96SERNDaMqi/TRsZdGe5mB/29ngC/QBMpEbRBLNRir5iUevKS7Pn4aph9Qjaxx/97siktK210FJT23KjHpgcUfjoQ6BgPBTLtEeQdRyDuc/CgfwIDAQABAoIBAGYDWOEpupQPSsZ4mjMnAYJwrp4ZISuMpEqVAORbhspVeb70bLKonT4IDcmiexCg7cQBcLQKGpPVM4CbQ0RFazXZPMVq470ZDeWDEyhoCfk3bGtdxc1Zc9CDxNMs6FeQs6r1beEZug6weG5J/yRn/qYxQife3qEuDMl+lzfl2EN3HYVOSnBmdt50dxRuX26iW3nqqbMRqYn9OHuJ1LvRRfYeyVKqgC5vgt/6Tf7DAJwGe0dD7q08byHV8DBZ0pnMVU0bYpf1GTgMibgjnLjK//EVWafFHtN+RXcjzGmyJrk3+7ZyPUpzpDjO21kpzUQLrpEkkBRnmg6bwHnSrBr8avECgYEA3pq1PTCAOuLQoIm1CWR9/dhkbJQiKTJevlWV8slXQLR50P0WvI2RdFuSxlWmA4xZej8s4e7iD3MYye6SBsQHygOVGc4efvvEZV8/XTlDdyj7iLVGhnEmu2r7AFKzy8cOvXx0QcLg+zNd7vxZv/8D3Qj9Jje2LjLHKM5n/dZ3RzUCgYEAzh5Lo2anc4WN8faLGt7rPkGQF+7/18ImQE11joHWa3LzAEy7FbeOGpE/vhOv5umq5M/KlWFIRahMEQv4RusieHWI19ZLIP+JwQFxWxS+cPp3xOiGcquSAZnlyVSxZ//dlVgaZq2o2MfrxECcovRlaknl2csyf+HjFFwKlNxHm2MCgYAr//R3BdEy0oZeVRndo2lr9YvUEmu2LOihQpWDCd0fQw0ZDA2kc28eysL2RROte95r1XTvq6IvX5a0w11FzRWlDpQ4J4/LlcQ6LVt+98SoFwew+/PWuyLmxLycUbyMOOpm9eSc4wJJZNvaUzMCSkvfMtmm5jgyZYMMQ9A2Ul/9SQKBgB9mfh9mhBwVPIqgBJETZMMXOdxrjI5SBYHGSyJqpT+5Q0vIZLfqPrvNZOiQFzwWXPJ+tV4Mc/YorW3rZOdo6tdvEGnRO6DLTTEaByrY/io3/gcBZXoSqSuVRmxleqFdWWRnB56c1hwwWLqNHU+1671FhL6pNghFYVK4suP6qu4BAoGBAMk+VipXcIlD67mfGrET/xDqiWWBZtgTzTMjTpODhDY1GZck1eb4CQMP5j5V3gFJ4cSgWDJvnWg8rcz0unz/q4aeMGl1rah5WNDWj1QKWMS6vJhMHM/rqN1WHWR0ZnV83svYgtg0zDnQKlLujqW4JmGXLMU7ur6a+e6lpa1fvLsP"
GOTRUE_MAX_VERIFIED_FACTORS=10
GOTRUE_SMS_TEST_OTP_VALID_UNTIL=""
Expand Down
4 changes: 2 additions & 2 deletions internal/api/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func (a *API) adminUserUpdate(w http.ResponseWriter, r *http.Request) error {
}

if banDuration != nil {
if terr := user.Ban(tx, *banDuration); terr != nil {
if terr := user.Ban(tx, *banDuration, nil); terr != nil {
return terr
}
}
Expand Down Expand Up @@ -493,7 +493,7 @@ func (a *API) adminUserCreate(w http.ResponseWriter, r *http.Request) error {
}

if banDuration != nil {
if terr := user.Ban(tx, *banDuration); terr != nil {
if terr := user.Ban(tx, *banDuration, nil); terr != nil {
return terr
}
}
Expand Down
50 changes: 50 additions & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,48 @@ func NewAPIWithVersion(globalConfig *conf.GlobalConfiguration, db *storage.Conne
r.Post("/", api.ExternalProviderCallback)
})

// SCIM v2 API endpoints
if api.config.SCIM.Enabled {
r.Route("/scim/v2", func(r *router) {
r.Use(api.requireSCIMAuthentication)

// SCIM-specific NotFound handler for proper error format
r.NotFound(api.scimNotFound)
r.MethodNotAllowed(api.scimMethodNotAllowed)

// Service Provider Configuration
r.Get("/ServiceProviderConfig", api.scimServiceProviderConfig)
r.Get("/ResourceTypes", api.scimResourceTypes)
r.Get("/ResourceTypes/{resource_type_id}", api.scimResourceTypeByID)
r.Get("/Schemas", api.scimSchemas)
r.Get("/Schemas/{schema_id}", api.scimSchemaByID)

// User endpoints
r.Route("/Users", func(r *router) {
r.Get("/", api.scimListUsers)
r.Post("/", api.scimCreateUser)
r.Route("/{user_id}", func(r *router) {
r.Get("/", api.scimGetUser)
r.Put("/", api.scimReplaceUser)
r.Patch("/", api.scimPatchUser)
r.Delete("/", api.scimDeleteUser)
})
})

// Group endpoints
r.Route("/Groups", func(r *router) {
r.Get("/", api.scimListGroups)
r.Post("/", api.scimCreateGroup)
r.Route("/{group_id}", func(r *router) {
r.Get("/", api.scimGetGroup)
r.Put("/", api.scimReplaceGroup)
r.Patch("/", api.scimPatchGroup)
r.Delete("/", api.scimDeleteGroup)
})
})
})
}

r.Route("/", func(r *router) {

r.Use(api.isValidExternalHost)
Expand Down Expand Up @@ -352,6 +394,14 @@ func NewAPIWithVersion(globalConfig *conf.GlobalConfiguration, db *storage.Conne
r.Get("/", api.adminSSOProvidersGet)
r.Put("/", api.adminSSOProvidersUpdate)
r.Delete("/", api.adminSSOProvidersDelete)

// SCIM management endpoints
r.Route("/scim", func(r *router) {
r.Get("/", api.adminSSOProviderGetSCIM)
r.Post("/", api.adminSSOProviderEnableSCIM)
r.Delete("/", api.adminSSOProviderDisableSCIM)
r.Post("/rotate", api.adminSSOProviderRotateSCIMToken)
})
})
})
})
Expand Down
78 changes: 78 additions & 0 deletions internal/api/apierrors/apierrors.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,81 @@ func (e *HTTPError) WithInternalMessage(fmtString string, args ...any) *HTTPErro
e.InternalMessage = fmt.Sprintf(fmtString, args...)
return e
}

// SCIMHTTPError is an error with SCIM-specific format per RFC 7644 Section 3.12
type SCIMHTTPError struct {
HTTPStatus int `json:"-"`
Schemas []string `json:"schemas"`
Status string `json:"status"`
Detail string `json:"detail,omitempty"`
ScimType string `json:"scimType,omitempty"`
InternalError error `json:"-"`
InternalMessage string `json:"-"`
}

const SCIMSchemaError = "urn:ietf:params:scim:api:messages:2.0:Error"

func NewSCIMHTTPError(httpStatus int, detail string, scimType string) *SCIMHTTPError {
return &SCIMHTTPError{
HTTPStatus: httpStatus,
Schemas: []string{SCIMSchemaError},
Status: fmt.Sprintf("%d", httpStatus),
Detail: detail,
ScimType: scimType,
}
}

func NewSCIMBadRequestError(detail string, scimType string) *SCIMHTTPError {
return NewSCIMHTTPError(http.StatusBadRequest, detail, scimType)
}

func NewSCIMNotFoundError(detail string) *SCIMHTTPError {
return NewSCIMHTTPError(http.StatusNotFound, detail, "")
}

func NewSCIMUnauthorizedError(detail string) *SCIMHTTPError {
return NewSCIMHTTPError(http.StatusUnauthorized, detail, "")
}

func NewSCIMConflictError(detail string, scimType string) *SCIMHTTPError {
return NewSCIMHTTPError(http.StatusConflict, detail, scimType)
}

func NewSCIMForbiddenError(detail string) *SCIMHTTPError {
return NewSCIMHTTPError(http.StatusForbidden, detail, "")
}

func NewSCIMRequestTooLargeError(detail string) *SCIMHTTPError {
return NewSCIMHTTPError(http.StatusRequestEntityTooLarge, detail, "")
}

func NewSCIMInternalServerError(detail string) *SCIMHTTPError {
return NewSCIMHTTPError(http.StatusInternalServerError, detail, "")
}

func (e *SCIMHTTPError) Error() string {
if e.InternalMessage != "" {
return e.InternalMessage
}
return fmt.Sprintf("%d: %s", e.HTTPStatus, e.Detail)
}

// Cause returns the root cause error
func (e *SCIMHTTPError) Cause() error {
if e.InternalError != nil {
return e.InternalError
}
return e
}

// WithInternalError adds internal error information to the error
func (e *SCIMHTTPError) WithInternalError(err error) *SCIMHTTPError {
e.InternalError = err
return e
}

// WithInternalMessage adds internal message information to the error
func (e *SCIMHTTPError) WithInternalMessage(fmtString string, args ...any) *SCIMHTTPError {
e.InternalMessage = fmt.Sprintf(fmtString, args...)
return e
}
1 change: 1 addition & 0 deletions internal/api/apierrors/errorcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const (
ErrorCodeUserAlreadyExists ErrorCode = "user_already_exists"
ErrorCodeSSOProviderNotFound ErrorCode = "sso_provider_not_found"
ErrorCodeSSOProviderDisabled ErrorCode = "sso_provider_disabled"
ErrorCodeSCIMDisabled ErrorCode = "scim_disabled"
ErrorCodeSAMLMetadataFetchFailed ErrorCode = "saml_metadata_fetch_failed"
ErrorCodeSAMLIdPAlreadyExists ErrorCode = "saml_idp_already_exists"
ErrorCodeSSODomainAlreadyExists ErrorCode = "sso_domain_already_exists"
Expand Down
13 changes: 13 additions & 0 deletions internal/api/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,19 @@ func HandleResponseError(err error, w http.ResponseWriter, r *http.Request) {
log.WithError(jsonErr).Warn("Failed to send JSON on ResponseWriter")
}

case *apierrors.SCIMHTTPError:
switch {
case e.HTTPStatus >= http.StatusInternalServerError:
log.WithError(e.Cause()).Error(e.Error())
case e.HTTPStatus == http.StatusTooManyRequests:
log.WithError(e.Cause()).Warn(e.Error())
default:
log.WithError(e.Cause()).Info(e.Error())
}
if jsonErr := sendSCIMJSON(w, e.HTTPStatus, e); jsonErr != nil && jsonErr != context.DeadlineExceeded {
log.WithError(jsonErr).Warn("Failed to send JSON on ResponseWriter")
}

case ErrorCause:
HandleResponseError(e.Cause(), w, r)

Expand Down
11 changes: 11 additions & 0 deletions internal/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ func (r *router) Post(pattern string, fn apiHandler) {
func (r *router) Put(pattern string, fn apiHandler) {
r.chi.Put(pattern, handler(fn))
}
func (r *router) Patch(pattern string, fn apiHandler) {
r.chi.Patch(pattern, handler(fn))
}
func (r *router) Delete(pattern string, fn apiHandler) {
r.chi.Delete(pattern, handler(fn))
}
Expand All @@ -51,6 +54,14 @@ func (r *router) UseBypass(fn func(next http.Handler) http.Handler) {
r.chi.Use(fn)
}

func (r *router) NotFound(fn apiHandler) {
r.chi.NotFound(handler(fn))
}

func (r *router) MethodNotAllowed(fn apiHandler) {
r.chi.MethodNotAllowed(handler(fn))
}

func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.chi.ServeHTTP(w, req)
}
Expand Down
Loading
Loading