Skip to content

fix(webapi): persist the Web API token refreshed mid-session - #743

Open
prefixFelix wants to merge 1 commit into
jpochyla:mainfrom
prefixFelix:token-refresh
Open

fix(webapi): persist the Web API token refreshed mid-session#743
prefixFelix wants to merge 1 commit into
jpochyla:mainfrom
prefixFelix:token-refresh

Conversation

@prefixFelix

Copy link
Copy Markdown
Contributor

Problem

Spotify rotates the refresh token on every PKCE refresh and revokes the previous one. psst has two refresh paths and only one of them saves the result.

Config::get_or_refresh_webapi_token() runs once at startup and calls save(). WebApi::access_token() runs on worker threads for every API call and only updated its in-memory copy. Its comment claimed the durable copy would be "refreshed by Config on the next save", which was never true: save() serializes Config::webapi_token, a separate field that WebApi never touches. WebApi holds no reference to Config and no way to reach the GUI thread, so it could not persist anything at all.

So once a session runs past the one hour mark, WebApi refreshes, Spotify revokes the old token, the replacement lives only in memory, and config.json keeps a corpse. The next launch presents the revoked token and gets invalid_grant, and so does every launch after that, because nothing ever replaces it. Until you re-authenticate, Web API features stay dead.

Follow up

We should type the invalid_grant response. refresh_webapi_token() collapses every failure into Error::OAuthError(String) built from the oauth2 error's Display and for a server response that Display is literally "Server returned error response", discarding the payload. So the log cannot tell a revoked token from a bad client ID or a network blip.

@jacksongoode jacksongoode left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good for the most part! I'm assuming you have done some testing to see the fix resolved a web api token expiry?

Comment on lines +763 to 766
// `WebApi` keeps its own copy and would save it right back into
// the config we just cleared.
crate::webapi::WebApi::global().set_webapi_credentials(None, None);
data.config.save();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note on these docs, theyre written with reference to the current issue. Unless there is some existing behavior that's present, its not so useful to write the docs in reference to past behavior or a fix we've completed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// `WebApi` keeps its own copy and would save it right back into
// the config we just cleared.
crate::webapi::WebApi::global().set_webapi_credentials(None, None);
data.config.save();
// `WebApi` holds an in-memory copy of the credentials that it
// persists back to the config on save, so clear it too.
crate::webapi::WebApi::global().set_webapi_credentials(None, None);
data.config.save();

*self.webapi_token.lock() = token;
}

/// Install the handle used to send refreshed tokens back to the GUI thread.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit weird that the gui owns the config right? Do you have any architectural thoughts on what would be better here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could maybe split the toke stuff from the config file into a own token-store?
Token-store would be moved to the core and the config stays in the gui (as it then only holds gui stuff).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would also add feat of #451

@prefixFelix

Copy link
Copy Markdown
Contributor Author

This looks good for the most part! I'm assuming you have done some testing to see the fix resolved a web api token expiry?

Yes I changed the epoch time from expires_at in the config so that it would expire in 2 minutes. Launched psst, waited 2 minutes and then clicked on a playlist. WebAPI then attempting refresh was immediately followed by saved config (refresh_token changed). Before that the refresh_token was unchanged.

@jacksongoode

Copy link
Copy Markdown
Collaborator

@prefixFelix I'd like to also get this in, can you rebase on the latest changes and test. I'll review post.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants