This is an automated email from the ASF dual-hosted git repository.

imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph-toolchain.git

commit 554f92b6b16a4bc4c880923c361daec63301c2a9
Author: dark <[email protected]>
AuthorDate: Sun Aug 16 16:40:07 2026 +0800

    fix(hubble): harden compatibility contracts
    
    - preserve member-manager compatibility
    - align version and CI capability boundaries
    - normalize PR-added source formatting
---
 AGENTS.md                                          |   2 +-
 README.md                                          |   6 +-
 .../org/apache/hugegraph/driver/HugeClient.java    |  22 +-
 .../hugegraph/driver/ServerCompatibility.java      |  24 ++-
 .../hugegraph/driver/ServerCompatibilityTest.java  |   4 +-
 hugegraph-hubble/README.md                         |  15 +-
 .../docs/images/compat-1.5-standalone.png          | Bin 0 -> 82286 bytes
 .../docs/images/compat-1.7-standalone.png          | Bin 0 -> 81494 bytes
 .../docs/images/compat-1.8-auth-account.png        | Bin 0 -> 68710 bytes
 .../docs/images/compat-1.8-auth-dashboard.png      | Bin 0 -> 106470 bytes
 .../docs/images/compat-1.8-graphspace-access.png   | Bin 0 -> 62654 bytes
 hugegraph-hubble/docs/images/compat-1.8-pd.png     | Bin 0 -> 28788 bytes
 .../docs/images/hubble-auth-connection-root.png    | Bin 56400 -> 0 bytes
 .../docs/images/hubble-auth-connection.png         | Bin 41540 -> 0 bytes
 .../docs/images/hubble-graphspace-permissions.png  | Bin 60284 -> 0 bytes
 .../docs/images/hubble-legacy-capability.png       | Bin 45865 -> 0 bytes
 hugegraph-hubble/docs/images/hubble-non-auth.png   | Bin 41540 -> 0 bytes
 .../docs/images/hubble-pd-connection.png           | Bin 47441 -> 0 bytes
 .../hugegraph/controller/BaseController.java       |  17 +-
 .../controller/auth/AccessController.java          |  10 +-
 .../controller/auth/BelongController.java          |  16 +-
 .../controller/auth/GraphSpaceUserController.java  |  11 +
 .../hugegraph/controller/auth/LoginController.java |   5 +-
 .../hugegraph/controller/auth/RoleController.java  |  12 +-
 .../controller/auth/TargetController.java          |  12 +-
 .../controller/space/GraphSpaceController.java     |  12 ++
 .../hugegraph/handler/CustomInterceptor.java       |  56 +++--
 .../apache/hugegraph/handler/LoginInterceptor.java |  16 ++
 .../apache/hugegraph/options/HubbleOptions.java    |   6 +-
 .../hugegraph/service/HugeClientPoolService.java   |   4 +
 .../hugegraph/service/auth/AuthContextService.java |  15 +-
 .../hugegraph/service/auth/AuthModeService.java    |   2 +-
 .../service/auth/GraphSpaceUserService.java        | 236 +++++++++++++++------
 .../apache/hugegraph/service/auth/UserService.java | 161 +++++++++-----
 .../hugegraph/service/space/GraphSpaceService.java |  44 +++-
 .../GraphSpaceAuthMutationAuthorizationTest.java   |  52 ++++-
 .../auth/GraphSpaceAuthOwnershipTest.java          |  41 +++-
 .../service/auth/AuthContextServiceTest.java       |   6 +-
 .../apache/hugegraph/unit/AuthSecurityTest.java    |  60 ++++++
 .../unit/UserServiceCompatibilityTest.java         |   4 +
 hugegraph-hubble/hubble-fe/src/App.test.js         |  36 +++-
 .../hubble-fe/src/api/auth-contract.test.js        |  18 ++
 hugegraph-hubble/hubble-fe/src/api/auth.js         |  25 +++
 .../hubble-fe/src/components/Sidebar/index.ant.js  |   6 +-
 .../src/components/Sidebar/index.ant.test.js       |  21 ++
 .../src/i18n/resources/en-US/modules/pages.json    |   4 +-
 .../src/i18n/resources/zh-CN/modules/pages.json    |   4 +-
 .../hubble-fe/src/pages/Account/EditLayer.js       |  22 +-
 .../hubble-fe/src/pages/Account/SpaceAccess.js     |  84 +++++---
 .../src/pages/Account/SpaceAccess.test.js          |  62 ++++--
 .../hubble-fe/src/pages/Account/index.js           |   3 +
 .../src/pages/Account/permissionPresets.js         |  32 ++-
 .../src/pages/Account/permissionPresets.test.js    |  19 ++
 hugegraph-hubble/hubble-fe/src/routes/index.js     |  18 +-
 .../hubble-fe/src/routes/route-guard.test.js       |  21 +-
 tools/checkstyle.xml                               |   2 +-
 56 files changed, 964 insertions(+), 284 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index 4aa6cceaa..9f8ca9eb7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -79,7 +79,7 @@ mvn test -Dtest=FuncTestSuite -pl hugegraph-tools -ntp
 ## Code Style
 
 Checkstyle enforced via `tools/checkstyle.xml`:
-- Max line length: 100 characters
+- Max line length: 120 characters
 - 4-space indentation (no tabs)
 - No star imports
 - No `System.out.println`
diff --git a/README.md b/README.md
index 3eb7a6253..bff20dad7 100644
--- a/README.md
+++ b/README.md
@@ -19,9 +19,11 @@ four readable presets. A thin adapter keeps 1.7 usable and 
limits 1.5 to its
 standalone core graph workflow; version checks are centralized rather than
 spread across UI pages.
 
-![Hubble authentication and connection 
modes](hugegraph-hubble/docs/images/hubble-auth-connection.png)
+![Hubble 1.5 standalone non-auth 
compatibility](hugegraph-hubble/docs/images/compat-1.5-standalone.png)
 
-![Hubble GraphSpace permission 
presets](hugegraph-hubble/docs/images/hubble-graphspace-permissions.png)
+![Hubble 1.8/master PD connection without a manual server 
URL](hugegraph-hubble/docs/images/compat-1.8-pd.png)
+
+![Hubble 1.8/master account / GraphSpace 
permission](hugegraph-hubble/docs/images/compat-1.8-auth-account.png)
 
 **Quick Navigation**: [Architecture](#architecture-overview) | [Quick 
Start](#quick-start) | [Modules](#module-overview) | 
[Build](#build--development) | [Docker](#docker) | [Related 
Projects](#related-projects)
 
diff --git 
a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java 
b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java
index f02fd6146..578b9ce4b 100644
--- a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java
@@ -24,6 +24,7 @@ import lombok.Getter;
 import org.apache.hugegraph.client.RestClient;
 import org.apache.hugegraph.rest.ClientException;
 import org.apache.hugegraph.rest.RestClientConfig;
+import org.apache.hugegraph.structure.auth.User;
 import org.apache.hugegraph.util.VersionUtil;
 import org.apache.hugegraph.version.ClientVersion;
 import org.slf4j.Logger;
@@ -57,6 +58,8 @@ public class HugeClient implements Closeable {
      */
     private volatile boolean apiVersionChecked;
     private final Object apiVersionLock = new Object();
+    private ServerCompatibility.Profile compatibility =
+            ServerCompatibility.Profile.LEGACY;
     private VersionManager version;
     private GraphsManager graphs;
     private SchemaManager schema;
@@ -209,9 +212,8 @@ public class HugeClient implements Closeable {
         //       0.81 equals to the {latest_api_version} +10
         VersionUtil.check(apiVersion, "0.38", "0.81", "hugegraph-api in 
server");
         this.client.apiVersion(apiVersion);
-        boolean supportGs = ServerCompatibility.supportsGraphSpace(
-                this.version.getCoreVersion());
-        this.client.setSupportGs(supportGs);
+        this.compatibility = 
ServerCompatibility.profile(this.version.getCoreVersion());
+        this.client.setSupportGs(this.compatibility.supportsGraphSpace());
     }
 
     public GraphsManager graphs() {
@@ -258,6 +260,20 @@ public class HugeClient implements Closeable {
         return this.auth;
     }
 
+    public boolean supportsDefaultRole() {
+        return this.compatibility.supportsDefaultRole();
+    }
+
+    public User findUserByName(String name) {
+        if (this.supportsDefaultRole()) {
+            return this.auth.getUserByName(name);
+        }
+        return this.auth.listUsers().stream()
+                        .filter(user -> name.equals(user.name()))
+                        .findFirst()
+                        .orElse(null);
+    }
+
     public MetricsManager metrics() {
         return this.metrics;
     }
diff --git 
a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/ServerCompatibility.java
 
b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/ServerCompatibility.java
index ac975d661..18fb6bd2c 100644
--- 
a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/ServerCompatibility.java
+++ 
b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/ServerCompatibility.java
@@ -29,6 +29,7 @@ import org.apache.hugegraph.util.VersionUtil;
 public final class ServerCompatibility {
 
     private static final String GRAPHSPACE_MIN_VERSION = "1.7.0";
+    private static final String DEFAULT_ROLE_MIN_VERSION = "1.8.0";
 
     private ServerCompatibility() {
     }
@@ -39,8 +40,10 @@ public final class ServerCompatibility {
         }
         try {
             String normalized = coreVersion.trim();
-            return VersionUtil.gte(normalized, GRAPHSPACE_MIN_VERSION) ?
-                   Profile.MODERN : Profile.LEGACY;
+            if (VersionUtil.gte(normalized, DEFAULT_ROLE_MIN_VERSION)) {
+                return Profile.MODERN;
+            }
+            return VersionUtil.gte(normalized, GRAPHSPACE_MIN_VERSION) ? 
Profile.GRAPHSPACE : Profile.LEGACY;
         } catch (RuntimeException ignored) {
             return Profile.LEGACY;
         }
@@ -50,18 +53,29 @@ public final class ServerCompatibility {
         return profile(coreVersion).supportsGraphSpace();
     }
 
+    public static boolean supportsDefaultRole(String coreVersion) {
+        return profile(coreVersion).supportsDefaultRole();
+    }
+
     public enum Profile {
-        LEGACY(false),
-        MODERN(true);
+        LEGACY(false, false),
+        GRAPHSPACE(true, false),
+        MODERN(true, true);
 
         private final boolean graphSpace;
+        private final boolean defaultRole;
 
-        Profile(boolean graphSpace) {
+        Profile(boolean graphSpace, boolean defaultRole) {
             this.graphSpace = graphSpace;
+            this.defaultRole = defaultRole;
         }
 
         public boolean supportsGraphSpace() {
             return this.graphSpace;
         }
+
+        public boolean supportsDefaultRole() {
+            return this.defaultRole;
+        }
     }
 }
diff --git 
a/hugegraph-client/src/test/java/org/apache/hugegraph/driver/ServerCompatibilityTest.java
 
b/hugegraph-client/src/test/java/org/apache/hugegraph/driver/ServerCompatibilityTest.java
index f3bccbf17..96f98ad0c 100644
--- 
a/hugegraph-client/src/test/java/org/apache/hugegraph/driver/ServerCompatibilityTest.java
+++ 
b/hugegraph-client/src/test/java/org/apache/hugegraph/driver/ServerCompatibilityTest.java
@@ -35,7 +35,9 @@ public class ServerCompatibilityTest {
         Assert.assertTrue(ServerCompatibility.supportsGraphSpace("1.7.0"));
         Assert.assertTrue(ServerCompatibility.supportsGraphSpace(" 1.7.0 "));
         Assert.assertTrue(ServerCompatibility.supportsGraphSpace("1.8.0"));
-        Assert.assertEquals(ServerCompatibility.Profile.MODERN,
+        Assert.assertFalse(ServerCompatibility.supportsDefaultRole("1.7.0"));
+        Assert.assertTrue(ServerCompatibility.supportsDefaultRole("1.8.0"));
+        Assert.assertEquals(ServerCompatibility.Profile.GRAPHSPACE,
                             ServerCompatibility.profile("1.7.1"));
     }
 }
diff --git a/hugegraph-hubble/README.md b/hugegraph-hubble/README.md
index 5b3fb9955..4062f4f9f 100644
--- a/hugegraph-hubble/README.md
+++ b/hugegraph-hubble/README.md
@@ -34,15 +34,20 @@ being scattered through controllers or pages. See
 The following captures are collected from Chrome against the running Hubble
 application and document the supported states:
 
-![Authenticated connection and GraphSpace 
switch](docs/images/hubble-auth-connection.png)
+![HugeGraph Server 1.5 standalone 
non-auth](docs/images/compat-1.5-standalone.png)
 
-![Anonymous mode with account controls hidden](docs/images/hubble-non-auth.png)
+![HugeGraph Server 1.7 standalone 
non-auth](docs/images/compat-1.7-standalone.png)
 
-![GraphSpace permission presets](docs/images/hubble-graphspace-permissions.png)
+![HugeGraph 1.8/master PD mode without a manual server 
URL](docs/images/compat-1.8-pd.png)
 
-![PD connection discovered server 
address](docs/images/hubble-pd-connection.png)
+![HugeGraph 1.8/master authenticated account 
presets](docs/images/compat-1.8-auth-account.png)
 
-![Legacy compatibility capability 
notice](docs/images/hubble-legacy-capability.png)
+![HugeGraph 1.8/master GraphSpace member 
permissions](docs/images/compat-1.8-graphspace-access.png)
+
+![HugeGraph 1.8/master authenticated PD 
dashboard](docs/images/compat-1.8-auth-dashboard.png)
+
+The 1.5 and 1.7 captures show standalone non-auth core workflows. The 
1.8/master
+captures cover both PD/non-auth connection discovery and authenticated account 
/ GraphSpace permission surfaces.
 
 ## Local development feedback loop
 
diff --git a/hugegraph-hubble/docs/images/compat-1.5-standalone.png 
b/hugegraph-hubble/docs/images/compat-1.5-standalone.png
new file mode 100644
index 000000000..34223b135
Binary files /dev/null and 
b/hugegraph-hubble/docs/images/compat-1.5-standalone.png differ
diff --git a/hugegraph-hubble/docs/images/compat-1.7-standalone.png 
b/hugegraph-hubble/docs/images/compat-1.7-standalone.png
new file mode 100644
index 000000000..c656488e2
Binary files /dev/null and 
b/hugegraph-hubble/docs/images/compat-1.7-standalone.png differ
diff --git a/hugegraph-hubble/docs/images/compat-1.8-auth-account.png 
b/hugegraph-hubble/docs/images/compat-1.8-auth-account.png
new file mode 100644
index 000000000..e86c1c2d7
Binary files /dev/null and 
b/hugegraph-hubble/docs/images/compat-1.8-auth-account.png differ
diff --git a/hugegraph-hubble/docs/images/compat-1.8-auth-dashboard.png 
b/hugegraph-hubble/docs/images/compat-1.8-auth-dashboard.png
new file mode 100644
index 000000000..9bbffb315
Binary files /dev/null and 
b/hugegraph-hubble/docs/images/compat-1.8-auth-dashboard.png differ
diff --git a/hugegraph-hubble/docs/images/compat-1.8-graphspace-access.png 
b/hugegraph-hubble/docs/images/compat-1.8-graphspace-access.png
new file mode 100644
index 000000000..537694b0d
Binary files /dev/null and 
b/hugegraph-hubble/docs/images/compat-1.8-graphspace-access.png differ
diff --git a/hugegraph-hubble/docs/images/compat-1.8-pd.png 
b/hugegraph-hubble/docs/images/compat-1.8-pd.png
new file mode 100644
index 000000000..a72899a35
Binary files /dev/null and b/hugegraph-hubble/docs/images/compat-1.8-pd.png 
differ
diff --git a/hugegraph-hubble/docs/images/hubble-auth-connection-root.png 
b/hugegraph-hubble/docs/images/hubble-auth-connection-root.png
deleted file mode 100644
index 966624185..000000000
Binary files a/hugegraph-hubble/docs/images/hubble-auth-connection-root.png and 
/dev/null differ
diff --git a/hugegraph-hubble/docs/images/hubble-auth-connection.png 
b/hugegraph-hubble/docs/images/hubble-auth-connection.png
deleted file mode 100644
index f205e3621..000000000
Binary files a/hugegraph-hubble/docs/images/hubble-auth-connection.png and 
/dev/null differ
diff --git a/hugegraph-hubble/docs/images/hubble-graphspace-permissions.png 
b/hugegraph-hubble/docs/images/hubble-graphspace-permissions.png
deleted file mode 100644
index 06f4a0c3f..000000000
Binary files a/hugegraph-hubble/docs/images/hubble-graphspace-permissions.png 
and /dev/null differ
diff --git a/hugegraph-hubble/docs/images/hubble-legacy-capability.png 
b/hugegraph-hubble/docs/images/hubble-legacy-capability.png
deleted file mode 100644
index 982b789dc..000000000
Binary files a/hugegraph-hubble/docs/images/hubble-legacy-capability.png and 
/dev/null differ
diff --git a/hugegraph-hubble/docs/images/hubble-non-auth.png 
b/hugegraph-hubble/docs/images/hubble-non-auth.png
deleted file mode 100644
index f205e3621..000000000
Binary files a/hugegraph-hubble/docs/images/hubble-non-auth.png and /dev/null 
differ
diff --git a/hugegraph-hubble/docs/images/hubble-pd-connection.png 
b/hugegraph-hubble/docs/images/hubble-pd-connection.png
deleted file mode 100644
index 8decb8bf6..000000000
Binary files a/hugegraph-hubble/docs/images/hubble-pd-connection.png and 
/dev/null differ
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java
index db091fe14..faf93d6ba 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java
@@ -146,10 +146,9 @@ public abstract class BaseController {
             client.assignGraph(graphSpace, graph);
             return client;
         }
-        HugeClient client = this.authMode.anonymous() ?
-                            this.hugeClientPoolService.createUnauthClient() :
-                            this.hugeClientPoolService.createAuthClient(
-                                graphSpace, graph, this.getToken());
+        HugeClient client = this.authMode != null && this.authMode.anonymous() 
?
+                            
this.hugeClientPoolService.createUnauthClient(graphSpace, graph) :
+                            
this.hugeClientPoolService.createAuthClient(graphSpace, graph, this.getToken());
         if (graphSpace != null || graph != null) {
             client.assignGraph(graphSpace, graph);
         }
@@ -177,6 +176,16 @@ public abstract class BaseController {
         return client;
     }
 
+    protected HugeClient requireGraphSpaceAuthorizationAdmin(
+            String graphSpace) {
+        HugeClient client = this.authClient(null, null);
+        if (!this.userService.isSuperAdmin(client)) {
+            throw new ForbiddenException("Permission denied: manage 
authorization objects");
+        }
+        client.assignGraph(graphSpace, null);
+        return client;
+    }
+
     protected HugeClient requireGraphSpaceAdministrator() {
         HugeClient client = this.authClient(null, null);
         if (!this.userService.isSuperAdmin(client)) {
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AccessController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AccessController.java
index 0f97026ed..f2bbc78f5 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AccessController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AccessController.java
@@ -47,28 +47,28 @@ public class AccessController extends AuthController {
             @PathVariable("graphspace") String graphSpace,
             @RequestParam(value = "role_id", required = false) String roleId,
             @RequestParam(value = "target_id", required = false) String 
targetId) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.accessService.list(client, graphSpace, roleId, targetId);
     }
 
     @GetMapping("{id}")
     public AccessEntity get(@PathVariable("graphspace") String graphSpace,
                             @PathVariable("id") String accessId) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.accessService.get(client, graphSpace, accessId);
     }
 
     @PostMapping
     public AccessEntity add(@PathVariable("graphspace") String graphSpace,
                             @RequestBody AccessEntity accessEntity) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.accessService.addOrUpdate(client, graphSpace, 
accessEntity);
     }
 
     @PutMapping
     public AccessEntity update(@PathVariable("graphspace") String graphSpace,
                                @RequestBody AccessEntity accessEntity) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.accessService.addOrUpdate(client, graphSpace, 
accessEntity);
     }
 
@@ -76,7 +76,7 @@ public class AccessController extends AuthController {
     public void delete(@PathVariable("graphspace") String graphSpace,
                        @RequestParam("role_id") String roleId,
                        @RequestParam("target_id") String targetId) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         this.accessService.delete(client, graphSpace, roleId, targetId);
     }
 }
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/BelongController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/BelongController.java
index 757a4152b..1f7ce5660 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/BelongController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/BelongController.java
@@ -48,7 +48,7 @@ public class BelongController extends AuthController {
             @PathVariable("graphspace") String graphSpace,
             @RequestParam(value = "role_id", required = false) String roleId,
             @RequestParam(value = "user_id", required = false) String userId) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.belongService.list(client, graphSpace, roleId, userId);
     }
 
@@ -61,7 +61,7 @@ public class BelongController extends AuthController {
                           defaultValue = "1") int pageNo,
             @RequestParam(name = "page_size", required = false,
                           defaultValue = "10") int pageSize) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.belongService.listPage(client, graphSpace, roleId, userId,
                                            pageNo, pageSize);
     }
@@ -69,14 +69,14 @@ public class BelongController extends AuthController {
     @GetMapping("{id}")
     public BelongEntity get(@PathVariable("graphspace") String graphSpace,
                             @PathVariable("id") String belongId) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.belongService.get(client, graphSpace, belongId);
     }
 
     @PostMapping
     public void create(@PathVariable("graphspace") String graphSpace,
                        @RequestBody BelongEntity belongEntity) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         this.belongService.add(client, graphSpace, belongEntity.getRoleId(),
                                belongEntity.getUserId());
     }
@@ -84,7 +84,7 @@ public class BelongController extends AuthController {
     @PostMapping("ids")
     public void createMany(@PathVariable("graphspace") String graphSpace,
                            @RequestBody BelongService.BelongsReq belongsReq) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         for (String userId : belongsReq.getUserIds()) {
             this.belongService.add(client, graphSpace,
                                    belongsReq.getRoleId(), userId);
@@ -94,7 +94,7 @@ public class BelongController extends AuthController {
     @DeleteMapping("{id}")
     public void delete(@PathVariable("graphspace") String graphSpace,
                        @PathVariable("id") String belongId) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         this.belongService.deleteById(client, graphSpace, belongId);
     }
 
@@ -102,7 +102,7 @@ public class BelongController extends AuthController {
     public void delete(@PathVariable("graphspace") String graphSpace,
                        @RequestParam("role_id") String roleId,
                        @RequestParam("user_id") String userId) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         if (StringUtils.isNotEmpty(roleId) && StringUtils.isNotEmpty(userId)) {
             this.belongService.delete(client, graphSpace, roleId, userId);
         }
@@ -111,7 +111,7 @@ public class BelongController extends AuthController {
     @PostMapping("delids")
     public void deleteMany(@PathVariable("graphspace") String graphSpace,
                            @RequestBody DelIdsReq delIdsReq) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         this.belongService.deleteMany(client, graphSpace,
                                       delIdsReq.ids.toArray(new String[0]));
     }
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java
index 8ec43ae5a..a4a9832f0 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java
@@ -18,6 +18,8 @@
 
 package org.apache.hugegraph.controller.auth;
 
+import java.util.Map;
+
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.apache.hugegraph.common.Constant;
 import org.apache.hugegraph.driver.HugeClient;
@@ -86,6 +88,15 @@ public class GraphSpaceUserController extends AuthController 
{
         return client.auth().addSpaceAdmin(userId, graphSpace);
     }
 
+    @PutMapping("{id}/preset")
+    public void setPermissionPreset(
+            @PathVariable("graphspace") String graphSpace,
+            @PathVariable("id") String userId,
+            @RequestBody Map<String, String> body) {
+        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        this.userService.applySpacePreset(client, graphSpace, userId, 
body.get("permission_preset"));
+    }
+
     @DeleteMapping("spaceadmin/{id}")
     public void removeGraphSpaceAdmin(
             @PathVariable("graphspace") String graphSpace,
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/LoginController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/LoginController.java
index 3400d066b..c5a51e226 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/LoginController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/LoginController.java
@@ -75,7 +75,7 @@ public class LoginController extends BaseController {
 
     @PostMapping("/login")
     public Object login(@RequestBody Login login) {
-        if (this.authMode.anonymous()) {
+        if (this.authMode != null && this.authMode.anonymous()) {
             throw new ExternalException(HttpStatus.FORBIDDEN.value(),
                                         "Authentication is disabled");
         }
@@ -229,6 +229,9 @@ public class LoginController extends BaseController {
 
     @GetMapping("/status")
     public Object status() {
+        if (this.authMode != null && this.authMode.anonymous()) {
+            return ImmutableMap.of("level", "ANONYMOUS");
+        }
 
         HugeClient client = authClient(null, null);
 
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/RoleController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/RoleController.java
index 06e0e268f..f7ed393f6 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/RoleController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/RoleController.java
@@ -46,7 +46,7 @@ public class RoleController extends AuthController {
 
     @GetMapping("list")
     public List<Role> listName(@PathVariable("graphspace") String graphSpace) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.roleService.list(client, graphSpace,
                                      this.userService.isSuperAdmin(client));
     }
@@ -60,7 +60,7 @@ public class RoleController extends AuthController {
                           defaultValue = "1") int pageNo,
             @RequestParam(name = "page_size", required = false,
                           defaultValue = "10") int pageSize) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.roleService.queryPage(
                 client, graphSpace, query, pageNo, pageSize,
                 this.userService.isSuperAdmin(client));
@@ -69,7 +69,7 @@ public class RoleController extends AuthController {
     @GetMapping("{id}")
     public Role get(@PathVariable("graphspace") String graphSpace,
                     @PathVariable("id") String roleId) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.roleService.get(client, graphSpace, roleId,
                                     this.userService.isSuperAdmin(client));
     }
@@ -77,7 +77,7 @@ public class RoleController extends AuthController {
     @PostMapping
     public Role add(@PathVariable("graphspace") String graphSpace,
                     @RequestBody Role role) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         role.graphSpace(graphSpace);
         return this.roleService.insert(client, graphSpace, role);
     }
@@ -86,7 +86,7 @@ public class RoleController extends AuthController {
     public Role update(@PathVariable("graphspace") String graphSpace,
                        @PathVariable("id") String id,
                        @RequestBody Map<String, Object> body) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         boolean includeLegacy = this.userService.isSuperAdmin(client);
         Role current = this.roleService.get(client, graphSpace, id,
                                             includeLegacy);
@@ -108,7 +108,7 @@ public class RoleController extends AuthController {
     @DeleteMapping("{id}")
     public void delete(@PathVariable("graphspace") String graphSpace,
                        @PathVariable("id") String id) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         this.roleService.delete(client, graphSpace, id,
                                 this.userService.isSuperAdmin(client));
     }
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/TargetController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/TargetController.java
index a11368d11..a3d25e3c5 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/TargetController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/TargetController.java
@@ -45,7 +45,7 @@ public class TargetController extends AuthController {
 
     @GetMapping("list")
     public List<Target> list(@PathVariable("graphspace") String graphSpace) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.targetService.list(client, graphSpace);
     }
 
@@ -58,7 +58,7 @@ public class TargetController extends AuthController {
                           defaultValue = "1") int pageNo,
             @RequestParam(name = "page_size", required = false,
                           defaultValue = "10") int pageSize) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.targetService.queryPage(client, graphSpace, query, pageNo,
                                             pageSize);
     }
@@ -66,14 +66,14 @@ public class TargetController extends AuthController {
     @GetMapping("{id}")
     public Target get(@PathVariable("graphspace") String graphSpace,
                       @PathVariable("id") String targetId) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.targetService.get(client, graphSpace, targetId);
     }
 
     @PostMapping
     public Target add(@PathVariable("graphspace") String graphSpace,
                       @RequestBody Target target) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         return this.targetService.add(client, graphSpace, target);
     }
 
@@ -81,7 +81,7 @@ public class TargetController extends AuthController {
     public Target update(@PathVariable("graphspace") String graphSpace,
                          @PathVariable("id") String targetId,
                          @RequestBody Target target) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         Target current = this.targetService.get(client, graphSpace, targetId);
         current.resources(target.resources());
         current.description(target.description());
@@ -91,7 +91,7 @@ public class TargetController extends AuthController {
     @DeleteMapping("{id}")
     public void delete(@PathVariable("graphspace") String graphSpace,
                        @PathVariable("id") String targetId) {
-        HugeClient client = this.requireGraphSpaceManager(graphSpace);
+        HugeClient client = 
this.requireGraphSpaceAuthorizationAdmin(graphSpace);
         this.targetService.delete(client, graphSpace, targetId);
     }
 }
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/GraphSpaceController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/GraphSpaceController.java
index b8c3bc712..fb1d8900a 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/GraphSpaceController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/space/GraphSpaceController.java
@@ -49,6 +49,7 @@ import org.springframework.web.bind.annotation.RestController;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 @RestController
 @RequestMapping(Constant.API_VERSION + "graphspaces")
@@ -101,6 +102,11 @@ public class GraphSpaceController extends BaseController {
             return ImmutableMap.of("records", Collections.emptyList(),
                                   "total", 0);
         }
+        if (this.authMode != null && this.authMode.anonymous()) {
+            HugeClient client = this.authClient(null, null);
+            List<Map<String, Object>> graphSpaces = 
this.graphSpaceService.queryAnonymousGs(client, query, createTime);
+            return all ? graphSpaces : PageUtil.page(graphSpaces, pageNo, 
pageSize);
+        }
         if (all) {
             HugeClient client = this.authClient(null, null);
             return this.userService.isSuperAdmin(client) ?
@@ -137,6 +143,12 @@ public class GraphSpaceController extends BaseController {
             return this.graphSpaceService.toView(stub);
         }
         HugeClient client = this.authClient(null, null);
+        if (this.authMode != null && this.authMode.anonymous()) {
+            GraphSpaceEntity entity = 
GraphSpaceEntity.fromGraphSpace(this.graphSpaceService.getWithoutAdmins(client,
+                                                            graphspace));
+            entity.setStatistic(this.graphSpaceService.evCount(client, 
graphspace));
+            return this.graphSpaceService.toView(entity);
+        }
         // Get GraphSpace Info
         return graphSpaceService.toView(
                 graphSpaceService.getWithAdmins(client, graphspace));
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/handler/CustomInterceptor.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/handler/CustomInterceptor.java
index 2bdc11f5a..9db4a23a4 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/handler/CustomInterceptor.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/handler/CustomInterceptor.java
@@ -19,6 +19,8 @@
 package org.apache.hugegraph.handler;
 
 import java.util.regex.Pattern;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -61,6 +63,9 @@ public class CustomInterceptor extends 
HandlerInterceptorAdapter {
         validatePage(request, "page_no", false);
         validatePage(request, "page_size", true);
         String url = request.getRequestURI();
+        if (url.endsWith("/config")) {
+            return true;
+        }
         if (!CHECK_API_PATTERN.matcher(url).matches()) {
             setHugeClientToRequest(request);
             return true;
@@ -122,24 +127,20 @@ public class CustomInterceptor extends 
HandlerInterceptorAdapter {
             if (this.isLogoutRequest(uri)) {
                 return;
             }
-            if (this.authMode.anonymous()) {
-                client = unauthClient();
+            if (this.authMode != null && this.authMode.anonymous() &&
+                uri.endsWith("/auth/status")) {
+                return;
+            }
+            String[] scope = this.requestScope(uri);
+            String graphSpace = scope[0];
+            String graph = scope[1];
+            if (this.authMode != null && this.authMode.anonymous()) {
+                client = unauthClient(graphSpace, graph);
             } else if (!this.hasAuthSession(request)) {
                 return;
             } else {
                 String token =
                         (String) 
request.getSession().getAttribute(Constant.TOKEN_KEY);
-                String [] res = uri.split("/");
-                String graphSpace = null;
-                String graph = null;
-                for (int i = 0; i < res.length; i++) {
-                    if ("graphspaces".equals(res[i]) && i < res.length - 1) {
-                        graphSpace = res[i + 1];
-                    }
-                    if ("graphs".equals(res[i]) && i < res.length - 1) {
-                        graph = res[i + 1];
-                    }
-                }
                 client = this.authClient(graphSpace, graph, token);
             }
         }
@@ -180,4 +181,33 @@ public class CustomInterceptor extends 
HandlerInterceptorAdapter {
     protected HugeClient unauthClient() {
         return this.hugeClientPoolService.createUnauthClient();
     }
+
+    protected HugeClient unauthClient(String graphSpace, String graph) {
+        return this.hugeClientPoolService.createUnauthClient(graphSpace, 
graph);
+    }
+
+    private String[] requestScope(String uri) {
+        String graphSpace = null;
+        String graph = null;
+        String[] parts = uri.split("/");
+        for (int i = 0; i < parts.length; i++) {
+            if ("graphspaces".equals(parts[i]) && i < parts.length - 1) {
+                graphSpace = parts[i + 1];
+                graphSpace = decodeSegment(graphSpace);
+            }
+            if ("graphs".equals(parts[i]) && i < parts.length - 1) {
+                graph = parts[i + 1];
+                graph = decodeSegment(graph);
+            }
+        }
+        return new String[]{graphSpace, graph};
+    }
+
+    private static String decodeSegment(String segment) {
+        try {
+            return URLDecoder.decode(segment, StandardCharsets.UTF_8.name());
+        } catch (java.io.UnsupportedEncodingException ignored) {
+            return segment;
+        }
+    }
 }
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/handler/LoginInterceptor.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/handler/LoginInterceptor.java
index 779c65c62..5d49aa9f8 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/handler/LoginInterceptor.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/handler/LoginInterceptor.java
@@ -20,7 +20,9 @@ package org.apache.hugegraph.handler;
 
 import org.apache.hugegraph.common.Constant;
 import org.apache.hugegraph.exception.UnauthorizedException;
+import org.apache.hugegraph.exception.ExternalException;
 import org.apache.hugegraph.service.auth.AuthModeService;
+import org.springframework.http.HttpStatus;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.StringUtils;
 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
@@ -41,6 +43,10 @@ public class LoginInterceptor extends 
HandlerInterceptorAdapter {
             return true;
         }
         if (this.authMode != null && this.authMode.anonymous()) {
+            if (isAnonymousAuthManagement(request.getRequestURI())) {
+                throw new ExternalException(HttpStatus.FORBIDDEN.value(),
+                                            "Authentication is disabled");
+            }
             return true;
         }
 
@@ -57,4 +63,14 @@ public class LoginInterceptor extends 
HandlerInterceptorAdapter {
         Object value = request.getSession().getAttribute(key);
         return value instanceof String && StringUtils.hasText((String) value);
     }
+
+    private static boolean isAnonymousAuthManagement(String uri) {
+        if (uri.contains("/graphspaces/") && uri.contains("/auth/")) {
+            return true;
+        }
+        if (!uri.contains("/auth/")) {
+            return false;
+        }
+        return !uri.endsWith("/auth/context") && !uri.endsWith("/auth/status") 
&& !uri.endsWith("/auth/logout");
+    }
 }
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/options/HubbleOptions.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/options/HubbleOptions.java
index 7cb589d1a..3ae56c344 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/options/HubbleOptions.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/options/HubbleOptions.java
@@ -280,10 +280,8 @@ public class HubbleOptions extends OptionHolder {
             );
 
     public static final ConfigOption<Boolean> AUTH_ENABLED =
-            new ConfigOption<>(
-                    "auth.enabled",
-                    "Whether Hubble requires a user session. Set false when " +
-                    "the connected HugeGraph Server runs in anonymous mode.",
+            new ConfigOption<>("auth.enabled",
+                    "Whether Hubble requires a user session. Set false when " 
+ "the connected HugeGraph Server runs in anonymous mode.",
                     null,
                     true
             );
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/HugeClientPoolService.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/HugeClientPoolService.java
index ccd3b7033..167f7f5c0 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/HugeClientPoolService.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/HugeClientPoolService.java
@@ -91,6 +91,10 @@ public final class HugeClientPoolService {
         return getOrCreate(null, null, null, null);
     }
 
+    public HugeClient createUnauthClient(String graphSpace, String graph) {
+        return getOrCreate(null, graphSpace, graph, null);
+    }
+
     public HugeClient createTempTokenClient(String token) {
         return getOrCreate(null, null, null, token);
     }
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthContextService.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthContextService.java
index b141b55de..11cd95d8e 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthContextService.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthContextService.java
@@ -87,7 +87,8 @@ public class AuthContextService {
     }
 
     public Map<String, Object> context(HugeClient client, String username) {
-        if (!this.config.get(HubbleOptions.AUTH_ENABLED)) {
+        if (Boolean.FALSE.equals(
+                this.config.get(HubbleOptions.AUTH_ENABLED))) {
             return anonymousContext(this.config.get(HubbleOptions.PD_ENABLED));
         }
         boolean pdEnabled = this.config.get(HubbleOptions.PD_ENABLED);
@@ -136,10 +137,8 @@ public class AuthContextService {
         context.put("username", null);
         context.put("role", "ANONYMOUS");
         Map<String, Set<String>> actions = new LinkedHashMap<>();
-        actions.put("graphspaces", pdEnabled ?
-                    Collections.singleton("read") : Collections.emptySet());
-        context.put("capabilities", pdEnabled ?
-                    set(GRAPH_RESOURCES_ACCESS, GRAPHSPACES_READ) :
+        actions.put("graphspaces", pdEnabled ? Collections.singleton("read") : 
Collections.emptySet());
+        context.put("capabilities", pdEnabled ? set(GRAPH_RESOURCES_ACCESS, 
GRAPHSPACES_READ) :
                     Collections.singleton(GRAPH_RESOURCES_ACCESS));
         context.put("actions", actions);
         Map<String, Object> scopes = new LinkedHashMap<>();
@@ -166,6 +165,8 @@ public class AuthContextService {
         if (pdEnabled && (SUPERADMIN.equals(role) ||
                           SPACEADMIN.equals(role))) {
             capabilities.add(GRAPHSPACE_MEMBERS_MANAGE);
+        }
+        if (pdEnabled && SUPERADMIN.equals(role)) {
             capabilities.add(GRAPHSPACE_ROLES_MANAGE);
             capabilities.add(GRAPHSPACE_AUTHORIZATIONS_MANAGE);
         }
@@ -185,8 +186,8 @@ public class AuthContextService {
         actions.put("graphspaces", pdEnabled ?
                    (superAdmin ? CRUD_ACTIONS : set("read")) : emptySet());
         actions.put("members", spaceManager ? MEMBER_ACTIONS : emptySet());
-        actions.put("roles", spaceManager ? CRUD_ACTIONS : emptySet());
-        actions.put("authorizations", spaceManager ?
+        actions.put("roles", superAdmin ? CRUD_ACTIONS : emptySet());
+        actions.put("authorizations", superAdmin ?
                    AUTHORIZATION_ACTIONS : emptySet());
         actions.put("operations", superAdmin ?
                    OPERATIONS_ACTIONS : emptySet());
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthModeService.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthModeService.java
index 9500b709d..f4f8c8230 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthModeService.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/AuthModeService.java
@@ -38,7 +38,7 @@ public final class AuthModeService {
     }
 
     public boolean enabled() {
-        return this.config.get(HubbleOptions.AUTH_ENABLED);
+        return 
!Boolean.FALSE.equals(this.config.get(HubbleOptions.AUTH_ENABLED));
     }
 
     public boolean anonymous() {
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/GraphSpaceUserService.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/GraphSpaceUserService.java
index ad8a669fa..225a1b535 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/GraphSpaceUserService.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/GraphSpaceUserService.java
@@ -20,9 +20,7 @@ package org.apache.hugegraph.service.auth;
 
 import java.util.ArrayList;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -35,7 +33,6 @@ import org.apache.hugegraph.driver.HugeClient;
 import org.apache.hugegraph.entity.auth.BelongEntity;
 import org.apache.hugegraph.entity.auth.RoleEntity;
 import org.apache.hugegraph.entity.auth.UserView;
-import org.apache.hugegraph.exception.ExternalException;
 import org.apache.hugegraph.structure.auth.User;
 import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.PageUtil;
@@ -48,11 +45,9 @@ public class GraphSpaceUserService extends AuthService {
 
     @Autowired
     private BelongService belongService;
-    @Autowired
-    private RoleService roleService;
 
     public List<UserView> listUsers(HugeClient client, String graphSpace) {
-        List<UserView> users = new ArrayList<>();
+        Map<String, UserView> users = new java.util.LinkedHashMap<>();
         List<BelongEntity> belongs = this.belongService.list(
                 client, graphSpace, null, null);
 
@@ -69,9 +64,22 @@ public class GraphSpaceUserService extends AuthService {
                 user.addRole(new RoleEntity(belong.getRoleId(),
                                             belong.getRoleName()));
             });
-            users.add(user);
+            users.put(userId, user);
         });
-        return users;
+        client.auth().listSpaceMember(graphSpace).forEach(username -> {
+            User account = client.findUserByName(username);
+            if (account == null) {
+                return;
+            }
+            String userId = account.id().toString();
+            UserView user = users.computeIfAbsent(userId,
+                    id -> new UserView(id, username, new ArrayList<>()));
+            if (client.supportsDefaultRole()) {
+                this.addDefaultRole(client, graphSpace, user, username, 
"observer");
+                this.addDefaultRole(client, graphSpace, user, username, 
"analyst");
+            }
+        });
+        return new ArrayList<>(users.values());
     }
 
     public UserView getUser(HugeClient client, String graphSpace,
@@ -86,6 +94,19 @@ public class GraphSpaceUserService extends AuthService {
             user.addRole(new RoleEntity(belong.getRoleId(),
                                         belong.getRoleName()));
         });
+        if (user.getId() == null) {
+            User account = client.auth().getUser(userId);
+            if (account != null) {
+                user.setId(account.id().toString());
+                user.setName(account.name());
+                if (client.supportsDefaultRole()) {
+                    this.addDefaultRole(client, graphSpace, user,
+                                        account.name(), "observer");
+                    this.addDefaultRole(client, graphSpace, user,
+                                        account.name(), "analyst");
+                }
+            }
+        }
         return user;
     }
 
@@ -147,82 +168,151 @@ public class GraphSpaceUserService extends AuthService {
         if (preset == null || "SUPER_ADMIN".equals(preset)) {
             return;
         }
-        User account = client.auth().getUserByName(username);
+        E.checkArgument(client.supportsDefaultRole(), "Permission presets 
require HugeGraph Server 1.8+");
+        User account = client.findUserByName(username);
         if (account == null) {
             return;
         }
-        Map<String, String> desired = new HashMap<>();
-        List<Map<String, String>> requested = permissions == null ?
-                                             new ArrayList<>() : permissions;
+        Map<String, String> desired = new java.util.LinkedHashMap<>();
+        List<Map<String, String>> requested = permissions == null ? new 
ArrayList<>() : permissions;
         for (Map<String, String> permission : requested) {
             String graphSpace = permission.get("graphspace");
             String permissionPreset = permission.get("permission_preset");
             if (graphSpace != null) {
-                desired.put(graphSpace,
-                           "GS_READ_ONLY".equals(permissionPreset) ?
-                           "observer" : 
"GS_READ_WRITE".equals(permissionPreset) ?
-                             "analyst" : null);
+                desired.put(graphSpace, permissionPreset);
             }
         }
         for (String graphSpace : client.graphSpace().listGraphSpace()) {
-            UserView current = this.getUser(client, graphSpace,
-                                             account.id().toString());
-            if (!desired.containsKey(graphSpace)) {
-                if (!current.getRoles().isEmpty() &&
-                    current.getRoles().stream().allMatch(
-                        GraphSpaceUserService::isPresetRole)) {
-                    this.unauthUser(client, graphSpace,
-                                    account.id().toString());
-                }
-                continue;
+            String desiredPreset = desired.get(graphSpace);
+            if (desiredPreset == null) {
+                this.unauthUser(client, graphSpace,
+                                account.id().toString());
+            } else {
+                this.applySpacePreset(client, graphSpace,
+                                      account.id().toString(),
+                                      desiredPreset);
             }
-            String presetRole = desired.get(graphSpace);
-            if (presetRole == null) {
-                List<RoleEntity> customRoles = current.getRoles().stream()
-                    .filter(existing -> !isPresetRole(existing))
-                    .collect(Collectors.toList());
-                if (customRoles.isEmpty() && !current.getRoles().isEmpty()) {
-                    this.unauthUser(client, graphSpace,
-                                    account.id().toString());
-                } else if (!customRoles.isEmpty()) {
-                    UserView view = new UserView(account.id().toString(),
-                                                 username, customRoles);
-                    this.createOrUpdate(client, graphSpace, view);
-                }
-                continue;
+        }
+    }
+
+    public void validatePermissionPresets(
+            HugeClient client, List<Map<String, String>> permissions,
+            String preset) {
+        if (preset == null || "SUPER_ADMIN".equals(preset)) {
+            return;
+        }
+        E.checkArgument(client.supportsDefaultRole(), "Permission presets 
require HugeGraph Server 1.8+");
+        Set<String> graphSpaces =
+                new java.util.HashSet<>(client.graphSpace().listGraphSpace());
+        for (Map<String, String> permission :
+                permissions == null ? new ArrayList<Map<String, String>>() : 
permissions) {
+            String graphSpace = permission.get("graphspace");
+            String permissionPreset = permission.get("permission_preset");
+            E.checkArgument(graphSpace != null && 
graphSpaces.contains(graphSpace),
+                            "The graphspace does not exist: %s", graphSpace);
+            E.checkArgument("GS_READ_ONLY".equals(permissionPreset) ||
+                            "GS_READ_WRITE".equals(permissionPreset) || 
"GS_ADMIN".equals(permissionPreset),
+                            "Unsupported permission preset: %s",
+                            permissionPreset);
+        }
+    }
+
+    public void applySpacePreset(HugeClient client, String graphSpace,
+                                 String userId, String preset) {
+        E.checkArgument("GS_READ_ONLY".equals(preset) || 
"GS_READ_WRITE".equals(preset) || "GS_ADMIN".equals(preset),
+                        "Unsupported permission preset: %s", preset);
+        E.checkArgument(client.supportsDefaultRole(),
+                        "Permission presets require HugeGraph Server 1.8+");
+        User account = client.auth().getUser(userId);
+        E.checkNotNull(account, "User");
+        this.clearCustomRoles(client, graphSpace, userId);
+        this.clearDefaultRoles(client, graphSpace, account.name());
+        if (!client.auth().listSpaceMember(graphSpace)
+                   .contains(account.name())) {
+            client.auth().addSpaceMember(account.name(), graphSpace);
+        }
+        if ("GS_ADMIN".equals(preset)) {
+            if (!client.auth().listSpaceAdmin(graphSpace)
+                       .contains(account.name())) {
+                client.auth().addSpaceAdmin(account.name(), graphSpace);
             }
-            String role = this.resolvePresetRole(client, graphSpace,
-                                                 presetRole);
-            UserView view = new UserView(account.id().toString(), username,
-                                         new ArrayList<>());
-            view.addRole(new RoleEntity(role, role));
-            current.getRoles().stream()
-                   .filter(existing -> !isPresetRole(existing))
-                   .forEach(view::addRole);
-            this.createOrUpdate(client, graphSpace, view);
+            this.setDefaultRole(client, graphSpace, account.name(), "analyst");
+            return;
         }
+        if (client.auth().listSpaceAdmin(graphSpace).contains(account.name())) 
{
+            client.auth().delSpaceAdmin(account.name(), graphSpace);
+        }
+        String role = "GS_READ_ONLY".equals(preset) ? "observer" : "analyst";
+        this.setDefaultRole(client, graphSpace, account.name(), role);
+    }
+
+    public void removeSpacePreset(HugeClient client, String graphSpace,
+                                  String userId) {
+        this.unauthUser(client, graphSpace, userId);
     }
 
-    private String resolvePresetRole(HugeClient client, String graphSpace,
-                                     String roleName) {
-        return this.roleService.list(client, graphSpace, true).stream()
-                   .filter(role -> roleName.equalsIgnoreCase(role.name()) ||
-                                   roleName.equalsIgnoreCase(role.nickname()))
-                   .map(role -> role.id().toString())
-                   .findFirst()
-                   .orElseThrow(() -> new ExternalException(
-                           "auth.role.not-exist", roleName));
+    public boolean hasCustomRoles(HugeClient client, String graphSpace,
+                                  String userId) {
+        return !this.belongService.list(client, graphSpace, null, 
userId).isEmpty();
     }
 
-    private static boolean isPresetRole(RoleEntity role) {
-        String name = role.getName() == null ? role.getId() :
-                      role.getName();
-        if (name == null) {
+    public boolean hasGraphSpaceAccess(HugeClient client, String graphSpace,
+                                       String username) {
+        if (!client.supportsDefaultRole()) {
             return false;
         }
-        String normalized = name.toLowerCase(Locale.ROOT);
-        return "observer".equals(normalized) ||
-               "analyst".equals(normalized);
+        if (client.graphSpace().checkDefaultRole(
+                graphSpace, username, "analyst")) {
+            return true;
+        }
+        return this.graphs(client, graphSpace).stream().anyMatch(
+                graph -> client.graphSpace().checkDefaultRole(graphSpace, 
username, "observer", graph));
+    }
+
+    private void clearDefaultRoles(HugeClient client, String graphSpace,
+                                   String username) {
+        if (client.graphSpace().checkDefaultRole(
+                graphSpace, username, "analyst")) {
+            client.graphSpace().deleteDefaultRole(graphSpace, username, 
"analyst");
+        }
+        for (String graph : this.graphs(client, graphSpace)) {
+            if (client.graphSpace().checkDefaultRole(
+                    graphSpace, username, "observer", graph)) {
+                client.graphSpace().deleteDefaultRole(graphSpace, username, 
"observer", graph);
+            }
+        }
+    }
+
+    private void clearCustomRoles(HugeClient client, String graphSpace,
+                                  String userId) {
+        this.belongService.list(client, graphSpace, null, userId)
+            .forEach(belong -> this.belongService.deleteById(client, 
graphSpace, belong.getId()));
+    }
+
+    private void setDefaultRole(HugeClient client, String graphSpace,
+                                String username, String role) {
+        if ("observer".equals(role)) {
+            for (String graph : this.graphs(client, graphSpace)) {
+                client.graphSpace().setDefaultRole(graphSpace, username, role, 
graph);
+            }
+            return;
+        }
+        client.graphSpace().setDefaultRole(graphSpace, username, role);
+    }
+
+    private void addDefaultRole(HugeClient client, String graphSpace,
+                                UserView user, String username, String role) {
+        boolean assigned = "observer".equals(role) ? this.graphs(client, 
graphSpace).stream().anyMatch(
+                               graph -> 
client.graphSpace().checkDefaultRole(graphSpace, username, role, graph)) :
+                           client.graphSpace().checkDefaultRole(graphSpace, 
username, role);
+        if (assigned) {
+            user.addRole(new RoleEntity(role, role));
+        }
+    }
+
+    private List<String> graphs(HugeClient client, String graphSpace) {
+        client.assignGraph(graphSpace, "");
+        return client.graphs().listGraph();
     }
 
     public void unauthUser(HugeClient client, String graphSpace,
@@ -231,11 +321,18 @@ public class GraphSpaceUserService extends AuthService {
         E.checkNotNull(account, "User");
         List<BelongEntity> belongs = this.belongService.list(
                 client, graphSpace, null, userId);
-        E.checkState(!belongs.isEmpty(), "The user: (%s) not exists", userId);
         belongs.forEach(belong -> {
             this.belongService.deleteById(client, graphSpace, belong.getId());
         });
-        client.auth().delSpaceMember(account.name(), graphSpace);
+        if (client.supportsDefaultRole()) {
+            this.clearDefaultRoles(client, graphSpace, account.name());
+        }
+        if (client.auth().listSpaceAdmin(graphSpace).contains(account.name())) 
{
+            client.auth().delSpaceAdmin(account.name(), graphSpace);
+        }
+        if 
(client.auth().listSpaceMember(graphSpace).contains(account.name())) {
+            client.auth().delSpaceMember(account.name(), graphSpace);
+        }
     }
 
     public IPage<User> querySpaceAdmins(HugeClient client, String graphSpace,
@@ -253,7 +350,10 @@ public class GraphSpaceUserService extends AuthService {
         List<String> spaceAdmins = client.auth().listSpaceAdmin(graphSpace);
         ArrayList<User> users = new ArrayList<>();
         for (String spaceAdmin : spaceAdmins) {
-            users.add(client.auth().getUser(spaceAdmin));
+            User user = client.findUserByName(spaceAdmin);
+            if (user != null) {
+                users.add(user);
+            }
         }
         return users;
     }
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/UserService.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/UserService.java
index 6e696705f..4e8153a4d 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/UserService.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/auth/UserService.java
@@ -26,7 +26,6 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Locale;
 import java.util.stream.Collectors;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -42,10 +41,9 @@ import org.apache.hugegraph.config.HugeConfig;
 import org.apache.hugegraph.driver.AuthManager;
 import org.apache.hugegraph.driver.HugeClient;
 import org.apache.hugegraph.entity.auth.UserEntity;
-import org.apache.hugegraph.entity.auth.RoleEntity;
-import org.apache.hugegraph.entity.auth.UserView;
 import org.apache.hugegraph.exception.InternalException;
 import org.apache.hugegraph.structure.auth.User;
+import org.apache.hugegraph.util.E;
 import org.apache.hugegraph.util.HubbleUtil;
 import org.apache.hugegraph.util.PageUtil;
 
@@ -105,7 +103,7 @@ public class UserService extends AuthService {
     }
 
     public UserEntity getUser(HugeClient client, String name) {
-        return convert(client, client.auth().getUserByName(name));
+        return convert(client, client.findUserByName(name));
     }
 
     public Object queryPage(HugeClient hugeClient, String query,
@@ -159,9 +157,13 @@ public class UserService extends AuthService {
             Map<String, List<String>> spaceMap =
                     HubbleUtil.uncheckedCast(listMap.get(0));
             List<String> adminSpaces = spaceMap.get(userId);
+            if (adminSpaces == null) {
+                adminSpaces = new ArrayList<>();
+            }
             List<String> resSpaces = new ArrayList<>();
             for (String space : spaces) {
-                if (hugeClient.graphSpace().checkDefaultRole(space, userId, 
"analyst")) {
+                if (this.graphSpaceUserService.hasGraphSpaceAccess(
+                        hugeClient, space, user.name())) {
                     resSpaces.add(space);
                 }
             }
@@ -180,8 +182,7 @@ public class UserService extends AuthService {
     }
 
     public UserEntity getpersonal(HugeClient hugeClient, String username) {
-        AuthManager auth = hugeClient.auth();
-        User user = auth.getUserByName(username);
+        User user = hugeClient.findUserByName(username);
         if (user == null) {
             throw new InternalException("auth.user.get.%s Not Exits",
                     username);
@@ -197,7 +198,7 @@ public class UserService extends AuthService {
                     adminSpaces.add(space);
                 }
                 if (hugeClient.auth().isSpaceAdmin(space) ||
-                        hugeClient.auth().checkDefaultRole(space, "analyst")) {
+                    hasCurrentUserAccess(hugeClient, space)) {
                     resSpaces.add(space);
                 }
             }
@@ -215,6 +216,10 @@ public class UserService extends AuthService {
     }
 
     public void add(HugeClient client, UserEntity ue) {
+        if (isPdEnabled()) {
+            this.graphSpaceUserService.validatePermissionPresets(client, 
ue.getGraphspacePermissions(),
+                    ue.getPermissionPreset());
+        }
         User user = new User();
         user.name(ue.getName());
         user.password(ue.getPassword());
@@ -233,8 +238,7 @@ public class UserService extends AuthService {
             }
         }
         if (isPdEnabled()) {
-            this.graphSpaceUserService.applyPermissionPresets(
-                    client, ue.getName(), ue.getGraphspacePermissions(),
+            this.graphSpaceUserService.applyPermissionPresets(client, 
ue.getName(), ue.getGraphspacePermissions(),
                     ue.getPermissionPreset());
         }
 
@@ -364,68 +368,110 @@ public class UserService extends AuthService {
     private void populatePermissionPresets(HugeClient client,
                                             UserEntity userEntity) {
         List<Map<String, String>> permissions = new ArrayList<>();
+        if (userEntity.isSuperadmin()) {
+            userEntity.setGraphspacePermissions(permissions);
+            userEntity.setPermissionPreset("SUPER_ADMIN");
+            return;
+        }
+        if (!client.supportsDefaultRole()) {
+            userEntity.setGraphspacePermissions(permissions);
+            userEntity.setPermissionPreset("LEGACY_CUSTOM");
+            return;
+        }
         List<String> graphSpaces = client.graphSpace().listGraphSpace();
+        boolean legacyCustom = false;
         for (String graphSpace : graphSpaces) {
+            legacyCustom |= this.graphSpaceUserService.hasCustomRoles(client, 
graphSpace, userEntity.getId());
             if (userEntity.getAdminSpaces() != null &&
                 userEntity.getAdminSpaces().contains(graphSpace)) {
                 permissions.add(permission(graphSpace, "GS_ADMIN"));
                 continue;
             }
-            UserView view = this.graphSpaceUserService.getUser(
-                    client, graphSpace, userEntity.getId());
-            view.getRoles().forEach(role -> {
-                String name = role.getName() == null ? "" :
-                             role.getName().toLowerCase(Locale.ROOT);
-                if ("observer".equals(name)) {
-                    permissions.add(permission(graphSpace, "GS_READ_ONLY"));
-                } else if ("analyst".equals(name)) {
-                    permissions.add(permission(graphSpace, "GS_READ_WRITE"));
-                }
-            });
+            if (client.graphSpace().checkDefaultRole(
+                    graphSpace, userEntity.getName(), "analyst")) {
+                permissions.add(permission(graphSpace, "GS_READ_WRITE"));
+            } else if (hasObserverRole(client, graphSpace, 
userEntity.getName())) {
+                permissions.add(permission(graphSpace, "GS_READ_ONLY"));
+            }
         }
         userEntity.setGraphspacePermissions(permissions);
+        if (legacyCustom) {
+            userEntity.setPermissionPreset("LEGACY_CUSTOM");
+        }
     }
 
     private void populatePermissionPresets(HugeClient client,
                                             Collection<UserEntity> users) {
-        Map<String, List<Map<String, String>>> permissions = new HashMap<>();
-        for (String graphSpace : client.graphSpace().listGraphSpace()) {
-            for (UserView view : this.graphSpaceUserService.listUsers(
-                    client, graphSpace)) {
-                for (RoleEntity role : view.getRoles()) {
-                    String name = role.getName() == null ? "" :
-                                 role.getName().toLowerCase(Locale.ROOT);
-                    String preset = "observer".equals(name) ?
-                                    "GS_READ_ONLY" : "analyst".equals(name) ?
-                                    "GS_READ_WRITE" : null;
-                    if (preset != null) {
-                        permissions.computeIfAbsent(view.getId(),
-                            key -> new ArrayList<>()).add(
-                            permission(graphSpace, preset));
-                    }
-                }
+        if (!client.supportsDefaultRole()) {
+            for (UserEntity user : users) {
+                user.setGraphspacePermissions(new ArrayList<>());
+                user.setPermissionPreset(user.isSuperadmin() ? "SUPER_ADMIN" : 
"LEGACY_CUSTOM");
             }
+            return;
         }
+        List<String> graphSpaces = client.graphSpace().listGraphSpace();
         for (UserEntity user : users) {
-            List<Map<String, String>> values = permissions.getOrDefault(
-                    user.getId(), new ArrayList<>());
+            List<Map<String, String>> values = new ArrayList<>();
+            if (user.isSuperadmin()) {
+                user.setGraphspacePermissions(values);
+                user.setPermissionPreset("SUPER_ADMIN");
+                continue;
+            }
+            boolean legacyCustom = false;
             if (user.getAdminSpaces() != null) {
                 for (String graphSpace : user.getAdminSpaces()) {
                     values.add(permission(graphSpace, "GS_ADMIN"));
                 }
             }
+            for (String graphSpace : graphSpaces) {
+                legacyCustom |= 
this.graphSpaceUserService.hasCustomRoles(client, graphSpace, user.getId());
+                if (user.getAdminSpaces() != null &&
+                    user.getAdminSpaces().contains(graphSpace)) {
+                    continue;
+                }
+                if (client.graphSpace().checkDefaultRole(
+                        graphSpace, user.getName(), "analyst")) {
+                    values.add(permission(graphSpace, "GS_READ_WRITE"));
+                } else if (hasObserverRole(client, graphSpace, 
user.getName())) {
+                    values.add(permission(graphSpace, "GS_READ_ONLY"));
+                }
+            }
             user.setGraphspacePermissions(values);
+            if (legacyCustom) {
+                user.setPermissionPreset("LEGACY_CUSTOM");
+            }
         }
     }
 
     private static Map<String, String> permission(String graphSpace,
-                                                   String preset) {
+                                                  String preset) {
         Map<String, String> permission = new HashMap<>();
         permission.put("graphspace", graphSpace);
         permission.put("permission_preset", preset);
         return permission;
     }
 
+    private static boolean hasObserverRole(HugeClient client,
+                                           String graphSpace,
+                                           String username) {
+        client.assignGraph(graphSpace, "");
+        return client.graphs().listGraph().stream().anyMatch(
+                graph -> client.graphSpace().checkDefaultRole(graphSpace, 
username, "observer", graph));
+    }
+
+    private static boolean hasCurrentUserAccess(HugeClient client,
+                                                String graphSpace) {
+        if (!client.supportsDefaultRole()) {
+            return false;
+        }
+        if (client.auth().checkDefaultRole(graphSpace, "analyst")) {
+            return true;
+        }
+        client.assignGraph(graphSpace, "");
+        return client.graphs().listGraph().stream().anyMatch(
+                graph -> client.auth().checkDefaultRole(graphSpace, 
"observer", graph));
+    }
+
     protected List<Object> getSpaceAndSpacenum(HugeClient hugeClient) {
         AuthManager auth = hugeClient.auth();
         List<Object> listMap = new ArrayList<>();
@@ -459,6 +505,10 @@ public class UserService extends AuthService {
     }
 
     public void update(HugeClient hugeClient, UserEntity userEntity) {
+        if (isPdEnabled()) {
+            this.graphSpaceUserService.validatePermissionPresets(hugeClient, 
userEntity.getGraphspacePermissions(),
+                    userEntity.getPermissionPreset());
+        }
         User user = new User();
         user.setId(userEntity.getId());
         user.name(userEntity.getName());
@@ -482,8 +532,7 @@ public class UserService extends AuthService {
 
         hugeClient.auth().updateUser(user);
         if (isPdEnabled()) {
-            this.graphSpaceUserService.applyPermissionPresets(
-                    hugeClient, userEntity.getName(),
+            this.graphSpaceUserService.applyPermissionPresets(hugeClient, 
userEntity.getName(),
                     userEntity.getGraphspacePermissions(),
                     userEntity.getPermissionPreset());
         }
@@ -491,8 +540,7 @@ public class UserService extends AuthService {
 
     public void updatePersonal(HugeClient hugeClient, String username,
                                String nickname, String description) {
-        AuthManager auth = hugeClient.auth();
-        User user = auth.getUserByName(username);
+        User user = hugeClient.findUserByName(username);
         if (isPdEnabled()) {
             user.nickname(nickname);
         } else {
@@ -519,7 +567,7 @@ public class UserService extends AuthService {
         }
         // Must fetch user first to get the ID, otherwise updateUser sends
         // PUT to the collection path (no {id}) and gets HTTP 405.
-        User user = hugeClient.auth().getUserByName(username);
+        User user = hugeClient.findUserByName(username);
         user.password(newpwd);
         hugeClient.auth().updateUser(user);
         return Response.builder()
@@ -531,8 +579,6 @@ public class UserService extends AuthService {
         if (!isPdEnabled()) {
             return new ArrayList<>();
         }
-        AuthManager auth = hugeClient.auth();
-        List<User> users = auth.listUsers();
         List<String> spaces = hugeClient.graphSpace().listGraphSpace();
         List<String> adminspace = new ArrayList<String>();
         for (String space : spaces) {
@@ -553,14 +599,31 @@ public class UserService extends AuthService {
             return;
         }
         List<String> oldadminspaces = listAdminSpace(hugeClient, username);
+        User account = hugeClient.findUserByName(username);
+        E.checkNotNull(account, "User");
+        if (!hugeClient.supportsDefaultRole()) {
+            for (String adminspace : adminspaces) {
+                if (!oldadminspaces.contains(adminspace)) {
+                    hugeClient.auth().addSpaceAdmin(username, adminspace);
+                }
+            }
+            for (String oldadminspace : oldadminspaces) {
+                if (!adminspaces.contains(oldadminspace)) {
+                    hugeClient.auth().delSpaceAdmin(username, oldadminspace);
+                }
+            }
+            return;
+        }
         for (String adminspace : adminspaces) {
             if (!oldadminspaces.contains(adminspace)) {
-                hugeClient.auth().addSpaceAdmin(username, adminspace);
+                this.graphSpaceUserService.applySpacePreset(hugeClient, 
adminspace, account.id().toString(),
+                        "GS_ADMIN");
             }
         }
         for (String oldadminspace : oldadminspaces) {
             if (!adminspaces.contains(oldadminspace)) {
-                hugeClient.auth().delSpaceAdmin(username, oldadminspace);
+                this.graphSpaceUserService.removeSpacePreset(hugeClient, 
oldadminspace,
+                        account.id().toString());
             }
         }
     }
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/space/GraphSpaceService.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/space/GraphSpaceService.java
index b5b85dca5..ebd45a05b 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/space/GraphSpaceService.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/service/space/GraphSpaceService.java
@@ -159,8 +159,46 @@ public class GraphSpaceService {
                                  space.getCreateTime().compareTo(after) > 0)
                 .filter(space -> !space.isAuth() ||
                                  client.auth().isSpaceAdmin(space.getName()) ||
-                                 client.auth().checkDefaultRole(
-                                         space.getName(), "analyst"))
+                                 hasCurrentUserAccess(client, space.getName()))
+                .map(space -> {
+                    GraphSpaceEntity entity =
+                            GraphSpaceEntity.fromGraphSpace(space);
+                    entity.setStatistic(evCount(client, space.getName()));
+                    Map<String, Object> info = toView(entity);
+                    info.put("authed", true);
+                    info.put("default", false);
+                    return info;
+                })
+                .collect(Collectors.toList());
+        Collections.sort(results, (a, b) ->
+                new BuiltInFirst().compare(a.get("name").toString(),
+                                           b.get("name").toString()));
+        return results;
+    }
+
+    private static boolean hasCurrentUserAccess(HugeClient client,
+                                                String graphSpace) {
+        if (!client.supportsDefaultRole()) {
+            return false;
+        }
+        if (client.auth().checkDefaultRole(graphSpace, "analyst")) {
+            return true;
+        }
+        client.assignGraph(graphSpace, "");
+        return client.graphs().listGraph().stream().anyMatch(
+                graph -> client.auth().checkDefaultRole(graphSpace, 
"observer", graph));
+    }
+
+    public List<Map<String, Object>> queryAnonymousGs(HugeClient client,
+                                                      String query,
+                                                      String createTime) {
+        String prefix = query == null ? "" : query;
+        String after = createTime == null ? "" : createTime;
+        List<Map<String, Object>> results = client.graphSpace()
+                .listGraphSpace().stream()
+                .map(client.graphSpace()::getGraphSpace).filter(space -> space 
!= null && (space.getName().contains(prefix) ||
+                                  space.getNickname() != null && 
space.getNickname().contains(prefix)))
+                .filter(space -> space.getCreateTime() == null || 
space.getCreateTime().compareTo(after) > 0)
                 .map(space -> {
                     GraphSpaceEntity entity =
                             GraphSpaceEntity.fromGraphSpace(space);
@@ -200,7 +238,7 @@ public class GraphSpaceService {
      * @param graphSpace
      * @return
      */
-    Map<String, Object> evCount(HugeClient client, String graphSpace) {
+    public Map<String, Object> evCount(HugeClient client, String graphSpace) {
         Long vertexTotal = 0L;
         Long edgeTotal = 0L;
         Map<String, Object> statisticTotal = new HashMap<>();
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthMutationAuthorizationTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthMutationAuthorizationTest.java
index 177b04147..9cf151c9e 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthMutationAuthorizationTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthMutationAuthorizationTest.java
@@ -140,7 +140,7 @@ public class GraphSpaceAuthMutationAuthorizationTest {
     }
 
     @Test
-    public void testCurrentSpaceAdminCanManageScopedAuthorizationResources()
+    public void testCurrentSpaceAdminCanManageMembersOnly()
             throws Exception {
         Mockito.when(this.authorizationService.isAssignSpaceAdmin(
                 this.client, "SPACE"))
@@ -149,8 +149,11 @@ public class GraphSpaceAuthMutationAuthorizationTest {
                 Mockito.eq(this.client), Mockito.any()))
                .thenReturn("SPACEADMIN");
 
-        this.assertScopedReadsAllowed();
-        this.assertScopedCreatesAllowed();
+        this.assertLowLevelReadsForbidden();
+        ReadRoute members = this.memberReadRoute();
+        mvc(members.controller).perform(members.request)
+                               .andExpect(status().isOk());
+        this.assertScopedCreatesForbidden();
     }
 
     @Test
@@ -191,6 +194,12 @@ public class GraphSpaceAuthMutationAuthorizationTest {
         }
     }
 
+    private void assertLowLevelReadsForbidden() throws Exception {
+        for (ReadRoute route : this.lowLevelReadRoutes()) {
+            assertForbidden(mvc(route.controller), route.request);
+        }
+    }
+
     private void assertScopedReadsForbidden() throws Exception {
         for (ReadRoute route : this.scopedReadRoutes()) {
             assertForbidden(mvc(route.controller), route.request);
@@ -198,6 +207,14 @@ public class GraphSpaceAuthMutationAuthorizationTest {
     }
 
     private ReadRoute[] scopedReadRoutes() {
+        ReadRoute[] lowLevel = this.lowLevelReadRoutes();
+        return new ReadRoute[]{
+                lowLevel[0], lowLevel[1], lowLevel[2], lowLevel[3],
+                this.memberReadRoute()
+        };
+    }
+
+    private ReadRoute[] lowLevelReadRoutes() {
         return new ReadRoute[]{
                 new ReadRoute(this.prepare(new TestBelongController(
                         this.client), "belongService",
@@ -214,12 +231,15 @@ public class GraphSpaceAuthMutationAuthorizationTest {
                 new ReadRoute(this.prepare(new TestTargetController(
                         this.client), "targetService",
                         Mockito.mock(TargetService.class)),
-                              get("/api/v1.3/graphspaces/SPACE/auth/targets")),
-                new ReadRoute(this.prepare(new TestGraphSpaceUserController(
+                              get("/api/v1.3/graphspaces/SPACE/auth/targets"))
+        };
+    }
+
+    private ReadRoute memberReadRoute() {
+        return new ReadRoute(this.prepare(new TestGraphSpaceUserController(
                         this.client), "userService",
                         Mockito.mock(GraphSpaceUserService.class)),
-                              get("/api/v1.3/graphspaces/SPACE/auth/users"))
-        };
+                             get("/api/v1.3/graphspaces/SPACE/auth/users"));
     }
 
     private void assertScopedCreatesAllowed() throws Exception {
@@ -242,6 +262,24 @@ public class GraphSpaceAuthMutationAuthorizationTest {
                    .andExpect(status().isOk());
     }
 
+    private void assertScopedCreatesForbidden() throws Exception {
+        BelongController belong = this.prepare(new TestBelongController(
+                this.client), "belongService", 
Mockito.mock(BelongService.class));
+        AccessController access = this.prepare(new TestAccessController(
+                this.client), "accessService", 
Mockito.mock(AccessService.class));
+        TargetController target = this.prepare(new TestTargetController(
+                this.client), "targetService", 
Mockito.mock(TargetService.class));
+
+        assertForbidden(mvc(belong),
+                post("/api/v1.3/graphspaces/SPACE/auth/belongs")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("{\"role_id\":\"r\",\"user_id\":\"u\"}"));
+        assertForbidden(mvc(access),
+                
post("/api/v1.3/graphspaces/SPACE/auth/accesses").contentType(MediaType.APPLICATION_JSON).content("{}"));
+        assertForbidden(mvc(target),
+                
post("/api/v1.3/graphspaces/SPACE/auth/targets").contentType(MediaType.APPLICATION_JSON).content("{}"));
+    }
+
     private <T extends BaseController> T prepare(T controller,
                                                   String serviceField,
                                                   Object service) {
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthOwnershipTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthOwnershipTest.java
index 4516f1286..c8aa3634f 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthOwnershipTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/GraphSpaceAuthOwnershipTest.java
@@ -35,6 +35,8 @@ import 
org.springframework.test.web.servlet.setup.MockMvcBuilders;
 
 import org.apache.hugegraph.controller.BaseController;
 import org.apache.hugegraph.driver.AuthManager;
+import org.apache.hugegraph.driver.GraphSpaceManager;
+import org.apache.hugegraph.driver.GraphsManager;
 import org.apache.hugegraph.driver.HugeClient;
 import org.apache.hugegraph.entity.auth.AccessEntity;
 import org.apache.hugegraph.entity.auth.BelongEntity;
@@ -65,12 +67,24 @@ public class GraphSpaceAuthOwnershipTest {
 
     private HugeClient client;
     private AuthManager auth;
+    private GraphSpaceManager graphSpace;
+    private GraphsManager graphs;
 
     @Before
     public void setup() {
         this.client = Mockito.mock(HugeClient.class);
         this.auth = Mockito.mock(AuthManager.class);
+        this.graphSpace = Mockito.mock(GraphSpaceManager.class);
+        this.graphs = Mockito.mock(GraphsManager.class);
         Mockito.when(this.client.auth()).thenReturn(this.auth);
+        Mockito.when(this.client.graphSpace()).thenReturn(this.graphSpace);
+        Mockito.when(this.client.graphs()).thenReturn(this.graphs);
+        Mockito.when(this.client.supportsDefaultRole()).thenReturn(true);
+        
Mockito.when(this.graphs.listGraph()).thenReturn(Collections.emptyList());
+        Mockito.when(this.auth.listSpaceAdmin(Mockito.anyString()))
+               .thenReturn(Collections.emptyList());
+        Mockito.when(this.auth.listSpaceMember(Mockito.anyString()))
+               .thenReturn(Collections.emptyList());
     }
 
     @Test
@@ -451,14 +465,15 @@ public class GraphSpaceAuthOwnershipTest {
     public void testGraphSpaceUserRemovalDeletesOnlyScopedBelongs() {
         BelongService belongs = Mockito.mock(BelongService.class);
         User account = new User();
+        account.setId("user-id");
         account.name("graph-user");
         Mockito.when(this.auth.getUser("user-id")).thenReturn(account);
+        
Mockito.when(this.auth.listSpaceMember("SPACE_A")).thenReturn(Collections.singletonList("graph-user"));
         BelongEntity scoped = BelongEntity.builder()
                                           .id("belong-a")
                                           .userId("user-id")
                                           .build();
-        Mockito.when(belongs.list(this.client, "SPACE_A", null, "user-id"))
-               .thenReturn(Collections.singletonList(scoped));
+        Mockito.when(belongs.list(this.client, "SPACE_A", null, 
"user-id")).thenReturn(Collections.singletonList(scoped));
         GraphSpaceUserService service = new GraphSpaceUserService();
         ReflectionTestUtils.setField(service, "belongService", belongs);
 
@@ -471,6 +486,27 @@ public class GraphSpaceAuthOwnershipTest {
         Mockito.verify(this.auth).delSpaceMember("graph-user", "SPACE_A");
     }
 
+    @Test
+    public void testReadOnlyPresetAppliesObserverToEveryGraph() {
+        User account = new User();
+        account.setId("user-id");
+        account.name("graph-user");
+        Mockito.when(this.auth.getUser("user-id")).thenReturn(account);
+        Mockito.when(this.graphs.listGraph())
+               .thenReturn(Arrays.asList("graph-1", "graph-2"));
+        BelongService belongs = Mockito.mock(BelongService.class);
+        Mockito.when(belongs.list(this.client, "SPACE_A", null, 
"user-id")).thenReturn(Collections.emptyList());
+        GraphSpaceUserService service = new GraphSpaceUserService();
+        ReflectionTestUtils.setField(service, "belongService", belongs);
+
+        service.applySpacePreset(this.client, "SPACE_A", "user-id", 
"GS_READ_ONLY");
+
+        Mockito.verify(this.auth).addSpaceMember("graph-user", "SPACE_A");
+        Mockito.verify(this.graphSpace).setDefaultRole("SPACE_A", 
"graph-user", "observer", "graph-1");
+        Mockito.verify(this.graphSpace).setDefaultRole("SPACE_A", 
"graph-user", "observer", "graph-2");
+        Mockito.verify(this.graphSpace, 
Mockito.never()).setDefaultRole("SPACE_A", "graph-user", "observer");
+    }
+
     @Test
     public void testGraphSpaceUserRoleUpdatePreflightsAllRoles() {
         List<Group> groups = this.createScopedGroups("SPACE_A", "SPACE_B");
@@ -500,6 +536,7 @@ public class GraphSpaceAuthOwnershipTest {
         Mockito.when(this.auth.getGraphSpaceGroup("local-role"))
                .thenReturn(group);
         User account = new User();
+        account.setId("user-id");
         account.name("graph-user");
         Mockito.when(this.auth.getUser("user-id")).thenReturn(account);
         Mockito.when(this.auth.listSpaceMember("SPACE_A"))
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/AuthContextServiceTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/AuthContextServiceTest.java
index d5a68defb..cc4ba00df 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/AuthContextServiceTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/AuthContextServiceTest.java
@@ -86,8 +86,10 @@ public class AuthContextServiceTest {
         Assert.assertTrue(capabilities(context).contains(
                           "graphspace_members_manage"));
         Assert.assertTrue(actions(context, "members").contains("add"));
-        Assert.assertTrue(actions(context, "roles").contains("update"));
-        Assert.assertTrue(actions(context, 
"authorizations").contains("grant"));
+        Assert.assertTrue(actions(context, "roles").isEmpty());
+        Assert.assertTrue(actions(context, "authorizations").isEmpty());
+        
Assert.assertFalse(capabilities(context).contains("graphspace_roles_manage"));
+        
Assert.assertFalse(capabilities(context).contains("graphspace_authorizations_manage"));
         Assert.assertEquals(Arrays.asList("space-a", "space-b"),
                             scopes(context).get("admin_graphspaces"));
         Assert.assertFalse((Boolean) scopes(context).get("all_graphspaces"));
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/AuthSecurityTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/AuthSecurityTest.java
index 2675fc1a1..64df7fce9 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/AuthSecurityTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/AuthSecurityTest.java
@@ -74,6 +74,7 @@ import org.apache.hugegraph.handler.LoginInterceptor;
 import org.apache.hugegraph.handler.MessageSourceHandler;
 import org.apache.hugegraph.options.HubbleOptions;
 import org.apache.hugegraph.service.auth.AuthContextService;
+import org.apache.hugegraph.service.auth.AuthModeService;
 import org.apache.hugegraph.service.auth.LoginAttemptGuard;
 import org.apache.hugegraph.service.auth.UserService;
 import org.apache.hugegraph.structure.auth.Login;
@@ -177,6 +178,40 @@ public class AuthSecurityTest {
                                                null));
     }
 
+    @Test
+    public void testAnonymousModeBlocksAuthManagementButAllowsContext() {
+        LoginInterceptor interceptor = new LoginInterceptor();
+        HugeConfig config = Mockito.mock(HugeConfig.class);
+        Mockito.when(config.get(HubbleOptions.AUTH_ENABLED))
+               .thenReturn(false);
+        AuthModeService mode = new AuthModeService(config);
+        ReflectionTestUtils.setField(interceptor, "authMode", mode);
+
+        MockHttpServletRequest users = new MockHttpServletRequest("GET", 
"/api/v1.3/auth/users");
+        try {
+            interceptor.preHandle(users, new MockHttpServletResponse(), null);
+            Assert.fail("Expected anonymous auth management to be blocked");
+        } catch (ExternalException forbidden) {
+            Assert.assertEquals(HttpStatus.FORBIDDEN.value(),
+                                forbidden.status());
+        }
+
+        MockHttpServletRequest context = new MockHttpServletRequest("GET", 
"/api/v1.3/auth/context");
+        Assert.assertTrue(interceptor.preHandle(context, new 
MockHttpServletResponse(), null));
+    }
+
+    @Test
+    public void testConfigBootstrapDoesNotCreateServerClient()
+           throws Exception {
+        TestCustomInterceptor interceptor = new TestCustomInterceptor();
+        MockHttpServletRequest request = new MockHttpServletRequest("GET", 
"/api/v1.3/config");
+
+        Assert.assertTrue(interceptor.preHandle(request, new 
MockHttpServletResponse(), null));
+        Assert.assertEquals(0, interceptor.authClients);
+        Assert.assertEquals(0, interceptor.unauthClients);
+        Assert.assertNull(request.getAttribute("hugeClient"));
+    }
+
     @Test
     public void testCustomInterceptorDoesNotCreateClientForMissingSession()
            throws Exception {
@@ -313,6 +348,22 @@ public class AuthSecurityTest {
         Assert.assertEquals("token", interceptor.token);
     }
 
+    @Test
+    public void testAnonymousClientUsesGraphSpaceScope() throws Exception {
+        TestCustomInterceptor interceptor = new TestCustomInterceptor();
+        HugeConfig config = Mockito.mock(HugeConfig.class);
+        Mockito.when(config.get(HubbleOptions.AUTH_ENABLED))
+               .thenReturn(false);
+        AuthModeService mode = new AuthModeService(config);
+        ReflectionTestUtils.setField(interceptor, "authMode", mode);
+        MockHttpServletRequest request = new MockHttpServletRequest("GET", 
"/api/v1.3/graphspaces/SPACE/graphs/graph/schema");
+
+        Assert.assertTrue(interceptor.preHandle(request, new 
MockHttpServletResponse(), null));
+        Assert.assertEquals(1, interceptor.unauthClients);
+        Assert.assertEquals("SPACE", interceptor.graphSpace);
+        Assert.assertEquals("graph", interceptor.graph);
+    }
+
     @Test
     public void testCustomInterceptorDoesNotCreateClientForLogout()
            throws Exception {
@@ -871,6 +922,15 @@ public class AuthSecurityTest {
             this.unauthClients++;
             return null;
         }
+
+        @Override
+        protected org.apache.hugegraph.driver.HugeClient unauthClient(
+                  String graphSpace, String graph) {
+            this.unauthClients++;
+            this.graphSpace = graphSpace;
+            this.graph = graph;
+            return null;
+        }
     }
 
     private static void setField(Object object, String name, Object value)
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UserServiceCompatibilityTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UserServiceCompatibilityTest.java
index ae0319de2..62570641a 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UserServiceCompatibilityTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UserServiceCompatibilityTest.java
@@ -32,6 +32,7 @@ import org.apache.hugegraph.driver.AuthManager;
 import org.apache.hugegraph.driver.HugeClient;
 import org.apache.hugegraph.entity.auth.UserEntity;
 import org.apache.hugegraph.options.HubbleOptions;
+import org.apache.hugegraph.service.auth.GraphSpaceUserService;
 import org.apache.hugegraph.service.auth.UserService;
 import org.apache.hugegraph.structure.auth.User;
 
@@ -52,6 +53,8 @@ public class UserServiceCompatibilityTest {
                .thenReturn(new User());
         this.service = new UserService();
         ReflectionTestUtils.setField(this.service, "config", this.config);
+        ReflectionTestUtils.setField(this.service, "graphSpaceUserService",
+                Mockito.mock(GraphSpaceUserService.class));
     }
 
     @Test
@@ -111,6 +114,7 @@ public class UserServiceCompatibilityTest {
         
Mockito.when(this.config.get(HubbleOptions.PD_ENABLED)).thenReturn(false);
         Mockito.when(this.auth.getUserByName("user"))
                .thenReturn(user("user"));
+        
Mockito.when(this.client.findUserByName("user")).thenReturn(user("user"));
 
         this.service.updatePersonal(this.client, "user", "display-name",
                                     "description");
diff --git a/hugegraph-hubble/hubble-fe/src/App.test.js 
b/hugegraph-hubble/hubble-fe/src/App.test.js
index 11805599a..a3ed5534d 100644
--- a/hugegraph-hubble/hubble-fe/src/App.test.js
+++ b/hugegraph-hubble/hubble-fe/src/App.test.js
@@ -19,15 +19,29 @@
 import {render, screen} from '@testing-library/react';
 import {MemoryRouter} from 'react-router-dom';
 import App from './App';
+import * as api from './api';
+
+jest.mock('./api', () => ({
+    config: {
+        getConfig: jest.fn(),
+    },
+}));
 
 jest.mock('./routes', () => ({element}) => (
     <div data-testid="app-route">{element}</div>
 ));
 
 jest.mock('./layout.ant', () => () => <div>Hubble layout</div>);
+jest.mock('./auth/AuthContext', () => ({
+    AuthContextProvider: ({children}) => children,
+}));
 
-test('wires the Hubble layout into the application router', () => {
+test('wires the Hubble layout into the application router', async () => {
     sessionStorage.clear();
+    api.config.getConfig.mockResolvedValue({
+        status: 200,
+        data: {pd_enabled: false, auth_enabled: false},
+    });
     render(
         <MemoryRouter
             future={{v7_startTransition: true, v7_relativeSplatPath: true}}
@@ -35,6 +49,24 @@ test('wires the Hubble layout into the application router', 
() => {
             <App />
         </MemoryRouter>
     );
-    expect(screen.getByTestId('app-route')).toBeInTheDocument();
+    expect(await screen.findByTestId('app-route')).toBeInTheDocument();
     expect(screen.getByText('Hubble layout')).toBeInTheDocument();
+    
expect(JSON.parse(sessionStorage.getItem('hubble_config_'))).toEqual({pd_enabled:
 false, auth_enabled: false});
+});
+
+test('shows a retry surface when configuration bootstrap fails', async () => {
+    api.config.getConfig.mockRejectedValue(new Error('offline'));
+
+    render(
+        <MemoryRouter
+            future={{v7_startTransition: true, v7_relativeSplatPath: true}}
+        >
+            <App />
+        </MemoryRouter>
+    );
+
+    expect(await screen.findByRole('alert')).toHaveTextContent(
+        'Unable to load Hubble configuration.'
+    );
+    expect(screen.getByRole('button', {name: 'Retry'})).toBeInTheDocument();
 });
diff --git a/hugegraph-hubble/hubble-fe/src/api/auth-contract.test.js 
b/hugegraph-hubble/hubble-fe/src/api/auth-contract.test.js
index 8c435cbc6..aaef6bb25 100644
--- a/hugegraph-hubble/hubble-fe/src/api/auth-contract.test.js
+++ b/hugegraph-hubble/hubble-fe/src/api/auth-contract.test.js
@@ -105,6 +105,14 @@ describe('auth API contract', () => {
     it.each([
         ['getSpaceMembers', ['A/B', {page_no: 1}], 'get',
             '/graphspaces/A%2FB/auth/users'],
+        ['getSpaceAdmins', ['A/B', {page_no: 1}], 'get',
+            '/graphspaces/A%2FB/auth/users/spaceadmin'],
+        ['setSpaceAdmin', ['A/B', 'u/1'], 'post-empty',
+            '/graphspaces/A%2FB/auth/users/spaceadmin/u%2F1'],
+        ['removeSpaceAdmin', ['A/B', 'u/1'], 'delete',
+            '/graphspaces/A%2FB/auth/users/spaceadmin/u%2F1'],
+        ['setSpacePreset', ['A/B', 'u/1', 'GS_READ_ONLY'], 'put-preset',
+            '/graphspaces/A%2FB/auth/users/u%2F1/preset'],
         ['addSpaceMember', ['A/B', {user_id: 'u'}], 'post',
             '/graphspaces/A%2FB/auth/users'],
         ['updateSpaceMember', ['A/B', 'u/1', {roles: []}], 'put',
@@ -151,6 +159,16 @@ describe('auth API contract', () => {
                 route, expectedParams, config
             );
         }
+        else if (verb === 'post-empty') {
+            expect(mockRequest.post).toHaveBeenCalledWith(
+                route, undefined, config
+            );
+        }
+        else if (verb === 'put-preset') {
+            expect(mockRequest.put).toHaveBeenCalledWith(
+                route, {permission_preset: args[2]}, config
+            );
+        }
         else {
             expect(mockRequest[verb]).toHaveBeenCalledWith(
                 route, args.at(-1), config
diff --git a/hugegraph-hubble/hubble-fe/src/api/auth.js 
b/hugegraph-hubble/hubble-fe/src/api/auth.js
index 18a5dee05..541c099f8 100644
--- a/hugegraph-hubble/hubble-fe/src/api/auth.js
+++ b/hugegraph-hubble/hubble-fe/src/api/auth.js
@@ -97,6 +97,27 @@ const getSpaceMembers = (graphspace, params, config = {}) => 
{
     return request.get(scopedAuthPath(graphspace, 'users'), {...config, 
params});
 };
 
+const getSpaceAdmins = (graphspace, params, config = {}) => {
+    return request.get(scopedAuthPath(graphspace, 'users/spaceadmin'),
+        {...config, params});
+};
+
+const setSpaceAdmin = (graphspace, id, config) => {
+    return request.post(scopedAuthPath(graphspace, 'users/spaceadmin', id),
+        undefined, config);
+};
+
+const removeSpaceAdmin = (graphspace, id, config) => {
+    return request.delete(scopedAuthPath(graphspace, 'users/spaceadmin', id),
+        undefined, config);
+};
+
+const setSpacePreset = (graphspace, id, preset, config) => {
+    return request.put(`${scopedAuthPath(graphspace, 'users', id)}/preset`, {
+        permission_preset: preset,
+    }, config);
+};
+
 const addSpaceMember = (graphspace, data, config) => {
     return request.post(scopedAuthPath(graphspace, 'users'), data, config);
 };
@@ -161,6 +182,10 @@ const deleteSpaceAccess = (graphspace, roleId, targetId, 
config) => {
 
 export {
     getSpaceMembers,
+    getSpaceAdmins,
+    setSpaceAdmin,
+    removeSpaceAdmin,
+    setSpacePreset,
     addSpaceMember,
     updateSpaceMember,
     deleteSpaceMember,
diff --git a/hugegraph-hubble/hubble-fe/src/components/Sidebar/index.ant.js 
b/hugegraph-hubble/hubble-fe/src/components/Sidebar/index.ant.js
index 6ed39db42..744fae2c0 100644
--- a/hugegraph-hubble/hubble-fe/src/components/Sidebar/index.ant.js
+++ b/hugegraph-hubble/hubble-fe/src/components/Sidebar/index.ant.js
@@ -30,7 +30,7 @@ import {
     ClusterOutlined,
 } from '@ant-design/icons';
 import {Link, useLocation} from 'react-router-dom';
-import {isPdEnabled} from '../../utils/config';
+import {isAuthEnabled, isPdEnabled} from '../../utils/config';
 import {getGraphspacePath} from '../../utils/productMode';
 import {getPreparationSchemaPath} from '../../utils/dataPreparationNavigation';
 import {getSidebarMenuKey} from '../../utils/sidebarNavigation';
@@ -46,11 +46,11 @@ const items = (t, pathname, capabilities = []) => {
     const ACCOUNT = {label: <Link to='/account'>{t('home.account')}</Link>, 
key: 'account'};
 
     // TODO temporary hided the resource and role modules
-    let systemList = [MY];
+    let systemList = isAuthEnabled() ? [MY] : [];
     if (capabilities.includes('accounts_manage')
         || capabilities.includes('graphspace_members_manage')) {
         // systemList = [MY, RESOURCE, ROLE];
-        systemList = [MY, ACCOUNT];
+        systemList = isAuthEnabled() ? [MY, ACCOUNT] : [];
     }
     const operationsList = [
         ...(pdMode && capabilities.includes('operations_health_read') ? [{
diff --git 
a/hugegraph-hubble/hubble-fe/src/components/Sidebar/index.ant.test.js 
b/hugegraph-hubble/hubble-fe/src/components/Sidebar/index.ant.test.js
index 541015d68..db826c47f 100644
--- a/hugegraph-hubble/hubble-fe/src/components/Sidebar/index.ant.test.js
+++ b/hugegraph-hubble/hubble-fe/src/components/Sidebar/index.ant.test.js
@@ -73,6 +73,27 @@ test('hides PD-only operations links in standalone mode 
without topology access'
     expect(screen.queryByRole('link', {name: '节点详情'})).not.toBeInTheDocument();
 });
 
+test('hides account and profile links in anonymous mode', async () => {
+    sessionStorage.setItem('hubble_config_', JSON.stringify({
+        pd_enabled: false,
+        auth_enabled: false,
+    }));
+
+    render(
+        <MemoryRouter
+            initialEntries={['/navigation']}
+            future={{v7_startTransition: true, v7_relativeSplatPath: true}}
+        >
+            <Sidebar />
+        </MemoryRouter>
+    );
+
+    expect(await screen.findByRole('navigation', {name: '主导航'}))
+        .toBeInTheDocument();
+    expect(screen.queryByRole('link', {name: '个人中心'})).not.toBeInTheDocument();
+    expect(screen.queryByRole('link', {name: '账号管理'})).not.toBeInTheDocument();
+});
+
 test('keeps monitoring and account links in one operations section', async () 
=> {
     sessionStorage.setItem('hubble_config_', JSON.stringify({pd_enabled: 
true}));
     render(
diff --git 
a/hugegraph-hubble/hubble-fe/src/i18n/resources/en-US/modules/pages.json 
b/hugegraph-hubble/hubble-fe/src/i18n/resources/en-US/modules/pages.json
index e8b92359a..eda395aec 100644
--- a/hugegraph-hubble/hubble-fe/src/i18n/resources/en-US/modules/pages.json
+++ b/hugegraph-hubble/hubble-fe/src/i18n/resources/en-US/modules/pages.json
@@ -391,7 +391,9 @@
       "SUPER_ADMIN": "Super Administrator",
       "GS_READ_ONLY": "GraphSpace Read-only",
       "GS_READ_WRITE": "GraphSpace Read-write",
-      "GS_ADMIN": "GraphSpace Administrator"
+      "GS_ADMIN": "GraphSpace Administrator",
+      "mixed": "Mixed GraphSpace access",
+      "legacy_custom": "Legacy/custom access"
     },
     "form": {
       "title_detail": "View Account",
diff --git 
a/hugegraph-hubble/hubble-fe/src/i18n/resources/zh-CN/modules/pages.json 
b/hugegraph-hubble/hubble-fe/src/i18n/resources/zh-CN/modules/pages.json
index da3e7f7e0..fa025bb7e 100644
--- a/hugegraph-hubble/hubble-fe/src/i18n/resources/zh-CN/modules/pages.json
+++ b/hugegraph-hubble/hubble-fe/src/i18n/resources/zh-CN/modules/pages.json
@@ -391,7 +391,9 @@
       "SUPER_ADMIN": "超级管理员",
       "GS_READ_ONLY": "GraphSpace 只读",
       "GS_READ_WRITE": "GraphSpace 读写",
-      "GS_ADMIN": "GraphSpace 管理员"
+      "GS_ADMIN": "GraphSpace 管理员",
+      "mixed": "混合 GraphSpace 权限",
+      "legacy_custom": "旧版/自定义权限"
     },
     "form": {
       "title_detail": "查看账号",
diff --git a/hugegraph-hubble/hubble-fe/src/pages/Account/EditLayer.js 
b/hugegraph-hubble/hubble-fe/src/pages/Account/EditLayer.js
index 8d5d43292..775f23587 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/Account/EditLayer.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/Account/EditLayer.js
@@ -32,8 +32,7 @@ import {
 
 const PAGE_ERROR_CONFIG = {suppressBusinessErrorToast: true};
 const DEFAULT_ALLOWED_OPERATIONS = {create: true, edit: true, auth: true};
-const permissionPresetChanged = (prev, next) => (
-    prev.permission_preset !== next.permission_preset
+const permissionPresetChanged = (prev, next) => (prev.permission_preset !== 
next.permission_preset
 );
 
 const HelpLabel = ({t, labelKey}) => (
@@ -79,8 +78,7 @@ const EditLayer = ({
         }).catch(() => message.error(t('common.msg.operation_failed')));
     }, [onCancel, refresh, t]);
     const updateUser = useCallback(values => {
-        return api.auth.updateUser(
-            data.id, toPermissionPayload(values), PAGE_ERROR_CONFIG
+        return api.auth.updateUser(data.id, toPermissionPayload(values), 
PAGE_ERROR_CONFIG
         ).then(res => {
             if (res.status === 200) {
                 message.success(t('common.msg.update_success'));
@@ -99,8 +97,7 @@ const EditLayer = ({
             ...values,
             permission_preset: PERMISSION_PRESETS.GS_ADMIN,
         });
-        return api.auth.updateAdminspace(
-            data.id, payload.adminSpaces, PAGE_ERROR_CONFIG
+        return api.auth.updateAdminspace(data.id, payload.adminSpaces, 
PAGE_ERROR_CONFIG
         ).then(res => {
             if (res.status === 200) {
                 message.success(t('common.msg.set_success'));
@@ -195,7 +192,9 @@ const EditLayer = ({
                         form.setFieldsValue({
                             ...res.data,
                             permission_preset: getAccountPreset(res.data),
-                            graphspaces: getPresetSpaces(res.data),
+                            graphspaces: op === 'auth'
+                                ? (res.data?.adminSpaces ?? [])
+                                : getPresetSpaces(res.data),
                         });
                     }
                     setDetail(res.data);
@@ -253,14 +252,12 @@ const EditLayer = ({
                             <Form.Item label={t('account.form.name')} 
className={style.item}>
                                 {detail.user_nickname}
                             </Form.Item>
-                            <Form.Item 
label={t('account.form.permission_preset')} className={style.item}>
-                                
{t(`account.permission_preset.${getAccountPreset(detail)}`)}
+                            <Form.Item 
label={t('account.form.permission_preset')} className={style.item}> 
{t(`account.permission_preset.${getAccountPreset(detail) ?? 'mixed'}`)}
                             </Form.Item>
                             <Form.Item label={t('account.form.remark')} 
className={style.item}>
                                 {detail.user_description}
                             </Form.Item>
-                            <Form.Item label={t('account.form.graphspaces')} 
className={style.item}>
-                                {getPresetSpaces(detail).join(', ')}
+                            <Form.Item label={t('account.form.graphspaces')} 
className={style.item}> {getPresetSpaces(detail).join(', ')}
                             </Form.Item>
                             <Form.Item label={t('account.col.create_time')} 
className={style.item}>
                                 {detail.user_create}
@@ -341,8 +338,7 @@ const EditLayer = ({
                                         noStyle
                                         shouldUpdate={permissionPresetChanged}
                                     >
-                                        {({getFieldValue}) => 
(getFieldValue('permission_preset')
-                                            !== PERMISSION_PRESETS.GS_ADMIN ? 
null : (
+                                        {({getFieldValue}) => 
(getFieldValue('permission_preset') === PERMISSION_PRESETS.SUPER_ADMIN ? null : 
(
                                                 <Form.Item
                                                     label={<HelpLabel t={t} 
labelKey='account.form.graphspaces' />}
                                                     name="graphspaces"
diff --git a/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.js 
b/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.js
index cd537c444..d6eb374ee 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.js
@@ -40,6 +40,31 @@ import {PERMISSION_PRESETS} from './permissionPresets';
 const PAGE_ERROR_CONFIG = {suppressBusinessErrorToast: true};
 const PAGE_PARAMS = {query: '', page_no: 1, page_size: 200};
 const responseRecords = response => response?.data?.records ?? [];
+const adminRole = {
+    role_id: PERMISSION_PRESETS.GS_ADMIN,
+    permission_preset: PERMISSION_PRESETS.GS_ADMIN,
+};
+
+const mergeMembersAndAdmins = (members, admins) => {
+    const rows = new Map(members.map(member => [
+        member.user_id,
+        {...member, member_roles: member.roles ?? []},
+    ]));
+    admins.forEach(admin => {
+        const userId = admin.id ?? admin.user_id;
+        const existing = rows.get(userId) ?? {};
+        rows.set(userId, {
+            ...existing,
+            user_id: userId,
+            user_name: admin.name ?? admin.user_name ?? existing.user_name,
+            member_roles: existing.roles ?? existing.member_roles ?? [],
+            roles: [...(existing.roles ?? existing.member_roles ?? []), 
adminRole],
+            is_space_admin: true,
+        });
+    });
+    return Array.from(rows.values());
+};
+
 const rolePreset = role => {
     const explicit = role?.permission_preset ?? role?.permissionPreset;
     if (Object.values(PERMISSION_PRESETS).includes(explicit)) {
@@ -68,11 +93,17 @@ const rolePreset = role => {
     return null;
 };
 
+const rolesPreset = roles => {
+    const values = roles ?? [];
+    const presets = values.map(rolePreset);
+    return values.length > 0 && presets.every(Boolean) && new 
Set(presets).size === 1 ? presets[0] : null;
+};
+
 const roleLabel = (role, t) => {
     const preset = rolePreset(role);
     return preset
         ? t(`account.permission_preset.${preset}`)
-        : role?.role_name ?? role?.role_nickname ?? t('common.label.unknown');
+        : t('account.permission_preset.legacy_custom');
 };
 
 const RowAction = ({row, onAction, children}) => {
@@ -212,20 +243,28 @@ const SpaceAccess = () => {
     const loadMembers = useCallback(space => api.auth.getSpaceMembers(
         space, PAGE_PARAMS, PAGE_ERROR_CONFIG
     ), []);
-    const loadRoles = useCallback(space => api.auth.getSpaceRoles(
+    const loadAdmins = useCallback(space => api.auth.getSpaceAdmins(
         space, PAGE_PARAMS, PAGE_ERROR_CONFIG
     ), []);
     const members = useScopedResource(
         graphSpace, contextVersion, loadMembers, responseRecords
     );
-    const roles = useScopedResource(
-        graphSpace, contextVersion, loadRoles, responseRecords
+    const admins = useScopedResource(graphSpace, contextVersion, loadAdmins, 
responseRecords
     );
+    const visibleMembers = {
+        data: mergeMembersAndAdmins(members.data, admins.data),
+        loading: members.loading || admins.loading,
+        error: members.error || admins.error,
+        retry: () => {
+            members.retry();
+            admins.retry();
+        },
+    };
 
     const refreshAll = useCallback(() => {
         members.retry();
-        roles.retry();
-    }, [members, roles]);
+        admins.retry();
+    }, [admins, members]);
 
     const runMutation = useCallback(async (operation, close) => {
         if (submitting) {
@@ -253,7 +292,7 @@ const SpaceAccess = () => {
     const openMember = useCallback(row => {
         memberForm.setFieldsValue({
             user_id: row?.user_id,
-            permission_preset: rolePreset(row?.roles?.[0]),
+            permission_preset: rolesPreset(row?.roles),
         });
         setMemberDialog(row ?? {});
     }, [memberForm]);
@@ -262,27 +301,13 @@ const SpaceAccess = () => {
         memberForm.resetFields();
     }, [memberForm]);
     const submitMember = useCallback(values => {
-        const role = roles.data.find(item => rolePreset(item) === 
values.permission_preset);
-        if (!role) {
-            message.error(t('account.space_access.member.preset_unavailable'));
-            return;
-        }
-        const payload = {
-            user_id: values.user_id,
-            roles: role ? [{
-                role_id: role.id,
-                role_name: role.role_name ?? role.role_nickname ?? 
values.permission_preset,
-            }] : [],
-        };
-        const operation = memberDialog?.user_id
-            ? () => api.auth.updateSpaceMember(
-                graphSpace, memberDialog.user_id, payload, PAGE_ERROR_CONFIG
-            )
-            : () => api.auth.addSpaceMember(
-                graphSpace, payload, PAGE_ERROR_CONFIG
-            );
-        runMutation(operation, closeMember);
-    }, [closeMember, graphSpace, memberDialog, roles.data, runMutation, t]);
+        runMutation(
+            () => api.auth.setSpacePreset(graphSpace, values.user_id, 
values.permission_preset,
+                PAGE_ERROR_CONFIG
+            ),
+            closeMember
+        );
+    }, [closeMember, graphSpace, runMutation]);
 
     const confirmDelete = useCallback((title, operation) => {
         Modal.confirm({
@@ -392,7 +417,7 @@ const SpaceAccess = () => {
                         key: 'members',
                         label: t('account.space_access.tabs.members'),
                         children: table(
-                            members, memberColumns, 'user_id',
+                            visibleMembers, memberColumns, 'user_id',
                             t('account.space_access.member.add'),
                             addMember, canAddMember
                         ),
@@ -437,4 +462,5 @@ const SpaceAccess = () => {
     );
 };
 
+export {rolesPreset};
 export default SpaceAccess;
diff --git a/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.test.js 
b/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.test.js
index a74866cc8..7367678f9 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.test.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/Account/SpaceAccess.test.js
@@ -17,7 +17,7 @@
  */
 
 import {act, fireEvent, render, screen, waitFor} from '@testing-library/react';
-import SpaceAccess from './SpaceAccess';
+import SpaceAccess, {rolesPreset} from './SpaceAccess';
 import * as api from '../../api';
 
 let mockAuthContext;
@@ -33,6 +33,10 @@ jest.mock('../../auth/AuthContext', () => ({
 jest.mock('../../api', () => ({
     auth: {
         getSpaceMembers: jest.fn(),
+        getSpaceAdmins: jest.fn(),
+        setSpaceAdmin: jest.fn(),
+        removeSpaceAdmin: jest.fn(),
+        setSpacePreset: jest.fn(),
         getSpaceRoles: jest.fn(),
         getSpaceTargets: jest.fn(),
         getSpaceAccesses: jest.fn(),
@@ -66,11 +70,23 @@ const page = records => ({status: 200, data: {records, 
total: records.length}});
 
 const setResponses = ({members = [], roles = [], targets = [], accesses = []} 
= {}) => {
     api.auth.getSpaceMembers.mockResolvedValue(page(members));
+    api.auth.getSpaceAdmins.mockResolvedValue(page([]));
     api.auth.getSpaceRoles.mockResolvedValue(page(roles));
     api.auth.getSpaceTargets.mockResolvedValue(page(targets));
     api.auth.getSpaceAccesses.mockResolvedValue({status: 200, data: accesses});
 };
 
+test('requires explicit preset for mixed or legacy member roles', () => {
+    expect(rolesPreset([
+        {role_name: 'observer'},
+        {role_name: 'custom-role'},
+    ])).toBeNull();
+    expect(rolesPreset([
+        {role_name: 'observer'},
+        {role_name: 'analyst'},
+    ])).toBeNull();
+});
+
 beforeEach(() => {
     jest.clearAllMocks();
     mockAuthContext = {
@@ -106,7 +122,8 @@ test('uses only path-scoped APIs for a space 
administrator', async () => {
     expect(api.auth.getSpaceMembers).toHaveBeenCalledWith(
         'SPACE_A', expect.any(Object), expect.any(Object)
     );
-    expect(api.auth.getSpaceRoles).toHaveBeenCalledWith(
+    expect(api.auth.getSpaceRoles).not.toHaveBeenCalled();
+    expect(api.auth.getSpaceAdmins).toHaveBeenCalledWith(
         'SPACE_A', expect.any(Object), expect.any(Object)
     );
     expect(api.auth.getSpaceTargets).not.toHaveBeenCalled();
@@ -169,12 +186,8 @@ test('does not infer mutations when the server grants 
read-only actions', async
     })).not.toBeInTheDocument();
 });
 
-test('maps a selected preset to the authoritative role id when adding a 
member', async () => {
-    api.auth.getSpaceRoles.mockResolvedValueOnce(page([{
-        id: 'writer-id',
-        permission_preset: 'GS_READ_WRITE',
-    }]));
-    api.auth.addSpaceMember.mockResolvedValue({status: 200});
+test('submits only the selected preset when adding a member', async () => {
+    api.auth.setSpacePreset.mockResolvedValue({status: 200});
     render(<SpaceAccess />);
 
     await screen.findAllByText('alice');
@@ -190,18 +203,13 @@ test('maps a selected preset to the authoritative role id 
when adding a member',
     
fireEvent.click(screen.getByText('account.permission_preset.GS_READ_WRITE'));
     fireEvent.click(screen.getByRole('button', {name: 'OK'}));
 
-    await waitFor(() => expect(api.auth.addSpaceMember).toHaveBeenCalledWith(
-        'SPACE_A',
-        {
-            user_id: 'bob',
-            roles: [{role_id: 'writer-id', role_name: 'GS_READ_WRITE'}],
-        },
-        expect.any(Object)
+    await waitFor(() => expect(api.auth.setSpacePreset).toHaveBeenCalledWith(
+        'SPACE_A', 'bob', 'GS_READ_WRITE', expect.any(Object)
     ));
 });
 
-test('does not submit when the selected preset has no server role', async () 
=> {
-    api.auth.addSpaceMember.mockResolvedValue({status: 200});
+test('uses the preset API for GS admin', async () => {
+    api.auth.setSpacePreset.mockResolvedValue({status: 200});
     render(<SpaceAccess />);
 
     await screen.findAllByText('alice');
@@ -214,8 +222,24 @@ test('does not submit when the selected preset has no 
server role', async () =>
     });
     const comboboxes = screen.getAllByRole('combobox');
     fireEvent.mouseDown(comboboxes[comboboxes.length - 1]);
-    
fireEvent.click(screen.getByText('account.permission_preset.GS_READ_WRITE'));
+    fireEvent.click(screen.getByText('account.permission_preset.GS_ADMIN'));
     fireEvent.click(screen.getByRole('button', {name: 'OK'}));
 
-    await waitFor(() => 
expect(api.auth.addSpaceMember).not.toHaveBeenCalled());
+    await waitFor(() => expect(api.auth.setSpacePreset).toHaveBeenCalledWith(
+        'SPACE_A', 'bob', 'GS_ADMIN', expect.any(Object)
+    ));
+    expect(api.auth.addSpaceMember).not.toHaveBeenCalled();
+});
+
+test('shows GraphSpace administrators through the preset model', async () => {
+    api.auth.getSpaceAdmins.mockResolvedValueOnce(page([{
+        id: 'admin-id',
+        name: 'space-admin',
+    }]));
+
+    render(<SpaceAccess />);
+
+    expect(await screen.findByText('space-admin')).toBeInTheDocument();
+    expect(screen.getByText('account.permission_preset.GS_ADMIN'))
+        .toBeInTheDocument();
 });
diff --git a/hugegraph-hubble/hubble-fe/src/pages/Account/index.js 
b/hugegraph-hubble/hubble-fe/src/pages/Account/index.js
index 7c0bd4aae..572795bc4 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/Account/index.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/Account/index.js
@@ -137,6 +137,9 @@ const GlobalAccounts = () => {
             width: 140,
             render: row => {
                 const preset = getAccountPreset(row);
+                if (!preset) {
+                    return <Tag>{t('account.permission_preset.mixed')}</Tag>;
+                }
                 const color = preset === PERMISSION_PRESETS.SUPER_ADMIN ? 'red'
                     : preset === PERMISSION_PRESETS.GS_ADMIN ? 'blue' : 
'default';
                 return <Tag 
color={color}>{t(`account.permission_preset.${preset}`)}</Tag>;
diff --git a/hugegraph-hubble/hubble-fe/src/pages/Account/permissionPresets.js 
b/hugegraph-hubble/hubble-fe/src/pages/Account/permissionPresets.js
index 99fb9a64e..072d8a227 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/Account/permissionPresets.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/Account/permissionPresets.js
@@ -34,9 +34,21 @@ const getAccountPreset = account => {
     if (account?.is_superadmin) {
         return PERMISSION_PRESETS.SUPER_ADMIN;
     }
-    const scopedPresets = (account?.graphspace_permissions ?? [])
+    if (explicit === 'LEGACY_CUSTOM') {
+        return null;
+    }
+    const scopedPermissions = Array.isArray(account?.graphspace_permissions) ? 
account.graphspace_permissions : [];
+    const scopedPresets = [...scopedPermissions,
+        ...(Array.isArray(account?.adminSpaces)
+            ? account.adminSpaces.map(graphspace => ({
+                graphspace,
+                permission_preset: PERMISSION_PRESETS.GS_ADMIN,
+            })) : [])]
         .map(permission => permission?.permission_preset)
         .filter(preset => presetKeys.includes(preset));
+    if (new Set(scopedPresets).size > 1) {
+        return null;
+    }
     if (scopedPresets.includes(PERMISSION_PRESETS.GS_ADMIN)) {
         return PERMISSION_PRESETS.GS_ADMIN;
     }
@@ -50,10 +62,22 @@ const getAccountPreset = account => {
 };
 
 const getPresetSpaces = account => {
-    const spaces = account?.graphspace_permissions ?? account?.adminSpaces;
+    const scoped = Array.isArray(account?.graphspace_permissions)
+        ? account.graphspace_permissions : [];
+    const admins = Array.isArray(account?.adminSpaces)
+        ? account.adminSpaces.map(graphspace => ({
+            graphspace,
+            permission_preset: PERMISSION_PRESETS.GS_ADMIN,
+        })) : [];
+    const spaces = [...scoped, ...admins];
     return Array.isArray(spaces)
-        ? spaces.map(space => (typeof space === 'string'
-            ? space : space.name ?? space.graphspace)).filter(Boolean)
+        ? Array.from(new Set(spaces.map(space => {
+            if (typeof space === 'string') {
+                return space;
+            }
+            const value = space?.name ?? space?.graphspace;
+            return typeof value === 'string' ? value : value?.name;
+        }).filter(Boolean)))
         : [];
 };
 
diff --git 
a/hugegraph-hubble/hubble-fe/src/pages/Account/permissionPresets.test.js 
b/hugegraph-hubble/hubble-fe/src/pages/Account/permissionPresets.test.js
index cf287cae4..bed366348 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/Account/permissionPresets.test.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/Account/permissionPresets.test.js
@@ -68,3 +68,22 @@ test('clears admin spaces for the super administrator 
preset', () => {
         is_superadmin: true,
     });
 });
+
+test('requires an explicit choice before replacing mixed GraphSpace presets', 
() => {
+    expect(getAccountPreset({
+        graphspace_permissions: [
+            {graphspace: 'read', permission_preset: 'GS_READ_ONLY'},
+            {graphspace: 'write', permission_preset: 'GS_READ_WRITE'},
+        ],
+    })).toBeNull();
+});
+
+test('requires an explicit choice before migrating legacy custom access', () 
=> {
+    expect(getAccountPreset({
+        permission_preset: 'LEGACY_CUSTOM',
+        graphspace_permissions: [{
+            graphspace: 'legacy',
+            permission_preset: 'GS_READ_WRITE',
+        }],
+    })).toBeNull();
+});
diff --git a/hugegraph-hubble/hubble-fe/src/routes/index.js 
b/hugegraph-hubble/hubble-fe/src/routes/index.js
index 8d7a27599..307217715 100644
--- a/hugegraph-hubble/hubble-fe/src/routes/index.js
+++ b/hugegraph-hubble/hubble-fe/src/routes/index.js
@@ -89,7 +89,7 @@ const AccountRoute = () => {
     return hasCapability('accounts_manage')
         || hasCapability('graphspace_members_manage')
         ? <Account />
-        : <Navigate to='/profile' replace />;
+        : <Navigate to='/navigation' replace />;
 };
 
 const GraphSpaceListRoute = () => {
@@ -102,7 +102,7 @@ const GraphSpaceListRoute = () => {
         return null;
     }
     return hasCapability('graphspaces_read')
-        ? <GraphSpace /> : <Navigate to='/profile' replace />;
+        ? <GraphSpace /> : <Navigate to='/navigation' replace />;
 };
 
 const LegacyProfileRedirect = () => {
@@ -140,7 +140,12 @@ const GraphspaceParamRoute = ({children, fallback}) => {
 const RouteList = ({element}) => {
     return (
         <Routes>
-            <Route path="/login" element={<Login />} />
+            <Route
+                path="/login"
+                element={isAuthEnabled()
+                    ? <Login />
+                    : <Navigate to='/navigation' replace />}
+            />
             <Route
                 path="/"
                 element={(
@@ -184,7 +189,12 @@ const RouteList = ({element}) => {
                 <Route path="/task/edit" element={<TaskEdit />} />
                 <Route path="/task/detail/:taskid" element={<TaskDetail />} />
 
-                <Route path='/profile' element={<My />} />
+                <Route
+                    path='/profile'
+                    element={isAuthEnabled()
+                        ? <My />
+                        : <Navigate to='/navigation' replace />}
+                />
                 <Route path='/my' element={<LegacyProfileRedirect />} />
                 <Route path='/resource' element={<Navigate to='/navigation' 
replace />} />
                 <Route path='/role' element={<Navigate to='/navigation' 
replace />} />
diff --git a/hugegraph-hubble/hubble-fe/src/routes/route-guard.test.js 
b/hugegraph-hubble/hubble-fe/src/routes/route-guard.test.js
index 930013503..070bf67e4 100644
--- a/hugegraph-hubble/hubble-fe/src/routes/route-guard.test.js
+++ b/hugegraph-hubble/hubble-fe/src/routes/route-guard.test.js
@@ -19,7 +19,7 @@
 import {render, screen} from '@testing-library/react';
 import {MemoryRouter, Outlet, useLocation} from 'react-router-dom';
 import RouteList from './index';
-import {isPdEnabled} from '../utils/config';
+import {isAuthEnabled, isPdEnabled} from '../utils/config';
 
 let mockCapabilities = new Set();
 
@@ -54,6 +54,7 @@ jest.mock('../pages/Test', () => () => <div>test page</div>);
 jest.mock('../pages/GraphAnalysis', () => () => <div>graph analysis 
page</div>);
 jest.mock('../pages/AsyncTaskResult', () => () => <div>async task result 
page</div>);
 jest.mock('../utils/config', () => ({
+    isAuthEnabled: jest.fn(() => true),
     isPdEnabled: jest.fn(() => false),
 }));
 jest.mock('../auth/AuthContext', () => ({
@@ -97,6 +98,7 @@ describe('route guard', () => {
         sessionStorage.clear();
         mockAccountRender.mockClear();
         mockCapabilities = new Set();
+        isAuthEnabled.mockReturnValue(true);
         isPdEnabled.mockReturnValue(false);
     });
 
@@ -125,6 +127,17 @@ describe('route guard', () => {
         expect(sessionStorage.getItem('redirect')).toBeNull();
     });
 
+    it.each(['/login', '/profile'])('hides authentication route %s in 
anonymous mode',
+        route => {
+            isAuthEnabled.mockReturnValue(false);
+            renderRoutes(route);
+
+            expect(screen.getByText('navigation page')).toBeTruthy();
+            expect(screen.queryByText(/^login:/)).toBeNull();
+            expect(screen.queryByText('my page')).toBeNull();
+        }
+    );
+
     it('renders the standard 404 surface for an unknown operations route', () 
=> {
         sessionStorage.setItem('user_', JSON.stringify({
             id: 'admin',
@@ -188,7 +201,7 @@ describe('route guard', () => {
 
         renderRoutes('/account');
 
-        expect(screen.getByText('my page')).toBeTruthy();
+        expect(screen.getByText('navigation page')).toBeTruthy();
         expect(screen.queryByText('account page')).toBeNull();
         expect(mockAccountRender).not.toHaveBeenCalled();
     });
@@ -231,7 +244,7 @@ describe('route guard', () => {
 
     it.each([
         ['/graphspace', 'graph page'],
-        ['/account', 'my page'],
+        ['/account', 'navigation page'],
     ])('uses the non-PD fallback for %s', (route, page) => {
         sessionStorage.setItem('user_', JSON.stringify({
             id: 'admin',
@@ -280,7 +293,7 @@ describe('route guard', () => {
 
         renderRoutes('/account');
 
-        expect(screen.getByText('my page')).toBeTruthy();
+        expect(screen.getByText('navigation page')).toBeTruthy();
         expect(screen.queryByText('account page')).toBeNull();
     });
 
diff --git a/tools/checkstyle.xml b/tools/checkstyle.xml
index 60b785836..99f32095e 100644
--- a/tools/checkstyle.xml
+++ b/tools/checkstyle.xml
@@ -25,7 +25,7 @@
         <property name="eachLine" value="true"/>
     </module>
     <module name="LineLength">
-        <property name="max" value="100"/>
+        <property name="max" value="120"/>
         <property name="ignorePattern" value="^package.*|^import.*|a 
href|href|http://|https://|ftp://|Reflection\.*"/>
     </module>
     <module name="RegexpSingleline">

Reply via email to