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 ff1334c765554744cedeff443c34a9a52acf1a44
Author: dark <[email protected]>
AuthorDate: Sat Aug 29 13:26:06 2026 +0800

    fix(hubble): complete legacy auth compatibility
    
    - support the scoped Basic Gremlin fallback
    - require relogin after password changes
    - detect anonymous mode and gate unsupported features
    - fetch one exact Server revision in CI
---
 .github/workflows/hubble-ci.yml                    | 25 +++++--
 .../org/apache/hugegraph/driver/HugeClient.java    | 25 +++++++
 .../hugegraph/driver/ServerCompatibility.java      | 31 +++++++--
 .../hugegraph/driver/ServerCompatibilityTest.java  |  9 ++-
 hugegraph-hubble/AGENTS.md                         | 12 ++--
 hugegraph-hubble/README.md                         | 17 ++---
 .../java/org/apache/hugegraph/common/Constant.java |  7 ++
 .../org/apache/hugegraph/config/HubbleConfig.java  | 24 +------
 .../hugegraph/controller/BaseController.java       | 50 +++++++++++++-
 .../hugegraph/controller/ConfigController.java     | 48 ++++++++++++-
 .../controller/algorithm/OltpAlgoController.java   | 52 +++++++--------
 .../hugegraph/controller/auth/LoginController.java |  7 ++
 .../hugegraph/controller/auth/UserController.java  |  8 ++-
 .../controller/langchain/LangChainController.java  |  2 +-
 .../controller/saas/SaasGraphViewController.java   |  4 +-
 .../controller/schema/SchemaController.java        |  2 +-
 .../apache/hugegraph/options/HubbleOptions.java    |  8 ---
 .../hugegraph/service/HugeClientPoolService.java   | 20 +++++-
 .../hugegraph/service/auth/AuthContextService.java | 11 +--
 .../hugegraph/service/auth/AuthModeService.java    | 46 +++++++++++--
 .../config/HubbleConfigEnvironmentTest.java        | 68 -------------------
 .../auth/AccountMutationAuthorizationTest.java     | 19 ++++++
 .../controller/ingest/IngestControllerTest.java    |  6 +-
 .../controller/space/GraphSpaceControllerTest.java |  9 +--
 .../SchemaTemplateControllerSecurityTest.java      |  7 +-
 .../service/auth/AuthContextServiceTest.java       |  8 ++-
 .../service/auth/AuthModeServiceTest.java          | 55 +++++++++++++++
 .../apache/hugegraph/unit/AuthSecurityTest.java    | 57 +++++++++++-----
 .../unit/BaseControllerGremlinClientTest.java      | 27 ++++++--
 .../hugegraph/unit/ConfigControllerTest.java       | 78 +++++++++++++++++++++-
 .../hugegraph/unit/OperationsControllerTest.java   |  5 +-
 .../org/apache/hugegraph/unit/UnitTestSuite.java   |  7 +-
 .../static/conf/hugegraph-hubble.properties        |  3 -
 .../assembly/travis/download-hugegraph.sh          |  2 +-
 .../hubble-fe/src/auth/graphspaceAccess.js         | 10 ++-
 .../hubble-fe/src/auth/graphspaceAccess.test.js    |  5 ++
 .../src/components/GraphContextSwitcher/index.js   | 15 +++--
 .../components/GraphContextSwitcher/index.test.js  | 20 ++++++
 .../hubble-fe/src/components/Sidebar/index.ant.js  |  7 +-
 .../src/components/Sidebar/index.ant.test.js       | 25 +++++++
 .../src/i18n/resources/en-US/modules/pages.json    |  2 +
 .../src/i18n/resources/zh-CN/modules/pages.json    |  2 +
 .../hubble-fe/src/modules/analysis/Home/index.js   |  3 +
 .../src/modules/analysis/QueryBar/Home/index.js    |  5 +-
 .../modules/analysis/QueryBar/Home/index.test.js   | 15 +++++
 .../src/pages/Graph/default-card-actions.test.js   |  5 +-
 .../hubble-fe/src/pages/Graph/index.js             | 45 +++++++------
 hugegraph-hubble/hubble-fe/src/pages/My/index.js   | 12 +++-
 .../hubble-fe/src/pages/My/my-recovery.test.js     | 34 ++++++++--
 hugegraph-hubble/hubble-fe/src/utils/config.js     | 17 ++++-
 .../src/utils/{config.js => config.test.js}        | 36 +++++-----
 .../hubble-fe/src/utils/productMode.js             |  3 -
 .../hubble-fe/src/utils/productMode.test.js        |  5 +-
 53 files changed, 736 insertions(+), 289 deletions(-)

diff --git a/.github/workflows/hubble-ci.yml b/.github/workflows/hubble-ci.yml
index ae7290aa4..425e09d76 100644
--- a/.github/workflows/hubble-ci.yml
+++ b/.github/workflows/hubble-ci.yml
@@ -24,8 +24,9 @@ on:
 env:
   TRAVIS_DIR: hugegraph-hubble/hubble-dist/assembly/travis
   # Server PR #3159 declares the GraphSpace default-role contract as API 0.72.
-  HUGEGRAPH_SERVER_COMMIT: 52035dad9ee8d6b666329ca0d03950c773d3e1eb
-  HUGEGRAPH_SERVER_FETCH_REF: refs/pull/3159/head
+  # TODO: After #3159 merges, switch these values to the stable upstream 
branch.
+  HUGEGRAPH_SERVER_GIT_URL: https://github.com/hugegraph/hugegraph.git
+  HUGEGRAPH_SERVER_BRANCH: cx/bump-server-api-version
 
 jobs:
   hubble-ci:
@@ -88,11 +89,22 @@ jobs:
           path: ~/.cache/ms-playwright
           key: ${{ runner.os }}-playwright-${{ 
hashFiles('hugegraph-hubble/hubble-fe/yarn.lock') }}
 
+      - name: Resolve HugeGraph Server branch
+        id: server-ref
+        run: |
+          SERVER_COMMIT="$(git ls-remote "$HUGEGRAPH_SERVER_GIT_URL" \
+            "refs/heads/$HUGEGRAPH_SERVER_BRANCH" | cut -f1)"
+          if [[ -z "$SERVER_COMMIT" ]]; then
+            echo "Unable to resolve HugeGraph Server branch" >&2
+            exit 1
+          fi
+          echo "commit=$SERVER_COMMIT" >> "$GITHUB_OUTPUT"
+
       - name: Cache HugeGraph Server
         uses: actions/cache@v6
         with:
-          path: ~/hugegraph-cache-${{ env.HUGEGRAPH_SERVER_COMMIT }}
-          key: ${{ runner.os }}-hugegraph-server-${{ 
env.HUGEGRAPH_SERVER_COMMIT }}
+          path: ~/hugegraph-cache-${{ steps.server-ref.outputs.commit }}
+          key: ${{ runner.os }}-hugegraph-server-${{ 
steps.server-ref.outputs.commit }}
 
       - name: Frontend i18n check
         working-directory: hugegraph-hubble/hubble-fe
@@ -116,8 +128,9 @@ jobs:
 
       - name: Prepare env and service
         env:
-          COMMIT_ID: ${{ env.HUGEGRAPH_SERVER_COMMIT }}
-          COMMIT_REF: ${{ env.HUGEGRAPH_SERVER_FETCH_REF }}
+          COMMIT_ID: ${{ steps.server-ref.outputs.commit }}
+          COMMIT_REF: ${{ steps.server-ref.outputs.commit }}
+          HUGEGRAPH_GIT_URL: ${{ env.HUGEGRAPH_SERVER_GIT_URL }}
           NODE_OPTIONS: --max-old-space-size=4096
         run: |
           echo "=== Environment Info ==="
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 c314405f9..b0e4a205d 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
@@ -268,10 +268,35 @@ public class HugeClient implements Closeable {
         return this.compatibility.supportsDefaultRole();
     }
 
+    public boolean supportsGraphSpace() {
+        return this.compatibility.supportsGraphSpace();
+    }
+
+    public boolean supportsGraphCreate() {
+        return ServerCompatibility.supportsGraphCreate(
+               this.version.getApiVersion());
+    }
+
+    public boolean supportsCypher() {
+        return this.compatibility.supportsCypher();
+    }
+
     public boolean supportsPersonalProfileUpdate() {
         return this.compatibility.supportsPersonalProfileUpdate();
     }
 
+    public boolean isServerAuthEnabled() {
+        try {
+            this.graphs.listGraph();
+            return false;
+        } catch (ServerException e) {
+            if (e.status() == 401) {
+                return true;
+            }
+            throw e;
+        }
+    }
+
     public User findUserByName(String name) {
         if (this.supportsDefaultRole()) {
             return this.auth.getUserByName(name);
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 ac329aacf..67b3d9ead 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 GRAPH_CREATE_MIN_API_VERSION = "0.67";
     private static final String DEFAULT_ROLE_MIN_API_VERSION = "0.72";
 
     private ServerCompatibility() {
@@ -54,12 +55,16 @@ public final class ServerCompatibility {
     }
 
     private static boolean supportsDefaultRoleApi(String apiVersion) {
+        return supportsApi(apiVersion, DEFAULT_ROLE_MIN_API_VERSION);
+    }
+
+    private static boolean supportsApi(String apiVersion,
+                                       String minimumVersion) {
         if (apiVersion == null || apiVersion.trim().isEmpty()) {
             return false;
         }
         try {
-            return VersionUtil.gte(apiVersion.trim(),
-                                   DEFAULT_ROLE_MIN_API_VERSION);
+            return VersionUtil.gte(apiVersion.trim(), minimumVersion);
         } catch (RuntimeException ignored) {
             return false;
         }
@@ -69,6 +74,14 @@ public final class ServerCompatibility {
         return profile(coreVersion).supportsGraphSpace();
     }
 
+    public static boolean supportsCypher(String coreVersion) {
+        return profile(coreVersion).supportsCypher();
+    }
+
+    public static boolean supportsGraphCreate(String apiVersion) {
+        return supportsApi(apiVersion, GRAPH_CREATE_MIN_API_VERSION);
+    }
+
     public static boolean supportsDefaultRole(String coreVersion,
                                               String apiVersion) {
         return profile(coreVersion, apiVersion).supportsDefaultRole();
@@ -81,17 +94,19 @@ public final class ServerCompatibility {
     }
 
     public enum Profile {
-        LEGACY(false, false, false),
-        GRAPHSPACE(true, false, false),
-        MODERN(true, true, true);
+        LEGACY(false, false, false, false),
+        GRAPHSPACE(true, true, false, false),
+        MODERN(true, true, true, true);
 
         private final boolean graphSpace;
+        private final boolean cypher;
         private final boolean defaultRole;
         private final boolean personalProfileUpdate;
 
-        Profile(boolean graphSpace, boolean defaultRole,
+        Profile(boolean graphSpace, boolean cypher, boolean defaultRole,
                 boolean personalProfileUpdate) {
             this.graphSpace = graphSpace;
+            this.cypher = cypher;
             this.defaultRole = defaultRole;
             this.personalProfileUpdate = personalProfileUpdate;
         }
@@ -100,6 +115,10 @@ public final class ServerCompatibility {
             return this.graphSpace;
         }
 
+        public boolean supportsCypher() {
+            return this.cypher;
+        }
+
         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 577071974..783508326 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
@@ -25,14 +25,21 @@ public class ServerCompatibilityTest {
     @Test
     public void shouldKeepLegacyServersConservative() {
         Assert.assertFalse(ServerCompatibility.supportsGraphSpace("1.5.0"));
-        Assert.assertFalse(ServerCompatibility.supportsGraphSpace("1.6.0"));
+        Assert.assertFalse(ServerCompatibility.supportsCypher("1.5.0"));
         Assert.assertFalse(ServerCompatibility.supportsGraphSpace(null));
+        Assert.assertFalse(ServerCompatibility.supportsCypher(null));
+        Assert.assertFalse(ServerCompatibility.supportsGraphCreate(null));
         
Assert.assertFalse(ServerCompatibility.supportsGraphSpace("not-a-version"));
+        
Assert.assertFalse(ServerCompatibility.supportsCypher("not-a-version"));
+        
Assert.assertFalse(ServerCompatibility.supportsGraphCreate("not-a-version"));
+        Assert.assertFalse(ServerCompatibility.supportsGraphCreate("0.66"));
     }
 
     @Test
     public void shouldExposeGraphSpaceForModernServers() {
         Assert.assertTrue(ServerCompatibility.supportsGraphSpace("1.7.0"));
+        Assert.assertTrue(ServerCompatibility.supportsCypher("1.7.0"));
+        Assert.assertTrue(ServerCompatibility.supportsGraphCreate("0.67"));
         Assert.assertTrue(ServerCompatibility.supportsGraphSpace(" 1.7.0 "));
         Assert.assertTrue(ServerCompatibility.supportsGraphSpace("1.8.0"));
         Assert.assertFalse(ServerCompatibility.supportsDefaultRole(
diff --git a/hugegraph-hubble/AGENTS.md b/hugegraph-hubble/AGENTS.md
index 9f732a344..4793edafc 100644
--- a/hugegraph-hubble/AGENTS.md
+++ b/hugegraph-hubble/AGENTS.md
@@ -2,12 +2,12 @@
 
 ## Authentication and connection boundary
 
-The `1.8/master` path is the source of truth. Backend configuration exposes
-one `auth.enabled` switch and one connection resolver. The resolver chooses
-either a direct server URL or an address discovered from PD; callers must not
-reimplement `usePD` or infer connection state from page-local flags. In PD mode
-the server address returned by discovery is authoritative, so a manual server
-URL is not required.
+The `1.8/master` path is the source of truth. The backend detects 
authentication
+mode from HugeGraph Server and uses one connection resolver. The resolver
+chooses either a direct server URL or an address discovered from PD; callers
+must not reimplement `usePD` or infer connection state from page-local flags.
+In PD mode the server address returned by discovery is authoritative, so a
+manual server URL is not required.
 
 Use the unauthenticated HugeGraph client for anonymous mode. Do not manufacture
 an empty token or an administrator session. Anonymous mode has no account
diff --git a/hugegraph-hubble/README.md b/hugegraph-hubble/README.md
index 445b4ee20..fde297990 100644
--- a/hugegraph-hubble/README.md
+++ b/hugegraph-hubble/README.md
@@ -10,17 +10,12 @@ graph data load, schema management, graph relationship 
analysis, and graphical d
 ## Authentication, connections, and compatibility
 
 Hubble uses one capability-driven connection boundary for `1.8/master`.
-`auth.enabled=true` creates an authenticated session; when it is `false`, 
Hubble
-uses an unauthenticated client and does not create a fake user. Account and
-permission entry points are hidden in anonymous mode. Connection switching
-always goes through the backend resolver. In PD mode, a valid server address
-returned by discovery is sufficient; a manually configured server URL is not
-required.
-
-Container and orchestrated deployments can set `HUBBLE_AUTH_ENABLED=true` or
-`false`. This explicit runtime value overrides `auth.enabled` from the
-properties file, and invalid values fail startup instead of silently selecting
-an authentication mode.
+Authentication mode is detected from the connected HugeGraph Server; Hubble
+does not maintain a separate authentication switch. Account and permission
+entry points are hidden when Server allows anonymous access. Connection
+switching always goes through the backend resolver. In PD mode, a valid server
+address returned by discovery is sufficient; a manually configured server URL
+is not required.
 
 The UI presents four stable permission meanings: super administrator, 
GraphSpace
 read-only, GraphSpace read-write, and GraphSpace administrator. The last one
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java
index 63be9e458..66f94ed6a 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java
@@ -58,6 +58,13 @@ public final class Constant {
 
     public static final String TOKEN_KEY = "auth_token";
     public static final String USERNAME_KEY = "username";
+    /**
+     * Server-side-only legacy Gremlin credentials. 1.7's Gremlin HTTP
+     * channel accepts Basic auth while its REST APIs accept the login token.
+     */
+    public static final String PASSWORD_KEY = "auth_password";
+    public static final String PASSWORD_EXPIRE_AT_KEY =
+                               "auth_password_expire_at";
     public static final String GRAPHSPACE_ACCESS_KEY =
                                "validated_graphspace";
 
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java
index ea40f8668..8caad2787 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java
@@ -28,14 +28,10 @@ import org.springframework.context.annotation.Configuration;
 
 import java.io.File;
 import java.net.URL;
-import java.util.Locale;
-import java.util.Map;
 
 @Configuration
 public class HubbleConfig {
 
-    static final String AUTH_ENABLED_ENV = "HUBBLE_AUTH_ENABLED";
-
     @Autowired
     private ApplicationArguments arguments;
 
@@ -60,24 +56,6 @@ public class HubbleConfig {
                 conf = path;
             }
         }
-        HugeConfig config = new HugeConfig(conf);
-        applyEnvironmentOverrides(config, System.getenv());
-        return config;
-    }
-
-    static void applyEnvironmentOverrides(HugeConfig config,
-                                          Map<String, String> environment) {
-        String authEnabled = environment.get(AUTH_ENABLED_ENV);
-        if (authEnabled == null) {
-            return;
-        }
-
-        String normalized = authEnabled.trim().toLowerCase(Locale.ROOT);
-        if (!normalized.equals("true") && !normalized.equals("false")) {
-            throw new ExternalException(
-                      AUTH_ENABLED_ENV + " must be true or false");
-        }
-        config.setProperty(HubbleOptions.AUTH_ENABLED.name(),
-                           Boolean.valueOf(normalized));
+        return new HugeConfig(conf);
     }
 }
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 1f7634785..eb7cffa4f 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
@@ -21,6 +21,7 @@ package org.apache.hugegraph.controller;
 import java.util.List;
 import java.util.function.Function;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
 
 import org.apache.hugegraph.driver.HugeClient;
 import org.apache.hugegraph.driver.factory.PDHugeClientFactory;
@@ -143,6 +144,8 @@ public abstract class BaseController {
     protected void clearAuthSession() {
         this.delSession(Constant.TOKEN_KEY);
         this.delSession(Constant.USERNAME_KEY);
+        this.delSession(Constant.PASSWORD_KEY);
+        this.delSession(Constant.PASSWORD_EXPIRE_AT_KEY);
     }
 
     protected HugeClient authClient(String graphSpace, String graph) {
@@ -217,7 +220,52 @@ public abstract class BaseController {
     }
 
     protected HugeClient authGremlinClient(String graphSpace, String graph) {
-        return this.authClient(graphSpace, graph);
+        if (this.authMode != null && this.authMode.anonymous()) {
+            return this.authClient(graphSpace, graph);
+        }
+
+        HttpServletRequest request = this.getRequest();
+        HttpSession session = request.getSession(false);
+        if (session == null) {
+            return this.authClient(graphSpace, graph);
+        }
+
+        String username = (String) session.getAttribute(Constant.USERNAME_KEY);
+        String token = (String) session.getAttribute(Constant.TOKEN_KEY);
+        String password = this.validSessionPassword(session);
+        if (!StringUtils.hasText(username) || !StringUtils.hasText(token) ||
+            !StringUtils.hasText(password)) {
+            return this.authClient(graphSpace, graph);
+        }
+
+        Object existing = request.getAttribute("hugeClient");
+        if (existing instanceof HugeClient) {
+            ((HugeClient) existing).close();
+        }
+        HugeClient client = this.createBasicClient(graphSpace, graph,
+                                                   username, password);
+        this.requireGraphSpaceAccess(client, graphSpace);
+        request.setAttribute("hugeClient", client);
+        return client;
+    }
+
+    protected HugeClient createBasicClient(String graphSpace, String graph,
+                                           String username, String password) {
+        return this.hugeClientPoolService.createBasicClient(
+                    graphSpace, graph, username, password);
+    }
+
+    private String validSessionPassword(HttpSession session) {
+        Object password = session.getAttribute(Constant.PASSWORD_KEY);
+        Object expiresAt = session.getAttribute(
+                           Constant.PASSWORD_EXPIRE_AT_KEY);
+        if (!(password instanceof String) || !(expiresAt instanceof Number) ||
+            System.currentTimeMillis() >= ((Number) expiresAt).longValue()) {
+            session.removeAttribute(Constant.PASSWORD_KEY);
+            session.removeAttribute(Constant.PASSWORD_EXPIRE_AT_KEY);
+            return null;
+        }
+        return (String) password;
     }
 
     protected HugeClient unauthClient() {
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ConfigController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ConfigController.java
index 8f8f5cb6c..8ee22aa1c 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ConfigController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ConfigController.java
@@ -23,7 +23,10 @@ import java.util.Map;
 
 import org.apache.hugegraph.common.Constant;
 import org.apache.hugegraph.config.HugeConfig;
+import org.apache.hugegraph.driver.HugeClient;
 import org.apache.hugegraph.options.HubbleOptions;
+import org.apache.hugegraph.service.HugeClientPoolService;
+import org.apache.hugegraph.service.auth.AuthModeService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -33,14 +36,57 @@ import 
org.springframework.web.bind.annotation.RestController;
 @RequestMapping(Constant.API_VERSION + "config")
 public class ConfigController {
 
+    private static final int CAPABILITY_PROBE_TIMEOUT_SECONDS = 3;
+
     @Autowired
     private HugeConfig config;
 
+    @Autowired(required = false)
+    private HugeClientPoolService hugeClientPoolService;
+
+    @Autowired
+    private AuthModeService authModeService;
+
     @GetMapping
     public Map<String, Object> getConfig() {
         Map<String, Object> result = new HashMap<>();
         result.put("pd_enabled", config.get(HubbleOptions.PD_ENABLED));
-        result.put("auth_enabled", config.get(HubbleOptions.AUTH_ENABLED));
+        result.putAll(this.serverCapabilities());
         return result;
     }
+
+    private Map<String, Object> serverCapabilities() {
+        Map<String, Object> capabilities = new HashMap<>();
+        boolean pdEnabled = config.get(HubbleOptions.PD_ENABLED);
+        if (pdEnabled) {
+            capabilities.put("auth_enabled", this.authModeService.enabled());
+            capabilities.put("graph_create_enabled", true);
+            capabilities.put("cypher_enabled", true);
+            return capabilities;
+        }
+        capabilities.put("auth_enabled", true);
+        capabilities.put("graph_create_enabled", false);
+        capabilities.put("cypher_enabled", false);
+        if (hugeClientPoolService == null) {
+            return capabilities;
+        }
+
+        try (HugeClient client = this.createUnauthClient()) {
+            capabilities.put("auth_enabled",
+                             this.authModeService.update(
+                             client.isServerAuthEnabled()));
+            capabilities.put("graph_create_enabled",
+                             client.supportsGraphCreate());
+            capabilities.put("cypher_enabled", client.supportsCypher());
+            return capabilities;
+        } catch (RuntimeException ignored) {
+            // Keep bootstrap resilient when the Server is temporarily 
unavailable.
+            return capabilities;
+        }
+    }
+
+    protected HugeClient createUnauthClient() {
+        return this.hugeClientPoolService.createUnauthClient(
+               CAPABILITY_PROBE_TIMEOUT_SECONDS);
+    }
 }
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java
index c50d77ab0..05408442d 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java
@@ -95,7 +95,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult rings(@PathVariable("graphspace") String graphSpace,
                                @PathVariable("graph") String graph,
                                @RequestBody RingsEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.rings(client, body);
     }
 
@@ -103,7 +103,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult advancedPaths(@PathVariable("graphspace") String 
graphSpace,
                                        @PathVariable("graph") String graph,
                                        @RequestBody PathsRequest body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.advancedpaths(client, body);
     }
 
@@ -111,7 +111,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult sameNeighbors(@PathVariable("graphspace") String 
graphSpace,
                                        @PathVariable("graph") String graph,
                                        @RequestBody SameNeighborsEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.sameNeighbors(client, body);
     }
 
@@ -119,7 +119,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult kout(@PathVariable("graphspace") String graphSpace,
                               @PathVariable("graph") String graph,
                               @RequestBody KoutEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.kout(client, body);
     }
 
@@ -127,7 +127,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult koutPost(@PathVariable("graphspace") String 
graphSpace,
                                   @PathVariable("graph") String graph,
                                   @RequestBody KoutRequest body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.koutPost(client, body);
     }
 
@@ -135,7 +135,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult kneighbor(@PathVariable("graphspace") String 
graphSpace,
                                    @PathVariable("graph") String graph,
                                    @RequestBody KneighborEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.kneighbor(client, body);
     }
 
@@ -143,7 +143,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult kneighborPost(@PathVariable("graphspace") String 
graphSpace,
                                        @PathVariable("graph") String graph,
                                        @RequestBody KneighborRequest body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.kneighborPost(client, body);
     }
 
@@ -151,7 +151,7 @@ public class OltpAlgoController extends BaseController {
     public JaccardsimilarityView jaccardSimilarity(@PathVariable("graphspace") 
String graphSpace,
                                                    @PathVariable("graph") 
String graph,
                                                    @RequestBody 
JaccardSimilarityEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.jaccardSimilarity(client, body);
     }
 
@@ -160,7 +160,7 @@ public class OltpAlgoController extends BaseController {
             @PathVariable("graphspace") String graphSpace,
             @PathVariable("graph") String graph,
             @RequestBody SingleSourceJaccardSimilarityRequest body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.jaccardSimilarityPost(client, body);
     }
 
@@ -168,7 +168,7 @@ public class OltpAlgoController extends BaseController {
     public RanksView personalRank(@PathVariable("graphspace") String 
graphSpace,
                                   @PathVariable("graph") String graph,
                                   @RequestBody PersonalRankAPI.Request body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.personalRank(client, body);
     }
 
@@ -176,7 +176,7 @@ public class OltpAlgoController extends BaseController {
     public RanksView neighborRank(@PathVariable("graphspace") String 
graphSpace,
                                   @PathVariable("graph") String graph,
                                   @RequestBody NeighborRankAPI.Request body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.neighborRank(client, body);
     }
 
@@ -184,7 +184,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult allShortPaths(@PathVariable("graphspace") String 
graphSpace,
                                        @PathVariable("graph") String graph,
                                        @RequestBody AllShortestPathsEntity 
body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.allShortestPaths(client, body);
     }
 
@@ -199,7 +199,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult weightedShortestPath(@PathVariable("graphspace") 
String graphSpace,
                                               @PathVariable("graph") String 
graph,
                                               @RequestBody 
WeightedShortestPathEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.weightedShortestPath(client, body);
     }
 
@@ -208,7 +208,7 @@ public class OltpAlgoController extends BaseController {
             @PathVariable("graphspace") String graphSpace,
             @PathVariable("graph") String graph,
             @RequestBody SingleSourceShortestPathEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.singleSourceShortestPath(client, body);
     }
 
@@ -216,7 +216,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult multiNodeShortestPath(@PathVariable("graphspace") 
String graphSpace,
                                                @PathVariable("graph") String 
graph,
                                                @RequestBody 
MultiNodeShortestPathRequest body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.multiNodeShortestPath(client, body);
     }
 
@@ -224,7 +224,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult paths(@PathVariable("graphspace") String graphSpace,
                                @PathVariable("graph") String graph,
                                @RequestBody PathsEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.paths(client, body);
     }
 
@@ -232,7 +232,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult customizedPaths(@PathVariable("graphspace") String 
graphSpace,
                                          @PathVariable("graph") String graph,
                                          @RequestBody CustomizedPathsRequest 
body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.customizedPaths(client, body);
     }
 
@@ -240,7 +240,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult templatePaths(@PathVariable("graphspace") String 
graphSpace,
                                        @PathVariable("graph") String graph,
                                        @RequestBody TemplatePathsRequest body) 
{
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.templatePaths(client, body);
     }
 
@@ -248,7 +248,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult crosspoints(@PathVariable("graphspace") String 
graphSpace,
                                      @PathVariable("graph") String graph,
                                      @RequestBody CrossPointsEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.crosspoints(client, body);
     }
 
@@ -256,7 +256,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult customizedcrosspoints(@PathVariable("graphspace") 
String graphSpace,
                                                @PathVariable("graph") String 
graph,
                                                @RequestBody CrosspointsRequest 
body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.customizedcrosspoints(client, body);
     }
 
@@ -264,7 +264,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult rays(@PathVariable("graphspace") String graphSpace,
                               @PathVariable("graph") String graph,
                               @RequestBody RaysEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.rays(client, body);
     }
 
@@ -272,7 +272,7 @@ public class OltpAlgoController extends BaseController {
     public FusiformsimilarityView 
fusiformsimilarity(@PathVariable("graphspace") String graphSpace,
                                                      @PathVariable("graph") 
String graph,
                                                      @RequestBody 
FusiformSimilarityRequest body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.fusiformsimilarity(client, body);
     }
 
@@ -280,7 +280,7 @@ public class OltpAlgoController extends BaseController {
     public Map<String, Double> adamicadar(@PathVariable("graphspace") String 
graphSpace,
                                           @PathVariable("graph") String graph,
                                           @RequestBody AdamicadarEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.adamicadar(client, body);
     }
 
@@ -288,7 +288,7 @@ public class OltpAlgoController extends BaseController {
     public Map<String, Double> resourceallocation(@PathVariable("graphspace") 
String graphSpace,
                                                   @PathVariable("graph") 
String graph,
                                                   @RequestBody 
ResourceallocationEntity body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.resourceallocation(client, body);
     }
 
@@ -296,7 +296,7 @@ public class OltpAlgoController extends BaseController {
     public GremlinResult sameneighborsbatch(@PathVariable("graphspace") String 
graphSpace,
                                             @PathVariable("graph") String 
graph,
                                             @RequestBody 
SameNeighborsBatchRequest body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.sameneighborsbatch(client, body);
     }
 
@@ -304,7 +304,7 @@ public class OltpAlgoController extends BaseController {
     public EgonetView egonet(@PathVariable("graphspace") String graphSpace,
                              @PathVariable("graph") String graph,
                              @RequestBody EgonetRequest body) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         return this.service.egonet(client, body);
     }
 }
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 c5a51e226..955909cf4 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
@@ -104,6 +104,13 @@ public class LoginController extends BaseController {
             this.getRequest().changeSessionId();
             this.setUser(login.name());
             this.setToken(result.token());
+            // HugeGraph 1.7's Gremlin HTTP channel only accepts Basic auth.
+            // Keep the credential server-side for the session lifetime so
+            // graph queries can use the same identity as REST requests.
+            this.setSession(Constant.PASSWORD_KEY, login.password());
+            this.setSession(Constant.PASSWORD_EXPIRE_AT_KEY,
+                            System.currentTimeMillis() +
+                            TOKEN_EXPIRE_SECONDS * 1000L);
             return user;
         } catch (Throwable e) {
             this.clearAuthSession();
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/UserController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/UserController.java
index 55ff8e9a3..a0c9d2495 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/UserController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/UserController.java
@@ -131,7 +131,13 @@ public class UserController extends BaseController {
                     "Permission denied: change another account password");
         }
         HugeClient client = this.authClient(null, null);
-        return userService.updatepwd(client, pwd.getUsername(), 
pwd.getOldpwd(), pwd.getNewpwd());
+        Response response = userService.updatepwd(client, pwd.getUsername(),
+                                                  pwd.getOldpwd(),
+                                                  pwd.getNewpwd());
+        if (response.getStatus() == Constant.STATUS_OK) {
+            this.clearAuthSession();
+        }
+        return response;
     }
 
     @GetMapping("listadminspace/{username}")
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/langchain/LangChainController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/langchain/LangChainController.java
index 8c1695fe0..3e39a0eb8 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/langchain/LangChainController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/langchain/LangChainController.java
@@ -184,7 +184,7 @@ public class LangChainController extends BaseController {
                 requestLangChainParams.userName, 
requestLangChainParams.password);
 
         try {
-            HugeClient client = this.authClient(graphSpace, graph);
+            HugeClient client = this.authGremlinClient(graphSpace, graph);
             JsonView result =
                     this.queryService.executeSingleGremlinQuery(client, query);
             return result.getData();
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/saas/SaasGraphViewController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/saas/SaasGraphViewController.java
index fd34bef72..b06c6ac9b 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/saas/SaasGraphViewController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/saas/SaasGraphViewController.java
@@ -84,7 +84,7 @@ public class SaasGraphViewController extends 
GremlinController {
         StopWatch timer = StopWatch.createStarted();
 
         try {
-            HugeClient client = this.authClient(graphSpace, graph);
+            HugeClient client = this.authGremlinClient(graphSpace, graph);
             GremlinResult result =
                     this.queryService.executeGremlinQuery(client,
                                                           
query.convert2GremlinQuery());
@@ -124,7 +124,7 @@ public class SaasGraphViewController extends 
GremlinController {
         Map<String, Object> result = new HashMap<>(3);
 
         try {
-            HugeClient client = this.authClient(graphSpace, graph);
+            HugeClient client = this.authGremlinClient(graphSpace, graph);
             asyncId = this.queryService.executeGremlinAsyncTask(client,
                                                                 
query.convert2GremlinQuery());
             status = ExecuteStatus.ASYNC_TASK_SUCCESS;
diff --git 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/schema/SchemaController.java
 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/schema/SchemaController.java
index 58009368b..d209e8733 100644
--- 
a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/schema/SchemaController.java
+++ 
b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/schema/SchemaController.java
@@ -91,7 +91,7 @@ public class SchemaController extends BaseController {
     public Object addSchemaGroovy(@PathVariable("graphspace") String 
graphSpace,
                                   @PathVariable("graph") String graph,
                                   @RequestBody SchemaGroovy schemaGroovy) {
-        HugeClient client = this.authClient(graphSpace, graph);
+        HugeClient client = this.authGremlinClient(graphSpace, graph);
         String content = schemaGroovy.getSchemaGroovy();
         log.info("Add schema groovy: {}", content);
         checkSchemaGroovy(content);
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 56b544dc3..c04132c32 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
@@ -279,14 +279,6 @@ public class HubbleOptions extends OptionHolder {
                     true
             );
 
-    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.",
-                    null,
-                    true
-            );
-
     public static final ConfigOption<String> SERVER_URL =
             new ConfigOption<>(
                     "server.direct_url",
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 167f7f5c0..bb92abc6e 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(int timeout) {
+        return this.create(null, null, null, null, null, null, timeout);
+    }
+
     public HugeClient createUnauthClient(String graphSpace, String graph) {
         return getOrCreate(null, graphSpace, graph, null);
     }
@@ -132,6 +136,13 @@ public final class HugeClientPoolService {
 
     private HugeClient create(String url, String graphSpace, String graph,
             String token, String username, String password) {
+        return this.create(url, graphSpace, graph, token, username, password,
+                           null);
+    }
+
+    private HugeClient create(String url, String graphSpace, String graph,
+            String token, String username, String password,
+            Integer timeoutOverride) {
         if (StringUtils.isEmpty(url)) {
             List<String> urls = this.allAvailableURLs(graphSpace, graph);
 
@@ -143,8 +154,9 @@ public final class HugeClientPoolService {
                 if (StringUtils.isEmpty(tmpurl)) {
                     continue;
                 }
-                HugeClient tmpclient = this.create(tmpurl, graphSpace, graph, 
token,
-                                                   username, password);
+                HugeClient tmpclient = this.create(tmpurl, graphSpace, graph,
+                                                   token, username, password,
+                                                   timeoutOverride);
 
                 if (checkHealth(tmpclient)) {
                     return tmpclient;
@@ -171,7 +183,9 @@ public final class HugeClientPoolService {
         connection.setGraphSpace(graphSpace);
         connection.setGraph(graph);
         if (connection.getTimeout() == null) {
-            int timeout = 
this.config.get(HubbleOptions.CLIENT_REQUEST_TIMEOUT);
+            int timeout = timeoutOverride != null ?
+                          timeoutOverride :
+                          
this.config.get(HubbleOptions.CLIENT_REQUEST_TIMEOUT);
             connection.setTimeout(timeout);
         }
         this.sslService.configSSL(this.config, connection);
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 80ad33a69..cc2d45589 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
@@ -84,16 +84,18 @@ public class AuthContextService {
 
     private final HugeConfig config;
     private final UserService users;
+    private final AuthModeService authMode;
 
     @Autowired
-    public AuthContextService(HugeConfig config, UserService users) {
+    public AuthContextService(HugeConfig config, UserService users,
+                              AuthModeService authMode) {
         this.config = config;
         this.users = users;
+        this.authMode = authMode;
     }
 
     public Map<String, Object> context(HugeClient client, String username) {
-        if (Boolean.FALSE.equals(
-                this.config.get(HubbleOptions.AUTH_ENABLED))) {
+        if (this.authMode.anonymous()) {
             return anonymousContext(this.config.get(HubbleOptions.PD_ENABLED));
         }
         boolean pdEnabled = this.config.get(HubbleOptions.PD_ENABLED);
@@ -149,8 +151,7 @@ public class AuthContextService {
 
     public void requireGraphSpaceWrite(HugeClient client, String username,
                                        String graphSpace) {
-        if (Boolean.FALSE.equals(
-                this.config.get(HubbleOptions.AUTH_ENABLED)) ||
+        if (this.authMode.anonymous() ||
             !this.config.get(HubbleOptions.PD_ENABLED) ||
             !client.supportsDefaultRole()) {
             return;
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 f4f8c8230..ad0382fce 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
@@ -18,8 +18,10 @@
 
 package org.apache.hugegraph.service.auth;
 
-import org.apache.hugegraph.config.HugeConfig;
-import org.apache.hugegraph.options.HubbleOptions;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.service.HugeClientPoolService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -28,20 +30,50 @@ import org.springframework.stereotype.Service;
  * not infer mode from sessions or PD settings.
  */
 @Service
-public final class AuthModeService {
+public class AuthModeService {
+
+    private static final int PROBE_TIMEOUT_SECONDS = 3;
+    private static final long CACHE_NANOS = TimeUnit.SECONDS.toNanos(30L);
 
-    private final HugeConfig config;
+    private final HugeClientPoolService hugeClientPoolService;
+    private volatile Boolean serverAuthEnabled;
+    private volatile long detectedAt;
 
     @Autowired
-    public AuthModeService(HugeConfig config) {
-        this.config = config;
+    public AuthModeService(HugeClientPoolService hugeClientPoolService) {
+        this.hugeClientPoolService = hugeClientPoolService;
     }
 
     public boolean enabled() {
-        return 
!Boolean.FALSE.equals(this.config.get(HubbleOptions.AUTH_ENABLED));
+        Boolean cached = this.serverAuthEnabled;
+        long now = System.nanoTime();
+        if (cached != null && now - this.detectedAt < CACHE_NANOS) {
+            return cached;
+        }
+        try (HugeClient client = this.createUnauthClient()) {
+            return this.update(client.isServerAuthEnabled(), now);
+        } catch (RuntimeException ignored) {
+            // Fail closed while Server state is unavailable.
+            return this.update(true, now);
+        }
     }
 
     public boolean anonymous() {
         return !this.enabled();
     }
+
+    public boolean update(boolean enabled) {
+        return this.update(enabled, System.nanoTime());
+    }
+
+    private boolean update(boolean enabled, long detectedAt) {
+        this.serverAuthEnabled = enabled;
+        this.detectedAt = detectedAt;
+        return enabled;
+    }
+
+    protected HugeClient createUnauthClient() {
+        return this.hugeClientPoolService.createUnauthClient(
+               PROBE_TIMEOUT_SECONDS);
+    }
 }
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/config/HubbleConfigEnvironmentTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/config/HubbleConfigEnvironmentTest.java
deleted file mode 100644
index e15649fce..000000000
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/config/HubbleConfigEnvironmentTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.hugegraph.config;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.hugegraph.exception.ExternalException;
-import org.apache.hugegraph.options.HubbleOptions;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class HubbleConfigEnvironmentTest {
-
-    @Test
-    public void testAuthModeCanBeOverriddenByEnvironment() {
-        HugeConfig config = config(true);
-
-        HubbleConfig.applyEnvironmentOverrides(
-                     config, Map.of("HUBBLE_AUTH_ENABLED", "false"));
-
-        Assert.assertFalse(config.get(HubbleOptions.AUTH_ENABLED));
-    }
-
-    @Test
-    public void testMissingEnvironmentKeepsFileValue() {
-        HugeConfig config = config(false);
-
-        HubbleConfig.applyEnvironmentOverrides(config, Collections.emptyMap());
-
-        Assert.assertFalse(config.get(HubbleOptions.AUTH_ENABLED));
-    }
-
-    @Test(expected = ExternalException.class)
-    public void testAuthModeRejectsInvalidEnvironmentValue() {
-        HugeConfig config = config(true);
-
-        HubbleConfig.applyEnvironmentOverrides(
-                     config, Map.of("HUBBLE_AUTH_ENABLED", "disabled"));
-    }
-
-    private static HugeConfig config(boolean authEnabled) {
-        if (!OptionSpace.containKey(HubbleOptions.AUTH_ENABLED.name())) {
-            OptionSpace.register("hubble-environment-test",
-                                 HubbleOptions.instance());
-        }
-        Map<String, Object> properties = new HashMap<>();
-        properties.put(HubbleOptions.AUTH_ENABLED.name(), authEnabled);
-        return new HugeConfig(properties);
-    }
-}
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/AccountMutationAuthorizationTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/AccountMutationAuthorizationTest.java
index 3eabedb0a..412edbfcc 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/AccountMutationAuthorizationTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/auth/AccountMutationAuthorizationTest.java
@@ -22,6 +22,7 @@ import java.util.Arrays;
 import java.util.Collections;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -32,6 +33,8 @@ import 
org.springframework.test.web.servlet.setup.MockMvcBuilders;
 import org.springframework.web.multipart.MultipartFile;
 
 import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.common.Response;
 import org.apache.hugegraph.driver.HugeClient;
 import org.apache.hugegraph.entity.auth.UserEntity;
 import org.apache.hugegraph.entity.auth.UserView;
@@ -111,9 +114,15 @@ public class AccountMutationAuthorizationTest {
                                            .oldpwd("old")
                                            .newpwd("new")
                                            .build();
+        Mockito.when(this.authorizationService.updatepwd(
+                     this.client, "alice", "old", "new"))
+               .thenReturn(Response.builder()
+                                   .status(Constant.STATUS_OK)
+                                   .build());
         controller.updatepwd(own);
         Mockito.verify(this.authorizationService)
                .updatepwd(this.client, "alice", "old", "new");
+        Assert.assertTrue(controller.authSessionCleared());
     }
 
     @Test
@@ -605,6 +614,7 @@ public class AccountMutationAuthorizationTest {
 
         private final HugeClient client;
         private final String username;
+        private boolean authSessionCleared;
 
         TestUserController(HugeClient client, String username) {
             this.client = client;
@@ -620,6 +630,15 @@ public class AccountMutationAuthorizationTest {
         protected String getUser() {
             return this.username;
         }
+
+        @Override
+        protected void clearAuthSession() {
+            this.authSessionCleared = true;
+        }
+
+        boolean authSessionCleared() {
+            return this.authSessionCleared;
+        }
     }
 
     private static class TestGraphSpaceUserController
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/ingest/IngestControllerTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/ingest/IngestControllerTest.java
index e1c9841db..d3f765bfd 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/ingest/IngestControllerTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/ingest/IngestControllerTest.java
@@ -377,10 +377,10 @@ public class IngestControllerTest {
            throws Exception {
         TestIngestController controller = new TestIngestController();
         HugeConfig config = Mockito.mock(HugeConfig.class);
-        Mockito.when(config.get(HubbleOptions.AUTH_ENABLED))
-               .thenReturn(false);
         Mockito.when(config.get(HubbleOptions.PD_ENABLED))
                .thenReturn(true);
+        AuthModeService authMode = Mockito.mock(AuthModeService.class);
+        Mockito.when(authMode.anonymous()).thenReturn(true);
         GraphSpaceService graphSpaces = Mockito.mock(GraphSpaceService.class);
         JobManagerService jobs = Mockito.mock(JobManagerService.class);
         LoadTaskService loadTasks = Mockito.mock(LoadTaskService.class);
@@ -402,7 +402,7 @@ public class IngestControllerTest {
         Mockito.when(graphSpaces.listAnonymous(Mockito.any()))
                .thenReturn(Collections.singletonList("public"));
         this.setField(controller, "config", config);
-        this.setField(controller, "authMode", new AuthModeService(config));
+        this.setField(controller, "authMode", authMode);
         this.setField(controller, "graphSpaceAccessService", graphSpaces);
         this.setField(controller, "jobManagerService", jobs);
         this.setField(controller, "loadTaskService", loadTasks);
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/space/GraphSpaceControllerTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/space/GraphSpaceControllerTest.java
index 8280a659e..e12a24d91 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/space/GraphSpaceControllerTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/space/GraphSpaceControllerTest.java
@@ -118,8 +118,8 @@ public class GraphSpaceControllerTest {
                 new TestGraphSpaceController(client);
         HugeConfig config = Mockito.mock(HugeConfig.class);
         Mockito.when(config.get(HubbleOptions.PD_ENABLED)).thenReturn(true);
-        Mockito.when(config.get(HubbleOptions.AUTH_ENABLED)).thenReturn(false);
-        AuthModeService authMode = new AuthModeService(config);
+        AuthModeService authMode = Mockito.mock(AuthModeService.class);
+        Mockito.when(authMode.anonymous()).thenReturn(true);
         Mockito.when(graphSpaceService.listAnonymous(client))
                .thenReturn(java.util.Collections.singletonList("public"));
         Mockito.when(graphSpaceService.getAnonymous(client, "public"))
@@ -270,9 +270,10 @@ public class GraphSpaceControllerTest {
                                               client, userService,
                                               graphSpaceService);
         HugeConfig config = Mockito.mock(HugeConfig.class);
-        Mockito.when(config.get(HubbleOptions.AUTH_ENABLED)).thenReturn(false);
+        AuthModeService authMode = Mockito.mock(AuthModeService.class);
+        Mockito.when(authMode.anonymous()).thenReturn(true);
         ReflectionTestUtils.setField(controller, "authMode",
-                                     new AuthModeService(config));
+                                     authMode);
 
         assertForbidden(() -> controller.add(new GraphSpaceEntity()));
 
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/space/SchemaTemplateControllerSecurityTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/space/SchemaTemplateControllerSecurityTest.java
index aa31149ad..9a9c50164 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/space/SchemaTemplateControllerSecurityTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/controller/space/SchemaTemplateControllerSecurityTest.java
@@ -142,10 +142,11 @@ public class SchemaTemplateControllerSecurityTest {
         TestController controller = new TestController();
         HugeConfig config = Mockito.mock(HugeConfig.class);
         Mockito.when(config.get(HubbleOptions.PD_ENABLED)).thenReturn(true);
-        Mockito.when(config.get(HubbleOptions.AUTH_ENABLED))
-               .thenReturn(authEnabled);
         controller.config = config;
-        controller.setAuthMode(new AuthModeService(config));
+        AuthModeService authMode = Mockito.mock(AuthModeService.class);
+        Mockito.when(authMode.enabled()).thenReturn(authEnabled);
+        Mockito.when(authMode.anonymous()).thenReturn(!authEnabled);
+        controller.setAuthMode(authMode);
         controller.schemaTemplateService =
                 Mockito.mock(SchemaTemplateService.class);
         return controller;
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 1091906ff..a697a84bb 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
@@ -256,8 +256,7 @@ public class AuthContextServiceTest {
     @Test
     public void testAnonymousModeGetsReadOnlyOperationsCapabilities() {
         Fixture fixture = new Fixture(true);
-        Mockito.when(fixture.config.get(HubbleOptions.AUTH_ENABLED))
-               .thenReturn(false);
+        Mockito.when(fixture.authMode.anonymous()).thenReturn(true);
 
         Map<String, Object> context = fixture.service.context(fixture.client,
                                                               null);
@@ -341,6 +340,8 @@ public class AuthContextServiceTest {
         private final HugeClient client = Mockito.mock(HugeClient.class);
         private final HugeConfig config = Mockito.mock(HugeConfig.class);
         private final UserService users = Mockito.mock(UserService.class);
+        private final AuthModeService authMode =
+                Mockito.mock(AuthModeService.class);
         private final AuthContextService service;
 
         private Fixture(boolean pdEnabled) {
@@ -348,7 +349,8 @@ public class AuthContextServiceTest {
                    .thenReturn(pdEnabled);
             Mockito.when(this.client.supportsPersonalProfileUpdate())
                    .thenReturn(true);
-            this.service = new AuthContextService(this.config, this.users);
+            this.service = new AuthContextService(this.config, this.users,
+                                                  this.authMode);
         }
     }
 }
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/AuthModeServiceTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/AuthModeServiceTest.java
new file mode 100644
index 000000000..79a53cf92
--- /dev/null
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/service/auth/AuthModeServiceTest.java
@@ -0,0 +1,55 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.service.auth;
+
+import org.apache.hugegraph.driver.HugeClient;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class AuthModeServiceTest {
+
+    @Test
+    public void testUsesDetectedServerAuthState() {
+        HugeClient client = Mockito.mock(HugeClient.class);
+        Mockito.when(client.isServerAuthEnabled()).thenReturn(false);
+        AuthModeService service = new AuthModeService(null) {
+            @Override
+            protected HugeClient createUnauthClient() {
+                return client;
+            }
+        };
+
+        Assert.assertFalse(service.enabled());
+        Assert.assertTrue(service.anonymous());
+        Mockito.verify(client).isServerAuthEnabled();
+    }
+
+    @Test
+    public void testFailsClosedWhenServerCannotBeProbed() {
+        AuthModeService service = new AuthModeService(null) {
+            @Override
+            protected HugeClient createUnauthClient() {
+                throw new IllegalStateException("unavailable");
+            }
+        };
+
+        Assert.assertTrue(service.enabled());
+    }
+}
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 34594952d..86b837b5e 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
@@ -187,10 +187,8 @@ public class AuthSecurityTest {
     @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);
+        AuthModeService mode = Mockito.mock(AuthModeService.class);
+        Mockito.when(mode.anonymous()).thenReturn(true);
         ReflectionTestUtils.setField(interceptor, "authMode", mode);
 
         MockHttpServletRequest users = new MockHttpServletRequest("GET", 
"/api/v1.3/auth/users");
@@ -246,8 +244,9 @@ public class AuthSecurityTest {
                 new ServletRequestAttributes(request));
         TestBaseController controller = new TestBaseController();
         HugeConfig config = Mockito.mock(HugeConfig.class);
-        Mockito.when(config.get(HubbleOptions.AUTH_ENABLED)).thenReturn(false);
         Mockito.when(config.get(HubbleOptions.PD_ENABLED)).thenReturn(true);
+        AuthModeService mode = Mockito.mock(AuthModeService.class);
+        Mockito.when(mode.anonymous()).thenReturn(true);
         GraphSpaceService spaces = Mockito.mock(GraphSpaceService.class);
         HugeClient client = Mockito.mock(HugeClient.class);
         Mockito.when(spaces.requirePublicSpace(client, "protected"))
@@ -255,7 +254,7 @@ public class AuthSecurityTest {
                                                 "unavailable"));
         ReflectionTestUtils.setField(controller, "config", config);
         ReflectionTestUtils.setField(controller, "authMode",
-                                     new AuthModeService(config));
+                                     mode);
         ReflectionTestUtils.setField(controller, "graphSpaceAccessService",
                                      spaces);
 
@@ -396,6 +395,9 @@ public class AuthSecurityTest {
                                             "/api/v1.3/graphspaces/space1");
         request.getSession().setAttribute(Constant.TOKEN_KEY, "token");
         request.getSession().setAttribute(Constant.USERNAME_KEY, "admin");
+        request.getSession().setAttribute(Constant.PASSWORD_KEY, "secret");
+        request.getSession().setAttribute(Constant.PASSWORD_EXPIRE_AT_KEY,
+                                          System.currentTimeMillis() + 10000L);
 
         Assert.assertTrue(interceptor.preHandle(request,
                                                 new MockHttpServletResponse(),
@@ -451,6 +453,27 @@ public class AuthSecurityTest {
         Mockito.verify(spaces).requireAccessibleSpace(null, "space1");
     }
 
+    @Test
+    public void testCustomInterceptorKeepsBearerForRestWithLegacyPassword()
+           throws Exception {
+        TestCustomInterceptor interceptor = new TestCustomInterceptor();
+        MockHttpServletRequest request = new MockHttpServletRequest(
+                                            "GET",
+                                            
"/api/v1.3/auth/users/getpersonal");
+        request.getSession().setAttribute(Constant.TOKEN_KEY, "token");
+        request.getSession().setAttribute(Constant.USERNAME_KEY, "admin");
+        request.getSession().setAttribute(Constant.PASSWORD_KEY, "secret");
+        request.getSession().setAttribute(Constant.PASSWORD_EXPIRE_AT_KEY,
+                                          System.currentTimeMillis() + 10000L);
+
+        Assert.assertTrue(interceptor.preHandle(request,
+                                                new MockHttpServletResponse(),
+                                                null));
+
+        Assert.assertEquals(1, interceptor.authClients);
+        Assert.assertEquals("token", interceptor.token);
+    }
+
     @Test
     public void testCustomInterceptorKeepsGraphCollectionActionsUnscoped()
            throws Exception {
@@ -513,11 +536,10 @@ public class AuthSecurityTest {
     public void testAnonymousClientUsesGraphSpaceScope() throws Exception {
         TestCustomInterceptor interceptor = new TestCustomInterceptor();
         HugeConfig config = Mockito.mock(HugeConfig.class);
-        Mockito.when(config.get(HubbleOptions.AUTH_ENABLED))
-               .thenReturn(false);
         Mockito.when(config.get(HubbleOptions.PD_ENABLED))
                .thenReturn(true);
-        AuthModeService mode = new AuthModeService(config);
+        AuthModeService mode = Mockito.mock(AuthModeService.class);
+        Mockito.when(mode.anonymous()).thenReturn(true);
         ReflectionTestUtils.setField(interceptor, "authMode", mode);
         ReflectionTestUtils.setField(interceptor, "config", config);
         GraphSpaceService spaces = Mockito.mock(GraphSpaceService.class);
@@ -537,12 +559,12 @@ public class AuthSecurityTest {
            throws Exception {
         TestCustomInterceptor interceptor = new TestCustomInterceptor();
         HugeConfig config = Mockito.mock(HugeConfig.class);
-        Mockito.when(config.get(HubbleOptions.AUTH_ENABLED))
-               .thenReturn(false);
         Mockito.when(config.get(HubbleOptions.PD_ENABLED))
                .thenReturn(true);
+        AuthModeService mode = Mockito.mock(AuthModeService.class);
+        Mockito.when(mode.anonymous()).thenReturn(true);
         ReflectionTestUtils.setField(interceptor, "authMode",
-                                     new AuthModeService(config));
+                                     mode);
         ReflectionTestUtils.setField(interceptor, "config", config);
         GraphSpaceService spaces = Mockito.mock(GraphSpaceService.class);
         Mockito.doThrow(new ExternalException(HttpStatus.NOT_FOUND.value(),
@@ -745,6 +767,9 @@ public class AuthSecurityTest {
 
         
Assert.assertNull(request.getSession().getAttribute(Constant.TOKEN_KEY));
         
Assert.assertNull(request.getSession().getAttribute(Constant.USERNAME_KEY));
+        
Assert.assertNull(request.getSession().getAttribute(Constant.PASSWORD_KEY));
+        Assert.assertNull(request.getSession().getAttribute(
+                          Constant.PASSWORD_EXPIRE_AT_KEY));
     }
 
     @Test
@@ -814,9 +839,11 @@ public class AuthSecurityTest {
                             Constant.USERNAME_KEY));
         Assert.assertEquals("server-token", request.getSession().getAttribute(
                             Constant.TOKEN_KEY));
-        Assert.assertNull(request.getSession().getAttribute("auth_password"));
-        Assert.assertNull(request.getSession().getAttribute(
-                          "auth_password_expire_at"));
+        Assert.assertEquals("pa", request.getSession().getAttribute(
+                            Constant.PASSWORD_KEY));
+        Assert.assertTrue(((Number) request.getSession().getAttribute(
+                           Constant.PASSWORD_EXPIRE_AT_KEY)).longValue() >
+                          System.currentTimeMillis());
     }
 
     @Test
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/BaseControllerGremlinClientTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/BaseControllerGremlinClientTest.java
index 1e5a7c6a3..039321d01 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/BaseControllerGremlinClientTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/BaseControllerGremlinClientTest.java
@@ -38,8 +38,9 @@ public class BaseControllerGremlinClientTest {
     }
 
     @Test
-    public void testGremlinClientIgnoresLegacyPasswordAndUsesToken() {
+    public void testGremlinClientUsesLegacyBasicCredentials() {
         HugeClient tokenClient = Mockito.mock(HugeClient.class);
+        HugeClient basicClient = Mockito.mock(HugeClient.class);
 
         MockHttpServletRequest request = this.requestWithAuth();
         request.setAttribute("hugeClient", tokenClient);
@@ -48,15 +49,14 @@ public class BaseControllerGremlinClientTest {
 
         TestController controller = new TestController();
         controller.authClient = tokenClient;
+        controller.basicClient = basicClient;
 
         HugeClient client = controller.gremlinClient("DEFAULT", "hugegraph");
 
-        Assert.assertSame(tokenClient, client);
-        Assert.assertSame(tokenClient, request.getAttribute("hugeClient"));
-        Assert.assertTrue(controller.authClientCreated);
-        Assert.assertEquals("DEFAULT", controller.graphSpace);
-        Assert.assertEquals("hugegraph", controller.graph);
-        Mockito.verify(tokenClient, Mockito.never()).close();
+        Assert.assertSame(basicClient, client);
+        Assert.assertSame(basicClient, request.getAttribute("hugeClient"));
+        Assert.assertFalse(controller.authClientCreated);
+        Mockito.verify(tokenClient).close();
     }
 
     @Test
@@ -132,6 +132,7 @@ public class BaseControllerGremlinClientTest {
     private static class TestController extends BaseController {
 
         private HugeClient authClient;
+        private HugeClient basicClient;
         private boolean authClientCreated;
         private String graphSpace;
         private String graph;
@@ -152,5 +153,17 @@ public class BaseControllerGremlinClientTest {
             this.getRequest().setAttribute("hugeClient", this.authClient);
             return this.authClient;
         }
+
+        @Override
+        protected HugeClient createBasicClient(String graphSpace, String graph,
+                                               String username, String 
password) {
+            return this.basicClient;
+        }
+
+        @Override
+        protected void requireGraphSpaceAccess(HugeClient client,
+                                               String graphSpace) {
+            // No graph-space service is needed for this client-selection test.
+        }
     }
 }
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/ConfigControllerTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/ConfigControllerTest.java
index a60c457d9..e99ed7393 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/ConfigControllerTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/ConfigControllerTest.java
@@ -27,7 +27,10 @@ import org.springframework.test.util.ReflectionTestUtils;
 
 import org.apache.hugegraph.config.HugeConfig;
 import org.apache.hugegraph.controller.ConfigController;
+import org.apache.hugegraph.driver.HugeClient;
 import org.apache.hugegraph.options.HubbleOptions;
+import org.apache.hugegraph.service.HugeClientPoolService;
+import org.apache.hugegraph.service.auth.AuthModeService;
 
 public class ConfigControllerTest {
 
@@ -35,15 +38,84 @@ public class ConfigControllerTest {
     public void testBootstrapConfigDoesNotExposeBackendUrl() {
         HugeConfig config = Mockito.mock(HugeConfig.class);
         Mockito.when(config.get(HubbleOptions.PD_ENABLED)).thenReturn(false);
-        Mockito.when(config.get(HubbleOptions.AUTH_ENABLED)).thenReturn(true);
+        HugeClient client = Mockito.mock(HugeClient.class);
+        AuthModeService authMode = Mockito.mock(AuthModeService.class);
+        Mockito.when(client.isServerAuthEnabled()).thenReturn(false);
+        Mockito.when(authMode.update(false)).thenReturn(false);
 
-        ConfigController controller = new ConfigController();
+        ConfigController controller = new ConfigController() {
+            @Override
+            protected HugeClient createUnauthClient() {
+                return client;
+            }
+        };
         ReflectionTestUtils.setField(controller, "config", config);
+        ReflectionTestUtils.setField(controller, "hugeClientPoolService",
+                                     new HugeClientPoolService());
+        ReflectionTestUtils.setField(controller, "authModeService", authMode);
 
         Map<String, Object> result = controller.getConfig();
 
         Assert.assertEquals(Map.of("pd_enabled", false,
-                                   "auth_enabled", true), result);
+                                   "auth_enabled", false,
+                                   "graph_create_enabled", false,
+                                   "cypher_enabled", false), result);
         Mockito.verify(config, Mockito.never()).get(HubbleOptions.SERVER_URL);
+        Mockito.verify(client).close();
+    }
+
+    @Test
+    public void testStandaloneConfigExposesServerCapabilities() {
+        HugeConfig config = Mockito.mock(HugeConfig.class);
+        Mockito.when(config.get(HubbleOptions.PD_ENABLED)).thenReturn(false);
+        HugeClient client = Mockito.mock(HugeClient.class);
+        AuthModeService authMode = Mockito.mock(AuthModeService.class);
+        Mockito.when(client.isServerAuthEnabled()).thenReturn(true);
+        Mockito.when(authMode.update(true)).thenReturn(true);
+        Mockito.when(client.supportsGraphCreate()).thenReturn(true);
+        Mockito.when(client.supportsCypher()).thenReturn(false);
+
+        ConfigController controller = new ConfigController() {
+            @Override
+            protected HugeClient createUnauthClient() {
+                return client;
+            }
+        };
+        ReflectionTestUtils.setField(controller, "config", config);
+        ReflectionTestUtils.setField(controller, "hugeClientPoolService",
+                                     new HugeClientPoolService());
+        ReflectionTestUtils.setField(controller, "authModeService", authMode);
+
+        Map<String, Object> result = controller.getConfig();
+
+        Assert.assertEquals(Map.of("pd_enabled", false,
+                                   "auth_enabled", true,
+                                   "graph_create_enabled", true,
+                                   "cypher_enabled", false), result);
+        Mockito.verify(client).close();
+    }
+
+    @Test
+    public void testStandaloneConfigSurvivesCapabilityProbeFailure() {
+        HugeConfig config = Mockito.mock(HugeConfig.class);
+        Mockito.when(config.get(HubbleOptions.PD_ENABLED)).thenReturn(false);
+        AuthModeService authMode = Mockito.mock(AuthModeService.class);
+        ConfigController controller = new ConfigController() {
+            @Override
+            protected HugeClient createUnauthClient() {
+                throw new IllegalStateException("server unavailable");
+            }
+        };
+        ReflectionTestUtils.setField(controller, "config", config);
+        ReflectionTestUtils.setField(controller, "hugeClientPoolService",
+                                     new HugeClientPoolService());
+        ReflectionTestUtils.setField(controller, "authModeService", authMode);
+
+        Map<String, Object> result = controller.getConfig();
+
+        Assert.assertEquals(Map.of("pd_enabled", false,
+                                   "auth_enabled", true,
+                                   "graph_create_enabled", false,
+                                   "cypher_enabled", false), result);
     }
 }
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/OperationsControllerTest.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/OperationsControllerTest.java
index 6f483ab48..83fe739de 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/OperationsControllerTest.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/OperationsControllerTest.java
@@ -122,9 +122,8 @@ public class OperationsControllerTest {
                                              OperationsDataService.class);
         OperationsController controller = new OperationsController();
         HugeConfig config = Mockito.mock(HugeConfig.class);
-        Mockito.when(config.get(HubbleOptions.AUTH_ENABLED))
-               .thenReturn(!anonymous);
-        AuthModeService authMode = new AuthModeService(config);
+        AuthModeService authMode = Mockito.mock(AuthModeService.class);
+        Mockito.when(authMode.anonymous()).thenReturn(anonymous);
         ReflectionTestUtils.setField(controller, "authMode", authMode);
         ReflectionTestUtils.setField(controller, "userService", userService);
         ReflectionTestUtils.setField(controller, "dataService", dataService);
diff --git 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java
 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java
index f2aca19e4..9ce4fe4e5 100644
--- 
a/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java
+++ 
b/hugegraph-hubble/hubble-be/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java
@@ -26,10 +26,11 @@ import 
org.apache.hugegraph.controller.langchain.LangChainControllerSecurityTest
 import org.apache.hugegraph.controller.schema.SchemaControllerSecurityTest;
 import org.apache.hugegraph.controller.space.GraphSpaceControllerTest;
 import 
org.apache.hugegraph.controller.space.SchemaTemplateControllerSecurityTest;
-import org.apache.hugegraph.config.HubbleConfigEnvironmentTest;
 import org.apache.hugegraph.handler.ResponseAdvisorStatusTest;
 import org.apache.hugegraph.service.load.IngestTransactionIntegrationTest;
 import org.apache.hugegraph.service.auth.AuthContextServiceTest;
+import org.apache.hugegraph.service.auth.AuthModeServiceTest;
+import org.apache.hugegraph.service.auth.GraphSpaceUserServiceTest;
 import org.apache.hugegraph.service.space.GraphSpaceServiceTest;
 import org.apache.hugegraph.service.op.DefaultOperationsDataServiceTest;
 import org.apache.hugegraph.service.op.LiveOperationsCollectorTest;
@@ -44,12 +45,14 @@ import org.junit.runners.Suite;
 @Suite.SuiteClasses({
     AccountMutationAuthorizationTest.class,
     AuthContextServiceTest.class,
+    AuthModeServiceTest.class,
     AuthSecurityTest.class,
     AppTypeTest.class,
     AuthzRouteRegistrationTest.class,
     BusinessAssertTest.class,
     BaseControllerGremlinClientTest.class,
     ConsolePrintTest.class,
+    ConfigControllerTest.class,
     EmptyCatchTest.class,
     FileMappingSchemaTest.class,
     FileUploadControllerTest.class,
@@ -61,10 +64,10 @@ import org.junit.runners.Suite;
     GraphSpaceAuthMutationAuthorizationTest.class,
     GraphSpaceAuthOwnershipTest.class,
     GraphSpaceServiceTest.class,
+    GraphSpaceUserServiceTest.class,
     GraphsControllerCanonicalTest.class,
     GremlinUtilTest.class,
     GremlinHistoryFailureTest.class,
-    HubbleConfigEnvironmentTest.class,
     HubbleOptionsTest.class,
     IngestControllerTest.class,
     IngestTransactionIntegrationTest.class,
diff --git 
a/hugegraph-hubble/hubble-dist/assembly/static/conf/hugegraph-hubble.properties 
b/hugegraph-hubble/hubble-dist/assembly/static/conf/hugegraph-hubble.properties
index 442560763..8dfce484c 100644
--- 
a/hugegraph-hubble/hubble-dist/assembly/static/conf/hugegraph-hubble.properties
+++ 
b/hugegraph-hubble/hubble-dist/assembly/static/conf/hugegraph-hubble.properties
@@ -28,9 +28,6 @@ idc=bddwd
 client.url_cache_max_entries=1024
 
 # ===== Deployment Mode =====
-# Require an authenticated Hubble session. HUBBLE_AUTH_ENABLED can explicitly
-# override this value for container and orchestrated deployments.
-auth.enabled=true
 # Set to false for standalone RocksDB mode (no PD dependency)
 pd.enabled=false
 # Direct server URL, only used when pd.enabled=false
diff --git a/hugegraph-hubble/hubble-dist/assembly/travis/download-hugegraph.sh 
b/hugegraph-hubble/hubble-dist/assembly/travis/download-hugegraph.sh
index df4abd4e2..b1e93be0e 100755
--- a/hugegraph-hubble/hubble-dist/assembly/travis/download-hugegraph.sh
+++ b/hugegraph-hubble/hubble-dist/assembly/travis/download-hugegraph.sh
@@ -25,7 +25,7 @@ fi
 
 COMMIT_ID=$1
 COMMIT_REF=${2:-}
-HUGEGRAPH_GIT_URL="https://github.com/apache/hugegraph.git";
+HUGEGRAPH_GIT_URL=${HUGEGRAPH_GIT_URL:-"https://github.com/apache/hugegraph.git"}
 GIT_DIR=hugegraph
 CACHE_DIR="${HOME}/hugegraph-cache-${COMMIT_ID}"
 
diff --git a/hugegraph-hubble/hubble-fe/src/auth/graphspaceAccess.js 
b/hugegraph-hubble/hubble-fe/src/auth/graphspaceAccess.js
index deadbf473..88567abae 100644
--- a/hugegraph-hubble/hubble-fe/src/auth/graphspaceAccess.js
+++ b/hugegraph-hubble/hubble-fe/src/auth/graphspaceAccess.js
@@ -29,11 +29,15 @@ const resolveGraphspaceAccess = (context, graphspace, 
pdEnabled) => {
 
     const scopes = context.scopes ?? {};
     const anonymousStandalone = context.mode === 'NON_AUTH' && !pdEnabled;
+    const anonymousPd = context.mode === 'NON_AUTH' && pdEnabled;
     const canManage = anonymousStandalone
-        || context.role === 'SUPERADMIN'
-        || scopes.all_graphspaces === true
-        || includes(scopes.admin_graphspaces, graphspace);
+        || (!anonymousPd && (
+            context.role === 'SUPERADMIN'
+            || scopes.all_graphspaces === true
+            || includes(scopes.admin_graphspaces, graphspace)
+        ));
     const canWrite = canManage
+        || anonymousPd
         || context.mode === 'NON_PD'
         || includes(scopes.write_graphspaces, graphspace);
     return {canManage, canWrite};
diff --git a/hugegraph-hubble/hubble-fe/src/auth/graphspaceAccess.test.js 
b/hugegraph-hubble/hubble-fe/src/auth/graphspaceAccess.test.js
index bf2cff573..86652aa59 100644
--- a/hugegraph-hubble/hubble-fe/src/auth/graphspaceAccess.test.js
+++ b/hugegraph-hubble/hubble-fe/src/auth/graphspaceAccess.test.js
@@ -33,6 +33,11 @@ test.each([
     }, true, true, true],
     [{mode: 'NON_PD', role: 'USER', scopes: {}}, false, false, true],
     [{mode: 'NON_PD', role: 'SUPERADMIN', scopes: {}}, false, true, true],
+    [{
+        mode: 'NON_AUTH',
+        role: 'ANONYMOUS',
+        scopes: {all_graphspaces: true},
+    }, true, false, true],
     [{mode: 'NON_AUTH', role: 'ANONYMOUS', scopes: {}}, false, true, true],
 ])(
     'resolves graphspace access for %#',
diff --git 
a/hugegraph-hubble/hubble-fe/src/components/GraphContextSwitcher/index.js 
b/hugegraph-hubble/hubble-fe/src/components/GraphContextSwitcher/index.js
index 92ae8b41b..bdb628edc 100644
--- a/hugegraph-hubble/hubble-fe/src/components/GraphContextSwitcher/index.js
+++ b/hugegraph-hubble/hubble-fe/src/components/GraphContextSwitcher/index.js
@@ -64,11 +64,14 @@ const getRecords = response => {
     return response.data?.records ?? [];
 };
 
-const errorKind = error => {
-    const status = error?.status ?? error?.response?.data?.status
-                   ?? error?.response?.status;
-    return status === 403 ? 'forbidden' : true;
-};
+const errorStatus = error => error?.status ?? error?.response?.data?.status
+                     ?? error?.response?.status;
+
+const errorKind = error => (
+    errorStatus(error) === 403 ? 'forbidden' : true
+);
+
+const unavailableGraphSpace = error => [403, 404].includes(errorStatus(error));
 
 const GraphContextSwitcher = () => {
     const {t} = useTranslation();
@@ -157,7 +160,7 @@ const GraphContextSwitcher = () => {
             .catch(error => {
                 if (!cancelled) {
                     const kind = errorKind(error);
-                    if (pdEnabled && kind === 'forbidden') {
+                    if (pdEnabled && unavailableGraphSpace(error)) {
                         clearWorkbenchGraphContext(localStorage);
                         setContext({});
                         setGraphs([]);
diff --git 
a/hugegraph-hubble/hubble-fe/src/components/GraphContextSwitcher/index.test.js 
b/hugegraph-hubble/hubble-fe/src/components/GraphContextSwitcher/index.test.js
index 8e1d764df..4002b2ed2 100644
--- 
a/hugegraph-hubble/hubble-fe/src/components/GraphContextSwitcher/index.test.js
+++ 
b/hugegraph-hubble/hubble-fe/src/components/GraphContextSwitcher/index.test.js
@@ -326,6 +326,26 @@ describe('GraphContextSwitcher', () => {
             .not.toBeInTheDocument();
     });
 
+    test('clears a masked missing PD GraphSpace after access is revoked', 
async () => {
+        sessionStorage.setItem('hubble_config_', JSON.stringify({pd_enabled: 
true}));
+        localStorage.setItem('hubble_workbench_graph_context', JSON.stringify({
+            graphspace: 'space_a',
+            graph: 'graph_a',
+        }));
+        api.manage.getGraphList.mockResolvedValueOnce({status: 404, data: 
null});
+        renderSwitcher('/gremlin/space_a/graph_a');
+
+        await waitFor(() => {
+            expect(screen.getByText('/navigation')).toBeInTheDocument();
+        });
+        
expect(localStorage.getItem('hubble_workbench_graph_context')).toBeNull();
+        expect(screen.getByRole('combobox', {
+            name: 'workbench.context.graphspace',
+        })).toHaveValue('');
+        expect(screen.queryByText('workbench.context.graphs_load_failed'))
+            .not.toBeInTheDocument();
+    });
+
     test('graph success cannot erase a concurrent GraphSpace failure', async 
() => {
         sessionStorage.setItem('hubble_config_', JSON.stringify({pd_enabled: 
true}));
         api.manage.getGraphSpaceList.mockResolvedValueOnce({status: 500, data: 
null});
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 744fae2c0..a75b2e76a 100644
--- a/hugegraph-hubble/hubble-fe/src/components/Sidebar/index.ant.js
+++ b/hugegraph-hubble/hubble-fe/src/components/Sidebar/index.ant.js
@@ -63,6 +63,7 @@ const items = (t, pathname, capabilities = []) => {
             key: 'nodes',
         }] : []),
     ];
+    const supportList = [...operationsList, ...systemList];
 
     const menu = [
         {
@@ -125,12 +126,12 @@ const items = (t, pathname, capabilities = []) => {
                 },
             ],
         },
-        {
+        ...(supportList.length > 0 ? [{
             label: t('operations.section'),
             key: 'support',
             icon: <DashboardOutlined />,
-            children: [...operationsList, ...systemList],
-        },
+            children: supportList,
+        }] : []),
     ];
 
     return menu;
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 db826c47f..2fe9f7aa8 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
@@ -94,6 +94,31 @@ test('hides account and profile links in anonymous mode', 
async () => {
     expect(screen.queryByRole('link', {name: '账号管理'})).not.toBeInTheDocument();
 });
 
+test('hides an empty operations section in anonymous standalone mode', async 
() => {
+    sessionStorage.setItem('hubble_config_', JSON.stringify({
+        pd_enabled: false,
+        auth_enabled: false,
+    }));
+    useOperationsCapabilities.mockReturnValue({
+        loading: false,
+        capabilities: [],
+        error: null,
+    });
+
+    render(
+        <MemoryRouter
+            initialEntries={['/navigation']}
+            future={{v7_startTransition: true, v7_relativeSplatPath: true}}
+        >
+            <Sidebar />
+        </MemoryRouter>
+    );
+
+    expect(await screen.findByRole('navigation', {name: '主导航'}))
+        .toBeInTheDocument();
+    expect(screen.queryByText('系统与运维')).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 7547d188f..97463509a 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
@@ -186,6 +186,7 @@
     "title": "Graph Management",
     "view_mode": "Graph list view",
     "create": "New Graph",
+    "server_upgrade_hint": "This Server version is too old for some features. 
Please upgrade to the latest version.",
     "search_placeholder": "Search by graph name",
     "unavailable": "Graphs are unavailable. Check the server connection and 
retry.",
     "set_default_confirm": "Confirm changing the default graph?",
@@ -513,6 +514,7 @@
       "new_password_placeholder": "Enter new password",
       "confirm_password_placeholder": "Enter new password again",
       "password_mismatch": "Passwords do not match",
+      "password_changed_relogin": "Password changed successfully. Please sign 
in again.",
       "account_name_rule": "Use 1–16 characters without spaces; letters, 
numbers, Chinese/CJK forms, and underscores are supported; underscores cannot 
be first or last"
     }
   },
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 322f3e4ce..0861fd367 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
@@ -186,6 +186,7 @@
     "title": "图管理",
     "view_mode": "图列表视图",
     "create": "新建图",
+    "server_upgrade_hint": "当前 Server 版本过低,部分功能不可用,请及时升级到最新版本。",
     "search_placeholder": "请输入图名称",
     "unavailable": "图列表暂不可用,请检查 Server 连接后重试。",
     "set_default_confirm": "确认更改图的默认设置?",
@@ -513,6 +514,7 @@
       "new_password_placeholder": "请输入新密码",
       "confirm_password_placeholder": "请再次输入密码",
       "password_mismatch": "两次密码不一致",
+      "password_changed_relogin": "密码修改成功,请重新登录。",
       "account_name_rule": "使用 1–16 个不含空格的字符,支持中文/东亚兼容字符、字母、数字和下划线;下划线不能位于首尾"
     }
   },
diff --git a/hugegraph-hubble/hubble-fe/src/modules/analysis/Home/index.js 
b/hugegraph-hubble/hubble-fe/src/modules/analysis/Home/index.js
index dec24f9ba..32b60df0b 100644
--- a/hugegraph-hubble/hubble-fe/src/modules/analysis/Home/index.js
+++ b/hugegraph-hubble/hubble-fe/src/modules/analysis/Home/index.js
@@ -32,6 +32,7 @@ import * as api from '../../../api';
 import _ from 'lodash';
 import {scopedStorageKey} from '../../../utils/user';
 import {sanitizePublicError} from '../../../utils/publicError';
+import {isCypherEnabled} from '../../../utils/config';
 
 const {STANDBY, LOADING, SUCCESS, FAILED} = GRAPH_STATUS;
 const {QUERY} = GREMLIN_EXECUTES_MODE;
@@ -75,6 +76,7 @@ export const extractQueryErrorMessage = (error, fallback) => {
 const AnalysisHome = () => {
     const {t} = useTranslation();
     const {graphSpace, graph} = useContext(GraphAnalysisContext);
+    const cypherEnabled = isCypherEnabled();
     const [queryStatus, setQueryStatus] = useState(STANDBY);
     const [queryMessage, setQueryMessage] = useState();
     const [isQueryMode, setQueryMode] = useState(true);
@@ -567,6 +569,7 @@ const AnalysisHome = () => {
                 onExecute={onExecute}
                 onRefresh={onFavoriteRefresh}
                 isExecuting={queryStatus === LOADING}
+                cypherEnabled={cypherEnabled}
             />
             {analysisMode !== TEXT2GQL && <QueryResult
                 queryResult={queryResult}
diff --git 
a/hugegraph-hubble/hubble-fe/src/modules/analysis/QueryBar/Home/index.js 
b/hugegraph-hubble/hubble-fe/src/modules/analysis/QueryBar/Home/index.js
index 44850e7d5..9d9281aba 100644
--- a/hugegraph-hubble/hubble-fe/src/modules/analysis/QueryBar/Home/index.js
+++ b/hugegraph-hubble/hubble-fe/src/modules/analysis/QueryBar/Home/index.js
@@ -42,6 +42,7 @@ const QueryBar = props => {
         onTabsChange,
         onExecute,
         isExecuting,
+        cypherEnabled = false,
     } = args;
 
     const [isEmptyQuery, setIsEmptyQuery] = useState(() => !codeEditorContent);
@@ -132,11 +133,11 @@ const QueryBar = props => {
             key: GREMLIN,
             children: renderEditor('gremlin'),
         },
-        {
+        ...(cypherEnabled ? [{
             label: t('analysis.query.cypher_tab'),
             key: CYPHER,
             children: renderEditor('cypher'),
-        },
+        }] : []),
         {
             label: (
                 <span>
diff --git 
a/hugegraph-hubble/hubble-fe/src/modules/analysis/QueryBar/Home/index.test.js 
b/hugegraph-hubble/hubble-fe/src/modules/analysis/QueryBar/Home/index.test.js
index e3a6027d8..106d55d95 100644
--- 
a/hugegraph-hubble/hubble-fe/src/modules/analysis/QueryBar/Home/index.test.js
+++ 
b/hugegraph-hubble/hubble-fe/src/modules/analysis/QueryBar/Home/index.test.js
@@ -109,12 +109,26 @@ it('shows a same-level Text2GQL preview with no 
executable control', () => {
     expect(screen.queryByRole('button', {name: 
/run|execute/i})).not.toBeInTheDocument();
 });
 
+it('hides Cypher when capability is absent', () => {
+    render(
+        <QueryBar
+            activeTab='Gremlin'
+            onTabsChange={jest.fn()}
+            codeEditorContent=''
+            setCodeEditorContent={jest.fn()}
+        />
+    );
+
+    expect(screen.queryByRole('tab', {name: 
'Cypher'})).not.toBeInTheDocument();
+});
+
 it('does not transfer an open favorite popover when query tabs change', () => {
     const ControlledQueryBar = () => {
         const [activeTab, setActiveTab] = useState('Gremlin');
         return (
             <QueryBar
                 activeTab={activeTab}
+                cypherEnabled
                 onTabsChange={setActiveTab}
                 codeEditorContent='g.V()'
                 setCodeEditorContent={jest.fn()}
@@ -137,6 +151,7 @@ it('matches the editor placeholder to the active query 
language without promotio
         return (
             <QueryBar
                 activeTab={activeTab}
+                cypherEnabled
                 onTabsChange={setActiveTab}
                 codeEditorContent=''
                 setCodeEditorContent={jest.fn()}
diff --git 
a/hugegraph-hubble/hubble-fe/src/pages/Graph/default-card-actions.test.js 
b/hugegraph-hubble/hubble-fe/src/pages/Graph/default-card-actions.test.js
index 9507133e4..f3bf49228 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/Graph/default-card-actions.test.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/Graph/default-card-actions.test.js
@@ -96,7 +96,10 @@ beforeAll(installMatchMedia);
 beforeEach(() => {
     jest.clearAllMocks();
     installMatchMedia();
-    sessionStorage.setItem('hubble_config_', JSON.stringify({pd_enabled: 
true}));
+    sessionStorage.setItem('hubble_config_', JSON.stringify({
+        pd_enabled: true,
+        graph_create_enabled: true,
+    }));
     mockAuthContext = {
         context: {role: 'SUPERADMIN', scopes: {all_graphspaces: true}},
     };
diff --git a/hugegraph-hubble/hubble-fe/src/pages/Graph/index.js 
b/hugegraph-hubble/hubble-fe/src/pages/Graph/index.js
index 60d634f5b..8d7732033 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/Graph/index.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/Graph/index.js
@@ -42,10 +42,9 @@ import {PlusOutlined} from '@ant-design/icons';
 import {Link, useParams, useNavigate} from 'react-router-dom';
 import style from './index.module.scss';
 import * as api from '../../api';
-import {isPdEnabled} from '../../utils/config';
+import {isGraphCreateEnabled, isPdEnabled} from '../../utils/config';
 import {
     DEFAULT_GRAPHSPACE,
-    isGraphCreateEnabled,
     isGraphDefaultMutationEnabled,
 } from '../../utils/productMode';
 import moment from 'moment';
@@ -107,7 +106,10 @@ const Graph = () => {
     const {graphspace} = useParams();
     const navigate = useNavigate();
     const pdMode = isPdEnabled();
-    const graphCreateEnabled = isGraphCreateEnabled(pdMode);
+    const graphCreateEnabled = isGraphCreateEnabled();
+    const graphCreateUnavailableHint = t('graph.server_upgrade_hint');
+    const cloneUnavailableHint = graphCreateEnabled
+        ? t('graph.clone.unavailable') : graphCreateUnavailableHint;
     const graphDefaultMutationEnabled = isGraphDefaultMutationEnabled(pdMode);
     const {canManage, canWrite} = useGraphspaceAccess(graphspace);
 
@@ -435,19 +437,17 @@ const Graph = () => {
                                     </GraphRowAction>
                                 )
                         )}
-                        {graphCreateEnabled && (
-                            <Tooltip title={t('graph.clone.unavailable')}>
-                                <span
-                                    className={style.disable}
-                                    role='button'
-                                    aria-disabled='true'
-                                    aria-label={`${t('graph.menu.clone')}: 
${t('graph.clone.unavailable')}`}
-                                    tabIndex={0}
-                                >
-                                    {t('graph.menu.clone')}
-                                </span>
-                            </Tooltip>
-                        )}
+                        <Tooltip title={cloneUnavailableHint}>
+                            <span
+                                className={style.disable}
+                                role='button'
+                                aria-disabled='true'
+                                aria-label={`${t('graph.menu.clone')}: 
${cloneUnavailableHint}`}
+                                tabIndex={0}
+                            >
+                                {t('graph.menu.clone')}
+                            </span>
+                        </Tooltip>
                     </Space>
                 );
             },
@@ -518,13 +518,13 @@ const Graph = () => {
                 onClick: immutable || !canManage
                     ? undefined : () => deleteGraph(item.name),
             },
-            graphCreateEnabled && {
+            {
                 key: 'clone',
                 disabled: true,
                 label: (
-                    <Tooltip title={t('graph.clone.unavailable')}>
+                    <Tooltip title={cloneUnavailableHint}>
                         <span
-                            aria-label={`${t('graph.menu.clone')}: 
${t('graph.clone.unavailable')}`}
+                            aria-label={`${t('graph.menu.clone')}: 
${cloneUnavailableHint}`}
                         >
                             {t('graph.menu.clone')}
                         </span>
@@ -614,6 +614,13 @@ const Graph = () => {
                         )}
                     />
                 )}
+                {!graphCreateEnabled && (
+                    <Alert
+                        showIcon
+                        type='info'
+                        message={graphCreateUnavailableHint}
+                    />
+                )}
                 {listType === 'image'
                     ? (
                         <>
diff --git a/hugegraph-hubble/hubble-fe/src/pages/My/index.js 
b/hugegraph-hubble/hubble-fe/src/pages/My/index.js
index cad01a205..f1b786774 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/My/index.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/My/index.js
@@ -24,7 +24,11 @@ import style from './index.module.scss';
 import EditLayer from './EditLayer';
 import * as api from '../../api';
 import * as rules from '../../utils/rules';
+import * as user from '../../utils/user';
 import {useAuthContext} from '../../auth/AuthContext';
+import {
+    clearPersistedAlgorithmFormsForUser,
+} from '../../modules/algorithm/algorithmsForm/algorithmFormPersistence';
 
 const My = () => {
     const {t} = useTranslation();
@@ -76,8 +80,12 @@ const My = () => {
             setLoading(true);
             const res = await api.auth.updatePwd(data.user_name, old_password, 
user_password);
             if (res.status === 200) {
-                message.success(t('common.msg.update_success'));
-                setChangePass(false);
+                message.success(t('my.edit.password_changed_relogin'));
+                user.beginLogoutTransition();
+                sessionStorage.removeItem('redirect');
+                clearPersistedAlgorithmFormsForUser();
+                user.clearLogin();
+                window.location.replace('/login');
                 return;
             }
 
diff --git a/hugegraph-hubble/hubble-fe/src/pages/My/my-recovery.test.js 
b/hugegraph-hubble/hubble-fe/src/pages/My/my-recovery.test.js
index a530bbe62..d7b7c6042 100644
--- a/hugegraph-hubble/hubble-fe/src/pages/My/my-recovery.test.js
+++ b/hugegraph-hubble/hubble-fe/src/pages/My/my-recovery.test.js
@@ -20,6 +20,10 @@ import {render, screen, waitFor} from 
'@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import My from './index';
 import * as api from '../../api';
+import * as user from '../../utils/user';
+import {
+    clearPersistedAlgorithmFormsForUser,
+} from '../../modules/algorithm/algorithmsForm/algorithmFormPersistence';
 
 let mockAuthContext = null;
 
@@ -39,6 +43,15 @@ jest.mock('../../api', () => ({
     },
 }));
 
+jest.mock('../../utils/user', () => ({
+    clearLogin: jest.fn(),
+    beginLogoutTransition: jest.fn(),
+}));
+
+jest.mock('../../modules/algorithm/algorithmsForm/algorithmFormPersistence', 
() => ({
+    clearPersistedAlgorithmFormsForUser: jest.fn(),
+}));
+
 jest.mock('./EditLayer', () => ({refresh}) => (
     <button type='button' onClick={refresh}>mock profile saved</button>
 ));
@@ -60,6 +73,9 @@ beforeEach(() => {
         addListener: jest.fn(),
         removeListener: jest.fn(),
     }));
+    sessionStorage.clear();
+    delete window.location;
+    window.location = {replace: jest.fn()};
     api.auth.status.mockResolvedValue({status: 200, data: {level: 'ADMIN'}});
 });
 
@@ -232,6 +248,8 @@ test('stops password submit loading when form validation 
rejects', async () => {
     await waitFor(() => 
expect(document.querySelector('.ant-form-item-has-error')).not.toBeNull());
     await waitFor(() => expect(confirm).not.toHaveClass('ant-btn-loading'));
     expect(api.auth.updatePwd).not.toHaveBeenCalled();
+    expect(user.clearLogin).not.toHaveBeenCalled();
+    expect(window.location.replace).not.toHaveBeenCalled();
     
expect(screen.getByPlaceholderText('my.edit.new_password_placeholder')).toBeInTheDocument();
 });
 
@@ -247,6 +265,8 @@ test('stops password submit loading when the request 
rejects', async () => {
     ));
     await waitFor(() => expect(confirm).not.toHaveClass('ant-btn-loading'));
     
expect(screen.getByPlaceholderText('my.edit.new_password_placeholder')).toBeInTheDocument();
+    expect(user.clearLogin).not.toHaveBeenCalled();
+    expect(window.location.replace).not.toHaveBeenCalled();
 });
 
 test('stops password submit loading and preserves the form on a non-200 
response', async () => {
@@ -261,11 +281,14 @@ test('stops password submit loading and preserves the 
form on a non-200 response
     ));
     await waitFor(() => expect(confirm).not.toHaveClass('ant-btn-loading'));
     
expect(screen.getByPlaceholderText('my.edit.old_password_placeholder')).toHaveValue('old-pass');
+    expect(user.clearLogin).not.toHaveBeenCalled();
+    expect(window.location.replace).not.toHaveBeenCalled();
 });
 
-test('keeps loading during a password request and closes the form only on 
success', async () => {
+test('clears local auth and redirects only after the password update 
succeeds', async () => {
     const request = deferred();
     api.auth.updatePwd.mockReturnValue(request.promise);
+    sessionStorage.setItem('redirect', '/gremlin/DEFAULT/hugegraph');
     const confirm = await openPasswordForm();
     await fillValidPasswords();
 
@@ -278,8 +301,9 @@ test('keeps loading during a password request and closes 
the form only on succes
 
     request.resolve({status: 200});
 
-    await waitFor(() => expect(
-        screen.queryByPlaceholderText('my.edit.new_password_placeholder')
-    ).not.toBeInTheDocument());
-    expect(screen.getByRole('heading', {name: 
'Administrator'})).toBeInTheDocument();
+    await waitFor(() => expect(user.clearLogin).toHaveBeenCalledTimes(1));
+    expect(user.beginLogoutTransition).toHaveBeenCalledTimes(1);
+    expect(clearPersistedAlgorithmFormsForUser).toHaveBeenCalledTimes(1);
+    expect(sessionStorage.getItem('redirect')).toBeNull();
+    expect(window.location.replace).toHaveBeenCalledWith('/login');
 });
diff --git a/hugegraph-hubble/hubble-fe/src/utils/config.js 
b/hugegraph-hubble/hubble-fe/src/utils/config.js
index e17acbf93..0409bbcaf 100644
--- a/hugegraph-hubble/hubble-fe/src/utils/config.js
+++ b/hugegraph-hubble/hubble-fe/src/utils/config.js
@@ -35,4 +35,19 @@ const isAuthEnabled = () => {
     return getConfig().auth_enabled !== false;
 };
 
-export {setConfig, getConfig, isPdEnabled, isAuthEnabled};
+const isGraphCreateEnabled = () => {
+    return getConfig().graph_create_enabled === true;
+};
+
+const isCypherEnabled = () => {
+    return getConfig().cypher_enabled === true;
+};
+
+export {
+    setConfig,
+    getConfig,
+    isPdEnabled,
+    isAuthEnabled,
+    isGraphCreateEnabled,
+    isCypherEnabled,
+};
diff --git a/hugegraph-hubble/hubble-fe/src/utils/config.js 
b/hugegraph-hubble/hubble-fe/src/utils/config.test.js
similarity index 60%
copy from hugegraph-hubble/hubble-fe/src/utils/config.js
copy to hugegraph-hubble/hubble-fe/src/utils/config.test.js
index e17acbf93..0a6ff2448 100644
--- a/hugegraph-hubble/hubble-fe/src/utils/config.js
+++ b/hugegraph-hubble/hubble-fe/src/utils/config.test.js
@@ -16,23 +16,27 @@
  * under the License.
  */
 
-const CONFIG_KEY = 'hubble_config_';
+import {
+    isCypherEnabled,
+    isGraphCreateEnabled,
+    setConfig,
+} from './config';
 
-const setConfig = config => {
-    sessionStorage.setItem(CONFIG_KEY, JSON.stringify(config));
-};
+beforeEach(() => {
+    sessionStorage.clear();
+});
 
-const getConfig = () => {
-    const str = sessionStorage.getItem(CONFIG_KEY);
-    return str ? JSON.parse(str) : {pd_enabled: true};
-};
+test('keeps unknown server capabilities disabled', () => {
+    expect(isGraphCreateEnabled()).toBe(false);
+    expect(isCypherEnabled()).toBe(false);
+});
 
-const isPdEnabled = () => {
-    return getConfig().pd_enabled;
-};
+test('uses explicit server capability flags', () => {
+    setConfig({
+        graph_create_enabled: true,
+        cypher_enabled: false,
+    });
 
-const isAuthEnabled = () => {
-    return getConfig().auth_enabled !== false;
-};
-
-export {setConfig, getConfig, isPdEnabled, isAuthEnabled};
+    expect(isGraphCreateEnabled()).toBe(true);
+    expect(isCypherEnabled()).toBe(false);
+});
diff --git a/hugegraph-hubble/hubble-fe/src/utils/productMode.js 
b/hugegraph-hubble/hubble-fe/src/utils/productMode.js
index 9f0cbdf0f..a9e971a2d 100644
--- a/hugegraph-hubble/hubble-fe/src/utils/productMode.js
+++ b/hugegraph-hubble/hubble-fe/src/utils/productMode.js
@@ -65,8 +65,6 @@ const getTaskGraphspaceOptions = (pdEnabled, graphspaces = 
[]) => {
     }));
 };
 
-const isGraphCreateEnabled = () => true;
-
 const isGraphDefaultMutationEnabled = pdEnabled => pdEnabled;
 
 export {
@@ -74,7 +72,6 @@ export {
     getGraphspacePath,
     getManageNavItems,
     getTaskGraphspaceOptions,
-    isGraphCreateEnabled,
     isGraphDefaultMutationEnabled,
     isPdOnlyPath,
     shouldUseNonPdDefaultGraphspace,
diff --git a/hugegraph-hubble/hubble-fe/src/utils/productMode.test.js 
b/hugegraph-hubble/hubble-fe/src/utils/productMode.test.js
index b928d7152..544a6b5cd 100644
--- a/hugegraph-hubble/hubble-fe/src/utils/productMode.test.js
+++ b/hugegraph-hubble/hubble-fe/src/utils/productMode.test.js
@@ -20,7 +20,6 @@ import {
     getGraphspacePath,
     getManageNavItems,
     getTaskGraphspaceOptions,
-    isGraphCreateEnabled,
     isGraphDefaultMutationEnabled,
     isPdOnlyPath,
     shouldUseNonPdDefaultGraphspace,
@@ -55,9 +54,7 @@ describe('product mode helpers', () => {
         expect(shouldUseNonPdDefaultGraphspace(true, 'demo')).toBe(false);
     });
 
-    test('keeps graph create available while disabling default mutation in 
non-PD mode', () => {
-        expect(isGraphCreateEnabled(false)).toBe(true);
-        expect(isGraphCreateEnabled(true)).toBe(true);
+    test('keeps default mutation scoped to PD mode', () => {
         expect(isGraphDefaultMutationEnabled(false)).toBe(false);
         expect(isGraphDefaultMutationEnabled(true)).toBe(true);
     });

Reply via email to