Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion example/tokenauth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
func main() {
fmt.Print("GitHub Token: ")
byteToken, _ := terminal.ReadPassword(int(syscall.Stdin))
println()
token := string(byteToken)

ctx := context.Background()
Expand All @@ -39,7 +40,12 @@ func main() {
}

// Rate.Limit should most likely be 5000 when authorized.
log.Printf("Rate: %#v", resp.Rate)
log.Printf("Rate: %#v\n", resp.Rate)

// If a Token Expiration has been set, it will be displayed.
if !resp.TokenExpiration.IsZero() {
log.Printf("Token Expiration: %v\n", resp.TokenExpiration)
}

fmt.Printf("\n%v\n", github.Stringify(user))
}
17 changes: 17 additions & 0 deletions github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ const (
headerRateReset = "X-RateLimit-Reset"
headerOTP = "X-GitHub-OTP"

headerTokenExpiration = "GitHub-Authentication-Token-Expiration"

mediaTypeV3 = "application/vnd.github.v3+json"
defaultMediaType = "application/octet-stream"
mediaTypeV3SHA = "application/vnd.github.v3.sha"
Expand Down Expand Up @@ -464,6 +466,9 @@ type Response struct {
// Explicitly specify the Rate type so Rate's String() receiver doesn't
// propagate to Response.
Rate Rate

// token's expiration date
TokenExpiration Timestamp
}

// newResponse creates a new Response for the provided http.Response.
Expand All @@ -472,6 +477,7 @@ func newResponse(r *http.Response) *Response {
response := &Response{Response: r}
response.populatePageValues()
response.Rate = parseRate(r)
response.TokenExpiration = parseTokenExpiration(r)
return response
}

Expand Down Expand Up @@ -551,6 +557,17 @@ func parseRate(r *http.Response) Rate {
return rate
}

// parseTokenExpiration parses the TokenExpiration related headers.
func parseTokenExpiration(r *http.Response) Timestamp {
var exp Timestamp
if v := r.Header.Get(headerTokenExpiration); v != "" {
if t, err := time.Parse("2006-01-02 03:04:05 MST", v); err == nil {
exp = Timestamp{t.Local()}
}
}
return exp
}

type requestContext uint8

const (
Expand Down
33 changes: 33 additions & 0 deletions github/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2206,3 +2206,36 @@ func TestRateLimits_Marshal(t *testing.T) {

testJSONMarshal(t, u, want)
}

func TestParseTokenExpiration(t *testing.T) {
tests := []struct {
header string
want Timestamp
}{
{
header: "",
want: Timestamp{},
},
{
header: "this is a garbage",
want: Timestamp{},
},
{
header: "2021-09-03 02:34:04 UTC",
want: Timestamp{time.Date(2021, time.September, 3, 2, 34, 4, 0, time.UTC)},
},
}

for _, tt := range tests {
res := &http.Response{
Request: &http.Request{},
Header: http.Header{},
}

res.Header.Set(headerTokenExpiration, tt.header)
exp := parseTokenExpiration(res)
if !exp.Equal(tt.want) {
t.Errorf("parseTokenExpiration returned %#v, want %#v", exp, tt.want)
}
}
}