audiobookshelf/test/server/auth/TokenManager.test.js
Daniel 95af4812cd Make the refresh-token grace period configurable (default 10 min)
When /auth/refresh rotates the refresh token, the previous token is kept
valid for a short grace period so a client that never received the
rotation response (e.g. a dropped or suspended mobile request) can retry
with the old token and get back the already-rotated current token
instead of a 401.

That window was hardcoded to 60 seconds, which is too short for real
mobile conditions: iOS backgrounding, VPN/proxy timeouts, or network
handoff can delay the retry past a minute, permanently locking the
session out (#5281).

Make it configurable via REFRESH_TOKEN_GRACE_PERIOD (seconds), following
the existing REFRESH_TOKEN_EXPIRY / ACCESS_TOKEN_EXPIRY pattern, and
raise the default to 10 minutes. Adds unit tests for the new config.

Fixes #5281
2026-07-18 21:42:06 -04:00

52 lines
2 KiB
JavaScript

const chai = require('chai')
const sinon = require('sinon')
const { expect } = chai
// Require Database before TokenManager to resolve the Database -> Auth -> TokenManager
// require cycle (otherwise requiring TokenManager first yields an incomplete export).
require('../../../server/Database')
const Logger = require('../../../server/Logger')
const TokenManager = require('../../../server/auth/TokenManager')
describe('TokenManager', () => {
describe('refresh token grace period configuration', () => {
let loggerInfoStub
const originalEnv = process.env.REFRESH_TOKEN_GRACE_PERIOD
beforeEach(() => {
loggerInfoStub = sinon.stub(Logger, 'info')
delete process.env.REFRESH_TOKEN_GRACE_PERIOD
})
afterEach(() => {
loggerInfoStub.restore()
if (originalEnv === undefined) delete process.env.REFRESH_TOKEN_GRACE_PERIOD
else process.env.REFRESH_TOKEN_GRACE_PERIOD = originalEnv
})
it('defaults to 10 minutes (600 seconds) when the env var is not set', () => {
const tokenManager = new TokenManager()
expect(tokenManager.RefreshTokenGracePeriod).to.equal(600)
})
it('reads a positive value from REFRESH_TOKEN_GRACE_PERIOD (in seconds)', () => {
process.env.REFRESH_TOKEN_GRACE_PERIOD = '300'
const tokenManager = new TokenManager()
expect(tokenManager.RefreshTokenGracePeriod).to.equal(300)
expect(loggerInfoStub.calledWithMatch(/grace period set from ENV/i)).to.equal(true)
})
it('falls back to the default for a non-positive or invalid value', () => {
process.env.REFRESH_TOKEN_GRACE_PERIOD = '0'
expect(new TokenManager().RefreshTokenGracePeriod).to.equal(600)
process.env.REFRESH_TOKEN_GRACE_PERIOD = 'not-a-number'
expect(new TokenManager().RefreshTokenGracePeriod).to.equal(600)
})
it('does not log the ENV override message when the env var is unset', () => {
new TokenManager()
expect(loggerInfoStub.calledWithMatch(/grace period set from ENV/i)).to.equal(false)
})
})
})