feat: SOC2 audit log completeness — hybrid interceptor + explicit calls
Add AuditInterceptor as a safety net that auto-audits any POST/PUT/DELETE without an explicit audit call (excludes data ingestion + heartbeat). AuditService sets a request attribute so the interceptor skips when explicit logging already happened. New explicit audit calls: - ApplicationConfigController: view/update app config - AgentCommandController: send/broadcast commands (AGENT category) - AgentRegistrationController: agent register + token refresh - UiAuthController: UI token refresh - OidcAuthController: OIDC callback failure - AuditLogController: view audit log (sensitive read) - UserAdminController: view users (sensitive read) - OidcConfigAdminController: view OIDC config (sensitive read) New AuditCategory.AGENT added. Frontend audit log filter updated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.cameleer3.server.app.config;
|
||||
|
||||
import com.cameleer3.server.app.interceptor.AuditInterceptor;
|
||||
import com.cameleer3.server.app.interceptor.ProtocolVersionInterceptor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
@@ -7,17 +8,17 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Web MVC configuration.
|
||||
* <p>
|
||||
* Registers the {@link ProtocolVersionInterceptor} on data and agent endpoint paths,
|
||||
* excluding health, API docs, and Swagger UI paths that do not require protocol versioning.
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
private final ProtocolVersionInterceptor protocolVersionInterceptor;
|
||||
private final AuditInterceptor auditInterceptor;
|
||||
|
||||
public WebConfig(ProtocolVersionInterceptor protocolVersionInterceptor) {
|
||||
public WebConfig(ProtocolVersionInterceptor protocolVersionInterceptor,
|
||||
AuditInterceptor auditInterceptor) {
|
||||
this.protocolVersionInterceptor = protocolVersionInterceptor;
|
||||
this.auditInterceptor = auditInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -33,5 +34,14 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
"/api/v1/agents/register",
|
||||
"/api/v1/agents/*/refresh"
|
||||
);
|
||||
|
||||
// Safety-net audit: catches any unaudited POST/PUT/DELETE
|
||||
registry.addInterceptor(auditInterceptor)
|
||||
.addPathPatterns("/api/v1/**")
|
||||
.excludePathPatterns(
|
||||
"/api/v1/data/**",
|
||||
"/api/v1/agents/*/heartbeat",
|
||||
"/api/v1/health"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ import com.cameleer3.server.app.dto.CommandAckRequest;
|
||||
import com.cameleer3.server.app.dto.CommandBroadcastResponse;
|
||||
import com.cameleer3.server.app.dto.CommandRequest;
|
||||
import com.cameleer3.server.app.dto.CommandSingleResponse;
|
||||
import com.cameleer3.server.core.admin.AuditCategory;
|
||||
import com.cameleer3.server.core.admin.AuditResult;
|
||||
import com.cameleer3.server.core.admin.AuditService;
|
||||
import com.cameleer3.server.core.agent.AgentCommand;
|
||||
import com.cameleer3.server.core.agent.AgentEventService;
|
||||
import com.cameleer3.server.core.agent.AgentInfo;
|
||||
@@ -13,6 +16,7 @@ import com.cameleer3.server.core.agent.AgentState;
|
||||
import com.cameleer3.server.core.agent.CommandType;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -51,15 +55,18 @@ public class AgentCommandController {
|
||||
private final SseConnectionManager connectionManager;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final AgentEventService agentEventService;
|
||||
private final AuditService auditService;
|
||||
|
||||
public AgentCommandController(AgentRegistryService registryService,
|
||||
SseConnectionManager connectionManager,
|
||||
ObjectMapper objectMapper,
|
||||
AgentEventService agentEventService) {
|
||||
AgentEventService agentEventService,
|
||||
AuditService auditService) {
|
||||
this.registryService = registryService;
|
||||
this.connectionManager = connectionManager;
|
||||
this.objectMapper = objectMapper;
|
||||
this.agentEventService = agentEventService;
|
||||
this.auditService = auditService;
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/commands")
|
||||
@@ -69,7 +76,8 @@ public class AgentCommandController {
|
||||
@ApiResponse(responseCode = "400", description = "Invalid command payload")
|
||||
@ApiResponse(responseCode = "404", description = "Agent not registered")
|
||||
public ResponseEntity<CommandSingleResponse> sendCommand(@PathVariable String id,
|
||||
@RequestBody CommandRequest request) throws JsonProcessingException {
|
||||
@RequestBody CommandRequest request,
|
||||
HttpServletRequest httpRequest) throws JsonProcessingException {
|
||||
AgentInfo agent = registryService.findById(id);
|
||||
if (agent == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Agent not found: " + id);
|
||||
@@ -81,6 +89,10 @@ public class AgentCommandController {
|
||||
|
||||
String status = connectionManager.isConnected(id) ? "DELIVERED" : "PENDING";
|
||||
|
||||
auditService.log("send_agent_command", AuditCategory.AGENT, id,
|
||||
java.util.Map.of("type", request.type(), "status", status),
|
||||
AuditResult.SUCCESS, httpRequest);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.ACCEPTED)
|
||||
.body(new CommandSingleResponse(command.id(), status));
|
||||
}
|
||||
@@ -91,7 +103,8 @@ public class AgentCommandController {
|
||||
@ApiResponse(responseCode = "202", description = "Commands accepted")
|
||||
@ApiResponse(responseCode = "400", description = "Invalid command payload")
|
||||
public ResponseEntity<CommandBroadcastResponse> sendGroupCommand(@PathVariable String group,
|
||||
@RequestBody CommandRequest request) throws JsonProcessingException {
|
||||
@RequestBody CommandRequest request,
|
||||
HttpServletRequest httpRequest) throws JsonProcessingException {
|
||||
CommandType type = mapCommandType(request.type());
|
||||
String payloadJson = request.payload() != null ? objectMapper.writeValueAsString(request.payload()) : "{}";
|
||||
|
||||
@@ -106,6 +119,10 @@ public class AgentCommandController {
|
||||
commandIds.add(command.id());
|
||||
}
|
||||
|
||||
auditService.log("broadcast_group_command", AuditCategory.AGENT, group,
|
||||
java.util.Map.of("type", request.type(), "agentCount", agents.size()),
|
||||
AuditResult.SUCCESS, httpRequest);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.ACCEPTED)
|
||||
.body(new CommandBroadcastResponse(commandIds, agents.size()));
|
||||
}
|
||||
@@ -115,7 +132,8 @@ public class AgentCommandController {
|
||||
description = "Sends a command to all agents currently in LIVE state")
|
||||
@ApiResponse(responseCode = "202", description = "Commands accepted")
|
||||
@ApiResponse(responseCode = "400", description = "Invalid command payload")
|
||||
public ResponseEntity<CommandBroadcastResponse> broadcastCommand(@RequestBody CommandRequest request) throws JsonProcessingException {
|
||||
public ResponseEntity<CommandBroadcastResponse> broadcastCommand(@RequestBody CommandRequest request,
|
||||
HttpServletRequest httpRequest) throws JsonProcessingException {
|
||||
CommandType type = mapCommandType(request.type());
|
||||
String payloadJson = request.payload() != null ? objectMapper.writeValueAsString(request.payload()) : "{}";
|
||||
|
||||
@@ -127,6 +145,10 @@ public class AgentCommandController {
|
||||
commandIds.add(command.id());
|
||||
}
|
||||
|
||||
auditService.log("broadcast_all_command", AuditCategory.AGENT, null,
|
||||
java.util.Map.of("type", request.type(), "agentCount", liveAgents.size()),
|
||||
AuditResult.SUCCESS, httpRequest);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.ACCEPTED)
|
||||
.body(new CommandBroadcastResponse(commandIds, liveAgents.size()));
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import com.cameleer3.server.app.dto.AgentRegistrationRequest;
|
||||
import com.cameleer3.server.app.dto.AgentRegistrationResponse;
|
||||
import com.cameleer3.server.app.dto.ErrorResponse;
|
||||
import com.cameleer3.server.app.security.BootstrapTokenValidator;
|
||||
import com.cameleer3.server.core.admin.AuditCategory;
|
||||
import com.cameleer3.server.core.admin.AuditResult;
|
||||
import com.cameleer3.server.core.admin.AuditService;
|
||||
import com.cameleer3.server.core.agent.AgentEventService;
|
||||
import com.cameleer3.server.core.agent.AgentInfo;
|
||||
import com.cameleer3.server.core.agent.AgentRegistryService;
|
||||
@@ -58,6 +61,7 @@ public class AgentRegistrationController {
|
||||
private final JwtService jwtService;
|
||||
private final Ed25519SigningService ed25519SigningService;
|
||||
private final AgentEventService agentEventService;
|
||||
private final AuditService auditService;
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
public AgentRegistrationController(AgentRegistryService registryService,
|
||||
@@ -66,6 +70,7 @@ public class AgentRegistrationController {
|
||||
JwtService jwtService,
|
||||
Ed25519SigningService ed25519SigningService,
|
||||
AgentEventService agentEventService,
|
||||
AuditService auditService,
|
||||
JdbcTemplate jdbc) {
|
||||
this.registryService = registryService;
|
||||
this.config = config;
|
||||
@@ -73,6 +78,7 @@ public class AgentRegistrationController {
|
||||
this.jwtService = jwtService;
|
||||
this.ed25519SigningService = ed25519SigningService;
|
||||
this.agentEventService = agentEventService;
|
||||
this.auditService = auditService;
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
@@ -113,6 +119,10 @@ public class AgentRegistrationController {
|
||||
agentEventService.recordEvent(request.agentId(), application, "REGISTERED",
|
||||
"Agent registered: " + request.name());
|
||||
|
||||
auditService.log(request.agentId(), "agent_register", AuditCategory.AGENT, request.agentId(),
|
||||
Map.of("application", application, "name", request.name()),
|
||||
AuditResult.SUCCESS, httpRequest);
|
||||
|
||||
// Issue JWT tokens with AGENT role
|
||||
List<String> roles = List.of("AGENT");
|
||||
String accessToken = jwtService.createAccessToken(request.agentId(), application, roles);
|
||||
@@ -135,7 +145,8 @@ public class AgentRegistrationController {
|
||||
@ApiResponse(responseCode = "401", description = "Invalid or expired refresh token")
|
||||
@ApiResponse(responseCode = "404", description = "Agent not found")
|
||||
public ResponseEntity<AgentRefreshResponse> refresh(@PathVariable String id,
|
||||
@RequestBody AgentRefreshRequest request) {
|
||||
@RequestBody AgentRefreshRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
if (request.refreshToken() == null || request.refreshToken().isBlank()) {
|
||||
return ResponseEntity.status(401).build();
|
||||
}
|
||||
@@ -169,6 +180,9 @@ public class AgentRegistrationController {
|
||||
String newAccessToken = jwtService.createAccessToken(agentId, agent.application(), roles);
|
||||
String newRefreshToken = jwtService.createRefreshToken(agentId, agent.application(), roles);
|
||||
|
||||
auditService.log(agentId, "agent_token_refresh", AuditCategory.AUTH, agentId,
|
||||
null, AuditResult.SUCCESS, httpRequest);
|
||||
|
||||
return ResponseEntity.ok(new AgentRefreshResponse(newAccessToken, newRefreshToken));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.cameleer3.server.app.controller;
|
||||
|
||||
import com.cameleer3.common.model.ApplicationConfig;
|
||||
import com.cameleer3.server.app.storage.PostgresApplicationConfigRepository;
|
||||
import com.cameleer3.server.core.admin.AuditCategory;
|
||||
import com.cameleer3.server.core.admin.AuditResult;
|
||||
import com.cameleer3.server.core.admin.AuditService;
|
||||
import com.cameleer3.server.core.agent.AgentCommand;
|
||||
import com.cameleer3.server.core.agent.AgentInfo;
|
||||
import com.cameleer3.server.core.agent.AgentRegistryService;
|
||||
@@ -12,6 +15,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -35,20 +39,24 @@ public class ApplicationConfigController {
|
||||
private final PostgresApplicationConfigRepository configRepository;
|
||||
private final AgentRegistryService registryService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final AuditService auditService;
|
||||
|
||||
public ApplicationConfigController(PostgresApplicationConfigRepository configRepository,
|
||||
AgentRegistryService registryService,
|
||||
ObjectMapper objectMapper) {
|
||||
ObjectMapper objectMapper,
|
||||
AuditService auditService) {
|
||||
this.configRepository = configRepository;
|
||||
this.registryService = registryService;
|
||||
this.objectMapper = objectMapper;
|
||||
this.auditService = auditService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "List all application configs",
|
||||
description = "Returns stored configurations for all applications")
|
||||
@ApiResponse(responseCode = "200", description = "Configs returned")
|
||||
public ResponseEntity<List<ApplicationConfig>> listConfigs() {
|
||||
public ResponseEntity<List<ApplicationConfig>> listConfigs(HttpServletRequest httpRequest) {
|
||||
auditService.log("view_app_configs", AuditCategory.CONFIG, null, null, AuditResult.SUCCESS, httpRequest);
|
||||
return ResponseEntity.ok(configRepository.findAll());
|
||||
}
|
||||
|
||||
@@ -56,7 +64,9 @@ public class ApplicationConfigController {
|
||||
@Operation(summary = "Get application config",
|
||||
description = "Returns the current configuration for an application. Returns defaults if none stored.")
|
||||
@ApiResponse(responseCode = "200", description = "Config returned")
|
||||
public ResponseEntity<ApplicationConfig> getConfig(@PathVariable String application) {
|
||||
public ResponseEntity<ApplicationConfig> getConfig(@PathVariable String application,
|
||||
HttpServletRequest httpRequest) {
|
||||
auditService.log("view_app_config", AuditCategory.CONFIG, application, null, AuditResult.SUCCESS, httpRequest);
|
||||
return ResponseEntity.ok(
|
||||
configRepository.findByApplication(application)
|
||||
.orElse(defaultConfig(application)));
|
||||
@@ -68,7 +78,8 @@ public class ApplicationConfigController {
|
||||
@ApiResponse(responseCode = "200", description = "Config saved and pushed")
|
||||
public ResponseEntity<ApplicationConfig> updateConfig(@PathVariable String application,
|
||||
@RequestBody ApplicationConfig config,
|
||||
Authentication auth) {
|
||||
Authentication auth,
|
||||
HttpServletRequest httpRequest) {
|
||||
String updatedBy = auth != null ? auth.getName() : "system";
|
||||
|
||||
config.setApplication(application);
|
||||
@@ -77,6 +88,10 @@ public class ApplicationConfigController {
|
||||
int pushed = pushConfigToAgents(application, saved);
|
||||
log.info("Config v{} saved for '{}', pushed to {} agent(s)", saved.getVersion(), application, pushed);
|
||||
|
||||
auditService.log("update_app_config", AuditCategory.CONFIG, application,
|
||||
Map.of("version", saved.getVersion(), "agentsPushed", pushed),
|
||||
AuditResult.SUCCESS, httpRequest);
|
||||
|
||||
return ResponseEntity.ok(saved);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,11 @@ import com.cameleer3.server.core.admin.AuditCategory;
|
||||
import com.cameleer3.server.core.admin.AuditRepository;
|
||||
import com.cameleer3.server.core.admin.AuditRepository.AuditPage;
|
||||
import com.cameleer3.server.core.admin.AuditRepository.AuditQuery;
|
||||
import com.cameleer3.server.core.admin.AuditResult;
|
||||
import com.cameleer3.server.core.admin.AuditService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@@ -26,14 +29,17 @@ import java.time.ZoneOffset;
|
||||
public class AuditLogController {
|
||||
|
||||
private final AuditRepository auditRepository;
|
||||
private final AuditService auditService;
|
||||
|
||||
public AuditLogController(AuditRepository auditRepository) {
|
||||
public AuditLogController(AuditRepository auditRepository, AuditService auditService) {
|
||||
this.auditRepository = auditRepository;
|
||||
this.auditService = auditService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "Search audit log entries with pagination")
|
||||
public ResponseEntity<AuditLogPageResponse> getAuditLog(
|
||||
HttpServletRequest httpRequest,
|
||||
@RequestParam(required = false) String username,
|
||||
@RequestParam(required = false) String category,
|
||||
@RequestParam(required = false) String search,
|
||||
@@ -58,6 +64,8 @@ public class AuditLogController {
|
||||
}
|
||||
}
|
||||
|
||||
auditService.log("view_audit_log", AuditCategory.AUTH, null, null, AuditResult.SUCCESS, httpRequest);
|
||||
|
||||
AuditQuery query = new AuditQuery(username, cat, search, fromInstant, toInstant, sort, order, page, size);
|
||||
AuditPage result = auditRepository.find(query);
|
||||
|
||||
|
||||
@@ -61,7 +61,8 @@ public class OidcConfigAdminController {
|
||||
@GetMapping
|
||||
@Operation(summary = "Get OIDC configuration")
|
||||
@ApiResponse(responseCode = "200", description = "Current OIDC configuration (client_secret masked)")
|
||||
public ResponseEntity<OidcAdminConfigResponse> getConfig() {
|
||||
public ResponseEntity<OidcAdminConfigResponse> getConfig(HttpServletRequest httpRequest) {
|
||||
auditService.log("view_oidc_config", AuditCategory.CONFIG, null, null, AuditResult.SUCCESS, httpRequest);
|
||||
Optional<OidcConfig> config = configRepository.find();
|
||||
if (config.isEmpty()) {
|
||||
return ResponseEntity.ok(OidcAdminConfigResponse.unconfigured());
|
||||
|
||||
@@ -58,7 +58,8 @@ public class UserAdminController {
|
||||
@GetMapping
|
||||
@Operation(summary = "List all users with RBAC detail")
|
||||
@ApiResponse(responseCode = "200", description = "User list returned")
|
||||
public ResponseEntity<List<UserDetail>> listUsers() {
|
||||
public ResponseEntity<List<UserDetail>> listUsers(HttpServletRequest httpRequest) {
|
||||
auditService.log("view_users", AuditCategory.USER_MGMT, null, null, AuditResult.SUCCESS, httpRequest);
|
||||
return ResponseEntity.ok(rbacService.listUsers());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.cameleer3.server.app.interceptor;
|
||||
|
||||
import com.cameleer3.server.core.admin.AuditCategory;
|
||||
import com.cameleer3.server.core.admin.AuditResult;
|
||||
import com.cameleer3.server.core.admin.AuditService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Safety-net audit interceptor that logs a basic entry for any state-changing
|
||||
* request (POST/PUT/DELETE) that was not explicitly audited by the controller.
|
||||
* <p>
|
||||
* Controllers that call {@link AuditService#log} set the {@code audit.logged}
|
||||
* request attribute, which this interceptor checks to avoid double-recording.
|
||||
*/
|
||||
@Component
|
||||
public class AuditInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final Set<String> AUDITABLE_METHODS = Set.of("POST", "PUT", "DELETE");
|
||||
|
||||
private final AuditService auditService;
|
||||
|
||||
public AuditInterceptor(AuditService auditService) {
|
||||
this.auditService = auditService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler, Exception ex) {
|
||||
if (!AUDITABLE_METHODS.contains(request.getMethod())) {
|
||||
return;
|
||||
}
|
||||
if (Boolean.TRUE.equals(request.getAttribute("audit.logged"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
String path = request.getRequestURI();
|
||||
AuditResult result = response.getStatus() < 400 ? AuditResult.SUCCESS : AuditResult.FAILURE;
|
||||
|
||||
auditService.log(
|
||||
"HTTP " + request.getMethod() + " " + path,
|
||||
AuditCategory.INFRA,
|
||||
path,
|
||||
Map.of("status", response.getStatus()),
|
||||
result,
|
||||
request);
|
||||
}
|
||||
}
|
||||
@@ -159,6 +159,9 @@ public class OidcAuthController {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("OIDC callback failed: {}", e.getMessage(), e);
|
||||
auditService.log("unknown", "login_oidc", AuditCategory.AUTH, null,
|
||||
Map.of("reason", e.getMessage() != null ? e.getMessage() : "unknown"),
|
||||
AuditResult.FAILURE, httpRequest);
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED,
|
||||
"OIDC authentication failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
@@ -123,7 +123,8 @@ public class UiAuthController {
|
||||
@ApiResponse(responseCode = "200", description = "Token refreshed")
|
||||
@ApiResponse(responseCode = "401", description = "Invalid refresh token",
|
||||
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||
public ResponseEntity<AuthTokenResponse> refresh(@RequestBody RefreshRequest request) {
|
||||
public ResponseEntity<AuthTokenResponse> refresh(@RequestBody RefreshRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
try {
|
||||
JwtValidationResult result = jwtService.validateRefreshToken(request.refreshToken());
|
||||
if (!result.subject().startsWith("user:")) {
|
||||
@@ -138,6 +139,7 @@ public class UiAuthController {
|
||||
String displayName = userRepository.findById(result.subject())
|
||||
.map(UserInfo::displayName)
|
||||
.orElse(result.subject());
|
||||
auditService.log(result.subject(), "token_refresh", AuditCategory.AUTH, null, null, AuditResult.SUCCESS, httpRequest);
|
||||
return ResponseEntity.ok(new AuthTokenResponse(accessToken, refreshToken, displayName, null));
|
||||
} catch (ResponseStatusException e) {
|
||||
throw e;
|
||||
|
||||
Reference in New Issue
Block a user