Skip to content

Commit

Permalink
Added --validate flag to twitch token (#277)
Browse files Browse the repository at this point in the history
Added --validate flag to twitch token
  • Loading branch information
Xemdo authored Sep 23, 2023
1 parent 103544e commit 94c00c5
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 0 deletions.
38 changes: 38 additions & 0 deletions cmd/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ package cmd
import (
"fmt"
"strconv"
"time"

"github.com/fatih/color"
"github.com/twitchdev/twitch-cli/internal/login"

"github.com/spf13/cobra"
Expand All @@ -15,6 +17,7 @@ import (
var isUserToken bool
var userScopes string
var revokeToken string
var validateToken string
var overrideClientId string
var tokenServerPort int
var tokenServerIP string
Expand All @@ -32,6 +35,7 @@ func init() {
loginCmd.Flags().BoolVarP(&isUserToken, "user-token", "u", false, "Whether to login as a user or getting an app access token.")
loginCmd.Flags().StringVarP(&userScopes, "scopes", "s", "", "Space separated list of scopes to request with your user token.")
loginCmd.Flags().StringVarP(&revokeToken, "revoke", "r", "", "Instead of generating a new token, revoke the one passed to this parameter.")
loginCmd.Flags().StringVarP(&validateToken, "validate", "v", "", "Instead of generating a new token, validate the one passed to this parameter.")
loginCmd.Flags().StringVar(&overrideClientId, "client-id", "", "Override/manually set client ID for token actions. By default client ID from CLI config will be used.")
loginCmd.Flags().StringVar(&tokenServerIP, "ip", "localhost", "Manually set the IP address to be binded to for the User Token web server.")
loginCmd.Flags().IntVarP(&tokenServerPort, "port", "p", 3000, "Manually set the port to be used for the User Token web server.")
Expand Down Expand Up @@ -67,6 +71,40 @@ func loginCmdRun(cmd *cobra.Command, args []string) error {
p.Token = revokeToken
p.URL = login.RevokeTokenURL
login.CredentialsLogout(p)
} else if validateToken != "" {
p.Token = validateToken
p.URL = login.ValidateTokenURL
r, err := login.ValidateCredentials(p)
if err != nil {
return fmt.Errorf("Failed to validate: %v", err.Error())
}

tokenType := "App Access Token"
if r.UserID != "" {
tokenType = "User Access Token"
}

expiresInTimestamp := time.Now().Add(time.Duration(r.ExpiresIn) * time.Second).UTC().Format(time.RFC1123)

lightYellow := color.New(color.FgHiYellow).PrintfFunc()
white := color.New(color.FgWhite).SprintfFunc()

lightYellow("Client ID: %v\n", white(r.ClientID))
lightYellow("Token Type: %v\n", white(tokenType))
if r.UserID != "" {
lightYellow("User ID: %v\n", white(r.UserID))
lightYellow("User Login: %v\n", white(r.UserLogin))
}
lightYellow("Expires In: %v\n", white("%v (%v)", strconv.FormatInt(r.ExpiresIn, 10), expiresInTimestamp))

if len(r.Scopes) == 0 {
lightYellow("User ID: %v\n", white("None"))
} else {
lightYellow("Scopes:\n")
for _, s := range r.Scopes {
fmt.Println(white("- %v\n", s))
}
}
} else if isUserToken == true {
p.URL = login.UserCredentialsURL
login.UserCredentialsLogin(p, tokenServerIP, webserverPort)
Expand Down
35 changes: 35 additions & 0 deletions internal/login/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ type LoginResponse struct {
ExpiresAt time.Time
}

type ValidateResponse struct {
ClientID string `json:"client_id"`
UserLogin string `json:"login"`
UserID string `json:"user_id"`
Scopes []string `json:"scopes"`
ExpiresIn int64 `json:"expires_in"`
}

const ClientCredentialsURL = "https://id.twitch.tv/oauth2/token?grant_type=client_credentials"

const UserCredentialsURL = "https://id.twitch.tv/oauth2/token?grant_type=authorization_code"
Expand All @@ -66,6 +74,8 @@ const RefreshTokenURL = "https://id.twitch.tv/oauth2/token?grant_type=refresh_to

const RevokeTokenURL = "https://id.twitch.tv/oauth2/revoke"

const ValidateTokenURL = "https://id.twitch.tv/oauth2/validate"

func ClientCredentialsLogin(p LoginParameters) (LoginResponse, error) {
u, err := url.Parse(p.URL)
if err != nil {
Expand Down Expand Up @@ -216,6 +226,31 @@ func RefreshUserToken(p RefreshParameters) (LoginResponse, error) {
return r, nil
}

func ValidateCredentials(p LoginParameters) (ValidateResponse, error) {
u, err := url.Parse(p.URL)
if err != nil {
log.Fatal(err)
}

resp, err := loginRequestWithHeaders(http.MethodGet, u.String(), nil, []loginHeader{
loginHeader{
Key: "Authorization",
Value: "OAuth " + p.Token,
},
})
if err != nil {
return ValidateResponse{}, err
}

// Handle validate response body
var r ValidateResponse
if err = json.Unmarshal(resp.Body, &r); err != nil {
return ValidateResponse{}, err
}

return r, nil
}

func handleLoginResponse(body []byte) (LoginResponse, error) {
var r AuthorizationResponse
if err := json.Unmarshal(body, &r); err != nil {
Expand Down
13 changes: 13 additions & 0 deletions internal/login/login_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,22 @@ type loginRequestResponse struct {
Body []byte
}

type loginHeader struct {
Key string
Value string
}

func loginRequest(method string, url string, payload io.Reader) (loginRequestResponse, error) {
return loginRequestWithHeaders(method, url, payload, []loginHeader{})
}

func loginRequestWithHeaders(method string, url string, payload io.Reader, headers []loginHeader) (loginRequestResponse, error) {
req, err := request.NewRequest(method, url, payload)

for _, header := range headers {
req.Header.Add(header.Key, header.Value)
}

client := &http.Client{
Timeout: time.Second * 10,
}
Expand Down

0 comments on commit 94c00c5

Please sign in to comment.