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

jiangtian pushed a commit to branch strong_password
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit 9812ddddbcfb3194a329b528afb2f7e260244f3d
Author: Tian Jiang <[email protected]>
AuthorDate: Mon Jun 9 10:27:37 2025 +0800

    Add enforce_strong_password
---
 .../iotdb/it/env/remote/env/RemoteServerEnv.java   |   2 +-
 .../org/apache/iotdb/db/it/auth/IoTDBAuthIT.java   | 228 ++++++++++++++++++++-
 .../java/org/apache/iotdb/rpc/TSStatusCode.java    |   1 +
 .../apache/iotdb/session/util/SessionUtils.java    |   5 +-
 .../client/async/CnToDnAsyncRequestType.java       |   3 +
 .../CnToDnInternalServiceAsyncRequestManager.java  |   5 +
 .../confignode/conf/ConfigNodeDescriptor.java      |  25 +++
 .../iotdb/confignode/manager/ConfigManager.java    |  19 +-
 .../confignode/manager/PermissionManager.java      |   4 +
 .../iotdb/confignode/persistence/AuthorInfo.java   |  18 +-
 .../thrift/ConfigNodeRPCServiceProcessor.java      |   5 +
 .../confignode/persistence/AuthorInfoTest.java     |   2 +-
 .../persistence/CNPhysicalPlanGeneratorTest.java   |   2 +-
 .../iotdb/db/auth/ClusterAuthorityFetcher.java     |  37 ++++
 .../apache/iotdb/db/auth/IAuthorityFetcher.java    |   3 +
 .../org/apache/iotdb/db/conf/IoTDBDescriptor.java  |  18 ++
 .../iotdb/db/protocol/client/ConfigNodeClient.java |   6 +
 .../iotdb/db/protocol/session/SessionManager.java  |  81 ++++++++
 .../impl/DataNodeInternalRPCServiceImpl.java       |  22 ++
 .../execution/config/TableConfigTaskVisitor.java   |  16 ++
 .../execution/config/TreeConfigTaskVisitor.java    |  16 ++
 .../sql/ast/RelationalAuthorStatement.java         |  47 +++++
 .../plan/statement/sys/AuthorStatement.java        |  37 ++++
 .../apache/iotdb/db/utils/DataNodeAuthUtils.java   | 172 ++++++++++++++++
 .../conf/iotdb-system.properties.template          |  22 ++
 .../iotdb/commons/auth/user/BasicUserManager.java  |  17 +-
 .../apache/iotdb/commons/conf/CommonConfig.java    |  29 +++
 .../iotdb/commons/conf/CommonDescriptor.java       |  17 ++
 .../pipe/config/constant/SystemConstant.java       |   2 +
 .../org/apache/iotdb/commons/utils/AuthUtils.java  |  45 +++-
 .../src/main/thrift/confignode.thrift              |   2 +
 .../src/main/thrift/datanode.thrift                |   4 +
 32 files changed, 892 insertions(+), 20 deletions(-)

diff --git 
a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java
 
b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java
index c2308cfe103..35a45f95acc 100644
--- 
a/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java
+++ 
b/integration-test/src/main/java/org/apache/iotdb/it/env/remote/env/RemoteServerEnv.java
@@ -134,7 +134,7 @@ public class RemoteServerEnv implements BaseEnv {
       connection =
           DriverManager.getConnection(
               Config.IOTDB_URL_PREFIX + ip_addr + ":" + port,
-              BaseEnv.constructProperties(this.user, this.password, 
sqlDialect));
+              BaseEnv.constructProperties(username, password, sqlDialect));
     } catch (ClassNotFoundException e) {
       e.printStackTrace();
       throw new AssertionError();
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/db/it/auth/IoTDBAuthIT.java 
b/integration-test/src/test/java/org/apache/iotdb/db/it/auth/IoTDBAuthIT.java
index f88c47bbe2e..ae80d070201 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/db/it/auth/IoTDBAuthIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/db/it/auth/IoTDBAuthIT.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.it.auth;
 
 import org.apache.iotdb.commons.auth.entity.PrivilegeType;
 import org.apache.iotdb.commons.schema.column.ColumnHeaderConstant;
+import org.apache.iotdb.commons.utils.AuthUtils;
 import org.apache.iotdb.db.it.utils.TestUtils;
 import org.apache.iotdb.it.env.EnvFactory;
 import org.apache.iotdb.it.framework.IoTDBTestRunner;
@@ -60,6 +61,7 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 /** This is an example for integration test. */
+@SuppressWarnings("EmptyTryBlock")
 @RunWith(IoTDBTestRunner.class)
 @Category({LocalStandaloneIT.class, ClusterIT.class})
 public class IoTDBAuthIT {
@@ -454,7 +456,7 @@ public class IoTDBAuthIT {
         userStmt.execute("CREATE TIMESERIES root.a.c WITH 
DATATYPE=INT32,ENCODING=PLAIN");
         userStmt.execute("INSERT INTO root.a(timestamp,b,c) VALUES 
(1,100,1000)");
         // userStmt.execute("DELETE FROM root.a.b WHERE TIME <= 1000000000");
-        ResultSet resultSet = userStmt.executeQuery("SELECT * FROM root.**");
+        ResultSet resultSet = userStmt.executeQuery("SELECT * FROM root.a");
         validateResultSet(resultSet, "1,100,1000,\n");
         resultSet.close();
 
@@ -1206,7 +1208,7 @@ public class IoTDBAuthIT {
     adminStmt.execute("insert into root.sg.d2(time,s1,s2) values(1,1,1)");
     adminStmt.execute(
         "insert into root.sg.aligned_template(time,temperature,status) 
values(1,20,true)");
-    try (ResultSet resultSet = adminStmt.executeQuery("select * from 
root.**;")) {
+    try (ResultSet resultSet = adminStmt.executeQuery("select * from 
root.sg.**;")) {
       Set<String> standards =
           new HashSet<>(
               Arrays.asList(
@@ -1423,4 +1425,226 @@ public class IoTDBAuthIT {
         };
     resultSetEqualTest("show current_user", expectedHeader, retArray, 
"tempuser", "temppw");
   }
+
+  @Test
+  public void testStrongPassword() throws SQLException {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("SET CONFIGURATION 'enforce_strong_password'='true'");
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("create user userA 'NO_LOWER_CASE_123'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals(
+          "820: Invalid password, must contain at least one lowercase 
letter.", e.getMessage());
+    }
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("alter user root set password 'NO_LOWER_CASE_123'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals(
+          "820: Invalid password, must contain at least one lowercase 
letter.", e.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("create user userA 'no_upper_case_123'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals(
+          "820: Invalid password, must contain at least one uppercase 
letter.", e.getMessage());
+    }
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("alter user root set password 'no_upper_case_123'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals(
+          "820: Invalid password, must contain at least one uppercase 
letter.", e.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("create user userA 'no_DIGIT'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals("820: Invalid password, must contain at least one digit.", 
e.getMessage());
+    }
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("alter user root set password 'no_DIGIT'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals("820: Invalid password, must contain at least one digit.", 
e.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("create user userA 'noSpecial123'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals(
+          "820: Invalid password, must contain at least one special 
character.", e.getMessage());
+    }
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("alter user root set password 'noSpecial123'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals(
+          "820: Invalid password, must contain at least one special 
character.", e.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("create user userA '1234'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals(
+          "820: Invalid password, must contain at least one lowercase letter, 
one uppercase letter, one special character.",
+          e.getMessage());
+    }
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("alter user root set password '1234'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals(
+          "820: Invalid password, must contain at least one lowercase letter, 
one uppercase letter, one special character.",
+          e.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("create user userA 'userA'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals("820: Password cannot be the same as user name", 
e.getMessage());
+    }
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("alter user root set password 'root'");
+      fail();
+    } catch (SQLException e) {
+      assertEquals("820: Password cannot be the same as user name", 
e.getMessage());
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("create user userA 'strongPassword#1234'");
+
+      statement.execute("SET CONFIGURATION 'enforce_strong_password'='false'");
+
+      statement.execute("create user userB '1234'");
+      statement.execute("alter user root set password 'root'");
+    }
+  }
+
+  @Test
+  public void testPasswordHistory() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      testPasswordHistoryCreate(statement);
+      testPasswordHistoryAlter(statement);
+    } catch (SQLException e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  public void testPasswordHistoryCreate(Statement statement) throws 
SQLException {
+    statement.execute("create user userA '123456'");
+
+    try (ResultSet resultSet =
+        statement.executeQuery(
+            "select last password from 
root.__system.password_history.`userA`")) {
+      if (!resultSet.next()) {
+        fail("Password history not found");
+      }
+      assertEquals(AuthUtils.encryptPassword("123456"), 
resultSet.getString("Value"));
+    }
+  }
+
+  public void testPasswordHistoryAlter(Statement statement) throws 
SQLException {
+    statement.execute("alter user userA set password '654321'");
+
+    try (ResultSet resultSet =
+        statement.executeQuery(
+            "select last password from 
root.__system.password_history.`userA`")) {
+      if (!resultSet.next()) {
+        fail("Password history not found");
+      }
+      assertEquals(AuthUtils.encryptPassword("654321"), 
resultSet.getString("Value"));
+    }
+  }
+
+  @Test
+  public void testPasswordExpiration() throws SQLException {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("SET CONFIGURATION 'password_expiration_seconds'='1'");
+      statement.execute("CREATE USER userA 'password0001'");
+    }
+
+    long startTime = System.currentTimeMillis();
+    boolean loginFailed = false;
+    while (System.currentTimeMillis() - startTime < 60000) {
+      try (Connection ignored = EnvFactory.getEnv().getConnection("userA", 
"password0001")) {
+
+      } catch (SQLException e) {
+        assertEquals("820: Password has expired, please change to a new one", 
e.getMessage());
+        loginFailed = true;
+        break;
+      }
+    }
+
+    if (!loginFailed) {
+      fail("Password did not expire");
+    }
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("SET CONFIGURATION 'password_expiration_seconds'='0'");
+      statement.execute("ALTER USER userA SET PASSWORD 'password0002'");
+    }
+
+    try (Connection ignored = EnvFactory.getEnv().getConnection("userA", 
"password0002")) {}
+  }
+
+  @Test
+  public void testPasswordReuseInterval() throws SQLException {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("SET CONFIGURATION 
'password_reuse_interval_seconds'='5'");
+
+      statement.execute("CREATE USER userA 'password0001'");
+
+      long startTime = System.currentTimeMillis();
+      statement.execute("ALTER USER userA SET PASSWORD 'password0002'");
+
+      boolean changeSuccess = false;
+      while (System.currentTimeMillis() - startTime < 60000) {
+        try {
+          statement.execute("ALTER USER userA SET PASSWORD 'password0001'");
+          changeSuccess = true;
+          break;
+        } catch (SQLException e) {
+          assertTrue(e.getMessage().startsWith("701: The password has been 
used at"));
+        }
+      }
+
+      if (!changeSuccess) {
+        fail("Password is not successfully changed");
+      }
+      long currentTimeMillis = System.currentTimeMillis();
+      assertTrue(currentTimeMillis - startTime > 5000);
+    }
+  }
 }
diff --git 
a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java 
b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
index 3f7eb8f6507..ffbf77704a3 100644
--- 
a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
+++ 
b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
@@ -168,6 +168,7 @@ public enum TSStatusCode {
   NOT_HAS_PRIVILEGE_GRANTOPT(817),
   AUTH_OPERATE_EXCEPTION(818),
   OPTIMIZER_TIMEOUT(819),
+  ILLEGAL_PASSWORD(820),
 
   // Partition Error
   MIGRATE_REGION_ERROR(900),
diff --git 
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java
 
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java
index 8534b808754..cde07acd834 100644
--- 
a/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java
+++ 
b/iotdb-client/session/src/main/java/org/apache/iotdb/session/util/SessionUtils.java
@@ -152,7 +152,7 @@ public class SessionUtils {
     return buffer;
   }
 
-  private static int calculateLength(List<TSDataType> types, List<Object> 
values)
+  public static int calculateLength(List<TSDataType> types, List<? extends 
Object> values)
       throws IoTDBConnectionException {
     int res = 0;
     for (int i = 0; i < types.size(); i++) {
@@ -204,7 +204,8 @@ public class SessionUtils {
    * @param buffer buffer to insert
    * @throws IoTDBConnectionException
    */
-  private static void putValues(List<TSDataType> types, List<Object> values, 
ByteBuffer buffer)
+  public static void putValues(
+      List<TSDataType> types, List<? extends Object> values, ByteBuffer buffer)
       throws IoTDBConnectionException {
     for (int i = 0; i < values.size(); i++) {
       if (values.get(i) == null) {
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
index d138e333098..badad8a1fe7 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
@@ -123,4 +123,7 @@ public enum CnToDnAsyncRequestType {
   DELETE_DATA_FOR_TABLE_DEVICE,
   DELETE_TABLE_DEVICE_IN_BLACK_LIST,
   DETECT_TREE_DEVICE_VIEW_FIELD_TYPE,
+
+  // audit log and event write-back
+  INSERT_RECORD
 }
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
index 3b7a5c4bfb7..f683b9d8e42 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
@@ -99,6 +99,7 @@ import 
org.apache.iotdb.mpp.rpc.thrift.TTableDeviceInvalidateCacheReq;
 import org.apache.iotdb.mpp.rpc.thrift.TUpdateTableReq;
 import org.apache.iotdb.mpp.rpc.thrift.TUpdateTemplateReq;
 import org.apache.iotdb.mpp.rpc.thrift.TUpdateTriggerLocationReq;
+import org.apache.iotdb.service.rpc.thrift.TSInsertRecordReq;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -391,6 +392,10 @@ public class CnToDnInternalServiceAsyncRequestManager
         CnToDnAsyncRequestType.TEST_CONNECTION,
         (req, client, handler) ->
             client.testConnectionEmptyRPC((DataNodeTSStatusRPCHandler) 
handler));
+    actionMapBuilder.put(
+        CnToDnAsyncRequestType.INSERT_RECORD,
+        (req, client, handler) ->
+            client.insertRecord((TSInsertRecordReq) req, 
(DataNodeTSStatusRPCHandler) handler));
     actionMapBuilder.put(
         CnToDnAsyncRequestType.UPDATE_TABLE,
         (req, client, handler) ->
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/conf/ConfigNodeDescriptor.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/conf/ConfigNodeDescriptor.java
index ed8c4bb5f26..aabcd4f536c 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/conf/ConfigNodeDescriptor.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/conf/ConfigNodeDescriptor.java
@@ -755,6 +755,31 @@ public class ConfigNodeDescriptor {
   public void loadHotModifiedProps(TrimProperties properties) {
     Optional.ofNullable(properties.getProperty(IoTDBConstant.CLUSTER_NAME))
         .ifPresent(conf::setClusterName);
+
+    commonDescriptor
+        .getConfig()
+        .setEnforceStrongPassword(
+            Boolean.parseBoolean(
+                properties.getProperty(
+                    "enforce_strong_password",
+                    
String.valueOf(commonDescriptor.getConfig().isEnforceStrongPassword()))));
+
+    commonDescriptor
+        .getConfig()
+        .setPasswordExpirationSeconds(
+            Long.parseLong(
+                properties.getProperty(
+                    "password_expiration_seconds",
+                    
String.valueOf(commonDescriptor.getConfig().getPasswordExpirationSeconds()))));
+
+    commonDescriptor
+        .getConfig()
+        .setPasswordReuseIntervalSeconds(
+            Long.parseLong(
+                properties.getProperty(
+                    "password_reuse_interval_seconds",
+                    String.valueOf(
+                        
commonDescriptor.getConfig().getPasswordReuseIntervalSeconds()))));
   }
 
   public static ConfigNodeDescriptor getInstance() {
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
index cbd67795774..c5cdc734297 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
@@ -359,7 +359,7 @@ public class ConfigManager implements IManager {
     NodeInfo nodeInfo = new NodeInfo();
     ClusterSchemaInfo clusterSchemaInfo = new ClusterSchemaInfo();
     PartitionInfo partitionInfo = new PartitionInfo();
-    AuthorInfo authorInfo = new AuthorInfo();
+    AuthorInfo authorInfo = new AuthorInfo(this);
     ProcedureInfo procedureInfo = new ProcedureInfo(this);
     UDFInfo udfInfo = new UDFInfo();
     TriggerInfo triggerInfo = new TriggerInfo();
@@ -1371,6 +1371,23 @@ public class ConfigManager implements IManager {
     return resp;
   }
 
+  public TPermissionInfoResp getUser(String userName) {
+    TSStatus status = confirmLeader();
+    TPermissionInfoResp resp = new TPermissionInfoResp();
+    if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+      try {
+        resp = permissionManager.getUser(userName);
+      } catch (AuthException e) {
+        status.setCode(e.getCode().getStatusCode()).setMessage(e.getMessage());
+        resp.setStatus(status);
+        return resp;
+      }
+    } else {
+      resp.setStatus(status);
+    }
+    return resp;
+  }
+
   @Override
   public TConfigNodeRegisterResp registerConfigNode(TConfigNodeRegisterReq 
req) {
     final int ERROR_STATUS_NODE_ID = -1;
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/PermissionManager.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/PermissionManager.java
index adbb41cf9ea..46549c67449 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/PermissionManager.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/PermissionManager.java
@@ -137,4 +137,8 @@ public class PermissionManager {
       throws AuthException {
     return authorInfo.checkRoleOfUser(username, rolename);
   }
+
+  public TPermissionInfoResp getUser(String username) throws AuthException {
+    return authorInfo.getUser(username);
+  }
 }
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/AuthorInfo.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/AuthorInfo.java
index f18f0f44459..bafb17ce87e 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/AuthorInfo.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/AuthorInfo.java
@@ -45,6 +45,7 @@ import 
org.apache.iotdb.confignode.consensus.request.write.auth.AuthorPlan;
 import 
org.apache.iotdb.confignode.consensus.request.write.auth.AuthorRelationalPlan;
 import org.apache.iotdb.confignode.consensus.request.write.auth.AuthorTreePlan;
 import org.apache.iotdb.confignode.consensus.response.auth.PermissionInfoResp;
+import org.apache.iotdb.confignode.manager.ConfigManager;
 import org.apache.iotdb.confignode.rpc.thrift.TAuthizedPatternTreeResp;
 import org.apache.iotdb.confignode.rpc.thrift.TPermissionInfoResp;
 import org.apache.iotdb.confignode.rpc.thrift.TRoleResp;
@@ -77,11 +78,12 @@ public class AuthorInfo implements SnapshotProcessor {
   private static final String NO_USER_MSG = "No such user : ";
 
   private IAuthorizer authorizer;
+  private ConfigManager configManager;
 
-  public AuthorInfo() {
+  public AuthorInfo(ConfigManager configManager) {
     try {
       authorizer = BasicAuthorizer.getInstance();
-
+      this.configManager = configManager;
     } catch (AuthException e) {
       LOGGER.error("get user or role permissionInfo failed because ", e);
     }
@@ -651,6 +653,18 @@ public class AuthorInfo implements SnapshotProcessor {
     return result;
   }
 
+  public TPermissionInfoResp getUser(String username) throws AuthException {
+    TPermissionInfoResp result;
+    User user = authorizer.getUser(username);
+    if (user == null) {
+      throw new AuthException(
+          TSStatusCode.USER_NOT_EXIST, String.format("No such user : %s", 
username));
+    }
+    result = getUserPermissionInfo(username, ModelType.ALL);
+    result.setStatus(RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS));
+    return result;
+  }
+
   @Override
   public boolean processTakeSnapshot(File snapshotDir) throws TException, 
IOException {
     return authorizer.processTakeSnapshot(snapshotDir);
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java
index af8e7e593b4..0dec6b99ab8 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java
@@ -757,6 +757,11 @@ public class ConfigNodeRPCServiceProcessor implements 
IConfigNodeRPCService.Ifac
     return configManager.checkRoleOfUser(req.getUserName(), req.getRoleName());
   }
 
+  @Override
+  public TPermissionInfoResp getUser(String userName) {
+    return configManager.getUser(userName);
+  }
+
   @Override
   public TConfigNodeRegisterResp registerConfigNode(TConfigNodeRegisterReq 
req) {
     TConfigNodeRegisterResp resp = configManager.registerConfigNode(req);
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/AuthorInfoTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/AuthorInfoTest.java
index 8766109ac8c..8d8cb837b20 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/AuthorInfoTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/AuthorInfoTest.java
@@ -67,7 +67,7 @@ public class AuthorInfoTest {
 
   @BeforeClass
   public static void setup() {
-    authorInfo = new AuthorInfo();
+    authorInfo = new AuthorInfo(null);
     if (!snapshotDir.exists()) {
       snapshotDir.mkdirs();
     }
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/CNPhysicalPlanGeneratorTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/CNPhysicalPlanGeneratorTest.java
index b782b866847..58703cfb3f9 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/CNPhysicalPlanGeneratorTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/CNPhysicalPlanGeneratorTest.java
@@ -76,7 +76,7 @@ public class CNPhysicalPlanGeneratorTest {
   private static final String TEMPLATE_INFO_FILE_NAME = "template_info.bin";
 
   private static void setupAuthorInfo() {
-    authorInfo = new AuthorInfo();
+    authorInfo = new AuthorInfo(null);
     if (!snapshotDir.exists()) {
       snapshotDir.mkdir();
     }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java
index 23b0efcd1e2..ef3bb7a9746 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/ClusterAuthorityFetcher.java
@@ -379,6 +379,7 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
       if (TSStatusCode.SUCCESS_STATUS.getStatusCode() != tsStatus.getCode()) {
         future.setException(new IoTDBException(tsStatus.message, 
tsStatus.code));
       } else {
+        onOperatePermissionSuccess(plan);
         future.set(new ConfigTaskResult(TSStatusCode.SUCCESS_STATUS));
       }
     } catch (AuthException e) {
@@ -390,6 +391,16 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
     return future;
   }
 
+  private void onOperatePermissionSuccess(Object plan) {
+    if (plan instanceof RelationalAuthorStatement) {
+      RelationalAuthorStatement stmt = (RelationalAuthorStatement) plan;
+      stmt.onSuccess();
+    } else if (plan instanceof AuthorStatement) {
+      AuthorStatement stmt = (AuthorStatement) plan;
+      stmt.onSuccess();
+    }
+  }
+
   @Override
   public SettableFuture<ConfigTaskResult> operatePermission(AuthorStatement 
authorStatement) {
     return operatePermissionInternal(authorStatement, false);
@@ -511,6 +522,32 @@ public class ClusterAuthorityFetcher implements 
IAuthorityFetcher {
     }
   }
 
+  public User getUser(String userName) {
+    checkCacheAvailable();
+    User user = iAuthorCache.getUserCache(userName);
+    if (user != null) {
+      return user;
+    } else {
+      TPermissionInfoResp permissionInfoResp = null;
+      try (ConfigNodeClient configNodeClient =
+          
CONFIG_NODE_CLIENT_MANAGER.borrowClient(ConfigNodeInfo.CONFIG_REGION_ID)) {
+        // Send request to some API server
+        permissionInfoResp = configNodeClient.getUser(userName);
+      } catch (ClientManagerException | TException e) {
+        LOGGER.error(CONNECTERROR);
+      }
+      user = cacheUser(permissionInfoResp);
+      if (permissionInfoResp != null
+          && permissionInfoResp.getStatus().getCode()
+              == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+        if (acceptCache) {
+          iAuthorCache.putUserCache(userName, user);
+        }
+      }
+    }
+    return user;
+  }
+
   @Override
   public boolean checkRole(String userName, String roleName) {
     checkCacheAvailable();
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java
index c9f2970ad46..6b93c211fd6 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/IAuthorityFetcher.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.db.auth;
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.auth.AuthException;
 import org.apache.iotdb.commons.auth.entity.PrivilegeType;
+import org.apache.iotdb.commons.auth.entity.User;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.path.PathPatternTree;
 import org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult;
@@ -79,4 +80,6 @@ public interface IAuthorityFetcher {
   IAuthorCache getAuthorCache();
 
   void refreshToken();
+
+  User getUser(String username);
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
index dfd854f724c..d04a6916e35 100755
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
@@ -2040,6 +2040,24 @@ public class IoTDBDescriptor {
                       ConfigurationFileUtils.getConfigurationDefaultValue(
                           "timestamp_precision_check_enabled"))));
 
+      commonDescriptor
+          .getConfig()
+          .setPasswordExpirationSeconds(
+              Long.parseLong(
+                  properties.getProperty(
+                      "password_expiration_seconds",
+                      String.valueOf(
+                          
commonDescriptor.getConfig().getPasswordExpirationSeconds()))));
+
+      commonDescriptor
+          .getConfig()
+          .setPasswordReuseIntervalSeconds(
+              Long.parseLong(
+                  properties.getProperty(
+                      "password_reuse_interval_seconds",
+                      String.valueOf(
+                          
commonDescriptor.getConfig().getPasswordReuseIntervalSeconds()))));
+
       conf.setEnablePartialInsert(
           Boolean.parseBoolean(
               Optional.ofNullable(
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java
index 9d0dcd22f91..fee3a116c4d 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java
@@ -701,6 +701,12 @@ public class ConfigNodeClient implements 
IConfigNodeRPCService.Iface, ThriftClie
         () -> client.login(req), resp -> !updateConfigNodeLeader(resp.status));
   }
 
+  @Override
+  public TPermissionInfoResp getUser(String userName) throws TException {
+    return executeRemoteCallWithRetry(
+        () -> client.getUser(userName), resp -> 
!updateConfigNodeLeader(resp.status));
+  }
+
   @Override
   public TPermissionInfoResp checkUserPrivileges(TCheckUserPrivilegesReq req) 
throws TException {
     return executeRemoteCallWithRetry(
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
index 764d5e9fb25..420158cd3cf 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/session/SessionManager.java
@@ -20,18 +20,29 @@
 package org.apache.iotdb.db.protocol.session;
 
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.conf.CommonDescriptor;
 import org.apache.iotdb.commons.conf.IoTDBConstant;
+import org.apache.iotdb.commons.exception.IoTDBException;
+import org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
 import org.apache.iotdb.commons.service.JMXService;
 import org.apache.iotdb.commons.service.ServiceType;
 import org.apache.iotdb.commons.service.metric.MetricService;
 import org.apache.iotdb.commons.service.metric.enums.Metric;
 import org.apache.iotdb.commons.service.metric.enums.Tag;
+import org.apache.iotdb.commons.utils.AuthUtils;
 import org.apache.iotdb.db.audit.AuditLogger;
 import org.apache.iotdb.db.auth.AuthorityChecker;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.protocol.basic.BasicOpenSessionResp;
 import org.apache.iotdb.db.protocol.thrift.OperationType;
 import org.apache.iotdb.db.queryengine.common.SessionInfo;
+import org.apache.iotdb.db.queryengine.plan.Coordinator;
+import org.apache.iotdb.db.queryengine.plan.analyze.ClusterPartitionFetcher;
+import 
org.apache.iotdb.db.queryengine.plan.analyze.schema.ClusterSchemaFetcher;
+import org.apache.iotdb.db.queryengine.plan.execution.ExecutionResult;
+import org.apache.iotdb.db.queryengine.plan.execution.IQueryExecution;
+import org.apache.iotdb.db.queryengine.plan.parser.StatementGenerator;
+import org.apache.iotdb.db.queryengine.plan.statement.Statement;
 import org.apache.iotdb.db.queryengine.plan.statement.StatementType;
 import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement;
 import 
org.apache.iotdb.db.storageengine.dataregion.read.control.QueryResourceManager;
@@ -41,15 +52,19 @@ import org.apache.iotdb.rpc.RpcUtils;
 import org.apache.iotdb.rpc.TSStatusCode;
 import org.apache.iotdb.service.rpc.thrift.TSConnectionInfo;
 import org.apache.iotdb.service.rpc.thrift.TSConnectionInfoResp;
+import org.apache.iotdb.service.rpc.thrift.TSLastDataQueryReq;
 import org.apache.iotdb.service.rpc.thrift.TSProtocolVersion;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.tsfile.read.common.block.TsBlock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.time.ZoneId;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.concurrent.ConcurrentHashMap;
@@ -114,6 +129,63 @@ public class SessionManager implements SessionManagerMBean 
{
         IClientSession.SqlDialect.TREE);
   }
 
+  private TSStatus checkPasswordExpiration(String username, String password) {
+    // check password expiration
+    long passwordExpirationSeconds =
+        
CommonDescriptor.getInstance().getConfig().getPasswordExpirationSeconds();
+    if (passwordExpirationSeconds <= 0
+        || 
username.equals(CommonDescriptor.getInstance().getConfig().getAdminName())) {
+      return null;
+    }
+
+    TSLastDataQueryReq lastDataQueryReq = new TSLastDataQueryReq();
+    lastDataQueryReq.setSessionId(0);
+    lastDataQueryReq.setPaths(
+        Collections.singletonList(
+            SystemConstant.PREFIX_PASSWORD_HISTORY + ".`" + username + 
"`.password"));
+    try {
+      Statement statement = 
StatementGenerator.createStatement(lastDataQueryReq);
+      SessionInfo sessionInfo =
+          new SessionInfo(0, AuthorityChecker.SUPER_USER, 
ZoneId.systemDefault());
+
+      long queryId = requestQueryId();
+      ExecutionResult result =
+          Coordinator.getInstance()
+              .executeForTreeModel(
+                  statement,
+                  queryId,
+                  sessionInfo,
+                  "",
+                  ClusterPartitionFetcher.getInstance(),
+                  ClusterSchemaFetcher.getInstance());
+      if (result.status.getCode() != 
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+        LOGGER.warn("Fail to check password expiration: {}", result.status);
+        return null;
+      }
+
+      IQueryExecution queryExecution = 
Coordinator.getInstance().getQueryExecution(queryId);
+      Optional<TsBlock> batchResult = queryExecution.getBatchResult();
+      if (batchResult.isPresent()) {
+        TsBlock tsBlock = batchResult.get();
+        if (tsBlock.getPositionCount() <= 0) {
+          // no password history, may have upgraded from an older version
+          return null;
+        }
+        long lastPasswordTime = tsBlock.getTimeByIndex(0);
+        // columns of last query: [timeseriesName, value, dataType]
+        String oldPassword = tsBlock.getColumn(1).getBinary(0).toString();
+        if (oldPassword.equals(AuthUtils.encryptPassword(password))
+            && System.currentTimeMillis() - lastPasswordTime > 
passwordExpirationSeconds * 1000) {
+          return new TSStatus(TSStatusCode.ILLEGAL_PASSWORD.getStatusCode())
+              .setMessage("Password has expired, please change to a new one");
+        }
+      }
+    } catch (IoTDBException e) {
+      LOGGER.warn("Cannot generate query for checking password expiration", e);
+    }
+    return null;
+  }
+
   public BasicOpenSessionResp login(
       IClientSession session,
       String username,
@@ -125,6 +197,15 @@ public class SessionManager implements SessionManagerMBean 
{
     TSStatus loginStatus;
     BasicOpenSessionResp openSessionResp = new BasicOpenSessionResp();
 
+    loginStatus = checkPasswordExpiration(username, password);
+    if (loginStatus != null) {
+      openSessionResp
+          .sessionId(-1)
+          .setCode(loginStatus.getCode())
+          .setMessage(loginStatus.getMessage());
+      return openSessionResp;
+    }
+
     loginStatus = AuthorityChecker.checkUser(username, password);
     if (loginStatus.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
       // check the version compatibility
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
index 368b3b027d1..69818f707c9 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
@@ -84,6 +84,7 @@ import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.consensus.DataRegionConsensusImpl;
 import org.apache.iotdb.db.consensus.SchemaRegionConsensusImpl;
 import org.apache.iotdb.db.exception.StorageEngineException;
+import org.apache.iotdb.db.exception.query.QueryProcessException;
 import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
 import org.apache.iotdb.db.protocol.client.ConfigNodeInfo;
 import 
org.apache.iotdb.db.protocol.client.cn.DnToCnInternalServiceAsyncRequestManager;
@@ -98,6 +99,7 @@ import 
org.apache.iotdb.db.protocol.session.InternalClientSession;
 import org.apache.iotdb.db.protocol.session.SessionManager;
 import org.apache.iotdb.db.protocol.thrift.OperationType;
 import org.apache.iotdb.db.queryengine.common.FragmentInstanceId;
+import org.apache.iotdb.db.queryengine.common.SessionInfo;
 import 
org.apache.iotdb.db.queryengine.execution.executor.RegionExecutionResult;
 import org.apache.iotdb.db.queryengine.execution.executor.RegionReadExecutor;
 import org.apache.iotdb.db.queryengine.execution.executor.RegionWriteExecutor;
@@ -156,6 +158,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.Table
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.DeleteDevice;
 import org.apache.iotdb.db.queryengine.plan.scheduler.load.LoadTsFileScheduler;
 import org.apache.iotdb.db.queryengine.plan.statement.component.WhereCondition;
+import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowStatement;
 import org.apache.iotdb.db.queryengine.plan.statement.crud.QueryStatement;
 import org.apache.iotdb.db.queryengine.plan.udf.UDFManagementService;
 import org.apache.iotdb.db.schemaengine.SchemaEngine;
@@ -283,6 +286,7 @@ import 
org.apache.iotdb.mpp.rpc.thrift.TUpdateTriggerLocationReq;
 import org.apache.iotdb.rpc.RpcUtils;
 import org.apache.iotdb.rpc.TSStatusCode;
 import org.apache.iotdb.rpc.subscription.exception.SubscriptionException;
+import org.apache.iotdb.service.rpc.thrift.TSInsertRecordReq;
 import org.apache.iotdb.trigger.api.enums.FailureStrategy;
 import org.apache.iotdb.trigger.api.enums.TriggerEvent;
 
@@ -330,6 +334,7 @@ import java.util.stream.Stream;
 import static 
org.apache.iotdb.commons.client.request.TestConnectionUtils.testConnectionsImpl;
 import static 
org.apache.iotdb.commons.conf.IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD;
 import static 
org.apache.iotdb.db.service.RegionMigrateService.REGION_MIGRATE_PROCESS;
+import static org.apache.iotdb.db.utils.ErrorHandlingUtils.onIoTDBException;
 import static org.apache.iotdb.db.utils.ErrorHandlingUtils.onQueryException;
 
 public class DataNodeInternalRPCServiceImpl implements 
IDataNodeRPCService.Iface {
@@ -2827,6 +2832,23 @@ public class DataNodeInternalRPCServiceImpl implements 
IDataNodeRPCService.Iface
     return status;
   }
 
+  @Override
+  public TSStatus insertRecord(TSInsertRecordReq req) throws TException {
+    try {
+      InsertRowStatement statement = StatementGenerator.createStatement(req);
+      SessionInfo sessionInfo =
+          new SessionInfo(0, AuthorityChecker.SUPER_USER, 
ZoneId.systemDefault());
+
+      long queryId = SESSION_MANAGER.requestQueryId();
+      ExecutionResult result =
+          COORDINATOR.executeForTreeModel(
+              statement, queryId, sessionInfo, "", partitionFetcher, 
schemaFetcher);
+      return result.status;
+    } catch (IllegalPathException | QueryProcessException e) {
+      return onIoTDBException(e, OperationType.INSERT_RECORD, 
e.getErrorCode());
+    }
+  }
+
   public void handleClientExit() {
     // Do nothing
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
index a169096341f..8155643951e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
@@ -20,6 +20,7 @@
 package org.apache.iotdb.db.queryengine.plan.execution.config;
 
 import org.apache.iotdb.common.rpc.thrift.Model;
+import org.apache.iotdb.commons.auth.entity.User;
 import org.apache.iotdb.commons.exception.IllegalPathException;
 import org.apache.iotdb.commons.exception.auth.AccessDeniedException;
 import org.apache.iotdb.commons.executable.ExecutableManager;
@@ -34,6 +35,7 @@ import 
org.apache.iotdb.commons.schema.table.column.TimeColumnSchema;
 import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
 import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema;
 import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
+import org.apache.iotdb.db.auth.AuthorityChecker;
 import org.apache.iotdb.db.conf.IoTDBConfig;
 import org.apache.iotdb.db.exception.sql.SemanticException;
 import org.apache.iotdb.db.protocol.session.IClientSession;
@@ -192,6 +194,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.StopRepairData;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Use;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ViewFieldDefinition;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.rewrite.StatementRewrite;
+import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType;
 import 
org.apache.iotdb.db.queryengine.plan.relational.type.TypeNotFoundException;
 import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.DatabaseSchemaStatement;
 import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.RemoveConfigNodeStatement;
@@ -204,6 +207,7 @@ import 
org.apache.iotdb.db.queryengine.plan.statement.sys.SetConfigurationStatem
 import 
org.apache.iotdb.db.queryengine.plan.statement.sys.SetSystemStatusStatement;
 import 
org.apache.iotdb.db.queryengine.plan.statement.sys.StartRepairDataStatement;
 import 
org.apache.iotdb.db.queryengine.plan.statement.sys.StopRepairDataStatement;
+import org.apache.iotdb.db.utils.DataNodeAuthUtils;
 import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
 
 import org.apache.tsfile.common.conf.TSFileConfig;
@@ -1281,9 +1285,21 @@ public class TableConfigTaskVisitor extends 
AstVisitor<IConfigTask, MPPQueryCont
     accessControl.checkUserCanRunRelationalAuthorStatement(
         context.getSession().getUserName(), node);
     context.setQueryType(node.getQueryType());
+    if (node.getAuthorType() == AuthorRType.UPDATE_USER) {
+      visitUpdateUser(node);
+    }
     return new RelationalAuthorizerTask(node);
   }
 
+  private void visitUpdateUser(RelationalAuthorStatement node) {
+    User user = 
AuthorityChecker.getAuthorityFetcher().getUser(node.getUserName());
+    if (user == null) {
+      throw new SemanticException("User " + node.getUserName() + " not found");
+    }
+    node.setOldPassword(user.getPassword());
+    DataNodeAuthUtils.verifyPasswordReuse(node.getUserName(), 
node.getPassword());
+  }
+
   @Override
   protected IConfigTask visitKillQuery(KillQuery node, MPPQueryContext 
context) {
     context.setQueryType(QueryType.WRITE);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java
index 282839ec560..4b2e0c7bcdf 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java
@@ -20,9 +20,11 @@
 package org.apache.iotdb.db.queryengine.plan.execution.config;
 
 import org.apache.iotdb.common.rpc.thrift.Model;
+import org.apache.iotdb.commons.auth.entity.User;
 import org.apache.iotdb.commons.executable.ExecutableManager;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
+import org.apache.iotdb.db.auth.AuthorityChecker;
 import org.apache.iotdb.db.exception.sql.SemanticException;
 import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.metadata.CountDatabaseTask;
@@ -108,6 +110,7 @@ import 
org.apache.iotdb.db.queryengine.plan.execution.config.sys.subscription.Dr
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.sys.subscription.DropTopicTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.sys.subscription.ShowSubscriptionsTask;
 import 
org.apache.iotdb.db.queryengine.plan.execution.config.sys.subscription.ShowTopicsTask;
+import org.apache.iotdb.db.queryengine.plan.statement.AuthorType;
 import org.apache.iotdb.db.queryengine.plan.statement.Statement;
 import org.apache.iotdb.db.queryengine.plan.statement.StatementNode;
 import org.apache.iotdb.db.queryengine.plan.statement.StatementVisitor;
@@ -193,6 +196,7 @@ import 
org.apache.iotdb.db.queryengine.plan.statement.sys.quota.SetSpaceQuotaSta
 import 
org.apache.iotdb.db.queryengine.plan.statement.sys.quota.SetThrottleQuotaStatement;
 import 
org.apache.iotdb.db.queryengine.plan.statement.sys.quota.ShowSpaceQuotaStatement;
 import 
org.apache.iotdb.db.queryengine.plan.statement.sys.quota.ShowThrottleQuotaStatement;
+import org.apache.iotdb.db.utils.DataNodeAuthUtils;
 
 import org.apache.tsfile.exception.NotImplementedException;
 
@@ -292,9 +296,21 @@ public class TreeConfigTaskVisitor extends 
StatementVisitor<IConfigTask, MPPQuer
 
   @Override
   public IConfigTask visitAuthor(AuthorStatement statement, MPPQueryContext 
context) {
+    if (statement.getAuthorType() == AuthorType.UPDATE_USER) {
+      visitUpdateUser(statement);
+    }
     return new AuthorizerTask(statement);
   }
 
+  private void visitUpdateUser(AuthorStatement statement) {
+    User user = 
AuthorityChecker.getAuthorityFetcher().getUser(statement.getUserName());
+    if (user == null) {
+      throw new SemanticException("User " + statement.getUserName() + " not 
found");
+    }
+    statement.setPassWord(user.getPassword());
+    DataNodeAuthUtils.verifyPasswordReuse(statement.getUserName(), 
statement.getNewPassword());
+  }
+
   @Override
   public IConfigTask visitMerge(MergeStatement mergeStatement, MPPQueryContext 
context) {
     return new MergeTask(mergeStatement);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java
index 71c1962f2ea..74454e0eaf3 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/RelationalAuthorStatement.java
@@ -18,9 +18,13 @@
  */
 package org.apache.iotdb.db.queryengine.plan.relational.sql.ast;
 
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.auth.entity.PrivilegeType;
 import org.apache.iotdb.db.queryengine.plan.analyze.QueryType;
 import org.apache.iotdb.db.queryengine.plan.relational.type.AuthorRType;
+import org.apache.iotdb.db.utils.DataNodeAuthUtils;
+import org.apache.iotdb.rpc.RpcUtils;
+import org.apache.iotdb.rpc.StatementExecutionException;
 
 import com.google.common.collect.ImmutableList;
 
@@ -40,6 +44,7 @@ public class RelationalAuthorStatement extends Statement {
   private String roleName;
 
   private String password;
+  private String oldPassword;
 
   private Set<PrivilegeType> privilegeType;
 
@@ -244,4 +249,46 @@ public class RelationalAuthorStatement extends Statement {
         + ", grantOption:"
         + grantOption;
   }
+
+  /**
+   * Post-process when the statement is successfully executed.
+   *
+   * @return null if the post-process succeeds, a status otherwise.
+   */
+  public TSStatus onSuccess() {
+    if (authorType == AuthorRType.CREATE_USER) {
+      return onCreateUserSuccess();
+    } else if (authorType == AuthorRType.UPDATE_USER) {
+      return onUpdateUserSuccess();
+    }
+    return null;
+  }
+
+  private TSStatus onCreateUserSuccess() {
+    TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, 
null);
+    try {
+      RpcUtils.verifySuccess(tsStatus);
+    } catch (StatementExecutionException e) {
+      return new TSStatus(e.getStatusCode()).setMessage(e.getMessage());
+    }
+    return null;
+  }
+
+  private TSStatus onUpdateUserSuccess() {
+    TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, 
oldPassword);
+    try {
+      RpcUtils.verifySuccess(tsStatus);
+    } catch (StatementExecutionException e) {
+      return new TSStatus(e.getStatusCode()).setMessage(e.getMessage());
+    }
+    return null;
+  }
+
+  public String getOldPassword() {
+    return oldPassword;
+  }
+
+  public void setOldPassword(String oldPassword) {
+    this.oldPassword = oldPassword;
+  }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java
index 0c905c45d18..fc0481fa656 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/sys/AuthorStatement.java
@@ -29,6 +29,9 @@ import 
org.apache.iotdb.db.queryengine.plan.statement.IConfigStatement;
 import org.apache.iotdb.db.queryengine.plan.statement.Statement;
 import org.apache.iotdb.db.queryengine.plan.statement.StatementType;
 import org.apache.iotdb.db.queryengine.plan.statement.StatementVisitor;
+import org.apache.iotdb.db.utils.DataNodeAuthUtils;
+import org.apache.iotdb.rpc.RpcUtils;
+import org.apache.iotdb.rpc.StatementExecutionException;
 import org.apache.iotdb.rpc.TSStatusCode;
 
 import java.util.Collections;
@@ -345,4 +348,38 @@ public class AuthorStatement extends Statement implements 
IConfigStatement {
         throw new IllegalArgumentException("Unknown authorType: " + 
authorType);
     }
   }
+
+  /**
+   * Post-process when the statement is successfully executed.
+   *
+   * @return null if the post-process succeeds, a status otherwise.
+   */
+  public TSStatus onSuccess() {
+    if (authorType == AuthorType.CREATE_USER) {
+      return onCreateUserSuccess();
+    } else if (authorType == AuthorType.UPDATE_USER) {
+      return onUpdateUserSuccess();
+    }
+    return null;
+  }
+
+  private TSStatus onCreateUserSuccess() {
+    TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, password, 
null);
+    try {
+      RpcUtils.verifySuccess(tsStatus);
+    } catch (StatementExecutionException e) {
+      return new TSStatus(e.getStatusCode()).setMessage(e.getMessage());
+    }
+    return null;
+  }
+
+  private TSStatus onUpdateUserSuccess() {
+    TSStatus tsStatus = DataNodeAuthUtils.recordPassword(userName, 
newPassword, password);
+    try {
+      RpcUtils.verifySuccess(tsStatus);
+    } catch (StatementExecutionException e) {
+      return new TSStatus(e.getStatusCode()).setMessage(e.getMessage());
+    }
+    return null;
+  }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DataNodeAuthUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DataNodeAuthUtils.java
new file mode 100644
index 00000000000..d60ed5c9e27
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/DataNodeAuthUtils.java
@@ -0,0 +1,172 @@
+/*
+ * 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.iotdb.db.utils;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.conf.CommonDescriptor;
+import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.exception.IoTDBException;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
+import org.apache.iotdb.commons.utils.AuthUtils;
+import org.apache.iotdb.db.auth.AuthorityChecker;
+import org.apache.iotdb.db.exception.sql.SemanticException;
+import org.apache.iotdb.db.protocol.session.SessionManager;
+import org.apache.iotdb.db.queryengine.common.SessionInfo;
+import org.apache.iotdb.db.queryengine.plan.Coordinator;
+import org.apache.iotdb.db.queryengine.plan.analyze.ClusterPartitionFetcher;
+import 
org.apache.iotdb.db.queryengine.plan.analyze.schema.ClusterSchemaFetcher;
+import org.apache.iotdb.db.queryengine.plan.execution.ExecutionResult;
+import org.apache.iotdb.db.queryengine.plan.execution.IQueryExecution;
+import org.apache.iotdb.db.queryengine.plan.parser.StatementGenerator;
+import org.apache.iotdb.db.queryengine.plan.statement.Statement;
+import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertRowStatement;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.read.common.block.TsBlock;
+import org.apache.tsfile.utils.Binary;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.Optional;
+
+public class DataNodeAuthUtils {
+
+  private DataNodeAuthUtils() {}
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(DataNodeAuthUtils.class);
+
+  /**
+   * @return the timestamp when the password of the user is lastly changed 
from the given one to a
+   *     new one, or -1 if the password has not been used.
+   */
+  public static long getPasswordChangeTime(String username, String password) {
+
+    try {
+      Statement statement =
+          StatementGenerator.createStatement(
+              "SELECT password from "
+                  + SystemConstant.PREFIX_PASSWORD_HISTORY
+                  + ".`"
+                  + username
+                  + "` where oldPassword='"
+                  + AuthUtils.encryptPassword(password)
+                  + "'",
+              ZoneId.systemDefault());
+      SessionInfo sessionInfo =
+          new SessionInfo(0, AuthorityChecker.SUPER_USER, 
ZoneId.systemDefault());
+
+      long queryId = SessionManager.getInstance().requestQueryId();
+      ExecutionResult result =
+          Coordinator.getInstance()
+              .executeForTreeModel(
+                  statement,
+                  queryId,
+                  sessionInfo,
+                  "",
+                  ClusterPartitionFetcher.getInstance(),
+                  ClusterSchemaFetcher.getInstance());
+      if (result.status.getCode() != 
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+        LOGGER.warn("Fail to get password change time: {}", result.status);
+        return -1;
+      }
+
+      IQueryExecution queryExecution = 
Coordinator.getInstance().getQueryExecution(queryId);
+      TsBlock lastTsBlock = null;
+      Optional<TsBlock> batchResult = queryExecution.getBatchResult();
+      while (batchResult.isPresent()) {
+        if (!batchResult.get().isEmpty()) {
+          lastTsBlock = batchResult.get();
+        }
+        batchResult = queryExecution.getBatchResult();
+      }
+      if (lastTsBlock != null) {
+        if (lastTsBlock.getPositionCount() <= 0) {
+          // no password history, may have upgraded from an older version
+          return -1;
+        }
+        return lastTsBlock.getTimeByIndex(lastTsBlock.getPositionCount() - 1);
+      }
+    } catch (IoTDBException e) {
+      LOGGER.warn("Cannot generate query for checking password expiration", e);
+    }
+    return -1;
+  }
+
+  public static void verifyPasswordReuse(String username, String password) {
+    long passwordReuseIntervalSeconds =
+        
CommonDescriptor.getInstance().getConfig().getPasswordReuseIntervalSeconds();
+    if (password == null || passwordReuseIntervalSeconds <= 0) {
+      return;
+    }
+
+    long passwordChangeTime = 
DataNodeAuthUtils.getPasswordChangeTime(username, password);
+    long currentTimeMillis = System.currentTimeMillis();
+    long elapsedTime = currentTimeMillis - passwordChangeTime;
+    if (elapsedTime <= passwordReuseIntervalSeconds * 1000) {
+      throw new SemanticException(
+          String.format(
+              "The password has been used at %s and it cannot be reused before 
%s",
+              new Date(passwordChangeTime),
+              new Date(passwordChangeTime + passwordReuseIntervalSeconds)));
+    }
+    LOGGER.info(
+        "It has been {}ms, since the password was changed {} -> {}",
+        elapsedTime,
+        passwordChangeTime,
+        currentTimeMillis);
+  }
+
+  public static TSStatus recordPassword(String username, String password, 
String oldPassword) {
+    InsertRowStatement insertRowStatement = new InsertRowStatement();
+    try {
+      insertRowStatement.setDevicePath(
+          new PartialPath(SystemConstant.PREFIX_PASSWORD_HISTORY + ".`" + 
username + "`"));
+      insertRowStatement.setTime(System.currentTimeMillis());
+      insertRowStatement.setMeasurements(new String[] {"password", 
"oldPassword"});
+      insertRowStatement.setValues(
+          new Object[] {
+            new Binary(AuthUtils.encryptPassword(password), 
StandardCharsets.UTF_8),
+            oldPassword == null ? null : new Binary(oldPassword, 
StandardCharsets.UTF_8)
+          });
+      insertRowStatement.setDataTypes(new TSDataType[] {TSDataType.STRING, 
TSDataType.STRING});
+    } catch (IllegalPathException ignored) {
+    }
+
+    SessionInfo sessionInfo =
+        new SessionInfo(0, AuthorityChecker.SUPER_USER, 
ZoneId.systemDefault());
+
+    long queryId = SessionManager.getInstance().requestQueryId();
+    ExecutionResult result =
+        Coordinator.getInstance()
+            .executeForTreeModel(
+                insertRowStatement,
+                queryId,
+                sessionInfo,
+                "",
+                ClusterPartitionFetcher.getInstance(),
+                ClusterSchemaFetcher.getInstance());
+    return result.status;
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
 
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
index 48c97ad52d1..f7245ab9360 100644
--- 
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
+++ 
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
@@ -1670,6 +1670,28 @@ author_cache_expire_time=30
 # If you want to allow all URIs, you can specify it as .*
 trusted_uri_pattern=file:.*
 
+# When enabled, passwords of new users or new passwords of existing users must 
follow the following:
+# 1. contain at least one lower case letter;
+# 2. contain at least one upper case letter;
+# 3. contain at least one digit;
+# 4. contain at least one special character (another character except the 
above three).
+# Datatype: boolean
+# effectiveMode: hot_reload
+enforce_strong_password=false
+
+# When > 0, the password of a user will expire after last 
modification/creation in the giving
+# seconds, and the user can no longer login unless the user changes the 
password.
+# Notice: if the cluster have upgraded from an old version, the will not take 
effective
+# Datatype: int
+# effectiveMode: hot_reload
+password_expiration_seconds=0
+
+# When > 0, the old password of a user cannot be reused within the seconds 
specified by this item,
+# after a successful password change.
+# Datatype: int
+# effectiveMode: hot_reload
+password_reuse_interval_seconds=0
+
 ####################
 ### UDF Configuration
 ####################
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/BasicUserManager.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/BasicUserManager.java
index bc2c283a363..4e041e02253 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/BasicUserManager.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/BasicUserManager.java
@@ -107,7 +107,12 @@ public abstract class BasicUserManager extends 
BasicRoleManager {
   public boolean createUser(
       String username, String password, boolean validCheck, boolean 
enableEncrypt)
       throws AuthException {
-    if (validCheck) {
+    if (validCheck && 
!CommonDescriptor.getInstance().getConfig().getAdminName().equals(username)) {
+      if (username.equals(password)
+          && 
CommonDescriptor.getInstance().getConfig().isEnforceStrongPassword()) {
+        throw new AuthException(
+            TSStatusCode.ILLEGAL_PASSWORD, "Password cannot be the same as 
user name");
+      }
       AuthUtils.validateUsername(username);
       if (enableEncrypt) {
         AuthUtils.validatePassword(password);
@@ -129,12 +134,12 @@ public abstract class BasicUserManager extends 
BasicRoleManager {
   }
 
   public boolean updateUserPassword(String username, String newPassword) 
throws AuthException {
-    try {
-      AuthUtils.validatePassword(newPassword);
-    } catch (AuthException e) {
-      LOGGER.debug("An illegal password detected ", e);
-      return false;
+    if (CommonDescriptor.getInstance().getConfig().isEnforceStrongPassword()
+        && username.equals(newPassword)) {
+      throw new AuthException(
+          TSStatusCode.ILLEGAL_PASSWORD, "Password cannot be the same as user 
name");
     }
+    AuthUtils.validatePassword(newPassword);
 
     lock.writeLock(username);
     try {
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java
index 3004ace2e30..12e2b9df0dd 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java
@@ -398,6 +398,11 @@ public class CommonConfig {
 
   private volatile Pattern trustedUriPattern = Pattern.compile("file:.*");
 
+  private boolean enforceStrongPassword = false;
+  private long passwordExpirationSeconds = 0;
+  // an old password cannot be reused within the given interval if > 0.
+  private long passwordReuseIntervalSeconds = 0;
+
   CommonConfig() {
     // Empty constructor
   }
@@ -2473,4 +2478,28 @@ public class CommonConfig {
   public void setTrustedUriPattern(Pattern trustedUriPattern) {
     this.trustedUriPattern = trustedUriPattern;
   }
+
+  public boolean isEnforceStrongPassword() {
+    return enforceStrongPassword;
+  }
+
+  public void setEnforceStrongPassword(boolean enforceStrongPassword) {
+    this.enforceStrongPassword = enforceStrongPassword;
+  }
+
+  public long getPasswordExpirationSeconds() {
+    return passwordExpirationSeconds;
+  }
+
+  public void setPasswordExpirationSeconds(long passwordExpirationSeconds) {
+    this.passwordExpirationSeconds = passwordExpirationSeconds;
+  }
+
+  public long getPasswordReuseIntervalSeconds() {
+    return passwordReuseIntervalSeconds;
+  }
+
+  public void setPasswordReuseIntervalSeconds(long 
passwordReuseIntervalSeconds) {
+    this.passwordReuseIntervalSeconds = passwordReuseIntervalSeconds;
+  }
 }
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java
index ab9348f9de3..a6049865605 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonDescriptor.java
@@ -251,6 +251,23 @@ public class CommonDescriptor {
                 "cluster_device_limit_threshold",
                 String.valueOf(config.getDeviceLimitThreshold()))));
 
+    config.setEnforceStrongPassword(
+        Boolean.parseBoolean(
+            properties.getProperty(
+                "enforce_strong_password", 
String.valueOf(config.isEnforceStrongPassword()))));
+
+    config.setPasswordExpirationSeconds(
+        Long.parseLong(
+            properties.getProperty(
+                "password_expiration_seconds",
+                String.valueOf(config.getPasswordExpirationSeconds()))));
+
+    config.setPasswordReuseIntervalSeconds(
+        Long.parseLong(
+            properties.getProperty(
+                "password_reuse_interval_seconds",
+                String.valueOf(config.getPasswordReuseIntervalSeconds()))));
+
     loadRetryProperties(properties);
     loadBinaryAllocatorProps(properties);
   }
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/SystemConstant.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/SystemConstant.java
index 105390eab9c..175dee723dc 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/SystemConstant.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/SystemConstant.java
@@ -29,6 +29,8 @@ import java.util.Set;
 public class SystemConstant {
 
   public static final String SYSTEM_PREFIX_KEY = "__system";
+  public static final String PREFIX_PASSWORD_HISTORY =
+      "root." + SYSTEM_PREFIX_KEY + ".password_history";
 
   public static final String RESTART_KEY = "__system.restart";
   public static final boolean RESTART_DEFAULT_VALUE = false;
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java
index 64e1420e081..86bcd1a3afd 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/AuthUtils.java
@@ -69,6 +69,43 @@ public class AuthUtils {
    */
   public static void validatePassword(String password) throws AuthException {
     validateNameOrPassword(password);
+    if (CommonDescriptor.getInstance().getConfig().isEnforceStrongPassword()) {
+      boolean hasLowerCase = false;
+      boolean hasUpperCase = false;
+      boolean hasDigit = false;
+      boolean hasSpecialChar = false;
+      for (int i = 0; i < password.length(); i++) {
+        char c = password.charAt(i);
+        if (Character.isLowerCase(c)) {
+          hasLowerCase = true;
+        } else if (Character.isUpperCase(c)) {
+          hasUpperCase = true;
+        } else if (Character.isDigit(c)) {
+          hasDigit = true;
+        } else {
+          hasSpecialChar = true;
+        }
+      }
+
+      if (!hasLowerCase || !hasUpperCase || !hasDigit || !hasSpecialChar) {
+        StringBuilder builder = new StringBuilder("Invalid password, must 
contain at least");
+        if (!hasLowerCase) {
+          builder.append(" one lowercase letter,");
+        }
+        if (!hasUpperCase) {
+          builder.append(" one uppercase letter,");
+        }
+        if (!hasDigit) {
+          builder.append(" one digit,");
+        }
+        if (!hasSpecialChar) {
+          builder.append(" one special character,");
+        }
+        builder.deleteCharAt(builder.length() - 1);
+        builder.append(".");
+        throw new AuthException(TSStatusCode.ILLEGAL_PASSWORD, 
builder.toString());
+      }
+    }
   }
 
   /**
@@ -108,18 +145,18 @@ public class AuthUtils {
     int length = str.length();
     if (length < MIN_LENGTH) {
       throw new AuthException(
-          TSStatusCode.ILLEGAL_PARAMETER,
+          TSStatusCode.ILLEGAL_PASSWORD,
           "The length of name or password must be greater than or equal to " + 
MIN_LENGTH);
     } else if (length > MAX_LENGTH) {
       throw new AuthException(
-          TSStatusCode.ILLEGAL_PARAMETER,
+          TSStatusCode.ILLEGAL_PASSWORD,
           "The length of name or password must be less than or equal to " + 
MAX_LENGTH);
     } else if (str.contains(" ")) {
       throw new AuthException(
-          TSStatusCode.ILLEGAL_PARAMETER, "The name or password cannot contain 
spaces");
+          TSStatusCode.ILLEGAL_PASSWORD, "The name or password cannot contain 
spaces");
     } else if (!str.matches(REX_PATTERN)) {
       throw new AuthException(
-          TSStatusCode.ILLEGAL_PARAMETER,
+          TSStatusCode.ILLEGAL_PASSWORD,
           "The name or password can only contain letters, numbers or 
!@#$%^*()_+-=");
     }
   }
diff --git a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift 
b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
index 3f4c42d058a..593fc87b68d 100644
--- a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
+++ b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
@@ -1512,6 +1512,8 @@ service IConfigNodeRPCService {
 
   TPermissionInfoResp checkRoleOfUser(TAuthorizerReq req)
 
+  TPermissionInfoResp getUser(string userName);
+
 
 
   // ======================================================
diff --git a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift 
b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
index 70456a952f3..21b6388ff2e 100644
--- a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
+++ b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
@@ -17,6 +17,7 @@
  * under the License.
  */
 include "common.thrift"
+include "client.thrift"
 namespace java org.apache.iotdb.mpp.rpc.thrift
 namespace py iotdb.thrift.datanode
 
@@ -1207,6 +1208,9 @@ service IDataNodeRPCService {
 
   /** Empty rpc, only for connection test */
   common.TSStatus testConnectionEmptyRPC()
+
+  /** to write audit log or other events as time series **/
+  common.TSStatus insertRecord(1:client.TSInsertRecordReq req);
 }
 
 service MPPDataExchangeService {

Reply via email to