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 { Alert } from '@/_ui/Alert';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
|
@ -92,9 +92,16 @@ const CreateDraftVersionModal = ({
|
|||
}, [savedVersions, selectedVersion, selectedVersionForCreation]);
|
||||
|
||||
// Update version name when selectedVersionForCreation changes or when modal opens
|
||||
const hasInitializedVersionName = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (showCreateAppVersion && selectedVersionForCreation?.name) {
|
||||
if (!showCreateAppVersion) {
|
||||
hasInitializedVersionName.current = false;
|
||||
return;
|
||||
}
|
||||
if (!hasInitializedVersionName.current && selectedVersionForCreation?.name) {
|
||||
setVersionName(selectedVersionForCreation.name);
|
||||
hasInitializedVersionName.current = true;
|
||||
}
|
||||
}, [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 { OrganizationTjdbConfigurations } from '../src/entities/organization_tjdb_configurations.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 { dbTransactionWrap } from '../src/helpers/database.helper';
|
||||
|
||||
|
|
@ -43,7 +45,7 @@ Object.keys(ENV_VARS).forEach((key) => {
|
|||
*/
|
||||
|
||||
class RotationProgress {
|
||||
private totalTables = 5;
|
||||
private totalTables = 6;
|
||||
private completedTables = 0;
|
||||
private currentTable = '';
|
||||
private currentTableRows = 0;
|
||||
|
|
@ -173,6 +175,7 @@ async function bootstrap() {
|
|||
await rotateSSOConfigs(entityManager, dualKeyService, progress);
|
||||
await rotateTJDBConfigs(entityManager, dualKeyService, progress);
|
||||
await rotateUserDetails(entityManager, dualKeyService, progress);
|
||||
await rotateInstanceSettings(entityManager, dualKeyService, progress);
|
||||
|
||||
progress.complete();
|
||||
|
||||
|
|
@ -474,6 +477,40 @@ async function rotateUserDetails(
|
|||
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> {
|
||||
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);
|
||||
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++;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
console.log(' ⚠️ No encrypted data found to test (database might be empty)');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export class ValidateQuerySourceGuard implements CanActivate {
|
|||
if (id) {
|
||||
dataSource = await this.dataSourceRepository.findByQuery(id, user.organizationId, dataSourceId);
|
||||
} else {
|
||||
dataSource = await this.dataSourceRepository.findById(dataSourceId);
|
||||
dataSource = await this.dataSourceRepository.findById(dataSourceId, user.organizationId);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
await this.dataQueryUtilService.validateQueryActionsAgainstEnvironment(
|
||||
user.defaultOrganizationId,
|
||||
user.organizationId,
|
||||
appVersionId,
|
||||
'You cannot create queries in the promoted version.'
|
||||
);
|
||||
|
|
@ -103,7 +103,7 @@ export class DataQueriesService implements IDataQueriesService {
|
|||
const { name, options } = updateDataQueryDto;
|
||||
|
||||
await this.dataQueryUtilService.validateQueryActionsAgainstEnvironment(
|
||||
user.defaultOrganizationId,
|
||||
user.organizationId,
|
||||
versionId,
|
||||
'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) {
|
||||
return dbTransactionWrap(async (manager: EntityManager) => {
|
||||
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
|
||||
// if (dataSource.kind !== newDataSource.kind && dataSource) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import { QueryResult } from '@tooljet/plugins/dist/packages/common/lib';
|
|||
@InitModule(MODULES.GLOBAL_DATA_SOURCE)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class DataSourcesController implements IDataSourcesController {
|
||||
constructor(protected readonly dataSourcesService: DataSourcesService) { }
|
||||
constructor(protected readonly dataSourcesService: DataSourcesService) {}
|
||||
|
||||
// Listing of all global data sources
|
||||
@InitFeature(FEATURE_KEY.GET)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export class ValidateDataSourceGuard implements CanActivate {
|
|||
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 (!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 manager.findOneOrFail(DataSource, {
|
||||
where: { id: dataSourceId },
|
||||
where: { id: dataSourceId, organizationId },
|
||||
relations: ['plugin', 'apps', 'dataSourceOptions'],
|
||||
});
|
||||
}, manager || this.manager);
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ export class DataSourcesService implements IDataSourcesService {
|
|||
const { dataSourceId, environmentId } = updateOptions;
|
||||
|
||||
// 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);
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ export class DataSourcesService implements IDataSourcesService {
|
|||
}
|
||||
|
||||
async delete(dataSourceId: string, user: User) {
|
||||
const dataSource = await this.dataSourcesRepository.findById(dataSourceId);
|
||||
const dataSource = await this.dataSourcesRepository.findById(dataSourceId, user.organizationId);
|
||||
if (!dataSource) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export class DataSourcesUtilService implements IDataSourcesUtilService {
|
|||
protected readonly pluginsServiceSelector: PluginsServiceSelector,
|
||||
protected readonly organizationConstantsUtilService: OrganizationConstantsUtilService,
|
||||
protected readonly inMemoryCacheService: InMemoryCacheService
|
||||
) { }
|
||||
) {}
|
||||
async create(createArgumentsDto: CreateArgumentsDto, user: User): Promise<DataSource> {
|
||||
return await dbTransactionWrap(async (manager: EntityManager) => {
|
||||
const newDataSource = manager.create(DataSource, {
|
||||
|
|
@ -200,7 +200,7 @@ export class DataSourcesUtilService implements IDataSourcesUtilService {
|
|||
options: Array<object>,
|
||||
environmentId?: string
|
||||
): Promise<void> {
|
||||
const dataSource = await this.dataSourceRepository.findById(dataSourceId);
|
||||
const dataSource = await this.dataSourceRepository.findById(dataSourceId, organizationId);
|
||||
|
||||
if (dataSource.type === DataSourceTypes.SAMPLE) {
|
||||
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 Promise.all(
|
||||
arr.map((item) => this.resolveValue(item, organization_id, environment_id))
|
||||
);
|
||||
return Promise.all(arr.map((item) => this.resolveValue(item, organization_id, environment_id)));
|
||||
}
|
||||
|
||||
async resolveValue(value, organization_id, environment_id) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue