@@ -474,4 +474,131 @@ describe('Auth', () => {
474474 }
475475 } ) ;
476476 } ) ;
477+
478+ describe ( 'periodic cleanup' , ( ) => {
479+ it ( 'should clean up expired tokens and stale rate-limit entries' , ( ) => {
480+ const realSetInterval = global . setInterval ;
481+ let cleanupFn = null ;
482+ global . setInterval = ( fn , delay ) => {
483+ cleanupFn = fn ;
484+ return { unref : ( ) => { } } ;
485+ } ;
486+ try {
487+ // Re-require auth to capture the setInterval callback
488+ delete require . cache [ require . resolve ( '../src/auth' ) ] ;
489+ const { createAuth } = require ( '../src/auth' ) ;
490+ const auth = createAuth ( 'testpw' ) ;
491+ assert . ok ( cleanupFn , 'Should have captured the cleanup function' ) ;
492+
493+ // Generate some tokens and share tokens
494+ const validToken = auth . generateToken ( ) ;
495+ const shareToken = auth . generateShareToken ( ) ;
496+
497+ // Create some rate-limit entries via middleware
498+ const req = {
499+ cookies : { } ,
500+ headers : { authorization : 'Bearer wrong' } ,
501+ ip : '192.168.1.100' ,
502+ socket : { remoteAddress : '192.168.1.100' } ,
503+ path : '/api/test' ,
504+ } ;
505+ const res = {
506+ status ( ) {
507+ return this ;
508+ } ,
509+ json ( ) { } ,
510+ } ;
511+ auth . middleware ( req , res , ( ) => { } ) ;
512+
513+ // Run the cleanup with current time — nothing should be cleaned
514+ cleanupFn ( ) ;
515+ assert . ok ( auth . validateToken ( validToken ) , 'Valid token should survive cleanup' ) ;
516+
517+ // Advance time to expire everything
518+ const realNow = Date . now ;
519+ Date . now = ( ) => realNow ( ) + 25 * 60 * 60 * 1000 ; // 25 hours
520+ try {
521+ cleanupFn ( ) ;
522+ // Token should now be expired and cleaned up
523+ assert . strictEqual (
524+ auth . validateToken ( validToken ) ,
525+ false ,
526+ 'Expired token should be removed' ,
527+ ) ;
528+ // Share token should also be cleaned up (5 min expiry)
529+ assert . strictEqual (
530+ auth . validateShareToken ( shareToken ) ,
531+ false ,
532+ 'Expired share token should be removed' ,
533+ ) ;
534+ } finally {
535+ Date . now = realNow ;
536+ }
537+ } finally {
538+ global . setInterval = realSetInterval ;
539+ delete require . cache [ require . resolve ( '../src/auth' ) ] ;
540+ }
541+ } ) ;
542+
543+ it ( 'should clean up stale rate-limit entries but keep recent ones' , ( ) => {
544+ const realSetInterval = global . setInterval ;
545+ let cleanupFn = null ;
546+ global . setInterval = ( fn ) => {
547+ cleanupFn = fn ;
548+ return { unref : ( ) => { } } ;
549+ } ;
550+ try {
551+ delete require . cache [ require . resolve ( '../src/auth' ) ] ;
552+ const { createAuth } = require ( '../src/auth' ) ;
553+ const auth = createAuth ( 'testpw' ) ;
554+ assert . ok ( cleanupFn ) ;
555+
556+ // Create rate-limit entries via middleware
557+ const req1 = {
558+ cookies : { } ,
559+ headers : { authorization : 'Bearer wrong' } ,
560+ ip : '10.0.0.1' ,
561+ socket : { remoteAddress : '10.0.0.1' } ,
562+ path : '/api/test' ,
563+ } ;
564+ const req2 = {
565+ cookies : { } ,
566+ headers : { authorization : 'Bearer wrong' } ,
567+ ip : '10.0.0.2' ,
568+ socket : { remoteAddress : '10.0.0.2' } ,
569+ path : '/api/test' ,
570+ } ;
571+ const res = {
572+ status ( ) {
573+ return this ;
574+ } ,
575+ json ( ) { } ,
576+ } ;
577+ auth . middleware ( req1 , res , ( ) => { } ) ;
578+ auth . middleware ( req2 , res , ( ) => { } ) ;
579+
580+ // Advance time by 2 minutes (beyond 60s rate-limit window)
581+ const realNow = Date . now ;
582+ Date . now = ( ) => realNow ( ) + 2 * 60 * 1000 ;
583+ try {
584+ cleanupFn ( ) ;
585+ // After cleanup, rate limit entries for both IPs should be removed
586+ // Verify by making 5 attempts — should all succeed (no rate limit)
587+ let count = 0 ;
588+ Date . now = realNow ; // restore time for new attempts
589+ for ( let i = 0 ; i < 5 ; i ++ ) {
590+ auth . rateLimit ( { ip : '10.0.0.1' , socket : { remoteAddress : '10.0.0.1' } } , res , ( ) => {
591+ count ++ ;
592+ } ) ;
593+ }
594+ assert . strictEqual ( count , 5 , 'All 5 attempts should succeed after cleanup' ) ;
595+ } finally {
596+ Date . now = realNow ;
597+ }
598+ } finally {
599+ global . setInterval = realSetInterval ;
600+ delete require . cache [ require . resolve ( '../src/auth' ) ] ;
601+ }
602+ } ) ;
603+ } ) ;
477604} ) ;
0 commit comments