chatdesk-ui/auth_v2.169.0/internal/api/provider/linkedin.go

150 lines
4.2 KiB
Go

package provider
import (
"context"
"strings"
"github.com/supabase/auth/internal/conf"
"golang.org/x/oauth2"
)
const (
defaultLinkedinAPIBase = "api.linkedin.com"
)
type linkedinProvider struct {
*oauth2.Config
APIPath string
UserInfoURL string
UserEmailUrl string
}
// See https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin?context=linkedin/consumer/context
// for retrieving a member's profile. This requires the r_liteprofile scope.
type linkedinUser struct {
ID string `json:"id"`
FirstName linkedinName `json:"firstName"`
LastName linkedinName `json:"lastName"`
AvatarURL struct {
DisplayImage struct {
Elements []struct {
Identifiers []struct {
Identifier string `json:"identifier"`
} `json:"identifiers"`
} `json:"elements"`
} `json:"displayImage~"`
} `json:"profilePicture"`
}
func (u *linkedinUser) getAvatarUrl() string {
avatarURL := ""
if len(u.AvatarURL.DisplayImage.Elements) > 0 {
avatarURL = u.AvatarURL.DisplayImage.Elements[0].Identifiers[0].Identifier
}
return avatarURL
}
type linkedinName struct {
Localized interface{} `json:"localized"`
PreferredLocale linkedinLocale `json:"preferredLocale"`
}
type linkedinLocale struct {
Country string `json:"country"`
Language string `json:"language"`
}
// See https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin?context=linkedin/consumer/context#retrieving-member-email-address
// for retrieving a member email address. This requires the r_email_address scope.
type linkedinElements struct {
Elements []struct {
Handle string `json:"handle"`
HandleTilde struct {
EmailAddress string `json:"emailAddress"`
} `json:"handle~"`
} `json:"elements"`
}
// NewLinkedinProvider creates a Linkedin account provider.
func NewLinkedinProvider(ext conf.OAuthProviderConfiguration, scopes string) (OAuthProvider, error) {
if err := ext.ValidateOAuth(); err != nil {
return nil, err
}
apiPath := chooseHost(ext.URL, defaultLinkedinAPIBase)
oauthScopes := []string{
"r_emailaddress",
"r_liteprofile",
}
if scopes != "" {
oauthScopes = append(oauthScopes, strings.Split(scopes, ",")...)
}
return &linkedinProvider{
Config: &oauth2.Config{
ClientID: ext.ClientID[0],
ClientSecret: ext.Secret,
Endpoint: oauth2.Endpoint{
AuthURL: apiPath + "/oauth/v2/authorization",
TokenURL: apiPath + "/oauth/v2/accessToken",
},
Scopes: oauthScopes,
RedirectURL: ext.RedirectURI,
},
APIPath: apiPath,
}, nil
}
func (g linkedinProvider) GetOAuthToken(code string) (*oauth2.Token, error) {
return g.Exchange(context.Background(), code)
}
func GetName(name linkedinName) string {
key := name.PreferredLocale.Language + "_" + name.PreferredLocale.Country
myMap := name.Localized.(map[string]interface{})
return myMap[key].(string)
}
func (g linkedinProvider) GetUserData(ctx context.Context, tok *oauth2.Token) (*UserProvidedData, error) {
var u linkedinUser
if err := makeRequest(ctx, tok, g.Config, g.APIPath+"/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))", &u); err != nil {
return nil, err
}
var e linkedinElements
// Note: Use primary contact api for handling phone numbers
if err := makeRequest(ctx, tok, g.Config, g.APIPath+"/v2/emailAddress?q=members&projection=(elements*(handle~))", &e); err != nil {
return nil, err
}
data := &UserProvidedData{}
if e.Elements[0].HandleTilde.EmailAddress != "" {
// linkedin only returns the primary email which is verified for the r_emailaddress scope.
data.Emails = []Email{{
Email: e.Elements[0].HandleTilde.EmailAddress,
Primary: true,
Verified: true,
}}
}
avatarURL := u.getAvatarUrl()
data.Metadata = &Claims{
Issuer: g.APIPath,
Subject: u.ID,
Name: strings.TrimSpace(GetName(u.FirstName) + " " + GetName(u.LastName)),
Picture: avatarURL,
Email: e.Elements[0].HandleTilde.EmailAddress,
EmailVerified: true,
// To be deprecated
AvatarURL: avatarURL,
FullName: strings.TrimSpace(GetName(u.FirstName) + " " + GetName(u.LastName)),
ProviderId: u.ID,
}
return data, nil
}