diff --git a/src/main/java/edu/yu/capstone/DistributedSecretsVault/DistributedSecretsVaultApplication.java b/src/main/java/edu/yu/capstone/DistributedSecretsVault/DistributedSecretsVaultApplication.java
index 546a54e..b51f20c 100644
--- a/src/main/java/edu/yu/capstone/DistributedSecretsVault/DistributedSecretsVaultApplication.java
+++ b/src/main/java/edu/yu/capstone/DistributedSecretsVault/DistributedSecretsVaultApplication.java
@@ -5,11 +5,34 @@
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.scheduling.annotation.EnableScheduling;
+/**
+ * Entry point for the Distributed Secrets Vault application.
+ *
+ * This Spring Boot application provides a distributed, fault-tolerant secrets
+ * management system that uses Shamir's Secret Sharing to split secrets
+ * into shards distributed across multiple nodes. It relies on a two-phase
+ * prepare/commit protocol coordinated through Kafka, with ScaleCube for
+ * service discovery and Redis for shard storage.
+ *
+ * Key annotations:
+ *
+ * - {@code @ConfigurationPropertiesScan} — auto-discovers configuration
+ * property classes such as {@link edu.yu.capstone.DistributedSecretsVault.config.ClusterConfig},
+ * {@link edu.yu.capstone.DistributedSecretsVault.config.NetworkConfig}, etc.
+ * - {@code @EnableScheduling} — enables scheduled tasks such as
+ * {@link edu.yu.capstone.DistributedSecretsVault.service.internal.PendingActionsBuffer#evictExpired()}.
+ *
+ */
@SpringBootApplication
@ConfigurationPropertiesScan
@EnableScheduling
public class DistributedSecretsVaultApplication {
+ /**
+ * Launches the Spring Boot application.
+ *
+ * @param args command-line arguments forwarded to Spring Boot
+ */
public static void main(String[] args) {
SpringApplication.run(DistributedSecretsVaultApplication.class, args);
}
diff --git a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/ClusterConfig.java b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/ClusterConfig.java
index cf80ac0..01b2269 100644
--- a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/ClusterConfig.java
+++ b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/ClusterConfig.java
@@ -5,15 +5,37 @@
import lombok.Data;
+/**
+ * Configuration properties for cluster topology and distributed operation tuning.
+ *
+ * Bound from the {@code cluster.*} prefix in {@code application.yml}.
+ * These values control how secrets are split, how many nodes must acknowledge
+ * a write, and how long pending operations remain valid.
+ *
+ * @see edu.yu.capstone.DistributedSecretsVault.service.internal.PendingActionsBuffer
+ */
@Validated
@ConfigurationProperties(prefix = "cluster")
@Data
public class ClusterConfig {
+ /** Total number of nodes in the cluster (N in Shamir's scheme). */
private int totalNodes;
+
+ /** Minimum number of shards required to reconstruct a secret (K in Shamir's scheme). */
private int thresholdK;
+
+ /** Minimum number of peer ACKs required to commit a write (quorum M). */
private int quorumM;
+
+ /** Maximum time (ms) a pending action remains buffered before eviction. */
private long lockTimeoutMillis;
+
+ /** Maximum time (ms) to wait for all peer responses during a write operation. */
private long writeTimeoutMillis;
+
+ /** Whether read-repair is enabled (automatically re-split when shard count is low). */
private boolean repairEnabled = true;
+
+ /** Number of extra shards above the threshold before read-repair triggers. */
private int repairTriggerBuffer = 1;
}
diff --git a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/KafkaConfig.java b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/KafkaConfig.java
index 14eaac8..3976df6 100644
--- a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/KafkaConfig.java
+++ b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/KafkaConfig.java
@@ -5,12 +5,31 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.TopicBuilder;
+/**
+ * Kafka topic configuration for the distributed commit protocol.
+ *
+ * Defines the two Kafka topics used by the system:
+ *
+ * - {@link #COORDINATION_TOPIC} — reserved for future coordination messages
+ * - {@link #COMMIT_TOPIC} — carries {@link edu.yu.capstone.DistributedSecretsVault.dto.internal.CommitMessage}
+ * payloads that instruct all nodes to finalize a buffered prepare operation
+ *
+ * Both topics are configured with a 4-hour retention period.
+ */
@Configuration
public class KafkaConfig {
+ /** Topic name for general coordination messages between nodes. */
public static final String COORDINATION_TOPIC = "secrets-coordination";
+
+ /** Topic name for commit messages broadcast after quorum is reached. */
public static final String COMMIT_TOPIC = "secrets-commit";
+ /**
+ * Creates the coordination Kafka topic if it does not already exist.
+ *
+ * @return a {@link NewTopic} with 1 partition, 1 replica, and 4-hour retention
+ */
@Bean
public NewTopic coordinationTopic() {
return TopicBuilder.name(COORDINATION_TOPIC)
@@ -20,6 +39,11 @@ public NewTopic coordinationTopic() {
.build();
}
+ /**
+ * Creates the commit Kafka topic if it does not already exist.
+ *
+ * @return a {@link NewTopic} with 1 partition, 1 replica, and 4-hour retention
+ */
@Bean
public NewTopic commitTopic() {
return TopicBuilder.name(COMMIT_TOPIC)
diff --git a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/NetworkConfig.java b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/NetworkConfig.java
index 9a45723..f4f4d81 100644
--- a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/NetworkConfig.java
+++ b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/NetworkConfig.java
@@ -5,14 +5,32 @@
import lombok.Data;
+/**
+ * Configuration properties for node identity and network settings.
+ *
+ * Bound from the {@code network.*} prefix in {@code application.yml}.
+ * These settings identify the current node and configure network-level
+ * parameters such as multicast discovery and TCP timeouts.
+ */
@Validated
@ConfigurationProperties(prefix = "network")
@Data
public class NetworkConfig {
+ /** Unique identifier for this node within the cluster. */
private String nodeId;
+
+ /** Hostname or IP address the node binds to for incoming connections. */
private String bindHost;
+
+ /** Port the node binds to for incoming connections. */
private int bindPort;
+
+ /** Multicast group address used for node discovery. */
private String multicastGroup;
+
+ /** Port used for multicast discovery. */
private int multicastPort;
+
+ /** Timeout (ms) for TCP connections between nodes. */
private int tcpTimeoutMillis;
}
diff --git a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/RestClientConfig.java b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/RestClientConfig.java
index 00be21b..acdd5c4 100644
--- a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/RestClientConfig.java
+++ b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/RestClientConfig.java
@@ -14,6 +14,13 @@
@Configuration
public class RestClientConfig {
+ /**
+ * Creates a {@link RestClient} with connect and read timeouts derived from
+ * {@link ClusterConfig#getWriteTimeoutMillis()}, falling back to 5000ms.
+ *
+ * @param clusterConfig cluster configuration providing timeout values
+ * @return a configured {@link RestClient} instance
+ */
@Bean
public RestClient restClient(ClusterConfig clusterConfig) {
long timeoutMs = clusterConfig.getWriteTimeoutMillis();
diff --git a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/ScaleCubeConfig.java b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/ScaleCubeConfig.java
index ac63710..f28948a 100644
--- a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/ScaleCubeConfig.java
+++ b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/ScaleCubeConfig.java
@@ -19,6 +19,20 @@
import java.util.ArrayList;
import java.util.List;
+/**
+ * Spring configuration that bootstraps a ScaleCube Microservices node for
+ * cluster membership and service discovery.
+ *
+ * Each application instance joins a ScaleCube cluster by resolving seed
+ * members via DNS (headless Kubernetes service) and advertising its pod IP.
+ * The node exposes a simple {@link PingService} to verify cluster connectivity.
+ *
+ * Active only when the {@code test} profile is not active or the
+ * {@code scalecube-single-node} profile is active.
+ *
+ * @see edu.yu.capstone.DistributedSecretsVault.service.internal.NodeClient
+ * @see edu.yu.capstone.DistributedSecretsVault.health.ScaleCubeHealthIndicator
+ */
@Configuration
@Profile("!test | scalecube-single-node")
public class ScaleCubeConfig {
@@ -27,8 +41,18 @@ public class ScaleCubeConfig {
private static final int DEFAULT_DNS_RESOLVE_MAX_ATTEMPTS = 5;
private static final long DEFAULT_DNS_RESOLVE_RETRY_DELAY_MS = 1000L;
+ /**
+ * Functional interface abstracting DNS resolution for testability.
+ */
@FunctionalInterface
interface DnsResolver {
+ /**
+ * Resolve all IP addresses for the given hostname.
+ *
+ * @param host the hostname to resolve
+ * @return array of resolved {@link InetAddress}es
+ * @throws UnknownHostException if the host cannot be resolved
+ */
InetAddress[] resolveAllByName(String host) throws UnknownHostException;
}
@@ -42,6 +66,18 @@ public interface PingService {
private Microservices microservices;
+ /**
+ * Creates and starts a ScaleCube {@link Microservices} node.
+ *
+ * The method reads environment variables ({@code POD_IP}, {@code CLUSTER_PORT},
+ * {@code SEED_DNS_HOST}, {@code SEED_DNS_PORT}, {@code NODE_NAME}) to configure
+ * the node's address, cluster port, and seed members. Falls back to safe
+ * defaults for local (non-Kubernetes) development.
+ *
+ * @return a fully started {@link Microservices} instance
+ * @throws IllegalStateException if {@code SEED_DNS_HOST} is not set or DNS
+ * resolution fails after all retry attempts
+ */
@Bean
public Microservices scalecubeMicroservices() {
// 1. Read environment variables mapped from the k8s manifest
@@ -104,6 +140,9 @@ public Microservices scalecubeMicroservices() {
return microservices;
}
+ /**
+ * Gracefully shuts down the ScaleCube node on application context closure.
+ */
@PreDestroy
public void stopScaleCube() {
if (microservices != null) {
@@ -112,6 +151,17 @@ public void stopScaleCube() {
}
}
+ /**
+ * Attempts DNS resolution with retry logic.
+ *
+ * @param seedDnsHost the hostname to resolve (e.g. headless k8s service)
+ * @param seedDnsPort port each seed member listens on
+ * @param maxAttempts maximum number of resolution attempts
+ * @param retryDelayMs delay (ms) between retries
+ * @param dnsResolver abstraction for {@link InetAddress#getAllByName}
+ * @return array of resolved ScaleCube {@link Address}es
+ * @throws IllegalStateException if all attempts fail
+ */
static Address[] resolveSeedMembersWithRetry(String seedDnsHost, int seedDnsPort, int maxAttempts,
long retryDelayMs, DnsResolver dnsResolver) {
RuntimeException lastFailure = null;
@@ -134,6 +184,16 @@ static Address[] resolveSeedMembersWithRetry(String seedDnsHost, int seedDnsPort
lastFailure);
}
+ /**
+ * Resolves all IP addresses for the given DNS host and converts them to
+ * ScaleCube {@link Address}es.
+ *
+ * @param seedDnsHost the hostname to resolve
+ * @param seedDnsPort port to pair with each resolved IP
+ * @param dnsResolver abstraction for DNS lookups
+ * @return array of ScaleCube addresses
+ * @throws IllegalStateException if DNS returns no addresses or lookup fails
+ */
static Address[] resolveSeedMembers(String seedDnsHost, int seedDnsPort, DnsResolver dnsResolver) {
try {
InetAddress[] addresses = dnsResolver.resolveAllByName(seedDnsHost);
@@ -150,6 +210,9 @@ static Address[] resolveSeedMembers(String seedDnsHost, int seedDnsPort, DnsReso
}
}
+ /**
+ * Sleeps for the specified duration, restoring the interrupt flag if interrupted.
+ */
private static void sleepQuietly(long retryDelayMs) {
try {
Thread.sleep(retryDelayMs);
@@ -159,6 +222,12 @@ private static void sleepQuietly(long retryDelayMs) {
}
}
+ /**
+ * Reads a value from environment variables, falling back to system properties.
+ *
+ * @param key the environment variable / system property name
+ * @return the value, or {@code null} if not set
+ */
private static String readEnvOrSystemProperty(String key) {
String value = System.getenv(key);
if (value == null || value.isBlank()) {
diff --git a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/SecurityConfig.java b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/SecurityConfig.java
index 06157e6..9e84c5e 100644
--- a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/SecurityConfig.java
+++ b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/SecurityConfig.java
@@ -5,11 +5,24 @@
import lombok.Data;
+/**
+ * Configuration properties for authentication and authorization.
+ *
+ * Bound from the {@code security.*} prefix in {@code application.yml}.
+ * When {@link #authEnabled} is {@code true}, incoming requests must include
+ * a valid OAuth token issued by {@link #oauthIssuer} for the configured
+ * {@link #oauthAudience}.
+ */
@Validated
@ConfigurationProperties(prefix = "security")
@Data
public class SecurityConfig {
+ /** Whether authentication is enforced for incoming API requests. */
private boolean authEnabled;
+
+ /** OAuth 2.0 issuer URL used to validate JWT tokens (e.g. {@code https://accounts.google.com}). */
private String oauthIssuer;
+
+ /** Expected OAuth audience claim in the JWT. */
private String oauthAudience;
}
diff --git a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/StorageConfig.java b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/StorageConfig.java
index be5ff2e..0beb2eb 100644
--- a/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/StorageConfig.java
+++ b/src/main/java/edu/yu/capstone/DistributedSecretsVault/config/StorageConfig.java
@@ -5,11 +5,25 @@
import lombok.Data;
+/**
+ * Configuration properties for the Redis storage backend.
+ *
+ * Bound from the {@code storage.*} prefix in {@code application.yml}.
+ * Each node connects to its own Redis instance (sidecar pattern) to store
+ * its assigned secret shards.
+ *
+ * @see edu.yu.capstone.DistributedSecretsVault.repository.impl.RedisSecretPartRepository
+ */
@Validated
@ConfigurationProperties(prefix = "storage")
@Data
public class StorageConfig {
+ /** Hostname of the Redis instance. */
private String redisHost;
+
+ /** Port of the Redis instance. */
private int redisPort;
+
+ /** Password for Redis authentication (empty string if unauthenticated). */
private String redisPassword;
}
diff --git a/src/main/java/edu/yu/capstone/DistributedSecretsVault/controller/ClusterController.java b/src/main/java/edu/yu/capstone/DistributedSecretsVault/controller/ClusterController.java
index a250f25..c00b00d 100644
--- a/src/main/java/edu/yu/capstone/DistributedSecretsVault/controller/ClusterController.java
+++ b/src/main/java/edu/yu/capstone/DistributedSecretsVault/controller/ClusterController.java
@@ -14,6 +14,13 @@
import edu.yu.capstone.DistributedSecretsVault.config.ScaleCubeConfig.PingService;
import edu.yu.capstone.DistributedSecretsVault.dto.response.ClusterStatusResponse;
+/**
+ * REST controller for cluster health and diagnostics.
+ *
+ * Provides endpoints to verify cluster connectivity, list discovered nodes,
+ * and retrieve a summary of cluster health. Only available when ScaleCube
+ * is active (i.e., when a {@link Microservices} bean exists).
+ */
@RestController
@RequestMapping("/api/v1/cluster")
@ConditionalOnBean(Microservices.class)
@@ -22,6 +29,14 @@ public class ClusterController {
@Autowired
private Microservices microservices;
+ /**
+ * Sends a ping request through ScaleCube to any available node in the cluster.
+ *
+ * Useful for verifying that the ScaleCube service mesh is operational and
+ * that at least one peer can process requests.
+ *
+ * @return a "Pong from {nodeName}" response from the receiving node
+ */
@GetMapping("/ping")
public ResponseEntity ping() {
// Creates a proxy that routes the ping request over ScaleCube to any available node
@@ -30,6 +45,11 @@ public ResponseEntity ping() {
return ResponseEntity.ok(response);
}
+ /**
+ * Lists all nodes discovered by ScaleCube's service discovery.
+ *
+ * @return list of strings in the format {@code "nodeId @ host:port"}
+ */
@GetMapping("/nodes")
public ResponseEntity> listNodes() {
List nodes = microservices.serviceEndpoints().stream()
@@ -38,6 +58,14 @@ public ResponseEntity> listNodes() {
return ResponseEntity.ok(nodes);
}
+ /**
+ * Returns a summary of the cluster's health.
+ *
+ * Currently uses a simplified model where all discovered nodes are
+ * assumed healthy.
+ *
+ * @return a {@link ClusterStatusResponse} with node counts
+ */
@GetMapping("/status")
public ResponseEntity status() {
ClusterStatusResponse response = new ClusterStatusResponse();
diff --git a/src/main/java/edu/yu/capstone/DistributedSecretsVault/controller/InternalController.java b/src/main/java/edu/yu/capstone/DistributedSecretsVault/controller/InternalController.java
index 8dc18ad..a657a60 100644
--- a/src/main/java/edu/yu/capstone/DistributedSecretsVault/controller/InternalController.java
+++ b/src/main/java/edu/yu/capstone/DistributedSecretsVault/controller/InternalController.java
@@ -26,6 +26,18 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+/**
+ * Internal REST controller for node-to-node communication.
+ *
+ * Endpoints under {@code /internal} are called by peer nodes during the
+ * prepare phase of distributed operations and for fetching individual shards.
+ * These endpoints are not intended for external client use.
+ *
+ * Commit messages are delivered via Kafka rather than HTTP, so there are no
+ * commit endpoints here.
+ *
+ * @see edu.yu.capstone.DistributedSecretsVault.service.internal
+ */
@RestController
@RequestMapping("/internal")
public class InternalController {
@@ -36,6 +48,9 @@ public class InternalController {
private final DeletePrepareHandler deletePrepareHandler;
private final RepairPrepareHandler repairPrepareHandler;
+ /**
+ * Constructs the controller with all prepare handlers and the internal get service.
+ */
public InternalController(InternalGetService internalGetService,
PostPrepareHandler postPrepareHandler,
PutPrepareHandler putPrepareHandler,
@@ -48,6 +63,14 @@ public InternalController(InternalGetService internalGetService,
this.repairPrepareHandler = repairPrepareHandler;
}
+ /**
+ * Retrieves a single shard from this node's local Redis, optionally at a specific version.
+ *
+ * @param id the secret name
+ * @param user the secret owner
+ * @param version optional version; if omitted, the latest version is returned
+ * @return the {@link SecretPart} stored on this node
+ */
@GetMapping("/{id}")
public ResponseEntity getSecretPart(@PathVariable String id,
@RequestParam(value = "user") String user,
@@ -55,30 +78,71 @@ public ResponseEntity getSecretPart(@PathVariable String id,
return internalGetService.getVersion(user, id, version);
}
+ /**
+ * Retrieves all version shards from this node's local Redis.
+ *
+ * @param id the secret name
+ * @param user the secret owner
+ * @return map of version numbers to {@link SecretPart}s
+ */
@GetMapping("/{id}/all")
public ResponseEntity