Skip to content

Commit b7305b6

Browse files
authored
Merge branch 'master' into fix/openapi-spec-defects
2 parents d29bb5e + d6dec26 commit b7305b6

45 files changed

Lines changed: 1635 additions & 248 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# Changelog
22

3+
## [2.195.0](https://github.com/supabase/auth/compare/v2.194.0...v2.195.0) (2026-07-30)
4+
5+
6+
### Features
7+
8+
* gate SCIM router behind an experimental feature flag ([#2639](https://github.com/supabase/auth/issues/2639)) ([0fde049](https://github.com/supabase/auth/commit/0fde049ae7e60cdfa41daa6a52e7a1eb003f8733))
9+
10+
11+
### Bug Fixes
12+
13+
* accept offline_access OAuth scope for MCP refresh clients ([#2630](https://github.com/supabase/auth/issues/2630)) ([059b86c](https://github.com/supabase/auth/commit/059b86c41fb622959c2cc8a82662462492f1e217))
14+
* **auditlogs:** add missing events to docs ([3e5d493](https://github.com/supabase/auth/commit/3e5d4935f3594fd4c90263c4b04c50482c9a09f8))
15+
* **auditlogs:** remove unused mfa code audit log events ([2909344](https://github.com/supabase/auth/commit/2909344fac21e1355a0a9f1c8316af654ec5aff3))
16+
* **auditlogs:** remove unused recovery codes audit log events ([d4ca02a](https://github.com/supabase/auth/commit/d4ca02acf217c1949fea5a356d855aac9dfeea5c))
17+
* persist last_sign_in_at on v2 refresh token issuance ([#2655](https://github.com/supabase/auth/issues/2655)) ([fc654b0](https://github.com/supabase/auth/commit/fc654b05e7a02a6a8c375b3a7895bcd563b790ca))
18+
* reject AT from banned users ([#2642](https://github.com/supabase/auth/issues/2642)) ([b6878c2](https://github.com/supabase/auth/commit/b6878c2fa63f805dbd27436c36ba7ebbc2b7e44d))
19+
* update google.golang.org/grpc to v1.82.1 ([#2651](https://github.com/supabase/auth/issues/2651)) ([4a596c0](https://github.com/supabase/auth/commit/4a596c0d556e7bed6874f87af18aab3f4fb4efa1))
20+
321
## [2.194.0](https://github.com/supabase/auth/compare/v2.193.1...v2.194.0) (2026-07-27)
422

523

‎Dockerfile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.25.12-alpine3.23 as build
1+
FROM golang:1.26.5-alpine3.23 as build
22
ENV GO111MODULE=on
33
ENV CGO_ENABLED=0
44
ENV GOOS=linux

‎Dockerfile.dev‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.25.12-alpine3.23
1+
FROM golang:1.26.5-alpine3.23
22
ENV GO111MODULE=on
33
ENV CGO_ENABLED=0
44
ENV GOOS=linux

‎README.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,12 @@ The URI a OAuth2 provider will redirect to with the `code` and `state` values.
466466

467467
The base URL used for constructing the URLs to request authorization and access tokens. Used by `gitlab` and `keycloak`. For `gitlab` it defaults to `https://gitlab.com`. For `keycloak` you need to set this to your instance, for example: `https://keycloak.example.com/realms/myrealm`
468468

469+
#### Network hardening
470+
471+
Configuring an external authentication provider causes Auth to make outbound HTTP requests to that provider's authorization, token, and userinfo endpoints. Configuring a provider either via `GOTRUE_EXTERNAL_*` settings or an admin API is an administrative action, and doing so implies trust in the hosts and URLs that will be contacted.
472+
473+
The network Auth runs in should be hardened so these outbound connections cannot reach internal-only resources you don't want exposed, such as `localhost`/loopback addresses or cloud metadata endpoints (e.g. `169.254.169.254`). This matters most for providers with admin-configurable or discoverable endpoints (e.g. custom OAuth/OIDC providers), where a misconfigured or malicious URL could otherwise be used to reach internal infrastructure.
474+
469475
#### Apple OAuth
470476

471477
To try out external authentication with Apple locally, you will need to do the following:

‎example.env‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,12 @@ GOTRUE_SMS_TEST_OTP_VALID_UNTIL="<ISO date time>" # (e.g. 2023-09-29T08:14:06Z)
270270

271271
GOTRUE_MFA_WEB_AUTHN_ENROLL_ENABLED="false"
272272
GOTRUE_MFA_WEB_AUTHN_VERIFY_ENABLED="false"
273+
274+
# Experimental Configuration
275+
# The functionality related to experimental features may change or be removed in future releases
276+
# without prior notice.
277+
278+
# Creates the missing email identity for a user when a password is added to an
279+
# account that didn't have one (e.g. a user who signed up with an external
280+
# provider and later sets a password).
281+
GOTRUE_EXPERIMENTAL_CREATE_EMAIL_IDENTITY_ON_PASSWORD_SET_ENABLED="false"

‎go.mod‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,6 @@ require (
186186
gopkg.in/yaml.v3 v3.0.1 // indirect
187187
)
188188

189-
go 1.25.12
189+
go 1.26.5
190190

191191
replace github.com/joho/godotenv => ./internal/forks/godotenv

‎go.sum‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:
632632
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
633633
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
634634
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
635-
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
636-
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
637635
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
638636
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
639637
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=

‎internal/api/admin.go‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ func (a *API) adminUserUpdate(w http.ResponseWriter, r *http.Request) error {
230230
banDuration = &duration
231231
}
232232

233+
// must be evaluated before setting password below (via user.SetPassword)
234+
addingFirstPassword := params.Password != nil && *params.Password != "" && !user.HasPassword()
235+
233236
if params.Password != nil {
234237
password := *params.Password
235238

@@ -269,12 +272,12 @@ func (a *API) adminUserUpdate(w http.ResponseWriter, r *http.Request) error {
269272

270273
var identities []models.Identity
271274
if params.Email != "" {
272-
if identity, terr := models.FindIdentityByIdAndProvider(tx, user.ID.String(), "email"); terr != nil && !models.IsNotFoundError(terr) {
275+
if identity, terr := models.FindIdentityByIdAndProvider(tx, user.ID.String(), EmailProvider); terr != nil && !models.IsNotFoundError(terr) {
273276
return terr
274277
} else if identity == nil {
275278
// if the user doesn't have an existing email
276279
// then updating the user's email should create a new email identity
277-
i, terr := a.createNewIdentity(tx, user, "email", structs.Map(provider.Claims{
280+
i, terr := a.createNewIdentity(tx, user, EmailProvider, structs.Map(provider.Claims{
278281
Subject: user.ID.String(),
279282
Email: params.Email,
280283
EmailVerified: params.EmailConfirm,
@@ -305,12 +308,12 @@ func (a *API) adminUserUpdate(w http.ResponseWriter, r *http.Request) error {
305308
}
306309

307310
if params.Phone != "" {
308-
if identity, terr := models.FindIdentityByIdAndProvider(tx, user.ID.String(), "phone"); terr != nil && !models.IsNotFoundError(terr) {
311+
if identity, terr := models.FindIdentityByIdAndProvider(tx, user.ID.String(), PhoneProvider); terr != nil && !models.IsNotFoundError(terr) {
309312
return terr
310313
} else if identity == nil {
311314
// if the user doesn't have an existing phone
312315
// then updating the user's phone should create a new phone identity
313-
identity, terr := a.createNewIdentity(tx, user, "phone", structs.Map(provider.Claims{
316+
identity, terr := a.createNewIdentity(tx, user, PhoneProvider, structs.Map(provider.Claims{
314317
Subject: user.ID.String(),
315318
Phone: params.Phone,
316319
PhoneVerified: params.PhoneConfirm,
@@ -340,6 +343,12 @@ func (a *API) adminUserUpdate(w http.ResponseWriter, r *http.Request) error {
340343
}
341344
user.Identities = append(user.Identities, identities...)
342345

346+
if addingFirstPassword {
347+
if terr := a.ensureEmailIdentityForPassword(tx, user); terr != nil {
348+
return terr
349+
}
350+
}
351+
343352
if params.AppMetaData != nil {
344353
if terr := user.UpdateAppMetaData(tx, params.AppMetaData); terr != nil {
345354
return terr
@@ -407,7 +416,7 @@ func (a *API) adminUserCreate(w http.ResponseWriter, r *http.Request) error {
407416
} else if user != nil {
408417
return apierrors.NewUnprocessableEntityError(apierrors.ErrorCodeEmailExists, DuplicateEmailMsg)
409418
}
410-
providers = append(providers, "email")
419+
providers = append(providers, EmailProvider)
411420
}
412421

413422
if params.Phone != "" {
@@ -420,7 +429,7 @@ func (a *API) adminUserCreate(w http.ResponseWriter, r *http.Request) error {
420429
} else if exists {
421430
return apierrors.NewUnprocessableEntityError(apierrors.ErrorCodePhoneExists, "Phone number already registered by another user")
422431
}
423-
providers = append(providers, "phone")
432+
providers = append(providers, PhoneProvider)
424433
}
425434

426435
if params.Password != nil && params.PasswordHash != "" {
@@ -486,7 +495,7 @@ func (a *API) adminUserCreate(w http.ResponseWriter, r *http.Request) error {
486495

487496
var identities []models.Identity
488497
if user.GetEmail() != "" {
489-
identity, terr := a.createNewIdentity(tx, user, "email", structs.Map(provider.Claims{
498+
identity, terr := a.createNewIdentity(tx, user, EmailProvider, structs.Map(provider.Claims{
490499
Subject: user.ID.String(),
491500
Email: user.GetEmail(),
492501
}))
@@ -498,7 +507,7 @@ func (a *API) adminUserCreate(w http.ResponseWriter, r *http.Request) error {
498507
}
499508

500509
if user.GetPhone() != "" {
501-
identity, terr := a.createNewIdentity(tx, user, "phone", structs.Map(provider.Claims{
510+
identity, terr := a.createNewIdentity(tx, user, PhoneProvider, structs.Map(provider.Claims{
502511
Subject: user.ID.String(),
503512
Phone: user.GetPhone(),
504513
}))

‎internal/api/anonymous.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func (a *API) SignupAnonymously(w http.ResponseWriter, r *http.Request) error {
2424
return err
2525
}
2626
params.Aud = aud
27-
params.Provider = "anonymous"
27+
params.Provider = AnonymousProvider
2828

2929
newUser, err := params.ToUserModel(false /* <- isSSOUser */)
3030
if err != nil {

‎internal/api/api.go‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ func NewAPIWithVersion(globalConfig *conf.GlobalConfiguration, db *storage.Conne
138138
api.oauthServer = oauthserver.NewServer(globalConfig, db, api.tokenService)
139139
}
140140

141+
api.scim = scim.NewServer(globalConfig)
142+
141143
if api.config.Password.HIBP.Enabled {
142144
httpClient := &http.Client{
143145
// all HIBP API requests should finish quickly to avoid
@@ -213,14 +215,6 @@ func NewAPIWithVersion(globalConfig *conf.GlobalConfiguration, db *storage.Conne
213215
r.Post("/", api.ExternalProviderCallback)
214216
})
215217

216-
api.scim = scim.NewServer(globalConfig)
217-
r.Route("/scim/v2", func(r *router) {
218-
r.Use(api.scim.Middleware)
219-
r.Get("/ServiceProviderConfig", api.scim.ServiceProviderConfig)
220-
r.Get("/ResourceTypes", api.scim.ResourceTypes)
221-
r.Get("/Schemas", api.scim.Schemas)
222-
})
223-
224218
r.Route("/", func(r *router) {
225219

226220
r.Use(api.isValidExternalHost)
@@ -456,6 +450,15 @@ func NewAPIWithVersion(globalConfig *conf.GlobalConfiguration, db *storage.Conne
456450
r.With(api.requireAuthentication).Get("/authorizations/{authorization_id}", api.oauthServer.OAuthServerGetAuthorization)
457451
r.With(api.requireAuthentication).Post("/authorizations/{authorization_id}/consent", api.oauthServer.OAuthServerConsent)
458452
})
453+
454+
r.Route(scim.BasePath, func(r *router) {
455+
r.Use(api.requireScimServerEnabled)
456+
r.NotFound(api.scim.NotFound)
457+
458+
r.Get("/ServiceProviderConfig", api.scim.ServiceProviderConfig)
459+
r.Get("/ResourceTypes", api.scim.ResourceTypes)
460+
r.Get("/Schemas", api.scim.Schemas)
461+
})
459462
})
460463

461464
corsHandler := cors.New(cors.Options{

0 commit comments

Comments
 (0)