Skip to content

Keep bot usable when Spotify refresh token becomes invalid - #73

Open
vtvz wants to merge 3 commits into
masterfrom
fix-spotify-token-lockout
Open

Keep bot usable when Spotify refresh token becomes invalid#73
vtvz wants to merge 3 commits into
masterfrom
fix-spotify-token-lockout

Conversation

@vtvz

@vtvz vtvz commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Previously an invalid_grant on token refresh made user_state construction fail, so every Telegram update died before any reply was sent and the user was permanently locked out of the bot. Now token_refresh clears only the in-memory token (keeping the spotify_auth row), so the state behaves as unauthenticated and handlers prompt the user to /login again.

Previously token_refresh returned an error on invalid_grant, which made
user_state construction fail and every Telegram update die before any
reply was sent, locking the user out of the bot entirely. Now only the
in-memory token is cleared (the spotify_auth row is kept), so handlers
treat the user as unauthenticated and prompt them to /login again.
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Spotify token refresh outcomes now use TokenState, invalid tokens are cleared in memory and handled by App::user_state, invalid users receive Telegram notifications, and unauthenticated tick processing exits early.

Changes

Spotify token state propagation

Layer / File(s) Summary
Refresh state and client return value
src/spotify/mod.rs
token_refresh returns TokenState, preserves the database row for invalid refreshed tokens, clears the in-memory token, and for_user returns the state with AuthCodeSpotify.
Application handling and notification
src/app.rs, src/telegram/mod.rs
App::user_state updates invalid users to SpotifyTokenInvalid and calls notify_token_invalid, which sends the localized Telegram message with the start keyboard.
Unauthenticated tick guard
src/tick/user.rs
check returns Complete when Spotify authentication is unavailable, skipping track-processing logic.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AppUserState
  participant SpotifyManager
  participant Database
  participant Telegram
  participant TickUser
  AppUserState->>SpotifyManager: resolve Spotify client and TokenState
  SpotifyManager-->>AppUserState: TokenState::Invalid
  AppUserState->>Database: update user status
  AppUserState->>Telegram: notify_token_invalid
  TickUser->>TickUser: check Spotify authentication
  TickUser-->>TickUser: return Complete when unauthenticated
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: handling invalid Spotify refresh tokens without locking users out.
Description check ✅ Passed The description accurately describes the invalid_grant handling and fallback to re-login, matching the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-spotify-token-lockout

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/spotify/mod.rs`:
- Around line 390-394: Change the tracing call in the Spotify refresh-token
handling path from warn-level to debug-level for the expected invalid-token
case, while preserving the existing user_id field and message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 28f9b909-c936-4a0a-8928-7a1afae722f0

📥 Commits

Reviewing files that changed from the base of the PR and between c56249f and 9c48e39.

📒 Files selected for processing (1)
  • src/spotify/mod.rs

Comment thread src/spotify/mod.rs Outdated
@vtvz
vtvz deployed to production July 20, 2026 11:05 — with GitHub Actions Active
vtvz added 2 commits July 21, 2026 17:46
The invalid-token alert previously depended on the background tick being
the first to hit the dead token; if the user interacted with the bot
first, the status flipped silently and the alert was never sent. Now
user_state detects the transition itself, marks the user and sends the
localized /login prompt directly, whichever path discovers it first.
The spotify layer no longer touches user status and only reports
TokenState.
@vtvz

vtvz commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your plan includes PR reviews subject to rate limits. More reviews will be available in 36 minutes.

@vtvz

vtvz commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app.rs`:
- Around line 377-391: Update the invalid-token branch in the flow around
UserService::set_status to also set the in-memory user.status to
UserStatus::SpotifyTokenInvalid before constructing UserState, while preserving
the existing database update and notification behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0e769585-1aa5-4d80-82a7-69c10f00f2fc

📥 Commits

Reviewing files that changed from the base of the PR and between c56249f and 0daefe5.

📒 Files selected for processing (4)
  • src/app.rs
  • src/spotify/mod.rs
  • src/telegram/mod.rs
  • src/tick/user.rs

Comment thread src/app.rs
Comment on lines 377 to +391
let (user, newly_created) = UserService::upsert_by_id(self.db(), user_id).await?;
let (spotify, token_state) = self.spotify_manager.for_user(&self.db, user_id).await?;

if matches!(token_state, TokenState::Invalid)
&& user.status != UserStatus::SpotifyTokenInvalid
{
UserService::set_status(self.db(), user_id, UserStatus::SpotifyTokenInvalid).await?;

// NOTE: Yes, it's a hack. I don't know how to handle invalid Spotify token in the right way
// with error_handler module
if let Err(err) = telegram::notify_token_invalid(self, &user).await {
tracing::error!(err = ?err, %user_id, "Failed to notify about invalid Spotify token");
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Stale user status in UserState.

When the token is determined to be invalid, the user status is updated in the database via UserService::set_status, but the in-memory user variable is not updated. Consequently, the UserState created on line 392 will carry a stale status. Any downstream logic expecting the status to be SpotifyTokenInvalid during this execution flow will behave incorrectly.

Update the user model's status in memory before constructing UserState.

♻️ Proposed fix
-        let (user, newly_created) = UserService::upsert_by_id(self.db(), user_id).await?;
+        let (mut user, newly_created) = UserService::upsert_by_id(self.db(), user_id).await?;
         let (spotify, token_state) = self.spotify_manager.for_user(&self.db, user_id).await?;

         if matches!(token_state, TokenState::Invalid)
             && user.status != UserStatus::SpotifyTokenInvalid
         {
             UserService::set_status(self.db(), user_id, UserStatus::SpotifyTokenInvalid).await?;
+            user.status = UserStatus::SpotifyTokenInvalid;

             // NOTE: Yes, it's a hack. I don't know how to handle invalid Spotify token in the right way
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let (user, newly_created) = UserService::upsert_by_id(self.db(), user_id).await?;
let (spotify, token_state) = self.spotify_manager.for_user(&self.db, user_id).await?;
if matches!(token_state, TokenState::Invalid)
&& user.status != UserStatus::SpotifyTokenInvalid
{
UserService::set_status(self.db(), user_id, UserStatus::SpotifyTokenInvalid).await?;
// NOTE: Yes, it's a hack. I don't know how to handle invalid Spotify token in the right way
// with error_handler module
if let Err(err) = telegram::notify_token_invalid(self, &user).await {
tracing::error!(err = ?err, %user_id, "Failed to notify about invalid Spotify token");
}
}
let (mut user, newly_created) = UserService::upsert_by_id(self.db(), user_id).await?;
let (spotify, token_state) = self.spotify_manager.for_user(&self.db, user_id).await?;
if matches!(token_state, TokenState::Invalid)
&& user.status != UserStatus::SpotifyTokenInvalid
{
UserService::set_status(self.db(), user_id, UserStatus::SpotifyTokenInvalid).await?;
user.status = UserStatus::SpotifyTokenInvalid;
// NOTE: Yes, it's a hack. I don't know how to handle invalid Spotify token in the right way
// with error_handler module
if let Err(err) = telegram::notify_token_invalid(self, &user).await {
tracing::error!(err = ?err, %user_id, "Failed to notify about invalid Spotify token");
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app.rs` around lines 377 - 391, Update the invalid-token branch in the
flow around UserService::set_status to also set the in-memory user.status to
UserStatus::SpotifyTokenInvalid before constructing UserState, while preserving
the existing database update and notification behavior.

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.

1 participant