From ac2d4ebc8ad3237cfe1f9d9b573fce82f2bc35ad Mon Sep 17 00:00:00 2001 From: chenbaowang <49091147+Rsweater@users.noreply.github.com> Date: Sat, 25 Apr 2026 10:09:27 +0800 Subject: [PATCH] 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 --- .../skillhub/controller/TokenController.java | 4 ++-- .../auth/device/DeviceAuthService.java | 2 +- .../policy/RouteSecurityPolicyRegistry.java | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/TokenController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/TokenController.java index 19115087..3e099610 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/TokenController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/TokenController.java @@ -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\"]"; } } diff --git a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/device/DeviceAuthService.java b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/device/DeviceAuthService.java index e838c9a8..333a1ca1 100644 --- a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/device/DeviceAuthService.java +++ b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/device/DeviceAuthService.java @@ -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 redisTemplate; private final ApiTokenService apiTokenService; diff --git a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/policy/RouteSecurityPolicyRegistry.java b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/policy/RouteSecurityPolicyRegistry.java index 9d1133f1..e9de30d6 100644 --- a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/policy/RouteSecurityPolicyRegistry.java +++ b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/policy/RouteSecurityPolicyRegistry.java @@ -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/**"),