token.go

 1package oauth
 2
 3import (
 4	"log/slog"
 5	"time"
 6)
 7
 8// minRefreshBuffer is the minimum number of seconds before actual
 9// expiry at which IsExpired returns true. Prevents very short-lived
10// tokens from having a meaningless refresh window.
11const minRefreshBuffer = 30
12
13// Token represents an OAuth2 token.
14type Token struct {
15	AccessToken  string `json:"access_token"`
16	RefreshToken string `json:"refresh_token"`
17	ExpiresIn    int    `json:"expires_in"`
18	ExpiresAt    int64  `json:"expires_at"`
19}
20
21// SetExpiresAt calculates and sets the ExpiresAt field based on the
22// current time and ExpiresIn. If ExpiresIn is zero or negative, it
23// defaults to 3600 seconds and logs a warning.
24func (t *Token) SetExpiresAt() {
25	if t.ExpiresIn <= 0 {
26		slog.Warn("OAuth token has invalid expires_in, defaulting to 3600s", "expires_in", t.ExpiresIn)
27		t.ExpiresIn = 3600
28	}
29	t.ExpiresAt = time.Now().Add(time.Duration(t.ExpiresIn) * time.Second).Unix()
30}
31
32// IsExpired checks if the token is expired or about to expire. It
33// uses a buffer of max(expires_in/10, minRefreshBuffer) seconds to
34// trigger proactive refresh before the token actually expires.
35func (t *Token) IsExpired() bool {
36	buffer := int64(t.ExpiresIn) / 10
37	if buffer < minRefreshBuffer {
38		buffer = minRefreshBuffer
39	}
40	return time.Now().Unix() >= (t.ExpiresAt - buffer)
41}
42
43// SetExpiresIn calculates and sets the ExpiresIn field based on the ExpiresAt field.
44func (t *Token) SetExpiresIn() {
45	t.ExpiresIn = int(time.Until(time.Unix(t.ExpiresAt, 0)).Seconds())
46}