From 6e0b863239622d5822ec6dfa62aaa53b244f86c2 Mon Sep 17 00:00:00 2001 From: Kang Ming Date: Tue, 24 Aug 2021 17:51:39 +0800 Subject: [PATCH 1/7] chore: add go-oidc package --- go.mod | 1 + go.sum | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/go.mod b/go.mod index 9f02925fe8..4336ba242c 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ require ( github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/badoux/checkmail v0.0.0-20170203135005-d0a759655d62 github.com/beevik/etree v1.1.0 + github.com/coreos/go-oidc/v3 v3.0.0 // indirect github.com/didip/tollbooth/v5 v5.1.1 github.com/fatih/color v1.10.0 // indirect github.com/go-chi/chi v4.0.2+incompatible diff --git a/go.sum b/go.sum index 1b2671ff06..6e9e042157 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkE github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ= +github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -655,6 +657,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -903,6 +906,8 @@ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkp gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= From 61446adbd098d6a379becd35e6ffb92f1c8f409e Mon Sep 17 00:00:00 2001 From: Kang Ming Date: Tue, 24 Aug 2021 17:52:03 +0800 Subject: [PATCH 2/7] fix: add id token grant flow --- api/token.go | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/api/token.go b/api/token.go index 404041df7a..81b2687d33 100644 --- a/api/token.go +++ b/api/token.go @@ -2,15 +2,21 @@ package api import ( "context" + "crypto/sha256" "encoding/json" + "fmt" "net/http" + "strconv" "time" + "github.com/coreos/go-oidc/v3/oidc" jwt "github.com/golang-jwt/jwt" "github.com/netlify/gotrue/conf" "github.com/netlify/gotrue/metering" "github.com/netlify/gotrue/models" "github.com/netlify/gotrue/storage" + "github.com/pkg/errors" + "github.com/sethvargo/go-password/password" ) // GoTrueClaims is a struct thats used for JWT claims @@ -44,6 +50,28 @@ type RefreshTokenGrantParams struct { RefreshToken string `json:"refresh_token"` } +// IdTokenGrantParams are the parameters the IdTokenGrant method accepts +type IdTokenGrantParams struct { + IdToken string `json:"id_token"` + Nonce string `json:"nonce"` + Provider string `json:"provider"` +} + +func (p *IdTokenGrantParams) getIssuer(ctx context.Context) (*oidc.Provider, error) { + switch p.Provider { + case "apple": + return oidc.NewProvider(ctx, "https://appleid.apple.com") + case "azure": + return oidc.NewProvider(ctx, "https://login.microsoftonline.com/common/v2.0") + case "facebook": + return oidc.NewProvider(ctx, "https://www.facebook.com") + case "google": + return oidc.NewProvider(ctx, "https://accounts.google.com") + default: + return nil, fmt.Errorf("Provider %s could not be found", p.Provider) + } +} + const useCookieHeader = "x-use-cookie" const useSessionCookie = "session" @@ -57,6 +85,8 @@ func (a *API) Token(w http.ResponseWriter, r *http.Request) error { return a.ResourceOwnerPasswordGrant(ctx, w, r) case "refresh_token": return a.RefreshTokenGrant(ctx, w, r) + case "id_token": + return a.IdTokenGrant(ctx, w, r) default: return oauthError("unsupported_grant_type", "") } @@ -208,6 +238,146 @@ func (a *API) RefreshTokenGrant(ctx context.Context, w http.ResponseWriter, r *h }) } +// IdTokenGrant implements the id_token grant type flow +func (a *API) IdTokenGrant(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + config := a.getConfig(ctx) + instanceID := getInstanceID(ctx) + + params := &IdTokenGrantParams{} + + jsonDecoder := json.NewDecoder(r.Body) + if err := jsonDecoder.Decode(params); err != nil { + return badRequestError("Could not read id token grant params: %v", err) + } + + if params.IdToken == "" || params.Nonce == "" || params.Provider == "" { + return oauthError("invalid request", "id_token, nonce and provider required") + } + + provider, err := params.getIssuer(ctx) + if err != nil { + return err + } + + verifier := provider.Verifier(&oidc.Config{SkipClientIDCheck: true}) + idToken, err := verifier.Verify(ctx, params.IdToken) + if err != nil { + return badRequestError("%v", err) + } + + claims := make(map[string]interface{}) + if err := idToken.Claims(&claims); err != nil { + return err + } + + // verify nonce to mitigate replay attacks + hashedNonce, ok := claims["nonce"] + if !ok { + return oauthError("invalid request", "missing nonce in id_token") + } + hash := fmt.Sprintf("%x", sha256.Sum256([]byte(params.Nonce))) + if hash != hashedNonce.(string) { + return oauthError("invalid nonce", "").WithInternalMessage("Possible abuse attempt: %v", r) + } + + // check if user exists already + email, ok := claims["email"].(string) + if !ok { + return errors.New("Unable to find email associated to provider") + } + + aud := claims["aud"].(string) + user, err := models.FindUserByEmailAndAudience(a.db, instanceID, email, aud) + + if err != nil && !models.IsNotFoundError(err) { + return internalServerError("Database error finding user").WithInternalError(err) + } + + var token *AccessTokenResponse + err = a.db.Transaction(func(tx *storage.Connection) error { + var terr error + if user == nil { + if config.DisableSignup { + return forbiddenError("Signups not allowed for this instance") + } + password, err := password.Generate(64, 10, 0, false, true) + if err != nil { + return internalServerError("error creating user").WithInternalError(err) + } + + signupParams := &SignupParams{ + Provider: params.Provider, + Email: email, + Password: password, + Aud: aud, + Data: claims, + } + + user, terr = a.signupNewUser(ctx, tx, signupParams) + if terr != nil { + return terr + } + } + + if !user.IsConfirmed() { + isEmailVerified := false + emailVerified, ok := claims["email_verified"].(string) + if ok { + isEmailVerified, terr = strconv.ParseBool(emailVerified) + if terr != nil { + return terr + } + } + if (!ok || !isEmailVerified) && !config.Mailer.Autoconfirm { + mailer := a.Mailer(ctx) + referrer := a.getReferrer(r) + if terr = sendConfirmation(tx, user, mailer, config.SMTP.MaxFrequency, referrer); terr != nil { + return internalServerError("Error sending confirmation mail").WithInternalError(terr) + } + return unauthorizedError("Error unverified email") + } + + if terr := models.NewAuditLogEntry(tx, instanceID, user, models.UserSignedUpAction, nil); terr != nil { + return terr + } + + if terr = triggerEventHooks(ctx, tx, SignupEvent, user, instanceID, config); terr != nil { + return terr + } + + if terr = user.Confirm(tx); terr != nil { + return internalServerError("Error updating user").WithInternalError(terr) + } + } else { + if terr := models.NewAuditLogEntry(tx, instanceID, user, models.LoginAction, nil); terr != nil { + return terr + } + if terr = triggerEventHooks(ctx, tx, LoginEvent, user, instanceID, config); terr != nil { + return terr + } + } + + token, terr = a.issueRefreshToken(ctx, tx, user) + if terr != nil { + return oauthError("server_error", terr.Error()) + } + return nil + }) + + if err != nil { + return err + } + + metering.RecordLogin("id_token", user.ID, instanceID) + return sendJSON(w, http.StatusOK, &AccessTokenResponse{ + Token: token.Token, + TokenType: token.TokenType, + ExpiresIn: token.ExpiresIn, + RefreshToken: token.RefreshToken, + User: user, + }) +} + func generateAccessToken(user *models.User, expiresIn time.Duration, secret string) (string, error) { claims := &GoTrueClaims{ StandardClaims: jwt.StandardClaims{ From 531e7ae926e4f5bb43aba79f0d5db83211fae794 Mon Sep 17 00:00:00 2001 From: Kang Ming Date: Mon, 30 Aug 2021 15:37:29 +0800 Subject: [PATCH 3/7] fix: add check for valid aud --- api/token.go | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/api/token.go b/api/token.go index 81b2687d33..2ed74a6364 100644 --- a/api/token.go +++ b/api/token.go @@ -57,23 +57,37 @@ type IdTokenGrantParams struct { Provider string `json:"provider"` } -func (p *IdTokenGrantParams) getIssuer(ctx context.Context) (*oidc.Provider, error) { +const useCookieHeader = "x-use-cookie" +const useSessionCookie = "session" + +func (p *IdTokenGrantParams) getVerifier(ctx context.Context) (*oidc.IDTokenVerifier, error) { + config := getConfig(ctx) + var provider *oidc.Provider + var err error + var clientId string switch p.Provider { case "apple": - return oidc.NewProvider(ctx, "https://appleid.apple.com") + clientId = config.External.Apple.ClientID + provider, err = oidc.NewProvider(ctx, "https://appleid.apple.com") case "azure": - return oidc.NewProvider(ctx, "https://login.microsoftonline.com/common/v2.0") + clientId = config.External.Azure.ClientID + provider, err = oidc.NewProvider(ctx, "https://login.microsoftonline.com/common/v2.0") case "facebook": - return oidc.NewProvider(ctx, "https://www.facebook.com") + clientId = config.External.Facebook.ClientID + provider, err = oidc.NewProvider(ctx, "https://www.facebook.com") case "google": - return oidc.NewProvider(ctx, "https://accounts.google.com") + clientId = config.External.Google.ClientID + provider, err = oidc.NewProvider(ctx, "https://accounts.google.com") default: - return nil, fmt.Errorf("Provider %s could not be found", p.Provider) + return nil, fmt.Errorf("Provider %s doesn't support the id_token grant flow", p.Provider) } -} -const useCookieHeader = "x-use-cookie" -const useSessionCookie = "session" + if err != nil { + return nil, err + } + + return provider.Verifier(&oidc.Config{ClientID: clientId}), nil +} // Token is the endpoint for OAuth access token requests func (a *API) Token(w http.ResponseWriter, r *http.Request) error { @@ -254,12 +268,11 @@ func (a *API) IdTokenGrant(ctx context.Context, w http.ResponseWriter, r *http.R return oauthError("invalid request", "id_token, nonce and provider required") } - provider, err := params.getIssuer(ctx) + verifier, err := params.getVerifier(ctx) if err != nil { return err } - verifier := provider.Verifier(&oidc.Config{SkipClientIDCheck: true}) idToken, err := verifier.Verify(ctx, params.IdToken) if err != nil { return badRequestError("%v", err) From 597cc81fc6f07d61702bd710dd6fda37c2d84f29 Mon Sep 17 00:00:00 2001 From: Kang Ming Date: Thu, 9 Sep 2021 11:51:53 +0800 Subject: [PATCH 4/7] chore: improve error msg for query error --- api/token.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/token.go b/api/token.go index 2ed74a6364..3d50d6dc93 100644 --- a/api/token.go +++ b/api/token.go @@ -139,7 +139,7 @@ func (a *API) ResourceOwnerPasswordGrant(ctx context.Context, w http.ResponseWri if models.IsNotFoundError(err) { return oauthError("invalid_grant", "Invalid login credentials") } - return internalServerError("Database error finding user").WithInternalError(err) + return internalServerError("Database error querying schema").WithInternalError(err) } if params.Email != "" && !user.IsConfirmed() { @@ -303,7 +303,7 @@ func (a *API) IdTokenGrant(ctx context.Context, w http.ResponseWriter, r *http.R user, err := models.FindUserByEmailAndAudience(a.db, instanceID, email, aud) if err != nil && !models.IsNotFoundError(err) { - return internalServerError("Database error finding user").WithInternalError(err) + return internalServerError("Database error querying schema").WithInternalError(err) } var token *AccessTokenResponse From e5ff39e1a828a39d214b85a33c9f4ef78bcc07f1 Mon Sep 17 00:00:00 2001 From: Kang Ming Date: Thu, 9 Sep 2021 12:17:23 +0800 Subject: [PATCH 5/7] chore: check if provider is enabled --- api/token.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/api/token.go b/api/token.go index 3d50d6dc93..db688d2fe6 100644 --- a/api/token.go +++ b/api/token.go @@ -64,19 +64,19 @@ func (p *IdTokenGrantParams) getVerifier(ctx context.Context) (*oidc.IDTokenVeri config := getConfig(ctx) var provider *oidc.Provider var err error - var clientId string + var oAuthProvider conf.OAuthProviderConfiguration switch p.Provider { case "apple": - clientId = config.External.Apple.ClientID + oAuthProvider = config.External.Apple provider, err = oidc.NewProvider(ctx, "https://appleid.apple.com") case "azure": - clientId = config.External.Azure.ClientID + oAuthProvider = config.External.Azure provider, err = oidc.NewProvider(ctx, "https://login.microsoftonline.com/common/v2.0") case "facebook": - clientId = config.External.Facebook.ClientID + oAuthProvider = config.External.Facebook provider, err = oidc.NewProvider(ctx, "https://www.facebook.com") case "google": - clientId = config.External.Google.ClientID + oAuthProvider = config.External.Google provider, err = oidc.NewProvider(ctx, "https://accounts.google.com") default: return nil, fmt.Errorf("Provider %s doesn't support the id_token grant flow", p.Provider) @@ -86,7 +86,11 @@ func (p *IdTokenGrantParams) getVerifier(ctx context.Context) (*oidc.IDTokenVeri return nil, err } - return provider.Verifier(&oidc.Config{ClientID: clientId}), nil + if !oAuthProvider.Enabled { + return nil, badRequestError("Provider is not enabled") + } + + return provider.Verifier(&oidc.Config{ClientID: oAuthProvider.ClientID}), nil } // Token is the endpoint for OAuth access token requests From a1bddcae857f07f66d66ea2b20122b1c4e7f14a8 Mon Sep 17 00:00:00 2001 From: Kang Ming Date: Thu, 7 Oct 2021 16:58:53 +0800 Subject: [PATCH 6/7] fix: add ios bundle id config --- conf/configuration.go | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/configuration.go b/conf/configuration.go index ebe311dedc..cb1c66d773 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -93,6 +93,7 @@ type ProviderConfiguration struct { Email EmailProviderConfiguration `json:"email"` Phone PhoneProviderConfiguration `json:"phone"` Saml SamlProviderConfiguration `json:"saml"` + IosBundleId string `json:"ios_bundle_id" split_words:"true"` RedirectURL string `json:"redirect_url"` } From de0c2b48406bdcccdd925c32a6a83bdf25e01b60 Mon Sep 17 00:00:00 2001 From: Kang Ming Date: Thu, 7 Oct 2021 17:02:33 +0800 Subject: [PATCH 7/7] fix: update id_token grant flow to match on provider_id --- api/external.go | 1 + api/provider/provider.go | 10 ++-- api/token.go | 99 +++++++++++++++++++++++++++------------- 3 files changed, 73 insertions(+), 37 deletions(-) diff --git a/api/external.go b/api/external.go index 28cfbd3f22..89931a542b 100644 --- a/api/external.go +++ b/api/external.go @@ -215,6 +215,7 @@ func (a *API) internalExternalProviderCallback(w http.ResponseWriter, r *http.Re if terr != nil { return terr } + identity.IdentityData = identityData if terr = tx.UpdateOnly(identity, "identity_data", "last_sign_in_at"); terr != nil { return terr } diff --git a/api/provider/provider.go b/api/provider/provider.go index 1196457235..afb36c5eee 100644 --- a/api/provider/provider.go +++ b/api/provider/provider.go @@ -9,11 +9,11 @@ import ( type Claims struct { // Reserved claims - Issuer string `json:"iss,omitempty"` - Subject string `json:"sub,omitempty"` - Aud string `json:"aud,omitempty"` - Iat string `json:"iat,omitempty"` - Exp string `json:"exp,omitempty"` + Issuer string `json:"iss,omitempty"` + Subject string `json:"sub,omitempty"` + Aud string `json:"aud,omitempty"` + Iat float64 `json:"iat,omitempty"` + Exp float64 `json:"exp,omitempty"` // Default profile claims Name string `json:"name,omitempty"` diff --git a/api/token.go b/api/token.go index f7dcf82eb7..f72b182897 100644 --- a/api/token.go +++ b/api/token.go @@ -15,8 +15,6 @@ import ( "github.com/netlify/gotrue/metering" "github.com/netlify/gotrue/models" "github.com/netlify/gotrue/storage" - "github.com/pkg/errors" - "github.com/sethvargo/go-password/password" ) // GoTrueClaims is a struct thats used for JWT claims @@ -63,21 +61,27 @@ const useSessionCookie = "session" func (p *IdTokenGrantParams) getVerifier(ctx context.Context) (*oidc.IDTokenVerifier, error) { config := getConfig(ctx) + var provider *oidc.Provider var err error var oAuthProvider conf.OAuthProviderConfiguration + var oAuthProviderClientId string switch p.Provider { case "apple": oAuthProvider = config.External.Apple + oAuthProviderClientId = config.External.IosBundleId provider, err = oidc.NewProvider(ctx, "https://appleid.apple.com") case "azure": oAuthProvider = config.External.Azure + oAuthProviderClientId = oAuthProvider.ClientID provider, err = oidc.NewProvider(ctx, "https://login.microsoftonline.com/common/v2.0") case "facebook": oAuthProvider = config.External.Facebook + oAuthProviderClientId = oAuthProvider.ClientID provider, err = oidc.NewProvider(ctx, "https://www.facebook.com") case "google": oAuthProvider = config.External.Google + oAuthProviderClientId = oAuthProvider.ClientID provider, err = oidc.NewProvider(ctx, "https://accounts.google.com") default: return nil, fmt.Errorf("Provider %s doesn't support the id_token grant flow", p.Provider) @@ -91,7 +95,24 @@ func (p *IdTokenGrantParams) getVerifier(ctx context.Context) (*oidc.IDTokenVeri return nil, badRequestError("Provider is not enabled") } - return provider.Verifier(&oidc.Config{ClientID: oAuthProvider.ClientID}), nil + return provider.Verifier(&oidc.Config{ClientID: oAuthProviderClientId}), nil +} + +func getEmailVerified(v interface{}) bool { + var emailVerified bool + var err error + switch v.(type) { + case string: + emailVerified, err = strconv.ParseBool(v.(string)) + case bool: + emailVerified = v.(bool) + default: + emailVerified = false + } + if err != nil { + return false + } + return emailVerified } // Token is the endpoint for OAuth access token requests @@ -304,53 +325,67 @@ func (a *API) IdTokenGrant(ctx context.Context, w http.ResponseWriter, r *http.R return oauthError("invalid nonce", "").WithInternalMessage("Possible abuse attempt: %v", r) } - // check if user exists already - email, ok := claims["email"].(string) + sub, ok := claims["sub"].(string) if !ok { - return errors.New("Unable to find email associated to provider") + return oauthError("invalid request", "missing sub claim in id_token") } - aud := claims["aud"].(string) - user, err := models.FindUserByEmailAndAudience(a.db, instanceID, email, aud) - - if err != nil && !models.IsNotFoundError(err) { - return internalServerError("Database error querying schema").WithInternalError(err) + email, ok := claims["email"].(string) + if !ok { + email = "" } + var user *models.User var token *AccessTokenResponse err = a.db.Transaction(func(tx *storage.Connection) error { var terr error - if user == nil { - if config.DisableSignup { - return forbiddenError("Signups not allowed for this instance") - } - password, err := password.Generate(64, 10, 0, false, true) - if err != nil { - return internalServerError("error creating user").WithInternalError(err) - } + var identity *models.Identity - signupParams := &SignupParams{ - Provider: params.Provider, - Email: email, - Password: password, - Aud: aud, - Data: claims, - } + if identity, terr = models.FindIdentityByIdAndProvider(tx, sub, params.Provider); terr != nil { + // create new identity & user if identity is not found + if models.IsNotFoundError(terr) { + if config.DisableSignup { + return forbiddenError("Signups not allowed for this instance") + } + aud := a.requestAud(ctx, r) + signupParams := &SignupParams{ + Provider: params.Provider, + Email: email, + Aud: aud, + Data: claims, + } - user, terr = a.signupNewUser(ctx, tx, signupParams) + user, terr = a.signupNewUser(ctx, tx, signupParams) + if terr != nil { + return terr + } + if identity, terr = a.createNewIdentity(tx, user, params.Provider, claims); terr != nil { + return terr + } + } else { + return terr + } + } else { + user, terr = models.FindUserByID(tx, identity.UserID) if terr != nil { return terr } + if email != "" { + identity.IdentityData["email"] = email + } + if terr = tx.UpdateOnly(identity, "identity_data", "last_sign_in_at"); terr != nil { + return terr + } + if terr = user.UpdateAppMetaDataProvider(tx); terr != nil { + return terr + } } if !user.IsConfirmed() { isEmailVerified := false - emailVerified, ok := claims["email_verified"].(string) + emailVerified, ok := claims["email_verified"] if ok { - isEmailVerified, terr = strconv.ParseBool(emailVerified) - if terr != nil { - return terr - } + isEmailVerified = getEmailVerified(emailVerified) } if (!ok || !isEmailVerified) && !config.Mailer.Autoconfirm { mailer := a.Mailer(ctx)