From f38cc039ba9b93b8fbaef9ec16131137d395fa3e Mon Sep 17 00:00:00 2001 From: mahdibenromdhane Date: Thu, 30 Mar 2023 19:10:53 +0200 Subject: [PATCH 01/26] move handleForceSynchronizeUser to restful --- src/server/rest/v1/router/api/BillingRouter.ts | 7 +++++++ src/types/Server.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/src/server/rest/v1/router/api/BillingRouter.ts b/src/server/rest/v1/router/api/BillingRouter.ts index b9f5f8aa9c..b6f00ffd37 100644 --- a/src/server/rest/v1/router/api/BillingRouter.ts +++ b/src/server/rest/v1/router/api/BillingRouter.ts @@ -27,6 +27,7 @@ export default class BillingRouter { // this.buildRouteBillingCreatePaymentMethod(); - // No use case so far // this.buildRouteBillingUpdatePaymentMethod(); - // No use case so far this.buildRouteBillingDeletePaymentMethod(); + this.buildRouteForceSynchronizeUser(); this.buildRouteBillingPaymentMethodSetup(); this.buildRouteBillingPaymentMethodAttach(); this.buildRouteBillingPaymentMethodDetach(); @@ -96,6 +97,12 @@ export default class BillingRouter { }); } + private buildRouteForceSynchronizeUser(): void { + this.router.post(`/${RESTServerRoute.REST_FORCE_SYNCHRONIZE_USER}`, (req: Request, res: Response, next: NextFunction) => { + void RouterUtils.handleRestServerAction(BillingService.handleForceSynchronizeUser.bind(this), ServerAction.BILLING_FORCE_SYNCHRONIZE_USER, req, res, next); + }); + } + private buildRouteBillingPaymentMethodSetup(): void { this.router.post(`/${RESTServerRoute.REST_BILLING_PAYMENT_METHOD_SETUP}`, (req: Request, res: Response, next: NextFunction) => { // STRIPE prerequisite - ask for a setup intent first! diff --git a/src/types/Server.ts b/src/types/Server.ts index b70b6cf04d..bb46af8c90 100644 --- a/src/types/Server.ts +++ b/src/types/Server.ts @@ -704,6 +704,7 @@ export enum RESTServerRoute { REST_BILLING_SETTING = 'billing-setting', // GET and PUT REST_BILLING_CHECK = 'billing/check', + REST_FORCE_SYNCHRONIZE_USER = 'billing/force-synchronize-user', REST_BILLING_CLEAR_TEST_DATA = 'billing/clearTestData', REST_BILLING_TAXES = 'billing/taxes', From 43d7a4c532c27903d5dde06e7b9992abe4faf8c2 Mon Sep 17 00:00:00 2001 From: ClaudeROSSI Date: Fri, 31 Mar 2023 14:25:10 +0200 Subject: [PATCH 02/26] email - remove fallback --- .../configuration/configuration-save.json | 4 +- .../email/EMailNotificationTask.ts | 66 ++----------------- src/types/configuration/EmailConfiguration.ts | 12 +--- src/utils/Configuration.ts | 3 - 4 files changed, 7 insertions(+), 78 deletions(-) diff --git a/src/assets/storage/schemas/configuration/configuration-save.json b/src/assets/storage/schemas/configuration/configuration-save.json index 8f3ceb9bee..d9ab3209bc 100644 --- a/src/assets/storage/schemas/configuration/configuration-save.json +++ b/src/assets/storage/schemas/configuration/configuration-save.json @@ -588,9 +588,7 @@ } }, "required": [ - "disableBackup", - "smtp", - "smtpBackup" + "smtp" ] }, "Authorization": { diff --git a/src/notification/email/EMailNotificationTask.ts b/src/notification/email/EMailNotificationTask.ts index d32bf0eb06..c7cc61d204 100644 --- a/src/notification/email/EMailNotificationTask.ts +++ b/src/notification/email/EMailNotificationTask.ts @@ -14,7 +14,6 @@ import LoggingHelper from '../../utils/LoggingHelper'; import NotificationTask from '../NotificationTask'; import { ServerAction } from '../../types/Server'; import Tenant from '../../types/Tenant'; -import TenantStorage from '../../storage/mongodb/TenantStorage'; import User from '../../types/User'; import Utils from '../../utils/Utils'; import mjmlBuilder from './EmailMjmlBuilder'; @@ -25,7 +24,6 @@ const MODULE_NAME = 'EMailNotificationTask'; export default class EMailNotificationTask implements NotificationTask { private emailConfig: EmailConfiguration = Configuration.getEmailConfig(); private smtpMainClientInstance: SMTPClient; - private smtpBackupClientInstance: SMTPClient; public constructor() { // Connect to the SMTP servers @@ -39,16 +37,6 @@ export default class EMailNotificationTask implements NotificationTask { ssl: this.emailConfig.smtp.secure }); } - if (!this.emailConfig.disableBackup && !Utils.isUndefined(this.emailConfig.smtpBackup)) { - this.smtpBackupClientInstance = new SMTPClient({ - user: this.emailConfig.smtpBackup.user, - password: this.emailConfig.smtpBackup.password, - host: this.emailConfig.smtpBackup.host, - port: this.emailConfig.smtpBackup.port, - tls: this.emailConfig.smtpBackup.requireTLS, - ssl: this.emailConfig.smtpBackup.secure - }); - } } public async sendNewRegisteredUser(data: NewRegisteredUserNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise { @@ -239,7 +227,7 @@ export default class EMailNotificationTask implements NotificationTask { return await this.prepareAndSendEmail('user-create-password', data, user, tenant, severity); } - private async sendEmail(email: EmailNotificationMessage, data: any, tenant: Tenant, user: User, severity: NotificationSeverity, useSmtpClientBackup = false): Promise { + private async sendEmail(email: EmailNotificationMessage, data: any, tenant: Tenant, user: User, severity: NotificationSeverity): Promise { // Email configuration sanity checks if (!this.smtpMainClientInstance) { // No suitable main SMTP server configuration found to send the email @@ -256,25 +244,9 @@ export default class EMailNotificationTask implements NotificationTask { }); return; } - if (useSmtpClientBackup && !this.smtpBackupClientInstance) { - // No suitable backup SMTP server configuration found or activated to send the email - await Logging.logError({ - tenantID: tenant.id, - siteID: data?.siteID, - siteAreaID: data?.siteAreaID, - companyID: data?.companyID, - chargingStationID: data?.chargeBoxID, - action: ServerAction.EMAIL_NOTIFICATION, - module: MODULE_NAME, method: 'sendEmail', - message: 'No suitable backup SMTP server configuration found or activated to send email after an error on the main SMTP server', - actionOnUser: user - }); - return; - } // Create the message const messageToSend = new Message({ - from: !this.emailConfig.disableBackup && !Utils.isUndefined(this.emailConfig.smtpBackup) && useSmtpClientBackup ? - this.emailConfig.smtpBackup.from : this.emailConfig.smtp.from, + from: this.emailConfig.smtp.from, to: email.to, cc: email.cc, bcc: email.bccNeeded && email.bcc ? email.bcc : '', @@ -302,7 +274,7 @@ export default class EMailNotificationTask implements NotificationTask { } try { // Get the SMTP client - const smtpClient = this.getSMTPClient(useSmtpClientBackup); + const smtpClient = this.getSMTPClient(); // Send the message const messageSent: Message = await smtpClient.sendAsync(messageToSend); // Email sent successfully @@ -341,29 +313,6 @@ export default class EMailNotificationTask implements NotificationTask { error: error.stack, } }); - // Second try - let smtpFailed = true; - if (error instanceof SMTPError) { - const err: SMTPError = error; - switch (err.smtp) { - case 421: - case 432: - case 450: - case 451: - case 452: - case 454: - case 455: - case 510: - case 511: - case 550: - smtpFailed = false; - break; - } - } - // Use email backup? - if (smtpFailed && !useSmtpClientBackup) { - await this.sendEmail(email, data, tenant, user, severity, true); - } } } @@ -388,10 +337,8 @@ export default class EMailNotificationTask implements NotificationTask { subject: `e-Mobility - ${tenant.name} - ${title}`, html: html }; - // We may have a fallback - Not used anymore - const useSmtpClientFallback = false; // Send the email - await this.sendEmail(emailContent, context, tenant, recipient, severity, useSmtpClientFallback); + await this.sendEmail(emailContent, context, tenant, recipient, severity); return emailContent; } @@ -468,10 +415,7 @@ export default class EMailNotificationTask implements NotificationTask { }; } - private getSMTPClient(useSmtpClientBackup: boolean): SMTPClient { - if (useSmtpClientBackup) { - return this.smtpBackupClientInstance; - } + private getSMTPClient(): SMTPClient { return this.smtpMainClientInstance; } } diff --git a/src/types/configuration/EmailConfiguration.ts b/src/types/configuration/EmailConfiguration.ts index 91ab6b3384..7a61000bbb 100644 --- a/src/types/configuration/EmailConfiguration.ts +++ b/src/types/configuration/EmailConfiguration.ts @@ -8,15 +8,5 @@ export default interface EmailConfiguration { user: string; password: string; }; - disableBackup?: boolean; - troubleshootingMode?: boolean - smtpBackup?: { - from: string; - host: string; - port: number; - secure: boolean; - requireTLS: boolean; - user: string; - password: string; - }; + troubleshootingMode?: boolean // DEV only - set to true to dump the email HTML content } diff --git a/src/utils/Configuration.ts b/src/utils/Configuration.ts index 50927188dc..1e99874412 100644 --- a/src/utils/Configuration.ts +++ b/src/utils/Configuration.ts @@ -186,9 +186,6 @@ export default class Configuration { public static getEmailConfig(): EmailConfiguration { const email = Configuration.getConfig().Email; if (!Configuration.isUndefined('Email', email)) { - if (Configuration.isUndefined('Email.disableBackup', email.disableBackup)) { - email.disableBackup = false; - } return email; } } From 3de2e8e22c58390772e80e203848ed36000a28e1 Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Fri, 31 Mar 2023 17:16:24 +0200 Subject: [PATCH 03/26] added rate limiter --- package-lock.json | 11 +++++ package.json | 1 + .../services/JsonChargingStationService.ts | 40 +++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/package-lock.json b/package-lock.json index 95166665f3..c708d1686d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "pdfkit": "^0.13.0", "prom-client": "^14.1.0", "qrcode": "^1.5.1", + "rate-limiter-flexible": "^2.4.1", "rfc2047": "^4.0.1", "role-acl": "^4.5.4", "simple-odata-server": "^1.1.2", @@ -20010,6 +20011,11 @@ "node": ">= 0.6" } }, + "node_modules/rate-limiter-flexible": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.4.1.tgz", + "integrity": "sha512-dgH4T44TzKVO9CLArNto62hJOwlWJMLUjVVr/ii0uUzZXEXthDNr7/yefW5z/1vvHAfycc1tnuiYyNJ8CTRB3g==" + }, "node_modules/raw-body": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", @@ -40655,6 +40661,11 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, + "rate-limiter-flexible": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.4.1.tgz", + "integrity": "sha512-dgH4T44TzKVO9CLArNto62hJOwlWJMLUjVVr/ii0uUzZXEXthDNr7/yefW5z/1vvHAfycc1tnuiYyNJ8CTRB3g==" + }, "raw-body": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", diff --git a/package.json b/package.json index da5ddbcd45..71de9c2bf1 100644 --- a/package.json +++ b/package.json @@ -149,6 +149,7 @@ "pdfkit": "^0.13.0", "prom-client": "^14.1.0", "qrcode": "^1.5.1", + "rate-limiter-flexible": "^2.4.1", "rfc2047": "^4.0.1", "role-acl": "^4.5.4", "simple-odata-server": "^1.1.2", diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index f092f4b273..d50afcaf12 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -1,6 +1,9 @@ +import { RateLimiterMemory, RateLimiterRes } from 'rate-limiter-flexible'; import { OCPPAuthorizeRequest, OCPPAuthorizeResponse, OCPPBootNotificationRequest, OCPPBootNotificationResponse, OCPPDataTransferRequest, OCPPDataTransferResponse, OCPPDiagnosticsStatusNotificationRequest, OCPPDiagnosticsStatusNotificationResponse, OCPPFirmwareStatusNotificationRequest, OCPPFirmwareStatusNotificationResponse, OCPPHeartbeatRequest, OCPPHeartbeatResponse, OCPPMeterValuesRequest, OCPPMeterValuesResponse, OCPPStartTransactionRequest, OCPPStartTransactionResponse, OCPPStatusNotificationRequest, OCPPStatusNotificationResponse, OCPPStopTransactionRequest, OCPPStopTransactionResponse, OCPPVersion } from '../../../../types/ocpp/OCPPServer'; import { Command } from '../../../../types/ChargingStation'; +import { ServerAction } from '../../../../types/Server'; +import Constants from '../../../../utils/Constants'; import Logging from '../../../../utils/Logging'; import { OCPPHeader } from '../../../../types/ocpp/OCPPHeader'; import OCPPService from '../../services/OCPPService'; @@ -9,12 +12,25 @@ import global from '../../../../types/GlobalType'; const MODULE_NAME = 'JsonChargingStationService'; + export default class JsonChargingStationService { private chargingStationService: OCPPService; + private limiters = []; + private limiter = new RateLimiterMemory({ + points: 3, // Maximum number of points allowed per interval + duration: 60, // Interval duration in seconds + }); + + private limiterDdos = new RateLimiterMemory({ + points: 10, // Maximum number of points allowed per interval + duration: 60 * 60, // Interval duration in seconds + }); public constructor() { // Get the OCPP service this.chargingStationService = global.centralSystemJsonServer.getChargingStationService(OCPPVersion.VERSION_16); + this.limiters.push(this.limiter); + this.limiters.push(this.limiterDdos); } public async handleBootNotification(headers: OCPPHeader, payload: OCPPBootNotificationRequest): Promise { @@ -62,7 +78,13 @@ export default class JsonChargingStationService { return {}; } + public async handleStartTransaction(headers: OCPPHeader, payload: OCPPStartTransactionRequest): Promise { + + const { chargingStation, tenant } = headers; + const key = { connector: payload.connectorId, tenant: tenant.subdomain, chargingStation: chargingStation.id } ; + const keyString = `${key.connector}:${key.tenant}:${key.chargingStation}`; + await this.checkRateLimiters(keyString); const result: OCPPStartTransactionResponse = await this.handle(Command.START_TRANSACTION, headers, payload); return { transactionId: result.transactionId, @@ -80,6 +102,10 @@ export default class JsonChargingStationService { } public async handleStopTransaction(headers: OCPPHeader, payload: OCPPStopTransactionRequest): Promise { + const { chargingStation, tenant } = headers; + const key = { tenant: tenant.subdomain, chargingStation: chargingStation.id } ; + const keyString = `${key.tenant}:${key.chargingStation}`; + await this.checkRateLimiters(keyString); const result: OCPPStopTransactionResponse = await this.handle(Command.STOP_TRANSACTION, headers, payload); return { idTagInfo: { @@ -96,4 +122,18 @@ export default class JsonChargingStationService { throw error; } } + + private async checkRateLimiters(key: string) { + + for (let i = 0; i < this.limiters.length; i++) { + const limiter = this.limiters[i] as RateLimiterMemory; + const points = limiter.points; + const duration = limiter.duration; + try { + await this.limiters[i].consume(key); + } catch (error) { + throw new Error(`Rate limit exceeded: points : ${points} durations:${duration}`); + } + } + } } From 08586069e22cb39d493bad3d39aa328b2de732a9 Mon Sep 17 00:00:00 2001 From: mahdibenromdhane Date: Fri, 31 Mar 2023 18:31:07 +0200 Subject: [PATCH 04/26] use patch instead of post --- src/server/rest/v1/router/api/BillingRouter.ts | 3 ++- src/types/Server.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/server/rest/v1/router/api/BillingRouter.ts b/src/server/rest/v1/router/api/BillingRouter.ts index b6f00ffd37..3968fe5e76 100644 --- a/src/server/rest/v1/router/api/BillingRouter.ts +++ b/src/server/rest/v1/router/api/BillingRouter.ts @@ -98,7 +98,8 @@ export default class BillingRouter { } private buildRouteForceSynchronizeUser(): void { - this.router.post(`/${RESTServerRoute.REST_FORCE_SYNCHRONIZE_USER}`, (req: Request, res: Response, next: NextFunction) => { + this.router.patch(`/${RESTServerRoute.REST_BILLING_USER_SYNCHRONIZE}`, (req: Request, res: Response, next: NextFunction) => { + req.body.id = req.params.id; void RouterUtils.handleRestServerAction(BillingService.handleForceSynchronizeUser.bind(this), ServerAction.BILLING_FORCE_SYNCHRONIZE_USER, req, res, next); }); } diff --git a/src/types/Server.ts b/src/types/Server.ts index bb46af8c90..115f99d9c8 100644 --- a/src/types/Server.ts +++ b/src/types/Server.ts @@ -704,7 +704,7 @@ export enum RESTServerRoute { REST_BILLING_SETTING = 'billing-setting', // GET and PUT REST_BILLING_CHECK = 'billing/check', - REST_FORCE_SYNCHRONIZE_USER = 'billing/force-synchronize-user', + REST_BILLING_USER_SYNCHRONIZE = 'billing/users/:id/synchronize', REST_BILLING_CLEAR_TEST_DATA = 'billing/clearTestData', REST_BILLING_TAXES = 'billing/taxes', From 811b32584b3d194dd7a6fd63ac3338727a046774 Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Mon, 3 Apr 2023 15:52:19 +0200 Subject: [PATCH 05/26] added Shield config --- .../configuration/configuration-save.json | 25 +++++++++++++++++++ .../services/JsonChargingStationService.ts | 24 ++++++++++-------- src/types/configuration/Configuration.ts | 4 ++- .../configuration/RateLimiterConfiguration.ts | 10 ++++++++ src/utils/Configuration.ts | 8 ++++++ src/utils/Utils.ts | 16 ++++++++++++ 6 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 src/types/configuration/RateLimiterConfiguration.ts diff --git a/src/assets/storage/schemas/configuration/configuration-save.json b/src/assets/storage/schemas/configuration/configuration-save.json index 8f3ceb9bee..36754db5f9 100644 --- a/src/assets/storage/schemas/configuration/configuration-save.json +++ b/src/assets/storage/schemas/configuration/configuration-save.json @@ -659,6 +659,31 @@ "nbrTasksInParallel" ] }, + "Shield": { + "type": "object", + "properties": { + "active": { + "type": "boolean" + }, + "rateLimiters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "numberOfPoints": { + "type": "string" + }, + "numberOfSeconds": { + "type": "string" + } + } + } + } + } + }, "Scheduler": { "type": "object", "properties": { diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index d50afcaf12..ed31e94ef6 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -1,14 +1,18 @@ import { RateLimiterMemory, RateLimiterRes } from 'rate-limiter-flexible'; +import Bootstrap from '../../../../start'; +import ShieldConfiguration from '../../../../types/configuration/RateLimiterConfiguration'; import { OCPPAuthorizeRequest, OCPPAuthorizeResponse, OCPPBootNotificationRequest, OCPPBootNotificationResponse, OCPPDataTransferRequest, OCPPDataTransferResponse, OCPPDiagnosticsStatusNotificationRequest, OCPPDiagnosticsStatusNotificationResponse, OCPPFirmwareStatusNotificationRequest, OCPPFirmwareStatusNotificationResponse, OCPPHeartbeatRequest, OCPPHeartbeatResponse, OCPPMeterValuesRequest, OCPPMeterValuesResponse, OCPPStartTransactionRequest, OCPPStartTransactionResponse, OCPPStatusNotificationRequest, OCPPStatusNotificationResponse, OCPPStopTransactionRequest, OCPPStopTransactionResponse, OCPPVersion } from '../../../../types/ocpp/OCPPServer'; import { Command } from '../../../../types/ChargingStation'; import { ServerAction } from '../../../../types/Server'; +import Configuration from '../../../../utils/Configuration'; import Constants from '../../../../utils/Constants'; import Logging from '../../../../utils/Logging'; import { OCPPHeader } from '../../../../types/ocpp/OCPPHeader'; import OCPPService from '../../services/OCPPService'; import OCPPUtils from '../../utils/OCPPUtils'; import global from '../../../../types/GlobalType'; +import Utils from '../../../../utils/Utils'; const MODULE_NAME = 'JsonChargingStationService'; @@ -16,21 +20,19 @@ const MODULE_NAME = 'JsonChargingStationService'; export default class JsonChargingStationService { private chargingStationService: OCPPService; private limiters = []; - private limiter = new RateLimiterMemory({ - points: 3, // Maximum number of points allowed per interval - duration: 60, // Interval duration in seconds - }); - - private limiterDdos = new RateLimiterMemory({ - points: 10, // Maximum number of points allowed per interval - duration: 60 * 60, // Interval duration in seconds - }); public constructor() { // Get the OCPP service this.chargingStationService = global.centralSystemJsonServer.getChargingStationService(OCPPVersion.VERSION_16); - this.limiters.push(this.limiter); - this.limiters.push(this.limiterDdos); + const rateLimitersMap = Utils.getRateLimiters(); + const startStopTransactionLimiter = rateLimitersMap.get('StartStopTransaction'); + if (startStopTransactionLimiter) { + this.limiters.push(startStopTransactionLimiter); + } + const startStopTransactionDDosLimiter = rateLimitersMap.get('StartStopTransactionDDOS'); + if (startStopTransactionDDosLimiter) { + this.limiters.push(startStopTransactionDDosLimiter); + } } public async handleBootNotification(headers: OCPPHeader, payload: OCPPBootNotificationRequest): Promise { diff --git a/src/types/configuration/Configuration.ts b/src/types/configuration/Configuration.ts index 5b9549b763..3bf89ae00e 100644 --- a/src/types/configuration/Configuration.ts +++ b/src/types/configuration/Configuration.ts @@ -24,6 +24,7 @@ import OCPIServiceConfiguration from './OCPIServiceConfiguration'; import ODataServiceConfiguration from './ODataServiceConfiguration'; import OICPEndpointConfiguration from './OICPEndpointConfiguration'; import OICPServiceConfiguration from './OICPServiceConfiguration'; +import ShieldConfiguration from './RateLimiterConfiguration'; import SchedulerConfiguration from './SchedulerConfiguration'; import StorageConfiguration from './StorageConfiguration'; import TraceConfiguration from './TraceConfiguration'; @@ -50,6 +51,7 @@ export interface Configuration { ChargingStation: ChargingStationConfiguration; Locales?: LocalesConfiguration; Scheduler: SchedulerConfiguration; + Shield: ShieldConfiguration; AsyncTask: AsyncTaskConfiguration; Logging: LogConfiguration; HealthCheck?: HealthCheckConfiguration; @@ -62,4 +64,4 @@ export interface Configuration { Cache?: CacheConfiguration; } -export type ConfigurationSection = CryptoConfiguration|CentralSystemServerConfiguration|CentralSystemConfiguration|CentralSystemRestServiceConfiguration|CentralSystemFrontEndConfiguration|WSDLEndpointConfiguration|JsonEndpointConfiguration|OCPIEndpointConfiguration|OCPIServiceConfiguration|ODataServiceConfiguration|FirebaseConfiguration|EmailConfiguration|StorageConfiguration|NotificationConfiguration|AuthorizationConfiguration|ChargingStationConfiguration|SchedulerConfiguration|LocalesConfiguration|LogConfiguration|HealthCheckConfiguration|MigrationConfiguration|EVDatabaseConfiguration|ChargingStationTemplatesConfiguration|AxiosConfiguration|CacheConfiguration; +export type ConfigurationSection = CryptoConfiguration|CentralSystemServerConfiguration|CentralSystemConfiguration|CentralSystemRestServiceConfiguration|CentralSystemFrontEndConfiguration|WSDLEndpointConfiguration|JsonEndpointConfiguration|OCPIEndpointConfiguration|OCPIServiceConfiguration|ODataServiceConfiguration|FirebaseConfiguration|EmailConfiguration|StorageConfiguration|NotificationConfiguration|AuthorizationConfiguration|ChargingStationConfiguration|SchedulerConfiguration|LocalesConfiguration|LogConfiguration|HealthCheckConfiguration|MigrationConfiguration|EVDatabaseConfiguration|ChargingStationTemplatesConfiguration|AxiosConfiguration|CacheConfiguration|ShieldConfiguration; diff --git a/src/types/configuration/RateLimiterConfiguration.ts b/src/types/configuration/RateLimiterConfiguration.ts new file mode 100644 index 0000000000..f65eb7bd82 --- /dev/null +++ b/src/types/configuration/RateLimiterConfiguration.ts @@ -0,0 +1,10 @@ +export default interface ShieldConfiguration { + active: boolean; + rateLimiters: RateLimiterConfiguration[]; +} + +export interface RateLimiterConfiguration { + name: string; + numberOfPoints: number; + numberOfSeconds: number; +} diff --git a/src/utils/Configuration.ts b/src/utils/Configuration.ts index 50927188dc..d44c958c8d 100644 --- a/src/utils/Configuration.ts +++ b/src/utils/Configuration.ts @@ -9,6 +9,7 @@ import CentralSystemServerConfiguration from '../types/configuration/CentralSyst import ChargingStationConfiguration from '../types/configuration/ChargingStationConfiguration'; import { Configuration as ConfigurationData } from '../types/configuration/Configuration'; import ConfigurationValidatorStorage from '../storage/validator/ConfigurationValidatorStorage'; +import ShieldConfiguration from '../types/configuration/RateLimiterConfiguration'; import Constants from './Constants'; import CryptoConfiguration from '../types/configuration/CryptoConfiguration'; import EVDatabaseConfiguration from '../types/configuration/EVDatabaseConfiguration'; @@ -47,6 +48,13 @@ export default class Configuration { } } + public static getShieldConfig(): ShieldConfiguration { + const shield = Configuration.getConfig().Shield; + if (!Configuration.isUndefined('Shield', shield)) { + return shield; + } + } + public static getSchedulerConfig(): SchedulerConfiguration { const scheduler = Configuration.getConfig().Scheduler; if (!Configuration.isUndefined('Scheduler', scheduler)) { diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 28e453b1f2..a0a31c4b80 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,3 +1,4 @@ +import { RateLimiterMemory } from 'rate-limiter-flexible'; import { AnalyticsSettingsType, AssetSettingsType, BillingSettingsType, CarConnectorSettingsType, CryptoKeyProperties, PricingSettingsType, RefundSettingsType, RoamingSettingsType, SettingDBContent, SmartChargingContentType } from '../types/Setting'; import { Car, CarCatalog } from '../types/Car'; import ChargingStation, { ChargePoint, ChargingStationEndpoint, Connector, ConnectorCurrentLimitSource, CurrentType, Voltage } from '../types/ChargingStation'; @@ -1786,6 +1787,21 @@ export default class Utils { return this.hashCode(str) + 2147483647 + 1; } + + public static getRateLimiters(): Map { + + const shieldConfiguration = Configuration.getShieldConfig(); + const limiterMap = new Map(); + if (shieldConfiguration.active) { + shieldConfiguration.rateLimiters.forEach((rateLimiterConfig) => { + const limiter = new RateLimiterMemory({ points: rateLimiterConfig.numberOfPoints, duration: rateLimiterConfig.numberOfSeconds }); + limiterMap.set(rateLimiterConfig.name, limiter); + }); + } + return limiterMap; + } + + private static hashCode(s:string): number { let hash = 0,i = 0; const len = s.length; From 1ebf4d707fb351a463871804a1afd16c86231c4c Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Mon, 3 Apr 2023 16:03:25 +0200 Subject: [PATCH 06/26] Added support for empty Shield configuration --- src/utils/Utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index a0a31c4b80..e24c5fa552 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1792,7 +1792,7 @@ export default class Utils { const shieldConfiguration = Configuration.getShieldConfig(); const limiterMap = new Map(); - if (shieldConfiguration.active) { + if (shieldConfiguration?.active) { shieldConfiguration.rateLimiters.forEach((rateLimiterConfig) => { const limiter = new RateLimiterMemory({ points: rateLimiterConfig.numberOfPoints, duration: rateLimiterConfig.numberOfSeconds }); limiterMap.set(rateLimiterConfig.name, limiter); From 106c0f936057e5bcf93b2e3e7c7927d3171aee39 Mon Sep 17 00:00:00 2001 From: ClaudeROSSI Date: Mon, 3 Apr 2023 21:41:13 +0200 Subject: [PATCH 07/26] rate limiter - removed unused imports --- .../ocpp/json/services/JsonChargingStationService.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index ed31e94ef6..a8075c28ff 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -1,22 +1,16 @@ -import { RateLimiterMemory, RateLimiterRes } from 'rate-limiter-flexible'; -import Bootstrap from '../../../../start'; -import ShieldConfiguration from '../../../../types/configuration/RateLimiterConfiguration'; import { OCPPAuthorizeRequest, OCPPAuthorizeResponse, OCPPBootNotificationRequest, OCPPBootNotificationResponse, OCPPDataTransferRequest, OCPPDataTransferResponse, OCPPDiagnosticsStatusNotificationRequest, OCPPDiagnosticsStatusNotificationResponse, OCPPFirmwareStatusNotificationRequest, OCPPFirmwareStatusNotificationResponse, OCPPHeartbeatRequest, OCPPHeartbeatResponse, OCPPMeterValuesRequest, OCPPMeterValuesResponse, OCPPStartTransactionRequest, OCPPStartTransactionResponse, OCPPStatusNotificationRequest, OCPPStatusNotificationResponse, OCPPStopTransactionRequest, OCPPStopTransactionResponse, OCPPVersion } from '../../../../types/ocpp/OCPPServer'; import { Command } from '../../../../types/ChargingStation'; -import { ServerAction } from '../../../../types/Server'; -import Configuration from '../../../../utils/Configuration'; -import Constants from '../../../../utils/Constants'; import Logging from '../../../../utils/Logging'; import { OCPPHeader } from '../../../../types/ocpp/OCPPHeader'; import OCPPService from '../../services/OCPPService'; import OCPPUtils from '../../utils/OCPPUtils'; -import global from '../../../../types/GlobalType'; +import { RateLimiterMemory } from 'rate-limiter-flexible'; import Utils from '../../../../utils/Utils'; +import global from '../../../../types/GlobalType'; const MODULE_NAME = 'JsonChargingStationService'; - export default class JsonChargingStationService { private chargingStationService: OCPPService; private limiters = []; @@ -82,7 +76,6 @@ export default class JsonChargingStationService { public async handleStartTransaction(headers: OCPPHeader, payload: OCPPStartTransactionRequest): Promise { - const { chargingStation, tenant } = headers; const key = { connector: payload.connectorId, tenant: tenant.subdomain, chargingStation: chargingStation.id } ; const keyString = `${key.connector}:${key.tenant}:${key.chargingStation}`; @@ -126,7 +119,6 @@ export default class JsonChargingStationService { } private async checkRateLimiters(key: string) { - for (let i = 0; i < this.limiters.length; i++) { const limiter = this.limiters[i] as RateLimiterMemory; const points = limiter.points; From d705372ffcd76e517e56c19230670a874633393a Mon Sep 17 00:00:00 2001 From: ClaudeROSSI Date: Mon, 3 Apr 2023 21:43:31 +0200 Subject: [PATCH 08/26] rate limiter - BL --- src/utils/Utils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index e24c5fa552..cbe24fa4bd 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,4 +1,3 @@ -import { RateLimiterMemory } from 'rate-limiter-flexible'; import { AnalyticsSettingsType, AssetSettingsType, BillingSettingsType, CarConnectorSettingsType, CryptoKeyProperties, PricingSettingsType, RefundSettingsType, RoamingSettingsType, SettingDBContent, SmartChargingContentType } from '../types/Setting'; import { Car, CarCatalog } from '../types/Car'; import ChargingStation, { ChargePoint, ChargingStationEndpoint, Connector, ConnectorCurrentLimitSource, CurrentType, Voltage } from '../types/ChargingStation'; @@ -22,6 +21,7 @@ import LoggingHelper from './LoggingHelper'; import OCPPError from '../exception/OcppError'; import { Promise } from 'bluebird'; import QRCode from 'qrcode'; +import { RateLimiterMemory } from 'rate-limiter-flexible'; import { Request } from 'express'; import { ServerAction } from '../types/Server'; import SiteArea from '../types/SiteArea'; @@ -1789,7 +1789,6 @@ export default class Utils { public static getRateLimiters(): Map { - const shieldConfiguration = Configuration.getShieldConfig(); const limiterMap = new Map(); if (shieldConfiguration?.active) { @@ -1801,7 +1800,6 @@ export default class Utils { return limiterMap; } - private static hashCode(s:string): number { let hash = 0,i = 0; const len = s.length; From 6e29c158e312e7a8d7d49c93e218c4b05436f019 Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Tue, 4 Apr 2023 10:37:58 +0200 Subject: [PATCH 09/26] Added logs --- src/types/Server.ts | 3 ++- src/utils/Utils.ts | 59 +++++++++++++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/types/Server.ts b/src/types/Server.ts index b70b6cf04d..bd357163e5 100644 --- a/src/types/Server.ts +++ b/src/types/Server.ts @@ -494,7 +494,8 @@ export enum ServerAction { HTTP_RESPONSE = 'HttpResponse', HTTP_ERROR = 'HttpError', - EXPORT_TO_CSV = 'ExportToCSV' + EXPORT_TO_CSV = 'ExportToCSV', + SHIELD ='Shield' } // RESTful API diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index e24c5fa552..082ed842aa 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,4 +1,5 @@ import { RateLimiterMemory } from 'rate-limiter-flexible'; +import { Log } from '../types/Log'; import { AnalyticsSettingsType, AssetSettingsType, BillingSettingsType, CarConnectorSettingsType, CryptoKeyProperties, PricingSettingsType, RefundSettingsType, RoamingSettingsType, SettingDBContent, SmartChargingContentType } from '../types/Setting'; import { Car, CarCatalog } from '../types/Car'; import ChargingStation, { ChargePoint, ChargingStationEndpoint, Connector, ConnectorCurrentLimitSource, CurrentType, Voltage } from '../types/ChargingStation'; @@ -18,6 +19,7 @@ import BackendError from '../exception/BackendError'; import Configuration from './Configuration'; import Constants from './Constants'; import { Decimal } from 'decimal.js'; +import Logging from './Logging'; import LoggingHelper from './LoggingHelper'; import OCPPError from '../exception/OcppError'; import { Promise } from 'bluebird'; @@ -554,7 +556,7 @@ export default class Utils { // Charging Station if (connectorId === 0 && chargePointOfCS.power) { totalPower += chargePointOfCS.power; - // Connector + // Connector } else if (chargePointOfCS.connectorIDs.includes(connectorId) && chargePointOfCS.power) { if (chargePointOfCS.cannotChargeInParallel || chargePointOfCS.sharePowerToAllConnectors) { // Check Connector ID @@ -682,7 +684,7 @@ export default class Utils { // Charging Station if (connectorId === 0 && chargePointOfCS.currentType) { return chargePointOfCS.currentType; - // Connector + // Connector } else if (chargePointOfCS.connectorIDs.includes(connectorId) && chargePointOfCS.currentType) { // Check Connector ID const connector = Utils.getConnectorFromID(chargingStation, connectorId); @@ -937,7 +939,7 @@ export default class Utils { return `${baseSecureUrl}/${Utils.getOCPPServerVersionURLPath(ocppVersion)}/${tenant.id}/${token}`; } - public static alterBaseURL(tenant: Tenant, baseUrl: string) : string { + public static alterBaseURL(tenant: Tenant, baseUrl: string): string { if (tenant.cpmsDomainName) { const protocol = baseUrl.split(':').shift(); baseUrl = `${protocol}://${tenant.cpmsDomainName}`; @@ -1211,11 +1213,11 @@ export default class Utils { return false; } - public static getChargingStationEndpoint() : ChargingStationEndpoint { + public static getChargingStationEndpoint(): ChargingStationEndpoint { return ChargingStationEndpoint.AWS; } - public static async generateQrCode(data: string) :Promise { + public static async generateQrCode(data: string): Promise { return QRCode.toDataURL(data); } @@ -1388,13 +1390,13 @@ export default class Utils { } // REST API if (url.startsWith('/client/api/') || - url.startsWith('/v1/api/')) { + url.startsWith('/v1/api/')) { return PerformanceRecordGroup.REST_SECURED; } if (url.startsWith('/client/util/') || - url.startsWith('/client/auth/') || - url.startsWith('/v1/util/') || - url.startsWith('/v1/auth/')) { + url.startsWith('/client/auth/') || + url.startsWith('/v1/util/') || + url.startsWith('/v1/auth/')) { return PerformanceRecordGroup.REST_PUBLIC; } // OCPI @@ -1502,7 +1504,7 @@ export default class Utils { public static buildPerformanceRecord(params: { tenantSubdomain?: string; durationMs?: number; resSizeKb?: number; reqSizeKb?: number; - action: ServerAction|string; group?: PerformanceRecordGroup; httpUrl?: string; + action: ServerAction | string; group?: PerformanceRecordGroup; httpUrl?: string; httpMethod?: string; httpResponseCode?: number; egress?: boolean; chargingStationID?: string, userID?: string }): PerformanceRecord { const performanceRecord: PerformanceRecord = { @@ -1637,7 +1639,7 @@ export default class Utils { } // Push sub site area to parent children array siteAreaHashTable[siteAreaOfSite.parentSiteAreaID].childSiteAreas.push(siteAreaHashTable[siteAreaOfSite.id]); - // Root Site Area + // Root Site Area } else { // If no parent ID is defined push root site area to array rootSiteAreasOfSite.push(siteAreaHashTable[siteAreaOfSite.id]); @@ -1751,39 +1753,39 @@ export default class Utils { public static removeSensibeDataFromEntity(extraFilters: Record, entityData?: EntityData): void { // User data if (Utils.objectHasProperty(extraFilters, 'UserData') && - !Utils.isNullOrUndefined(extraFilters['UserData']) && extraFilters['UserData']) { + !Utils.isNullOrUndefined(extraFilters['UserData']) && extraFilters['UserData']) { Utils.deleteUserPropertiesFromEntity(entityData); } // Tag data if (Utils.objectHasProperty(extraFilters, 'TagData') && - !Utils.isNullOrUndefined(extraFilters['TagData']) && extraFilters['TagData']) { + !Utils.isNullOrUndefined(extraFilters['TagData']) && extraFilters['TagData']) { Utils.deleteTagPropertiesFromEntity(entityData); } // Car Catalog data if (Utils.objectHasProperty(extraFilters, 'CarCatalogData') && - !Utils.isNullOrUndefined(extraFilters['CarCatalogData']) && extraFilters['CarCatalogData']) { + !Utils.isNullOrUndefined(extraFilters['CarCatalogData']) && extraFilters['CarCatalogData']) { Utils.deleteCarCatalogPropertiesFromEntity(entityData); } // Car data if (Utils.objectHasProperty(extraFilters, 'CarData') && - !Utils.isNullOrUndefined(extraFilters['CarData']) && extraFilters['CarData']) { + !Utils.isNullOrUndefined(extraFilters['CarData']) && extraFilters['CarData']) { Utils.deleteCarPropertiesFromEntity(entityData); } // Billing data if (Utils.objectHasProperty(extraFilters, 'BillingData') && - !Utils.isNullOrUndefined(extraFilters['BillingData']) && extraFilters['BillingData']) { + !Utils.isNullOrUndefined(extraFilters['BillingData']) && extraFilters['BillingData']) { Utils.deleteBillingPropertiesFromEntity(entityData); } } - public static isMonitoringEnabled() : boolean { + public static isMonitoringEnabled(): boolean { if (((global.monitoringServer) && (process.env.K8S))) { return true; } return false; } - public static positiveHashCode(str :string):number { + public static positiveHashCode(str: string): number { return this.hashCode(str) + 2147483647 + 1; } @@ -1792,11 +1794,32 @@ export default class Utils { const shieldConfiguration = Configuration.getShieldConfig(); const limiterMap = new Map(); + let mess = ''; + if (shieldConfiguration?.active) { shieldConfiguration.rateLimiters.forEach((rateLimiterConfig) => { + void Logging.logDebug({ + tenantID: Constants.DEFAULT_TENANT_ID, + action: ServerAction.SHIELD, + module: MODULE_NAME, method: 'getRateLimiters', + message: `rate limiter with name:${rateLimiterConfig.name} numberOfPoints: ${rateLimiterConfig.numberOfPoints} duration:${rateLimiterConfig.numberOfSeconds} found` + } + ); const limiter = new RateLimiterMemory({ points: rateLimiterConfig.numberOfPoints, duration: rateLimiterConfig.numberOfSeconds }); limiterMap.set(rateLimiterConfig.name, limiter); }); + } else { + if (!shieldConfiguration) { + mess = 'Section shield not found'; + } else if (!shieldConfiguration.active) { + mess = 'Section shield is present but not active'; + } + void Logging.logDebug({ + tenantID: Constants.DEFAULT_TENANT_ID, + action: ServerAction.SHIELD, + module: MODULE_NAME, method: 'getRateLimiters', + message: mess + }); } return limiterMap; } From 8a79817169db6fd0f648e17073ede1b0203f8b85 Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Tue, 4 Apr 2023 11:28:56 +0200 Subject: [PATCH 10/26] Added logs at startup --- src/start.ts | 29 +++++++++++++++++++++++++++++ src/utils/Utils.ts | 21 --------------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/start.ts b/src/start.ts index 8b1b58011e..ef9de71161 100644 --- a/src/start.ts +++ b/src/start.ts @@ -1,4 +1,6 @@ +import { RateLimiterMemory } from 'rate-limiter-flexible'; import CentralSystemConfiguration, { CentralSystemImplementation } from './types/configuration/CentralSystemConfiguration'; +import ShieldConfiguration from './types/configuration/RateLimiterConfiguration'; import { ServerAction, ServerType } from './types/Server'; import AsyncTaskConfiguration from './types/configuration/AsyncTaskConfiguration'; @@ -83,6 +85,7 @@ export default class Bootstrap { Bootstrap.monitoringConfig = Configuration.getMonitoringConfig(); Bootstrap.cacheConfig = Configuration.getCacheConfig(); + // ------------------------------------------------------------------------- // Listen to promise failure // ------------------------------------------------------------------------- @@ -98,6 +101,32 @@ export default class Bootstrap { }); }); + const shieldConfiguration = Configuration.getShieldConfig(); + let mess = ''; + if (shieldConfiguration?.active) { + shieldConfiguration.rateLimiters.forEach((rateLimiterConfig) => { + void Logging.logDebug({ + tenantID: Constants.DEFAULT_TENANT_ID, + action: ServerAction.SHIELD, + module: MODULE_NAME, method: 'getRateLimiters', + message: `rate limiter with name:${rateLimiterConfig.name} numberOfPoints: ${rateLimiterConfig.numberOfPoints} duration:${rateLimiterConfig.numberOfSeconds} found` + }); + }); + } else { + if (!shieldConfiguration) { + mess = 'Section shield not found'; + } else if (!shieldConfiguration.active) { + mess = 'Section shield is present but not active'; + } + void Logging.logDebug({ + tenantID: Constants.DEFAULT_TENANT_ID, + action: ServerAction.SHIELD, + module: MODULE_NAME, method: 'getRateLimiters', + message: mess + }); + } + + // ------------------------------------------------------------------------- // Start Monitoring Server // ------------------------------------------------------------------------- diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 082ed842aa..39dd5ec806 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1794,32 +1794,11 @@ export default class Utils { const shieldConfiguration = Configuration.getShieldConfig(); const limiterMap = new Map(); - let mess = ''; - if (shieldConfiguration?.active) { shieldConfiguration.rateLimiters.forEach((rateLimiterConfig) => { - void Logging.logDebug({ - tenantID: Constants.DEFAULT_TENANT_ID, - action: ServerAction.SHIELD, - module: MODULE_NAME, method: 'getRateLimiters', - message: `rate limiter with name:${rateLimiterConfig.name} numberOfPoints: ${rateLimiterConfig.numberOfPoints} duration:${rateLimiterConfig.numberOfSeconds} found` - } - ); const limiter = new RateLimiterMemory({ points: rateLimiterConfig.numberOfPoints, duration: rateLimiterConfig.numberOfSeconds }); limiterMap.set(rateLimiterConfig.name, limiter); }); - } else { - if (!shieldConfiguration) { - mess = 'Section shield not found'; - } else if (!shieldConfiguration.active) { - mess = 'Section shield is present but not active'; - } - void Logging.logDebug({ - tenantID: Constants.DEFAULT_TENANT_ID, - action: ServerAction.SHIELD, - module: MODULE_NAME, method: 'getRateLimiters', - message: mess - }); } return limiterMap; } From 75ebed7c023bc83be9674f1589e5bd2494d9f4a0 Mon Sep 17 00:00:00 2001 From: ClaudeROSSI Date: Tue, 4 Apr 2023 13:44:17 +0200 Subject: [PATCH 11/26] rate limiters - logs --- src/start.ts | 12 +++--------- src/utils/Utils.ts | 2 -- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/start.ts b/src/start.ts index ef9de71161..930f018e60 100644 --- a/src/start.ts +++ b/src/start.ts @@ -1,6 +1,4 @@ -import { RateLimiterMemory } from 'rate-limiter-flexible'; import CentralSystemConfiguration, { CentralSystemImplementation } from './types/configuration/CentralSystemConfiguration'; -import ShieldConfiguration from './types/configuration/RateLimiterConfiguration'; import { ServerAction, ServerType } from './types/Server'; import AsyncTaskConfiguration from './types/configuration/AsyncTaskConfiguration'; @@ -85,7 +83,6 @@ export default class Bootstrap { Bootstrap.monitoringConfig = Configuration.getMonitoringConfig(); Bootstrap.cacheConfig = Configuration.getCacheConfig(); - // ------------------------------------------------------------------------- // Listen to promise failure // ------------------------------------------------------------------------- @@ -100,17 +97,16 @@ export default class Bootstrap { detailedMessages: (reason ? reason.stack : null) }); }); - const shieldConfiguration = Configuration.getShieldConfig(); let mess = ''; if (shieldConfiguration?.active) { shieldConfiguration.rateLimiters.forEach((rateLimiterConfig) => { - void Logging.logDebug({ + Logging.logDebug({ tenantID: Constants.DEFAULT_TENANT_ID, action: ServerAction.SHIELD, module: MODULE_NAME, method: 'getRateLimiters', message: `rate limiter with name:${rateLimiterConfig.name} numberOfPoints: ${rateLimiterConfig.numberOfPoints} duration:${rateLimiterConfig.numberOfSeconds} found` - }); + }).catch((error) => Logging.logPromiseError(error)); }); } else { if (!shieldConfiguration) { @@ -118,15 +114,13 @@ export default class Bootstrap { } else if (!shieldConfiguration.active) { mess = 'Section shield is present but not active'; } - void Logging.logDebug({ + await Logging.logDebug({ tenantID: Constants.DEFAULT_TENANT_ID, action: ServerAction.SHIELD, module: MODULE_NAME, method: 'getRateLimiters', message: mess }); } - - // ------------------------------------------------------------------------- // Start Monitoring Server // ------------------------------------------------------------------------- diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 9055663d7e..7413c5a114 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,4 +1,3 @@ -import { Log } from '../types/Log'; import { AnalyticsSettingsType, AssetSettingsType, BillingSettingsType, CarConnectorSettingsType, CryptoKeyProperties, PricingSettingsType, RefundSettingsType, RoamingSettingsType, SettingDBContent, SmartChargingContentType } from '../types/Setting'; import { Car, CarCatalog } from '../types/Car'; import ChargingStation, { ChargePoint, ChargingStationEndpoint, Connector, ConnectorCurrentLimitSource, CurrentType, Voltage } from '../types/ChargingStation'; @@ -18,7 +17,6 @@ import BackendError from '../exception/BackendError'; import Configuration from './Configuration'; import Constants from './Constants'; import { Decimal } from 'decimal.js'; -import Logging from './Logging'; import LoggingHelper from './LoggingHelper'; import OCPPError from '../exception/OcppError'; import { Promise } from 'bluebird'; From ff1df9b2219c090e2db2398f35ab02038538a06c Mon Sep 17 00:00:00 2001 From: OliveGerste Date: Tue, 4 Apr 2023 14:18:08 +0200 Subject: [PATCH 12/26] Made priority parameters nullable --- src/assets/schemas/common/common.json | 6 ++++-- .../chargingstation-action-transaction-start.json | 11 +++++++---- .../sap-smart-charging/SapSmartChargingIntegration.ts | 2 +- src/storage/mongodb/TransactionStorage.ts | 4 ++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/assets/schemas/common/common.json b/src/assets/schemas/common/common.json index 73c53d310b..8bf71e4061 100644 --- a/src/assets/schemas/common/common.json +++ b/src/assets/schemas/common/common.json @@ -136,11 +136,13 @@ }, "carStateOfCharge": { "type": "integer", - "sanitize": "mongo" + "sanitize": "mongo", + "nullable": true }, "targetStateOfCharge": { "type": "integer", - "sanitize": "mongo" + "sanitize": "mongo", + "nullable": true } } }, diff --git a/src/assets/server/rest/v1/schemas/chargingstation/chargingstation-action-transaction-start.json b/src/assets/server/rest/v1/schemas/chargingstation/chargingstation-action-transaction-start.json index ad8a3d8363..150f3832cd 100644 --- a/src/assets/server/rest/v1/schemas/chargingstation/chargingstation-action-transaction-start.json +++ b/src/assets/server/rest/v1/schemas/chargingstation/chargingstation-action-transaction-start.json @@ -12,8 +12,9 @@ "carStateOfCharge": { "type": "integer", "sanitize": "mongo", - "minimum": 1, - "maximum": 100 + "minimum": 0, + "maximum": 100, + "nullable": true }, "carOdometer": { "type": "integer", @@ -27,13 +28,15 @@ "type": "string", "format": "date-time", "customType": "date", - "sanitize": "mongo" + "sanitize": "mongo", + "nullable": true }, "targetStateOfCharge": { "type": "integer", "sanitize": "mongo", "minimum": 1, - "maximum": 100 + "maximum": 100, + "nullable": true }, "args": { "type": "object", diff --git a/src/integration/smart-charging/sap-smart-charging/SapSmartChargingIntegration.ts b/src/integration/smart-charging/sap-smart-charging/SapSmartChargingIntegration.ts index ed7b689e3c..4aa2e7fef3 100644 --- a/src/integration/smart-charging/sap-smart-charging/SapSmartChargingIntegration.ts +++ b/src/integration/smart-charging/sap-smart-charging/SapSmartChargingIntegration.ts @@ -598,7 +598,7 @@ export default class SapSmartChargingIntegration extends SmartChargingIntegratio if (transaction.stateOfCharge > 0) { customCar.startCapacity = (transaction.stateOfCharge / 100) * customCar.maxCapacity; // Check if manual state of charge is available - } else if (transaction.carStateOfCharge > 0) { + } else if (transaction.carStateOfCharge >= 0) { customCar.startCapacity = (transaction.carStateOfCharge / 100) * customCar.maxCapacity; // Handle if no state of charge is available } else { diff --git a/src/storage/mongodb/TransactionStorage.ts b/src/storage/mongodb/TransactionStorage.ts index b8ff2581fa..7394ccc20f 100644 --- a/src/storage/mongodb/TransactionStorage.ts +++ b/src/storage/mongodb/TransactionStorage.ts @@ -57,10 +57,10 @@ export default class TransactionStorage { tagID: transactionToSave.tagID, carID: transactionToSave.carID ? DatabaseUtils.convertToObjectID(transactionToSave.carID) : null, carCatalogID: transactionToSave.carCatalogID ? Utils.convertToInt(transactionToSave.carCatalogID) : null, - carStateOfCharge: Utils.convertToInt(transactionToSave.carStateOfCharge), + carStateOfCharge: transactionToSave.carStateOfCharge ? Utils.convertToInt(transactionToSave.carStateOfCharge) : null, carOdometer: Utils.convertToInt(transactionToSave.carOdometer), departureTime: Utils.convertToDate(transactionToSave.departureTime), - targetStateOfCharge: Utils.convertToInt(transactionToSave.targetStateOfCharge), + targetStateOfCharge: transactionToSave.targetStateOfCharge ? Utils.convertToInt(transactionToSave.targetStateOfCharge) : null, userID: DatabaseUtils.convertToObjectID(transactionToSave.userID), chargeBoxID: transactionToSave.chargeBoxID, meterStart: Utils.convertToInt(transactionToSave.meterStart), From b340e2ab734512a24a890c900c63747352452e71 Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Tue, 4 Apr 2023 15:31:36 +0200 Subject: [PATCH 13/26] Added boot notif rate limiters --- .../services/JsonChargingStationService.ts | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index a8075c28ff..e8d3f89073 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -13,7 +13,9 @@ const MODULE_NAME = 'JsonChargingStationService'; export default class JsonChargingStationService { private chargingStationService: OCPPService; - private limiters = []; + private limitersStartStopTransaction = new Array(); + private limitersBootNotifs = new Array(); + public constructor() { // Get the OCPP service @@ -21,15 +23,27 @@ export default class JsonChargingStationService { const rateLimitersMap = Utils.getRateLimiters(); const startStopTransactionLimiter = rateLimitersMap.get('StartStopTransaction'); if (startStopTransactionLimiter) { - this.limiters.push(startStopTransactionLimiter); + this.limitersStartStopTransaction.push(startStopTransactionLimiter); + } + const startStopTransactionLimiterPerDay = rateLimitersMap.get('StartStopTransactionPerDay'); + if (startStopTransactionLimiterPerDay) { + this.limitersStartStopTransaction.push(startStopTransactionLimiterPerDay); + } + const bootNotifRateLimiter = rateLimitersMap.get('BootNotifRateLimiter'); + if (bootNotifRateLimiter) { + this.limitersBootNotifs.push(bootNotifRateLimiter); } - const startStopTransactionDDosLimiter = rateLimitersMap.get('StartStopTransactionDDOS'); - if (startStopTransactionDDosLimiter) { - this.limiters.push(startStopTransactionDDosLimiter); + const bootNotifRateLimiterPerDay = rateLimitersMap.get('BootNotifRateLimiterPerDay'); + if (bootNotifRateLimiterPerDay) { + this.limitersBootNotifs.push(bootNotifRateLimiterPerDay); } } public async handleBootNotification(headers: OCPPHeader, payload: OCPPBootNotificationRequest): Promise { + const { chargingStation, tenant } = headers; + const key = { tenant: tenant.subdomain, chargingStation: chargingStation.id } ; + const keyString = `${key.tenant}:${key.chargingStation}`; + await this.checkRateLimiters(this.limitersBootNotifs, keyString); const result = await this.handle(Command.BOOT_NOTIFICATION, headers, payload); return { currentTime: result.currentTime, @@ -79,7 +93,7 @@ export default class JsonChargingStationService { const { chargingStation, tenant } = headers; const key = { connector: payload.connectorId, tenant: tenant.subdomain, chargingStation: chargingStation.id } ; const keyString = `${key.connector}:${key.tenant}:${key.chargingStation}`; - await this.checkRateLimiters(keyString); + await this.checkRateLimiters(this.limitersStartStopTransaction, keyString); const result: OCPPStartTransactionResponse = await this.handle(Command.START_TRANSACTION, headers, payload); return { transactionId: result.transactionId, @@ -100,7 +114,7 @@ export default class JsonChargingStationService { const { chargingStation, tenant } = headers; const key = { tenant: tenant.subdomain, chargingStation: chargingStation.id } ; const keyString = `${key.tenant}:${key.chargingStation}`; - await this.checkRateLimiters(keyString); + await this.checkRateLimiters(this.limitersStartStopTransaction, keyString); const result: OCPPStopTransactionResponse = await this.handle(Command.STOP_TRANSACTION, headers, payload); return { idTagInfo: { @@ -118,13 +132,13 @@ export default class JsonChargingStationService { } } - private async checkRateLimiters(key: string) { - for (let i = 0; i < this.limiters.length; i++) { - const limiter = this.limiters[i] as RateLimiterMemory; + private async checkRateLimiters(limiters: Array, key: string) { + for (let i = 0; i< limiters.length; i++) { + const limiter = limiters[i]; const points = limiter.points; const duration = limiter.duration; try { - await this.limiters[i].consume(key); + await this.limitersStartStopTransaction[i].consume(key); } catch (error) { throw new Error(`Rate limit exceeded: points : ${points} durations:${duration}`); } From 61a6bcb17b0e59db00f250a1b5c22f23319b37cd Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Tue, 4 Apr 2023 15:43:25 +0200 Subject: [PATCH 14/26] Added boot notif rate limiters --- .../services/JsonChargingStationService.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index e8d3f89073..3de20c0304 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -21,19 +21,19 @@ export default class JsonChargingStationService { // Get the OCPP service this.chargingStationService = global.centralSystemJsonServer.getChargingStationService(OCPPVersion.VERSION_16); const rateLimitersMap = Utils.getRateLimiters(); - const startStopTransactionLimiter = rateLimitersMap.get('StartStopTransaction'); - if (startStopTransactionLimiter) { - this.limitersStartStopTransaction.push(startStopTransactionLimiter); + const startStopTransactionLimiterPerMin = rateLimitersMap.get('StartStopTransactionPerMin'); + if (startStopTransactionLimiterPerMin) { + this.limitersStartStopTransaction.push(startStopTransactionLimiterPerMin); } - const startStopTransactionLimiterPerDay = rateLimitersMap.get('StartStopTransactionPerDay'); - if (startStopTransactionLimiterPerDay) { - this.limitersStartStopTransaction.push(startStopTransactionLimiterPerDay); + const startStopTransactionLimiterPerHour = rateLimitersMap.get('StartStopTransactionPerHour'); + if (startStopTransactionLimiterPerHour) { + this.limitersStartStopTransaction.push(startStopTransactionLimiterPerHour); } - const bootNotifRateLimiter = rateLimitersMap.get('BootNotifRateLimiter'); - if (bootNotifRateLimiter) { - this.limitersBootNotifs.push(bootNotifRateLimiter); + const bootNotifRateLimiterPerHour = rateLimitersMap.get('BootNotifPerHour'); + if (bootNotifRateLimiterPerHour) { + this.limitersBootNotifs.push(bootNotifRateLimiterPerHour); } - const bootNotifRateLimiterPerDay = rateLimitersMap.get('BootNotifRateLimiterPerDay'); + const bootNotifRateLimiterPerDay = rateLimitersMap.get('BootNotifPerDay'); if (bootNotifRateLimiterPerDay) { this.limitersBootNotifs.push(bootNotifRateLimiterPerDay); } From 1201dc20ee4248ff4ab07c517c394f57f4de7fbe Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Tue, 4 Apr 2023 16:13:37 +0200 Subject: [PATCH 15/26] fix in boot notif rate limiters --- src/server/ocpp/json/services/JsonChargingStationService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index 3de20c0304..5b750a1dcf 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -40,8 +40,8 @@ export default class JsonChargingStationService { } public async handleBootNotification(headers: OCPPHeader, payload: OCPPBootNotificationRequest): Promise { - const { chargingStation, tenant } = headers; - const key = { tenant: tenant.subdomain, chargingStation: chargingStation.id } ; + const { chargeBoxIdentity, tenant } = headers; + const key = { tenant: tenant.subdomain, chargingStation: chargeBoxIdentity} ; const keyString = `${key.tenant}:${key.chargingStation}`; await this.checkRateLimiters(this.limitersBootNotifs, keyString); const result = await this.handle(Command.BOOT_NOTIFICATION, headers, payload); From b06338a9187ff24b86a4b803d35369eee9dc5d9d Mon Sep 17 00:00:00 2001 From: ClaudeROSSI Date: Tue, 4 Apr 2023 17:32:04 +0200 Subject: [PATCH 16/26] prometheus - add counter for emails --- src/storage/mongodb/PerformanceStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage/mongodb/PerformanceStorage.ts b/src/storage/mongodb/PerformanceStorage.ts index 3954625b7e..7734ce55e0 100644 --- a/src/storage/mongodb/PerformanceStorage.ts +++ b/src/storage/mongodb/PerformanceStorage.ts @@ -79,7 +79,7 @@ export default class PerformanceStorage { const labels = Object.keys(labelValues); try { if (performanceRecord.durationMs) { - if (performanceRecord.group === PerformanceRecordGroup.MONGO_DB) { + if (performanceRecord.group === PerformanceRecordGroup.MONGO_DB || performanceRecord.group === PerformanceRecordGroup.NOTIFICATION) { const durationMetric = global.monitoringServer.getCountAvgClearableMetric(grafanaGroup, 'DurationMs', hashCode, 'duration in milliseconds', 'number of invocations', labels); durationMetric.setValue(labelValues, performanceRecord.durationMs); } else { From 8896ba0a338fd3df15498a2a81afdfb2a6e76d73 Mon Sep 17 00:00:00 2001 From: ClaudeROSSI Date: Tue, 4 Apr 2023 17:48:13 +0200 Subject: [PATCH 17/26] version 2.7.7 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c708d1686d..db5a4d809c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ev-server", - "version": "2.7.6", + "version": "2.7.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ev-server", - "version": "2.7.6", + "version": "2.7.7", "license": "Apache-2.0", "dependencies": { "ajv": "^8.11.0", diff --git a/package.json b/package.json index 71de9c2bf1..740487695b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ev-server", - "version": "2.7.6", + "version": "2.7.7", "engines": { "node": "16.x.x", "npm": "8.x.x" From b3edb0388488b7128cb993b7e8f4b173a3898dd2 Mon Sep 17 00:00:00 2001 From: OliveGerste Date: Wed, 5 Apr 2023 08:53:43 +0200 Subject: [PATCH 18/26] Accept 0 for carStateOfCharge --- src/storage/mongodb/TransactionStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage/mongodb/TransactionStorage.ts b/src/storage/mongodb/TransactionStorage.ts index 7394ccc20f..78c59978c3 100644 --- a/src/storage/mongodb/TransactionStorage.ts +++ b/src/storage/mongodb/TransactionStorage.ts @@ -57,7 +57,7 @@ export default class TransactionStorage { tagID: transactionToSave.tagID, carID: transactionToSave.carID ? DatabaseUtils.convertToObjectID(transactionToSave.carID) : null, carCatalogID: transactionToSave.carCatalogID ? Utils.convertToInt(transactionToSave.carCatalogID) : null, - carStateOfCharge: transactionToSave.carStateOfCharge ? Utils.convertToInt(transactionToSave.carStateOfCharge) : null, + carStateOfCharge: !Utils.isNullOrUndefined(transactionToSave.carStateOfCharge) ? Utils.convertToInt(transactionToSave.carStateOfCharge) : null, carOdometer: Utils.convertToInt(transactionToSave.carOdometer), departureTime: Utils.convertToDate(transactionToSave.departureTime), targetStateOfCharge: transactionToSave.targetStateOfCharge ? Utils.convertToInt(transactionToSave.targetStateOfCharge) : null, From 569517c5670a049f5461e0f3f750150fad5abfa3 Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Thu, 6 Apr 2023 11:46:20 +0200 Subject: [PATCH 19/26] Added log first time the limit is reached in the window --- .../services/JsonChargingStationService.ts | 71 +++++++++++++------ src/types/Server.ts | 3 +- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index 5b750a1dcf..92ee9fb05a 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -1,49 +1,55 @@ import { OCPPAuthorizeRequest, OCPPAuthorizeResponse, OCPPBootNotificationRequest, OCPPBootNotificationResponse, OCPPDataTransferRequest, OCPPDataTransferResponse, OCPPDiagnosticsStatusNotificationRequest, OCPPDiagnosticsStatusNotificationResponse, OCPPFirmwareStatusNotificationRequest, OCPPFirmwareStatusNotificationResponse, OCPPHeartbeatRequest, OCPPHeartbeatResponse, OCPPMeterValuesRequest, OCPPMeterValuesResponse, OCPPStartTransactionRequest, OCPPStartTransactionResponse, OCPPStatusNotificationRequest, OCPPStatusNotificationResponse, OCPPStopTransactionRequest, OCPPStopTransactionResponse, OCPPVersion } from '../../../../types/ocpp/OCPPServer'; import { Command } from '../../../../types/ChargingStation'; +import { ServerAction } from '../../../../types/Server'; +import Tenant from '../../../../types/Tenant'; +import Constants from '../../../../utils/Constants'; import Logging from '../../../../utils/Logging'; import { OCPPHeader } from '../../../../types/ocpp/OCPPHeader'; import OCPPService from '../../services/OCPPService'; import OCPPUtils from '../../utils/OCPPUtils'; -import { RateLimiterMemory } from 'rate-limiter-flexible'; +import { RateLimiterMemory, RateLimiterRes } from 'rate-limiter-flexible'; import Utils from '../../../../utils/Utils'; import global from '../../../../types/GlobalType'; const MODULE_NAME = 'JsonChargingStationService'; - export default class JsonChargingStationService { private chargingStationService: OCPPService; - private limitersStartStopTransaction = new Array(); - private limitersBootNotifs = new Array(); - + private limitersStartStopTransaction = new Array(); + private limitersBootNotifs = new Array(); public constructor() { // Get the OCPP service this.chargingStationService = global.centralSystemJsonServer.getChargingStationService(OCPPVersion.VERSION_16); const rateLimitersMap = Utils.getRateLimiters(); - const startStopTransactionLimiterPerMin = rateLimitersMap.get('StartStopTransactionPerMin'); + let name : string; + name = 'StartStopTransactionPerMin'; + const startStopTransactionLimiterPerMin = rateLimitersMap.get(name); if (startStopTransactionLimiterPerMin) { - this.limitersStartStopTransaction.push(startStopTransactionLimiterPerMin); + this.limitersStartStopTransaction.push({ name:name, limiter:startStopTransactionLimiterPerMin }); } - const startStopTransactionLimiterPerHour = rateLimitersMap.get('StartStopTransactionPerHour'); + name = 'StartStopTransactionPerHour'; + const startStopTransactionLimiterPerHour = rateLimitersMap.get(name); if (startStopTransactionLimiterPerHour) { - this.limitersStartStopTransaction.push(startStopTransactionLimiterPerHour); + this.limitersStartStopTransaction.push({ name:name, limiter:startStopTransactionLimiterPerHour }); } - const bootNotifRateLimiterPerHour = rateLimitersMap.get('BootNotifPerHour'); + name = 'BootNotifPerHour'; + const bootNotifRateLimiterPerHour = rateLimitersMap.get(name); if (bootNotifRateLimiterPerHour) { - this.limitersBootNotifs.push(bootNotifRateLimiterPerHour); + this.limitersBootNotifs.push({ name:name, limiter: bootNotifRateLimiterPerHour }); } - const bootNotifRateLimiterPerDay = rateLimitersMap.get('BootNotifPerDay'); + name = 'BootNotifPerDay'; + const bootNotifRateLimiterPerDay = rateLimitersMap.get(name); if (bootNotifRateLimiterPerDay) { - this.limitersBootNotifs.push(bootNotifRateLimiterPerDay); + this.limitersBootNotifs.push({ name: name, limiter: bootNotifRateLimiterPerDay }); } } public async handleBootNotification(headers: OCPPHeader, payload: OCPPBootNotificationRequest): Promise { const { chargeBoxIdentity, tenant } = headers; - const key = { tenant: tenant.subdomain, chargingStation: chargeBoxIdentity} ; + const key = { tenant: tenant.subdomain, chargingStation: chargeBoxIdentity } ; const keyString = `${key.tenant}:${key.chargingStation}`; - await this.checkRateLimiters(this.limitersBootNotifs, keyString); + await this.checkRateLimiters(tenant, this.limitersBootNotifs, keyString); const result = await this.handle(Command.BOOT_NOTIFICATION, headers, payload); return { currentTime: result.currentTime, @@ -93,7 +99,7 @@ export default class JsonChargingStationService { const { chargingStation, tenant } = headers; const key = { connector: payload.connectorId, tenant: tenant.subdomain, chargingStation: chargingStation.id } ; const keyString = `${key.connector}:${key.tenant}:${key.chargingStation}`; - await this.checkRateLimiters(this.limitersStartStopTransaction, keyString); + await this.checkRateLimiters(tenant, this.limitersStartStopTransaction, keyString); const result: OCPPStartTransactionResponse = await this.handle(Command.START_TRANSACTION, headers, payload); return { transactionId: result.transactionId, @@ -114,7 +120,7 @@ export default class JsonChargingStationService { const { chargingStation, tenant } = headers; const key = { tenant: tenant.subdomain, chargingStation: chargingStation.id } ; const keyString = `${key.tenant}:${key.chargingStation}`; - await this.checkRateLimiters(this.limitersStartStopTransaction, keyString); + await this.checkRateLimiters(tenant,this.limitersStartStopTransaction, keyString); const result: OCPPStopTransactionResponse = await this.handle(Command.STOP_TRANSACTION, headers, payload); return { idTagInfo: { @@ -132,16 +138,35 @@ export default class JsonChargingStationService { } } - private async checkRateLimiters(limiters: Array, key: string) { - for (let i = 0; i< limiters.length; i++) { - const limiter = limiters[i]; - const points = limiter.points; + private async checkRateLimiters(tenant:Tenant,limiters: Array, key: string) { + for (let i = 0; i < limiters.length; i++) { + const limiter = limiters[i].limiter; + const limiterName = limiters[i].name; + const points : number = limiter.points; + let pointsplusone = points ; + pointsplusone++; + const rateLimiter = this.limitersStartStopTransaction[i]; const duration = limiter.duration; try { - await this.limitersStartStopTransaction[i].consume(key); + const res = await limiter.consume(key); } catch (error) { - throw new Error(`Rate limit exceeded: points : ${points} durations:${duration}`); + const rateLimiterRes = error as RateLimiterRes; + if (rateLimiterRes.consumedPoints === pointsplusone) { + await Logging.logError({ + tenantID: tenant.id, + action: ServerAction.RATE_LIMITER, + module: MODULE_NAME, method: 'checkRateLimiters', + message: `RateLimiter ${limiterName} First time rate limit is reached for key: ${key} RateLimiterPoints : ${points} RateLimiterDurations:${duration}` + }); + } + throw new Error(`RateLimiter : ${limiterName} Rate limit exceeded: points : ${points} durations:${duration}`); } } } } + +export interface RateLimiterMemoryWithName +{ + name : string; + limiter : RateLimiterMemory +} diff --git a/src/types/Server.ts b/src/types/Server.ts index e7204f2c47..8dfd19aff6 100644 --- a/src/types/Server.ts +++ b/src/types/Server.ts @@ -495,7 +495,8 @@ export enum ServerAction { HTTP_ERROR = 'HttpError', EXPORT_TO_CSV = 'ExportToCSV', - SHIELD ='Shield' + SHIELD ='Shield', + RATE_LIMITER = 'RateLimiter' } // RESTful API From 8f33042dde44fe83abd4119c76d88995a3e68ed6 Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Thu, 6 Apr 2023 15:22:34 +0200 Subject: [PATCH 20/26] Changed log format to detailed message --- src/server/ocpp/json/services/JsonChargingStationService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index 92ee9fb05a..f8c816cd5b 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -156,7 +156,8 @@ export default class JsonChargingStationService { tenantID: tenant.id, action: ServerAction.RATE_LIMITER, module: MODULE_NAME, method: 'checkRateLimiters', - message: `RateLimiter ${limiterName} First time rate limit is reached for key: ${key} RateLimiterPoints : ${points} RateLimiterDurations:${duration}` + message: `RateLimiter ${limiterName} reached first time in windows`, + detailedMessages : `key: ${key} RateLimiterPoints : ${points} RateLimiterDurations:${duration}` }); } throw new Error(`RateLimiter : ${limiterName} Rate limit exceeded: points : ${points} durations:${duration}`); From 207a828a687eece8d5633b88f344f153ec2cc855 Mon Sep 17 00:00:00 2001 From: ClaudeROSSI Date: Thu, 6 Apr 2023 16:37:11 +0200 Subject: [PATCH 21/26] ooops - wrong comment --- src/types/configuration/EmailConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/configuration/EmailConfiguration.ts b/src/types/configuration/EmailConfiguration.ts index 7a61000bbb..2996ddeee9 100644 --- a/src/types/configuration/EmailConfiguration.ts +++ b/src/types/configuration/EmailConfiguration.ts @@ -8,5 +8,5 @@ export default interface EmailConfiguration { user: string; password: string; }; - troubleshootingMode?: boolean // DEV only - set to true to dump the email HTML content + troubleshootingMode?: boolean // DEV only - set to true to send real mails when running tests } From eac4e02ff022640caccbf05df0decb24234f0af5 Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Fri, 7 Apr 2023 10:04:05 +0200 Subject: [PATCH 22/26] changed init shield sequence --- src/start.ts | 52 ++++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/start.ts b/src/start.ts index 930f018e60..e628a26ee9 100644 --- a/src/start.ts +++ b/src/start.ts @@ -97,30 +97,6 @@ export default class Bootstrap { detailedMessages: (reason ? reason.stack : null) }); }); - const shieldConfiguration = Configuration.getShieldConfig(); - let mess = ''; - if (shieldConfiguration?.active) { - shieldConfiguration.rateLimiters.forEach((rateLimiterConfig) => { - Logging.logDebug({ - tenantID: Constants.DEFAULT_TENANT_ID, - action: ServerAction.SHIELD, - module: MODULE_NAME, method: 'getRateLimiters', - message: `rate limiter with name:${rateLimiterConfig.name} numberOfPoints: ${rateLimiterConfig.numberOfPoints} duration:${rateLimiterConfig.numberOfSeconds} found` - }).catch((error) => Logging.logPromiseError(error)); - }); - } else { - if (!shieldConfiguration) { - mess = 'Section shield not found'; - } else if (!shieldConfiguration.active) { - mess = 'Section shield is present but not active'; - } - await Logging.logDebug({ - tenantID: Constants.DEFAULT_TENANT_ID, - action: ServerAction.SHIELD, - module: MODULE_NAME, method: 'getRateLimiters', - message: mess - }); - } // ------------------------------------------------------------------------- // Start Monitoring Server // ------------------------------------------------------------------------- @@ -167,6 +143,7 @@ export default class Bootstrap { // Connect to the Database await Bootstrap.database.start(); await this.logDuration(startTimeMillis, 'Connected to the Database successfully'); + await this.initShield(); // ------------------------------------------------------------------------- // Tenant cache for subdomains only @@ -364,6 +341,33 @@ export default class Bootstrap { return serverTypes; } + private static async initShield(): Promise { + const shieldConfiguration = Configuration.getShieldConfig(); + let mess = ''; + if (shieldConfiguration?.active) { + shieldConfiguration.rateLimiters.forEach((rateLimiterConfig) => { + Logging.logDebug({ + tenantID: Constants.DEFAULT_TENANT_ID, + action: ServerAction.SHIELD, + module: MODULE_NAME, method: 'getRateLimiters', + message: `rate limiter with name:${rateLimiterConfig.name} numberOfPoints: ${rateLimiterConfig.numberOfPoints} duration:${rateLimiterConfig.numberOfSeconds} found` + }).catch((error) => Logging.logPromiseError(error)); + }); + } else { + if (!shieldConfiguration) { + mess = 'Section shield not found'; + } else if (!shieldConfiguration.active) { + mess = 'Section shield is present but not active'; + } + await Logging.logDebug({ + tenantID: Constants.DEFAULT_TENANT_ID, + action: ServerAction.SHIELD, + module: MODULE_NAME, method: 'getRateLimiters', + message: mess + }); + } + } + private static async fillTenantMap() : Promise { const tenants = await TenantStorage.getTenants({}, Constants.DB_PARAMS_MAX_LIMIT); // eslint-disable-next-line no-empty From 1cb2bf9e1d866d21819b71250bd53f0113a0b27b Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Wed, 12 Apr 2023 10:40:50 +0200 Subject: [PATCH 23/26] Cleaned checkRateLimiters --- .../json/services/JsonChargingStationService.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index f8c816cd5b..a4938c85c3 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -143,24 +143,23 @@ export default class JsonChargingStationService { const limiter = limiters[i].limiter; const limiterName = limiters[i].name; const points : number = limiter.points; - let pointsplusone = points ; - pointsplusone++; - const rateLimiter = this.limitersStartStopTransaction[i]; + let pointsPlusOne = points ; + pointsPlusOne++; const duration = limiter.duration; try { - const res = await limiter.consume(key); + await limiter.consume(key); } catch (error) { const rateLimiterRes = error as RateLimiterRes; - if (rateLimiterRes.consumedPoints === pointsplusone) { + if (rateLimiterRes.consumedPoints === pointsPlusOne) { await Logging.logError({ tenantID: tenant.id, action: ServerAction.RATE_LIMITER, module: MODULE_NAME, method: 'checkRateLimiters', message: `RateLimiter ${limiterName} reached first time in windows`, - detailedMessages : `key: ${key} RateLimiterPoints : ${points} RateLimiterDurations:${duration}` + detailedMessages : `key:${key} RateLimiterPoints:${points} RateLimiterDurations:${duration}` }); } - throw new Error(`RateLimiter : ${limiterName} Rate limit exceeded: points : ${points} durations:${duration}`); + throw new Error(`RateLimiter:${limiterName} Rate limit exceeded: points:${points} durations:${duration}`); } } } From 7d47ca3fb9e968a05f3993646235aee45d5573a0 Mon Sep 17 00:00:00 2001 From: Luc MARGARON Date: Wed, 12 Apr 2023 15:28:23 +0200 Subject: [PATCH 24/26] Added charging station in ratLimiters logs --- .../ocpp/json/services/JsonChargingStationService.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index a4938c85c3..5f4d1d1211 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -1,11 +1,12 @@ import { OCPPAuthorizeRequest, OCPPAuthorizeResponse, OCPPBootNotificationRequest, OCPPBootNotificationResponse, OCPPDataTransferRequest, OCPPDataTransferResponse, OCPPDiagnosticsStatusNotificationRequest, OCPPDiagnosticsStatusNotificationResponse, OCPPFirmwareStatusNotificationRequest, OCPPFirmwareStatusNotificationResponse, OCPPHeartbeatRequest, OCPPHeartbeatResponse, OCPPMeterValuesRequest, OCPPMeterValuesResponse, OCPPStartTransactionRequest, OCPPStartTransactionResponse, OCPPStatusNotificationRequest, OCPPStatusNotificationResponse, OCPPStopTransactionRequest, OCPPStopTransactionResponse, OCPPVersion } from '../../../../types/ocpp/OCPPServer'; -import { Command } from '../../../../types/ChargingStation'; +import ChargingStation, { Command } from '../../../../types/ChargingStation'; import { ServerAction } from '../../../../types/Server'; import Tenant from '../../../../types/Tenant'; import Constants from '../../../../utils/Constants'; import Logging from '../../../../utils/Logging'; import { OCPPHeader } from '../../../../types/ocpp/OCPPHeader'; +import LoggingHelper from '../../../../utils/LoggingHelper'; import OCPPService from '../../services/OCPPService'; import OCPPUtils from '../../utils/OCPPUtils'; import { RateLimiterMemory, RateLimiterRes } from 'rate-limiter-flexible'; @@ -49,7 +50,7 @@ export default class JsonChargingStationService { const { chargeBoxIdentity, tenant } = headers; const key = { tenant: tenant.subdomain, chargingStation: chargeBoxIdentity } ; const keyString = `${key.tenant}:${key.chargingStation}`; - await this.checkRateLimiters(tenant, this.limitersBootNotifs, keyString); + await this.checkRateLimiters(chargeBoxIdentity, tenant, this.limitersBootNotifs, keyString); const result = await this.handle(Command.BOOT_NOTIFICATION, headers, payload); return { currentTime: result.currentTime, @@ -99,7 +100,7 @@ export default class JsonChargingStationService { const { chargingStation, tenant } = headers; const key = { connector: payload.connectorId, tenant: tenant.subdomain, chargingStation: chargingStation.id } ; const keyString = `${key.connector}:${key.tenant}:${key.chargingStation}`; - await this.checkRateLimiters(tenant, this.limitersStartStopTransaction, keyString); + await this.checkRateLimiters(chargingStation.id, tenant, this.limitersStartStopTransaction, keyString); const result: OCPPStartTransactionResponse = await this.handle(Command.START_TRANSACTION, headers, payload); return { transactionId: result.transactionId, @@ -120,7 +121,7 @@ export default class JsonChargingStationService { const { chargingStation, tenant } = headers; const key = { tenant: tenant.subdomain, chargingStation: chargingStation.id } ; const keyString = `${key.tenant}:${key.chargingStation}`; - await this.checkRateLimiters(tenant,this.limitersStartStopTransaction, keyString); + await this.checkRateLimiters(chargingStation.id, tenant,this.limitersStartStopTransaction, keyString); const result: OCPPStopTransactionResponse = await this.handle(Command.STOP_TRANSACTION, headers, payload); return { idTagInfo: { @@ -138,7 +139,7 @@ export default class JsonChargingStationService { } } - private async checkRateLimiters(tenant:Tenant,limiters: Array, key: string) { + private async checkRateLimiters(chargingStationId: string, tenant:Tenant,limiters: Array, key: string) { for (let i = 0; i < limiters.length; i++) { const limiter = limiters[i].limiter; const limiterName = limiters[i].name; @@ -152,6 +153,7 @@ export default class JsonChargingStationService { const rateLimiterRes = error as RateLimiterRes; if (rateLimiterRes.consumedPoints === pointsPlusOne) { await Logging.logError({ + chargingStationID:chargingStationId, tenantID: tenant.id, action: ServerAction.RATE_LIMITER, module: MODULE_NAME, method: 'checkRateLimiters', From 85a3af5988254a3da81accc0ac6be19a1f22f2d6 Mon Sep 17 00:00:00 2001 From: ClaudeROSSI Date: Thu, 13 Apr 2023 15:18:36 +0200 Subject: [PATCH 25/26] Rate limiter - fix - wrong config type + cosmetic changes --- .../configuration/configuration-save.json | 4 +- .../services/JsonChargingStationService.ts | 56 +++++++++---------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/assets/storage/schemas/configuration/configuration-save.json b/src/assets/storage/schemas/configuration/configuration-save.json index 1f0e828e82..85e4071bec 100644 --- a/src/assets/storage/schemas/configuration/configuration-save.json +++ b/src/assets/storage/schemas/configuration/configuration-save.json @@ -672,10 +672,10 @@ "type": "string" }, "numberOfPoints": { - "type": "string" + "type": "number" }, "numberOfSeconds": { - "type": "string" + "type": "number" } } } diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index 5f4d1d1211..35aa94f12f 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -1,15 +1,13 @@ import { OCPPAuthorizeRequest, OCPPAuthorizeResponse, OCPPBootNotificationRequest, OCPPBootNotificationResponse, OCPPDataTransferRequest, OCPPDataTransferResponse, OCPPDiagnosticsStatusNotificationRequest, OCPPDiagnosticsStatusNotificationResponse, OCPPFirmwareStatusNotificationRequest, OCPPFirmwareStatusNotificationResponse, OCPPHeartbeatRequest, OCPPHeartbeatResponse, OCPPMeterValuesRequest, OCPPMeterValuesResponse, OCPPStartTransactionRequest, OCPPStartTransactionResponse, OCPPStatusNotificationRequest, OCPPStatusNotificationResponse, OCPPStopTransactionRequest, OCPPStopTransactionResponse, OCPPVersion } from '../../../../types/ocpp/OCPPServer'; -import ChargingStation, { Command } from '../../../../types/ChargingStation'; -import { ServerAction } from '../../../../types/Server'; -import Tenant from '../../../../types/Tenant'; -import Constants from '../../../../utils/Constants'; +import { Command } from '../../../../types/ChargingStation'; import Logging from '../../../../utils/Logging'; import { OCPPHeader } from '../../../../types/ocpp/OCPPHeader'; -import LoggingHelper from '../../../../utils/LoggingHelper'; import OCPPService from '../../services/OCPPService'; import OCPPUtils from '../../utils/OCPPUtils'; -import { RateLimiterMemory, RateLimiterRes } from 'rate-limiter-flexible'; +import { RateLimiterMemory } from 'rate-limiter-flexible'; +import { ServerAction } from '../../../../types/Server'; +import Tenant from '../../../../types/Tenant'; import Utils from '../../../../utils/Utils'; import global from '../../../../types/GlobalType'; @@ -27,30 +25,29 @@ export default class JsonChargingStationService { name = 'StartStopTransactionPerMin'; const startStopTransactionLimiterPerMin = rateLimitersMap.get(name); if (startStopTransactionLimiterPerMin) { - this.limitersStartStopTransaction.push({ name:name, limiter:startStopTransactionLimiterPerMin }); + this.limitersStartStopTransaction.push({ name, limiter: startStopTransactionLimiterPerMin }); } name = 'StartStopTransactionPerHour'; const startStopTransactionLimiterPerHour = rateLimitersMap.get(name); if (startStopTransactionLimiterPerHour) { - this.limitersStartStopTransaction.push({ name:name, limiter:startStopTransactionLimiterPerHour }); + this.limitersStartStopTransaction.push({ name, limiter: startStopTransactionLimiterPerHour }); } name = 'BootNotifPerHour'; const bootNotifRateLimiterPerHour = rateLimitersMap.get(name); if (bootNotifRateLimiterPerHour) { - this.limitersBootNotifs.push({ name:name, limiter: bootNotifRateLimiterPerHour }); + this.limitersBootNotifs.push({ name, limiter: bootNotifRateLimiterPerHour }); } name = 'BootNotifPerDay'; const bootNotifRateLimiterPerDay = rateLimitersMap.get(name); if (bootNotifRateLimiterPerDay) { - this.limitersBootNotifs.push({ name: name, limiter: bootNotifRateLimiterPerDay }); + this.limitersBootNotifs.push({ name, limiter: bootNotifRateLimiterPerDay }); } } public async handleBootNotification(headers: OCPPHeader, payload: OCPPBootNotificationRequest): Promise { const { chargeBoxIdentity, tenant } = headers; - const key = { tenant: tenant.subdomain, chargingStation: chargeBoxIdentity } ; - const keyString = `${key.tenant}:${key.chargingStation}`; - await this.checkRateLimiters(chargeBoxIdentity, tenant, this.limitersBootNotifs, keyString); + const keyString = `${tenant.subdomain}:${chargeBoxIdentity}`; + await this.checkRateLimiters(tenant, chargeBoxIdentity, this.limitersBootNotifs, keyString); const result = await this.handle(Command.BOOT_NOTIFICATION, headers, payload); return { currentTime: result.currentTime, @@ -95,12 +92,11 @@ export default class JsonChargingStationService { return {}; } - public async handleStartTransaction(headers: OCPPHeader, payload: OCPPStartTransactionRequest): Promise { const { chargingStation, tenant } = headers; const key = { connector: payload.connectorId, tenant: tenant.subdomain, chargingStation: chargingStation.id } ; const keyString = `${key.connector}:${key.tenant}:${key.chargingStation}`; - await this.checkRateLimiters(chargingStation.id, tenant, this.limitersStartStopTransaction, keyString); + await this.checkRateLimiters(tenant, chargingStation.id, this.limitersStartStopTransaction, keyString); const result: OCPPStartTransactionResponse = await this.handle(Command.START_TRANSACTION, headers, payload); return { transactionId: result.transactionId, @@ -121,7 +117,7 @@ export default class JsonChargingStationService { const { chargingStation, tenant } = headers; const key = { tenant: tenant.subdomain, chargingStation: chargingStation.id } ; const keyString = `${key.tenant}:${key.chargingStation}`; - await this.checkRateLimiters(chargingStation.id, tenant,this.limitersStartStopTransaction, keyString); + await this.checkRateLimiters(tenant, chargingStation.id, this.limitersStartStopTransaction, keyString); const result: OCPPStopTransactionResponse = await this.handle(Command.STOP_TRANSACTION, headers, payload); return { idTagInfo: { @@ -139,29 +135,31 @@ export default class JsonChargingStationService { } } - private async checkRateLimiters(chargingStationId: string, tenant:Tenant,limiters: Array, key: string) { - for (let i = 0; i < limiters.length; i++) { - const limiter = limiters[i].limiter; - const limiterName = limiters[i].name; - const points : number = limiter.points; - let pointsPlusOne = points ; - pointsPlusOne++; + private async checkRateLimiters(tenant:Tenant, chargingStationId: string, limiters: Array, key: string) { + for (const rateLimiter of limiters) { + const limiterName = rateLimiter.name; + const limiter = rateLimiter.limiter; + const points = limiter.points; + const pointsPlusOne = points + 1; const duration = limiter.duration; try { await limiter.consume(key); - } catch (error) { - const rateLimiterRes = error as RateLimiterRes; + } catch (rateLimiterRes) { if (rateLimiterRes.consumedPoints === pointsPlusOne) { + // Only log the first time we reach the limit in the current limiter window await Logging.logError({ - chargingStationID:chargingStationId, tenantID: tenant.id, + chargingStationID:chargingStationId, action: ServerAction.RATE_LIMITER, module: MODULE_NAME, method: 'checkRateLimiters', - message: `RateLimiter ${limiterName} reached first time in windows`, - detailedMessages : `key:${key} RateLimiterPoints:${points} RateLimiterDurations:${duration}` + message: `RateLimiter ${limiterName} reached for the key: ${key}`, + detailedMessages : { + rateLimiterPoints: points, + rateLimiterDurations: duration + } }); } - throw new Error(`RateLimiter:${limiterName} Rate limit exceeded: points:${points} durations:${duration}`); + throw new Error(`RateLimiter: ${limiterName} - Rate limit exceeded - key: ${key} - points: ${points} - durations: ${duration}`); } } } From 26dc7ce0dac417e6329a8a9af963518d5c833733 Mon Sep 17 00:00:00 2001 From: ClaudeROSSI Date: Thu, 13 Apr 2023 17:00:08 +0200 Subject: [PATCH 26/26] rate limiter - minor change --- src/server/ocpp/json/services/JsonChargingStationService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/server/ocpp/json/services/JsonChargingStationService.ts b/src/server/ocpp/json/services/JsonChargingStationService.ts index 35aa94f12f..05ce8fbd8e 100644 --- a/src/server/ocpp/json/services/JsonChargingStationService.ts +++ b/src/server/ocpp/json/services/JsonChargingStationService.ts @@ -140,12 +140,11 @@ export default class JsonChargingStationService { const limiterName = rateLimiter.name; const limiter = rateLimiter.limiter; const points = limiter.points; - const pointsPlusOne = points + 1; const duration = limiter.duration; try { await limiter.consume(key); } catch (rateLimiterRes) { - if (rateLimiterRes.consumedPoints === pointsPlusOne) { + if (rateLimiterRes.consumedPoints === (points + 1)) { // Only log the first time we reach the limit in the current limiter window await Logging.logError({ tenantID: tenant.id,