Handle charging station public flag in OCPI. Added check of EVSEs in checkLocations(). Tested CPO client. Added tenant/user hash check in new RESTful router.
MODIFY
src/client/ocpi/CpoOCPIClient.ts
src/client/ocpi/CpoOCPIClient.ts
@@ -70,11 +70,14 @@ export default class CpoOCPIClient extends OCPIClient {
do {
const startTimeLoop = new Date().getTime();
// Call IOP
- const response = await this.axiosInstance.get(tokensUrl, {
+ const response = await this.axiosInstance.get(
+ tokensUrl,
+ {
headers: {
Authorization: `Token ${this.ocpiEndpoint.token}`
},
- });
+ }
+ );
if (!response.data.data) {
throw new BackendError({
action: ServerAction.OCPI_PULL_TOKENS,
@@ -166,14 +169,16 @@ export default class CpoOCPIClient extends OCPIClient {
// Get tokens endpoint url
const tokensUrl = `${this.getEndpointUrl('tokens', ServerAction.OCPI_AUTHORIZE_TOKEN)}/${token.uid}/authorize`;
// Build payload
- const payload: OCPILocationReference =
+ const locationReference: OCPILocationReference =
{
location_id: chargingStation.siteID,
// Gireve does not support authorization request on multiple EVSE
evse_uids: [OCPIUtils.buildEvseUID(chargingStation, connector)]
};
// Call IOP
- const response = await this.axiosInstance.post(tokensUrl, payload,
+ const response = await this.axiosInstance.post(
+ tokensUrl,
+ locationReference,
{
headers: {
'Authorization': `Token ${this.ocpiEndpoint.token}`,
@@ -185,7 +190,7 @@ export default class CpoOCPIClient extends OCPIClient {
action: ServerAction.OCPI_AUTHORIZE_TOKEN,
message: 'Invalid response from Post Authorize',
module: MODULE_NAME, method: 'authorizeToken',
- detailedMessages: { data: response.data }
+ detailedMessages: { locationReference }
});
}
const authorizationInfo = response.data.data as OCPIAuthorizationInfo;
@@ -194,7 +199,7 @@ export default class CpoOCPIClient extends OCPIClient {
action: ServerAction.OCPI_AUTHORIZE_TOKEN,
message: 'Authorization rejected',
module: MODULE_NAME, method: 'authorizeToken',
- detailedMessages: { authorizationInfo }
+ detailedMessages: { locationReference, authorizationInfo }
});
}
if (!authorizationInfo.authorization_id) {
@@ -202,7 +207,7 @@ export default class CpoOCPIClient extends OCPIClient {
action: ServerAction.OCPI_AUTHORIZE_TOKEN,
message: 'Authorization allowed without \'authorization_id\'',
module: MODULE_NAME, method: 'authorizeToken',
- detailedMessages: { authorizationInfo }
+ detailedMessages: { locationReference, authorizationInfo }
});
}
await Logging.logInfo({
@@ -211,7 +216,7 @@ export default class CpoOCPIClient extends OCPIClient {
action: ServerAction.OCPI_AUTHORIZE_TOKEN,
message: `OCPI Authorization ID '${authorizationInfo.authorization_id}' has been issued successfully`,
module: MODULE_NAME, method: 'authorizeToken',
- detailedMessages: { response: response.data }
+ detailedMessages: { locationReference, response: response.data }
});
return authorizationInfo.authorization_id;
}
@@ -223,8 +228,7 @@ export default class CpoOCPIClient extends OCPIClient {
const ocpiLocation: OCPILocation = OCPIUtilsService.convertChargingStationToOCPILocation(this.tenant, site, chargingStation,
transaction.connectorId, this.getLocalCountryCode(ServerAction.OCPI_PUSH_SESSIONS), this.getLocalPartyID(ServerAction.OCPI_PUSH_SESSIONS));
// Build payload
- const ocpiSession: OCPISession =
- {
+ const ocpiSession: OCPISession = {
id: authorizationId,
start_datetime: transaction.timestamp,
kwh: 0,
@@ -238,7 +242,9 @@ export default class CpoOCPIClient extends OCPIClient {
last_updated: transaction.timestamp
};
// Call IOP
- const response = await this.axiosInstance.put(sessionsUrl, ocpiSession,
+ const response = await this.axiosInstance.put(
+ sessionsUrl,
+ ocpiSession,
{
headers: {
'Authorization': `Token ${this.ocpiEndpoint.token}`,
@@ -255,7 +261,7 @@ export default class CpoOCPIClient extends OCPIClient {
action: ServerAction.OCPI_START_SESSION,
message: `OCPI Session ID '${ocpiSession.id}' (ID '${transaction.id}') has been started successfully`,
module: MODULE_NAME, method: 'startSession',
- detailedMessages: { response: response.data }
+ detailedMessages: { ocpiSession, response: response.data }
});
}
@@ -278,7 +284,7 @@ export default class CpoOCPIClient extends OCPIClient {
transaction.ocpiData.session.status = OCPISessionStatus.ACTIVE;
transaction.ocpiData.session.charging_periods = await OCPIUtilsService.buildChargingPeriods(this.tenant.id, transaction);
// Send OCPI information
- const patchBody: Partial<OCPISession> = {
+ const session: Partial<OCPISession> = {
kwh: transaction.ocpiData.session.kwh,
last_updated: transaction.ocpiData.session.last_updated,
currency: transaction.ocpiData.session.currency,
@@ -287,7 +293,9 @@ export default class CpoOCPIClient extends OCPIClient {
charging_periods: transaction.ocpiData.session.charging_periods
};
// Call IOP
- const response = await this.axiosInstance.patch(sessionsUrl, patchBody, {
+ const response = await this.axiosInstance.patch(
+ sessionsUrl,
+ session, {
headers: {
'Authorization': `Token ${this.ocpiEndpoint.token}`,
'Content-Type': 'application/json'
@@ -299,7 +307,7 @@ export default class CpoOCPIClient extends OCPIClient {
action: ServerAction.OCPI_PUSH_SESSIONS,
message: `OCPI Session ID '${transaction.ocpiData.session.id}' (ID '${transaction.id}') patched successfully`,
module: MODULE_NAME, method: 'updateSession',
- detailedMessages: { response: response.data }
+ detailedMessages: { session, response: response.data }
});
}
@@ -337,7 +345,9 @@ export default class CpoOCPIClient extends OCPIClient {
transaction.ocpiData.session.status = OCPISessionStatus.COMPLETED;
transaction.ocpiData.session.charging_periods = await OCPIUtilsService.buildChargingPeriods(this.tenant.id, transaction);
// Call IOP
- const response = await this.axiosInstance.put(tokensUrl, transaction.ocpiData.session,
+ const response = await this.axiosInstance.put(
+ tokensUrl,
+ transaction.ocpiData.session,
{
headers: {
'Authorization': `Token ${this.ocpiEndpoint.token}`,
@@ -350,7 +360,7 @@ export default class CpoOCPIClient extends OCPIClient {
action: ServerAction.OCPI_STOP_SESSION,
message: `OCPI Session ID '${transaction.ocpiData.session.id}' (ID '${transaction.id}') has been stopped successfully`,
module: MODULE_NAME, method: 'stopSession',
- detailedMessages: { response: response.data }
+ detailedMessages: { session: transaction.ocpiData.session, response: response.data }
});
}
@@ -398,7 +408,9 @@ export default class CpoOCPIClient extends OCPIClient {
last_updated: transaction.stop.timestamp
};
// Call IOP
- const response = await this.axiosInstance.post(cdrsUrl, transaction.ocpiData.cdr,
+ const response = await this.axiosInstance.post(
+ cdrsUrl,
+ transaction.ocpiData.cdr,
{
headers: {
'Authorization': `Token ${this.ocpiEndpoint.token}`,
@@ -411,12 +423,12 @@ export default class CpoOCPIClient extends OCPIClient {
action: ServerAction.OCPI_PUSH_CDRS,
message: `Post CDR of OCPI Session ID '${transaction.ocpiData.session.id}' (ID '${transaction.id}') has been done successfully`,
module: MODULE_NAME, method: 'postCdr',
- detailedMessages: { response: response.data, cdr: transaction.ocpiData.cdr }
+ detailedMessages: { cdr: transaction.ocpiData.cdr, response: response.data }
});
}
- public async removeChargingStation(chargingStation: ChargingStation): Promise<void> {
- if (!chargingStation.siteAreaID && !chargingStation.siteArea) {
+ public async udpateChargingStationStatus(chargingStation: ChargingStation, status?: OCPIEvseStatus): Promise<void> {
+ if (!chargingStation.siteAreaID) {
throw new BackendError({
source: chargingStation.id,
action: ServerAction.OCPI_PATCH_STATUS,
@@ -434,7 +446,9 @@ export default class CpoOCPIClient extends OCPIClient {
}
const results: any[] = [];
for (const connector of chargingStation.connectors) {
- const result = await this.patchEVSEStatus(chargingStation.id, chargingStation.siteID, OCPIUtils.buildEvseUID(chargingStation, connector), OCPIEvseStatus.REMOVED);
+ const result = await this.patchEVSEStatus(
+ chargingStation.id, chargingStation.siteID, OCPIUtils.buildEvseUID(chargingStation, connector),
+ status ?? OCPIUtilsService.convertStatus2OCPIStatus(connector.status));
results.push(result.data);
}
await Logging.logInfo({
@@ -765,25 +779,26 @@ export default class CpoOCPIClient extends OCPIClient {
// Build url to EVSE
const fullUrl = locationsUrl + `/${countryCode}/${partyID}/${locationId}/${evseUID}`;
// Build payload
- const payload = { 'status': newStatus };
+ const evseStatus = { 'status': newStatus };
// Call IOP
- const result = await this.axiosInstance.patch(fullUrl, payload,
+ const response = await this.axiosInstance.patch(
+ fullUrl,
+ evseStatus,
{
headers: {
'Authorization': `Token ${this.ocpiEndpoint.token}`,
'Content-Type': 'application/json'
},
});
- // Log
await Logging.logInfo({
tenantID: this.tenant.id,
source: chargingStationID,
action: ServerAction.OCPI_PATCH_STATUS,
message: `OCPI Charging Station ID '${evseUID}' has been patched successfully to '${newStatus}'`,
module: MODULE_NAME, method: 'patchEVSEStatus',
- detailedMessages: { payload }
+ detailedMessages: { evseStatus, response: response.data }
});
- return result;
+ return response;
}
private async checkCdr(transaction: Transaction): Promise<boolean> {
@@ -805,7 +820,8 @@ export default class CpoOCPIClient extends OCPIClient {
}
const cdrsUrl = this.getEndpointUrl('cdrs', ServerAction.OCPI_CHECK_CDRS);
// Check CDR
- const response = await this.axiosInstance.get(`${cdrsUrl}/${transaction.ocpiData.cdr.id}`,
+ const response = await this.axiosInstance.get(
+ `${cdrsUrl}/${transaction.ocpiData.cdr.id}`,
{
headers: {
'Authorization': `Token ${this.ocpiEndpoint.token}`
@@ -820,7 +836,9 @@ export default class CpoOCPIClient extends OCPIClient {
detailedMessages: { response: response.data }
});
if (response.data.status_code === 3001) {
- await this.axiosInstance.post(cdrsUrl, transaction.ocpiData.cdr,
+ await this.axiosInstance.post(
+ cdrsUrl,
+ transaction.ocpiData.cdr,
{
headers: {
'Authorization': `Token ${this.ocpiEndpoint.token}`,
@@ -840,7 +858,7 @@ export default class CpoOCPIClient extends OCPIClient {
action: ServerAction.OCPI_CHECK_CDRS,
message: `Failed to check CDR of OCPI Session ID '${transaction.ocpiData.session.id}' (ID '${transaction.id}') at ${cdrsUrl}/${transaction.ocpiData.cdr.id}`,
module: MODULE_NAME, method: 'checkCdr',
- detailedMessages: { data: response.data }
+ detailedMessages: { cdr: transaction.ocpiData.cdr, data: response.data }
});
}
@@ -863,7 +881,8 @@ export default class CpoOCPIClient extends OCPIClient {
}
const sessionsUrl = `${this.getEndpointUrl('sessions', ServerAction.OCPI_CHECK_SESSIONS)}/${this.getLocalCountryCode(ServerAction.OCPI_CHECK_SESSIONS)}/${this.getLocalPartyID(ServerAction.OCPI_CHECK_SESSIONS)}/${transaction.ocpiData.session.id}`;
// Check
- const response = await this.axiosInstance.get(sessionsUrl,
+ const response = await this.axiosInstance.get(
+ sessionsUrl,
{
headers: {
'Authorization': `Token ${this.ocpiEndpoint.token}`
@@ -875,7 +894,7 @@ export default class CpoOCPIClient extends OCPIClient {
action: ServerAction.OCPI_CHECK_SESSIONS,
message: `OCPI Session ID '${transaction.ocpiData.session.id}' (ID '${transaction.id}') checked successfully`,
module: MODULE_NAME, method: 'checkSession',
- detailedMessages: { response: response.data }
+ detailedMessages: { transaction, response: response.data }
});
const session = response.data.data as OCPISession;
if (session) {
@@ -900,28 +919,39 @@ export default class CpoOCPIClient extends OCPIClient {
const partyID = this.getLocalPartyID(ServerAction.OCPI_CHECK_LOCATIONS);
const locationUrl = locationsUrl + `/${countryCode}/${partyID}/${location.id}`;
// Call IOP
- const response = await this.axiosInstance.get(locationUrl,
+ const response = await this.axiosInstance.get(
+ locationUrl,
{
headers: {
'Authorization': `Token ${this.ocpiEndpoint.token}`
},
});
- await Logging.logInfo({
- tenantID: this.tenant.id,
+ let result = true;
+ const ocpiLocation = response?.data?.data as OCPILocation;
+ if (ocpiLocation) {
+ // Check Evses
+ for (const evse of location.evses) {
+ if (!ocpiLocation?.evses.find((ocpiEvse) => ocpiEvse.evse_id === evse.evse_id)) {
+ result = false;
+ break;
+ }
+ }
+ }
+ if (!result) {
+ throw new BackendError({
action: ServerAction.OCPI_CHECK_LOCATIONS,
- message: `Location ID '${location.id}' checked successfully`,
+ message: `Failed to check Location ID '${location.id}'`,
module: MODULE_NAME, method: 'checkLocation',
- detailedMessages: { response: response.data }
+ detailedMessages: { location, ocpiLocation }
});
- const checkedLocation = response.data.data as OCPILocation;
- if (checkedLocation) {
- return true;
}
- throw new BackendError({
+ await Logging.logInfo({
+ tenantID: this.tenant.id,
action: ServerAction.OCPI_CHECK_LOCATIONS,
- message: `Failed to check Location ID '${location.id}'`,
+ message: `Location ID '${location.id}' checked successfully`,
module: MODULE_NAME, method: 'checkLocation',
- detailedMessages: { data: response.data }
+ detailedMessages: { location, ocpiLocation }
});
+ return result;
}
}

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
@@ -48,7 +48,7 @@ const MODULE_NAME = 'OCPIUtilsService';
export default class OCPIUtilsService {
public static async convertSite2Location(tenant: Tenant, site: Site,
- options: OCPILocationOptions, withAllChargingStations): Promise<OCPILocation> {
+ options: OCPILocationOptions, withChargingStations): Promise<OCPILocation> {
// Build object
return {
id: site.id,
@@ -62,7 +62,7 @@ export default class OCPIUtilsService {
longitude: site.address.coordinates[0].toString(),
latitude: site.address.coordinates[1].toString()
},
- evses: withAllChargingStations ?
+ evses: withChargingStations ?
await OCPIUtilsService.getEvsesFromSite(tenant, site.id, options, Constants.DB_PARAMS_MAX_LIMIT) : [],
last_updated: site.lastChangedOn ? site.lastChangedOn : site.createdOn,
opening_times: {
@@ -114,7 +114,7 @@ export default class OCPIUtilsService {
}
public static async getAllLocations(tenant: Tenant, limit: number, skip: number,
- options: OCPILocationOptions, withAllChargingStations: boolean): Promise<DataResult<OCPILocation>> {
+ options: OCPILocationOptions, withChargingStations: boolean): Promise<DataResult<OCPILocation>> {
// Result
const ocpiLocationsResult: DataResult<OCPILocation> = { count: 0, result: [] };
// Get all sites
@@ -125,10 +125,16 @@ export default class OCPIUtilsService {
// Convert Sites to Locations
for (const site of sites.result) {
ocpiLocationsResult.result.push(
- await OCPIUtilsService.convertSite2Location(tenant, site, options, withAllChargingStations));
+ await OCPIUtilsService.convertSite2Location(tenant, site, options, withChargingStations));
+ }
+ let nbrOfSites = sites.count;
+ if (nbrOfSites === -1) {
+ const sitesCount = await SiteStorage.getSites(tenant.id,
+ { issuer: true, onlyPublicSite: true }, Constants.DB_PARAMS_COUNT_ONLY);
+ nbrOfSites = sitesCount.count;
}
// Set count
- ocpiLocationsResult.count = sites.count;
+ ocpiLocationsResult.count = nbrOfSites;
// Return locations
return ocpiLocationsResult;
}
@@ -370,7 +376,7 @@ export default class OCPIUtilsService {
// Build based on consumptions
for (const consumption of consumptions.result) {
const chargingPeriod = this.buildChargingPeriod(consumption);
- if (chargingPeriod && chargingPeriod.dimensions && chargingPeriod.dimensions.length > 0) {
+ if (!Utils.isEmptyArray(chargingPeriod?.dimensions)) {
chargingPeriods.push(chargingPeriod);
}
}

MODIFY
src/server/rest/CentralRestServer.ts
src/server/rest/CentralRestServer.ts
@@ -47,7 +47,9 @@ export default class CentralRestServer {
// Routers
this.expressApplication.use('/v1', new GlobalRouter().buildRoutes());
// Secured API
- this.expressApplication.all('/client/api/:action', AuthService.authenticate(), CentralRestServerService.restServiceSecured.bind(this));
+ this.expressApplication.all('/client/api/:action',
+ AuthService.authenticate(),
+ CentralRestServerService.restServiceSecured.bind(this));
// Util API
this.expressApplication.all('/client/util/:action', CentralRestServerService.restServiceUtil.bind(this));
// Workaround URL encoding issue

MODIFY
src/server/rest/v1/router/GlobalRouter.ts
src/server/rest/v1/router/GlobalRouter.ts
@@ -34,7 +34,10 @@ export default class GlobalRouter {
}
protected buildRouteAPI(): void {
- this.router.use('/api', AuthService.authenticate(), [
+ this.router.use('/api',
+ AuthService.authenticate(),
+ AuthService.checkSessionHash.bind(this),
+ [
new ChargingStationRouter().buildRoutes(),
new TagRouter().buildRoutes(),
new TenantRouter().buildRoutes(),

MODIFY
src/server/rest/v1/service/AuthService.ts
src/server/rest/v1/service/AuthService.ts
@@ -15,6 +15,7 @@ import I18nManager from '../../../../utils/I18nManager';
import Logging from '../../../../utils/Logging';
import NotificationHandler from '../../../../notification/NotificationHandler';
import { ServerAction } from '../../../../types/Server';
+import SessionHashService from './SessionHashService';
import SettingStorage from '../../../../storage/mongodb/SettingStorage';
import SiteStorage from '../../../../storage/mongodb/SiteStorage';
import { StatusCodes } from 'http-status-codes';
@@ -59,6 +60,13 @@ export default class AuthService {
return passport.authenticate('jwt', { session: false });
}
+ public static async checkSessionHash(req: Request, res: Response, next: NextFunction) {
+ // Check if User has been updated and require new login
+ if(!await SessionHashService.isSessionHashUpdated(req, res, next)) {
+ next();
+ }
+ }
+
public static async handleLogIn(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
const filteredRequest = AuthValidator.getInstance().validateAuthSignIn(req.body);

MODIFY
src/server/rest/v1/service/ChargingStationService.ts
src/server/rest/v1/service/ChargingStationService.ts
@@ -24,6 +24,7 @@ import LockingHelper from '../../../../locking/LockingHelper';
import LockingManager from '../../../../locking/LockingManager';
import Logging from '../../../../utils/Logging';
import OCPIClientFactory from '../../../../client/ocpi/OCPIClientFactory';
+import { OCPIEvseStatus } from '../../../../types/ocpi/OCPIEvse';
import { OCPIRole } from '../../../../types/ocpi/OCPIRole';
import OCPPStorage from '../../../../storage/mongodb/OCPPStorage';
import OCPPUtils from '../../../ocpp/utils/OCPPUtils';
@@ -90,13 +91,18 @@ export default class ChargingStationService {
}
if (Utils.objectHasProperty(filteredRequest, 'public')) {
if (filteredRequest.public !== chargingStation.public) {
- if (!filteredRequest.public) {
- // Remove charging station from ocpi
+ // OCPI handling
if (Utils.isComponentActiveFromToken(req.user, TenantComponents.OCPI)) {
+ // Remove charging station from ocpi
try {
- const ocpiClient: CpoOCPIClient = await OCPIClientFactory.getAvailableOcpiClient(req.tenant, OCPIRole.CPO) as CpoOCPIClient;
+ const ocpiClient = await OCPIClientFactory.getAvailableOcpiClient(req.tenant, OCPIRole.CPO) as CpoOCPIClient;
+ let status: OCPIEvseStatus;
+ if (!filteredRequest.public) {
+ // Force remove
+ status = OCPIEvseStatus.REMOVED;
+ }
if (ocpiClient) {
- await ocpiClient.removeChargingStation(chargingStation);
+ await ocpiClient.udpateChargingStationStatus(chargingStation, status);
}
} catch (error) {
await Logging.logError({
@@ -109,14 +115,14 @@ export default class ChargingStationService {
});
}
}
- }
+ // OICP handling
if (Utils.isComponentActiveFromToken(req.user, TenantComponents.OICP)) {
let actionType = OICPActionType.INSERT;
- if (filteredRequest.public === false) {
+ if (!filteredRequest.public) {
actionType = OICPActionType.DELETE;
}
try {
- const oicpClient: CpoOICPClient = await OICPClientFactory.getAvailableOicpClient(req.tenant, OCPIRole.CPO) as CpoOICPClient;
+ const oicpClient = await OICPClientFactory.getAvailableOicpClient(req.tenant, OCPIRole.CPO) as CpoOICPClient;
if (oicpClient) {
// Define get option
const options = {

MODIFY
src/server/rest/v1/service/SessionHashService.ts
src/server/rest/v1/service/SessionHashService.ts
@@ -6,7 +6,6 @@ import Cypher from '../../../../utils/Cypher';
import { HTTPError } from '../../../../types/HTTPError';
import Logging from '../../../../utils/Logging';
import { ServerAction } from '../../../../types/Server';
-import { StatusCodes } from 'http-status-codes';
import Tenant from '../../../../types/Tenant';
import TenantStorage from '../../../../storage/mongodb/TenantStorage';
import User from '../../../../types/User';

Handle charging station public flag in OCPI. Added check of EVSEs in checkLocations(). Tested CPO client. Added tenant/user hash check in new RESTful router.