This repository has been archived by the owner on Feb 14, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 67
/
oauth.go
192 lines (155 loc) · 5.31 KB
/
oauth.go
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package strava
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
// An OAuthAuthenticator holds state about how OAuth requests should be authenticated.
type OAuthAuthenticator struct {
CallbackURL string // used to help generate the AuthorizationURL
// The RequestClientGenerator builds the http.Client that will be used
// to complete the token exchange. If nil, http.DefaultClient will be used.
// On Google's App Engine http.DefaultClient is not available and this generator
// can be used to create a client using the incoming request, for Example:
// func(r *http.Request) { return urlfetch.Client(appengine.NewContext(r)) }
RequestClientGenerator func(r *http.Request) *http.Client
}
// Permission represents the access of an access_token.
// The permission type is requested during the token exchange.
type Permission string
// Permissions defines the available permissions
var Permissions = struct {
Public Permission
ViewPrivate Permission
Write Permission
WriteViewPrivate Permission
}{
"public",
"view_private",
"write",
"write,view_private",
}
// AuthorizationResponse is returned as a result of the token exchange
type AuthorizationResponse struct {
AccessToken string `json:"access_token"`
State string `json:"State"`
Athlete AthleteDetailed `json:"athlete"`
}
// CallbackPath returns the path portion of the CallbackURL.
// Useful when setting a http path handler, for example:
// http.HandleFunc(stravaOAuth.CallbackURL(), stravaOAuth.HandlerFunc(successCallback, failureCallback))
func (auth OAuthAuthenticator) CallbackPath() (string, error) {
if auth.CallbackURL == "" {
return "", errors.New("callbackURL is empty")
}
url, err := url.Parse(auth.CallbackURL)
if err != nil {
return "", err
}
return url.Path, nil
}
// Authorize performs the second part of the OAuth exchange. The client has already been redirected to the
// Strava authorization page, has granted authorization to the application and has been redirected back to the
// defined URL. The code param was returned as a query string param in to the redirect_url.
func (auth OAuthAuthenticator) Authorize(code string, client *http.Client) (*AuthorizationResponse, error) {
// make sure a code was passed
if code == "" {
return nil, OAuthInvalidCodeErr
}
// if a client wasn't passed use the default client
if client == nil {
client = http.DefaultClient
}
resp, err := client.PostForm(basePath+"/oauth/token",
url.Values{"client_id": {fmt.Sprintf("%d", ClientId)}, "client_secret": {ClientSecret}, "code": {code}})
// this was a poor request, maybe strava servers down?
if err != nil {
return nil, err
}
defer resp.Body.Close()
// check status code, could be 500, or most likely the client_secret is incorrect
if resp.StatusCode/100 == 5 {
return nil, OAuthServerErr
}
if resp.StatusCode/100 != 2 {
var response Error
contents, _ := ioutil.ReadAll(resp.Body)
json.Unmarshal(contents, &response)
if len(response.Errors) == 0 {
return nil, OAuthServerErr
}
if response.Errors[0].Resource == "Application" {
return nil, OAuthInvalidCredentialsErr
}
if response.Errors[0].Resource == "RequestToken" {
return nil, OAuthInvalidCodeErr
}
return nil, &response
}
var response AuthorizationResponse
contents, _ := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(contents, &response)
if err != nil {
return nil, err
}
return &response, nil
}
// HandlerFunc builds a http.HandlerFunc that will complete the token exchange
// after a user authorizes an application on strava.com.
// This method handles the exchange and calls success or failure after it completes.
func (auth OAuthAuthenticator) HandlerFunc(
success func(auth *AuthorizationResponse, w http.ResponseWriter, r *http.Request),
failure func(err error, w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// user denied authorization
if r.FormValue("error") == "access_denied" {
failure(OAuthAuthorizationDeniedErr, w, r)
return
}
// use the client generator if provided.
client := http.DefaultClient
if auth.RequestClientGenerator != nil {
client = auth.RequestClientGenerator(r)
}
resp, err := auth.Authorize(r.FormValue("code"), client)
if err != nil {
failure(err, w, r)
return
}
resp.State = r.FormValue("state")
success(resp, w, r)
}
}
// AuthorizationURL constructs the url a user should use to authorize this specific application.
func (auth OAuthAuthenticator) AuthorizationURL(state string, scope Permission, force bool) string {
path := fmt.Sprintf("%s/oauth/authorize?client_id=%d&response_type=code&redirect_uri=%s&scope=%v", basePath, ClientId, auth.CallbackURL, scope)
if state != "" {
path += "&state=" + state
}
if force {
path += "&approval_prompt=force"
}
return path
}
/*********************************************************/
type OAuthService struct {
client *Client
}
func NewOAuthService(client *Client) *OAuthService {
return &OAuthService{client}
}
type OAuthDeauthorizeCall struct {
service *OAuthService
}
func (s *OAuthService) Deauthorize() *OAuthDeauthorizeCall {
return &OAuthDeauthorizeCall{
service: s,
}
}
func (c *OAuthDeauthorizeCall) Do() error {
_, err := c.service.client.run("POST", "/oauth/deauthorize", nil)
return err
}