Do not authorize start of transaction when OCPI authorization token has not been issued. Clean up expired OCPI auth in Charging Station.
MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
@@ -5,6 +5,7 @@ import User, { UserRole, UserStatus } from '../types/User';
import AuthorizationConfiguration from '../types/configuration/AuthorizationConfiguration';
import AuthorizationsDefinition from './AuthorizationsDefinition';
import BackendError from '../exception/BackendError';
+import ChargingStationStorage from '../storage/mongodb/ChargingStationStorage';
import Configuration from '../utils/Configuration';
import Constants from '../utils/Constants';
import CpoOCPIClient from '../client/ocpi/CpoOCPIClient';
@@ -13,6 +14,7 @@ import Logging from '../utils/Logging';
import NotificationHandler from '../notification/NotificationHandler';
import OCPIClientFactory from '../client/ocpi/OCPIClientFactory';
import { OCPIRole } from '../types/ocpi/OCPIRole';
+import OCPIUtils from '../server/ocpi/OCPIUtils';
import { OICPAuthorizationStatus } from '../types/oicp/OICPAuthentication';
import OICPClientFactory from '../client/oicp/OICPClientFactory';
import { OICPDefaultTagId } from '../types/oicp/OICPIdentification';
@@ -967,8 +969,31 @@ export default class Authorizations {
// Transaction can be nullified to assess the authorization at a higher level than connectors, default connector ID value to 1 then
const transactionConnector: Connector = transaction?.connectorId ?
Utils.getConnectorFromID(chargingStation, transaction.connectorId) : Utils.getConnectorFromID(chargingStation, 1);
- // Keep the Auth ID
- user.authorizationID = await ocpiClient.authorizeToken(tag.ocpiToken, chargingStation, transactionConnector);
+ // Check Authorization in Charging Statiom
+ if (!Utils.isEmptyArray(chargingStation.remoteAuthorizations)) {
+ for (const remoteAuthorization of chargingStation.remoteAuthorizations) {
+ if (remoteAuthorization.tagId === tag.ocpiToken.uid && OCPIUtils.isAuthorizationValid(remoteAuthorization.timestamp)) {
+ await Logging.logDebug({
+ source: chargingStation.id,
+ tenantID,
+ action: ServerAction.OCPI_AUTHORIZE_TOKEN,
+ message: `Valid Remote Authorization found for Tag ID '${tag.ocpiToken.uid}'`,
+ module: MODULE_NAME, method: 'authorizeToken',
+ detailedMessages: { response: remoteAuthorization }
+ });
+ user.authorizationID = remoteAuthorization.id;
+ break;
+ }
+ }
+ // Clean up
+ if (!user.authorizationID) {
+ chargingStation.remoteAuthorizations = [];
+ await ChargingStationStorage.saveChargingStation(tenantID, chargingStation);
+ }
+ }
+ // Retrieve Auth token from OCPI
+ user.authorizationID = await ocpiClient.authorizeToken(
+ tag.ocpiToken, chargingStation, transactionConnector);
}
}
return user;

MODIFY
src/client/ocpi/CpoOCPIClient.ts
src/client/ocpi/CpoOCPIClient.ts
@@ -8,6 +8,7 @@ import { OCPILocation, OCPILocationOptions, OCPILocationReference, OCPILocationT
import { AxiosResponse } from 'axios';
import BackendError from '../../exception/BackendError';
import { ChargePointStatus } from '../../types/ocpp/OCPPServer';
+import ChargingStationStorage from '../../storage/mongodb/ChargingStationStorage';
import Constants from '../../utils/Constants';
import Consumption from '../../types/Consumption';
import ConsumptionStorage from '../../storage/mongodb/ConsumptionStorage';
@@ -165,21 +166,6 @@ export default class CpoOCPIClient extends OCPIClient {
}
public async authorizeToken(token: OCPIToken, chargingStation: ChargingStation, connector: Connector): Promise<string> {
- if (!Utils.isEmptyArray(chargingStation.remoteAuthorizations)) {
- for (const remoteAuthorization of chargingStation.remoteAuthorizations) {
- if (remoteAuthorization.tagId === token.uid && OCPIUtils.isAuthorizationValid(remoteAuthorization.timestamp)) {
- await Logging.logDebug({
- source: chargingStation.id,
- tenantID: this.tenant.id,
- action: ServerAction.OCPI_AUTHORIZE_TOKEN,
- message: `Valid Remote Authorization found for Tag ID '${token.uid}'`,
- module: MODULE_NAME, method: 'authorizeToken',
- detailedMessages: { response: remoteAuthorization }
- });
- return remoteAuthorization.id;
- }
- }
- }
// Get tokens endpoint url
const tokensUrl = `${this.getEndpointUrl('tokens', ServerAction.OCPI_AUTHORIZE_TOKEN)}/${token.uid}/authorize`;
// Build payload
@@ -1052,13 +1038,13 @@ export default class CpoOCPIClient extends OCPIClient {
const ocpiLocation: OCPILocation = {
id: site.id,
name: site.name,
- address: `${site.address.address1} ${site.address.address2}`,
- city: site.address.city,
- postal_code: site.address.postalCode,
+ address: Utils.convertAddressToOneLine(site.address),
+ city: site.address?.city,
+ postal_code: site.address?.postalCode,
country: countries.getAlpha3Code(site.address.country, CountryLanguage.getCountryLanguages(countryId, (err, languages) => languages[0].iso639_1)),
coordinates: {
- latitude: site.address.coordinates[1].toString(),
- longitude: site.address.coordinates[0].toString()
+ latitude: site.address?.coordinates[1]?.toString(),
+ longitude: site.address?.coordinates[0]?.toString()
},
type: OCPILocationType.UNKNOWN,
evses: [{
@@ -1070,8 +1056,8 @@ export default class CpoOCPIClient extends OCPIClient {
capabilities: [OCPICapability.REMOTE_START_STOP_CAPABLE, OCPICapability.RFID_READER],
connectors: connectors,
coordinates: {
- latitude: chargingStation.coordinates[1].toString(),
- longitude: chargingStation.coordinates[0].toString()
+ latitude: chargingStation.coordinates[1]?.toString(),
+ longitude: chargingStation.coordinates[0]?.toString()
},
last_updated: chargingStation.lastSeen
}],

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPIUtilsService.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPIUtilsService.ts
@@ -367,7 +367,7 @@ export default class OCPIUtilsService {
if (!session.kwh) {
session.kwh = 0;
}
- let transaction: Transaction = await TransactionStorage.getOCPITransaction(tenantId, session.id);
+ let transaction: Transaction = await TransactionStorage.getOCPITransactionBySessionID(tenantId, session.id);
if (!transaction) {
const user = await UserStorage.getUser(tenantId, session.auth_id);
if (!user) {
@@ -491,7 +491,7 @@ export default class OCPIUtilsService {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- const transaction: Transaction = await TransactionStorage.getOCPITransaction(tenantId, cdr.id);
+ const transaction: Transaction = await TransactionStorage.getOCPITransactionBySessionID(tenantId, cdr.id);
if (!transaction) {
throw new AppError({
source: Constants.CENTRAL_SERVER,

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/cpo/CPOCommandsEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/cpo/CPOCommandsEndpoint.ts
@@ -207,7 +207,7 @@ export default class CPOCommandsEndpoint extends AbstractEndpoint {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- const transaction = await TransactionStorage.getOCPITransaction(tenant.id, stopSession.session_id);
+ const transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant.id, stopSession.session_id);
if (!transaction) {
await Logging.logError({
tenantID: tenant.id,

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPCdrsEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPCdrsEndpoint.ts
@@ -48,7 +48,7 @@ export default class EMSPCdrsEndpoint extends AbstractEndpoint {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- const transaction: Transaction = await TransactionStorage.getOCPITransaction(tenant.id, id);
+ const transaction: Transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant.id, id);
if (!transaction) {
throw new AppError({
source: Constants.CENTRAL_SERVER,

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPSessionsEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPSessionsEndpoint.ts
@@ -53,7 +53,7 @@ export default class EMSPSessionsEndpoint extends AbstractEndpoint {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- const transaction: Transaction = await TransactionStorage.getOCPITransaction(tenant.id, sessionId);
+ const transaction: Transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant.id, sessionId);
if (!transaction) {
throw new AppError({
source: Constants.CENTRAL_SERVER,
@@ -120,7 +120,7 @@ export default class EMSPSessionsEndpoint extends AbstractEndpoint {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- const transaction = await TransactionStorage.getOCPITransaction(tenant.id, sessionId);
+ const transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant.id, sessionId);
if (!transaction) {
throw new AppError({
source: Constants.CENTRAL_SERVER,

MODIFY
src/server/ocpp/services/OCPPService.ts
src/server/ocpp/services/OCPPService.ts
@@ -491,8 +491,9 @@ export default class OCPPService {
authorize.idTag, ServerAction.AUTHORIZE, Action.AUTHORIZE);
// Roaming User
if (user && !user.issuer && user.authorizationID) {
+ // Public Charging Station
if (chargingStation.public) {
- // Keep the Auth ID
+ // Keep Romaing Auth ID
authorize.authorizationId = user.authorizationID;
} else {
throw new BackendError({
@@ -1051,8 +1052,8 @@ export default class OCPPService {
});
return;
}
- // Check for inactivity
- await this.checkStatusNotificationExtraInactivity(tenantID, chargingStation, statusNotification, foundConnector);
+ // Check last transaction
+ await this.checkLastTransaction(tenantID, chargingStation, statusNotification, foundConnector);
// Set connector data
foundConnector.connectorId = statusNotification.connectorId;
foundConnector.status = statusNotification.status;
@@ -1115,16 +1116,19 @@ export default class OCPPService {
}
}
- private async checkStatusNotificationExtraInactivity(tenantID: string, chargingStation: ChargingStation,
+ private async checkLastTransaction(tenantID: string, chargingStation: ChargingStation,
statusNotification: OCPPStatusNotificationRequestExtended, connector: Connector) {
- // Check Inactivity
- if (statusNotification.status === ChargePointStatus.AVAILABLE &&
- Utils.objectHasProperty(statusNotification, 'timestamp')) {
+ // Check last transaction
+ if (statusNotification.status === ChargePointStatus.AVAILABLE) {
// Get the last transaction
- const lastTransaction = await TransactionStorage.getLastTransaction(
+ const lastTransaction = await TransactionStorage.getLastTransactionFromChargingStation(
tenantID, chargingStation.id, connector.connectorId, { withChargingStation: true, withUser: true });
+ // Transaction completed
+ if (lastTransaction?.stop) {
+ // Check Inactivity
+ if (Utils.objectHasProperty(statusNotification, 'timestamp')) {
// Session is finished
- if (lastTransaction && lastTransaction.stop && !lastTransaction.stop.extraInactivityComputed) {
+ if (!lastTransaction.stop.extraInactivityComputed) {
const transactionStopTimestamp = Utils.convertToDate(lastTransaction.stop.timestamp);
const currentStatusNotifTimestamp = Utils.convertToDate(statusNotification.timestamp);
// Diff
@@ -1137,6 +1141,18 @@ export default class OCPPService {
lastTransaction.stop.totalInactivitySecs + lastTransaction.stop.extraInactivitySecs);
// Build extra inactivity consumption
await OCPPUtils.buildExtraConsumptionInactivity(tenantID, lastTransaction);
+ // Log
+ await Logging.logInfo({
+ tenantID: tenantID,
+ source: chargingStation.id,
+ user: lastTransaction.userID,
+ module: MODULE_NAME, method: 'checkLastTransaction',
+ action: ServerAction.EXTRA_INACTIVITY,
+ message: `Connector ID '${lastTransaction.connectorId}' > Transaction ID '${lastTransaction.id}' > Extra Inactivity of ${lastTransaction.stop.extraInactivitySecs} secs has been added`,
+ detailedMessages: [statusNotification, connector, lastTransaction]
+ });
+ }
+ }
// OCPI: Post the CDR
if (lastTransaction.ocpiData?.session) {
await this.checkAndSendOCPITransactionCdr(tenantID, lastTransaction, chargingStation);
@@ -1147,16 +1163,6 @@ export default class OCPPService {
}
// Save
await TransactionStorage.saveTransaction(tenantID, lastTransaction);
- // Log
- await Logging.logInfo({
- tenantID: tenantID,
- source: chargingStation.id,
- user: lastTransaction.userID,
- module: MODULE_NAME, method: 'checkStatusNotificationExtraInactivity',
- action: ServerAction.EXTRA_INACTIVITY,
- message: `Connector ID '${lastTransaction.connectorId}' > Transaction ID '${lastTransaction.id}' > Extra Inactivity of ${lastTransaction.stop.extraInactivitySecs} secs has been added`,
- detailedMessages: [statusNotification, connector, lastTransaction]
- });
}
}
}

MODIFY
src/server/ocpp/utils/OCPPUtils.ts
src/server/ocpp/utils/OCPPUtils.ts
@@ -48,12 +48,7 @@ const MODULE_NAME = 'OCPPUtils';
export default class OCPPUtils {
public static async processOCPITransaction(tenantID: string, transaction: Transaction,
chargingStation: ChargingStation, transactionAction: TransactionAction): Promise<void> {
- try {
- if (!transaction.user || transaction.user.issuer) {
- return;
- }
- const user: User = transaction.user;
- const tenant: Tenant = await TenantStorage.getTenant(tenantID);
+ // Set Action
let action: ServerAction;
switch (transactionAction) {
case TransactionAction.START:
@@ -67,6 +62,18 @@ export default class OCPPUtils {
action = ServerAction.STOP_TRANSACTION;
break;
}
+ // Check User
+ if (!transaction.user || transaction.user.issuer) {
+ throw new BackendError({
+ user: transaction.user,
+ action,
+ module: MODULE_NAME,
+ method: 'processOCPITransaction',
+ message: 'User does not exist or does not belong to the local organization'
+ });
+ }
+ const user = transaction.user;
+ const tenant = await TenantStorage.getTenant(tenantID);
if (!Utils.isTenantComponentActive(tenant, TenantComponents.OCPI)) {
throw new BackendError({
user: user,
@@ -112,7 +119,8 @@ export default class OCPPUtils {
// Get the first non used Authorization OCPI ID
for (const authorization of authorizations.result) {
if (authorization.authorizationId) {
- const ocpiTransaction = await TransactionStorage.getOCPITransaction(tenant.id, authorization.authorizationId);
+ // OCPI authorizationId = OCPI sessionID
+ const ocpiTransaction = await TransactionStorage.getOCPITransactionBySessionID(tenant.id, authorization.authorizationId);
// OCPI ID not used yet
if (!ocpiTransaction) {
authorizationId = authorization.authorizationId;
@@ -141,17 +149,6 @@ export default class OCPPUtils {
await ocpiClient.postCdr(transaction);
break;
}
- } catch (error) {
- await Logging.logError({
- tenantID: tenantID,
- user: transaction.userID,
- source: Constants.CENTRAL_SERVER,
- action: ServerAction.OCPI_PUSH_SESSIONS,
- module: MODULE_NAME, method: 'processOCPITransaction',
- message: `Cannot ${transactionAction} Transaction ID '${transaction.id}' thought OCPI protocol`,
- detailedMessages: { error: error.message, stack: error.stack }
- });
- }
}
public static async processOICPTransaction(tenantID: string, transaction: Transaction,

MODIFY
src/server/oicp/OICPUtils.ts
src/server/oicp/OICPUtils.ts
@@ -4,23 +4,19 @@ import User, { UserStatus } from '../../types/User';
import ChargingStationStorage from '../../storage/mongodb/ChargingStationStorage';
import Constants from '../../utils/Constants';
-import Cypher from '../../utils/Cypher';
import OCPPStorage from '../../storage/mongodb/OCPPStorage';
import { OICPAcknowledgment } from '../../types/oicp/OICPAcknowledgment';
import { OICPEvseID } from '../../types/oicp/OICPEvse';
import { OICPSession } from '../../types/oicp/OICPSession';
import { OICPStatusCode } from '../../types/oicp/OICPStatusCode';
-import { OicpSetting } from '../../types/Setting';
import RoamingUtils from '../../utils/RoamingUtils';
import { ServerAction } from '../../types/Server';
-import SettingStorage from '../../storage/mongodb/SettingStorage';
import Tenant from '../../types/Tenant';
import Transaction from '../../types/Transaction';
import TransactionStorage from '../../storage/mongodb/TransactionStorage';
import UserStorage from '../../storage/mongodb/UserStorage';
import Utils from '../../utils/Utils';
import moment from 'moment';
-import sanitize from 'mongo-sanitize';
export default class OICPUtils {
@@ -171,7 +167,7 @@ export default class OICPUtils {
// Get the first non used Authorization OICP ID / Session ID
for (const authorization of authorizations.result) {
if (authorization.authorizationId) {
- const oicpTransaction = await TransactionStorage.getOICPTransaction(tenantID, authorization.authorizationId);
+ const oicpTransaction = await TransactionStorage.getOICPTransactionBySessionID(tenantID, authorization.authorizationId);
// OICP SessionID not used yet
if (!oicpTransaction) {
sessionId = authorization.authorizationId;

MODIFY
src/server/oicp/oicp-services-impl/oicp-2.3.0/CPORemoteAuthorizationsEndpoint.ts
src/server/oicp/oicp-services-impl/oicp-2.3.0/CPORemoteAuthorizationsEndpoint.ts
@@ -32,14 +32,6 @@ export default class CPORemoteAuthorizationsEndpoint extends AbstractEndpoint {
super(oicpService, EP_IDENTIFIER);
}
- /**
- * Main Process Method for the endpoint
- *
- * @param req
- * @param res
- * @param next
- * @param tenant
- */
public async process(req: Request, res: Response, next: NextFunction, tenant: Tenant): Promise<OICPAcknowledgment> {
switch (req.method) {
case 'POST':
@@ -55,14 +47,6 @@ export default class CPORemoteAuthorizationsEndpoint extends AbstractEndpoint {
}
}
- /**
- * Remote Start Transaction requested by Hubject
- *
- * @param req
- * @param res
- * @param next
- * @param tenant
- */
private async authorizeRemoteStart(req: Request, res: Response, next: NextFunction, tenant: Tenant): Promise<OICPAcknowledgment> {
const authorizeRemoteStart = req.body as OICPAuthorizeRemoteStartCpoReceive;
// Check props
@@ -160,14 +144,6 @@ export default class CPORemoteAuthorizationsEndpoint extends AbstractEndpoint {
return OICPUtils.noSuccess(session, 'Remote Start rejected by Charging Station');
}
- /**
- * Remote stop Transaction requested by Hubject
- *
- * @param req
- * @param res
- * @param next
- * @param tenant
- */
private async authorizeRemoteStop(req: Request, res: Response, next: NextFunction, tenant: Tenant): Promise<OICPAcknowledgment> {
const authorizeRemoteStop = req.body as OICPAuthorizeRemoteStopCpoReceive;
// Check props
@@ -175,7 +151,7 @@ export default class CPORemoteAuthorizationsEndpoint extends AbstractEndpoint {
const session = {} as Partial<OICPSession>;
session.id = authorizeRemoteStop.SessionID;
session.providerID = authorizeRemoteStop.ProviderID;
- const transaction = await TransactionStorage.getOICPTransaction(tenant.id, authorizeRemoteStop.SessionID);
+ const transaction = await TransactionStorage.getOICPTransactionBySessionID(tenant.id, authorizeRemoteStop.SessionID);
if (!transaction) {
await Logging.logDebug({
tenantID: tenant.id,
@@ -222,7 +198,8 @@ export default class CPORemoteAuthorizationsEndpoint extends AbstractEndpoint {
return OICPUtils.noSuccess(session, 'Remote Stop rejected by Charging Station');
}
- private async remoteStartTransaction(tenant: Tenant, chargingStation: ChargingStation, connector: Connector, authorizeRemoteStart: OICPAuthorizeRemoteStartCpoReceive): Promise<OCPPRemoteStartTransactionCommandResult> {
+ private async remoteStartTransaction(tenant: Tenant, chargingStation: ChargingStation,
+ connector: Connector, authorizeRemoteStart: OICPAuthorizeRemoteStartCpoReceive): Promise<OCPPRemoteStartTransactionCommandResult> {
const chargingStationClient = await ChargingStationClientFactory.getChargingStationClient(tenant.id, chargingStation);
if (!chargingStationClient) {
await Logging.logError({

MODIFY
src/storage/mongodb/TransactionStorage.ts
src/storage/mongodb/TransactionStorage.ts
@@ -936,13 +936,13 @@ export default class TransactionStorage {
return transactionsMDB.count === 1 ? transactionsMDB.result[0] : null;
}
- public static async getOCPITransaction(tenantID: string, sessionID: string): Promise<Transaction> {
+ public static async getOCPITransactionBySessionID(tenantID: string, sessionID: string): Promise<Transaction> {
const transactionsMDB = await TransactionStorage.getTransactions(tenantID,
{ ocpiSessionID: sessionID }, Constants.DB_PARAMS_SINGLE_RECORD);
return transactionsMDB.count === 1 ? transactionsMDB.result[0] : null;
}
- public static async getOICPTransaction(tenantID: string, sessionID: string): Promise<Transaction> {
+ public static async getOICPTransactionBySessionID(tenantID: string, sessionID: string): Promise<Transaction> {
const transactionsMDB = await TransactionStorage.getTransactions(tenantID,
{ oicpSessionID: sessionID }, Constants.DB_PARAMS_SINGLE_RECORD);
return transactionsMDB.count === 1 ? transactionsMDB.result[0] : null;
@@ -987,7 +987,7 @@ export default class TransactionStorage {
return transactionsMDB.length === 1 ? transactionsMDB[0] : null;
}
- public static async getLastTransaction(tenantID: string, chargeBoxID: string, connectorId: number,
+ public static async getLastTransactionFromChargingStation(tenantID: string, chargeBoxID: string, connectorId: number,
params: { withChargingStation?: boolean; withUser?: boolean; }): Promise<Transaction> {
// Debug
const uniqueTimerID = Logging.traceStart(tenantID, MODULE_NAME, 'getLastTransaction');

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -54,6 +54,17 @@ export default class Utils {
return connectors;
}
+ public static convertAddressToOneLine(address: Address): string {
+ const oneLineAddress: string[] = [];
+ if (address?.address1) {
+ oneLineAddress.push(address.address1);
+ }
+ if (address?.address2) {
+ oneLineAddress.push(address.address2);
+ }
+ return oneLineAddress.join(' ');
+ }
+
public static handleAxiosError(axiosError: AxiosError, urlRequest: string, action: ServerAction, module: string, method: string): void {
// Handle Error outside 2xx range
if (axiosError.response) {

Do not authorize start of transaction when OCPI authorization token has not been issued. Clean up expired OCPI auth in Charging Station.