1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
package oauth
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"github.com/anacrolix/missinggo/patreon"
)
func SimpleParser(r *http.Response) (UserProfile, error) {
var sup simpleUserProfile
err := json.NewDecoder(r.Body).Decode(&sup)
return sup, err
}
type Provider struct {
Client *Client
Endpoint *Endpoint
}
type Wrapper struct {
Scope string
Provider Provider
ProfileParser func(*http.Response) (UserProfile, error)
}
func (me Wrapper) GetAuthURL(redirectURI, state string) string {
return me.Provider.GetAuthURL(redirectURI, state, me.Scope)
}
func (me Wrapper) FetchUser(accessToken string) (up UserProfile, err error) {
resp, err := me.Provider.FetchUser(accessToken)
if err != nil {
return
}
defer resp.Body.Close()
return me.ProfileParser(resp)
}
type Client struct {
ID string
Secret string
}
func (me *Provider) GetAuthURL(redirectURI, state, scope string) string {
params := []string{
"client_id", me.Client.ID,
"response_type", "code",
"redirect_uri", redirectURI,
"state", state,
// This will ask again for the given scopes if they're not provided.
"auth_type", "rerequest",
}
if scope != "" {
params = append(params, "scope", scope)
}
return renderEndpointURL(me.Endpoint.AuthURL, params...)
}
func (me *Provider) ExchangeCode(code string, redirectURI string) (accessToken string, err error) {
v := url.Values{
"client_id": {me.Client.ID},
"redirect_uri": {redirectURI},
"client_secret": {me.Client.Secret},
"code": {code},
"grant_type": {"authorization_code"},
}
resp, err := http.Post(me.Endpoint.TokenURL, "application/x-www-form-urlencoded", bytes.NewBufferString(v.Encode()))
if err != nil {
return
}
var buf bytes.Buffer
io.Copy(&buf, resp.Body)
resp.Body.Close()
var msg map[string]interface{}
err = json.NewDecoder(&buf).Decode(&msg)
if err != nil {
return
}
defer func() {
r := recover()
if r == nil {
return
}
err = fmt.Errorf("bad access_token field in %q: %s", msg, r)
}()
accessToken = msg["access_token"].(string)
return
}
type simpleUserProfile struct {
Id string `json:"id"`
EmailField string `json:"email"`
}
var _ UserProfile = simpleUserProfile{}
func (me simpleUserProfile) IsEmailVerified() bool {
return true
}
func (me simpleUserProfile) Email() string {
return me.EmailField
}
type UserProfile interface {
IsEmailVerified() bool
Email() string
}
// TODO: Allow fields to be specified.
func (me *Provider) FetchUser(accessToken string) (*http.Response, error) {
return http.Get(renderEndpointURL(
me.Endpoint.ProfileURL,
"fields", "email",
"access_token", accessToken,
))
}
type PatreonUserProfile struct {
Data patreon.ApiUser `json:"data"`
}
var _ UserProfile = PatreonUserProfile{}
func (me PatreonUserProfile) IsEmailVerified() bool {
return me.Data.Attributes.IsEmailVerified
}
func (me PatreonUserProfile) Email() string {
return me.Data.Attributes.Email
}
func renderEndpointURL(endpoint string, params ...string) string {
u, err := url.Parse(endpoint)
if err != nil {
panic(err)
}
v := make(url.Values, len(params)/2)
for i := 0; i < len(params); i += 2 {
v.Set(params[i], params[i+1])
}
u.RawQuery = v.Encode()
return u.String()
}
|