113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
package oauth
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
clientID = os.Getenv("TWITCH_CLIENT_ID")
|
|
clientSecret = os.Getenv("TWITCH_CLIENT_SECRET")
|
|
redirectURI = "http://localhost:8080/callback"
|
|
|
|
scopes = []string{
|
|
"chat:read",
|
|
"chat:edit",
|
|
"channel:read:subscriptions",
|
|
"user:read:email",
|
|
}
|
|
)
|
|
|
|
type TokenResponse struct {
|
|
AccessToken string `json:"access_token"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
Scope []string `json:"scope"`
|
|
TokenType string `json:"token_type"`
|
|
}
|
|
|
|
// start http server for oauth login & callback
|
|
func StartOAuthServer(addr string) error {
|
|
if clientID == "" || clientSecret == "" {
|
|
return fmt.Errorf("TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET must be set")
|
|
}
|
|
|
|
http.HandleFunc("/", handleIndex)
|
|
http.HandleFunc("/callback", handleCallback)
|
|
|
|
log.Printf("OAuth server listening on %s", addr)
|
|
return http.ListenAndServe(addr, nil)
|
|
}
|
|
|
|
func handleIndex(w http.ResponseWriter, r *http.Request) {
|
|
authURL := fmt.Sprintf(
|
|
"https://id.twitch.tv/oauth2/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=%s",
|
|
url.QueryEscape(clientID),
|
|
url.QueryEscape(redirectURI),
|
|
url.QueryEscape(strings.Join(scopes, " ")),
|
|
)
|
|
|
|
html := fmt.Sprintf(`
|
|
<html><body>
|
|
<h1>Twitch OAuth Login</h1>
|
|
<a href="%s">Login with Twitch</a>
|
|
</body></html>
|
|
`, authURL)
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
w.Write([]byte(html))
|
|
}
|
|
|
|
func handleCallback(w http.ResponseWriter, r *http.Request) {
|
|
code := r.URL.Query().Get("code")
|
|
if code == "" {
|
|
http.Error(w, "Missing code in query", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
token, err := exchangeCodeForToken(code)
|
|
if err != nil {
|
|
http.Error(w, "Failed to get token: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
jsonData, _ := json.MarshalIndent(token, "", " ")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write(jsonData)
|
|
}
|
|
|
|
func exchangeCodeForToken(code string) (*TokenResponse, error) {
|
|
data := url.Values{}
|
|
data.Set("client_id", clientID)
|
|
data.Set("client_secret", clientSecret)
|
|
data.Set("code", code)
|
|
data.Set("grant_type", "authorization_code")
|
|
data.Set("redirect_uri", redirectURI)
|
|
|
|
resp, err := http.Post(
|
|
"https://id.twitch.tv/oauth2/token",
|
|
"application/x-www-form-urlencoded",
|
|
strings.NewReader(data.Encode()),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("token exchange failed: %s", resp.Status)
|
|
}
|
|
|
|
var tokenResp TokenResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &tokenResp, nil
|
|
}
|