feat!: capabilities replace API token scopes; every admin endpoint declares one, with a picker UI [CI]

This commit is contained in:
justme committed 2026-10-07 20:56:36 +08:00
1 parent 55b80f0a97
commit ddb6608e32
38 files changed
+1148 -218

No files matched your search

@@ -17,50 +17,67 @@ public static class AgentEndpoints
public static void Map(RouteGroupBuilder group)
{
group.MapGet("/agents", ListAsync)
.Api("agents_list", "All agents including pending and disabled ones", AgentsTag).Returns<PageDto<AgentDto>>().ProducesProblems(401, 403);
.Api("agents_list", "All agents including pending and disabled ones", AgentsTag).Returns<PageDto<AgentDto>>().ProducesProblems(401, 403)
.Requires(Capability.FleetRead);
group.MapGet("/agents/{id}", GetAsync)
.Api("agents_get", "One agent", AgentsTag).Returns<AgentDto>().ProducesProblems(401, 403, 404);
.Api("agents_get", "One agent", AgentsTag).Returns<AgentDto>().ProducesProblems(401, 403, 404)
.Requires(Capability.FleetRead);
group.MapPatch("/agents/{id}", PatchAsync)
.Accepts<AgentPatchRequest>("application/merge-patch+json", "application/json")
.Api("agents_update", "Update name, labels, public host or visibility (merge-patch, If-Match supported)", AgentsTag)
.Returns<AgentDto>().ProducesProblems(400, 401, 403, 404, 409, 412);
.Returns<AgentDto>().ProducesProblems(400, 401, 403, 404, 409, 412)
.Requires(Capability.FleetWrite);
group.MapPost("/agents/{id}/approve", (HttpContext http, AgentService agents, string id) => TransitionAsync(http, id, (nodeId, ct) => agents.ApproveAsync(nodeId, ct)))
.Api("agents_approve", "Approve a pending agent", AgentsTag).Returns<AgentDto>().ProducesProblems(401, 403, 404, 409);
.Api("agents_approve", "Approve a pending agent", AgentsTag).Returns<AgentDto>().ProducesProblems(401, 403, 404, 409)
.Requires(Capability.FleetEnroll);
group.MapPost("/agents/{id}/disable", (HttpContext http, AgentService agents, string id) => TransitionAsync(http, id, (nodeId, ct) => agents.DisableAsync(nodeId, ct)))
.Api("agents_disable", "Disable an agent and drop its connection", AgentsTag).Returns<AgentDto>().ProducesProblems(401, 403, 404, 409);
.Api("agents_disable", "Disable an agent and drop its connection", AgentsTag).Returns<AgentDto>().ProducesProblems(401, 403, 404, 409)
.Requires(Capability.FleetWrite);
group.MapPost("/agents/{id}/enable", (HttpContext http, AgentService agents, string id) => TransitionAsync(http, id, (nodeId, ct) => agents.EnableAsync(nodeId, ct)))
.Api("agents_enable", "Enable a disabled agent", AgentsTag).Returns<AgentDto>().ProducesProblems(401, 403, 404, 409);
.Api("agents_enable", "Enable a disabled agent", AgentsTag).Returns<AgentDto>().ProducesProblems(401, 403, 404, 409)
.Requires(Capability.FleetWrite);
group.MapPost("/agents/{id}/takeover", (HttpContext http, AgentService agents, string id) => TransitionAsync(http, id, (nodeId, ct) => agents.TakeoverAsync(nodeId, ct)))
.Api("agents_takeover", "Replace the existing node of the same name with this pending one, keeping history", AgentsTag)
.Returns<AgentDto>().ProducesProblems(401, 403, 404);
.Returns<AgentDto>().ProducesProblems(401, 403, 404)
.Requires(Capability.FleetEnroll);
group.MapDelete("/agents/{id}", DeleteAsync)
.Api("agents_delete", "Delete an agent; history expires with the retention policy", AgentsTag)
.Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(401, 403);
.Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(401, 403)
.Requires(Capability.FleetRevoke);
group.MapGet("/join-tokens", ListTokensAsync)
.Api("join_tokens_list", "Join tokens", TokensTag).Returns<PageDto<JoinTokenDto>>().ProducesProblems(401, 403);
.Api("join_tokens_list", "Join tokens", TokensTag).Returns<PageDto<JoinTokenDto>>().ProducesProblems(401, 403)
.Requires(Capability.FleetRead);
group.MapPost("/join-tokens", CreateTokenAsync)
.Api("join_tokens_create", "Create a join token for an agent or a probe (role); the plaintext is returned only once", TokensTag)
.Returns<CreatedJoinTokenDto>(StatusCodes.Status201Created).ProducesProblems(400, 401, 403);
.Returns<CreatedJoinTokenDto>(StatusCodes.Status201Created).ProducesProblems(400, 401, 403)
.Requires(Capability.FleetEnroll);
group.MapDelete("/join-tokens/{id}", RevokeTokenAsync)
.Api("join_tokens_revoke", "Revoke a join token; with purge=true delete the record for good", TokensTag)
.Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(401, 403);
.Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(401, 403)
.Requires(Capability.FleetEnroll);
group.MapGet("/probes", ListProbesAsync)
.Api("probes_list", "All probes, with the agents each one is connected to", ProbeTag).Returns<PageDto<ProbeDto>>().ProducesProblems(401, 403);
.Api("probes_list", "All probes, with the agents each one is connected to", ProbeTag).Returns<PageDto<ProbeDto>>().ProducesProblems(401, 403)
.Requires(Capability.FleetRead);
group.MapGet("/probes/{id}", GetProbeAsync)
.Api("probes_get", "One probe", ProbeTag).Returns<ProbeDto>().ProducesProblems(401, 403, 404);
.Api("probes_get", "One probe", ProbeTag).Returns<ProbeDto>().ProducesProblems(401, 403, 404)
.Requires(Capability.FleetRead);
group.MapPatch("/probes/{id}", PatchProbeAsync)
.Api("probes_update", "Rename a probe", ProbeTag).Returns<ProbeDto>().ProducesProblems(400, 401, 403, 404, 409);
.Api("probes_update", "Rename a probe", ProbeTag).Returns<ProbeDto>().ProducesProblems(400, 401, 403, 404, 409)
.Requires(Capability.FleetWrite);
group.MapPost("/probes/{id}/revoke", RevokeProbeAsync)
.Api("probes_revoke", "Revoke a probe: every agent drops its session and refuses it; history stays until it expires", ProbeTag)
.Returns<ProbeDto>().ProducesProblems(401, 403, 404);
.Returns<ProbeDto>().ProducesProblems(401, 403, 404)
.Requires(Capability.FleetRevoke);
group.MapDelete("/probes/{id}", DeleteProbeAsync)
.Api("probes_delete", "Delete a probe and its lines; stored data expires with the retention policy", ProbeTag)
.Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(401, 403);
.Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(401, 403)
.Requires(Capability.FleetRevoke);
group.MapPost("/agents/{id}/probes/{probe_id}/disconnect", DisconnectProbeAsync)
.Api("agents_probe_disconnect", "Make an agent drop one probe's session (the probe reconnects unless it is revoked)", AgentsTag)
.Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(401, 403, 404, 409);
.Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(401, 403, 404, 409)
.Requires(Capability.FleetWrite);
}
private static async Task<IResult> ListAsync(AgentService agents, CancellationToken ct) =>