]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(ocpp2): integrate InMemoryAuthCache into auth service
authorJérôme Benoit <jerome.benoit@sap.com>
Tue, 18 Nov 2025 18:20:29 +0000 (19:20 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Tue, 18 Nov 2025 18:20:29 +0000 (19:20 +0100)
Update factory, interfaces, and service to support cache integration:
- AuthComponentFactory.createAuthCache() now instantiates InMemoryAuthCache
- Add evictions field to CacheStats interface
- Add rateLimit field to AuthStats interface (blockedRequests, rateLimitedIdentifiers, totalChecks)
- Make OCPPAuthServiceImpl.getStats() async to fetch cache stats from strategies
- Update factory test to expect cache instance

Enables monitoring of cache evictions and rate limiting events.

src/charging-station/ocpp/auth/factories/AuthComponentFactory.ts
src/charging-station/ocpp/auth/interfaces/OCPPAuthService.ts
src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.ts
tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts

index 04399a00309d0255af012eb03564a2714f9e27ef..9e1370326581297755d826d13c4594e69aeda4a3 100644 (file)
@@ -9,6 +9,7 @@ import type {
 import type { AuthConfiguration } from '../types/AuthTypes.js'
 
 import { OCPPVersion } from '../../../../types/ocpp/OCPPVersion.js'
+import { InMemoryAuthCache } from '../cache/InMemoryAuthCache.js'
 import { AuthConfigValidator } from '../utils/ConfigValidator.js'
 
 /**
@@ -62,14 +63,20 @@ export class AuthComponentFactory {
   }
 
   /**
-   * Create authorization cache (delegated to service implementation)
+   * Create authorization cache with rate limiting
    * @param config - Authentication configuration
-   * @returns undefined (cache creation delegated to service)
+   * @returns AuthCache instance with configured TTL and rate limiting
    */
-  static createAuthCache (config: AuthConfiguration): undefined {
-    // Cache creation is delegated to OCPPAuthServiceImpl
-    // This method exists for API completeness
-    return undefined
+  static createAuthCache (config: AuthConfiguration): AuthCache {
+    return new InMemoryAuthCache({
+      defaultTtl: config.authorizationCacheLifetime ?? 3600,
+      maxEntries: config.maxCacheEntries ?? 1000,
+      rateLimit: {
+        enabled: true,
+        maxRequests: 10, // 10 requests per minute per identifier
+        windowMs: 60000, // 1 minute window
+      },
+    })
   }
 
   /**
index 939ba93b26b8ea98fbb07f05d5a6eb188e81f534..b18cd6a2afee6b560d7135b833ca6e2466ba0809 100644 (file)
@@ -88,6 +88,18 @@ export interface AuthStats {
   /** Local authorization usage rate */
   localUsageRate: number
 
+  /** Rate limiting statistics */
+  rateLimit?: {
+    /** Number of requests blocked by rate limiting */
+    blockedRequests: number
+
+    /** Number of identifiers currently rate-limited */
+    rateLimitedIdentifiers: number
+
+    /** Total rate limit checks performed */
+    totalChecks: number
+  }
+
   /** Remote authorization success rate */
   remoteSuccessRate: number
 
@@ -161,6 +173,9 @@ export interface AuthStrategy {
 }
 
 export interface CacheStats {
+  /** Number of entries evicted due to capacity limits */
+  evictions: number
+
   /** Expired entries count */
   expiredEntries: number
 
index 92d0976c2dba46a4e17dc1a509f84ec325ff3bd0..3e29665d67a00debf0fc21784c4c2be3d3c9803c 100644 (file)
@@ -335,8 +335,9 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
 
   /**
    * Get authentication statistics
+   * @returns Authentication statistics including cache and rate limiting metrics
    */
-  public getStats (): Promise<AuthStats> {
+  public async getStats (): Promise<AuthStats> {
     const avgResponseTime =
       this.metrics.totalRequests > 0
         ? this.metrics.totalResponseTime / this.metrics.totalRequests
@@ -353,16 +354,36 @@ export class OCPPAuthServiceImpl implements OCPPAuthService {
         ? this.metrics.successfulAuth / this.metrics.remoteAuthCount
         : 0
 
-    return Promise.resolve({
+    // Get rate limiting stats from cache via remote strategy
+    let rateLimitStats:
+      | undefined
+      | { blockedRequests: number; rateLimitedIdentifiers: number; totalChecks: number }
+    const remoteStrategy = this.strategies.get('remote')
+    if (remoteStrategy?.getStats) {
+      const strategyStats = await remoteStrategy.getStats()
+      if ('cache' in strategyStats) {
+        const cacheStats = strategyStats.cache as {
+          rateLimit?: {
+            blockedRequests: number
+            rateLimitedIdentifiers: number
+            totalChecks: number
+          }
+        }
+        rateLimitStats = cacheStats.rateLimit
+      }
+    }
+
+    return {
       avgResponseTime: Math.round(avgResponseTime * 100) / 100,
       cacheHitRate: Math.round(cacheHitRate * 10000) / 100,
       failedAuth: this.metrics.failedAuth,
       lastUpdated: this.metrics.lastReset,
       localUsageRate: Math.round(localUsageRate * 10000) / 100,
+      rateLimit: rateLimitStats,
       remoteSuccessRate: Math.round(remoteSuccessRate * 10000) / 100,
       successfulAuth: this.metrics.successfulAuth,
       totalRequests: this.metrics.totalRequests,
-    })
+    }
   }
 
   /**
index 4f7a47596827562511abddfb46002f3c8021b31f..8e3b47b5ecb408531ea0719a75cbddd55cd5d1c9 100644 (file)
@@ -61,7 +61,7 @@ await describe('AuthComponentFactory', async () => {
   })
 
   await describe('createAuthCache', async () => {
-    await it('should return undefined (delegated to service)', () => {
+    await it('should create InMemoryAuthCache instance', () => {
       const config: AuthConfiguration = {
         allowOfflineTxForUnknownId: false,
         authorizationCacheEnabled: true,
@@ -74,7 +74,11 @@ await describe('AuthComponentFactory', async () => {
 
       const result = AuthComponentFactory.createAuthCache(config)
 
-      expect(result).toBeUndefined()
+      expect(result).toBeDefined()
+      expect(result).toHaveProperty('get')
+      expect(result).toHaveProperty('set')
+      expect(result).toHaveProperty('clear')
+      expect(result).toHaveProperty('getStats')
     })
   })