package provider import ( "context" "strings" "github.com/coreos/go-oidc/v3/oidc" "github.com/sirupsen/logrus" "github.com/supabase/auth/internal/conf" "golang.org/x/oauth2" ) type googleUser struct { ID string `json:"id"` Subject string `json:"sub"` Issuer string `json:"iss"` Name string `json:"name"` AvatarURL string `json:"picture"` Email string `json:"email"` VerifiedEmail bool `json:"verified_email"` EmailVerified bool `json:"email_verified"` HostedDomain string `json:"hd"` } func (u googleUser) IsEmailVerified() bool { return u.VerifiedEmail || u.EmailVerified } const IssuerGoogle = "https://accounts.google.com" var internalIssuerGoogle = IssuerGoogle type googleProvider struct { *oauth2.Config oidc *oidc.Provider } // NewGoogleProvider creates a Google OAuth2 identity provider. func NewGoogleProvider(ctx context.Context, ext conf.OAuthProviderConfiguration, scopes string) (OAuthProvider, error) { if err := ext.ValidateOAuth(); err != nil { return nil, err } if ext.URL != "" { logrus.Warn("Google OAuth provider has URL config set which is ignored (check GOTRUE_EXTERNAL_GOOGLE_URL)") } oauthScopes := []string{ "email", "profile", } if scopes != "" { oauthScopes = append(oauthScopes, strings.Split(scopes, ",")...) } oidcProvider, err := oidc.NewProvider(ctx, internalIssuerGoogle) if err != nil { return nil, err } return &googleProvider{ Config: &oauth2.Config{ ClientID: ext.ClientID[0], ClientSecret: ext.Secret, Endpoint: oidcProvider.Endpoint(), Scopes: oauthScopes, RedirectURL: ext.RedirectURI, }, oidc: oidcProvider, }, nil } func (g googleProvider) GetOAuthToken(code string) (*oauth2.Token, error) { return g.Exchange(context.Background(), code) } const UserInfoEndpointGoogle = "https://www.googleapis.com/userinfo/v2/me" var internalUserInfoEndpointGoogle = UserInfoEndpointGoogle func (g googleProvider) GetUserData(ctx context.Context, tok *oauth2.Token) (*UserProvidedData, error) { if idToken := tok.Extra("id_token"); idToken != nil { _, data, err := ParseIDToken(ctx, g.oidc, &oidc.Config{ ClientID: g.Config.ClientID, }, idToken.(string), ParseIDTokenOptions{ AccessToken: tok.AccessToken, }) if err != nil { return nil, err } return data, nil } // This whole section offers legacy support in case the Google OAuth2 // flow does not return an ID Token for the user, which appears to // always be the case. logrus.Info("Using Google OAuth2 user info endpoint, an ID token was not returned by Google") var u googleUser if err := makeRequest(ctx, tok, g.Config, internalUserInfoEndpointGoogle, &u); err != nil { return nil, err } var data UserProvidedData if u.Email != "" { data.Emails = append(data.Emails, Email{ Email: u.Email, Verified: u.IsEmailVerified(), Primary: true, }) } data.Metadata = &Claims{ Issuer: internalUserInfoEndpointGoogle, Subject: u.ID, Name: u.Name, Picture: u.AvatarURL, Email: u.Email, EmailVerified: u.IsEmailVerified(), // To be deprecated AvatarURL: u.AvatarURL, FullName: u.Name, ProviderId: u.ID, } return &data, nil } // ResetGoogleProvider should only be used in tests! func ResetGoogleProvider() { internalIssuerGoogle = IssuerGoogle internalUserInfoEndpointGoogle = UserInfoEndpointGoogle } // OverrideGoogleProvider should only be used in tests! func OverrideGoogleProvider(issuer, userInfo string) { internalIssuerGoogle = issuer internalUserInfoEndpointGoogle = userInfo }