Problem
A hub lost its Alby Account session and required the user to log in manually, even though the OAuth refresh token should be long-lived.
Likely cause: in saveToken (alby/alby_oauth_service.go), the expiry, access token and refresh token are each written once with SetUpdate, and a failure is only logged (Failed to save refresh token). If that write fails, the DB keeps the old refresh token while the server has already rotated it. The hub then works until the access token expires, after which every refresh fails with invalid_grant until the user re-authenticates.
fetchUserToken always re-reads the token from the DB, so the freshly refreshed token is discarded even though it is the only valid credential left.
Applies to both SQLite and PostgreSQL (busy/lock timeout, connection error, or DB closing during shutdown).
Proposed fix (minimal, no refactor)
- Retry the token save a few times with a short delay (e.g. 3 attempts, ~500ms–1s apart) for each of the three keys in
saveToken.
- Keep the refreshed token in memory in
albyOAuthService (guarded by the existing tokenMutex). fetchUserToken should prefer the in-memory token when it is newer than the DB copy, and re-attempt the save on the next call if persisting failed. This way a failed save never strands the hub with a rotated refresh token.
How to verify
- Make
SetUpdate for AlbyOAuthRefreshToken fail once during a refresh; the hub should keep working and persist the token on a later attempt.
- Run with both SQLite and
TEST_DATABASE_URI set to PostgreSQL.
Problem
A hub lost its Alby Account session and required the user to log in manually, even though the OAuth refresh token should be long-lived.
Likely cause: in
saveToken(alby/alby_oauth_service.go), the expiry, access token and refresh token are each written once withSetUpdate, and a failure is only logged (Failed to save refresh token). If that write fails, the DB keeps the old refresh token while the server has already rotated it. The hub then works until the access token expires, after which every refresh fails withinvalid_grantuntil the user re-authenticates.fetchUserTokenalways re-reads the token from the DB, so the freshly refreshed token is discarded even though it is the only valid credential left.Applies to both SQLite and PostgreSQL (busy/lock timeout, connection error, or DB closing during shutdown).
Proposed fix (minimal, no refactor)
saveToken.albyOAuthService(guarded by the existingtokenMutex).fetchUserTokenshould prefer the in-memory token when it is newer than the DB copy, and re-attempt the save on the next call if persisting failed. This way a failed save never strands the hub with a rotated refresh token.How to verify
SetUpdateforAlbyOAuthRefreshTokenfail once during a refresh; the hub should keep working and persist the token on a later attempt.TEST_DATABASE_URIset to PostgreSQL.