Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js';
import { AccessLevelEnum } from '../../../../enums/index.js';
import { ConnectionPropertiesEntity } from '../../../connection-properties/connection-properties.entity.js';
import { FoundGroupDataWithUsersDs } from '../../../group/application/data-sctructures/found-user-groups.ds.js';
import { SimpleFoundUserInfoDs } from '../../../user/dto/found-user.dto.js';
import { UserEntity } from '../../../user/user.entity.js';
Expand Down Expand Up @@ -79,7 +80,7 @@ export class FoundDirectConnectionsDs {
isTestConnection: boolean;

@ApiProperty({ required: false })
connection_properties?: any;
connection_properties?: ConnectionPropertiesEntity;

@ApiProperty()
isFrozen: boolean;
Expand All @@ -102,7 +103,7 @@ export class FoundDirectConnectionsNonePermissionDs {
isTestConnection: boolean;

@ApiProperty()
connection_properties: any;
connection_properties: ConnectionPropertiesEntity;

@ApiProperty()
isFrozen: boolean;
Expand Down Expand Up @@ -131,7 +132,7 @@ export class FoundAgentConnectionsDs {
isTestConnection: boolean;

@ApiProperty({ required: false })
connection_properties: any;
connection_properties: ConnectionPropertiesEntity;
}

export class FoundSipleConnectionInfoDS {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
ConnectionTypesEnum,
ConnectionTypeTestEnum,
} from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js';
import { IsBoolean, IsEnum, IsNumber, IsOptional, IsString, Max, Min } from 'class-validator';
import { IsBoolean, IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString, Max, Min, ValidateIf } from 'class-validator';
import { isTest } from '../../../../helpers/app/is-test.js';

export class CreateConnectionDto {
Expand Down Expand Up @@ -63,24 +63,28 @@ export class CreateConnectionDto {
@ApiProperty({ required: false })
ssh?: boolean;

@IsOptional()
@ValidateIf((o) => o.ssh === true)
@IsNotEmpty({ message: 'SSH private key is required when SSH is enabled' })
@IsString()
@ApiProperty({ required: false })
privateSSHKey?: string;

@IsOptional()
@ValidateIf((o) => o.ssh === true)
@IsNotEmpty({ message: 'SSH host is required when SSH is enabled' })
@IsString()
@ApiProperty({ required: false })
sshHost?: string;

@IsOptional()
@ValidateIf((o) => o.ssh === true)
@IsNotEmpty({ message: 'SSH port is required when SSH is enabled' })
@IsNumber({ maxDecimalPlaces: 0 })
@Max(65535)
@Min(0)
@ApiProperty({ required: false })
sshPort?: number;

@IsOptional()
@ValidateIf((o) => o.ssh === true)
@IsNotEmpty({ message: 'SSH username is required when SSH is enabled' })
@IsString()
@ApiProperty({ required: false })
sshUsername?: string;
Expand Down
88 changes: 11 additions & 77 deletions backend/src/entities/connection/connection.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
Controller,
Get,
Inject,
Injectable,
Post,
Put,
Query,
Expand All @@ -15,8 +14,6 @@ import { ApiBearerAuth, ApiBody, ApiOperation, ApiQuery, ApiResponse, ApiTags }
import { SkipThrottle } from '@nestjs/throttler';
import { isRedisConnectionUrl } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js';
import { TestConnectionResultDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/test-result-connection.ds.js';
import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js';
import validator from 'validator';
import { IGlobalDatabaseContext } from '../../common/application/global-database-context.interface.js';
import { BaseType, UseCaseType } from '../../common/data-injection.tokens.js';
import { BodyUuid, GCLlId, MasterPassword, QueryUuid, SlugUuid, UserId } from '../../decorators/index.js';
Expand All @@ -26,7 +23,6 @@ import { Messages } from '../../exceptions/text/messages.js';
import { processExceptionMessage } from '../../exceptions/utils/process-exception-message.js';
import { ConnectionEditGuard, ConnectionReadGuard } from '../../guards/index.js';
import {
isConnectionEntityAgent,
isConnectionTypeAgent,
slackPostMessage,
toPrettyErrorsMsg,
Expand Down Expand Up @@ -61,7 +57,7 @@ import { FoundUserGroupsInConnectionDTO } from './application/dto/found-user-gro
import { ConnectionTokenResponseDTO } from './application/dto/new-connection-token-response.dto.js';
import { TestConnectionResponseDTO } from './application/dto/test-connection-response.dto.js';
import { UpdateMasterPasswordRequestBodyDto } from './application/dto/update-master-password-request-body.dto.js';
import { UpdatedConnectionResponseDTO } from './application/dto/updated-connection-responce.dto.js';
import { UpdatedConnectionResponseDTO } from './application/dto/updated-connection-response.dto.js';
import { ValidationResultRo } from './application/dto/validation-result.ro.js';
import {
ICreateConnection,
Expand All @@ -84,13 +80,13 @@ import {
} from './use-cases/use-cases.interfaces.js';
import { TokenValidationResult } from './use-cases/validate-connection-token.use.case.js';
import { isTestConnectionUtil } from './utils/is-test-connection-util.js';
import { validateCreateConnectionData } from './utils/validate-create-connection-data.js';

@UseInterceptors(SentryInterceptor)
@Timeout()
@Controller()
@ApiBearerAuth()
@ApiTags('Connection')
@Injectable()
export class ConnectionController {
constructor(
@Inject(UseCaseType.FIND_CONNECTIONS)
Expand Down Expand Up @@ -141,7 +137,6 @@ export class ConnectionController {
})
@Get('/connections')
async findAll(@UserId() userId: string, @GCLlId() glidCookieValue: string): Promise<FoundConnectionsDs> {
console.log(`findAll triggered in connection.controller ->: ${new Date().toISOString()}`);
const userData: FindUserDs = {
id: userId,
gclidValue: glidCookieValue,
Expand Down Expand Up @@ -544,11 +539,12 @@ export class ConnectionController {
masterPwd: masterPwd,
},
};
const errors = this.validateParameters(inputData.connection_parameters);
if (errors.length > 0) {
try {
await validateCreateConnectionData(inputData);
} catch (e) {
return {
result: false,
message: toPrettyErrorsMsg(errors),
message: e?.response?.message || e?.message || Messages.CONNECTION_TYPE_INVALID,
};
}
const result = await this.testConnectionUseCase.execute(inputData, InTransactionEnum.OFF);
Expand Down Expand Up @@ -633,17 +629,15 @@ export class ConnectionController {
},
};

const errors = this.validateParameters(connectionData.connection_parameters);

if (!connectionId) errors.push(Messages.ID_MISSING);
if (!connectionId) {
throw new BadRequestException(Messages.ID_MISSING);
}

if (connectionData.connection_parameters.masterEncryption && !masterPwd) {
errors.push(Messages.MASTER_PASSWORD_REQUIRED);
throw new BadRequestException(Messages.MASTER_PASSWORD_REQUIRED);
}

if (errors.length > 0) {
throw new BadRequestException(toPrettyErrorsMsg(errors));
}
await validateCreateConnectionData(connectionData);
return await this.restoreConnectionUseCase.execute(connectionData, InTransactionEnum.ON);
}

Expand Down Expand Up @@ -696,64 +690,4 @@ export class ConnectionController {
return await this.unfreezeConnectionUseCase.execute({ connectionId, userId }, InTransactionEnum.ON);
}

private validateParameters = (connectionData: CreateConnectionDto): Array<string> => {
const errors = [];

function validateConnectionType(type: string): string {
return Object.keys(ConnectionTypesEnum).find((key) => key === type);
}

if (!connectionData.type) errors.push(Messages.TYPE_MISSING);
if (!validateConnectionType(connectionData.type)) errors.push(Messages.CONNECTION_TYPE_INVALID);
if (!isConnectionEntityAgent(connectionData)) {
if (!connectionData.host) {
errors.push(Messages.HOST_MISSING);
return errors;
}

if (isRedisConnectionUrl(connectionData.host)) {
return errors;
}

if (!connectionData.username && connectionData.type !== ConnectionTypesEnum.redis)
errors.push(Messages.USERNAME_MISSING);

if (
connectionData.type === ConnectionTypesEnum.dynamodb ||
connectionData.type === ConnectionTypesEnum.elasticsearch
) {
return errors;
}

if (!connectionData.database && connectionData.type !== ConnectionTypesEnum.redis)
errors.push(Messages.DATABASE_MISSING);
if (process.env.NODE_ENV !== 'test' && !connectionData.ssh) {
if (!this.isMongoHost(connectionData.host)) {
if (!validator.isFQDN(connectionData.host) && !validator.isIP(connectionData.host))
errors.push(Messages.HOST_NAME_INVALID);
}
}

if (typeof connectionData.port !== 'number') errors.push(Messages.PORT_FORMAT_INCORRECT);
if (connectionData.port < 0 || connectionData.port > 65535 || !connectionData.port)
errors.push(Messages.PORT_MISSING);
if (typeof connectionData.ssh !== 'boolean') errors.push(Messages.SSH_FORMAT_INCORRECT);
if (connectionData.ssh) {
if (typeof connectionData.sshPort !== 'number') {
errors.push(Messages.SSH_PORT_FORMAT_INCORRECT);
}
if (!connectionData.sshPort) errors.push(Messages.SSH_PORT_MISSING);
if (!connectionData.sshUsername) errors.push(Messages.SSH_USERNAME_MISSING);
if (!connectionData.sshHost) errors.push(Messages.SSH_HOST_MISSING);
}
} else {
if (!connectionData.title) errors.push('Connection title missing');
}

return errors;
};

private isMongoHost(host: string): boolean {
return host.startsWith('mongodb+srv');
}
}
1 change: 0 additions & 1 deletion backend/src/entities/connection/connection.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ export class ConnectionEntity {
this.authSource = foundTestConnectionByType.authSource;
this.sid = foundTestConnectionByType.sid;
this.schema = foundTestConnectionByType.schema;
this.cert = foundTestConnectionByType.cert;
this.azure_encryption = foundTestConnectionByType.azure_encryption;

Copilot AI Feb 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cert assignment is removed here but still exists on line 189 (and throughout the rest of the file). This creates an inconsistency where the cert field is being set for test connections in one place but not here. Either the removal should be reverted, or line 189 should also be removed along with other references to ensure consistent behavior.

Copilot uses AI. Check for mistakes.
}
} else {
Expand Down
1 change: 0 additions & 1 deletion backend/src/entities/connection/connection.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ export class ConnectionModule implements NestModule {
consumer
.apply(AuthMiddleware)
.forRoutes(
// { path: 'connections', method: RequestMethod.GET },
{ path: 'connections/author', method: RequestMethod.GET },
{ path: 'connection/one/:connectionId', method: RequestMethod.GET },
{ path: '/connection/users/:connectionId', method: RequestMethod.GET },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,7 @@ export const customConnectionRepositoryExtension: IConnectionRepository = {
},

async findAllUserNonTestsConnections(userId: string): Promise<Array<ConnectionEntity>> {
const connectionQb = this.createQueryBuilder('connection')
.leftJoinAndSelect('connection.groups', 'group')
.leftJoinAndSelect('group.users', 'user')
.andWhere('user.id = :userId', { userId: userId })
.andWhere('connection.isTestConnection = :isTest', { isTest: false });
return await connectionQb.getMany();
return await this.findAllUserConnections(userId, false);
},

async findAllUsersInConnection(connectionId: string): Promise<Array<UserEntity>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

let cachedCertificate: string | null = null;

export async function readSslCertificate(): Promise<string> {
if (cachedCertificate) {
return cachedCertificate;
}
const fileName = 'global-bundle.pem';
return new Promise((resolve, reject) => {
fs.readFile(
join(__dirname, '..', '..', '..', '..', '..', 'files', 'certificates', fileName),
'utf8',
(err, data) => {
if (err) {
reject(err);
return reject(err);
}
cachedCertificate = data;
resolve(data);
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-acce
import AbstractUseCase from '../../../common/abstract-use.case.js';
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
import { BaseType } from '../../../common/data-injection.tokens.js';
import { AccessLevelEnum } from '../../../enums/access-level.enum.js';
import { TablePermissionDs } from '../../permission/application/data-structures/create-permissions.ds.js';
import { FoundPermissionsInConnectionDs } from '../application/data-structures/found-permissions-in-connection.ds.js';
import { GetPermissionsInConnectionDs } from '../application/data-structures/get-permissions-in-connection.ds.js';
Expand Down Expand Up @@ -36,15 +37,24 @@ export class GetPermissionsForGroupInConnectionUseCase
const dao = getDataAccessObject(connection);
const tables: Array<string> = (await dao.getTablesFromDB()).map((table) => table.tableName);

const tablesWithAccessLevels: Array<TablePermissionDs> = await Promise.all(
tables.map(async (table: string) => {
return await this._dbContext.permissionRepository.getGroupPermissionsForTable(
inputData.connectionId,
inputData.groupId,
table,
);
}),
const allTablePermissions = await this._dbContext.permissionRepository.getGroupPermissionsForAllTables(
inputData.connectionId,
inputData.groupId,
);

const tablesWithAccessLevels: Array<TablePermissionDs> = tables.map((tableName) => {
const tablePermissions = allTablePermissions.filter((p) => p.tableName === tableName);
return {
tableName,
accessLevel: {
add: !!tablePermissions.find((el) => el.accessLevel === AccessLevelEnum.add),
delete: !!tablePermissions.find((el) => el.accessLevel === AccessLevelEnum.delete),
edit: !!tablePermissions.find((el) => el.accessLevel === AccessLevelEnum.edit),
readonly: !!tablePermissions.find((el) => el.accessLevel === AccessLevelEnum.readonly),
visibility: !!tablePermissions.find((el) => el.accessLevel === AccessLevelEnum.visibility),
},
};
});
const allTableSettingsInConnection = await this._dbContext.tableSettingsRepository.findTableSettingsInConnection(
inputData.connectionId,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,13 @@ export class TestConnectionUseCase
};
}

let updated: any = Object.assign(toUpdate, connectionData);
const updated = Object.assign(toUpdate, connectionData);
const dataForProcessing: CreateConnectionDs = {
connection_parameters: updated,
creation_info: null,
};
updated = (await processAWSConnection(dataForProcessing)).connection_parameters;
const processed = (await processAWSConnection(dataForProcessing)).connection_parameters;
Object.assign(updated, processed);
const dao = getDataAccessObject(updated);

return await this.testConnection(
Expand All @@ -125,7 +126,7 @@ export class TestConnectionUseCase
} catch (e) {
return {
result: false,
message: `${Messages.CONNECTION_TEST_FILED}${e ? e : ''}`,
message: `${Messages.CONNECTION_TEST_FAILED}${e ? e : ''}`,
};
}
} else {
Expand Down Expand Up @@ -157,8 +158,8 @@ export class TestConnectionUseCase
}

private async testConnection(
dao: any,
connectionData: any,
dao: { testConnect: () => Promise<TestConnectionResultDs> },
connectionData: UpdateConnectionDs['connection_parameters'],
authorId: string,
connectionType: ConnectionTypesEnum,
): Promise<TestConnectionResultDs> {
Expand Down
Loading
Loading