UNPKG

@citrineos/base

Version:

The base module for OCPP v2.0.1 including all interfaces. This module is not intended to be used directly, but rather as a dependency for other modules.

543 lines 24.2 kB
"use strict"; // Copyright (c) 2023 S44, LLC // Copyright Contributors to the CitrineOS Project // // SPDX-License-Identifier: Apache 2.0 Object.defineProperty(exports, "__esModule", { value: true }); exports.RbacRulesSchema = exports.TenantSchema = exports.UrlPatternSchema = exports.HttpMethodSchema = exports.systemConfigSchema = exports.websocketServerSchema = exports.systemConfigInputSchema = exports.websocketServerInputSchema = void 0; const zod_1 = require("zod"); const model_1 = require("../ocpp/model"); const message_1 = require("../ocpp/rpc/message"); const messages_1 = require("../interfaces/messages"); const OCPP1_6_CallActionSchema = zod_1.z.nativeEnum(message_1.OCPP1_6_CallAction); const OCPP2_0_1_CallActionSchema = zod_1.z.nativeEnum(message_1.OCPP2_0_1_CallAction); const CallActionSchema = zod_1.z.union([OCPP1_6_CallActionSchema, OCPP2_0_1_CallActionSchema]); // TODO: Refactor other objects out of system config, such as certificatesModuleInputSchema etc. exports.websocketServerInputSchema = zod_1.z.object({ id: zod_1.z.string().optional(), host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(8080).optional(), pingInterval: zod_1.z.number().int().positive().default(60).optional(), protocol: zod_1.z.enum(['ocpp1.6', 'ocpp2.0.1']).default('ocpp2.0.1').optional(), securityProfile: zod_1.z.number().int().min(0).max(3).default(0).optional(), allowUnknownChargingStations: zod_1.z.boolean().default(false).optional(), tlsKeyFilePath: zod_1.z.string().optional(), // Leaf certificate's private key pem which decrypts the message from client tlsCertificateChainFilePath: zod_1.z.string().optional(), // Certificate chain pem consist of a leaf followed by sub CAs mtlsCertificateAuthorityKeyFilePath: zod_1.z.string().optional(), // Sub CA's private key which signs the leaf (e.g., // charging station certificate and csms certificate) rootCACertificateFilePath: zod_1.z.string().optional(), // Root CA certificate that overrides default CA certificates // allowed by Mozilla tenantId: zod_1.z.number(), }); exports.systemConfigInputSchema = zod_1.z.object({ env: zod_1.z.enum(['development', 'production']), centralSystem: zod_1.z.object({ host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(8081).optional(), }), modules: zod_1.z.object({ certificates: zod_1.z .object({ endpointPrefix: zod_1.z.string().default(messages_1.EventGroup.Certificates).optional(), host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(8081).optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }) .optional(), configuration: zod_1.z.object({ heartbeatInterval: zod_1.z.number().int().positive().default(60).optional(), bootRetryInterval: zod_1.z.number().int().positive().default(10).optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), ocpp2_0_1: zod_1.z .object({ unknownChargerStatus: zod_1.z .enum([ model_1.OCPP2_0_1.RegistrationStatusEnumType.Accepted, model_1.OCPP2_0_1.RegistrationStatusEnumType.Pending, model_1.OCPP2_0_1.RegistrationStatusEnumType.Rejected, ]) .default(model_1.OCPP2_0_1.RegistrationStatusEnumType.Accepted) .optional(), // Unknown chargers have no entry in BootConfig table getBaseReportOnPending: zod_1.z.boolean().default(true).optional(), bootWithRejectedVariables: zod_1.z.boolean().default(true).optional(), autoAccept: zod_1.z.boolean().default(true).optional(), // If false, only data endpoint can update boot status to accepted }) .optional(), ocpp1_6: zod_1.z .object({ unknownChargerStatus: zod_1.z .enum([ model_1.OCPP1_6.BootNotificationResponseStatus.Accepted, model_1.OCPP1_6.BootNotificationResponseStatus.Pending, model_1.OCPP1_6.BootNotificationResponseStatus.Rejected, ]) .default(model_1.OCPP1_6.BootNotificationResponseStatus.Accepted) .optional(), // Unknown chargers have no entry in BootConfig table }) .optional(), endpointPrefix: zod_1.z.string().default(messages_1.EventGroup.Configuration).optional(), host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(8081).optional(), }), evdriver: zod_1.z.object({ endpointPrefix: zod_1.z.string().default(messages_1.EventGroup.EVDriver).optional(), host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(8081).optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }), monitoring: zod_1.z.object({ endpointPrefix: zod_1.z.string().default(messages_1.EventGroup.Monitoring).optional(), host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(8081).optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }), reporting: zod_1.z.object({ endpointPrefix: zod_1.z.string().default(messages_1.EventGroup.Reporting).optional(), host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(8081).optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }), smartcharging: zod_1.z .object({ endpointPrefix: zod_1.z.string().default(messages_1.EventGroup.SmartCharging).optional(), host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(8081).optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }) .optional(), tenant: zod_1.z .object({ endpointPrefix: zod_1.z.string().default(messages_1.EventGroup.Tenant).optional(), host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(8081).optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }) .optional(), transactions: zod_1.z.object({ endpointPrefix: zod_1.z.string().default(messages_1.EventGroup.Transactions).optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(8081).optional(), costUpdatedInterval: zod_1.z.number().int().positive().default(60).optional(), sendCostUpdatedOnMeterValue: zod_1.z.boolean().default(false).optional(), signedMeterValuesConfiguration: zod_1.z .object({ publicKeyFileId: zod_1.z.string(), signingMethod: zod_1.z.enum(['RSASSA-PKCS1-v1_5', 'ECDSA']), }) .optional(), }), }), util: zod_1.z.object({ cache: zod_1.z .object({ memory: zod_1.z.boolean().optional(), redis: zod_1.z .object({ host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(6379).optional(), }) .optional(), }) .refine((obj) => obj.memory || obj.redis, { message: 'A cache implementation must be set', }), messageBroker: zod_1.z .object({ kafka: zod_1.z .object({ topicPrefix: zod_1.z.string().optional(), topicName: zod_1.z.string().optional(), brokers: zod_1.z.array(zod_1.z.string()), sasl: zod_1.z.object({ mechanism: zod_1.z.string(), username: zod_1.z.string(), password: zod_1.z.string(), }), }) .optional(), amqp: zod_1.z .object({ url: zod_1.z.string(), exchange: zod_1.z.string(), }) .optional(), }) .refine((obj) => obj.kafka || obj.amqp, { message: 'A message broker implementation must be set', }), authProvider: zod_1.z .object({ oidc: zod_1.z .object({ jwksUri: zod_1.z.string(), issuer: zod_1.z.string(), audience: zod_1.z.string(), cacheTime: zod_1.z.number().int().positive().optional(), rateLimit: zod_1.z.boolean().default(false).optional(), }) .optional(), localByPass: zod_1.z.boolean().default(false).optional(), }) .refine((obj) => obj.oidc || obj.localByPass, { message: 'An auth provider implementation must be set', }), swagger: zod_1.z .object({ path: zod_1.z.string().default('/docs').optional(), logoPath: zod_1.z.string(), exposeData: zod_1.z.boolean().default(true).optional(), exposeMessage: zod_1.z.boolean().default(true).optional(), }) .optional(), networkConnection: zod_1.z.object({ websocketServers: zod_1.z.array(exports.websocketServerInputSchema.optional()), }), certificateAuthority: zod_1.z.object({ v2gCA: zod_1.z .object({ name: zod_1.z.enum(['hubject']).default('hubject'), hubject: zod_1.z .object({ baseUrl: zod_1.z.string().default('https://open.plugncharge-test.hubject.com'), tokenUrl: zod_1.z .string() .default('https://hubject.stoplight.io/api/v1/projects/cHJqOjk0NTg5/nodes/6bb8b3bc79c2e-authorization-token'), isoVersion: zod_1.z.enum(['ISO15118-2', 'ISO15118-20']).default('ISO15118-2'), }) .optional(), }) .refine((obj) => { if (obj.name === 'hubject') { return obj.hubject; } else { return false; } }), chargingStationCA: zod_1.z .object({ name: zod_1.z.enum(['acme']).default('acme'), acme: zod_1.z .object({ env: zod_1.z.enum(['staging', 'production']).default('staging'), accountKeyFilePath: zod_1.z.string(), email: zod_1.z.string(), }) .optional(), }) .refine((obj) => { if (obj.name === 'acme') { return obj.acme; } else { return false; } }), }), }), logLevel: zod_1.z.number().min(0).max(6).default(0).optional(), maxCallLengthSeconds: zod_1.z.number().int().positive().default(5).optional(), maxCachingSeconds: zod_1.z.number().int().positive().default(10).optional(), ocpiServer: zod_1.z.object({ host: zod_1.z.string().default('localhost').optional(), port: zod_1.z.number().int().positive().default(8085).optional(), }), userPreferences: zod_1.z.object({ telemetryConsent: zod_1.z.boolean().default(false).optional(), }), rbacRulesFileName: zod_1.z.string().default('rbac-rules.json').optional(), rbacRulesDir: zod_1.z.string().optional(), }); exports.websocketServerSchema = zod_1.z .object({ id: zod_1.z.string(), host: zod_1.z.string(), port: zod_1.z.number().int().positive(), pingInterval: zod_1.z.number().int().positive(), protocol: zod_1.z.enum(['ocpp1.6', 'ocpp2.0.1']), securityProfile: zod_1.z.number().int().min(0).max(3), allowUnknownChargingStations: zod_1.z.boolean(), tlsKeyFilePath: zod_1.z.string().optional(), tlsCertificateChainFilePath: zod_1.z.string().optional(), mtlsCertificateAuthorityKeyFilePath: zod_1.z.string().optional(), rootCACertificateFilePath: zod_1.z.string().optional(), tenantId: zod_1.z.number(), }) .refine((obj) => { switch (obj.securityProfile) { case 0: // No security case 1: // Basic Auth return true; case 2: // Basic Auth + TLS return obj.tlsKeyFilePath && obj.tlsCertificateChainFilePath; case 3: // mTLS return (obj.tlsCertificateChainFilePath && obj.tlsKeyFilePath && obj.mtlsCertificateAuthorityKeyFilePath); default: return false; } }); exports.systemConfigSchema = zod_1.z .object({ env: zod_1.z.enum(['development', 'production']), centralSystem: zod_1.z.object({ host: zod_1.z.string(), port: zod_1.z.number().int().positive(), }), modules: zod_1.z.object({ certificates: zod_1.z .object({ endpointPrefix: zod_1.z.string(), host: zod_1.z.string().optional(), port: zod_1.z.number().int().positive().optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }) .optional(), evdriver: zod_1.z.object({ endpointPrefix: zod_1.z.string(), host: zod_1.z.string().optional(), port: zod_1.z.number().int().positive().optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }), configuration: zod_1.z .object({ heartbeatInterval: zod_1.z.number().int().positive(), bootRetryInterval: zod_1.z.number().int().positive(), ocpp2_0_1: zod_1.z .object({ unknownChargerStatus: zod_1.z.enum([ model_1.OCPP2_0_1.RegistrationStatusEnumType.Accepted, model_1.OCPP2_0_1.RegistrationStatusEnumType.Pending, model_1.OCPP2_0_1.RegistrationStatusEnumType.Rejected, ]), // Unknown chargers have no entry in BootConfig table getBaseReportOnPending: zod_1.z.boolean(), bootWithRejectedVariables: zod_1.z.boolean(), /** * If false, only data endpoint can update boot status to accepted */ autoAccept: zod_1.z.boolean(), }) .optional(), ocpp1_6: zod_1.z .object({ unknownChargerStatus: zod_1.z.enum([ model_1.OCPP1_6.BootNotificationResponseStatus.Accepted, model_1.OCPP1_6.BootNotificationResponseStatus.Pending, model_1.OCPP1_6.BootNotificationResponseStatus.Rejected, ]), // Unknown chargers have no entry in BootConfig table }) .optional(), endpointPrefix: zod_1.z.string(), host: zod_1.z.string().optional(), port: zod_1.z.number().int().positive().optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }) .refine((obj) => obj.ocpp1_6 || obj.ocpp2_0_1, { message: 'A protocol configuration must be set', }), // Configuration module is required monitoring: zod_1.z.object({ endpointPrefix: zod_1.z.string(), host: zod_1.z.string().optional(), port: zod_1.z.number().int().positive().optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }), reporting: zod_1.z.object({ endpointPrefix: zod_1.z.string(), host: zod_1.z.string().optional(), port: zod_1.z.number().int().positive().optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }), smartcharging: zod_1.z .object({ endpointPrefix: zod_1.z.string(), host: zod_1.z.string().optional(), port: zod_1.z.number().int().positive().optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }) .optional(), tenant: zod_1.z.object({ endpointPrefix: zod_1.z.string(), host: zod_1.z.string().optional(), port: zod_1.z.number().int().positive().optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), }), transactions: zod_1.z .object({ endpointPrefix: zod_1.z.string(), host: zod_1.z.string().optional(), port: zod_1.z.number().int().positive().optional(), costUpdatedInterval: zod_1.z.number().int().positive().optional(), sendCostUpdatedOnMeterValue: zod_1.z.boolean().optional(), requests: zod_1.z.array(CallActionSchema), responses: zod_1.z.array(CallActionSchema), signedMeterValuesConfiguration: zod_1.z .object({ publicKeyFileId: zod_1.z.string(), signingMethod: zod_1.z.enum(['RSASSA-PKCS1-v1_5', 'ECDSA']), }) .optional(), }) .refine((obj) => !(obj.costUpdatedInterval && obj.sendCostUpdatedOnMeterValue) && (obj.costUpdatedInterval || obj.sendCostUpdatedOnMeterValue), { message: 'Can only update cost based on the interval or in response to a transaction event /meter value' + ' update. Not allowed to have both costUpdatedInterval and sendCostUpdatedOnMeterValue configured', }), }), util: zod_1.z.object({ cache: zod_1.z .object({ memory: zod_1.z.boolean().optional(), redis: zod_1.z .object({ host: zod_1.z.string(), port: zod_1.z.number().int().positive(), }) .optional(), }) .refine((obj) => obj.memory || obj.redis, { message: 'A cache implementation must be set', }), messageBroker: zod_1.z .object({ kafka: zod_1.z .object({ topicPrefix: zod_1.z.string().optional(), topicName: zod_1.z.string().optional(), brokers: zod_1.z.array(zod_1.z.string()), sasl: zod_1.z.object({ mechanism: zod_1.z.string(), username: zod_1.z.string(), password: zod_1.z.string(), }), }) .optional(), amqp: zod_1.z .object({ url: zod_1.z.string(), exchange: zod_1.z.string(), }) .optional(), }) .refine((obj) => obj.kafka || obj.amqp, { message: 'A message broker implementation must be set', }), authProvider: zod_1.z .object({ oidc: zod_1.z .object({ jwksUri: zod_1.z.string(), issuer: zod_1.z.string(), audience: zod_1.z.string(), cacheTime: zod_1.z.number().int().positive().optional(), rateLimit: zod_1.z.boolean(), }) .optional(), localByPass: zod_1.z.boolean().default(false).optional(), }) .refine((obj) => obj.oidc || obj.localByPass, { message: 'An auth provider implementation must be set', }), swagger: zod_1.z .object({ path: zod_1.z.string(), logoPath: zod_1.z.string(), exposeData: zod_1.z.boolean(), exposeMessage: zod_1.z.boolean(), }) .optional(), networkConnection: zod_1.z.object({ websocketServers: zod_1.z.array(exports.websocketServerSchema).refine((array) => { const idsSeen = new Set(); return array.filter((obj) => { if (idsSeen.has(obj.id)) { return false; } else { idsSeen.add(obj.id); return true; } }); }), }), certificateAuthority: zod_1.z.object({ v2gCA: zod_1.z .object({ name: zod_1.z.enum(['hubject']), hubject: zod_1.z .object({ baseUrl: zod_1.z.string(), tokenUrl: zod_1.z.string(), isoVersion: zod_1.z.enum(['ISO15118-2', 'ISO15118-20']), }) .optional(), }) .refine((obj) => { if (obj.name === 'hubject') { return obj.hubject; } else { return false; } }), chargingStationCA: zod_1.z .object({ name: zod_1.z.enum(['acme']), acme: zod_1.z .object({ env: zod_1.z.enum(['staging', 'production']), accountKeyFilePath: zod_1.z.string(), email: zod_1.z.string(), }) .optional(), }) .refine((obj) => { if (obj.name === 'acme') { return obj.acme; } else { return false; } }), }), }), logLevel: zod_1.z.number().min(0).max(6), maxCallLengthSeconds: zod_1.z.number().int().positive(), maxCachingSeconds: zod_1.z.number().int().positive(), ocpiServer: zod_1.z.object({ host: zod_1.z.string(), port: zod_1.z.number().int().positive(), }), userPreferences: zod_1.z.object({ telemetryConsent: zod_1.z.boolean().optional(), }), rbacRulesFileName: zod_1.z.string().optional(), rbacRulesDir: zod_1.z.string().optional(), }) .refine((obj) => obj.maxCachingSeconds >= obj.maxCallLengthSeconds, { message: 'maxCachingSeconds cannot be less than maxCallLengthSeconds', }); exports.HttpMethodSchema = zod_1.z.record(zod_1.z.string(), // HTTP method (GET, POST, etc., or * for all methods) zod_1.z.array(zod_1.z.string())); exports.UrlPatternSchema = zod_1.z.record(zod_1.z.string(), // URL pattern (/api/users, /api/users/:id, etc.) exports.HttpMethodSchema); exports.TenantSchema = zod_1.z.record(zod_1.z.string(), // Tenant ID exports.UrlPatternSchema); exports.RbacRulesSchema = exports.TenantSchema; //# sourceMappingURL=types.js.map