gspot/src/services/auth.go

163 lines
4.6 KiB
Go
Raw Normal View History

2024-02-18 06:57:47 +00:00
package services
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/zmb3/spotify/v2"
spotifyauth "github.com/zmb3/spotify/v2/auth"
"golang.org/x/exp/slog"
"golang.org/x/oauth2"
2024-02-19 02:51:25 +00:00
"git.asdf.cafe/abs3nt/gspot/src/config"
2024-02-18 06:57:47 +00:00
)
var (
auth *spotifyauth.Authenticator
ch = make(chan *spotify.Client)
state = "abc123"
configDir, _ = os.UserConfigDir()
)
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return fn(req)
}
2024-02-19 07:33:32 +00:00
func GetClient(conf *config.Config) (c *spotify.Client, err error) {
2024-02-18 06:57:47 +00:00
if conf.ClientId == "" || (conf.ClientSecret == "" && conf.ClientSecretCmd == "") || conf.Port == "" {
2024-02-19 07:33:32 +00:00
return nil, fmt.Errorf("INVALID CONFIG")
2024-02-18 06:57:47 +00:00
}
if conf.ClientSecretCmd != "" {
args := strings.Fields(conf.ClientSecretCmd)
cmd := args[0]
secret_command := exec.Command(cmd)
if len(args) > 1 {
secret_command.Args = args
}
secret, err := secret_command.Output()
if err != nil {
panic(err)
}
conf.ClientSecret = strings.TrimSpace(string(secret))
}
auth = spotifyauth.New(
spotifyauth.WithClientID(conf.ClientId),
spotifyauth.WithClientSecret(conf.ClientSecret),
spotifyauth.WithRedirectURL(fmt.Sprintf("http://localhost:%s/callback", conf.Port)),
spotifyauth.WithScopes(
spotifyauth.ScopeImageUpload,
spotifyauth.ScopePlaylistReadPrivate,
spotifyauth.ScopePlaylistModifyPublic,
spotifyauth.ScopePlaylistModifyPrivate,
spotifyauth.ScopePlaylistReadCollaborative,
spotifyauth.ScopeUserFollowModify,
spotifyauth.ScopeUserFollowRead,
spotifyauth.ScopeUserLibraryModify,
spotifyauth.ScopeUserLibraryRead,
spotifyauth.ScopeUserReadPrivate,
spotifyauth.ScopeUserReadEmail,
spotifyauth.ScopeUserReadCurrentlyPlaying,
spotifyauth.ScopeUserReadPlaybackState,
spotifyauth.ScopeUserModifyPlaybackState,
spotifyauth.ScopeUserReadRecentlyPlayed,
spotifyauth.ScopeUserTopRead,
spotifyauth.ScopeStreaming,
),
)
2024-02-19 02:51:25 +00:00
if _, err := os.Stat(filepath.Join(configDir, "gspot/auth.json")); err == nil {
authFilePath := filepath.Join(configDir, "gspot/auth.json")
2024-02-18 06:57:47 +00:00
authFile, err := os.Open(authFilePath)
if err != nil {
2024-02-19 07:33:32 +00:00
return nil, err
2024-02-18 06:57:47 +00:00
}
defer authFile.Close()
tok := &oauth2.Token{}
err = json.NewDecoder(authFile).Decode(tok)
if err != nil {
2024-02-19 07:33:32 +00:00
return nil, err
2024-02-18 06:57:47 +00:00
}
authCtx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
slog.Debug("ROUND_TRIPPER", "request", r.URL.Path)
return http.DefaultTransport.RoundTrip(r)
}),
})
authClient := auth.Client(authCtx, tok)
client := spotify.New(authClient)
new_token, err := client.Token()
if err != nil {
2024-02-19 07:33:32 +00:00
return nil, err
2024-02-18 06:57:47 +00:00
}
out, err := json.MarshalIndent(new_token, "", " ")
if err != nil {
2024-02-19 07:33:32 +00:00
return nil, err
2024-02-18 06:57:47 +00:00
}
err = os.WriteFile(authFilePath, out, 0o600)
if err != nil {
2024-02-19 07:33:32 +00:00
return nil, fmt.Errorf("failed to save auth")
2024-02-18 06:57:47 +00:00
}
2024-02-19 07:33:32 +00:00
return client, nil
2024-02-18 06:57:47 +00:00
}
// first start an HTTP server
http.HandleFunc("/callback", completeAuth)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
slog.Info("AUTHENTICATOR", "received request", r.URL.String())
})
server := &http.Server{
Addr: fmt.Sprintf(":%s", conf.Port),
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
_ = server.ListenAndServe()
}()
url := auth.AuthURL(state)
2024-02-18 19:34:08 +00:00
slog.Info("AUTH", "url", url)
2024-02-18 06:57:47 +00:00
cmd := exec.Command("xdg-open", url)
_ = cmd.Start()
// wait for auth to complete
client := <-ch
_ = server.Shutdown(context.Background())
// use the client to make calls that require authorization
user, err := client.CurrentUser(context.Background())
if err != nil {
2024-02-19 07:33:32 +00:00
return nil, err
2024-02-18 06:57:47 +00:00
}
2024-02-18 19:34:08 +00:00
slog.Info("AUTH", "You are logged in as:", user.ID)
2024-02-19 07:33:32 +00:00
return client, nil
2024-02-18 06:57:47 +00:00
}
func completeAuth(w http.ResponseWriter, r *http.Request) {
tok, err := auth.Token(r.Context(), state, r)
if err != nil {
http.Error(w, "Couldn't get token", http.StatusForbidden)
}
if st := r.FormValue("state"); st != state {
http.NotFound(w, r)
slog.Error("State mismatch: %s != %s\n", st, state)
os.Exit(1)
}
out, err := json.MarshalIndent(tok, "", " ")
if err != nil {
slog.Error("AUTHENTICATOR", "failed to unmarshal", err)
os.Exit(1)
}
2024-02-19 02:51:25 +00:00
err = os.WriteFile(filepath.Join(configDir, "gspot/auth.json"), out, 0o600)
2024-02-18 06:57:47 +00:00
if err != nil {
slog.Error("AUTHENTICATOR", "failed to save auth", err)
}
// use the token to get an authenticated client
client := spotify.New(auth.Client(r.Context(), tok))
fmt.Fprintf(w, "Login Completed!")
ch <- client
}