@@ -6,9 +6,11 @@ import (
6
6
"encoding/json"
7
7
"fmt"
8
8
"io"
9
+ "mime"
9
10
"net/http"
10
11
"net/url"
11
12
"regexp"
13
+ "strconv"
12
14
"strings"
13
15
"time"
14
16
@@ -321,13 +323,31 @@ func (c *DeviceAuth) AuthorizeDevice(ctx context.Context) (*codersdk.ExternalAut
321
323
}
322
324
err = json .NewDecoder (resp .Body ).Decode (& r )
323
325
if err != nil {
324
- // Some status codes do not return json payloads, and we should
325
- // return a better error.
326
- switch resp .StatusCode {
327
- case http .StatusTooManyRequests :
328
- return nil ,xerrors .New ("rate limit hit, unable to authorize device. please try again later" )
326
+ mediaType ,_ ,err := mime .ParseMediaType (resp .Header .Get ("Content-Type" ))
327
+ if err != nil {
328
+ mediaType = "unknown"
329
+ }
330
+
331
+ // If the json fails to decode, do a best effort to return a better error.
332
+ switch {
333
+ case resp .StatusCode == http .StatusTooManyRequests :
334
+ retryIn := "please try again later"
335
+ resetIn := resp .Header .Get ("x-ratelimit-reset" )
336
+ if resetIn != "" {
337
+ // Best effort to tell the user exactly how long they need
338
+ // to wait for.
339
+ unix ,err := strconv .ParseInt (resetIn ,10 ,64 )
340
+ if err == nil {
341
+ waitFor := time .Unix (unix ,0 ).Sub (time .Now ().Truncate (time .Second ))
342
+ retryIn = fmt .Sprintf (" retry after %s" ,waitFor .Truncate (time .Second ))
343
+ }
344
+ }
345
+ // 429 returns a plaintext payload with a message.
346
+ return nil ,xerrors .New (fmt .Sprintf ("rate limit hit, unable to authorize device. %s" ,retryIn ))
347
+ case mediaType == "application/x-www-form-urlencoded" :
348
+ return nil ,xerrors .Errorf ("%s payload response is form-url encoded, expected a json payload" ,resp .StatusCode )
329
349
default :
330
- return nil ,fmt .Errorf ("status_code=%d: %w" ,resp .StatusCode ,err )
350
+ return nil ,fmt .Errorf ("status_code=%d, mediaType=%s : %w" ,resp .StatusCode , mediaType ,err )
331
351
}
332
352
}
333
353
if r .ErrorDescription != "" {