-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathauth.go
121 lines (105 loc) · 3.38 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
package main
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"math/rand"
"net/http"
"net/url"
"os"
"github.com/pkg/browser"
)
type AuthResponse struct {
AccessToken string `json:"access_token"`
}
var (
clientID = os.Getenv("SPOTIFY_CLIENT_ID")
clientSecret = os.Getenv("SPOTIFY_CLIENT_SECRET")
authHeader = fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(clientID+":"+clientSecret)))
)
/*
// If we would use only such Spotify APIs that do not require user permission,
// client token would be sufficient
func fetchClientToken() string {
if clientID == "" && clientSecret == "" {
panic(fmt.Errorf("spotify client ID and secret missing!"))
}
data, err := doPostRequest("https://accounts.spotify.com/api/token", url.Values{"grant_type": {"client_credentials"}}, authHeader)
if err == nil {
response := AuthResponse{}
if err = json.Unmarshal(data, &response); err == nil {
return response.AccessToken
}
}
panic(fmt.Errorf("Unable to acquire Spotify token!"))
}
*/
func fetchUserToken() string {
const (
redirectURL = "http://localhost:4321"
spotifyLoginURL = "https://accounts.spotify.com/authorize?client_id=%s&response_type=code&redirect_uri=%s&scope=%s&state=%s"
)
if clientID == "" && clientSecret == "" {
panic(fmt.Errorf("spotify client ID and secret missing"))
}
// authorization code - received in callback
code := ""
// local state parameter for cross-site request forgery prevention
state := fmt.Sprint(rand.Int())
// scope of the access: we want to modify user's playlists
scope := "playlist-modify-public&playlist-modify-private"
// loginURL
path := fmt.Sprintf(spotifyLoginURL, clientID, redirectURL, scope, state)
// channel for signaling that server shutdown can be done
messages := make(chan bool)
// callback handler, redirect from login is handled here
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// check that the state parameter matches
if s, ok := r.URL.Query()["state"]; ok && s[0] == state {
// code is received as query parameter
if codes, ok := r.URL.Query()["code"]; ok && len(codes) == 1 {
// save code and signal shutdown
code = codes[0]
messages <- true
}
}
// redirect user's browser to spotify home page
http.Redirect(w, r, "https://www.spotify.com/", http.StatusSeeOther)
})
// open user's browser to login page
if err := browser.OpenURL(path); err != nil {
panic(fmt.Errorf("failed to open browser for authentication %s", err.Error()))
}
server := &http.Server{Addr: ":4321"}
// go routine for shutting down the server
go func() {
okToClose := <-messages
if okToClose {
if err := server.Shutdown(context.Background()); err != nil {
log.Println("Failed to shutdown server", err)
}
}
}()
// start listening for callback - we don't continue until server is shut down
log.Println(server.ListenAndServe())
// authentication complete - fetch the access token
params := url.Values{}
params.Add("grant_type", "authorization_code")
params.Add("code", code)
params.Add("redirect_uri", redirectURL)
data, err := doPostRequest(
"https://accounts.spotify.com/api/token",
params,
authHeader,
)
if err == nil {
response := AuthResponse{}
if err = json.Unmarshal(data, &response); err == nil {
// happy end: token parsed successfully
return response.AccessToken
}
}
panic(fmt.Errorf("unable to acquire Spotify user token"))
}