fix user cache misses for mixed-case username and email lookups

getUserByUsername/getUserByEmail normalize to lowercase before the
cache lookup, but the cache comparators matched case-sensitively, so a
mixed-case stored username never cache-hit and every login ran an
unindexed LOWER() scan. Compare case-insensitively inside the cache.
This commit is contained in:
Kevin Gatera 2026-08-02 18:39:42 -04:00
parent 65bcc8488a
commit 9a5980e76b
No known key found for this signature in database
GPG key ID: F0D9F5932458CFB9
2 changed files with 38 additions and 2 deletions

View file

@ -18,12 +18,16 @@ class UserCache {
}
getByEmail(email) {
const user = this.cache.find((u) => u.email === email)
if (!email) return null
const normalizedEmail = email.toLowerCase()
const user = this.cache.find((u) => u.email && u.email.toLowerCase() === normalizedEmail)
return user
}
getByUsername(username) {
const user = this.cache.find((u) => u.username === username)
if (!username) return null
const normalizedUsername = username.toLowerCase()
const user = this.cache.find((u) => u.username && u.username.toLowerCase() === normalizedUsername)
return user
}

View file

@ -43,6 +43,38 @@ describe('User model', () => {
expect(options.where.attribute.args[0].col).to.equal('email')
expect(options.where.logic).to.equal('example.user@example.com')
})
it('should hit the user cache for mixed-case username lookups', async () => {
User.sequelize = {
models: {
mediaProgress: {}
}
}
const cachedUser = { id: 'cache-test-user-1', username: 'CacheTestUser', email: 'cachetest@example.com', extraData: {} }
const findOneStub = sinon.stub(User, 'findOne').resolves(cachedUser)
await User.getUserByUsername('CacheTestUser')
await User.getUserByUsername('cachetestuser')
await User.getUserByUsername('CACHETESTUSER')
expect(findOneStub.callCount).to.equal(1)
})
it('should hit the user cache for mixed-case email lookups', async () => {
User.sequelize = {
models: {
mediaProgress: {}
}
}
const cachedUser = { id: 'cache-test-user-2', username: 'CacheEmailUser', email: 'CacheMail@Example.com', extraData: {} }
const findOneStub = sinon.stub(User, 'findOne').resolves(cachedUser)
await User.getUserByEmail('CacheMail@Example.com')
await User.getUserByEmail('cachemail@example.com')
await User.getUserByEmail('CACHEMAIL@EXAMPLE.COM')
expect(findOneStub.callCount).to.equal(1)
})
})
describe('getUserByIdOrOldId', () => {