@@ -13,6 +13,7 @@ import uuid from 'uuid';
13
13
import { runLiveQueryEventHandlers } from '../triggers' ;
14
14
import { getAuthForSessionToken , Auth } from '../Auth' ;
15
15
import { getCacheController } from '../Controllers' ;
16
+ import LRU from 'lru-cache' ;
16
17
17
18
class ParseLiveQueryServer {
18
19
clients : Map ;
@@ -45,8 +46,17 @@ class ParseLiveQueryServer {
45
46
Parse . serverURL = serverURL ;
46
47
Parse . initialize ( config . appId , Parse . javaScriptKey , config . masterKey ) ;
47
48
49
+ // The cache controller is a proper cache controller
50
+ // With access to User and Roles
48
51
this . cacheController = getCacheController ( config )
49
52
53
+ // This auth cache stores the promises for each auth resolution
54
+ // The main benefit is to be able to reuse the same user / session token resolution
55
+ // And to chain
56
+ this . authCache = new LRU ( {
57
+ max : 500 , // 500 concurrent
58
+ maxAge : 60 * 60 * 1000 // 1h
59
+ } ) ;
50
60
// Initialize websocket server
51
61
this . parseWebSocketServer = new ParseWebSocketServer (
52
62
server ,
@@ -334,24 +344,38 @@ class ParseLiveQueryServer {
334
344
}
335
345
336
346
async getAuthForSessionToken ( sessionToken : ?string ) : { auth: ?Auth , userId : ?string } {
347
+ if ( ! sessionToken ) {
348
+ return { } ;
349
+ }
350
+ const fromCache = this . authCache . get ( sessionToken ) ;
351
+ if ( fromCache ) {
352
+ return fromCache ;
353
+ }
337
354
try {
338
- const auth = await getAuthForSessionToken ( { cacheController : this . cacheController , sessionToken : sessionToken } ) ;
339
- return { auth, userId : auth && auth . user && auth . user . id } // return the ID of the found user
355
+ const authPromise = getAuthForSessionToken ( { cacheController : this . cacheController , sessionToken : sessionToken } )
356
+ . then ( ( auth ) => {
357
+ return { auth, userId : auth && auth . user && auth . user . id } ;
358
+ } , ( ) => {
359
+ // If you can't continue, let's just wrap it up and delete it.
360
+ // Next time, one will try again
361
+ this . authCache . del ( sessionToken ) ;
362
+ } ) ;
363
+ this . authCache . set ( sessionToken , authPromise ) ;
364
+ return authPromise ;
340
365
} catch ( e ) { /* ignore errors */ }
341
366
return { } ;
342
367
}
343
368
344
369
async _matchesCLP ( classLevelPermissions : ?any , object : any , client : any , requestId : number , op : string) : any {
345
370
// try to match on user first, less expensive than with roles
346
371
const subscriptionInfo = client . getSubscriptionInfo ( requestId ) ;
347
- if ( typeof subscriptionInfo === 'undefined' ) {
348
- return Promise . resolve ( [ '*' ] ) ;
349
- }
350
- const subscriptionSessionToken = subscriptionInfo . sessionToken ;
351
372
const aclGroup = [ '*' ] ;
352
- const { userId } = await this . getAuthForSessionToken ( subscriptionSessionToken ) ;
353
- if ( userId ) {
354
- aclGroup . push ( userId ) ;
373
+ let userId ;
374
+ if ( typeof subscriptionInfo !== 'undefined' ) {
375
+ const { userId } = await this . getAuthForSessionToken ( subscriptionInfo . sessionToken ) ;
376
+ if ( userId ) {
377
+ aclGroup . push ( userId ) ;
378
+ }
355
379
}
356
380
try {
357
381
await SchemaController . validatePermission ( classLevelPermissions , object . className , aclGroup , op ) ;
@@ -390,9 +414,8 @@ class ParseLiveQueryServer {
390
414
return Promise . resolve ( false ) ;
391
415
}
392
416
393
- const subscriptionSessionToken = subscriptionInfo . sessionToken ;
394
417
// TODO: get auth there and de-duplicate code below to work with the same Auth obj.
395
- const { auth, userId } = await this . getAuthForSessionToken ( subscriptionSessionToken ) ;
418
+ const { auth, userId } = await this . getAuthForSessionToken ( subscriptionInfo . sessionToken ) ;
396
419
const isSubscriptionSessionTokenMatched = acl . getReadAccess ( userId ) ;
397
420
if ( isSubscriptionSessionTokenMatched ) {
398
421
return Promise . resolve ( true ) ;
0 commit comments