This commit replaces the StreamTitle.env file, previously used to store both the client credentials and the refresh token, into a separate config file that contains the client credentials, and a state file that contains the current access token and refresh token of the application. The new files use TOML, because I am planning on having more data types in the future and I'd like to store full data structures. I'm thinking about profiles in the config file. For the first time, I am storing the access token to try to use it before giving up and using the refresh token to issue a new access token. Therefore, the validation flow when starting the application will now try to validate the previous access token before discarding it and issuing a new one. After applying this commit, it will be necessary to recreate the config file according to what the READMe file states, or the application will not work.
58 lines
1.9 KiB
Go
58 lines
1.9 KiB
Go
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
|
)
|
|
|
|
// An internal data structure just to pass two variables between channels.
|
|
// There are other solutions in Go to pass multiple variables through the
|
|
// same channel, but I don't like anonymous structs.
|
|
type loginTokens struct {
|
|
accessToken, refreshToken string
|
|
}
|
|
|
|
// A tricky function that has to spawn a full HTTP server so that an OAuth
|
|
// flow can be made from the command line. This goroutine will block the
|
|
// caller, print a Twitch authorization URL to the stdout, and wait until
|
|
// the internal web server receives the redirection from the Twitch OAuth.
|
|
func spawnAuthorizationServer(client *Client, done chan loginTokens) {
|
|
// State parameter to be used in the OAuth flow.
|
|
state, _ := gonanoid.New()
|
|
|
|
http.HandleFunc("/st-callback", func(w http.ResponseWriter, h *http.Request) {
|
|
// Validate the state parameter (why do I even bother?)
|
|
if h.URL.Query().Get("state") != state {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Header().Add("Content-Type", "text/plain")
|
|
fmt.Fprintln(w, "Invalid state parameter, is everything OK?")
|
|
panic("Invalid state parameter, is everything OK?")
|
|
}
|
|
|
|
// Send affirmative message to the browser.
|
|
w.Header().Add("Content-Type", "text/plain")
|
|
fmt.Fprintln(w, "You should be able to close this window now!")
|
|
|
|
// Handle the token and send the credentials to the caller.
|
|
code := h.URL.Query().Get("code")
|
|
resp, err := client.client.RequestUserAccessToken(code)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
done <- loginTokens{
|
|
accessToken: resp.Data.AccessToken,
|
|
refreshToken: resp.Data.RefreshToken,
|
|
}
|
|
})
|
|
|
|
// Present the URL via stdout.
|
|
url := client.AuthorizationURL(state)
|
|
fmt.Println("Please visit", url, "to continue.")
|
|
|
|
// Start the HTTP server to handle the OAuth callback.
|
|
http.ListenAndServe(":9300", nil)
|
|
}
|