From 127a321621c62dc66fd217b69c23b2d1c89427c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joe=20K=C3=BCng?= Date: Tue, 3 Mar 2026 13:09:55 +0100 Subject: [PATCH] fix(deploy): fix security --- .gitea/workflows/deploy.yaml | 1 + .gitea/workflows/pr-checks.yaml | 11 +- .../CustomQuoteRequestController.java | 75 ++++++++++- .../admin/AdminOrderController.java | 32 ++++- .../service/FileSystemStorageService.java | 37 ++++-- .../service/SlicerService.java | 76 ++++++++--- .../service/TwintPaymentService.java | 6 +- .../resources/application-local.properties | 5 +- .../src/main/resources/application.properties | 6 +- .../email/contact-request-admin.html | 121 ++++++++++++++++++ .../controller/AdminAuthSecurityTest.java | 2 +- docker-compose.yml | 2 +- 12 files changed, 326 insertions(+), 48 deletions(-) create mode 100644 backend/src/main/resources/templates/email/contact-request-admin.html diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 591d162..ebdb49a 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -20,6 +20,7 @@ jobs: with: java-version: "21" distribution: "temurin" + cache: gradle - name: Run Tests with Gradle run: | diff --git a/.gitea/workflows/pr-checks.yaml b/.gitea/workflows/pr-checks.yaml index e341dfa..46d4971 100644 --- a/.gitea/workflows/pr-checks.yaml +++ b/.gitea/workflows/pr-checks.yaml @@ -108,7 +108,15 @@ jobs: - name: Run Gitleaks (secrets scan) shell: bash run: | - gitleaks detect --source . --no-git --redact --exit-code 1 + set +e + gitleaks detect --source . --no-git --redact --exit-code 1 \ + --report-format json --report-path /tmp/gitleaks-report.json + rc=$? + if [[ $rc -ne 0 ]]; then + echo "Gitleaks findings:" + cat /tmp/gitleaks-report.json + fi + exit $rc test-backend: runs-on: ubuntu-latest @@ -121,6 +129,7 @@ jobs: with: java-version: "21" distribution: "temurin" + cache: gradle - name: Run Tests with Gradle run: | diff --git a/backend/src/main/java/com/printcalculator/controller/CustomQuoteRequestController.java b/backend/src/main/java/com/printcalculator/controller/CustomQuoteRequestController.java index b3918c5..b407bcb 100644 --- a/backend/src/main/java/com/printcalculator/controller/CustomQuoteRequestController.java +++ b/backend/src/main/java/com/printcalculator/controller/CustomQuoteRequestController.java @@ -1,18 +1,24 @@ package com.printcalculator.controller; +import com.printcalculator.dto.QuoteRequestDto; import com.printcalculator.entity.CustomQuoteRequest; import com.printcalculator.entity.CustomQuoteRequestAttachment; import com.printcalculator.repository.CustomQuoteRequestAttachmentRepository; import com.printcalculator.repository.CustomQuoteRequestRepository; +import com.printcalculator.service.ClamAVService; +import com.printcalculator.service.email.EmailNotificationService; +import jakarta.validation.Valid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.StringUtils; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.multipart.MultipartFile; -import jakarta.validation.Valid; import java.io.IOException; import java.io.InputStream; @@ -22,8 +28,11 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.time.OffsetDateTime; +import java.time.Year; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.regex.Pattern; @@ -32,10 +41,18 @@ import java.util.regex.Pattern; @RequestMapping("/api/custom-quote-requests") public class CustomQuoteRequestController { + private static final Logger logger = LoggerFactory.getLogger(CustomQuoteRequestController.class); private final CustomQuoteRequestRepository requestRepo; private final CustomQuoteRequestAttachmentRepository attachmentRepo; - private final com.printcalculator.service.ClamAVService clamAVService; - + private final ClamAVService clamAVService; + private final EmailNotificationService emailNotificationService; + + @Value("${app.mail.contact-request.admin.enabled:true}") + private boolean contactRequestAdminMailEnabled; + + @Value("${app.mail.contact-request.admin.address:infog@3d-fab.ch}") + private String contactRequestAdminMailAddress; + // TODO: Inject Storage Service private static final Path STORAGE_ROOT = Paths.get("storage_requests").toAbsolutePath().normalize(); private static final Pattern SAFE_EXTENSION_PATTERN = Pattern.compile("^[a-z0-9]{1,10}$"); @@ -59,17 +76,19 @@ public class CustomQuoteRequestController { public CustomQuoteRequestController(CustomQuoteRequestRepository requestRepo, CustomQuoteRequestAttachmentRepository attachmentRepo, - com.printcalculator.service.ClamAVService clamAVService) { + ClamAVService clamAVService, + EmailNotificationService emailNotificationService) { this.requestRepo = requestRepo; this.attachmentRepo = attachmentRepo; this.clamAVService = clamAVService; + this.emailNotificationService = emailNotificationService; } // 1. Create Custom Quote Request @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Transactional public ResponseEntity createCustomQuoteRequest( - @Valid @RequestPart("request") com.printcalculator.dto.QuoteRequestDto requestDto, + @Valid @RequestPart("request") QuoteRequestDto requestDto, @RequestPart(value = "files", required = false) List files ) throws IOException { if (!requestDto.isAcceptTerms() || !requestDto.isAcceptPrivacy()) { @@ -96,6 +115,7 @@ public class CustomQuoteRequestController { request = requestRepo.save(request); // 2. Handle Attachments + int attachmentsCount = 0; if (files != null && !files.isEmpty()) { if (files.size() > 15) { throw new IOException("Too many files. Max 15 allowed."); @@ -148,9 +168,12 @@ public class CustomQuoteRequestController { try (InputStream inputStream = file.getInputStream()) { Files.copy(inputStream, absolutePath, StandardCopyOption.REPLACE_EXISTING); } + attachmentsCount++; } } - + + sendAdminContactRequestNotification(request, attachmentsCount); + return ResponseEntity.ok(request); } @@ -203,4 +226,42 @@ public class CustomQuoteRequestController { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid attachment path"); } } + + private void sendAdminContactRequestNotification(CustomQuoteRequest request, int attachmentsCount) { + if (!contactRequestAdminMailEnabled) { + return; + } + if (contactRequestAdminMailAddress == null || contactRequestAdminMailAddress.isBlank()) { + logger.warn("Contact request admin notification enabled but no admin address configured."); + return; + } + + Map templateData = new HashMap<>(); + templateData.put("requestId", request.getId()); + templateData.put("createdAt", request.getCreatedAt()); + templateData.put("requestType", safeValue(request.getRequestType())); + templateData.put("customerType", safeValue(request.getCustomerType())); + templateData.put("name", safeValue(request.getName())); + templateData.put("companyName", safeValue(request.getCompanyName())); + templateData.put("contactPerson", safeValue(request.getContactPerson())); + templateData.put("email", safeValue(request.getEmail())); + templateData.put("phone", safeValue(request.getPhone())); + templateData.put("message", safeValue(request.getMessage())); + templateData.put("attachmentsCount", attachmentsCount); + templateData.put("currentYear", Year.now().getValue()); + + emailNotificationService.sendEmail( + contactRequestAdminMailAddress, + "Nuova richiesta di contatto #" + request.getId(), + "contact-request-admin", + templateData + ); + } + + private String safeValue(String value) { + if (value == null || value.isBlank()) { + return "-"; + } + return value; + } } diff --git a/backend/src/main/java/com/printcalculator/controller/admin/AdminOrderController.java b/backend/src/main/java/com/printcalculator/controller/admin/AdminOrderController.java index b8d3c46..0fa09b3 100644 --- a/backend/src/main/java/com/printcalculator/controller/admin/AdminOrderController.java +++ b/backend/src/main/java/com/printcalculator/controller/admin/AdminOrderController.java @@ -30,6 +30,8 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; import java.nio.charset.StandardCharsets; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Locale; @@ -144,9 +146,13 @@ public class AdminOrderController { if (relativePath == null || relativePath.isBlank() || "PENDING".equals(relativePath)) { throw new ResponseStatusException(NOT_FOUND, "File not available"); } + Path safeRelativePath = resolveOrderItemRelativePath(relativePath, orderId, orderItemId); + if (safeRelativePath == null) { + throw new ResponseStatusException(NOT_FOUND, "File not available"); + } try { - Resource resource = storageService.loadAsResource(Paths.get(relativePath)); + Resource resource = storageService.loadAsResource(safeRelativePath); MediaType contentType = MediaType.APPLICATION_OCTET_STREAM; if (item.getMimeType() != null && !item.getMimeType().isBlank()) { try { @@ -276,9 +282,9 @@ public class AdminOrderController { private ResponseEntity generateDocument(Order order, boolean isConfirmation) { String displayOrderNumber = getDisplayOrderNumber(order); if (isConfirmation) { - String relativePath = "orders/" + order.getId() + "/documents/confirmation-" + displayOrderNumber + ".pdf"; + Path relativePath = buildConfirmationPdfRelativePath(order.getId(), displayOrderNumber); try { - byte[] existingPdf = storageService.loadAsResource(Paths.get(relativePath)).getInputStream().readAllBytes(); + byte[] existingPdf = storageService.loadAsResource(relativePath).getInputStream().readAllBytes(); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"confirmation-" + displayOrderNumber + ".pdf\"") .contentType(MediaType.APPLICATION_PDF) @@ -298,4 +304,24 @@ public class AdminOrderController { .contentType(MediaType.APPLICATION_PDF) .body(pdf); } + + private Path resolveOrderItemRelativePath(String storedRelativePath, UUID orderId, UUID orderItemId) { + try { + Path candidate = Path.of(storedRelativePath).normalize(); + if (candidate.isAbsolute()) { + return null; + } + Path expectedPrefix = Path.of("orders", orderId.toString(), "3d-files", orderItemId.toString()); + if (!candidate.startsWith(expectedPrefix)) { + return null; + } + return candidate; + } catch (InvalidPathException e) { + return null; + } + } + + private Path buildConfirmationPdfRelativePath(UUID orderId, String orderNumber) { + return Path.of("orders", orderId.toString(), "documents", "confirmation-" + orderNumber + ".pdf"); + } } diff --git a/backend/src/main/java/com/printcalculator/service/FileSystemStorageService.java b/backend/src/main/java/com/printcalculator/service/FileSystemStorageService.java index 7db4aa8..38e1e25 100644 --- a/backend/src/main/java/com/printcalculator/service/FileSystemStorageService.java +++ b/backend/src/main/java/com/printcalculator/service/FileSystemStorageService.java @@ -21,10 +21,12 @@ import java.nio.file.StandardCopyOption; public class FileSystemStorageService implements StorageService { private final Path rootLocation; + private final Path normalizedRootLocation; private final ClamAVService clamAVService; public FileSystemStorageService(@Value("${storage.location:storage_orders}") String storageLocation, ClamAVService clamAVService) { this.rootLocation = Paths.get(storageLocation); + this.normalizedRootLocation = this.rootLocation.toAbsolutePath().normalize(); this.clamAVService = clamAVService; } @@ -39,10 +41,7 @@ public class FileSystemStorageService implements StorageService { @Override public void store(MultipartFile file, Path destinationRelativePath) throws IOException { - Path destinationFile = this.rootLocation.resolve(destinationRelativePath).normalize().toAbsolutePath(); - if (!destinationFile.getParent().startsWith(this.rootLocation.toAbsolutePath())) { - throw new StorageException("Cannot store file outside current directory."); - } + Path destinationFile = resolveInsideStorage(destinationRelativePath); // 1. Salva prima il file su disco per evitare problemi di stream con file grandi Files.createDirectories(destinationFile.getParent()); @@ -63,32 +62,46 @@ public class FileSystemStorageService implements StorageService { @Override public void store(Path source, Path destinationRelativePath) throws IOException { - Path destinationFile = this.rootLocation.resolve(destinationRelativePath).normalize().toAbsolutePath(); - if (!destinationFile.getParent().startsWith(this.rootLocation.toAbsolutePath())) { - throw new StorageException("Cannot store file outside current directory."); - } + Path destinationFile = resolveInsideStorage(destinationRelativePath); Files.createDirectories(destinationFile.getParent()); Files.copy(source, destinationFile, StandardCopyOption.REPLACE_EXISTING); } @Override public void delete(Path path) throws IOException { - Path file = rootLocation.resolve(path); + Path file = resolveInsideStorage(path); Files.deleteIfExists(file); } @Override public Resource loadAsResource(Path path) throws IOException { try { - Path file = rootLocation.resolve(path); + Path file = resolveInsideStorage(path); Resource resource = new UrlResource(file.toUri()); if (resource.exists() || resource.isReadable()) { return resource; } else { - throw new RuntimeException("Could not read file: " + path); + throw new StorageException("Could not read file: " + path); } } catch (MalformedURLException e) { - throw new RuntimeException("Could not read file: " + path, e); + throw new StorageException("Could not read file: " + path, e); } } + + private Path resolveInsideStorage(Path relativePath) { + if (relativePath == null) { + throw new StorageException("Path is required."); + } + + Path normalizedRelative = relativePath.normalize(); + if (normalizedRelative.isAbsolute()) { + throw new StorageException("Cannot access absolute paths."); + } + + Path resolved = normalizedRootLocation.resolve(normalizedRelative).normalize(); + if (!resolved.startsWith(normalizedRootLocation)) { + throw new StorageException("Cannot access files outside storage root."); + } + return resolved; + } } diff --git a/backend/src/main/java/com/printcalculator/service/SlicerService.java b/backend/src/main/java/com/printcalculator/service/SlicerService.java index f9ef570..186a241 100644 --- a/backend/src/main/java/com/printcalculator/service/SlicerService.java +++ b/backend/src/main/java/com/printcalculator/service/SlicerService.java @@ -10,9 +10,9 @@ import org.springframework.stereotype.Service; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -30,7 +30,7 @@ public class SlicerService { private static final Pattern SIZE_Y_PATTERN = Pattern.compile("(?m)^\\s*size_y\\s*=\\s*([-+]?\\d+(?:\\.\\d+)?)\\s*$"); private static final Pattern SIZE_Z_PATTERN = Pattern.compile("(?m)^\\s*size_z\\s*=\\s*([-+]?\\d+(?:\\.\\d+)?)\\s*$"); - private final String slicerPath; + private final String trustedSlicerPath; private final ProfileManager profileManager; private final GCodeParser gCodeParser; private final ObjectMapper mapper; @@ -40,7 +40,7 @@ public class SlicerService { ProfileManager profileManager, GCodeParser gCodeParser, ObjectMapper mapper) { - this.slicerPath = slicerPath; + this.trustedSlicerPath = normalizeExecutablePath(slicerPath); this.profileManager = profileManager; this.gCodeParser = gCodeParser; this.mapper = mapper; @@ -83,17 +83,24 @@ public class SlicerService { basename = basename.substring(0, basename.length() - 4); } Path slicerLogPath = tempDir.resolve("orcaslicer.log"); + String machineProfilePath = requireSafeArgument(mFile.getAbsolutePath(), "machine profile path"); + String processProfilePath = requireSafeArgument(pFile.getAbsolutePath(), "process profile path"); + String filamentProfilePath = requireSafeArgument(fFile.getAbsolutePath(), "filament profile path"); + String outputDirPath = requireSafeArgument(tempDir.toAbsolutePath().toString(), "output directory path"); + String inputStlPath = requireSafeArgument(inputStl.getAbsolutePath(), "input STL path"); // 3. Run slicer. Retry with arrange only for out-of-volume style failures. for (boolean useArrange : new boolean[]{false, true}) { - List command = new ArrayList<>(); - command.add(slicerPath); + // Build process arguments explicitly to avoid shell interpretation and command injection. + ProcessBuilder pb = new ProcessBuilder(); + List command = pb.command(); + command.add(trustedSlicerPath); command.add("--load-settings"); - command.add(mFile.getAbsolutePath()); + command.add(machineProfilePath); command.add("--load-settings"); - command.add(pFile.getAbsolutePath()); + command.add(processProfilePath); command.add("--load-filaments"); - command.add(fFile.getAbsolutePath()); + command.add(filamentProfilePath); command.add("--ensure-on-bed"); if (useArrange) { command.add("--arrange"); @@ -102,13 +109,12 @@ public class SlicerService { command.add("--slice"); command.add("0"); command.add("--outputdir"); - command.add(tempDir.toAbsolutePath().toString()); - command.add(inputStl.getAbsolutePath()); + command.add(outputDirPath); + command.add(inputStlPath); logger.info("Executing Slicer" + (useArrange ? " (retry with arrange)" : "") + ": " + String.join(" ", command)); Files.deleteIfExists(slicerLogPath); - ProcessBuilder pb = new ProcessBuilder(command); pb.directory(tempDir.toFile()); pb.redirectErrorStream(true); pb.redirectOutput(slicerLogPath.toFile()); @@ -157,17 +163,17 @@ public class SlicerService { } public Optional inspectModelDimensions(File inputModel) { - Path tempDir = null; + Path tempDir = null; try { tempDir = Files.createTempDirectory("slicer_info_"); Path infoLogPath = tempDir.resolve("orcaslicer-info.log"); + String inputModelPath = requireSafeArgument(inputModel.getAbsolutePath(), "input model path"); - List command = new ArrayList<>(); - command.add(slicerPath); - command.add("--info"); - command.add(inputModel.getAbsolutePath()); - - ProcessBuilder pb = new ProcessBuilder(command); + ProcessBuilder pb = new ProcessBuilder(); + List infoCommand = pb.command(); + infoCommand.add(trustedSlicerPath); + infoCommand.add("--info"); + infoCommand.add(inputModelPath); pb.directory(tempDir.toFile()); pb.redirectErrorStream(true); pb.redirectOutput(infoLogPath.toFile()); @@ -267,4 +273,38 @@ public class SlicerService { || normalized.contains("no object is fully inside the print volume") || normalized.contains("calc_exclude_triangles"); } + + private String normalizeExecutablePath(String configuredPath) { + if (configuredPath == null || configuredPath.isBlank()) { + throw new IllegalArgumentException("slicer.path is required"); + } + if (containsControlChars(configuredPath)) { + throw new IllegalArgumentException("slicer.path contains invalid control characters"); + } + try { + return Path.of(configuredPath.trim()).normalize().toString(); + } catch (InvalidPathException e) { + throw new IllegalArgumentException("Invalid slicer.path: " + configuredPath, e); + } + } + + private String requireSafeArgument(String value, String argName) throws IOException { + if (value == null || value.isBlank()) { + throw new IOException("Missing required argument: " + argName); + } + if (containsControlChars(value)) { + throw new IOException("Invalid control characters in " + argName); + } + return value; + } + + private boolean containsControlChars(String value) { + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + if (ch == '\0' || ch == '\n' || ch == '\r') { + return true; + } + } + return false; + } } diff --git a/backend/src/main/java/com/printcalculator/service/TwintPaymentService.java b/backend/src/main/java/com/printcalculator/service/TwintPaymentService.java index 539d339..6ed915b 100644 --- a/backend/src/main/java/com/printcalculator/service/TwintPaymentService.java +++ b/backend/src/main/java/com/printcalculator/service/TwintPaymentService.java @@ -15,13 +15,17 @@ public class TwintPaymentService { private final String twintPaymentUrl; public TwintPaymentService( - @Value("${payment.twint.url:https://go.twint.ch/1/e/tw?tw=acq.gERQQytOTnyIMuQHUqn4hlxgciHE5X7nnqHnNSPAr2OF2K3uBlXJDr2n9JU3sgxa.}") + @Value("${payment.twint.url:}") String twintPaymentUrl ) { this.twintPaymentUrl = twintPaymentUrl; } public String getTwintPaymentUrl(com.printcalculator.entity.Order order) { + if (twintPaymentUrl == null || twintPaymentUrl.isBlank()) { + throw new IllegalStateException("TWINT_PAYMENT_URL is not configured"); + } + StringBuilder urlBuilder = new StringBuilder(twintPaymentUrl); if (order != null) { diff --git a/backend/src/main/resources/application-local.properties b/backend/src/main/resources/application-local.properties index 84e4ff5..04cf953 100644 --- a/backend/src/main/resources/application-local.properties +++ b/backend/src/main/resources/application-local.properties @@ -1,7 +1,8 @@ app.mail.enabled=false app.mail.admin.enabled=false +app.mail.contact-request.admin.enabled=false # Admin back-office local test credentials -admin.password=ciaociao -admin.session.secret=local-admin-session-secret-0123456789abcdef0123456789 +admin.password=local-admin-password +admin.session.secret=local-session-secret-for-dev-only-000000000000000000000000 admin.session.ttl-minutes=480 diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index a24a15c..37dab8d 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -4,7 +4,7 @@ server.port=8000 # Database Configuration spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:5432/printcalc} spring.datasource.username=${DB_USERNAME:printcalc} -spring.datasource.password=${DB_PASSWORD:printcalc_secret} +spring.datasource.password=${DB_PASSWORD:} spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.open-in-view=false @@ -26,7 +26,7 @@ clamav.port=${CLAMAV_PORT:3310} clamav.enabled=${CLAMAV_ENABLED:false} # TWINT Configuration -payment.twint.url=${TWINT_PAYMENT_URL:https://go.twint.ch/1/e/tw?tw=acq.gERQQytOTnyIMuQHUqn4hlxgciHE5X7nnqHnNSPAr2OF2K3uBlXJDr2n9JU3sgxa.} +payment.twint.url=${TWINT_PAYMENT_URL:} # Mail Configuration spring.mail.host=${MAIL_HOST:mail.infomaniak.com} @@ -41,6 +41,8 @@ app.mail.enabled=${APP_MAIL_ENABLED:true} app.mail.from=${APP_MAIL_FROM:${MAIL_USERNAME:noreply@printcalculator.local}} app.mail.admin.enabled=${APP_MAIL_ADMIN_ENABLED:true} app.mail.admin.address=${APP_MAIL_ADMIN_ADDRESS:admin@printcalculator.local} +app.mail.contact-request.admin.enabled=${APP_MAIL_CONTACT_REQUEST_ADMIN_ENABLED:true} +app.mail.contact-request.admin.address=${APP_MAIL_CONTACT_REQUEST_ADMIN_ADDRESS:infog@3d-fab.ch} app.frontend.base-url=${APP_FRONTEND_BASE_URL:http://localhost:4200} # Admin back-office authentication diff --git a/backend/src/main/resources/templates/email/contact-request-admin.html b/backend/src/main/resources/templates/email/contact-request-admin.html new file mode 100644 index 0000000..4341c34 --- /dev/null +++ b/backend/src/main/resources/templates/email/contact-request-admin.html @@ -0,0 +1,121 @@ + + + + + Nuova richiesta di contatto + + + +
+

Nuova richiesta di contatto

+

E' stata ricevuta una nuova richiesta dal form contatti/su misura.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID richiesta00000000-0000-0000-0000-000000000000
Data2026-03-03T10:00:00Z
Tipo richiestaPRINT_SERVICE
Tipo clientePRIVATE
NomeMario Rossi
Azienda3D Fab SA
ContattoMario Rossi
Emailcliente@example.com
Telefono+41 00 000 00 00
MessaggioTesto richiesta cliente...
Allegati0
+ + +
+ + diff --git a/backend/src/test/java/com/printcalculator/controller/AdminAuthSecurityTest.java b/backend/src/test/java/com/printcalculator/controller/AdminAuthSecurityTest.java index e5cd834..62511f7 100644 --- a/backend/src/test/java/com/printcalculator/controller/AdminAuthSecurityTest.java +++ b/backend/src/test/java/com/printcalculator/controller/AdminAuthSecurityTest.java @@ -32,7 +32,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. }) @TestPropertySource(properties = { "admin.password=test-admin-password", - "admin.session.secret=0123456789abcdef0123456789abcdef", + "admin.session.secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "admin.session.ttl-minutes=60" }) class AdminAuthSecurityTest { diff --git a/docker-compose.yml b/docker-compose.yml index 83bc72e..c144f47 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: container_name: print-calculator-db environment: - POSTGRES_USER=printcalc - - POSTGRES_PASSWORD=printcalc_secret + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=printcalc ports: - "5432:5432"