1// Package facebook implements the OAuth2 protocol for authenticating users through Facebook.
2// This package can be used as a reference implementation of an OAuth2 provider for Goth.
3package facebook
4
5import (
6	"bytes"
7	"crypto/hmac"
8	"crypto/sha256"
9	"encoding/hex"
10	"encoding/json"
11	"errors"
12	"fmt"
13	"io"
14	"io/ioutil"
15	"net/http"
16	"net/url"
17	"strings"
18
19	"github.com/markbates/goth"
20	"golang.org/x/oauth2"
21)
22
23const (
24	authURL         string = "https://www.facebook.com/dialog/oauth"
25	tokenURL        string = "https://graph.facebook.com/oauth/access_token"
26	endpointProfile string = "https://graph.facebook.com/me?fields="
27)
28
29// New creates a new Facebook provider, and sets up important connection details.
30// You should always call `facebook.New` to get a new Provider. Never try to create
31// one manually.
32func New(clientKey, secret, callbackURL string, scopes ...string) *Provider {
33	p := &Provider{
34		ClientKey:    clientKey,
35		Secret:       secret,
36		CallbackURL:  callbackURL,
37		providerName: "facebook",
38	}
39	p.config = newConfig(p, scopes)
40	p.Fields = "email,first_name,last_name,link,about,id,name,picture,location"
41	return p
42}
43
44// Provider is the implementation of `goth.Provider` for accessing Facebook.
45type Provider struct {
46	ClientKey    string
47	Secret       string
48	CallbackURL  string
49	HTTPClient   *http.Client
50	Fields       string
51	config       *oauth2.Config
52	providerName string
53}
54
55// Name is the name used to retrieve this provider later.
56func (p *Provider) Name() string {
57	return p.providerName
58}
59
60// SetName is to update the name of the provider (needed in case of multiple providers of 1 type)
61func (p *Provider) SetName(name string) {
62	p.providerName = name
63}
64
65// SetCustomFields sets the fields used to return information
66// for a user.
67//
68// A list of available field values can be found at
69// https://developers.facebook.com/docs/graph-api/reference/user
70func (p *Provider) SetCustomFields(fields []string) *Provider {
71	p.Fields = strings.Join(fields, ",")
72	return p
73}
74
75func (p *Provider) Client() *http.Client {
76	return goth.HTTPClientWithFallBack(p.HTTPClient)
77}
78
79// Debug is a no-op for the facebook package.
80func (p *Provider) Debug(debug bool) {}
81
82// BeginAuth asks Facebook for an authentication end-point.
83func (p *Provider) BeginAuth(state string) (goth.Session, error) {
84	authUrl := p.config.AuthCodeURL(state)
85	session := &Session{
86		AuthURL: authUrl,
87	}
88	return session, nil
89}
90
91// FetchUser will go to Facebook and access basic information about the user.
92func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
93	sess := session.(*Session)
94	user := goth.User{
95		AccessToken: sess.AccessToken,
96		Provider:    p.Name(),
97		ExpiresAt:   sess.ExpiresAt,
98	}
99
100	if user.AccessToken == "" {
101		// data is not yet retrieved since accessToken is still empty
102		return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName)
103	}
104
105	// always add appsecretProof to make calls more protected
106	// https://github.com/markbates/goth/issues/96
107	// https://developers.facebook.com/docs/graph-api/securing-requests
108	hash := hmac.New(sha256.New, []byte(p.Secret))
109	hash.Write([]byte(sess.AccessToken))
110	appsecretProof := hex.EncodeToString(hash.Sum(nil))
111
112	reqUrl := fmt.Sprint(
113		endpointProfile,
114		p.Fields,
115		"&access_token=",
116		url.QueryEscape(sess.AccessToken),
117		"&appsecret_proof=",
118		appsecretProof,
119	)
120	response, err := p.Client().Get(reqUrl)
121	if err != nil {
122		return user, err
123	}
124	defer response.Body.Close()
125
126	if response.StatusCode != http.StatusOK {
127		return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, response.StatusCode)
128	}
129
130	bits, err := ioutil.ReadAll(response.Body)
131	if err != nil {
132		return user, err
133	}
134
135	err = json.NewDecoder(bytes.NewReader(bits)).Decode(&user.RawData)
136	if err != nil {
137		return user, err
138	}
139
140	err = userFromReader(bytes.NewReader(bits), &user)
141	return user, err
142}
143
144func userFromReader(reader io.Reader, user *goth.User) error {
145	u := struct {
146		ID        string `json:"id"`
147		Email     string `json:"email"`
148		About     string `json:"about"`
149		Name      string `json:"name"`
150		FirstName string `json:"first_name"`
151		LastName  string `json:"last_name"`
152		Link      string `json:"link"`
153		Picture   struct {
154			Data struct {
155				URL string `json:"url"`
156			} `json:"data"`
157		} `json:"picture"`
158		Location struct {
159			Name string `json:"name"`
160		} `json:"location"`
161	}{}
162
163	err := json.NewDecoder(reader).Decode(&u)
164	if err != nil {
165		return err
166	}
167
168	user.Name = u.Name
169	user.FirstName = u.FirstName
170	user.LastName = u.LastName
171	user.NickName = u.Name
172	user.Email = u.Email
173	user.Description = u.About
174	user.AvatarURL = u.Picture.Data.URL
175	user.UserID = u.ID
176	user.Location = u.Location.Name
177
178	return err
179}
180
181func newConfig(provider *Provider, scopes []string) *oauth2.Config {
182	c := &oauth2.Config{
183		ClientID:     provider.ClientKey,
184		ClientSecret: provider.Secret,
185		RedirectURL:  provider.CallbackURL,
186		Endpoint: oauth2.Endpoint{
187			AuthURL:  authURL,
188			TokenURL: tokenURL,
189		},
190		Scopes: []string{
191			"email",
192		},
193	}
194
195	defaultScopes := map[string]struct{}{
196		"email": {},
197	}
198
199	for _, scope := range scopes {
200		if _, exists := defaultScopes[scope]; !exists {
201			c.Scopes = append(c.Scopes, scope)
202		}
203	}
204
205	return c
206}
207
208//RefreshToken refresh token is not provided by facebook
209func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
210	return nil, errors.New("Refresh token is not provided by facebook")
211}
212
213//RefreshTokenAvailable refresh token is not provided by facebook
214func (p *Provider) RefreshTokenAvailable() bool {
215	return false
216}
217