90 lines
3.2 KiB
Go
90 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/gin-gonic/gin"
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Used to unmarshal response data into type safe struct
|
|
type SpotifyToken struct {
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
}
|
|
|
|
// Makes a HTTP request to grab a short lived access token from Spotify
|
|
func getSpotifyToken(clientID string, clientSecret string) (SpotifyToken, error) {
|
|
|
|
// See spotify API docs for details on parameters/urls required here
|
|
// https://developer.spotify.com/documentation/web-api/tutorials/client-credentials-flow
|
|
spotifyAuthURL := "https://accounts.spotify.com/api/token"
|
|
reqBody := strings.NewReader("grant_type=client_credentials")
|
|
authData := []byte(fmt.Sprintf("%s:%s", clientID, clientSecret))
|
|
b64AuthData := base64.StdEncoding.EncodeToString(authData)
|
|
|
|
req, err := http.NewRequest("POST", spotifyAuthURL, reqBody)
|
|
if err != nil {
|
|
slog.Error("[GOMUSIC] Failed to build HTTP request", "Error", err)
|
|
return SpotifyToken{}, fmt.Errorf("Failed to authenticate with spotify: %w", err)
|
|
}
|
|
|
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", b64AuthData))
|
|
|
|
// Send request and grab response data
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
slog.Error("[GOMUSIC] Failed to send authentication request to spotify", "Error", err)
|
|
return SpotifyToken{}, fmt.Errorf("Failed to authenticate with spotify: %w", err)
|
|
|
|
} else if resp.StatusCode != 200 {
|
|
slog.Error("[GOMUSIC] Failed to authenticate with spotify", "Error", resp.Status)
|
|
return SpotifyToken{}, fmt.Errorf("Failed to authenticate with spotify: %s", resp.Status)
|
|
}
|
|
|
|
var spotifyToken SpotifyToken
|
|
err = json.NewDecoder(resp.Body).Decode(&spotifyToken)
|
|
if err != nil {
|
|
slog.Error("[GOMUSIC] Failed to parse spotify authentication response", "Error", err)
|
|
return SpotifyToken{}, fmt.Errorf("Failed to parse spotify JSON response: %w", err)
|
|
}
|
|
resp.Body.Close()
|
|
return spotifyToken, nil
|
|
}
|
|
|
|
// Authentication middleware to handle spotify for us
|
|
func spotifyAuth(clientID string, clientSecret string) gin.HandlerFunc {
|
|
// Setup a per-instance variable to safely store/update
|
|
// Short lived spotify access tokens during execution
|
|
spotifyToken, err := getSpotifyToken(clientID, clientSecret)
|
|
if err != nil {
|
|
slog.Error("[GOMUSIC] Failed to get spotify access token", "Error", err)
|
|
}
|
|
|
|
// Calculate the future expiry time
|
|
expDuration := time.Duration(spotifyToken.ExpiresIn) * time.Second
|
|
expireTime := time.Now().Add(expDuration)
|
|
|
|
return func(c *gin.Context) {
|
|
if time.Now().After(expireTime) {
|
|
// Token expired so we grab a new one
|
|
slog.Info("[GOMUSIC] Replacing expired Spotify access token")
|
|
spotifyToken, err = getSpotifyToken(clientID, clientSecret)
|
|
if err != nil {
|
|
slog.Error("[GOMUSIC] Failed to get new spotify access token", "Error", err)
|
|
}
|
|
expDuration := time.Duration(spotifyToken.ExpiresIn) * time.Second
|
|
expireTime = time.Now().Add(expDuration)
|
|
}
|
|
|
|
// Pass the authorization header into context
|
|
c.Set("spotifyAuthToken", fmt.Sprintf("Bearer %s", spotifyToken.AccessToken))
|
|
c.Next()
|
|
}
|
|
}
|