Skip to content

Fix duration arithmetic mismatch in clean_old_tokens#1678

Open
naanlizard wants to merge 2 commits intolynndylanhurley:masterfrom
naanlizard:fix/duration-arithmetic-mismatch
Open

Fix duration arithmetic mismatch in clean_old_tokens#1678
naanlizard wants to merge 2 commits intolynndylanhurley:masterfrom
naanlizard:fix/duration-arithmetic-mismatch

Conversation

@naanlizard
Copy link
Copy Markdown

Summary

Fixes #1677

This PR fixes a bug introduced in v1.2.6 (PR #1657, commit 9719d24) where newly created tokens are immediately filtered out when token_lifespan is set to variable-length durations like 6.months.

Commits

  1. First commit (failing test): Adds a test that demonstrates the bug using 6.months duration with time travel to March 1st, 2026
  2. Second commit (fix): Applies the fix to make the test pass

Problem

There was an arithmetic mismatch between two methods:

  • TokenFactory.expiry uses: (Time.zone.now + lifespan).to_i (calendar arithmetic)
  • clean_old_tokens used: Time.now.to_i + lifespan.to_i (fixed-second arithmetic)

For 6.months starting March 1st:

  • Calendar arithmetic: March 1 → September 1 = 184.0 days
  • Fixed seconds: 6.months.to_i = 15,778,476 seconds = 182.62 days
  • Mismatch: 119,124 seconds (1.38 days)

This caused the filter at line 268 to reject newly created tokens because their expiry (calculated with calendar arithmetic) exceeded the max_lifespan_expiry (calculated with fixed-second arithmetic).

Solution

Changed line 265 in app/models/devise_token_auth/concerns/user.rb to use the same calendar arithmetic as TokenFactory.expiry:

# Before (buggy):
max_lifespan_expiry = Time.now.to_i + DeviseTokenAuth.token_lifespan.to_i

# After (fixed):
max_lifespan_expiry = (Time.zone.now + DeviseTokenAuth.token_lifespan).to_i

Impact

  • Affected: Configurations using variable-length durations (6.months, 1.year)
  • Not affected: Fixed durations (2.weeks, 30.days)
  • Symptom: Password resets fail when max_number_of_devices limit is reached

Reproduction

A complete standalone reproduction case is available at: https://github.com/naanlizard/dta-bug-repro

The reproduction uses a fixed date (March 1st, 2026) to consistently demonstrate the bug.

Testing

The first commit adds a test that will FAIL, demonstrating the bug. The second commit (coming shortly) will apply the fix and make the test PASS.

Garrett added 2 commits March 2, 2026 01:20
This test demonstrates the bug where newly created tokens are
immediately filtered out when token_lifespan is set to variable-length
durations like 6.months.

The test uses time travel to March 1st, 2026 to maximize the mismatch
between calendar arithmetic and fixed-second arithmetic.

This test will FAIL with the current code and PASS after the fix.

Related to lynndylanhurley#1677
This fixes the bug where newly created tokens were immediately
filtered out when token_lifespan was set to variable-length
durations like 6.months.

Changed line 265 to use calendar arithmetic matching TokenFactory.expiry:
- Before: Time.now.to_i + DeviseTokenAuth.token_lifespan.to_i
- After: (Time.zone.now + DeviseTokenAuth.token_lifespan).to_i

This ensures consistency between token creation and cleanup.

Fixes lynndylanhurley#1677
@mbiang
Copy link
Copy Markdown

mbiang commented Mar 10, 2026

@naanlizard I just encountered this bug as well. Thank you for the fix.

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.

Duration arithmetic mismatch in v1.2.6 causes newly created tokens to be immediately evicted with variable-length lifespans

2 participants