From c83418d10ba6c166419d1ff781380c3a0b0b0ca1 Mon Sep 17 00:00:00 2001 From: Kieran Klukas Date: Fri, 29 May 2026 14:36:42 -0400 Subject: [PATCH] fix(oauth): stop fabricating token lifetime when expires_in is missing SetExpiresAt no longer silently defaults to 3600s when ExpiresIn is zero or negative. If ExpiresAt was already set by the provider (some return exp directly), it is preserved. Otherwise the token is marked as immediately expired (ExpiresAt=0) so callers are forced to refresh rather than operating with a guessed lifetime. Also modernizes IsExpired to use max() for the buffer calculation. --- internal/oauth/token.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/internal/oauth/token.go b/internal/oauth/token.go index 1c66374a0f4c10611e9395da676fde83565b480f..16c5a750f359250d0b73e4186921c55c95dc6494 100644 --- a/internal/oauth/token.go +++ b/internal/oauth/token.go @@ -19,24 +19,35 @@ type Token struct { } // SetExpiresAt calculates and sets the ExpiresAt field based on the -// current time and ExpiresIn. If ExpiresIn is zero or negative, it -// defaults to 3600 seconds and logs a warning. +// current time and ExpiresIn. If ExpiresIn is zero or negative and +// ExpiresAt is already set (e.g. from the provider response), it is +// left unchanged. If neither is usable, ExpiresAt is set to zero so +// IsExpired treats the token as immediately expired, forcing a refresh +// rather than guessing a lifetime. func (t *Token) SetExpiresAt() { - if t.ExpiresIn <= 0 { - slog.Warn("OAuth token has invalid expires_in, defaulting to 3600s", "expires_in", t.ExpiresIn) - t.ExpiresIn = 3600 + if t.ExpiresIn > 0 { + t.ExpiresAt = time.Now().Add(time.Duration(t.ExpiresIn) * time.Second).Unix() + return } - t.ExpiresAt = time.Now().Add(time.Duration(t.ExpiresIn) * time.Second).Unix() + // ExpiresIn is missing or invalid. If ExpiresAt was already + // populated by the provider (some return exp directly), trust it. + if t.ExpiresAt > 0 { + slog.Warn("OAuth token has invalid expires_in but valid expires_at, using expires_at", + "expires_in", t.ExpiresIn, "expires_at", t.ExpiresAt) + return + } + // Neither field is usable. Mark as expired so the caller is forced + // to refresh rather than operating with a fabricated lifetime. + slog.Warn("OAuth token has no valid expiry information, marking as expired", + "expires_in", t.ExpiresIn, "expires_at", t.ExpiresAt) + t.ExpiresAt = 0 } // IsExpired checks if the token is expired or about to expire. It // uses a buffer of max(expires_in/10, minRefreshBuffer) seconds to // trigger proactive refresh before the token actually expires. func (t *Token) IsExpired() bool { - buffer := int64(t.ExpiresIn) / 10 - if buffer < minRefreshBuffer { - buffer = minRefreshBuffer - } + buffer := max(int64(t.ExpiresIn)/10, minRefreshBuffer) return time.Now().Unix() >= (t.ExpiresAt - buffer) }