1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2024-11-28 10:03:42 +02:00

[#654] updated OAuth2 providers to return the access token and raw user data

This commit is contained in:
Gani Georgiev 2022-11-30 15:16:09 +02:00
parent 9ba710cdc5
commit 799e1d96f8
12 changed files with 250 additions and 115 deletions

View File

@ -9,11 +9,13 @@ import (
// AuthUser defines a standardized oauth2 user data structure. // AuthUser defines a standardized oauth2 user data structure.
type AuthUser struct { type AuthUser struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Username string `json:"username"` Username string `json:"username"`
Email string `json:"email"` Email string `json:"email"`
AvatarUrl string `json:"avatarUrl"` AvatarUrl string `json:"avatarUrl"`
RawUser map[string]any `json:"rawUser"`
AccessToken string `json:"accessToken"`
} }
// Provider defines a common interface for an OAuth2 client. // Provider defines a common interface for an OAuth2 client.
@ -73,7 +75,7 @@ type Provider interface {
// FetchRawUserData requests and marshalizes into `result` the // FetchRawUserData requests and marshalizes into `result` the
// the OAuth user api response. // the OAuth user api response.
FetchRawUserData(token *oauth2.Token, result any) error FetchRawUserData(token *oauth2.Token) ([]byte, error)
// FetchAuthUser is similar to FetchRawUserData, but normalizes and // FetchAuthUser is similar to FetchRawUserData, but normalizes and
// marshalizes the user api response into a standardized AuthUser struct. // marshalizes the user api response into a standardized AuthUser struct.

View File

@ -2,7 +2,6 @@ package auth
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -107,42 +106,41 @@ func (p *baseProvider) Client(token *oauth2.Token) *http.Client {
} }
// FetchRawUserData implements Provider.FetchRawUserData interface. // FetchRawUserData implements Provider.FetchRawUserData interface.
func (p *baseProvider) FetchRawUserData(token *oauth2.Token, result any) error { func (p *baseProvider) FetchRawUserData(token *oauth2.Token) ([]byte, error) {
req, err := http.NewRequest("GET", p.userApiUrl, nil) req, err := http.NewRequest("GET", p.userApiUrl, nil)
if err != nil { if err != nil {
return err return nil, err
} }
return p.sendRawUserDataRequest(req, token, result) return p.sendRawUserDataRequest(req, token)
} }
// sendRawUserDataRequest sends the specified request and // sendRawUserDataRequest sends the specified user data request and return its raw response body.
// unmarshal the response body into result. func (p *baseProvider) sendRawUserDataRequest(req *http.Request, token *oauth2.Token) ([]byte, error) {
func (p *baseProvider) sendRawUserDataRequest(req *http.Request, token *oauth2.Token, result any) error {
client := p.Client(token) client := p.Client(token)
response, err := client.Do(req) response, err := client.Do(req)
if err != nil { if err != nil {
return err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
content, err := ioutil.ReadAll(response.Body) result, err := ioutil.ReadAll(response.Body)
if err != nil { if err != nil {
return err return nil, err
} }
// http.Client.Get doesn't treat non 2xx responses as error // http.Client.Get doesn't treat non 2xx responses as error
if response.StatusCode >= 400 { if response.StatusCode >= 400 {
return fmt.Errorf( return nil, fmt.Errorf(
"Failed to fetch OAuth2 user profile via %s (%d):\n%s", "Failed to fetch OAuth2 user profile via %s (%d):\n%s",
p.userApiUrl, p.userApiUrl,
response.StatusCode, response.StatusCode,
string(content), string(result),
) )
} }
return json.Unmarshal(content, &result) return result, nil
} }
// oauth2Config constructs a oauth2.Config instance based on the provider settings. // oauth2Config constructs a oauth2.Config instance based on the provider settings.

View File

@ -1,6 +1,7 @@
package auth package auth
import ( import (
"encoding/json"
"fmt" "fmt"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -29,33 +30,48 @@ func NewDiscordProvider() *Discord {
} }
// FetchAuthUser returns an AuthUser instance from Discord's user api. // FetchAuthUser returns an AuthUser instance from Discord's user api.
//
// API reference: https://discord.com/developers/docs/resources/user#user-object
func (p *Discord) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { func (p *Discord) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// https://discord.com/developers/docs/resources/user#user-object data, err := p.FetchRawUserData(token)
rawData := struct { if err != nil {
return nil, err
}
rawUser := map[string]any{}
if err := json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}
extracted := struct {
Id string `json:"id"` Id string `json:"id"`
Username string `json:"username"` Username string `json:"username"`
Discriminator string `json:"discriminator"` Discriminator string `json:"discriminator"`
Email string `json:"email"` Email string `json:"email"`
Verified bool `json:"verified"`
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
}{} }{}
if err := json.Unmarshal(data, &extracted); err != nil {
if err := p.FetchRawUserData(token, &rawData); err != nil {
return nil, err return nil, err
} }
// Build a full avatar URL using the avatar hash provided in the API response // Build a full avatar URL using the avatar hash provided in the API response
// https://discord.com/developers/docs/reference#image-formatting // https://discord.com/developers/docs/reference#image-formatting
avatarUrl := fmt.Sprintf("https://cdn.discordapp.com/avatars/%s/%s.png", rawData.Id, rawData.Avatar) avatarUrl := fmt.Sprintf("https://cdn.discordapp.com/avatars/%s/%s.png", extracted.Id, extracted.Avatar)
// Concatenate the user's username and discriminator into a single username string // Concatenate the user's username and discriminator into a single username string
username := fmt.Sprintf("%s#%s", rawData.Username, rawData.Discriminator) username := fmt.Sprintf("%s#%s", extracted.Username, extracted.Discriminator)
user := &AuthUser{ user := &AuthUser{
Id: rawData.Id, Id: extracted.Id,
Name: username, Name: username,
Username: rawData.Username, Username: extracted.Username,
Email: rawData.Email, AvatarUrl: avatarUrl,
AvatarUrl: avatarUrl, RawUser: rawUser,
AccessToken: token.AccessToken,
}
if extracted.Verified {
user.Email = extracted.Email
} }
return user, nil return user, nil

View File

@ -1,6 +1,8 @@
package auth package auth
import ( import (
"encoding/json"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/facebook" "golang.org/x/oauth2/facebook"
) )
@ -26,9 +28,20 @@ func NewFacebookProvider() *Facebook {
} }
// FetchAuthUser returns an AuthUser instance based on the Facebook's user api. // FetchAuthUser returns an AuthUser instance based on the Facebook's user api.
//
// API reference: https://developers.facebook.com/docs/graph-api/reference/user/
func (p *Facebook) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { func (p *Facebook) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// https://developers.facebook.com/docs/graph-api/reference/user/ data, err := p.FetchRawUserData(token)
rawData := struct { if err != nil {
return nil, err
}
rawUser := map[string]any{}
if err := json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}
extracted := struct {
Id string Id string
Name string Name string
Email string Email string
@ -36,16 +49,17 @@ func (p *Facebook) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
Data struct{ Url string } Data struct{ Url string }
} }
}{} }{}
if err := json.Unmarshal(data, &extracted); err != nil {
if err := p.FetchRawUserData(token, &rawData); err != nil {
return nil, err return nil, err
} }
user := &AuthUser{ user := &AuthUser{
Id: rawData.Id, Id: extracted.Id,
Name: rawData.Name, Name: extracted.Name,
Email: rawData.Email, Email: extracted.Email,
AvatarUrl: rawData.Picture.Data.Url, AvatarUrl: extracted.Picture.Data.Url,
RawUser: rawUser,
AccessToken: token.AccessToken,
} }
return user, nil return user, nil

View File

@ -30,26 +30,38 @@ func NewGithubProvider() *Github {
} }
// FetchAuthUser returns an AuthUser instance based the Github's user api. // FetchAuthUser returns an AuthUser instance based the Github's user api.
//
// API reference: https://docs.github.com/en/rest/reference/users#get-the-authenticated-user
func (p *Github) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { func (p *Github) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// https://docs.github.com/en/rest/reference/users#get-the-authenticated-user data, err := p.FetchRawUserData(token)
rawData := struct { if err != nil {
return nil, err
}
rawUser := map[string]any{}
if err := json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}
extracted := struct {
Login string `json:"login"` Login string `json:"login"`
Id int `json:"id"` Id int `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Email string `json:"email"` Email string `json:"email"`
AvatarUrl string `json:"avatar_url"` AvatarUrl string `json:"avatar_url"`
}{} }{}
if err := json.Unmarshal(data, &extracted); err != nil {
if err := p.FetchRawUserData(token, &rawData); err != nil {
return nil, err return nil, err
} }
user := &AuthUser{ user := &AuthUser{
Id: strconv.Itoa(rawData.Id), Id: strconv.Itoa(extracted.Id),
Name: rawData.Name, Name: extracted.Name,
Username: rawData.Login, Username: extracted.Login,
Email: rawData.Email, Email: extracted.Email,
AvatarUrl: rawData.AvatarUrl, AvatarUrl: extracted.AvatarUrl,
RawUser: rawUser,
AccessToken: token.AccessToken,
} }
// in case user set "Keep my email address private", // in case user set "Keep my email address private",

View File

@ -1,6 +1,7 @@
package auth package auth
import ( import (
"encoding/json"
"strconv" "strconv"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -27,26 +28,38 @@ func NewGitlabProvider() *Gitlab {
} }
// FetchAuthUser returns an AuthUser instance based the Gitlab's user api. // FetchAuthUser returns an AuthUser instance based the Gitlab's user api.
//
// API reference: https://docs.gitlab.com/ee/api/users.html#for-admin
func (p *Gitlab) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { func (p *Gitlab) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// https://docs.gitlab.com/ee/api/users.html#for-admin data, err := p.FetchRawUserData(token)
rawData := struct { if err != nil {
return nil, err
}
rawUser := map[string]any{}
if err := json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}
extracted := struct {
Id int `json:"id"` Id int `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Username string `json:"username"` Username string `json:"username"`
Email string `json:"email"` Email string `json:"email"`
AvatarUrl string `json:"avatar_url"` AvatarUrl string `json:"avatar_url"`
}{} }{}
if err := json.Unmarshal(data, &extracted); err != nil {
if err := p.FetchRawUserData(token, &rawData); err != nil {
return nil, err return nil, err
} }
user := &AuthUser{ user := &AuthUser{
Id: strconv.Itoa(rawData.Id), Id: strconv.Itoa(extracted.Id),
Name: rawData.Name, Name: extracted.Name,
Username: rawData.Username, Username: extracted.Username,
Email: rawData.Email, Email: extracted.Email,
AvatarUrl: rawData.AvatarUrl, AvatarUrl: extracted.AvatarUrl,
RawUser: rawUser,
AccessToken: token.AccessToken,
} }
return user, nil return user, nil

View File

@ -1,6 +1,8 @@
package auth package auth
import ( import (
"encoding/json"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -29,22 +31,33 @@ func NewGoogleProvider() *Google {
// FetchAuthUser returns an AuthUser instance based the Google's user api. // FetchAuthUser returns an AuthUser instance based the Google's user api.
func (p *Google) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { func (p *Google) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
rawData := struct { data, err := p.FetchRawUserData(token)
Id string `json:"id"` if err != nil {
Name string `json:"name"` return nil, err
Email string `json:"email"` }
Picture string `json:"picture"`
}{}
if err := p.FetchRawUserData(token, &rawData); err != nil { rawUser := map[string]any{}
if err := json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}
extracted := struct {
Id string
Name string
Email string
Picture string
}{}
if err := json.Unmarshal(data, &extracted); err != nil {
return nil, err return nil, err
} }
user := &AuthUser{ user := &AuthUser{
Id: rawData.Id, Id: extracted.Id,
Name: rawData.Name, Name: extracted.Name,
Email: rawData.Email, Email: extracted.Email,
AvatarUrl: rawData.Picture, AvatarUrl: extracted.Picture,
RawUser: rawUser,
AccessToken: token.AccessToken,
} }
return user, nil return user, nil

View File

@ -1,6 +1,7 @@
package auth package auth
import ( import (
"encoding/json"
"strconv" "strconv"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -28,9 +29,20 @@ func NewKakaoProvider() *Kakao {
} }
// FetchAuthUser returns an AuthUser instance based on the Kakao's user api. // FetchAuthUser returns an AuthUser instance based on the Kakao's user api.
//
// API reference: https://developers.kakao.com/docs/latest/en/kakaologin/rest-api#req-user-info-response
func (p *Kakao) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { func (p *Kakao) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// https://developers.kakao.com/docs/latest/en/kakaologin/rest-api#req-user-info-response data, err := p.FetchRawUserData(token)
rawData := struct { if err != nil {
return nil, err
}
rawUser := map[string]any{}
if err := json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}
extracted := struct {
Id int `json:"id"` Id int `json:"id"`
Profile struct { Profile struct {
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
@ -42,18 +54,19 @@ func (p *Kakao) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
IsEmailValid bool `json:"is_email_valid"` IsEmailValid bool `json:"is_email_valid"`
} `json:"kakao_account"` } `json:"kakao_account"`
}{} }{}
if err := json.Unmarshal(data, &extracted); err != nil {
if err := p.FetchRawUserData(token, &rawData); err != nil {
return nil, err return nil, err
} }
user := &AuthUser{ user := &AuthUser{
Id: strconv.Itoa(rawData.Id), Id: strconv.Itoa(extracted.Id),
Username: rawData.Profile.Nickname, Username: extracted.Profile.Nickname,
AvatarUrl: rawData.Profile.ImageUrl, AvatarUrl: extracted.Profile.ImageUrl,
RawUser: rawUser,
AccessToken: token.AccessToken,
} }
if rawData.KakaoAccount.IsEmailValid && rawData.KakaoAccount.IsEmailVerified { if extracted.KakaoAccount.IsEmailValid && extracted.KakaoAccount.IsEmailVerified {
user.Email = rawData.KakaoAccount.Email user.Email = extracted.KakaoAccount.Email
} }
return user, nil return user, nil

View File

@ -1,6 +1,8 @@
package auth package auth
import ( import (
"encoding/json"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/microsoft" "golang.org/x/oauth2/microsoft"
) )
@ -27,23 +29,35 @@ func NewMicrosoftProvider() *Microsoft {
} }
// FetchAuthUser returns an AuthUser instance based on the Microsoft's user api. // FetchAuthUser returns an AuthUser instance based on the Microsoft's user api.
//
// API reference: https://learn.microsoft.com/en-us/azure/active-directory/develop/userinfo
// Graph explorer: https://developer.microsoft.com/en-us/graph/graph-explorer
func (p *Microsoft) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { func (p *Microsoft) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// https://learn.microsoft.com/en-us/azure/active-directory/develop/userinfo data, err := p.FetchRawUserData(token)
// explore graph: https://developer.microsoft.com/en-us/graph/graph-explorer if err != nil {
rawData := struct { return nil, err
}
rawUser := map[string]any{}
if err := json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}
extracted := struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"displayName"` Name string `json:"displayName"`
Email string `json:"mail"` Email string `json:"mail"`
}{} }{}
if err := json.Unmarshal(data, &extracted); err != nil {
if err := p.FetchRawUserData(token, &rawData); err != nil {
return nil, err return nil, err
} }
user := &AuthUser{ user := &AuthUser{
Id: rawData.Id, Id: extracted.Id,
Name: rawData.Name, Name: extracted.Name,
Email: rawData.Email, Email: extracted.Email,
RawUser: rawUser,
AccessToken: token.AccessToken,
} }
return user, nil return user, nil

View File

@ -1,6 +1,8 @@
package auth package auth
import ( import (
"encoding/json"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/spotify" "golang.org/x/oauth2/spotify"
) )
@ -30,9 +32,20 @@ func NewSpotifyProvider() *Spotify {
} }
// FetchAuthUser returns an AuthUser instance based on the Spotify's user api. // FetchAuthUser returns an AuthUser instance based on the Spotify's user api.
//
// API reference: https://developer.spotify.com/documentation/web-api/reference/#/operations/get-current-users-profile
func (p *Spotify) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { func (p *Spotify) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// https://developer.spotify.com/documentation/web-api/reference/#/operations/get-current-users-profile data, err := p.FetchRawUserData(token)
rawData := struct { if err != nil {
return nil, err
}
rawUser := map[string]any{}
if err := json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}
extracted := struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"display_name"` Name string `json:"display_name"`
Images []struct { Images []struct {
@ -43,17 +56,18 @@ func (p *Spotify) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// that it actually belongs to the user // that it actually belongs to the user
// Email string `json:"email"` // Email string `json:"email"`
}{} }{}
if err := json.Unmarshal(data, &extracted); err != nil {
if err := p.FetchRawUserData(token, &rawData); err != nil {
return nil, err return nil, err
} }
user := &AuthUser{ user := &AuthUser{
Id: rawData.Id, Id: extracted.Id,
Name: rawData.Name, Name: extracted.Name,
RawUser: rawUser,
AccessToken: token.AccessToken,
} }
if len(rawData.Images) > 0 { if len(extracted.Images) > 0 {
user.AvatarUrl = rawData.Images[0].Url user.AvatarUrl = extracted.Images[0].Url
} }
return user, nil return user, nil

View File

@ -1,6 +1,7 @@
package auth package auth
import ( import (
"encoding/json"
"errors" "errors"
"net/http" "net/http"
@ -29,9 +30,20 @@ func NewTwitchProvider() *Twitch {
} }
// FetchAuthUser returns an AuthUser instance based the Twitch's user api. // FetchAuthUser returns an AuthUser instance based the Twitch's user api.
//
// API reference: https://dev.twitch.tv/docs/api/reference#get-users
func (p *Twitch) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { func (p *Twitch) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// https://dev.twitch.tv/docs/api/reference#get-users data, err := p.FetchRawUserData(token)
rawData := struct { if err != nil {
return nil, err
}
rawUser := map[string]any{}
if err := json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}
extracted := struct {
Data []struct { Data []struct {
Id string `json:"id"` Id string `json:"id"`
Login string `json:"login"` Login string `json:"login"`
@ -40,21 +52,22 @@ func (p *Twitch) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
ProfileImageUrl string `json:"profile_image_url"` ProfileImageUrl string `json:"profile_image_url"`
} `json:"data"` } `json:"data"`
}{} }{}
if err := json.Unmarshal(data, &extracted); err != nil {
if err := p.FetchRawUserData(token, &rawData); err != nil {
return nil, err return nil, err
} }
if len(rawData.Data) == 0 { if len(extracted.Data) == 0 {
return nil, errors.New("Failed to fetch AuthUser data") return nil, errors.New("Failed to fetch AuthUser data")
} }
user := &AuthUser{ user := &AuthUser{
Id: rawData.Data[0].Id, Id: extracted.Data[0].Id,
Name: rawData.Data[0].DisplayName, Name: extracted.Data[0].DisplayName,
Username: rawData.Data[0].Login, Username: extracted.Data[0].Login,
Email: rawData.Data[0].Email, Email: extracted.Data[0].Email,
AvatarUrl: rawData.Data[0].ProfileImageUrl, AvatarUrl: extracted.Data[0].ProfileImageUrl,
RawUser: rawUser,
AccessToken: token.AccessToken,
} }
return user, nil return user, nil
@ -63,13 +76,13 @@ func (p *Twitch) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// FetchRawUserData implements Provider.FetchRawUserData interface. // FetchRawUserData implements Provider.FetchRawUserData interface.
// //
// This differ from baseProvider because Twitch requires the `Client-Id` header. // This differ from baseProvider because Twitch requires the `Client-Id` header.
func (p *Twitch) FetchRawUserData(token *oauth2.Token, result any) error { func (p *Twitch) FetchRawUserData(token *oauth2.Token) ([]byte, error) {
req, err := http.NewRequest("GET", p.userApiUrl, nil) req, err := http.NewRequest("GET", p.userApiUrl, nil)
if err != nil { if err != nil {
return err return nil, err
} }
req.Header.Set("Client-Id", p.clientId) req.Header.Set("Client-Id", p.clientId)
return p.sendRawUserDataRequest(req, token, result) return p.sendRawUserDataRequest(req, token)
} }

View File

@ -1,6 +1,8 @@
package auth package auth
import ( import (
"encoding/json"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -31,9 +33,20 @@ func NewTwitterProvider() *Twitter {
} }
// FetchAuthUser returns an AuthUser instance based on the Twitter's user api. // FetchAuthUser returns an AuthUser instance based on the Twitter's user api.
//
// API reference: https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me
func (p *Twitter) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) { func (p *Twitter) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me data, err := p.FetchRawUserData(token)
rawData := struct { if err != nil {
return nil, err
}
rawUser := map[string]any{}
if err := json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}
extracted := struct {
Data struct { Data struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@ -42,20 +55,20 @@ func (p *Twitter) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
// NB! At the time of writing, Twitter OAuth2 doesn't support returning the user email address // NB! At the time of writing, Twitter OAuth2 doesn't support returning the user email address
// (see https://twittercommunity.com/t/which-api-to-get-user-after-oauth2-authorization/162417/33) // (see https://twittercommunity.com/t/which-api-to-get-user-after-oauth2-authorization/162417/33)
Email string `json:"email"` // Email string `json:"email"`
} `json:"data"` } `json:"data"`
}{} }{}
if err := json.Unmarshal(data, &extracted); err != nil {
if err := p.FetchRawUserData(token, &rawData); err != nil {
return nil, err return nil, err
} }
user := &AuthUser{ user := &AuthUser{
Id: rawData.Data.Id, Id: extracted.Data.Id,
Name: rawData.Data.Name, Name: extracted.Data.Name,
Username: rawData.Data.Username, Username: extracted.Data.Username,
Email: rawData.Data.Email, AvatarUrl: extracted.Data.ProfileImageUrl,
AvatarUrl: rawData.Data.ProfileImageUrl, RawUser: rawUser,
AccessToken: token.AccessToken,
} }
return user, nil return user, nil