@@ -66,6 +66,14 @@ type ValidateResponse struct {
66
66
ExpiresIn int64 `json:"expires_in"`
67
67
}
68
68
69
+ type DeviceCodeFlowInitResponse struct {
70
+ DeviceCode string `json:"device_code"`
71
+ ExpiresIn int `json:"expires_in"`
72
+ Interval int `json:"interval"`
73
+ UserCode string `json:"user_code"`
74
+ VerificationUri string `json:"verification_uri"`
75
+ }
76
+
69
77
const ClientCredentialsURL = "https://id.twitch.tv/oauth2/token?grant_type=client_credentials"
70
78
const UserCredentialsURL = "https://id.twitch.tv/oauth2/token?grant_type=authorization_code"
71
79
@@ -75,6 +83,10 @@ const RefreshTokenURL = "https://id.twitch.tv/oauth2/token?grant_type=refresh_to
75
83
const RevokeTokenURL = "https://id.twitch.tv/oauth2/revoke"
76
84
const ValidateTokenURL = "https://id.twitch.tv/oauth2/validate"
77
85
86
+ const DeviceCodeFlowUrl = "https://id.twitch.tv/oauth2/device"
87
+ const DeviceCodeFlowTokenURL = "https://id.twitch.tv/oauth2/token"
88
+ const DeviceCodeFlowGrantType = "urn:ietf:params:oauth:grant-type:device_code"
89
+
78
90
// Sends `https://id.twitch.tv/oauth2/token?grant_type=client_credentials`.
79
91
// Generates a new App Access Token. Stores new token information in the CLI's config.
80
92
func ClientCredentialsLogin (p LoginParameters ) (LoginResponse , error ) {
@@ -104,9 +116,10 @@ func ClientCredentialsLogin(p LoginParameters) (LoginResponse, error) {
104
116
return r , nil
105
117
}
106
118
119
+ // Uses Authorization Code Flow: https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/#authorization-code-grant-flow
107
120
// Sends `https://id.twitch.tv/oauth2/token?grant_type=authorization_code`.
108
- // Generates a new App Access Token, requiring the use of a web browser. Stores new token information in the CLI's config.
109
- func UserCredentialsLogin (p LoginParameters , webserverIP string , webserverPort string ) (LoginResponse , error ) {
121
+ // Generates a new User Access Token, requiring the use of a web browser. Stores new token information in the CLI's config.
122
+ func UserCredentialsLogin_AuthorizationCodeFlow (p LoginParameters , webserverIP string , webserverPort string ) (LoginResponse , error ) {
110
123
u , err := url .Parse (p .AuthorizeURL )
111
124
if err != nil {
112
125
return LoginResponse {}, fmt .Errorf ("Internal error (parsing AuthorizeURL): %v" , err .Error ())
@@ -161,6 +174,14 @@ func UserCredentialsLogin(p LoginParameters, webserverIP string, webserverPort s
161
174
return LoginResponse {}, fmt .Errorf ("Error reading body: %v" , err .Error ())
162
175
}
163
176
177
+ if resp .StatusCode == 400 {
178
+ // If 400 is returned, the applications' Client Type was set up as "Public", and you can only use Implicit Auth or Device Code Flow to get a User Access Token
179
+ return LoginResponse {}, fmt .Errorf (
180
+ "This Client Type of this Client ID is set to \" Public\" , which doesn't allow the use of Authorization Code Grant Flow.\n " +
181
+ "Please call the token command with the --dcf flag to use Device Code Flow. For example: twitch token -u --dcf" ,
182
+ )
183
+ }
184
+
164
185
r , err := handleLoginResponse (resp .Body , true )
165
186
if err != nil {
166
187
return LoginResponse {}, fmt .Errorf ("Error handling login: %v" , err .Error ())
@@ -169,6 +190,55 @@ func UserCredentialsLogin(p LoginParameters, webserverIP string, webserverPort s
169
190
return r , nil
170
191
}
171
192
193
+ // Uses Device Code Flow: https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/#device-code-grant-flow
194
+ // Generates a new User Access Token, requiring the use of a web browser from any device. Stores new token information in the CLI's config.
195
+ func UserCredentialsLogin_DeviceCodeFlow (p LoginParameters ) (LoginResponse , error ) {
196
+ // Initiate DCF flow
197
+ deviceResp , err := dcfInitiateRequest (DeviceCodeFlowUrl , p .ClientID , p .Scopes )
198
+ if err != nil {
199
+ return LoginResponse {}, fmt .Errorf ("Error initiating Device Code Flow: %v" , err .Error ())
200
+ }
201
+
202
+ var deviceObj DeviceCodeFlowInitResponse
203
+ if err := json .Unmarshal (deviceResp .Body , & deviceObj ); err != nil {
204
+ return LoginResponse {}, fmt .Errorf ("Error reading body: %v" , err .Error ())
205
+ }
206
+ expirationTime := time .Now ().Add (time .Second * time .Duration (deviceObj .ExpiresIn ))
207
+
208
+ fmt .Printf ("Started Device Code Flow login.\n " )
209
+ fmt .Printf ("Use this URL to log in: %v\n " , deviceObj .VerificationUri )
210
+ fmt .Printf ("Use this code when prompted at the above URL: %v\n \n " , deviceObj .UserCode )
211
+ fmt .Printf ("This system will check every %v seconds, and will expire after %v minutes.\n " , deviceObj .Interval , (deviceObj .ExpiresIn / 60 ))
212
+
213
+ // Loop and check for user login. Respects given interval, and times out after expiration
214
+ tokenResp := loginRequestResponse {StatusCode : 999 }
215
+ for tokenResp .StatusCode != 0 {
216
+ // Check for expiration
217
+ if time .Now ().After (expirationTime ) {
218
+ return LoginResponse {}, fmt .Errorf ("The Device Code used for getting access token has expired. Run token command again to generate a new user." )
219
+ }
220
+
221
+ // Wait interval
222
+ time .Sleep (time .Second * time .Duration (deviceObj .Interval ))
223
+
224
+ // Check for token
225
+ tokenResp , err = dcfTokenRequest (DeviceCodeFlowTokenURL , p .ClientID , p .Scopes , deviceObj .DeviceCode , DeviceCodeFlowGrantType )
226
+ if err != nil {
227
+ return LoginResponse {}, fmt .Errorf ("Error getting token via Device Code Flow: %v" , err )
228
+ }
229
+
230
+ if tokenResp .StatusCode == 200 {
231
+ r , err := handleLoginResponse (tokenResp .Body , true )
232
+ if err != nil {
233
+ return LoginResponse {}, fmt .Errorf ("Error handling login: %v" , err .Error ())
234
+ }
235
+ return r , nil
236
+ }
237
+ }
238
+
239
+ return LoginResponse {}, nil
240
+ }
241
+
172
242
// Sends `https://id.twitch.tv/oauth2/revoke`.
173
243
// Revokes the provided token. Does not change the CLI's config at all.
174
244
func CredentialsLogout (p LoginParameters ) (LoginResponse , error ) {
0 commit comments