From aceea89c5c4f0caedda1f510e0e0ba6d7dc45124 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Mon, 12 Oct 2026 11:55:18 +0530 Subject: [PATCH] fix(tenants): keep the 'system' username when provisioning a new tenant Problem Commit 3d3ce92e9 personalised the three users seeded into a brand-new tenant database and renamed user id 2 from 'system' to '_system' (for example 'default_system'). Fineract looks this user up by the fixed name AppUserConstants.SYSTEM_USER_NAME ('system') through AppUserRepositoryWrapper.fetchSystemUser(), so on every fresh tenant: - every scheduler job failed once a minute with "UserNotFoundException: User with name 'system' does not exist" (seen with the Send Asynchronous Events job); - COB initialisation, savings escheat, current account closure and the stuck-job listener would fail in the same way. Existing tenants were not affected because the provisioner only runs right after the very first migration of a new tenant. Fix - TenantInitialUserProvisioner now sets user id 2's username to AppUserConstants.SYSTEM_USER_NAME and only replaces its password. - Admin and interop users are still personalised as '_admin' and '_interop'. - Javadoc and the startup log line now say the system user keeps its name and why. Resulting users on a new tenant - _admin (password: initial-user-password or built-in default) - system (same password, name unchanged) - _interop (same password) Tenants already created with '_system' need a one-off fix: UPDATE m_appuser SET username = 'system' WHERE id = 2; Co-Authored-By: Claude Opus 5.5 --- .../migration/TenantInitialUserProvisioner.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantInitialUserProvisioner.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantInitialUserProvisioner.java index eb0b17c3d..80ca8c636 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantInitialUserProvisioner.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantInitialUserProvisioner.java @@ -22,6 +22,7 @@ import java.util.Locale; import javax.sql.DataSource; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.useradministration.service.AppUserConstants; import org.springframework.core.env.Environment; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.crypto.factory.PasswordEncoderFactories; @@ -37,8 +38,9 @@ import org.springframework.stereotype.Component; * touched. The seeded changeSet itself is deliberately left alone: editing it would change its Liquibase checksum and * break validation on every tenant that already ran it. * - * Usernames become {@code _admin}, {@code _system} and - * {@code _interop}. All three share one password: the value of + * Usernames become {@code _admin} and {@code _interop}. The system user keeps the + * {@link AppUserConstants#SYSTEM_USER_NAME} username because scheduler jobs, COB and other background processes look it + * up by that name; only its password is changed. All three share one password: the value of * {@code fineract.tenant.initial-user-password} (env {@code FINERACT_TENANT_INITIAL_USER_PASSWORD}) when set, * otherwise {@link #DEFAULT_PASSWORD}. */ @@ -68,11 +70,11 @@ public class TenantInitialUserProvisioner { final String encoded = encoder.encode(password); update(jdbc, ADMIN_ID, prefix + "_admin", encoded); - update(jdbc, SYSTEM_ID, prefix + "_system", encoded); + update(jdbc, SYSTEM_ID, AppUserConstants.SYSTEM_USER_NAME, encoded); update(jdbc, INTEROP_ID, prefix + "_interop", encoded); - log.warn("Tenant '{}' initial users created: {}_admin, {}_system, {}_interop (password: {}). Change it after first login.", - tenantIdentifier, prefix, prefix, prefix, overridden ? "from " + PASSWORD_PROPERTY : "built-in default"); + log.warn("Tenant '{}' initial users created: {}_admin, {}, {}_interop (password: {}). Change it after first login.", + tenantIdentifier, prefix, AppUserConstants.SYSTEM_USER_NAME, prefix, overridden ? "from " + PASSWORD_PROPERTY : "built-in default"); } private void update(final JdbcTemplate jdbc, final long id, final String username, final String encodedPassword) {