fix: return rotated refresh token from agent token refresh endpoint
Previously the refresh endpoint only returned a new accessToken, causing agents to lose their refreshToken after the first refresh cycle and forcing a full re-registration every ~2 hours. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -159,8 +159,9 @@ public class AgentRegistrationController {
|
|||||||
List<String> roles = result.roles().isEmpty()
|
List<String> roles = result.roles().isEmpty()
|
||||||
? List.of("AGENT") : result.roles();
|
? List.of("AGENT") : result.roles();
|
||||||
String newAccessToken = jwtService.createAccessToken(agentId, agent.group(), roles);
|
String newAccessToken = jwtService.createAccessToken(agentId, agent.group(), roles);
|
||||||
|
String newRefreshToken = jwtService.createRefreshToken(agentId, agent.group(), roles);
|
||||||
|
|
||||||
return ResponseEntity.ok(new AgentRefreshResponse(newAccessToken));
|
return ResponseEntity.ok(new AgentRefreshResponse(newAccessToken, newRefreshToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/{id}/heartbeat")
|
@PostMapping("/{id}/heartbeat")
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ package com.cameleer3.server.app.dto;
|
|||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
@Schema(description = "Refreshed access token")
|
@Schema(description = "Refreshed access and refresh tokens")
|
||||||
public record AgentRefreshResponse(@NotNull String accessToken) {}
|
public record AgentRefreshResponse(@NotNull String accessToken, @NotNull String refreshToken) {}
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ class JwtRefreshIT extends AbstractPostgresIT {
|
|||||||
|
|
||||||
JsonNode body = objectMapper.readTree(response.getBody());
|
JsonNode body = objectMapper.readTree(response.getBody());
|
||||||
assertThat(body.get("accessToken").asText()).isNotEmpty();
|
assertThat(body.get("accessToken").asText()).isNotEmpty();
|
||||||
|
assertThat(body.get("refreshToken").asText()).isNotEmpty();
|
||||||
|
assertThat(body.get("refreshToken").asText()).isNotEqualTo(refreshToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -2913,14 +2913,18 @@
|
|||||||
},
|
},
|
||||||
"AgentRefreshResponse": {
|
"AgentRefreshResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Refreshed access token",
|
"description": "Refreshed access and refresh tokens",
|
||||||
"properties": {
|
"properties": {
|
||||||
"accessToken": {
|
"accessToken": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"refreshToken": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"accessToken"
|
"accessToken",
|
||||||
|
"refreshToken"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"CommandRequest": {
|
"CommandRequest": {
|
||||||
|
|||||||
3
ui/src/api/schema.d.ts
vendored
3
ui/src/api/schema.d.ts
vendored
@@ -1069,9 +1069,10 @@ export interface components {
|
|||||||
AgentRefreshRequest: {
|
AgentRefreshRequest: {
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
};
|
};
|
||||||
/** @description Refreshed access token */
|
/** @description Refreshed access and refresh tokens */
|
||||||
AgentRefreshResponse: {
|
AgentRefreshResponse: {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
};
|
};
|
||||||
/** @description Command to send to agent(s) */
|
/** @description Command to send to agent(s) */
|
||||||
CommandRequest: {
|
CommandRequest: {
|
||||||
|
|||||||
Reference in New Issue
Block a user