on main help sign in
fix(oauth): harden IsExpired with minimum buffer and ExpiresIn guard

Add 30-second minimum refresh buffer so short-lived tokens always have a meaningful proactive refresh window. Guard against ExpiresIn=0 in SetExpiresAt by defaulting to 3600s with a warning, preventing tokens from being dead on arrival.

commitd87a632af1cd528b8ed76adbc3f45041acd2b128 authorKieran Klukas <kieran@dunkirk.sh> dateThu 28 May 2026, 14:38 (5 months ago) parent7ce0936
1 file changed+21−3
internal/oauth/token.go+21−3
internal/oauth/token.go+21−3
@@ -1,9 +1,15 @@
11package oauth
22
33import (
4+ "log/slog"
45 "time"
56)
67
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.
11+const minRefreshBuffer = 30
12+
713// Token represents an OAuth2 token.
814type Token struct {
915 AccessToken string `json:"access_token"`
@@ -12,14 +18,26 @@ type Token struct {
1218 ExpiresAt int64 `json:"expires_at"`
1319}
1420
15−// SetExpiresAt calculates and sets the ExpiresAt field based on the current time and ExpiresIn.
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.
1624func (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+ }
1729 t.ExpiresAt = time.Now().Add(time.Duration(t.ExpiresIn) * time.Second).Unix()
1830}
1931
20−// IsExpired checks if the token is expired or about to expire (within 10% of its lifetime).
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.
2135func (t *Token) IsExpired() bool {
22− return time.Now().Unix() >= (t.ExpiresAt - int64(t.ExpiresIn)/10)
36+ buffer := int64(t.ExpiresIn) / 10
37+ if buffer < minRefreshBuffer {
38+ buffer = minRefreshBuffer
39+ }
40+ return time.Now().Unix() >= (t.ExpiresAt - buffer)
2341}
2442
2543// SetExpiresIn calculates and sets the ExpiresIn field based on the ExpiresAt field.