fix: facebook auth
This commit is contained in:
+56
-7
@@ -17,7 +17,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
@@ -150,6 +152,11 @@ type facebook struct{ id, secret string }
|
||||
|
||||
func (f *facebook) Name() string { return "facebook" }
|
||||
|
||||
// Bump in lockstep with whatever version your Meta app is configured for
|
||||
// (Settings → Basic → API Version). Stay on a single supported version so
|
||||
// /me responses match the documented schema.
|
||||
const facebookGraphVersion = "v25.0"
|
||||
|
||||
func (f *facebook) Config(redirectURL string) *oauth2.Config {
|
||||
return &oauth2.Config{
|
||||
ClientID: f.id,
|
||||
@@ -157,8 +164,8 @@ func (f *facebook) Config(redirectURL string) *oauth2.Config {
|
||||
RedirectURL: redirectURL,
|
||||
Scopes: []string{"email", "public_profile"},
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://www.facebook.com/v18.0/dialog/oauth",
|
||||
TokenURL: "https://graph.facebook.com/v18.0/oauth/access_token",
|
||||
AuthURL: "https://www.facebook.com/" + facebookGraphVersion + "/dialog/oauth",
|
||||
TokenURL: "https://graph.facebook.com/" + facebookGraphVersion + "/oauth/access_token",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -187,8 +194,18 @@ type facebookMe struct {
|
||||
}
|
||||
|
||||
func (f *facebook) FetchProfile(ctx context.Context, tok *oauth2.Token) (*Profile, error) {
|
||||
const url = "https://graph.facebook.com/v18.0/me?fields=id,email,name,picture.type(large)"
|
||||
body, err := httpGet(ctx, tok, url)
|
||||
// Facebook Graph API: pass the token as ?access_token=... (their
|
||||
// canonical mode) rather than via Bearer header. Bearer works most
|
||||
// of the time but is documented as a secondary mode and has been
|
||||
// reported to silently drop fields in some Graph versions.
|
||||
params := url.Values{
|
||||
"fields": {"id,email,name,picture.type(large)"},
|
||||
"access_token": {tok.AccessToken},
|
||||
}
|
||||
reqURL := "https://graph.facebook.com/" + facebookGraphVersion +
|
||||
"/me?" + params.Encode()
|
||||
|
||||
body, err := plainGet(ctx, reqURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("facebook /me: %w", err)
|
||||
}
|
||||
@@ -199,6 +216,12 @@ func (f *facebook) FetchProfile(ctx context.Context, tok *oauth2.Token) (*Profil
|
||||
if u.ID == "" {
|
||||
return nil, fmt.Errorf("facebook /me: empty id")
|
||||
}
|
||||
// If email permission was granted but FB still didn't return one,
|
||||
// log the raw response so the operator can diagnose (commonly the
|
||||
// user's FB account email is unverified — Graph API omits it silently).
|
||||
if u.Email == "" {
|
||||
log.Printf("oauth/facebook: /me returned no email; raw=%s", truncate(body, 512))
|
||||
}
|
||||
return &Profile{
|
||||
Provider: "facebook",
|
||||
ProviderUserID: u.ID,
|
||||
@@ -209,9 +232,35 @@ func (f *facebook) FetchProfile(ctx context.Context, tok *oauth2.Token) (*Profil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// httpGet performs a token-authenticated GET and returns the response body.
|
||||
// The provider tokens include their auth header already (Bearer for Google,
|
||||
// query-param for Facebook), so we let oauth2.Client handle it.
|
||||
func truncate(b []byte, n int) string {
|
||||
if len(b) <= n {
|
||||
return string(b)
|
||||
}
|
||||
return string(b[:n]) + "…"
|
||||
}
|
||||
|
||||
// plainGet performs an unauthenticated GET (the caller is expected to have
|
||||
// already embedded any access token in the URL). Used by Facebook because
|
||||
// Graph API treats `?access_token=` as the canonical auth mode.
|
||||
func plainGet(ctx context.Context, url string) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
// httpGet performs a Bearer-authenticated GET — used by Google. Facebook
|
||||
// uses plainGet with the token in a query param instead.
|
||||
func httpGet(ctx context.Context, tok *oauth2.Token, url string) ([]byte, error) {
|
||||
cli := oauth2.NewClient(ctx, oauth2.StaticTokenSource(tok))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
|
||||
Reference in New Issue
Block a user