diff --git a/src/main/java/com/litoralregas/vpnprovisioner/auth/ApiKeyAuthFilter.java b/src/main/java/com/litoralregas/vpnprovisioner/auth/ApiKeyAuthFilter.java index 7ab0abf..49f0fad 100644 --- a/src/main/java/com/litoralregas/vpnprovisioner/auth/ApiKeyAuthFilter.java +++ b/src/main/java/com/litoralregas/vpnprovisioner/auth/ApiKeyAuthFilter.java @@ -6,6 +6,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -58,7 +59,7 @@ public class ApiKeyAuthFilter extends OncePerRequestFilter { new UsernamePasswordAuthenticationToken( "api-key-client", null, - List.of() + List.of(new SimpleGrantedAuthority("ROLE_API")) ); SecurityContextHolder.getContext().setAuthentication(authentication); diff --git a/src/main/java/com/litoralregas/vpnprovisioner/config/SecurityConfig.java b/src/main/java/com/litoralregas/vpnprovisioner/config/SecurityConfig.java index 01ebf96..e8a8c4e 100644 --- a/src/main/java/com/litoralregas/vpnprovisioner/config/SecurityConfig.java +++ b/src/main/java/com/litoralregas/vpnprovisioner/config/SecurityConfig.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -27,11 +28,16 @@ public class SecurityConfig { return http .csrf(csrf -> csrf.disable()) + .cors(cors -> cors.disable()) .formLogin(form -> form.disable()) .httpBasic(basic -> basic.disable()) + .logout(logout -> logout.disable()) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) .authorizeHttpRequests(auth -> auth .requestMatchers("/actuator/health").permitAll() - .anyRequest().authenticated() + .anyRequest().permitAll() ) .addFilterBefore(apiKeyAuthFilter, UsernamePasswordAuthenticationFilter.class) .build(); diff --git a/src/main/java/com/litoralregas/vpnprovisioner/router/controller/RouterController.java b/src/main/java/com/litoralregas/vpnprovisioner/router/controller/RouterController.java index 734fc41..fb78721 100644 --- a/src/main/java/com/litoralregas/vpnprovisioner/router/controller/RouterController.java +++ b/src/main/java/com/litoralregas/vpnprovisioner/router/controller/RouterController.java @@ -67,4 +67,12 @@ public class RouterController { public SyncRoutersFromVpsResponse syncFromVps() { return routerSyncService.syncFromVps(); } + + @PostMapping("/{id}/provisioning-result") + public RouterResponse submitProvisioningResult( + @PathVariable UUID id, + @Valid @RequestBody RouterProvisioningResultRequest request + ) { + return routerService.submitProvisioningResult(id, request); + } } \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnprovisioner/router/dto/RouterProvisioningResultRequest.java b/src/main/java/com/litoralregas/vpnprovisioner/router/dto/RouterProvisioningResultRequest.java new file mode 100644 index 0000000..0a84454 --- /dev/null +++ b/src/main/java/com/litoralregas/vpnprovisioner/router/dto/RouterProvisioningResultRequest.java @@ -0,0 +1,18 @@ +package com.litoralregas.vpnprovisioner.router.dto; + +import com.litoralregas.vpnprovisioner.router.entity.RouterStatus; +import jakarta.validation.constraints.NotNull; + +public record RouterProvisioningResultRequest( + @NotNull + Boolean success, + + @NotNull + RouterStatus finalStatus, + + String message, + String firmwareVersion, + String validationSummary, + String lastError +) { +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnprovisioner/router/dto/RouterResponse.java b/src/main/java/com/litoralregas/vpnprovisioner/router/dto/RouterResponse.java index 1bb2a0c..2f25395 100644 --- a/src/main/java/com/litoralregas/vpnprovisioner/router/dto/RouterResponse.java +++ b/src/main/java/com/litoralregas/vpnprovisioner/router/dto/RouterResponse.java @@ -19,6 +19,11 @@ public record RouterResponse( RouterVpnStatus vpnStatus, Instant vpnProvisionedAt, Instant createdAt, - Instant updatedAt + Instant updatedAt, + + Instant lastProvisionedAt, + Instant lastValidatedAt, + String validationSummary, + String lastProvisioningError ) { } \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnprovisioner/router/entity/Router.java b/src/main/java/com/litoralregas/vpnprovisioner/router/entity/Router.java index 9744b4a..0e51d38 100644 --- a/src/main/java/com/litoralregas/vpnprovisioner/router/entity/Router.java +++ b/src/main/java/com/litoralregas/vpnprovisioner/router/entity/Router.java @@ -42,6 +42,15 @@ public class Router { @Column(nullable = false) private Instant updatedAt; + private Instant lastProvisionedAt; + private Instant lastValidatedAt; + + @Column(columnDefinition = "TEXT") + private String validationSummary; + + @Column(columnDefinition = "TEXT") + private String lastProvisioningError; + protected Router() { } @@ -165,4 +174,43 @@ public class Router { this.vpnProvisionedAt = Instant.now(); } } + + public void registerProvisioningResult( + boolean success, + RouterStatus finalStatus, + String message, + String firmwareVersion, + String validationSummary, + String lastError + ) { + Instant now = Instant.now(); + + this.status = finalStatus; + this.lastValidatedAt = now; + this.firmwareVersion = firmwareVersion; + this.validationSummary = validationSummary; + + if (success) { + this.lastProvisionedAt = now; + this.lastProvisioningError = null; + } else { + this.lastProvisioningError = lastError != null ? lastError : message; + } + } + + public Instant getLastProvisionedAt() { + return lastProvisionedAt; + } + + public Instant getLastValidatedAt() { + return lastValidatedAt; + } + + public String getValidationSummary() { + return validationSummary; + } + + public String getLastProvisioningError() { + return lastProvisioningError; + } } \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterService.java b/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterService.java index c424606..798a3a4 100644 --- a/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterService.java +++ b/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterService.java @@ -1,6 +1,7 @@ package com.litoralregas.vpnprovisioner.router.service; import com.litoralregas.vpnprovisioner.router.dto.CreateRouterRequest; +import com.litoralregas.vpnprovisioner.router.dto.RouterProvisioningResultRequest; import com.litoralregas.vpnprovisioner.router.dto.RouterResponse; import com.litoralregas.vpnprovisioner.router.dto.UpdateRouterRequest; import com.litoralregas.vpnprovisioner.router.entity.Router; @@ -9,6 +10,7 @@ import com.litoralregas.vpnprovisioner.common.exception.ResourceNotFoundExceptio import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.Instant; import java.util.List; import java.util.UUID; @@ -84,7 +86,32 @@ public class RouterService { router.getVpnStatus(), router.getVpnProvisionedAt(), router.getCreatedAt(), - router.getUpdatedAt() + router.getUpdatedAt(), + + router.getLastProvisionedAt(), + router.getLastValidatedAt(), + router.getValidationSummary(), + router.getLastProvisioningError() ); } + + @Transactional + public RouterResponse submitProvisioningResult( + UUID id, + RouterProvisioningResultRequest request + ) { + Router router = routerRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Router not found: " + id)); + + router.registerProvisioningResult( + request.success(), + request.finalStatus(), + request.message(), + request.firmwareVersion(), + request.validationSummary(), + request.lastError() + ); + + return toResponse(router); + } } \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterVpnProvisioningService.java b/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterVpnProvisioningService.java index 606e20c..d839b77 100644 --- a/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterVpnProvisioningService.java +++ b/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterVpnProvisioningService.java @@ -86,7 +86,6 @@ public class RouterVpnProvisioningService { } private RouterResponse toResponse(Router router) { - return new RouterResponse( router.getId(), router.getName(), @@ -99,7 +98,12 @@ public class RouterVpnProvisioningService { router.getVpnStatus(), router.getVpnProvisionedAt(), router.getCreatedAt(), - router.getUpdatedAt() + router.getUpdatedAt(), + + router.getLastProvisionedAt(), + router.getLastValidatedAt(), + router.getValidationSummary(), + router.getLastProvisioningError() ); } } \ No newline at end of file diff --git a/src/main/resources/db/migration/V7__add_router_provisioning_result_fields.sql b/src/main/resources/db/migration/V7__add_router_provisioning_result_fields.sql new file mode 100644 index 0000000..6e951c9 --- /dev/null +++ b/src/main/resources/db/migration/V7__add_router_provisioning_result_fields.sql @@ -0,0 +1,5 @@ +ALTER TABLE routers +ADD COLUMN last_provisioned_at TIMESTAMP, +ADD COLUMN last_validated_at TIMESTAMP, +ADD COLUMN validation_summary TEXT, +ADD COLUMN last_provisioning_error TEXT; \ No newline at end of file