feat(auth): add skill:manage scope for lifecycle governance API token policies

- Add require(skill:manage) for hide/unhide/archive/unarchive endpoints
- Add allow (no scope) for social ops: star, rate, report, notifications
- Update DeviceAuthService default scope to include skill:manage
- Update TokenController default scope to include skill:manage
This commit is contained in:
chenbaowang 2026-04-25 10:09:27 +08:00
parent 4ae9518add
commit ac2d4ebc8a
3 changed files with 22 additions and 3 deletions

View file

@ -41,12 +41,12 @@ public class TokenController extends BaseApiController {
@Valid @RequestBody TokenCreateRequest request) {
String scopeJson;
if (request.scopes() == null || request.scopes().isEmpty()) {
scopeJson = "[\"skill:read\",\"skill:publish\"]";
scopeJson = "[\"skill:read\",\"skill:publish\",\"skill:manage\"]";
} else {
try {
scopeJson = objectMapper.writeValueAsString(request.scopes());
} catch (JsonProcessingException e) {
scopeJson = "[\"skill:read\",\"skill:publish\"]";
scopeJson = "[\"skill:read\",\"skill:publish\",\"skill:manage\"]";
}
}

View file

@ -29,7 +29,7 @@ public class DeviceAuthService {
private static final long PENDING_CODE_TTL_MINUTES = EXPIRES_IN_SECONDS / 60L;
private static final long USED_CODE_TTL_MINUTES = 1L;
private static final String CLI_DEVICE_TOKEN_NAME = "CLI Device Flow";
private static final String CLI_DEVICE_SCOPE_JSON = "[\"skill:read\",\"skill:publish\"]";
private static final String CLI_DEVICE_SCOPE_JSON = "[\"skill:read\",\"skill:publish\",\"skill:manage\"]";
private final RedisTemplate<String, Object> redisTemplate;
private final ApiTokenService apiTokenService;

View file

@ -126,6 +126,25 @@ public class RouteSecurityPolicyRegistry {
ApiTokenPolicy.allow(HttpMethod.POST, "/api/v1/reviews/**"),
ApiTokenPolicy.allow(HttpMethod.GET, "/api/web/reviews/**"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/web/reviews/**"),
// Lifecycle governance require skill:manage scope (must appear before broad allow)
ApiTokenPolicy.require(HttpMethod.POST, "/api/v1/skills/*/*/hide", "skill:manage"),
ApiTokenPolicy.require(HttpMethod.POST, "/api/v1/skills/*/*/unhide", "skill:manage"),
ApiTokenPolicy.require(HttpMethod.POST, "/api/v1/skills/*/*/archive", "skill:manage"),
ApiTokenPolicy.require(HttpMethod.POST, "/api/v1/skills/*/*/unarchive", "skill:manage"),
ApiTokenPolicy.require(HttpMethod.POST, "/api/web/skills/*/*/hide", "skill:manage"),
ApiTokenPolicy.require(HttpMethod.POST, "/api/web/skills/*/*/unhide", "skill:manage"),
ApiTokenPolicy.require(HttpMethod.POST, "/api/web/skills/*/*/archive", "skill:manage"),
ApiTokenPolicy.require(HttpMethod.POST, "/api/web/skills/*/*/unarchive", "skill:manage"),
// Social / personal actions no scope required
ApiTokenPolicy.allow(HttpMethod.PUT, "/api/v1/skills/*/star"),
ApiTokenPolicy.allow(HttpMethod.DELETE, "/api/v1/skills/*/star"),
ApiTokenPolicy.allow(HttpMethod.PUT, "/api/web/skills/*/star"),
ApiTokenPolicy.allow(HttpMethod.DELETE, "/api/web/skills/*/star"),
ApiTokenPolicy.allow(HttpMethod.PUT, "/api/v1/skills/*/rating"),
ApiTokenPolicy.allow(HttpMethod.PUT, "/api/web/skills/*/rating"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/v1/skills/*/*/reports"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/web/skills/*/*/reports"),
// Broad fallback for remaining skill operations (publish, etc.)
ApiTokenPolicy.allow(HttpMethod.POST, "/api/v1/skills/**"),
ApiTokenPolicy.allow(HttpMethod.POST, "/api/web/skills/**"),
ApiTokenPolicy.allow(HttpMethod.PUT, "/api/v1/skills/**"),