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 and
23// ExpiresAt is already set (e.g. from the provider response), it is
24// left unchanged. If neither is usable, ExpiresAt is set to zero so
25// IsExpired treats the token as immediately expired, forcing a refresh
26// rather than guessing a lifetime.
27func (t *Token) SetExpiresAt() {
28 if t.ExpiresIn > 0 {
29 t.ExpiresAt = time.Now().Add(time.Duration(t.ExpiresIn) * time.Second).Unix()
30 return
31 }
32 // ExpiresIn is missing or invalid. If ExpiresAt was already
33 // populated by the provider (some return exp directly), trust it.
34 if t.ExpiresAt > 0 {
35 slog.Warn("OAuth token has invalid expires_in but valid expires_at, using expires_at",
36 "expires_in", t.ExpiresIn, "expires_at", t.ExpiresAt)
37 return
38 }
39 // Neither field is usable. Mark as expired so the caller is forced
40 // to refresh rather than operating with a fabricated lifetime.
41 slog.Warn("OAuth token has no valid expiry information, marking as expired",
42 "expires_in", t.ExpiresIn, "expires_at", t.ExpiresAt)
43 t.ExpiresAt = 0
44}
45
46// IsExpired checks if the token is expired or about to expire. It
47// uses a buffer of max(expires_in/10, minRefreshBuffer) seconds to
48// trigger proactive refresh before the token actually expires.
49func (t *Token) IsExpired() bool {
50 buffer := max(int64(t.ExpiresIn)/10, minRefreshBuffer)
51 return time.Now().Unix() >= (t.ExpiresAt - buffer)
52}
53
54// SetExpiresIn calculates and sets the ExpiresIn field based on the ExpiresAt field.
55func (t *Token) SetExpiresIn() {
56 t.ExpiresIn = int(time.Until(time.Unix(t.ExpiresAt, 0)).Seconds())
57}