Skip to content
Merged
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
13 changes: 13 additions & 0 deletions core/src/main/java/com/opencontext/common/CommonResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,17 @@ public static <T> CommonResponse<T> error(String message, String errorCode) {
LocalDateTime.now()
);
}

/**
* Static factory method for creating error responses from ErrorCode enum.
*/
public static <T> CommonResponse<T> error(com.opencontext.enums.ErrorCode errorCode, String message) {
return new CommonResponse<>(
false,
null,
message,
errorCode.getCode(),
LocalDateTime.now()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.opencontext.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.opencontext.common.CommonResponse;
import com.opencontext.enums.ErrorCode;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

/**
* Filter for API Key authentication on admin endpoints.
*
* This filter validates the X-API-KEY header for endpoints that require
* admin access, specifically the document management APIs.
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class ApiKeyAuthenticationFilter extends OncePerRequestFilter {

private final ObjectMapper objectMapper;

@Value("${opencontext.api.key:dev-api-key-123}")
private String validApiKey;

// Endpoints that require API Key authentication
Comment on lines +35 to +38

Copilot AI Aug 11, 2025

Copy link

Choose a reason for hiding this comment

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

The default API key 'dev-api-key-123' is weak and predictable. Consider using a stronger default or requiring the API key to be explicitly configured in production environments.

Suggested change
@Value("${opencontext.api.key:dev-api-key-123}")
private String validApiKey;
// Endpoints that require API Key authentication
@Value("${opencontext.api.key}")
private String validApiKey;
// Endpoints that require API Key authentication
// Ensure the API key is set and not weak
@jakarta.annotation.PostConstruct
private void validateApiKey() {
if (!StringUtils.hasText(validApiKey) || "dev-api-key-123".equals(validApiKey)) {
throw new IllegalStateException("API key must be explicitly configured and not use a default/weak value.");
}
}

Copilot uses AI. Check for mistakes.
private static final List<String> PROTECTED_ENDPOINTS = Arrays.asList(
"/api/v1/sources"
);

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

String requestPath = request.getRequestURI();

// Check if this endpoint requires API Key authentication
boolean requiresAuth = PROTECTED_ENDPOINTS.stream()
.anyMatch(requestPath::startsWith);

if (requiresAuth) {
String apiKey = request.getHeader("X-API-KEY");

if (!StringUtils.hasText(apiKey)) {
log.warn("API Key missing for protected endpoint: {}", requestPath);
sendErrorResponse(response, ErrorCode.INSUFFICIENT_PERMISSION,
"API Key is required. Please provide X-API-KEY header.");
return;
}

if (!validApiKey.equals(apiKey)) {
log.warn("Invalid API Key provided for endpoint: {}", requestPath);
sendErrorResponse(response, ErrorCode.INSUFFICIENT_PERMISSION,
"Invalid API Key provided.");
return;
}

log.debug("API Key authentication successful for endpoint: {}", requestPath);
}

filterChain.doFilter(request, response);
}

/**
* Sends an error response in JSON format.
*/
private void sendErrorResponse(HttpServletResponse response, ErrorCode errorCode, String message)
throws IOException {

response.setStatus(errorCode.getHttpStatus().value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");

CommonResponse<Void> errorResponse = CommonResponse.error(errorCode, message);

String jsonResponse = objectMapper.writeValueAsString(errorResponse);
response.getWriter().write(jsonResponse);
}
}
41 changes: 41 additions & 0 deletions core/src/main/java/com/opencontext/config/MinIOConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.opencontext.config;

import io.minio.MinioClient;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* MinIO client configuration for object storage operations.
*
* This configuration creates a MinIO client bean that can be used throughout
* the application for file upload, download, and management operations.
*/
@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinIOConfig {

private String endpoint;
private String accessKey;
private String secretKey;
private String bucketName;

/**
* Creates and configures the MinIO client bean.
*
* @return configured MinioClient instance
*/
@Bean
public MinioClient minioClient() {
log.info("Initializing MinIO client with endpoint: {}", endpoint);

return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
9 changes: 5 additions & 4 deletions core/src/main/java/com/opencontext/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
public class SecurityConfig {

private final Environment environment;
private final ApiKeyAuthenticationFilter apiKeyAuthenticationFilter;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand Down Expand Up @@ -62,15 +63,15 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// Allow development mock data endpoints (development only)
.requestMatchers("/api/v1/dev/**").permitAll()

// Admin APIs will require API Key authentication (TODO: implement in next step)
.requestMatchers("/api/v1/sources/**").permitAll() // Temporary - will add API key auth
// Admin APIs require API Key authentication
.requestMatchers("/api/v1/sources/**").permitAll() // Authentication handled by filter

// All other requests require authentication
.anyRequest().authenticated()
);

// TODO: Add API Key authentication filter for Admin APIs
// http.addFilterBefore(apiKeyAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
// Add API Key authentication filter for Admin APIs
http.addFilterBefore(apiKeyAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

log.info("Security configuration completed successfully");
return http.build();
Expand Down
Loading