diff --git a/selfservice/strategy/oidc/provider.go b/selfservice/strategy/oidc/provider.go index 30ea305a22ed..77e2c1000580 100644 --- a/selfservice/strategy/oidc/provider.go +++ b/selfservice/strategy/oidc/provider.go @@ -27,6 +27,7 @@ type Provider interface { type OAuth2Provider interface { Provider AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption + AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption OAuth2(ctx context.Context) (*oauth2.Config, error) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) } diff --git a/selfservice/strategy/oidc/provider_apple.go b/selfservice/strategy/oidc/provider_apple.go index 706a7150c5e4..e41787e68809 100644 --- a/selfservice/strategy/oidc/provider_apple.go +++ b/selfservice/strategy/oidc/provider_apple.go @@ -20,6 +20,8 @@ import ( "golang.org/x/oauth2" ) +var _ OAuth2Provider = (*ProviderApple)(nil) + type ProviderApple struct { *ProviderGenericOIDC JWKSUrl string @@ -152,7 +154,7 @@ func (a *ProviderApple) DecodeQuery(query url.Values, claims *Claims) { } } -var _ IDTokenVerifier = new(ProviderApple) +var _ IDTokenVerifier = (*ProviderApple)(nil) const issuerUrlApple = "https://appleid.apple.com" @@ -163,7 +165,7 @@ func (a *ProviderApple) Verify(ctx context.Context, rawIDToken string) (*Claims, return verifyToken(ctx, keySet, a.config, rawIDToken, issuerUrlApple) } -var _ NonceValidationSkipper = new(ProviderApple) +var _ NonceValidationSkipper = (*ProviderApple)(nil) func (a *ProviderApple) CanSkipNonce(c *Claims) bool { return c.NonceSupported diff --git a/selfservice/strategy/oidc/provider_auth0.go b/selfservice/strategy/oidc/provider_auth0.go index a4c9ee46e1ab..0fbd89db19f5 100644 --- a/selfservice/strategy/oidc/provider_auth0.go +++ b/selfservice/strategy/oidc/provider_auth0.go @@ -25,6 +25,8 @@ import ( "github.com/ory/herodot" ) +var _ OAuth2Provider = (*ProviderAuth0)(nil) + type ProviderAuth0 struct { *ProviderGenericOIDC } diff --git a/selfservice/strategy/oidc/provider_dingtalk.go b/selfservice/strategy/oidc/provider_dingtalk.go index 12abffe85942..4038a23dcdb5 100644 --- a/selfservice/strategy/oidc/provider_dingtalk.go +++ b/selfservice/strategy/oidc/provider_dingtalk.go @@ -6,6 +6,7 @@ package oidc import ( "context" "encoding/json" + "net/http" "net/url" "strings" "time" @@ -20,6 +21,8 @@ import ( "github.com/ory/herodot" ) +var _ OAuth2Provider = (*ProviderDingTalk)(nil) + type ProviderDingTalk struct { config *Configuration reg Dependencies @@ -61,6 +64,10 @@ func (g *ProviderDingTalk) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { } } +func (g *ProviderDingTalk) AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + func (g *ProviderDingTalk) OAuth2(ctx context.Context) (*oauth2.Config, error) { return g.oauth2(ctx), nil } diff --git a/selfservice/strategy/oidc/provider_discord.go b/selfservice/strategy/oidc/provider_discord.go index 99bea24d5770..923f8310ed93 100644 --- a/selfservice/strategy/oidc/provider_discord.go +++ b/selfservice/strategy/oidc/provider_discord.go @@ -6,6 +6,7 @@ package oidc import ( "context" "fmt" + "net/http" "net/url" "github.com/ory/kratos/x" @@ -19,6 +20,8 @@ import ( "github.com/ory/x/stringsx" ) +var _ OAuth2Provider = (*ProviderDiscord)(nil) + type ProviderDiscord struct { config *Configuration reg Dependencies @@ -66,6 +69,10 @@ func (d *ProviderDiscord) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { } } +func (g *ProviderDiscord) AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + func (d *ProviderDiscord) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { grantedScopes := stringsx.Splitx(fmt.Sprintf("%s", exchange.Extra("scope")), " ") for _, check := range d.Config().Scope { diff --git a/selfservice/strategy/oidc/provider_facebook.go b/selfservice/strategy/oidc/provider_facebook.go index 8bbca9b24e83..00cdfb44a95d 100644 --- a/selfservice/strategy/oidc/provider_facebook.go +++ b/selfservice/strategy/oidc/provider_facebook.go @@ -24,6 +24,8 @@ import ( "github.com/ory/herodot" ) +var _ OAuth2Provider = (*ProviderFacebook)(nil) + type ProviderFacebook struct { *ProviderGenericOIDC } diff --git a/selfservice/strategy/oidc/provider_generic_oidc.go b/selfservice/strategy/oidc/provider_generic_oidc.go index 146505165807..b2913eb3586d 100644 --- a/selfservice/strategy/oidc/provider_generic_oidc.go +++ b/selfservice/strategy/oidc/provider_generic_oidc.go @@ -5,6 +5,7 @@ package oidc import ( "context" + "net/http" "net/url" "github.com/pkg/errors" @@ -16,7 +17,8 @@ import ( "github.com/ory/x/stringslice" ) -var _ Provider = new(ProviderGenericOIDC) +var _ Provider = (*ProviderGenericOIDC)(nil) +var _ OAuth2Provider = (*ProviderGenericOIDC)(nil) type ProviderGenericOIDC struct { p *gooidc.Provider @@ -96,6 +98,10 @@ func (g *ProviderGenericOIDC) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption return options } +func (g *ProviderGenericOIDC) AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + func (g *ProviderGenericOIDC) verifyAndDecodeClaimsWithProvider(ctx context.Context, provider *gooidc.Provider, raw string) (*Claims, error) { token, err := provider.VerifierContext(g.withHTTPClientContext(ctx), &gooidc.Config{ClientID: g.config.ClientID}).Verify(ctx, raw) if err != nil { diff --git a/selfservice/strategy/oidc/provider_github.go b/selfservice/strategy/oidc/provider_github.go index fe1d2bc371d1..aac88d3f6647 100644 --- a/selfservice/strategy/oidc/provider_github.go +++ b/selfservice/strategy/oidc/provider_github.go @@ -6,6 +6,7 @@ package oidc import ( "context" "fmt" + "net/http" "net/url" "github.com/ory/kratos/x" @@ -23,6 +24,8 @@ import ( "github.com/ory/herodot" ) +var _ OAuth2Provider = (*ProviderGitHub)(nil) + type ProviderGitHub struct { config *Configuration reg Dependencies @@ -60,6 +63,10 @@ func (g *ProviderGitHub) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } +func (g *ProviderGitHub) AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + func (g *ProviderGitHub) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { grantedScopes := stringsx.Splitx(fmt.Sprintf("%s", exchange.Extra("scope")), ",") for _, check := range g.Config().Scope { diff --git a/selfservice/strategy/oidc/provider_github_app.go b/selfservice/strategy/oidc/provider_github_app.go index 83cfd9bdb882..ff3f10bc6f91 100644 --- a/selfservice/strategy/oidc/provider_github_app.go +++ b/selfservice/strategy/oidc/provider_github_app.go @@ -6,6 +6,7 @@ package oidc import ( "context" "fmt" + "net/http" "net/url" "github.com/ory/kratos/x" @@ -20,6 +21,8 @@ import ( "github.com/ory/herodot" ) +var _ OAuth2Provider = (*ProviderGitHubApp)(nil) + type ProviderGitHubApp struct { config *Configuration reg Dependencies @@ -57,6 +60,10 @@ func (g *ProviderGitHubApp) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } +func (g *ProviderGitHubApp) AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + func (g *ProviderGitHubApp) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { ctx, client := httpx.SetOAuth2(ctx, g.reg.HTTPClient(ctx), g.oauth2(ctx), exchange) gh := ghapi.NewClient(client.HTTPClient) diff --git a/selfservice/strategy/oidc/provider_gitlab.go b/selfservice/strategy/oidc/provider_gitlab.go index 9ef55b4beef7..a0cf7508c944 100644 --- a/selfservice/strategy/oidc/provider_gitlab.go +++ b/selfservice/strategy/oidc/provider_gitlab.go @@ -25,6 +25,8 @@ const ( defaultEndpoint = "https://gitlab.com" ) +var _ OAuth2Provider = (*ProviderGitLab)(nil) + type ProviderGitLab struct { *ProviderGenericOIDC } diff --git a/selfservice/strategy/oidc/provider_google.go b/selfservice/strategy/oidc/provider_google.go index e27832692faa..fd3fe0560330 100644 --- a/selfservice/strategy/oidc/provider_google.go +++ b/selfservice/strategy/oidc/provider_google.go @@ -12,6 +12,8 @@ import ( "github.com/ory/x/stringslice" ) +var _ OAuth2Provider = (*ProviderGoogle)(nil) + type ProviderGoogle struct { *ProviderGenericOIDC JWKSUrl string @@ -69,7 +71,7 @@ func (g *ProviderGoogle) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { return options } -var _ IDTokenVerifier = new(ProviderGoogle) +var _ IDTokenVerifier = (*ProviderGoogle)(nil) const issuerUrlGoogle = "https://accounts.google.com" @@ -79,7 +81,7 @@ func (p *ProviderGoogle) Verify(ctx context.Context, rawIDToken string) (*Claims return verifyToken(ctx, keySet, p.config, rawIDToken, issuerUrlGoogle) } -var _ NonceValidationSkipper = new(ProviderGoogle) +var _ NonceValidationSkipper = (*ProviderGoogle)(nil) func (a *ProviderGoogle) CanSkipNonce(c *Claims) bool { // Not all SDKs support nonce validation, so we skip it if no nonce is present in the claims of the ID Token. diff --git a/selfservice/strategy/oidc/provider_lark.go b/selfservice/strategy/oidc/provider_lark.go index 52902dc20e8c..d66d5c0b2230 100644 --- a/selfservice/strategy/oidc/provider_lark.go +++ b/selfservice/strategy/oidc/provider_lark.go @@ -16,6 +16,8 @@ import ( "github.com/ory/x/httpx" ) +var _ OAuth2Provider = (*ProviderLark)(nil) + type ProviderLark struct { *ProviderGenericOIDC } diff --git a/selfservice/strategy/oidc/provider_linkedin.go b/selfservice/strategy/oidc/provider_linkedin.go index 03a3db3e490d..f748d49a7b5b 100644 --- a/selfservice/strategy/oidc/provider_linkedin.go +++ b/selfservice/strategy/oidc/provider_linkedin.go @@ -63,6 +63,8 @@ const ( IntrospectionURL string = "https://www.linkedin.com/oauth/v2/introspectToken" ) +var _ OAuth2Provider = (*ProviderLinkedIn)(nil) + type ProviderLinkedIn struct { config *Configuration reg Dependencies @@ -100,6 +102,10 @@ func (l *ProviderLinkedIn) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } +func (g *ProviderLinkedIn) AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + func (l *ProviderLinkedIn) fetch(ctx context.Context, client *retryablehttp.Client, url string, result interface{}) (err error) { ctx, span := l.reg.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.oidc.ProviderLinkedIn.fetch") defer otelx.End(span, &err) diff --git a/selfservice/strategy/oidc/provider_microsoft.go b/selfservice/strategy/oidc/provider_microsoft.go index d69206ec4d87..5ff919a56be9 100644 --- a/selfservice/strategy/oidc/provider_microsoft.go +++ b/selfservice/strategy/oidc/provider_microsoft.go @@ -23,6 +23,8 @@ import ( "github.com/ory/herodot" ) +var _ OAuth2Provider = (*ProviderMicrosoft)(nil) + type ProviderMicrosoft struct { *ProviderGenericOIDC } diff --git a/selfservice/strategy/oidc/provider_netid.go b/selfservice/strategy/oidc/provider_netid.go index dfe83c958433..8b5c7eda46a6 100644 --- a/selfservice/strategy/oidc/provider_netid.go +++ b/selfservice/strategy/oidc/provider_netid.go @@ -28,6 +28,8 @@ const ( defaultBrokerHost = "broker.netid.de" ) +var _ OAuth2Provider = (*ProviderNetID)(nil) + type ProviderNetID struct { *ProviderGenericOIDC } diff --git a/selfservice/strategy/oidc/provider_patreon.go b/selfservice/strategy/oidc/provider_patreon.go index 745dc8fcc199..efe6e6fc2af6 100644 --- a/selfservice/strategy/oidc/provider_patreon.go +++ b/selfservice/strategy/oidc/provider_patreon.go @@ -6,6 +6,7 @@ package oidc import ( "context" "encoding/json" + "net/http" "net/url" "github.com/hashicorp/go-retryablehttp" @@ -18,6 +19,8 @@ import ( "github.com/ory/herodot" ) +var _ OAuth2Provider = (*ProviderPatreon)(nil) + type ProviderPatreon struct { config *Configuration reg Dependencies @@ -79,6 +82,10 @@ func (d *ProviderPatreon) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { } } +func (g *ProviderPatreon) AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + func (d *ProviderPatreon) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { identityUrl := "https://www.patreon.com/api/oauth2/v2/identity?fields%5Buser%5D=first_name,last_name,url,full_name,email,image_url" diff --git a/selfservice/strategy/oidc/provider_salesforce.go b/selfservice/strategy/oidc/provider_salesforce.go index 1d028a1a8de7..04d514ccdf22 100644 --- a/selfservice/strategy/oidc/provider_salesforce.go +++ b/selfservice/strategy/oidc/provider_salesforce.go @@ -25,6 +25,8 @@ import ( "github.com/ory/herodot" ) +var _ OAuth2Provider = (*ProviderSalesforce)(nil) + type ProviderSalesforce struct { *ProviderGenericOIDC } diff --git a/selfservice/strategy/oidc/provider_slack.go b/selfservice/strategy/oidc/provider_slack.go index 7c7e26c99da4..e9312ca8300c 100644 --- a/selfservice/strategy/oidc/provider_slack.go +++ b/selfservice/strategy/oidc/provider_slack.go @@ -6,6 +6,7 @@ package oidc import ( "context" "fmt" + "net/http" "net/url" "github.com/ory/herodot" @@ -19,6 +20,8 @@ import ( "github.com/slack-go/slack" ) +var _ OAuth2Provider = (*ProviderSlack)(nil) + type ProviderSlack struct { config *Configuration reg Dependencies @@ -61,6 +64,10 @@ func (d *ProviderSlack) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } +func (g *ProviderSlack) AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + func (d *ProviderSlack) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { grantedScopes := stringsx.Splitx(fmt.Sprintf("%s", exchange.Extra("scope")), ",") for _, check := range d.Config().Scope { diff --git a/selfservice/strategy/oidc/provider_spotify.go b/selfservice/strategy/oidc/provider_spotify.go index 366105c94d0e..1cf88858848e 100644 --- a/selfservice/strategy/oidc/provider_spotify.go +++ b/selfservice/strategy/oidc/provider_spotify.go @@ -6,6 +6,7 @@ package oidc import ( "context" "fmt" + "net/http" "net/url" "golang.org/x/oauth2/spotify" @@ -23,6 +24,8 @@ import ( "github.com/ory/herodot" ) +var _ OAuth2Provider = (*ProviderSpotify)(nil) + type ProviderSpotify struct { config *Configuration reg Dependencies @@ -60,6 +63,10 @@ func (g *ProviderSpotify) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } +func (g *ProviderSpotify) AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + func (g *ProviderSpotify) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { grantedScopes := stringsx.Splitx(fmt.Sprintf("%s", exchange.Extra("scope")), " ") for _, check := range g.Config().Scope { diff --git a/selfservice/strategy/oidc/provider_vk.go b/selfservice/strategy/oidc/provider_vk.go index 2a3513b6e050..12fd63045654 100644 --- a/selfservice/strategy/oidc/provider_vk.go +++ b/selfservice/strategy/oidc/provider_vk.go @@ -6,8 +6,11 @@ package oidc import ( "context" "encoding/json" + "net/http" "net/url" "strconv" + "strings" + "time" "github.com/hashicorp/go-retryablehttp" @@ -19,6 +22,8 @@ import ( "github.com/ory/herodot" ) +var _ OAuth2Provider = (*ProviderVK)(nil) + type ProviderVK struct { config *Configuration reg Dependencies @@ -34,38 +39,56 @@ func NewProviderVK( } } -func (g *ProviderVK) Config() *Configuration { - return g.config +func (p *ProviderVK) Config() *Configuration { + return p.config } -func (g *ProviderVK) oauth2(ctx context.Context) *oauth2.Config { - return &oauth2.Config{ - ClientID: g.config.ClientID, - ClientSecret: g.config.ClientSecret, - Endpoint: oauth2.Endpoint{ +func (p *ProviderVK) oauth2(ctx context.Context) *oauth2.Config { + var endpoint oauth2.Endpoint + if p.config.PKCE == "force" { + endpoint = oauth2.Endpoint{ + AuthURL: "https://id.vk.com/authorize", + TokenURL: "https://id.vk.com/oauth2/auth", + } + } else { + endpoint = oauth2.Endpoint{ AuthURL: "https://oauth.vk.com/authorize", TokenURL: "https://oauth.vk.com/access_token", - }, - Scopes: g.config.Scope, - RedirectURL: g.config.Redir(g.reg.Config().OIDCRedirectURIBase(ctx)), + } + } + return &oauth2.Config{ + ClientID: p.config.ClientID, + ClientSecret: p.config.ClientSecret, + Endpoint: endpoint, + Scopes: p.config.Scope, + RedirectURL: p.config.Redir(p.reg.Config().OIDCRedirectURIBase(ctx)), } } -func (g *ProviderVK) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { +func (p *ProviderVK) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + +func (p *ProviderVK) AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption { + if p.config.PKCE == "force" { + if deviceID := r.URL.Query().Get("device_id"); deviceID != "" { + return []oauth2.AuthCodeOption{oauth2.SetAuthURLParam("device_id", deviceID)} + } + } return []oauth2.AuthCodeOption{} } -func (g *ProviderVK) OAuth2(ctx context.Context) (*oauth2.Config, error) { - return g.oauth2(ctx), nil +func (p *ProviderVK) OAuth2(ctx context.Context) (*oauth2.Config, error) { + return p.oauth2(ctx), nil } -func (g *ProviderVK) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { - o, err := g.OAuth2(ctx) +func (p *ProviderVK) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { + o, err := p.OAuth2(ctx) if err != nil { return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) } - ctx, client := httpx.SetOAuth2(ctx, g.reg.HTTPClient(ctx), o, exchange) + ctx, client := httpx.SetOAuth2(ctx, p.reg.HTTPClient(ctx), o, exchange) req, err := retryablehttp.NewRequestWithContext(ctx, "GET", "https://api.vk.com/method/users.get?fields=photo_200,nickname,bdate,sex&access_token="+exchange.AccessToken+"&v=5.103", nil) if err != nil { return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) @@ -77,7 +100,7 @@ func (g *ProviderVK) Claims(ctx context.Context, exchange *oauth2.Token, query u } defer resp.Body.Close() - if err := logUpstreamError(g.reg.Logger(), resp); err != nil { + if err := logUpstreamError(p.reg.Logger(), resp); err != nil { return nil, err } @@ -88,6 +111,7 @@ func (g *ProviderVK) Claims(ctx context.Context, exchange *oauth2.Token, query u Nickname string `json:"nickname,omitempty"` Picture string `json:"photo_200,omitempty"` Email string `json:"-"` + Phone string `json:"-"` Gender int `json:"sex,omitempty"` BirthDay string `json:"bdate,omitempty"` } @@ -106,8 +130,45 @@ func (g *ProviderVK) Claims(ctx context.Context, exchange *oauth2.Token, query u user := response.Result[0] - if email, ok := exchange.Extra("email").(string); ok { - user.Email = email + if p.config.PKCE == "force" { + reqData := strings.NewReader("client_id=" + p.config.ClientID + "&access_token=" + exchange.AccessToken) + req, err = retryablehttp.NewRequestWithContext(ctx, "POST", "https://id.vk.com/oauth2/user_info", reqData) + if err != nil { + return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err = client.Do(req) + if err != nil { + return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) + } + defer resp.Body.Close() + + if err := logUpstreamError(p.reg.Logger(), resp); err != nil { + return nil, err + } + + var userInfo struct { + User struct { + Email string `json:"email,omitempty"` + Phone string `json:"phone,omitempty"` + } `json:"user,omitempty"` + } + + if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { + return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) + } + + if len(userInfo.User.Phone) > 0 && userInfo.User.Phone[0] != '+' { + userInfo.User.Phone = "+" + userInfo.User.Phone + } + + user.Email = userInfo.User.Email + user.Phone = userInfo.User.Phone + } else { + if email, ok := exchange.Extra("email").(string); ok { + user.Email = email + } } gender := "" @@ -118,15 +179,22 @@ func (g *ProviderVK) Claims(ctx context.Context, exchange *oauth2.Token, query u gender = "male" } + t, err := time.Parse("2.1.2006", user.BirthDay) + if err != nil { + return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) + } + birthDay := t.Format("2006-01-02") + return &Claims{ - Issuer: "https://api.vk.com/method/users.get", - Subject: strconv.Itoa(user.Id), - GivenName: user.FirstName, - FamilyName: user.LastName, - Nickname: user.Nickname, - Picture: user.Picture, - Email: user.Email, - Gender: gender, - Birthdate: user.BirthDay, + Issuer: "https://api.vk.com/method/users.get", + Subject: strconv.Itoa(user.Id), + GivenName: user.FirstName, + FamilyName: user.LastName, + Nickname: user.Nickname, + Picture: user.Picture, + Email: user.Email, + PhoneNumber: user.Phone, + Gender: gender, + Birthdate: birthDay, }, nil } diff --git a/selfservice/strategy/oidc/provider_yandex.go b/selfservice/strategy/oidc/provider_yandex.go index 07b30caee52b..716c1c9e8807 100644 --- a/selfservice/strategy/oidc/provider_yandex.go +++ b/selfservice/strategy/oidc/provider_yandex.go @@ -6,6 +6,7 @@ package oidc import ( "context" "encoding/json" + "net/http" "net/url" "github.com/hashicorp/go-retryablehttp" @@ -17,6 +18,8 @@ import ( "github.com/ory/herodot" ) +var _ OAuth2Provider = (*ProviderYandex)(nil) + type ProviderYandex struct { config *Configuration reg Dependencies @@ -32,38 +35,42 @@ func NewProviderYandex( } } -func (g *ProviderYandex) Config() *Configuration { - return g.config +func (p *ProviderYandex) Config() *Configuration { + return p.config } -func (g *ProviderYandex) oauth2(ctx context.Context) *oauth2.Config { +func (p *ProviderYandex) oauth2(ctx context.Context) *oauth2.Config { return &oauth2.Config{ - ClientID: g.config.ClientID, - ClientSecret: g.config.ClientSecret, + ClientID: p.config.ClientID, + ClientSecret: p.config.ClientSecret, Endpoint: oauth2.Endpoint{ AuthURL: "https://oauth.yandex.com/authorize", TokenURL: "https://oauth.yandex.com/token", }, - Scopes: g.config.Scope, - RedirectURL: g.config.Redir(g.reg.Config().OIDCRedirectURIBase(ctx)), + Scopes: p.config.Scope, + RedirectURL: p.config.Redir(p.reg.Config().OIDCRedirectURIBase(ctx)), } } -func (g *ProviderYandex) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { +func (p *ProviderYandex) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + +func (p *ProviderYandex) AccessTokenURLOptions(r *http.Request) []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } -func (g *ProviderYandex) OAuth2(ctx context.Context) (*oauth2.Config, error) { - return g.oauth2(ctx), nil +func (p *ProviderYandex) OAuth2(ctx context.Context) (*oauth2.Config, error) { + return p.oauth2(ctx), nil } -func (g *ProviderYandex) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { - o, err := g.OAuth2(ctx) +func (p *ProviderYandex) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (*Claims, error) { + o, err := p.OAuth2(ctx) if err != nil { return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) } - ctx, client := httpx.SetOAuth2(ctx, g.reg.HTTPClient(ctx), o, exchange) + ctx, client := httpx.SetOAuth2(ctx, p.reg.HTTPClient(ctx), o, exchange) req, err := retryablehttp.NewRequestWithContext(ctx, "GET", "https://login.yandex.ru/info?format=json&oauth_token="+exchange.AccessToken, nil) if err != nil { return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) @@ -75,15 +82,18 @@ func (g *ProviderYandex) Claims(ctx context.Context, exchange *oauth2.Token, que } defer resp.Body.Close() - if err := logUpstreamError(g.reg.Logger(), resp); err != nil { + if err := logUpstreamError(p.reg.Logger(), resp); err != nil { return nil, err } var user struct { - Id string `json:"id,omitempty"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - Email string `json:"default_email,omitempty"` + Id string `json:"id,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + Email string `json:"default_email,omitempty"` + Phone struct { + Number string `json:"number,omitempty"` + } `json:"default_phone,omitempty"` Picture string `json:"default_avatar_id,omitempty"` PictureEmpty bool `json:"is_avatar_empty,omitempty"` Gender string `json:"sex,omitempty"` @@ -101,13 +111,14 @@ func (g *ProviderYandex) Claims(ctx context.Context, exchange *oauth2.Token, que } return &Claims{ - Issuer: "https://login.yandex.ru/info", - Subject: user.Id, - GivenName: user.FirstName, - FamilyName: user.LastName, - Picture: user.Picture, - Email: user.Email, - Gender: user.Gender, - Birthdate: user.BirthDay, + Issuer: "https://login.yandex.ru/info", + Subject: user.Id, + GivenName: user.FirstName, + FamilyName: user.LastName, + Picture: user.Picture, + Email: user.Email, + PhoneNumber: user.Phone.Number, + Gender: user.Gender, + Birthdate: user.BirthDay, }, nil } diff --git a/selfservice/strategy/oidc/strategy.go b/selfservice/strategy/oidc/strategy.go index 6539d2cfd094..e13ed47c8b81 100644 --- a/selfservice/strategy/oidc/strategy.go +++ b/selfservice/strategy/oidc/strategy.go @@ -405,7 +405,7 @@ func (s *Strategy) HandleCallback(w http.ResponseWriter, r *http.Request, ps htt var et *identity.CredentialsOIDCEncryptedTokens switch p := provider.(type) { case OAuth2Provider: - token, err := s.ExchangeCode(ctx, provider, code, PKCEVerifier(state)) + token, err := s.ExchangeCode(ctx, provider, code, append(p.AccessTokenURLOptions(r), PKCEVerifier(state)...)) if err != nil { s.forwardError(ctx, w, r, req, s.handleError(ctx, w, r, req, state.ProviderId, nil, err)) return