From 55555bcc374bd88087630e403200a9e31fead696 Mon Sep 17 00:00:00 2001 From: "Aarthi Manivannan, Premanathan Aarthi Manivannan" Date: Tue, 9 Dec 2025 17:59:09 +0100 Subject: [PATCH] Update 6 files - /src1/main/resources/application.yml - /src1/main/java/com/v2d/document/config/AppProperties.java - /src1/main/java/com/v2d/document/service/ExternalApiService.java - /src1/main/java/com/v2d/document/controller/GenerateController.java - /src1/test/AppPropertiesTest.java - /README.md --- README.md | 43 ++++++++++++++++++ .../v2d/document/config/AppProperties.java | 13 ++++++ .../controller/GenerateController.java | 26 +++++++++++ .../document/service/ExternalApiService.java | 45 +++++++++++++++++++ src1/main/resources/application.yml | 7 +++ src1/test/AppPropertiesTest.java | 20 +++++++++ 6 files changed, 154 insertions(+) create mode 100644 src1/main/java/com/v2d/document/config/AppProperties.java create mode 100644 src1/main/java/com/v2d/document/controller/GenerateController.java create mode 100644 src1/main/java/com/v2d/document/service/ExternalApiService.java create mode 100644 src1/main/resources/application.yml create mode 100644 src1/test/AppPropertiesTest.java diff --git a/README.md b/README.md index 70d8dc6..eb1c002 100644 --- a/README.md +++ b/README.md @@ -91,3 +91,46 @@ For open source projects, say how it is licensed. ## Project status If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +## 🔐 Secure API Key Management (Sprint 4 – V2D Document) + +This project uses secure environment variables to store and manage all external API keys +(required for LLM/Transcription APIs). No API key is ever committed into the repository. + +### ✔ How the API Key Works +The application reads the key from an environment variable named: + +`LLM_API_KEY` + +Spring Boot loads it automatically using the following configuration in `application.yml`: + + +### ✔ Local Development (developer machines) +Developers must manually set their API key locally: + + +### ✔ GitLab CI/CD Setup (secure by default) +To provide the key for all environments securely: + +1. Go to **GitLab → Settings → CI/CD → Variables** +2. Add variable: + - **Key:** `LLM_API_KEY` + - **Value:** your real API key + - **Masked:** ✓ Enable + - **Protected:** (optional) +3. Save. + +Pipelines will automatically use the secure key without exposing it. + +### ✔ Security Guarantees +- The API key is **not stored** in the repository +- `.env` files are ignored through `.gitignore` +- The key is **never printed**, logged, or exposed to users +- Every new user of V2D Document can use the system **without needing their own key** + +### ✔ Files Added in This User Story +- `src/main/resources/application.yml` +- `src/main/java/com/v2d/document/config/AppProperties.java` +- `src/main/java/com/v2d/document/service/ExternalApiService.java` +- `src/test/java/com/v2d/document/config/AppPropertiesTest.java` + +This completes Sprint 4 User Story: **Backend – Secure Management & Storage of API Keys**. diff --git a/src1/main/java/com/v2d/document/config/AppProperties.java b/src1/main/java/com/v2d/document/config/AppProperties.java new file mode 100644 index 0000000..a080c4c --- /dev/null +++ b/src1/main/java/com/v2d/document/config/AppProperties.java @@ -0,0 +1,13 @@ +package com.v2d.document.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "app.external") +public class AppProperties { + private String apiKey; + + public String getApiKey() { return apiKey; } + public void setApiKey(String apiKey) { this.apiKey = apiKey; } +} diff --git a/src1/main/java/com/v2d/document/controller/GenerateController.java b/src1/main/java/com/v2d/document/controller/GenerateController.java new file mode 100644 index 0000000..bd56478 --- /dev/null +++ b/src1/main/java/com/v2d/document/controller/GenerateController.java @@ -0,0 +1,26 @@ +package com.v2d.document.controller; + +import com.v2d.document.service.ExternalApiService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/api/generate") +public class GenerateController { + + private final ExternalApiService externalApiService; + + public GenerateController(ExternalApiService externalApiService) { + this.externalApiService = externalApiService; + } + + @PostMapping + public ResponseEntity generate(@RequestBody Map body) { + // Build provider payload from the user's body (transform safely) + String payload = "{\"text\": \"use this text\"}"; // adapt for real usage + String providerResponse = externalApiService.callProvider(payload); + return ResponseEntity.ok(providerResponse); + } +} diff --git a/src1/main/java/com/v2d/document/service/ExternalApiService.java b/src1/main/java/com/v2d/document/service/ExternalApiService.java new file mode 100644 index 0000000..98b1dc7 --- /dev/null +++ b/src1/main/java/com/v2d/document/service/ExternalApiService.java @@ -0,0 +1,45 @@ +package com.v2d.document.service; + +import com.v2d.document.config.AppProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +@Service +public class ExternalApiService { + private final Logger log = LoggerFactory.getLogger(ExternalApiService.class); + private final AppProperties props; + private final HttpClient http = HttpClient.newHttpClient(); + + public ExternalApiService(AppProperties props) { + this.props = props; + } + + public String callProvider(String jsonPayload) { + String key = props.getApiKey(); + if (key == null || key.isBlank()) { + log.warn("External API key is not configured."); + throw new IllegalStateException("External API key missing"); + } + + try { + HttpRequest req = HttpRequest.newBuilder() + .uri(URI.create("https://api.example.com/endpoint")) // replace with real endpoint + .header("Authorization", "Bearer " + key) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) + .build(); + + HttpResponse resp = http.send(req, HttpResponse.BodyHandlers.ofString()); + return resp.body(); + } catch (Exception e) { + log.error("External API call failed: {}", e.getMessage()); + throw new RuntimeException("External API call failed", e); + } + } +} diff --git a/src1/main/resources/application.yml b/src1/main/resources/application.yml new file mode 100644 index 0000000..04b5693 --- /dev/null +++ b/src1/main/resources/application.yml @@ -0,0 +1,7 @@ +spring: + application: + name: v2d-document + +app: + external: + apiKey: ${LLM_API_KEY:} diff --git a/src1/test/AppPropertiesTest.java b/src1/test/AppPropertiesTest.java new file mode 100644 index 0000000..76a2a1f --- /dev/null +++ b/src1/test/AppPropertiesTest.java @@ -0,0 +1,20 @@ +package com.v2d.document.config; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AppPropertiesTest { + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(AppProperties.class) + .withPropertyValues("app.external.apiKey=TEST_KEY"); + + @Test + void bindsApiKey() { + contextRunner.run(context -> { + AppProperties props = context.getBean(AppProperties.class); + assertThat(props.getApiKey()).isEqualTo("TEST_KEY"); + }); + } +}