mirror of
https://github.com/ToolJet/ToolJet
synced 2026-04-21 21:47:17 +00:00
Merge branch 'lts-3.16' into chore/improve-perfo
This commit is contained in:
commit
2df10907c2
15 changed files with 860 additions and 546 deletions
2
.version
2
.version
|
|
@ -1 +1 @@
|
||||||
3.20.109-lts
|
3.20.111-lts
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
3.20.109-lts
|
3.20.111-lts
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit c627418d7a88fae52147f3a48f55d313bfa679b2
|
Subproject commit e102ff435e1e81e01492101846298c117ea7a240
|
||||||
|
|
@ -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]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
3.20.109-lts
|
3.20.111-lts
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9371c9a62069621fc38aa6a90e1889aaec97d2d0
|
Subproject commit 9971656e87d1018b38a543ae77814e0b328f5f6f
|
||||||
1299
server/package-lock.json
generated
1299
server/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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)');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue