@@ -54,6 +54,10 @@ export interface PolicyWithRules {
5454 targets : any [ ] ;
5555}
5656
57+ // Attribute cache for reducing database queries
58+ const attributeCache = new Map < string , { attributes : Map < string , AttributeValue > ; timestamp : number } > ( ) ;
59+ const CACHE_TTL = 300000 ; // 5 minutes
60+
5761// Debug logging utility
5862function debugLog (
5963 config : AuthorizationConfig | undefined ,
@@ -65,6 +69,16 @@ function debugLog(
6569 }
6670}
6771
72+ // Cache cleanup to prevent memory growth
73+ function cleanupCache ( ) {
74+ const now = Date . now ( ) ;
75+ for ( const [ key , value ] of attributeCache ) {
76+ if ( now - value . timestamp > CACHE_TTL ) {
77+ attributeCache . delete ( key ) ;
78+ }
79+ }
80+ }
81+
6882// Operators for rule evaluation
6983const OPERATORS = {
7084 equals : ( a : any , b : any ) => a === b ,
@@ -167,13 +181,27 @@ export async function canUserPerformAction(
167181}
168182
169183/**
170- * Gather all relevant attributes for the authorization request
184+ * Gather all relevant attributes for the authorization request with caching
171185 */
172186async function gatherAttributes (
173187 db : Kysely < Database > ,
174188 request : AuthorizationRequest ,
175189 config ?: AuthorizationConfig
176190) : Promise < Map < string , AttributeValue > > {
191+ // Clean up old cache entries periodically
192+ if ( Math . random ( ) < 0.1 ) { // 10% chance to clean up on each call
193+ cleanupCache ( ) ;
194+ }
195+
196+ // Create cache key based on request parameters
197+ const cacheKey = `${ request . subjectId } -${ request . resourceId || 'no-resource' } -${ request . actionName } ` ;
198+ const cached = attributeCache . get ( cacheKey ) ;
199+
200+ if ( cached && ( Date . now ( ) - cached . timestamp ) < CACHE_TTL ) {
201+ debugLog ( config , "🎯 Using cached attributes for:" , cacheKey ) ;
202+ return new Map ( cached . attributes ) ;
203+ }
204+
177205 const attributeMap = new Map < string , AttributeValue > ( ) ;
178206
179207 debugLog (
@@ -407,11 +435,17 @@ async function gatherAttributes(
407435 }
408436 }
409437
438+ // Cache the results
439+ attributeCache . set ( cacheKey , {
440+ attributes : new Map ( attributeMap ) ,
441+ timestamp : Date . now ( )
442+ } ) ;
443+
410444 return attributeMap ;
411445}
412446
413447/**
414- * Add dynamic environment attributes from context
448+ * Add dynamic environment attributes from context - optimized for memory
415449 */
416450function addDynamicEnvironmentAttributes (
417451 attributeMap : Map < string , AttributeValue > ,
@@ -420,21 +454,22 @@ function addDynamicEnvironmentAttributes(
420454) {
421455 debugLog ( config , "🔄 Adding dynamic environment attributes..." ) ;
422456
457+ // Reuse Date object to reduce allocation
458+ const now = new Date ( ) ;
459+ const currentTimeString = now . toISOString ( ) ;
460+
423461 // Current time
424462 attributeMap . set ( "environment.current_time" , {
425463 id : "env-current-time" ,
426464 name : "current_time" ,
427465 type : "string" ,
428466 category : "environment" ,
429- value : new Date ( ) . toISOString ( ) ,
467+ value : currentTimeString ,
430468 } ) ;
431- debugLog (
432- config ,
433- ` ✓ environment.current_time = "${ new Date ( ) . toISOString ( ) } "`
434- ) ;
469+ debugLog ( config , ` ✓ environment.current_time = "${ currentTimeString } "` ) ;
435470
436471 // Current day of week
437- const dayOfWeek = new Date ( ) . getDay ( ) . toString ( ) ;
472+ const dayOfWeek = now . getDay ( ) . toString ( ) ;
438473 attributeMap . set ( "environment.day_of_week" , {
439474 id : "env-day-of-week" ,
440475 name : "day_of_week" ,
@@ -444,20 +479,26 @@ function addDynamicEnvironmentAttributes(
444479 } ) ;
445480 debugLog ( config , ` ✓ environment.day_of_week = "${ dayOfWeek } "` ) ;
446481
447- // Add context attributes
448- if ( context ) {
482+ // Add context attributes - limit context size to prevent memory issues
483+ if ( context && Object . keys ( context ) . length > 0 ) {
449484 debugLog ( config , "🎯 Adding context attributes..." ) ;
450- Object . entries ( context ) . forEach ( ( [ key , value ] ) => {
485+ const contextEntries = Object . entries ( context ) . slice ( 0 , 50 ) ; // Limit to 50 context attributes
486+
487+ for ( const [ key , value ] of contextEntries ) {
451488 const envKey = `environment.${ key } ` ;
452489 attributeMap . set ( envKey , {
453490 id : `ctx-${ key } ` ,
454491 name : key ,
455492 type : typeof value ,
456493 category : "environment" ,
457- value : String ( value ) ,
494+ value : String ( value ) . slice ( 0 , 1000 ) , // Limit value length to 1000 chars
458495 } ) ;
459496 debugLog ( config , ` ✓ ${ envKey } = "${ value } "` ) ;
460- } ) ;
497+ }
498+
499+ if ( Object . keys ( context ) . length > 50 ) {
500+ console . warn ( `Context has ${ Object . keys ( context ) . length } attributes, limiting to 50 for memory efficiency` ) ;
501+ }
461502 }
462503}
463504
@@ -931,7 +972,7 @@ function makeFinalDecision(
931972}
932973
933974/**
934- * Log the access request for auditing
975+ * Log the access request for auditing - optimized for memory usage
935976 */
936977async function logAccessRequest (
937978 db : Kysely < Database > ,
@@ -965,6 +1006,20 @@ async function logAccessRequest(
9651006 return ;
9661007 }
9671008
1009+ // Optimize JSON stringification to reduce memory usage
1010+ const appliedPoliciesString = evaluations . length > 0
1011+ ? JSON . stringify ( evaluations . map ( e => ( {
1012+ policyId : e . policyId ,
1013+ policyName : e . policyName ,
1014+ effect : e . effect ,
1015+ matches : e . matches ,
1016+ } ) ) )
1017+ : "[]" ;
1018+
1019+ const contextString = request . context && Object . keys ( request . context ) . length > 0
1020+ ? JSON . stringify ( request . context )
1021+ : "{}" ;
1022+
9681023 await db
9691024 . insertInto ( "access_request" )
9701025 . values ( {
@@ -973,15 +1028,8 @@ async function logAccessRequest(
9731028 resource_id : resourceId ,
9741029 action_id : action ?. id || request . actionName ,
9751030 decision : decision . decision ,
976- applied_policies : JSON . stringify (
977- evaluations . map ( ( e ) => ( {
978- policyId : e . policyId ,
979- policyName : e . policyName ,
980- effect : e . effect ,
981- matches : e . matches ,
982- } ) )
983- ) ,
984- request_context : JSON . stringify ( request . context || { } ) ,
1031+ applied_policies : appliedPoliciesString ,
1032+ request_context : contextString ,
9851033 processing_time_ms : processingTime ,
9861034 created_at : new Date ( ) ,
9871035 } )
@@ -1060,7 +1108,7 @@ export async function canUserDelete(
10601108}
10611109
10621110/**
1063- * Batch authorization check for multiple resources
1111+ * Batch authorization check for multiple resources with concurrency limiting
10641112 */
10651113export async function canUserPerformActionOnResources (
10661114 db : Kysely < Database > ,
@@ -1071,27 +1119,51 @@ export async function canUserPerformActionOnResources(
10711119 config ?: AuthorizationConfig
10721120) : Promise < Record < string , AuthorizationResult > > {
10731121 const results : Record < string , AuthorizationResult > = { } ;
1122+ const BATCH_SIZE = 10 ; // Limit concurrent operations to prevent memory spikes
1123+
1124+ // Process resources in batches to limit memory usage
1125+ for ( let i = 0 ; i < resourceIds . length ; i += BATCH_SIZE ) {
1126+ const batch = resourceIds . slice ( i , i + BATCH_SIZE ) ;
1127+
1128+ const promises = batch . map ( async ( resourceId ) => {
1129+ try {
1130+ const result = await canUserPerformAction (
1131+ db ,
1132+ {
1133+ subjectId : userId ,
1134+ resourceId,
1135+ actionName,
1136+ context,
1137+ } ,
1138+ config
1139+ ) ;
1140+ return { resourceId, result, success : true } ;
1141+ } catch ( error ) {
1142+ debugLog ( config , `Error processing resource ${ resourceId } :` , error ) ;
1143+ return {
1144+ resourceId,
1145+ result : {
1146+ decision : "indeterminate" as const ,
1147+ reason : `Error processing resource: ${ error instanceof Error ? error . message : 'Unknown error' } ` ,
1148+ appliedPolicies : [ ] ,
1149+ processingTimeMs : 0 ,
1150+ } ,
1151+ success : false
1152+ } ;
1153+ }
1154+ } ) ;
10741155
1075- // Process in parallel for better performance
1076- const promises = resourceIds . map ( async ( resourceId ) => {
1077- const result = await canUserPerformAction (
1078- db ,
1079- {
1080- subjectId : userId ,
1081- resourceId,
1082- actionName,
1083- context,
1084- } ,
1085- config
1086- ) ;
1087- return { resourceId, result } ;
1088- } ) ;
1156+ const batchResults = await Promise . all ( promises ) ;
10891157
1090- const resolvedResults = await Promise . all ( promises ) ;
1158+ batchResults . forEach ( ( { resourceId, result } ) => {
1159+ results [ resourceId ] = result ;
1160+ } ) ;
10911161
1092- resolvedResults . forEach ( ( { resourceId, result } ) => {
1093- results [ resourceId ] = result ;
1094- } ) ;
1162+ // Add small delay between batches to prevent overwhelming the database
1163+ if ( i + BATCH_SIZE < resourceIds . length ) {
1164+ await new Promise ( resolve => setTimeout ( resolve , 10 ) ) ;
1165+ }
1166+ }
10951167
10961168 return results ;
10971169}
0 commit comments