forked from zmb3/spotify
-
Notifications
You must be signed in to change notification settings - Fork 0
/
auth.go
180 lines (166 loc) · 6.77 KB
/
auth.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
package spotify
import (
"context"
"crypto/tls"
"errors"
"net/http"
"os"
"golang.org/x/oauth2"
)
const (
// AuthURL is the URL to Spotify Accounts Service's OAuth2 endpoint.
AuthURL = "https://accounts.spotify.com/authorize"
// TokenURL is the URL to the Spotify Accounts Service's OAuth2
// token endpoint.
TokenURL = "https://accounts.spotify.com/api/token"
)
// Scopes let you specify exactly which types of data your application wants to access.
// The set of scopes you pass in your authentication request determines what access the
// permissions the user is asked to grant.
const (
// ScopeImageUpload seeks permission to upload images to Spotify on your behalf.
ScopeImageUpload = "ugc-image-upload"
// ScopePlaylistReadPrivate seeks permission to read
// a user's private playlists.
ScopePlaylistReadPrivate = "playlist-read-private"
// ScopePlaylistModifyPublic seeks write access
// to a user's public playlists.
ScopePlaylistModifyPublic = "playlist-modify-public"
// ScopePlaylistModifyPrivate seeks write access to
// a user's private playlists.
ScopePlaylistModifyPrivate = "playlist-modify-private"
// ScopePlaylistReadCollaborative seeks permission to
// access a user's collaborative playlists.
ScopePlaylistReadCollaborative = "playlist-read-collaborative"
// ScopeUserFollowModify seeks write/delete access to
// the list of artists and other users that a user follows.
ScopeUserFollowModify = "user-follow-modify"
// ScopeUserFollowRead seeks read access to the list of
// artists and other users that a user follows.
ScopeUserFollowRead = "user-follow-read"
// ScopeUserLibraryModify seeks write/delete access to a
// user's "Your Music" library.
ScopeUserLibraryModify = "user-library-modify"
// ScopeUserLibraryRead seeks read access to a user's "Your Music" library.
ScopeUserLibraryRead = "user-library-read"
// ScopeUserReadPrivate seeks read access to a user's
// subsription details (type of user account).
ScopeUserReadPrivate = "user-read-private"
// ScopeUserReadEmail seeks read access to a user's email address.
ScopeUserReadEmail = "user-read-email"
// ScopeUserReadBirthdate seeks read access to a user's birthdate.
ScopeUserReadBirthdate = "user-read-birthdate"
// ScopeUserReadCurrentlyPlaying seeks read access to a user's currently playing track
ScopeUserReadCurrentlyPlaying = "user-read-currently-playing"
// ScopeUserReadPlaybackState seeks read access to the user's current playback state
ScopeUserReadPlaybackState = "user-read-playback-state"
// ScopeUserModifyPlaybackState seeks write access to the user's current playback state
ScopeUserModifyPlaybackState = "user-modify-playback-state"
// ScopeUserReadRecentlyPlayed allows access to a user's recently-played songs
ScopeUserReadRecentlyPlayed = "user-read-recently-played"
// ScopeUserTopRead seeks read access to a user's top tracks and artists
ScopeUserTopRead = "user-top-read"
)
// Authenticator provides convenience functions for implementing the OAuth2 flow.
// You should always use `NewAuthenticator` to make them.
//
// Example:
//
// a := spotify.NewAuthenticator(redirectURL, spotify.ScopeUserLibaryRead, spotify.ScopeUserFollowRead)
// // direct user to Spotify to log in
// http.Redirect(w, r, a.AuthURL("state-string"), http.StatusFound)
//
// // then, in redirect handler:
// token, err := a.Token(state, r)
// client := a.NewClient(token)
//
type Authenticator struct {
config *oauth2.Config
context context.Context
}
// NewAuthenticator creates an authenticator which is used to implement the
// OAuth2 authorization flow. The redirectURL must exactly match one of the
// URLs specified in your Spotify developer account.
//
// By default, NewAuthenticator pulls your client ID and secret key from the
// SPOTIFY_ID and SPOTIFY_SECRET environment variables. If you'd like to provide
// them from some other source, you can call `SetAuthInfo(id, key)` on the
// returned authenticator.
func NewAuthenticator(redirectURL string, scopes ...string) Authenticator {
cfg := &oauth2.Config{
ClientID: os.Getenv("SPOTIFY_ID"),
ClientSecret: os.Getenv("SPOTIFY_SECRET"),
RedirectURL: redirectURL,
Scopes: scopes,
Endpoint: oauth2.Endpoint{
AuthURL: AuthURL,
TokenURL: TokenURL,
},
}
// disable HTTP/2 for DefaultClient, see: https://github.com/zmb3/spotify/issues/20
tr := &http.Transport{
TLSNextProto: map[string]func(authority string, c *tls.Conn) http.RoundTripper{},
}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{Transport: tr})
return Authenticator{
config: cfg,
context: ctx,
}
}
// SetAuthInfo overwrites the client ID and secret key used by the authenticator.
// You can use this if you don't want to store this information in environment variables.
func (a *Authenticator) SetAuthInfo(clientID, secretKey string) {
a.config.ClientID = clientID
a.config.ClientSecret = secretKey
}
// AuthURL returns a URL to the the Spotify Accounts Service's OAuth2 endpoint.
//
// State is a token to protect the user from CSRF attacks. You should pass the
// same state to `Token`, where it will be validated. For more info, refer to
// http://tools.ietf.org/html/rfc6749#section-10.12.
func (a Authenticator) AuthURL(state string) string {
return a.config.AuthCodeURL(state)
}
// Token pulls an authorization code from an HTTP request and attempts to exchange
// it for an access token. The standard use case is to call Token from the handler
// that handles requests to your application's redirect URL.
func (a Authenticator) Token(state string, r *http.Request) (*oauth2.Token, error) {
values := r.URL.Query()
if e := values.Get("error"); e != "" {
return nil, errors.New("spotify: auth failed - " + e)
}
code := values.Get("code")
if code == "" {
return nil, errors.New("spotify: didn't get access code")
}
actualState := values.Get("state")
if actualState != state {
return nil, errors.New("spotify: redirect state parameter doesn't match")
}
return a.config.Exchange(a.context, code)
}
// Exchange is like Token, except it allows you to manually specify the access
// code instead of pulling it out of an HTTP request.
func (a Authenticator) Exchange(code string) (*oauth2.Token, error) {
return a.config.Exchange(a.context, code)
}
// NewClient creates a Client that will use the specified access token for its API requests.
func (a Authenticator) NewClient(token *oauth2.Token) Client {
client := a.config.Client(a.context, token)
return Client{
http: client,
baseURL: baseAddress,
}
}
// Token gets the client's current token.
func (c *Client) Token() (*oauth2.Token, error) {
transport, ok := c.http.Transport.(*oauth2.Transport)
if !ok {
return nil, errors.New("spotify: oauth2 transport type not correct")
}
t, err := transport.Source.Token()
if err != nil {
return nil, err
}
return t, nil
}