fix: Using Parse Server option extendSessionOnUse does not correctly clear memory and functions as a debounce instead of a throttle (#8683)

This commit is contained in:
Daniel
2025-03-06 11:34:52 +11:00
committed by GitHub
parent 3cb3c5f7c6
commit 6258a6a112
2 changed files with 40 additions and 32 deletions

View File

@@ -106,6 +106,8 @@ describe('Auth', () => {
updatedAt: updatedAt.toISOString(), updatedAt: updatedAt.toISOString(),
} }
); );
Parse.Server.cacheController.clear();
await new Promise(resolve => setTimeout(resolve, 1000));
await session.fetch(); await session.fetch();
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
await session.fetch(); await session.fetch();

View File

@@ -2,6 +2,7 @@ const Parse = require('parse/node');
import { isDeepStrictEqual } from 'util'; import { isDeepStrictEqual } from 'util';
import { getRequestObject, resolveError } from './triggers'; import { getRequestObject, resolveError } from './triggers';
import { logger } from './logger'; import { logger } from './logger';
import { LRUCache as LRU } from 'lru-cache';
import RestQuery from './RestQuery'; import RestQuery from './RestQuery';
import RestWrite from './RestWrite'; import RestWrite from './RestWrite';
@@ -67,6 +68,10 @@ function nobody(config) {
return new Auth({ config, isMaster: false }); return new Auth({ config, isMaster: false });
} }
const throttle = new LRU({
max: 10000,
ttl: 500,
});
/** /**
* Checks whether session should be updated based on last update time & session length. * Checks whether session should be updated based on last update time & session length.
*/ */
@@ -78,44 +83,45 @@ function shouldUpdateSessionExpiry(config, session) {
return lastUpdated <= skipRange; return lastUpdated <= skipRange;
} }
const throttle = {};
const renewSessionIfNeeded = async ({ config, session, sessionToken }) => { const renewSessionIfNeeded = async ({ config, session, sessionToken }) => {
if (!config?.extendSessionOnUse) { if (!config?.extendSessionOnUse) {
return; return;
} }
clearTimeout(throttle[sessionToken]); if (throttle.get(sessionToken)) {
throttle[sessionToken] = setTimeout(async () => { return;
try { }
if (!session) { throttle.set(sessionToken, true);
const query = await RestQuery({ try {
method: RestQuery.Method.get, if (!session) {
config, const query = await RestQuery({
auth: master(config), method: RestQuery.Method.get,
runBeforeFind: false,
className: '_Session',
restWhere: { sessionToken },
restOptions: { limit: 1 },
});
const { results } = await query.execute();
session = results[0];
}
if (!shouldUpdateSessionExpiry(config, session) || !session) {
return;
}
const expiresAt = config.generateSessionExpiresAt();
await new RestWrite(
config, config,
master(config), auth: master(config),
'_Session', runBeforeFind: false,
{ objectId: session.objectId }, className: '_Session',
{ expiresAt: Parse._encode(expiresAt) } restWhere: { sessionToken },
).execute(); restOptions: { limit: 1 },
} catch (e) { });
if (e?.code !== Parse.Error.OBJECT_NOT_FOUND) { const { results } = await query.execute();
logger.error('Could not update session expiry: ', e); session = results[0];
}
} }
}, 500);
if (!shouldUpdateSessionExpiry(config, session) || !session) {
return;
}
const expiresAt = config.generateSessionExpiresAt();
await new RestWrite(
config,
master(config),
'_Session',
{ objectId: session.objectId },
{ expiresAt: Parse._encode(expiresAt) }
).execute();
} catch (e) {
if (e?.code !== Parse.Error.OBJECT_NOT_FOUND) {
logger.error('Could not update session expiry: ', e);
}
}
}; };
// Returns a promise that resolves to an Auth object // Returns a promise that resolves to an Auth object