Skip to content

Commit a944203

Browse files
authored
fix: Session token expiration unchecked on cache hit (#10194)
1 parent 360b846 commit a944203

File tree

2 files changed

+49
-3
lines changed

2 files changed

+49
-3
lines changed

spec/ParseUser.spec.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3318,6 +3318,47 @@ describe('Parse.User testing', () => {
33183318
expect(session.get('expiresAt')).toEqual(expiresAt);
33193319
});
33203320

3321+
it('should reject expired session token even when served from cache', async () => {
3322+
// Use a 1-second session length with a 5-second cache TTL (default)
3323+
// so the session expires while the cache entry is still alive
3324+
await reconfigureServer({ sessionLength: 1 });
3325+
3326+
// Sign up user — creates a session with expiresAt = now + 1 second
3327+
const user = await Parse.User.signUp('cacheuser', 'somepass');
3328+
const sessionToken = user.getSessionToken();
3329+
3330+
// Make an authenticated request to prime the user cache
3331+
await request({
3332+
method: 'GET',
3333+
url: 'http://localhost:8378/1/users/me',
3334+
headers: {
3335+
'X-Parse-Application-Id': 'test',
3336+
'X-Parse-REST-API-Key': 'rest',
3337+
'X-Parse-Session-Token': sessionToken,
3338+
},
3339+
});
3340+
3341+
// Wait for the session to expire (1 second), but cache entry (5s TTL) is still alive
3342+
await new Promise(resolve => setTimeout(resolve, 1500));
3343+
3344+
// This request should be served from cache but still reject the expired session
3345+
try {
3346+
await request({
3347+
method: 'GET',
3348+
url: 'http://localhost:8378/1/users/me',
3349+
headers: {
3350+
'X-Parse-Application-Id': 'test',
3351+
'X-Parse-REST-API-Key': 'rest',
3352+
'X-Parse-Session-Token': sessionToken,
3353+
},
3354+
});
3355+
fail('Should have rejected expired session token from cache');
3356+
} catch (error) {
3357+
expect(error.data.code).toEqual(209);
3358+
expect(error.data.error).toEqual('Session token is expired.');
3359+
}
3360+
});
3361+
33213362
it('should not create extraneous session tokens', done => {
33223363
const config = Config.get(Parse.applicationId);
33233364
config.database

src/Auth.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,13 @@ const getAuthForSessionToken = async function ({
133133
}) {
134134
cacheController = cacheController || (config && config.cacheController);
135135
if (cacheController) {
136-
const userJSON = await cacheController.user.get(sessionToken);
137-
if (userJSON) {
136+
const cached = await cacheController.user.get(sessionToken);
137+
if (cached) {
138+
const { expiresAt: cachedExpiresAt, ...userJSON } = cached;
139+
if (cachedExpiresAt && new Date(cachedExpiresAt) < new Date()) {
140+
cacheController.user.del(sessionToken);
141+
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token is expired.');
142+
}
138143
const cachedUser = Parse.Object.fromJSON(userJSON);
139144
renewSessionIfNeeded({ config, sessionToken });
140145
return Promise.resolve(
@@ -195,7 +200,7 @@ const getAuthForSessionToken = async function ({
195200
obj['className'] = '_User';
196201
obj['sessionToken'] = sessionToken;
197202
if (cacheController) {
198-
cacheController.user.put(sessionToken, obj);
203+
cacheController.user.put(sessionToken, { ...obj, expiresAt: expiresAt?.toISOString() });
199204
}
200205
renewSessionIfNeeded({ config, session, sessionToken });
201206
const userObject = Parse.Object.fromJSON(obj);

0 commit comments

Comments
 (0)