Merge branch 'lts-3.16' into chore/improve-perfo

This commit is contained in:
Muhsin Shah 2026-03-03 12:21:45 +05:30
commit 2df10907c2
15 changed files with 860 additions and 546 deletions

View file

@ -1 +1 @@
3.20.109-lts 3.20.111-lts

View file

@ -1 +1 @@
3.20.109-lts 3.20.111-lts

@ -1 +1 @@
Subproject commit c627418d7a88fae52147f3a48f55d313bfa679b2 Subproject commit e102ff435e1e81e01492101846298c117ea7a240

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState ,useRef} from 'react';
import AlertDialog from '@/_ui/AlertDialog'; import AlertDialog from '@/_ui/AlertDialog';
import { Alert } from '@/_ui/Alert'; import { Alert } from '@/_ui/Alert';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
@ -92,9 +92,16 @@ const CreateDraftVersionModal = ({
}, [savedVersions, selectedVersion, selectedVersionForCreation]); }, [savedVersions, selectedVersion, selectedVersionForCreation]);
// Update version name when selectedVersionForCreation changes or when modal opens // Update version name when selectedVersionForCreation changes or when modal opens
const hasInitializedVersionName = useRef(false);
useEffect(() => { useEffect(() => {
if (showCreateAppVersion && selectedVersionForCreation?.name) { if (!showCreateAppVersion) {
hasInitializedVersionName.current = false;
return;
}
if (!hasInitializedVersionName.current && selectedVersionForCreation?.name) {
setVersionName(selectedVersionForCreation.name); setVersionName(selectedVersionForCreation.name);
hasInitializedVersionName.current = true;
} }
}, [selectedVersionForCreation, showCreateAppVersion]); }, [selectedVersionForCreation, showCreateAppVersion]);

View file

@ -1 +1 @@
3.20.109-lts 3.20.111-lts

@ -1 +1 @@
Subproject commit 9371c9a62069621fc38aa6a90e1889aaec97d2d0 Subproject commit 9971656e87d1018b38a543ae77814e0b328f5f6f

1299
server/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,8 @@ import { OrgEnvironmentConstantValue } from '../src/entities/org_environment_con
import { SSOConfigs } from '../src/entities/sso_config.entity'; import { SSOConfigs } from '../src/entities/sso_config.entity';
import { OrganizationTjdbConfigurations } from '../src/entities/organization_tjdb_configurations.entity'; import { OrganizationTjdbConfigurations } from '../src/entities/organization_tjdb_configurations.entity';
import { UserDetails } from '../src/entities/user_details.entity'; import { UserDetails } from '../src/entities/user_details.entity';
import { InstanceSettings } from '../src/entities/instance_settings.entity';
import { INSTANCE_SETTINGS_ENCRYPTION_KEY, INSTANCE_CONFIGS_DATA_TYPES } from '../src/modules/instance-settings/constants';
import { getEnvVars } from './database-config-utils'; import { getEnvVars } from './database-config-utils';
import { dbTransactionWrap } from '../src/helpers/database.helper'; import { dbTransactionWrap } from '../src/helpers/database.helper';
@ -43,7 +45,7 @@ Object.keys(ENV_VARS).forEach((key) => {
*/ */
class RotationProgress { class RotationProgress {
private totalTables = 5; private totalTables = 6;
private completedTables = 0; private completedTables = 0;
private currentTable = ''; private currentTable = '';
private currentTableRows = 0; private currentTableRows = 0;
@ -173,6 +175,7 @@ async function bootstrap() {
await rotateSSOConfigs(entityManager, dualKeyService, progress); await rotateSSOConfigs(entityManager, dualKeyService, progress);
await rotateTJDBConfigs(entityManager, dualKeyService, progress); await rotateTJDBConfigs(entityManager, dualKeyService, progress);
await rotateUserDetails(entityManager, dualKeyService, progress); await rotateUserDetails(entityManager, dualKeyService, progress);
await rotateInstanceSettings(entityManager, dualKeyService, progress);
progress.complete(); progress.complete();
@ -474,6 +477,40 @@ async function rotateUserDetails(
progress.completeTable(); progress.completeTable();
} }
// Table 6: instance_settings (password type rows, e.g. SMTP_PASSWORD)
async function rotateInstanceSettings(
entityManager: EntityManager,
dualKeyService: DualKeyEncryptionService,
progress: RotationProgress
): Promise<void> {
const settings = await entityManager.find(InstanceSettings, {
where: { dataType: INSTANCE_CONFIGS_DATA_TYPES.PASSWORD },
});
progress.startTable('instance_settings', settings.length);
for (const setting of settings) {
if (!setting.value) {
progress.incrementRow();
continue; // Skip nulls/empty
}
try {
// table = INSTANCE_SETTINGS_ENCRYPTION_KEY ('instance_settings'), column = the row's key (e.g. 'SMTP_PASSWORD')
const plainValue = await dualKeyService.decryptWithOldKey(INSTANCE_SETTINGS_ENCRYPTION_KEY, setting.key, setting.value);
const newCiphertext = await dualKeyService.encryptWithNewKey(INSTANCE_SETTINGS_ENCRYPTION_KEY, setting.key, plainValue);
setting.value = newCiphertext;
await entityManager.save(setting);
progress.incrementRow();
} catch (error) {
throw new Error(`Failed to rotate instance setting ${setting.key}: ${error.message}`);
}
}
progress.completeTable();
}
async function verifyRotation(entityManager: EntityManager, newKey: string): Promise<void> { async function verifyRotation(entityManager: EntityManager, newKey: string): Promise<void> {
const testService = new DualKeyEncryptionService(newKey, newKey); const testService = new DualKeyEncryptionService(newKey, newKey);
@ -521,6 +558,15 @@ async function verifyRotation(entityManager: EntityManager, newKey: string): Pro
await testService.decryptWithOldKey('user_details', 'userMetadata', userDetail.userMetadata); await testService.decryptWithOldKey('user_details', 'userMetadata', userDetail.userMetadata);
console.log(' ✓ User details table verified'); console.log(' ✓ User details table verified');
} }
// Test instance settings
const instanceSetting = await entityManager.findOne(InstanceSettings, {
where: { dataType: INSTANCE_CONFIGS_DATA_TYPES.PASSWORD },
});
if (instanceSetting?.value) {
await testService.decryptWithOldKey(INSTANCE_SETTINGS_ENCRYPTION_KEY, instanceSetting.key, instanceSetting.value);
console.log(' ✓ Instance settings table verified');
}
} }
/** /**
@ -583,6 +629,16 @@ async function testDecryptionWithOldKey(
testedCount++; testedCount++;
} }
// Test instance settings
const instanceSetting = await entityManager.findOne(InstanceSettings, {
where: { dataType: INSTANCE_CONFIGS_DATA_TYPES.PASSWORD },
});
if (instanceSetting?.value) {
await dualKeyService.decryptWithOldKey(INSTANCE_SETTINGS_ENCRYPTION_KEY, instanceSetting.key, instanceSetting.value);
console.log(' ✓ Instance settings table - old key works');
testedCount++;
}
if (testedCount === 0) { if (testedCount === 0) {
console.log(' ⚠️ No encrypted data found to test (database might be empty)'); console.log(' ⚠️ No encrypted data found to test (database might be empty)');
} }

View file

@ -40,7 +40,7 @@ export class ValidateQuerySourceGuard implements CanActivate {
if (id) { if (id) {
dataSource = await this.dataSourceRepository.findByQuery(id, user.organizationId, dataSourceId); dataSource = await this.dataSourceRepository.findByQuery(id, user.organizationId, dataSourceId);
} else { } else {
dataSource = await this.dataSourceRepository.findById(dataSourceId); dataSource = await this.dataSourceRepository.findById(dataSourceId, user.organizationId);
} }
// If app is not found, throw NotFoundException // If app is not found, throw NotFoundException

View file

@ -60,7 +60,7 @@ export class DataQueriesService implements IDataQueriesService {
const { kind, name, options, app_version_id: appVersionId } = dataQueryDto; const { kind, name, options, app_version_id: appVersionId } = dataQueryDto;
await this.dataQueryUtilService.validateQueryActionsAgainstEnvironment( await this.dataQueryUtilService.validateQueryActionsAgainstEnvironment(
user.defaultOrganizationId, user.organizationId,
appVersionId, appVersionId,
'You cannot create queries in the promoted version.' 'You cannot create queries in the promoted version.'
); );
@ -103,7 +103,7 @@ export class DataQueriesService implements IDataQueriesService {
const { name, options } = updateDataQueryDto; const { name, options } = updateDataQueryDto;
await this.dataQueryUtilService.validateQueryActionsAgainstEnvironment( await this.dataQueryUtilService.validateQueryActionsAgainstEnvironment(
user.defaultOrganizationId, user.organizationId,
versionId, versionId,
'You cannot update queries in the promoted version.' 'You cannot update queries in the promoted version.'
); );
@ -298,7 +298,7 @@ export class DataQueriesService implements IDataQueriesService {
async changeQueryDataSource(user: User, queryId: string, dataSource: DataSource, newDataSourceId: string) { async changeQueryDataSource(user: User, queryId: string, dataSource: DataSource, newDataSourceId: string) {
return dbTransactionWrap(async (manager: EntityManager) => { return dbTransactionWrap(async (manager: EntityManager) => {
const newDataSource = await this.dataSourceRepository.findOneOrFail({ const newDataSource = await this.dataSourceRepository.findOneOrFail({
where: { id: newDataSourceId }, where: { id: newDataSourceId, organizationId: user.organizationId },
}); });
// FIXME: Disabling this check as workflows can change data source of a query with different kind // FIXME: Disabling this check as workflows can change data source of a query with different kind
// if (dataSource.kind !== newDataSource.kind && dataSource) { // if (dataSource.kind !== newDataSource.kind && dataSource) {

View file

@ -34,7 +34,7 @@ import { QueryResult } from '@tooljet/plugins/dist/packages/common/lib';
@InitModule(MODULES.GLOBAL_DATA_SOURCE) @InitModule(MODULES.GLOBAL_DATA_SOURCE)
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
export class DataSourcesController implements IDataSourcesController { export class DataSourcesController implements IDataSourcesController {
constructor(protected readonly dataSourcesService: DataSourcesService) { } constructor(protected readonly dataSourcesService: DataSourcesService) {}
// Listing of all global data sources // Listing of all global data sources
@InitFeature(FEATURE_KEY.GET) @InitFeature(FEATURE_KEY.GET)

View file

@ -28,7 +28,7 @@ export class ValidateDataSourceGuard implements CanActivate {
throw new ForbiddenException(); throw new ForbiddenException();
} }
const dataSource = await this.dataSourceRepository.findById(id); const dataSource = await this.dataSourceRepository.findById(id, user.organizationId);
// If app is not found, throw NotFoundException // If app is not found, throw NotFoundException
if (!dataSource) { if (!dataSource) {

View file

@ -122,10 +122,10 @@ export class DataSourcesRepository extends Repository<DataSource> {
}); });
} }
async findById(dataSourceId: string, manager?: EntityManager): Promise<DataSource> { async findById(dataSourceId: string, organizationId: string, manager?: EntityManager): Promise<DataSource> {
return await dbTransactionWrap(async (manager: EntityManager) => { return await dbTransactionWrap(async (manager: EntityManager) => {
return await manager.findOneOrFail(DataSource, { return await manager.findOneOrFail(DataSource, {
where: { id: dataSourceId }, where: { id: dataSourceId, organizationId },
relations: ['plugin', 'apps', 'dataSourceOptions'], relations: ['plugin', 'apps', 'dataSourceOptions'],
}); });
}, manager || this.manager); }, manager || this.manager);

View file

@ -159,7 +159,7 @@ export class DataSourcesService implements IDataSourcesService {
const { dataSourceId, environmentId } = updateOptions; const { dataSourceId, environmentId } = updateOptions;
// Fetch datasource details for audit log // Fetch datasource details for audit log
const dataSource = await this.dataSourcesRepository.findById(dataSourceId); const dataSource = await this.dataSourcesRepository.findById(dataSourceId, user.organizationId);
await this.dataSourcesUtilService.update(dataSourceId, user.organizationId, user.id, name, options, environmentId); await this.dataSourcesUtilService.update(dataSourceId, user.organizationId, user.id, name, options, environmentId);
@ -187,7 +187,7 @@ export class DataSourcesService implements IDataSourcesService {
} }
async delete(dataSourceId: string, user: User) { async delete(dataSourceId: string, user: User) {
const dataSource = await this.dataSourcesRepository.findById(dataSourceId); const dataSource = await this.dataSourcesRepository.findById(dataSourceId, user.organizationId);
if (!dataSource) { if (!dataSource) {
return; return;
} }

View file

@ -33,7 +33,7 @@ export class DataSourcesUtilService implements IDataSourcesUtilService {
protected readonly pluginsServiceSelector: PluginsServiceSelector, protected readonly pluginsServiceSelector: PluginsServiceSelector,
protected readonly organizationConstantsUtilService: OrganizationConstantsUtilService, protected readonly organizationConstantsUtilService: OrganizationConstantsUtilService,
protected readonly inMemoryCacheService: InMemoryCacheService protected readonly inMemoryCacheService: InMemoryCacheService
) { } ) {}
async create(createArgumentsDto: CreateArgumentsDto, user: User): Promise<DataSource> { async create(createArgumentsDto: CreateArgumentsDto, user: User): Promise<DataSource> {
return await dbTransactionWrap(async (manager: EntityManager) => { return await dbTransactionWrap(async (manager: EntityManager) => {
const newDataSource = manager.create(DataSource, { const newDataSource = manager.create(DataSource, {
@ -200,7 +200,7 @@ export class DataSourcesUtilService implements IDataSourcesUtilService {
options: Array<object>, options: Array<object>,
environmentId?: string environmentId?: string
): Promise<void> { ): Promise<void> {
const dataSource = await this.dataSourceRepository.findById(dataSourceId); const dataSource = await this.dataSourceRepository.findById(dataSourceId, organizationId);
if (dataSource.type === DataSourceTypes.SAMPLE) { if (dataSource.type === DataSourceTypes.SAMPLE) {
throw new BadRequestException('Cannot update configuration of sample data source'); throw new BadRequestException('Cannot update configuration of sample data source');
@ -475,9 +475,7 @@ export class DataSourcesUtilService implements IDataSourcesUtilService {
return this.resolveValue(arr, organization_id, environment_id); return this.resolveValue(arr, organization_id, environment_id);
} }
return Promise.all( return Promise.all(arr.map((item) => this.resolveValue(item, organization_id, environment_id)));
arr.map((item) => this.resolveValue(item, organization_id, environment_id))
);
} }
async resolveValue(value, organization_id, environment_id) { async resolveValue(value, organization_id, environment_id) {