diff options
Diffstat (limited to 'internal/api/google_tasks_oauth.go')
| -rw-r--r-- | internal/api/google_tasks_oauth.go | 25 |
1 files changed, 25 insertions, 0 deletions
diff --git a/internal/api/google_tasks_oauth.go b/internal/api/google_tasks_oauth.go index 6edba21..c6f816c 100644 --- a/internal/api/google_tasks_oauth.go +++ b/internal/api/google_tasks_oauth.go @@ -3,6 +3,7 @@ package api import ( "context" "fmt" + "strings" "time" "golang.org/x/oauth2" @@ -18,6 +19,21 @@ import ( type TokenStore interface { GetOAuthToken(source string) (*oauth2.Token, error) SaveOAuthToken(source string, tok *oauth2.Token) error + // RecordOAuthTokenError persists why a refresh failed, so the settings page + // can distinguish "connected" from "has a row but the grant is dead". + RecordOAuthTokenError(source string, message string) error +} + +// NeedsReconnect reports whether an error means the stored grant is finished +// and only a fresh consent will fix it. +// +// Google answers invalid_grant when a refresh token has been revoked, +// superseded, or has simply aged out — which for an OAuth client still in +// "Testing" publishing status happens after seven days. Retrying cannot help; +// the user has to reconnect. Any other error (a network blip, a 5xx) is worth +// retrying and must not push the user through consent needlessly. +func NeedsReconnect(err error) bool { + return err != nil && strings.Contains(err.Error(), "invalid_grant") } // GoogleTasksOAuthSource is the key GoogleTasksClient's token is stored @@ -77,6 +93,15 @@ func (d *dbTokenSource) Token() (*oauth2.Token, error) { refreshed, err := d.config.TokenSource(ctx, current).Token() if err != nil { + // Record it: a failed refresh is invisible otherwise, and an + // invalid_grant in particular means no amount of waiting will fix it. + msg := err.Error() + if NeedsReconnect(err) { + msg = "Google rejected the saved authorisation (invalid_grant) — reconnect to continue. " + + "If this recurs weekly, the OAuth client is still in Testing publishing status, " + + "which expires refresh tokens after 7 days." + } + _ = d.store.RecordOAuthTokenError(d.source, msg) return nil, fmt.Errorf("refreshing token: %w", err) } |
