gomusic/auth.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()
}
}