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
1 change: 1 addition & 0 deletions backend/src/common/data-injection.tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export enum UseCaseType {
SAAS_CREATE_CONNECTION_FOR_HOSTED_DB = 'SAAS_CREATE_CONNECTION_FOR_HOSTED_DB',
SAAS_DELETE_CONNECTION_FOR_HOSTED_DB = 'SAAS_DELETE_CONNECTION_FOR_HOSTED_DB',
SAAS_UPDATE_HOSTED_CONNECTION_PASSWORD = 'SAAS_UPDATE_HOSTED_CONNECTION_PASSWORD',
SAAS_GET_CONNECTIONS_INFO_BY_IDS = 'SAAS_GET_CONNECTIONS_INFO_BY_IDS',

INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',
VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { ApiProperty } from '@nestjs/swagger';
import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js';

export class FoundConnectionInfoRO {
@ApiProperty()
id: string;

@ApiProperty()
title: string;

@ApiProperty({ enum: ConnectionTypesEnum })
type: ConnectionTypesEnum;

@ApiProperty()
host: string;

@ApiProperty()
port: number;

@ApiProperty()
database: string;

@ApiProperty()
schema: string;

@ApiProperty()
sid: string;

@ApiProperty()
ssh: boolean;

@ApiProperty()
ssl: boolean;

@ApiProperty()
createdAt: Date;

@ApiProperty()
updatedAt: Date;

@ApiProperty()
isTestConnection: boolean;

@ApiProperty()
is_frozen: boolean;

@ApiProperty()
masterEncryption: boolean;

@ApiProperty()
azure_encryption: boolean;

@ApiProperty()
authSource: string;

@ApiProperty()
dataCenter: string | null;
Comment on lines +11 to +57

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify nullable/optional source fields in entity vs non-null DTO contract
rg -n "type\\?:|host\\?:|port\\?:|database\\?:|schema\\?:|sid\\?:|ssl\\?:|authSource\\?:|dataCenter\\?:" backend/src/entities/connection/connection.entity.ts
rg -n "type: ConnectionTypesEnum|host: string;|port: number;|database: string;|schema: string;|sid: string;|ssl: boolean;|authSource: string;|dataCenter: string \\| null;" backend/src/microservices/saas-microservice/data-structures/found-connection-info.ro.ts

Repository: rocket-admin/rocketadmin

Length of output: 525


🏁 Script executed:

# Find the mapper that creates FoundConnectionInfoRO
rg -n "FoundConnectionInfoRO|toFoundConnectionInfo" backend/src --type ts -A 3 | head -100

Repository: rocket-admin/rocketadmin

Length of output: 5285


🏁 Script executed:

# Read the complete FoundConnectionInfoRO file to verify current state
cat -n backend/src/microservices/saas-microservice/data-structures/found-connection-info.ro.ts

Repository: rocket-admin/rocketadmin

Length of output: 1362


🏁 Script executed:

# Check the connection entity file to verify field declarations
cat -n backend/src/entities/connection/connection.entity.ts | head -120

Repository: rocket-admin/rocketadmin

Length of output: 4298


🏁 Script executed:

# Read the mapper function completely
sed -n '42,80p' backend/src/microservices/saas-microservice/use-cases/get-connections-info-by-ids.use.case.ts

Repository: rocket-admin/rocketadmin

Length of output: 772


Fix DTO nullability to match nullable entity fields returned by mapper.

The FoundConnectionInfoRO declares several fields as required/non-null, but ConnectionEntity defines them as nullable/optional, and the buildConnectionInfoRO mapper passes these values through unchanged. This creates an inaccurate API contract where the Swagger schema doesn't reflect that these fields can be null or undefined.

Fields requiring updates (currently required, should be optional/nullable):

  • type, host, database, schema, sid, ssl, authSource — are explicitly nullable in the entity (| null)
  • port — has @Column({ default: null }), allowing null values in the database
Proposed fix
-import { ApiProperty } from '@nestjs/swagger';
+import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
 import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js';

 export class FoundConnectionInfoRO {
 	`@ApiProperty`()
 	id: string;

 	`@ApiProperty`()
 	title: string;

-	`@ApiProperty`({ enum: ConnectionTypesEnum })
-	type: ConnectionTypesEnum;
+	`@ApiPropertyOptional`({ enum: ConnectionTypesEnum, nullable: true })
+	type: ConnectionTypesEnum | null;

-	`@ApiProperty`()
-	host: string;
+	`@ApiPropertyOptional`({ nullable: true })
+	host: string | null;

-	`@ApiProperty`()
-	port: number;
+	`@ApiPropertyOptional`({ nullable: true })
+	port: number | null;

-	`@ApiProperty`()
-	database: string;
+	`@ApiPropertyOptional`({ nullable: true })
+	database: string | null;

-	`@ApiProperty`()
-	schema: string;
+	`@ApiPropertyOptional`({ nullable: true })
+	schema: string | null;

-	`@ApiProperty`()
-	sid: string;
+	`@ApiPropertyOptional`({ nullable: true })
+	sid: string | null;

 	`@ApiProperty`()
 	ssh: boolean;

-	`@ApiProperty`()
-	ssl: boolean;
+	`@ApiPropertyOptional`({ nullable: true })
+	ssl: boolean | null;

 	`@ApiProperty`()
 	createdAt: Date;

 	`@ApiProperty`()
 	updatedAt: Date;

 	`@ApiProperty`()
 	isTestConnection: boolean;

 	`@ApiProperty`()
 	is_frozen: boolean;

 	`@ApiProperty`()
 	masterEncryption: boolean;

 	`@ApiProperty`()
 	azure_encryption: boolean;

-	`@ApiProperty`()
-	authSource: string;
+	`@ApiPropertyOptional`({ nullable: true })
+	authSource: string | null;

-	`@ApiProperty`()
+	`@ApiPropertyOptional`({ nullable: true })
 	dataCenter: string | null;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@ApiProperty({ enum: ConnectionTypesEnum })
type: ConnectionTypesEnum;
@ApiProperty()
host: string;
@ApiProperty()
port: number;
@ApiProperty()
database: string;
@ApiProperty()
schema: string;
@ApiProperty()
sid: string;
@ApiProperty()
ssh: boolean;
@ApiProperty()
ssl: boolean;
@ApiProperty()
createdAt: Date;
@ApiProperty()
updatedAt: Date;
@ApiProperty()
isTestConnection: boolean;
@ApiProperty()
is_frozen: boolean;
@ApiProperty()
masterEncryption: boolean;
@ApiProperty()
azure_encryption: boolean;
@ApiProperty()
authSource: string;
@ApiProperty()
dataCenter: string | null;
`@ApiProperty`()
id: string;
`@ApiProperty`()
title: string;
`@ApiPropertyOptional`({ enum: ConnectionTypesEnum, nullable: true })
type: ConnectionTypesEnum | null;
`@ApiPropertyOptional`({ nullable: true })
host: string | null;
`@ApiPropertyOptional`({ nullable: true })
port: number | null;
`@ApiPropertyOptional`({ nullable: true })
database: string | null;
`@ApiPropertyOptional`({ nullable: true })
schema: string | null;
`@ApiPropertyOptional`({ nullable: true })
sid: string | null;
`@ApiProperty`()
ssh: boolean;
`@ApiPropertyOptional`({ nullable: true })
ssl: boolean | null;
`@ApiProperty`()
createdAt: Date;
`@ApiProperty`()
updatedAt: Date;
`@ApiProperty`()
isTestConnection: boolean;
`@ApiProperty`()
is_frozen: boolean;
`@ApiProperty`()
masterEncryption: boolean;
`@ApiProperty`()
azure_encryption: boolean;
`@ApiPropertyOptional`({ nullable: true })
authSource: string | null;
`@ApiPropertyOptional`({ nullable: true })
dataCenter: string | null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/microservices/saas-microservice/data-structures/found-connection-info.ro.ts`
around lines 11 - 57, The DTO FoundConnectionInfoRO currently marks several
properties as required but the mapper buildConnectionInfoRO passes through
nullable/optional values from ConnectionEntity; update the listed properties
(type, host, port, database, schema, sid, ssl, authSource) to be
optional/nullable in the DTO by changing their TypeScript types to include |
null (or make them optional with ?) and annotate each with `@ApiProperty`({
required: false, nullable: true }) so the Swagger schema and runtime typing
match the entity/mapper behavior; keep other properties unchanged.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsNotEmpty, IsString } from 'class-validator';

export class GetConnectionsInfoByIdsDS {
@ApiProperty({ type: [String] })
@IsArray()
@IsNotEmpty()
@IsString({ each: true })
connectionIds: Array<string>;
}
18 changes: 18 additions & 0 deletions backend/src/microservices/saas-microservice/saas.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import { InTransactionEnum } from '../../enums/in-transaction.enum.js';
import { Messages } from '../../exceptions/text/messages.js';
import { SentryInterceptor } from '../../interceptors/sentry.interceptor.js';
import { CreatedConnectionResponse, SuccessResponse } from './data-structures/common-responce.ds.js';
import { FoundConnectionInfoRO } from './data-structures/found-connection-info.ro.js';
import { CreateConnectionForHostedDbDto } from './data-structures/create-connecttion-for-selfhosted-db.dto.js';
import { DeleteConnectionForHostedDbDto } from './data-structures/delete-connection-for-hosted-db.dto.js';
import { GetConnectionsInfoByIdsDS } from './data-structures/get-connections-info-by-ids.ds.js';
import { RegisterCompanyWebhookDS } from './data-structures/register-company.ds.js';
import { RegisteredCompanyDS } from './data-structures/registered-company.ds.js';
import { SaasRegisterUserWithGithub } from './data-structures/saas-register-user-with-github.js';
Expand All @@ -38,6 +40,7 @@ import {
ICreateConnectionForHostedDb,
IDeleteConnectionForHostedDb,
IFreezeConnectionsInCompany,
IGetConnectionsInfoByIds,
IGetUserInfo,
ILoginUserWithGitHub,
ILoginUserWithGoogle,
Expand Down Expand Up @@ -95,6 +98,8 @@ export class SaasController {
private readonly deleteConnectionForHostedDbUseCase: IDeleteConnectionForHostedDb,
@Inject(UseCaseType.SAAS_UPDATE_HOSTED_CONNECTION_PASSWORD)
private readonly updateHostedConnectionPasswordUseCase: IUpdateHostedConnectionPassword,
@Inject(UseCaseType.SAAS_GET_CONNECTIONS_INFO_BY_IDS)
private readonly getConnectionsInfoByIdsUseCase: IGetConnectionsInfoByIds,
) {}

@ApiOperation({ summary: 'Company registered webhook' })
Expand Down Expand Up @@ -326,4 +331,17 @@ export class SaasController {
): Promise<SuccessResponse> {
return await this.updateHostedConnectionPasswordUseCase.execute(updatePasswordData);
}

@ApiOperation({ summary: 'Get connections info by ids' })
@ApiBody({ type: GetConnectionsInfoByIdsDS })
@ApiResponse({
status: 200,
type: [FoundConnectionInfoRO],
})
@Post('/connections/info')
async getConnectionsInfoByIds(
Comment on lines +335 to +342

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

@Post('/connections/info') will return HTTP 201 by default in NestJS unless @HttpCode(200) is set. The Swagger decorator declares status: 200, so docs and actual status code may diverge. Consider adding @HttpCode(HttpStatus.OK) (or changing the documented status to 201) to keep behavior consistent.

Copilot uses AI. Check for mistakes.
@Body() connectionsData: GetConnectionsInfoByIdsDS,
): Promise<Array<FoundConnectionInfoRO>> {
return await this.getConnectionsInfoByIdsUseCase.execute(connectionsData);
Comment on lines +335 to +345

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

The endpoint is documented and typed to return ConnectionEntity[], which includes sensitive fields (e.g., username, password, privateSSHKey, signing_key, cert) defined on ConnectionEntity. Returning the entity from a SaaS-facing API risks leaking credentials/encrypted secrets. Consider returning a dedicated DTO with only non-sensitive fields (e.g., id, title, type, is_frozen, timestamps) and update the Swagger ApiResponse type accordingly.

Copilot uses AI. Check for mistakes.
}
Comment on lines +335 to +346

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

There’s existing e2e coverage for other /saas/* endpoints (e.g., backend/test/ava-tests/saas-tests/hosted-connection-e2e.test.ts), but this new /saas/connections/info behavior isn’t covered. Adding an e2e test for success + validation failure (empty connectionIds) would help prevent regressions, especially around response shape/sanitization.

Copilot uses AI. Check for mistakes.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
6 changes: 6 additions & 0 deletions backend/src/microservices/saas-microservice/saas.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { SaasUsualRegisterUseCase } from './use-cases/saas-usual-register-user.u
import { SuspendUsersUseCase } from './use-cases/suspend-users.use.case.js';
import { SuspendUsersOverLimitUseCase } from './use-cases/suspend-users-over-limit.use.case.js';
import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connections-in-company-use.case.js';
import { GetConnectionsInfoByIdsUseCase } from './use-cases/get-connections-info-by-ids.use.case.js';
import { UpdateHostedConnectionPasswordUseCase } from './use-cases/update-hosted-connection-password.use.case.js';

@Module({
Expand Down Expand Up @@ -100,6 +101,10 @@ import { UpdateHostedConnectionPasswordUseCase } from './use-cases/update-hosted
provide: UseCaseType.SAAS_UPDATE_HOSTED_CONNECTION_PASSWORD,
useClass: UpdateHostedConnectionPasswordUseCase,
},
{
provide: UseCaseType.SAAS_GET_CONNECTIONS_INFO_BY_IDS,
useClass: GetConnectionsInfoByIdsUseCase,
},
SignInAuditService,
],
controllers: [SaasController],
Expand All @@ -126,6 +131,7 @@ export class SaasModule {
{ path: 'saas/connection/hosted', method: RequestMethod.POST },
{ path: 'saas/connection/hosted/delete', method: RequestMethod.POST },
{ path: 'saas/connection/hosted/password', method: RequestMethod.POST },
{ path: 'saas/connections/info', method: RequestMethod.POST },
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
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 { ConnectionEntity } from '../../../entities/connection/connection.entity.js';
import { Messages } from '../../../exceptions/text/messages.js';
import { FoundConnectionInfoRO } from '../data-structures/found-connection-info.ro.js';
import { GetConnectionsInfoByIdsDS } from '../data-structures/get-connections-info-by-ids.ds.js';
import { IGetConnectionsInfoByIds } from './saas-use-cases.interface.js';

@Injectable()
export class GetConnectionsInfoByIdsUseCase
extends AbstractUseCase<GetConnectionsInfoByIdsDS, Array<FoundConnectionInfoRO>>
implements IGetConnectionsInfoByIds
{
constructor(
@Inject(BaseType.GLOBAL_DB_CONTEXT)
protected _dbContext: IGlobalDatabaseContext,
) {
Comment on lines +12 to +20

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

Most other SaaS microservice use cases are explicitly request-scoped (e.g., CreateConnectionForHostedDbUseCase, DeleteConnectionForHostedDbUseCase). This use case is left as default scope, but it depends on GlobalDatabaseContext which is request-scoped; making the scope explicit (@Injectable({ scope: Scope.REQUEST })) would better match the module’s established pattern and avoid surprising DI scope behavior.

Copilot uses AI. Check for mistakes.
super();
}

protected async implementation(inputData: GetConnectionsInfoByIdsDS): Promise<Array<FoundConnectionInfoRO>> {
const { connectionIds } = inputData;
if (!connectionIds || connectionIds.length === 0) {
throw new HttpException(
{
message: Messages.CONNECTION_ID_MISSING,
},
HttpStatus.BAD_REQUEST,
);
}

const connections = await this._dbContext.connectionRepository.findBy({
id: In(connectionIds),
});
Comment on lines +35 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add authorization scoping before fetching by arbitrary IDs.

The current query returns any matching connection.id records without checking caller scope (tenant/company/owner). With only service-token auth at the route level, this becomes an over-broad data access path. Please enforce scope constraints before findBy (or use a scoped repository method).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/microservices/saas-microservice/use-cases/get-connections-info-by-ids.use.case.ts`
around lines 34 - 36, The query in get-connections-info-by-ids.use.case.ts uses
this._dbContext.connectionRepository.findBy({ id: In(connectionIds) }) and
returns records by arbitrary IDs without tenant/owner scoping; update this to
enforce authorization by adding the caller's scope constraints (e.g.,
tenantId/companyId and/or ownerId) to the query or call a scoped repository
method (e.g., connectionRepository.findByScoped or findBy with { id:
In(connectionIds), tenantId: currentUser.tenantId, ownerId: currentUser.id })
and validate permissions before returning results so only connections within the
caller's authorized scope are fetched.


return connections.map((connection) => this.buildConnectionInfoRO(connection));
}

private buildConnectionInfoRO(connection: ConnectionEntity): FoundConnectionInfoRO {
return {
id: connection.id,
title: connection.title,
type: connection.type,
host: connection.host,
port: connection.port,
database: connection.database,
schema: connection.schema,
sid: connection.sid,
ssh: connection.ssh,
ssl: connection.ssl,
createdAt: connection.createdAt,
updatedAt: connection.updatedAt,
isTestConnection: connection.isTestConnection,
is_frozen: connection.is_frozen,
masterEncryption: connection.masterEncryption,
azure_encryption: connection.azure_encryption,
authSource: connection.authSource,
dataCenter: connection.dataCenter,
};
}
Comment on lines +35 to +63

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

This use case loads full ConnectionEntity records via connectionRepository.findBy({ id: In(connectionIds) }), which will include credential fields and other secrets (even if still encrypted) and then returns them directly. To avoid data exposure, fetch only the required columns (TypeORM select) and/or map results into a safe response DTO before returning.

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CompanyInfoEntity } from '../../../entities/company-info/company-info.entity.js';
import { CreatedConnectionDTO } from '../../../entities/connection/application/dto/created-connection.dto.js';
import { FoundConnectionInfoRO } from '../data-structures/found-connection-info.ro.js';
import { SaaSRegisterDemoUserAccountDS } from '../../../entities/user/application/data-structures/demo-user-account-register.ds.js';
import { SaasUsualUserRegisterDS } from '../../../entities/user/application/data-structures/usual-register-user.ds.js';
import { FoundUserDto } from '../../../entities/user/dto/found-user.dto.js';
Expand All @@ -9,6 +10,7 @@ import { CreatedConnectionResponse, SuccessResponse } from '../data-structures/c
import { CreateConnectionForHostedDbDto } from '../data-structures/create-connecttion-for-selfhosted-db.dto.js';
import { DeleteConnectionForHostedDbDto } from '../data-structures/delete-connection-for-hosted-db.dto.js';
import { FreezeConnectionsInCompanyDS } from '../data-structures/freeze-connections-in-company.ds.js';
import { GetConnectionsInfoByIdsDS } from '../data-structures/get-connections-info-by-ids.ds.js';
import { GetUserInfoByIdDS } from '../data-structures/get-user-info.ds.js';
import { GetUsersInfosByEmailDS } from '../data-structures/get-users-infos-by-email.ds.js';
import { RegisterCompanyWebhookDS } from '../data-structures/register-company.ds.js';
Expand Down Expand Up @@ -82,3 +84,7 @@ export interface IDeleteConnectionForHostedDb {
export interface IUpdateHostedConnectionPassword {
execute(inputData: UpdateHostedConnectionPasswordDto): Promise<SuccessResponse>;
}

export interface IGetConnectionsInfoByIds {
execute(inputData: GetConnectionsInfoByIdsDS): Promise<Array<FoundConnectionInfoRO>>;
}
Comment on lines +88 to +90

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

The use case interface exposes Promise<ConnectionEntity[]> as its return type. Since ConnectionEntity contains sensitive credential fields, it would be safer to define and return a dedicated response DTO (only the fields needed by SaaS) and update this interface to use that DTO type instead of the full entity.

Copilot uses AI. Check for mistakes.
Loading