Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/target
/.idea
*.iml
.DS_Store
.DS_Store.env
44 changes: 38 additions & 6 deletions src/main/resources/application-prod.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ db.ip = localhost
db.port = 3306
db.schema = stevedb
db.user = steve
db.password = changeme
db.password = StevePass2026!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. db.password plaintext in prod 📘 Rule violation ⛨ Security

The production properties file now contains a hardcoded database password in plaintext. This
violates boundary security hardening requirements and risks credential leakage via source control or
deployments.
Agent Prompt
## Issue description
`application-prod.properties` contains a plaintext database password (`db.password`), which is prohibited for production secret storage.

## Issue Context
Compliance requires secrets not be persisted in plaintext in repo-tracked configuration.

## Fix Focus Areas
- src/main/resources/application-prod.properties[13-13]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


# Credentials for Web interface access
#
Expand All @@ -20,18 +20,26 @@ auth.password = 1234
# The header key and value for Web API access using API key authorization.
# Both must be set for Web APIs to be enabled. Otherwise, we will block all calls.
#
webapi.key = STEVE-API-KEY
webapi.value =
#webapi.key = STEVE-API-KEY
#webapi.value =
Comment on lines +23 to +24

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. Unresolved webapi placeholders 🐞 Bug ✓ Correctness

In prod, webapi.key and webapi.value are commented out, but application.yml still requires
${webapi.key}/${webapi.value}, which can prevent Spring from resolving placeholders and stop SteVe
from starting in the prod profile.
Agent Prompt
### Issue description
Prod profile comments out `webapi.key`/`webapi.value`, but `application.yml` still references `${webapi.key}`/`${webapi.value}`. This can lead to unresolved placeholders and application startup failure.

### Issue Context
`steve.auth.web-api-key` and `steve.auth.web-api-secret` are bound from these placeholders.

### Fix Focus Areas
- src/main/resources/application-prod.properties[20-25]
- src/main/resources/application.yml[38-47]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


# ─────────────────────────────────────────────────────
# REST API CONFIGURATION (ADD THESE)
# ─────────────────────────────────────────────────────
rest.enabled=true
rest.api.path=/steve/api/v1

# Jetty configuration
#
server.host = 127.0.0.1
#server.host = 0.0.0.0
server.address = 0.0.0.0
server.port = 8080
server.gzip.enabled = true
Comment on lines +34 to 37

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

5. Prod service exposed publicly 🐞 Bug ⛨ Security

application-prod.properties binds the server to 0.0.0.0 and enables extensive DEBUG logging,
increasing exposure of the admin interface and sensitive operational data in logs.
Agent Prompt
### Issue description
Prod config binds on `0.0.0.0` and enables DEBUG logging broadly, while retaining weak default admin credentials.

### Issue Context
This combination significantly increases attack surface and risks log-based data exposure.

### Fix Focus Areas
- src/main/resources/application-prod.properties[15-19]
- src/main/resources/application-prod.properties[32-37]
- src/main/resources/application-prod.properties[90-98]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


# Jetty HTTP configuration
#
http.enabled = true
http.port = 8080
#http.enabled = true
#http.port = 8080

# Jetty HTTPS configuration
#
Expand All @@ -40,6 +48,11 @@ https.port = 8443
keystore.path =
keystore.password =

# OCPP WebSocket Server (CRITICAL for chargers)
#
ocpp.j.server.port = 8880
ocpp.j.server.host = 0.0.0.0

# When the WebSocket/Json charge point opens more than one WebSocket connection,
# we need a mechanism/strategy to select one of them for outgoing requests.
# For allowed values see de.rwth.idsg.steve.ocpp.ws.custom.WsSessionSelectStrategyEnum.
Expand All @@ -64,3 +77,22 @@ charge-box-id.validation.regex =
### DO NOT MODIFY ###
db.sql.logging = false
profile = prod

# SteVe Webhook Plugin Environment Variables
voltstartev.webhook.url=http://localhost:3000/api/webhooks/steve
voltstartev.webhook.secret=your-super-secret-webhook-key-min-32-characters

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. voltstartev.webhook.secret in prod 📘 Rule violation ⛨ Security

The production configuration adds a webhook shared secret value directly in the properties file.
Storing shared secrets in plaintext configuration violates secret storage hardening and increases
leak risk.
Agent Prompt
## Issue description
Production config stores `voltstartev.webhook.secret` as plaintext in a repo-tracked properties file.

## Issue Context
Webhook shared secrets should be injected at runtime (env var / secret manager), not committed.

## Fix Focus Areas
- src/main/resources/application-prod.properties[83-83]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

voltstartev.webhook.connect-timeout=3000
voltstartev.webhook.read-timeout=5000
voltstartev.webhook.retry-max-attempts=3
voltstartev.webhook.meter-values-sampling-enabled=true
voltstartev.webhook.meter-values-min-interval-seconds=30
# ─────────────────────────────────────────────────────
# 🔍 DEBUG LOGGING (Enable for troubleshooting)
# ─────────────────────────────────────────────────────
logging.level.de.rwth.idsg.steve=DEBUG
logging.level.de.rwth.idsg.steve.ocpp=DEBUG
logging.level.de.rwth.idsg.steve.ocpp.ws=DEBUG
logging.level.de.rwth.idsg.steve.service.reservation=DEBUG
logging.level.de.rwth.idsg.steve.service.transaction=DEBUG
logging.level.de.rwth.idsg.steve.web.api=DEBUG
logging.level.de.rwth.idsg.steve.ws=DEBUG
14 changes: 14 additions & 0 deletions steve-webhook-plugin/add-license.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

HEADER_FILE="steve-header.txt"

find . -name "*.java" | while read file; do
if ! grep -q "Licensed under the Apache License" "$file"; then
echo "Adding header to $file"
cat "$HEADER_FILE" "$file" > temp && mv temp "$file"
else
echo "Header already exists in $file"
fi
done

echo "License headers added."
66 changes: 66 additions & 0 deletions steve-webhook-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>

<groupId>com.voltstartev</groupId>
<artifactId>steve-webhook-plugin</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>

<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- HTTP Client with connection pooling -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>

<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

<!-- Metrics (optional) -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

<!-- SteVe Core (provided by SteVe runtime) -->
<dependency>
<groupId>de.rwth.idsg</groupId>
<artifactId>steve</artifactId>
<version>3.11.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
Comment on lines +19 to +56

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

6. Missing lombok dependency 🐞 Bug ✓ Correctness

steve-webhook-plugin uses Lombok annotations (@Data, @AllArgsConstructor, etc.) but does not declare
Lombok in its pom.xml, which will fail compilation of the plugin module.
Agent Prompt
### Issue description
Plugin module uses Lombok annotations but does not depend on Lombok, causing compilation failure.

### Issue Context
Multiple plugin classes rely on Lombok-generated methods/constructors.

### Fix Focus Areas
- steve-webhook-plugin/pom.xml[19-56]
- steve-webhook-plugin/src/main/java/com/voltstartev/steve/plugin/config/WebhookProperties.java[17-24]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve
* Copyright (C) 2013-2026 SteVe Community Team
* Copyright (C) 2026 VoltStar
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package com.voltstartev.steve.plugin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling // For retry worker
public class SteveWebhookPluginApplication {
public static void main(String[] args) {
SpringApplication.run(SteveWebhookPluginApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve
* Copyright (C) 2013-2026 SteVe Community Team
* Copyright (C) 2026 VoltStar
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package com.voltstartev.steve.plugin.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfig {

private final WebhookProperties properties;

public AsyncConfig(WebhookProperties properties) {
this.properties = properties;
}

@Bean(name = "webhookExecutor")
public Executor webhookExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("webhook-");
executor.setRejectedExecutionHandler(
new ThreadPoolExecutor.CallerRunsPolicy() // Backpressure: run in caller thread if queue full
);
Comment on lines +34 to +43

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

7. Missing imports break plugin 🐞 Bug ✓ Correctness

steve-webhook-plugin references ThreadPoolExecutor and WebhookProperties without importing them,
which prevents the module from compiling.
Agent Prompt
### Issue description
Plugin sources reference types that are not imported, causing compilation to fail.

### Issue Context
`AsyncConfig` uses `ThreadPoolExecutor.CallerRunsPolicy`, and multiple classes reference `WebhookProperties` without importing it.

### Fix Focus Areas
- steve-webhook-plugin/src/main/java/com/voltstartev/steve/plugin/config/AsyncConfig.java[17-46]
- steve-webhook-plugin/src/main/java/com/voltstartev/steve/plugin/listener/OcppEventListener.java[17-42]
- steve-webhook-plugin/src/main/java/com/voltstartev/steve/plugin/retry/WebhookRetryQueue.java[17-38]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(30);
executor.initialize();
return executor;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve
* Copyright (C) 2013-2026 SteVe Community Team
* Copyright (C) 2026 VoltStar
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package com.voltstartev.steve.plugin.config;

import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.util.Timeout;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.TimeUnit;

@Configuration
public class HttpClientConfig {

private final WebhookProperties properties;

public HttpClientConfig(WebhookProperties properties) {
this.properties = properties;
}

@Bean
public RestTemplate restTemplate() {
// Connection pooling
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(properties.getMaxConnections());
connectionManager.setDefaultMaxPerRoute(properties.getMaxConnectionsPerRoute());
connectionManager.setValidateAfterInactivity(5000);

// Request config
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(Timeout.ofMilliseconds(properties.getConnectTimeout()))
.setResponseTimeout(Timeout.ofMilliseconds(properties.getReadTimeout()))
.build();

// Build HTTP client
HttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.evictIdleConnections(30, TimeUnit.SECONDS)
.build();

// Create RestTemplate with factory
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);

return new RestTemplate(factory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve
* Copyright (C) 2013-2026 SteVe Community Team
* Copyright (C) 2026 VoltStar
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package com.voltstartev.steve.plugin.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "voltstartev.webhook")
public class WebhookProperties {
private String url = "http://localhost:3000/api/webhooks/steve";
private String secret = ""; // Shared secret for HMAC signature
private int connectTimeout = 3000;
private int readTimeout = 5000;
private int maxConnections = 50;
private int maxConnectionsPerRoute = 20;
private int retryMaxAttempts = 3;
private long retryInitialDelayMs = 500;
private boolean meterValuesSamplingEnabled = true;
private int meterValuesMinEnergyDeltaWh = 100; // Only send if energy changed by >100Wh
private int meterValuesMinIntervalSeconds = 30; // Or if >30s since last send
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve
* Copyright (C) 2013-2026 SteVe Community Team
* Copyright (C) 2026 VoltStar
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package com.voltstartev.steve.plugin.events;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.Instant;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ConnectorStatusEvent implements SteveWebhookEvent {
private String chargeBoxId;
private Integer connectorId;
private String status; // OCPP ConnectorStatus enum value
private String errorCode;
private String info;
private Instant eventTimestamp; // From OCPP message
private Instant receivedAt; // When SteVe received it

@Override
public String getChargeBoxId() { return chargeBoxId; }

@Override
public Integer getConnectorId() { return connectorId; }

@Override
public Instant getEventTimestamp() { return eventTimestamp; }

@Override
public Instant getReceivedAt() { return receivedAt; }
}
Loading