From a549be68715b5cc461a9c9a75288bcdf79545ed2 Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Tue, 2 Jun 2026 07:49:38 +0000 Subject: [PATCH 1/3] typescript strict null checks --- .../authorization/auth-with-api.middleware.ts | 7 +- backend/src/authorization/auth.middleware.ts | 5 +- .../non-scoped-auth.middleware.ts | 5 +- .../src/authorization/saas-auth.middleware.ts | 3 + .../temporary-auth.middleware.ts | 5 +- .../utils/extract-token-from-header.ts | 2 +- backend/src/common/abstract-use.case.ts | 15 ++-- .../application/global-database-context.ts | 5 +- backend/src/decorators/gclid-decorator.ts | 2 +- .../decorators/master-password.decorator.ts | 2 +- .../decorators/query-table-name.decorator.ts | 5 +- .../src/decorators/query-uuid.decorator.ts | 2 +- backend/src/decorators/slug-uuid.decorator.ts | 4 +- .../decorators/slug-verification.decorator.ts | 4 +- backend/src/entities/ai/ai.service.ts | 5 +- ...est-info-from-table-with-ai-v7.use.case.ts | 6 +- .../cedar-authorization.service.ts | 11 ++- .../cedar-permissions.service.ts | 14 ++-- .../data-structures/found-company-info.ds.ts | 14 ++-- .../application/dto/found-company-logo.ro.ts | 8 +-- ...found-company-white-label-properties.ro.ts | 16 ++--- .../company-info-helper.service.ts | 2 +- .../invitation-in-company.entity.ts | 8 +-- ...company-white-label-properties.use.case.ts | 5 +- .../get-all-users-in-company.use.case.ts | 3 + ...nnections-company-display-mode.use.case.ts | 3 + .../use-cases/update-company-name.use.case.ts | 6 +- .../update-users-company-roles.use.case.ts | 23 +++++- .../verify-invite-user-in-company.use.case.ts | 10 ++- .../utils/build-found-company-info-ds.ts | 7 +- .../create-connection-properties.ds.ts | 8 +-- .../found-connection-properties.ds.ts | 24 +++---- .../connection-properties.entity.ts | 8 +-- ...nnection-properties-use.cases.interface.ts | 2 +- .../create-connection-properties.use.case.ts | 14 +++- .../find-connection-properties-use.case.ts | 2 +- .../update-connection-properties.use.case.ts | 10 ++- .../build-connection-properties-entity.ts | 20 +++--- ...ild-update-connection-properties-object.ts | 2 +- ...alidate-create-connection-properties-ds.ts | 2 +- .../data-structures/found-connections.ds.ts | 26 +++---- .../found-one-connection.ds.ts | 2 +- .../found-permissions-in-connection.ds.ts | 2 +- .../application/dto/created-connection.dto.ts | 42 +++++------ .../connection/connection.controller.ts | 8 ++- .../entities/connection/connection.entity.ts | 36 +++++----- .../use-cases/create-connection.use.case.ts | 10 +-- .../create-group-in-connection.use.case.ts | 6 ++ .../use-cases/find-one-connection.use.case.ts | 4 +- .../get-connection-diagram.use.case.ts | 8 +-- ...ssions-for-group-in-connection.use.case.ts | 6 +- ...ssions-for-group-in-connection.use.case.ts | 18 ++--- .../preview-connection-diagram.use.case.ts | 8 +-- .../use-cases/restore-connection-use.case.ts | 10 ++- .../use-cases/test-connection.use.case.ts | 8 +-- .../use-cases/unfreeze-connection.use.case.ts | 6 +- ...ate-connection-master-password.use.case.ts | 6 +- .../update-connection-title.use.case.ts | 6 +- .../use-cases/update-connection.use.case.ts | 10 ++- ...ate-connection-master-password.use.case.ts | 2 +- .../utils/build-connection-entity.ts | 4 +- ...found-user-group-in-connection-dto.util.ts | 7 +- .../decrypt-connection-credentials-async.ts | 2 +- .../connection/utils/is-host-allowed.ts | 6 +- .../utils/is-sql-connection-type.util.ts | 4 +- .../utils/is-test-connection-util.ts | 2 +- .../utils/process-aws-connection.util.ts | 3 + ...pdate-connection-entity-for-restoration.ts | 4 +- .../utils/validate-create-connection-data.ts | 2 +- .../create-custom-fields.use.case.ts | 6 +- .../use-cases/update-custom-field.use.case.ts | 8 +++ .../utils/validate-create-custom-field-dto.ts | 2 +- .../entities/demo-data/demo-data.service.ts | 72 ++++++++++--------- .../email-config/email-config.service.ts | 6 +- .../entities/email/email/email.generator.ts | 3 +- .../entities/email/email/email.interface.ts | 3 +- .../src/entities/email/email/email.service.ts | 26 +++---- backend/src/entities/group/group.entity.ts | 2 +- .../remove-user-from-group.use.case.ts | 8 +++ .../saas-add-user-in-group-v2.use.case.ts | 8 +++ .../use-cases/update-group-title.use.case.ts | 10 ++- .../utils/biuld-found-group-response.dto.ts | 2 +- .../build-remove-user-from-group-result.ds.ts | 2 +- .../use-cases/get-s3-file-url.use.case.ts | 13 ++-- .../use-cases/get-s3-upload-url.use.case.ts | 3 + .../shared-jobs/shared-jobs.service.ts | 29 +++++--- .../action-event.entity.ts | 8 +-- .../build-action-event-with-rule.util.ts | 2 +- .../data-structures/create-action-rules.ds.ts | 10 +-- .../dto/activated-table-actions.dto.ts | 4 +- ...ction-rules-with-actions-and-events.dto.ts | 4 +- .../activate-actions-in-rule.use.case.ts | 13 +++- .../use-cases/create-action-rule.use.case.ts | 14 ++-- ...n-rule-with-actions-and-events.use.case.ts | 25 ++++--- .../table-action-activation.service.ts | 52 +++++++++++--- .../table-action.entity.ts | 8 +-- .../build-table-action-with-rule.util.ts | 6 +- .../found-table-categories-with-tables.ro.ts | 4 +- ...ate-or-update-table-categories.use.case.ts | 6 +- ...d-table-categories-with-tables.use.case.ts | 19 +++-- .../create-table-filters.ds.ts | 2 +- .../create-table-filters.dto.ts | 4 +- .../create-table-filters.use.case.ts | 7 +- .../update-table-filters.use.case.ts | 3 + ...ated-table-filters-response-object.util.ts | 18 ++--- .../utils/validate-table-filters-data.util.ts | 2 +- .../data-structures/create-log-record.ds.ts | 2 +- .../table-logs-repository.interface.ts | 6 +- .../entities/table-logs/table-logs.entity.ts | 2 +- .../entities/table-logs/table-logs.service.ts | 14 ++-- .../use-cases/export-logs-as-csv.use.case.ts | 6 +- .../use-cases/find-logs.use.case.ts | 4 +- .../ai/run-schema-change-ai-loop.ts | 4 +- ...pprove-and-apply-schema-change.use-case.ts | 2 +- .../approve-batch-schema-changes.use-case.ts | 2 +- .../generate-schema-change.use-case.ts | 2 +- .../rollback-batch-schema-changes.use-case.ts | 2 +- .../rollback-schema-change.use-case.ts | 2 +- .../utils/execute-schema-change.ts | 2 +- .../create-table-settings.ds.ts | 30 ++++---- .../found-table-settings.ds.ts | 28 ++++---- .../table-settings.controller.ts | 2 +- .../table-settings.entity.ts | 18 ++--- .../create-table-settings.use.case.ts | 13 +++- .../update-table-settings.use.case.ts | 12 +++- .../utils/build-new-table-settings-entity.ts | 40 +++++------ ...update-personal-table-settings.use.case.ts | 8 ++- .../data-structures/found-table-rows.ds.ts | 14 ++-- .../found-table-structure.ds.ts | 6 +- .../data-structures/found-table.ds.ts | 4 +- .../entities/table/table-datastructures.ts | 13 ++-- .../src/entities/table/table.controller.ts | 24 ++++--- .../use-cases/add-row-in-table.use.case.ts | 19 ++++- .../bulk-update-rows-in-table.use.case.ts | 6 +- .../delete-row-from-table.use.case.ts | 6 +- .../delete-rows-from-table.use.case.ts | 14 ++-- .../export-csv-from-table.use.case.ts | 8 ++- .../find-tables-in-connection-v2.use.case.ts | 13 ++-- .../find-tables-in-connection.use.case.ts | 11 +-- .../get-row-by-primary-key.use.case.ts | 6 +- .../use-cases/get-table-rows.use.case.ts | 19 +++-- .../use-cases/update-row-in-table.use.case.ts | 11 ++- .../utils/attach-foreign-column-names.util.ts | 5 +- .../build-common-table-settings-input.util.ts | 51 +++++++++++++ .../table/utils/form-full-table-structure.ts | 2 +- .../table/utils/hash-passwords-in-row.util.ts | 3 + .../utils/process-referenced-tables.util.ts | 4 +- .../table/utils/process-uuids-in-row-util.ts | 3 + ...able-info-in-database-orchestrator.util.ts | 10 +-- .../save-tables-info-in-database.util.ts | 2 +- .../table/utils/validate-connection.util.ts | 8 +-- .../table/utils/validate-table-row.util.ts | 9 ++- ...sers-actions-and-mailing-users.use.case.ts | 5 +- ...-users-logs-and-update-actions.use.case.ts | 2 +- .../data-structures/created-secret.ds.ts | 4 +- .../data-structures/found-secret.ds.ts | 4 +- .../data-structures/get-secrets.ds.ts | 4 +- .../data-structures/update-secret.ds.ts | 4 +- .../application/dto/found-secret.dto.ts | 4 +- .../application/dto/secret-list.dto.ts | 4 +- .../use-cases/update-secret.use.case.ts | 3 + .../user-secret/user-secret.entity.ts | 6 +- .../dto/create-sign-in-audit-record.ds.ts | 2 +- .../dto/found-sign-in-audit-record.ds.ts | 20 +++--- ...gn-in-audit-custom-repository-extension.ts | 2 +- .../sign-in-audit-repository.interface.ts | 4 +- .../sign-in-audit.entity.ts | 20 +++--- .../sign-in-audit.service.ts | 2 +- .../find-sign-in-audit-logs.use.case.ts | 4 +- .../data-structures/found-user-in-group.ds.ts | 6 +- .../data-structures/register-user-ds.ts | 6 +- .../data-structures/registered-user.ds.ts | 6 +- .../data-structures/save-user-settings.ds.ts | 2 +- .../data-structures/usual-login.ds.ts | 2 +- .../src/entities/user/dto/found-user.dto.ts | 20 +++--- .../user-custom-repository-extension.ts | 17 +++-- .../repository/user.repository.interface.ts | 8 +-- .../user/use-cases/otp-login-use.case.ts | 7 +- .../request-change-user-email.use.case.ts | 10 ++- .../request-reset-user-password.use.case.ts | 2 +- .../user/use-cases/usual-login-use.case.ts | 6 +- .../use-cases/verify-user-email.use.case.ts | 8 +++ .../src/entities/user/user-helper.service.ts | 4 +- .../user-invitation/user-invitation.entity.ts | 4 +- ...on-settings-custom-repository.extension.ts | 4 +- ...r-session-settings-repository.interface.ts | 4 +- .../user-session-settings.entity.ts | 2 +- backend/src/entities/user/user.controller.ts | 5 +- backend/src/entities/user/user.entity.ts | 22 +++--- ...uild-connection-entities-from-test-dtos.ts | 2 +- .../user/utils/build-created-user.ds.ts | 2 +- .../entities/user/utils/generate-gwt-token.ts | 8 ++- .../user/utils/is-jwt-scope-need.util.ts | 8 +-- ...enerate-panel-position-with-ai.use.case.ts | 6 +- ...nerate-table-dashboard-with-ai.use.case.ts | 6 +- .../panel/use-cases/execute-panel.use.case.ts | 2 +- .../panel/use-cases/test-db-query.use.case.ts | 2 +- .../create-table-widgets.ds.ts | 6 +- .../found-table-widgets.ds.ts | 4 +- .../entities/widget/table-widget.entity.ts | 4 +- .../entities/widget/table-widget.interface.ts | 4 +- ...te-update-delete-table-widgets.use.case.ts | 11 ++- .../use-cases/find-table-widgets.use.case.ts | 10 ++- .../utils/validate-create-widgets-ds.ts | 10 ++- backend/src/enums/widget-type.enum.ts | 1 + .../custom-exceptions/validation-exception.ts | 2 +- backend/src/exceptions/text/messages.ts | 1 + .../utils/processing-messages-replace.ts | 2 +- .../src/guards/action-event-trigger.guard.ts | 7 +- backend/src/guards/company-admin.guard.ts | 8 ++- backend/src/guards/company-user.guard.ts | 8 ++- .../src/guards/connection-diagram.guard.ts | 6 +- backend/src/guards/connection-edit.guard.ts | 6 +- backend/src/guards/connection-read.guard.ts | 6 +- backend/src/guards/dashboard-create.guard.ts | 6 +- backend/src/guards/dashboard-edit.guard.ts | 8 ++- backend/src/guards/dashboard-read.guard.ts | 8 ++- backend/src/guards/group-edit.guard.ts | 6 +- backend/src/guards/group-read.guard.ts | 6 +- backend/src/guards/paid-feature.guard.ts | 8 ++- backend/src/guards/panel-edit.guard.ts | 8 ++- backend/src/guards/panel-read.guard.ts | 8 ++- .../schema-change-batch-ownership.guard.ts | 5 +- .../guards/schema-change-ownership.guard.ts | 5 +- backend/src/guards/table-add.guard.ts | 8 ++- backend/src/guards/table-ai-request.guard.ts | 8 ++- backend/src/guards/table-delete.guard.ts | 8 ++- backend/src/guards/table-edit.guard.ts | 8 ++- backend/src/guards/table-read.guard.ts | 8 ++- backend/src/guards/tables-receive.guard.ts | 6 +- .../src/helpers/check-field-autoincrement.ts | 2 +- backend/src/helpers/constants/constants.ts | 9 ++- backend/src/helpers/encryption/encryptor.ts | 35 +++++++-- .../src/helpers/is-connection-entity-agent.ts | 2 +- .../helpers/operate-values-between-curlies.ts | 7 +- .../string-connection-to-database-parsers.ts | 56 +++++++-------- .../helpers/validators/validation-helper.ts | 4 +- .../src/interceptors/sentry.interceptor.ts | 1 + .../base-saas-gateway.service.ts | 2 +- .../saas-company-gateway.service.ts | 15 ++++ .../found-connection-info.ro.ts | 26 +++---- .../data-structures/found-user-info.ro.ts | 5 ++ .../hosted-connection-credentials.ro.ts | 10 +-- .../saas-microservice/saas.controller.ts | 5 +- ...reate-connection-for-hosted-db.use.case.ts | 10 ++- ...elete-connection-for-hosted-db.use.case.ts | 7 +- .../use-cases/get-user-info.use.case.ts | 19 +++-- .../get-users-infos-by-email.use.case.ts | 12 ++-- .../use-cases/login-with-github.use.case.ts | 6 +- .../use-cases/login-with-google.use.case.ts | 8 +-- .../register-demo-user-account.use.case.ts | 2 +- .../use-cases/saas-use-cases.interface.ts | 7 +- .../saas-usual-register-user.use.case.ts | 4 +- .../utils/build-found-user-info-ro.ts | 12 ++++ .../use-cases/create-initial-user.use.case.ts | 6 +- backend/src/shared/config/app-config.ts | 18 ++--- .../src/shared/database/database.providers.ts | 2 +- .../src/shared/services/turnstile.service.ts | 4 ++ backend/tsconfig.src.json | 1 + 259 files changed, 1421 insertions(+), 804 deletions(-) create mode 100644 backend/src/entities/table/utils/build-common-table-settings-input.util.ts create mode 100644 backend/src/microservices/saas-microservice/data-structures/found-user-info.ro.ts create mode 100644 backend/src/microservices/saas-microservice/utils/build-found-user-info-ro.ts diff --git a/backend/src/authorization/auth-with-api.middleware.ts b/backend/src/authorization/auth-with-api.middleware.ts index df4f1305b..47154333b 100644 --- a/backend/src/authorization/auth-with-api.middleware.ts +++ b/backend/src/authorization/auth-with-api.middleware.ts @@ -9,7 +9,7 @@ import { } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import Sentry from '@sentry/minimal'; -import { NextFunction, Request, Response } from 'express'; +import { NextFunction, Response } from 'express'; import jwt from 'jsonwebtoken'; import { Repository } from 'typeorm'; import { JwtScopesEnum } from '../entities/user/enums/jwt-scopes.enum.js'; @@ -48,7 +48,7 @@ export class AuthWithApiMiddleware implements NestMiddleware { } } - private getTokenFromCookie(req: Request): string | undefined { + private getTokenFromCookie(req: IRequestWithCognitoInfo): string | undefined { return req.cookies?.[Constants.JWT_COOKIE_KEY_NAME]; } @@ -62,6 +62,9 @@ export class AuthWithApiMiddleware implements NestMiddleware { private async authenticateWithToken(tokenFromCookie: string, req: IRequestWithCognitoInfo): Promise { try { const jwtSecret = appConfig.auth.jwtSecret; + if (!jwtSecret) { + throw new UnauthorizedException('JWT verification failed'); + } const data = jwt.verify(tokenFromCookie, jwtSecret) as jwt.JwtPayload; const userId = data.id; diff --git a/backend/src/authorization/auth.middleware.ts b/backend/src/authorization/auth.middleware.ts index e713b3ac3..cc06fe5c6 100644 --- a/backend/src/authorization/auth.middleware.ts +++ b/backend/src/authorization/auth.middleware.ts @@ -30,7 +30,7 @@ export class AuthMiddleware implements NestMiddleware { private readonly logOutRepository: Repository, ) {} async use(req: IRequestWithCognitoInfo, _res: Response, next: NextFunction): Promise { - let token: string; + let token: string | undefined; try { token = req.cookies[Constants.JWT_COOKIE_KEY_NAME]; } catch (_e) { @@ -50,6 +50,9 @@ export class AuthMiddleware implements NestMiddleware { try { const jwtSecret = appConfig.auth.jwtSecret; + if (!jwtSecret) { + throw new UnauthorizedException('JWT verification failed'); + } const data = jwt.verify(token, jwtSecret) as jwt.JwtPayload; const userId = data.id; diff --git a/backend/src/authorization/non-scoped-auth.middleware.ts b/backend/src/authorization/non-scoped-auth.middleware.ts index 3fbd36442..d9c66ae55 100644 --- a/backend/src/authorization/non-scoped-auth.middleware.ts +++ b/backend/src/authorization/non-scoped-auth.middleware.ts @@ -26,7 +26,7 @@ export class NonScopedAuthMiddleware implements NestMiddleware { ) {} async use(req: IRequestWithCognitoInfo, _res: Response, next: NextFunction): Promise { console.log(`auth middleware triggered ->: ${new Date().toISOString()}`); - let token: string; + let token: string | undefined; try { token = req.cookies[Constants.JWT_COOKIE_KEY_NAME]; } catch (_e) { @@ -46,6 +46,9 @@ export class NonScopedAuthMiddleware implements NestMiddleware { try { const jwtSecret = appConfig.auth.jwtSecret; + if (!jwtSecret) { + throw new UnauthorizedException('JWT verification failed'); + } const data = jwt.verify(token, jwtSecret) as jwt.JwtPayload; const userId = data.id; if (!userId) { diff --git a/backend/src/authorization/saas-auth.middleware.ts b/backend/src/authorization/saas-auth.middleware.ts index 4cc039a2f..cceb0e8c5 100644 --- a/backend/src/authorization/saas-auth.middleware.ts +++ b/backend/src/authorization/saas-auth.middleware.ts @@ -16,6 +16,9 @@ export class SaaSAuthMiddleware implements NestMiddleware { } try { const jwtSecret = appConfig.auth.microserviceJwtSecret; + if (!jwtSecret) { + throw new UnauthorizedException(Messages.AUTHORIZATION_REJECTED); + } const data = jwt.verify(token, jwtSecret) as jwt.JwtPayload; const requestId = data.request_id; diff --git a/backend/src/authorization/temporary-auth.middleware.ts b/backend/src/authorization/temporary-auth.middleware.ts index 8d0aa8980..1de4fe590 100644 --- a/backend/src/authorization/temporary-auth.middleware.ts +++ b/backend/src/authorization/temporary-auth.middleware.ts @@ -28,7 +28,7 @@ export class TemporaryAuthMiddleware implements NestMiddleware { ) {} async use(req: IRequestWithCognitoInfo, _res: Response, next: NextFunction): Promise { console.log(`temporary auth middleware triggered ->: ${new Date().toISOString()}`); - let token: string; + let token: string | undefined; try { token = req.cookies[Constants.JWT_COOKIE_KEY_NAME]; } catch (_e) { @@ -48,6 +48,9 @@ export class TemporaryAuthMiddleware implements NestMiddleware { try { const jwtSecret = appConfig.auth.temporaryJwtSecret; + if (!jwtSecret) { + throw new UnauthorizedException('JWT verification failed'); + } const data = jwt.verify(token, jwtSecret) as jwt.JwtPayload; const userId = data.id; if (!userId) { diff --git a/backend/src/authorization/utils/extract-token-from-header.ts b/backend/src/authorization/utils/extract-token-from-header.ts index e559e52c5..35e520301 100644 --- a/backend/src/authorization/utils/extract-token-from-header.ts +++ b/backend/src/authorization/utils/extract-token-from-header.ts @@ -1,5 +1,5 @@ import { Request } from 'express'; -export const extractTokenFromHeader = (request: Request): string | null => { +export const extractTokenFromHeader = (request: Pick): string | null => { const [type, token] = request.headers.authorization?.split(' ') ?? []; return type === 'Bearer' ? token : null; }; diff --git a/backend/src/common/abstract-use.case.ts b/backend/src/common/abstract-use.case.ts index b5bcaceec..3ef52830a 100644 --- a/backend/src/common/abstract-use.case.ts +++ b/backend/src/common/abstract-use.case.ts @@ -30,20 +30,27 @@ abstract class AbstractUseCase { protected abstract implementation(inputData: TInputData): Promise | TOutputData; + private getDbContext(): IDatabaseContext { + if (!this._dbContext) { + throw new Error('Database context is not initialized for this use case.'); + } + return this._dbContext; + } + private async startTransaction(): Promise { - await this._dbContext.startTransaction(); + await this.getDbContext().startTransaction(); } private async commitTransaction(): Promise { - await this._dbContext.commitTransaction(); + await this.getDbContext().commitTransaction(); } private async rollbackTransaction(): Promise { - await this._dbContext.rollbackTransaction(); + await this.getDbContext().rollbackTransaction(); } private async releaseQueryRunner(): Promise { - await this._dbContext.releaseQueryRunner(); + await this.getDbContext().releaseQueryRunner(); } } diff --git a/backend/src/common/application/global-database-context.ts b/backend/src/common/application/global-database-context.ts index 0a300738b..f5111d292 100644 --- a/backend/src/common/application/global-database-context.ts +++ b/backend/src/common/application/global-database-context.ts @@ -452,10 +452,9 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext { return this._schemaChangeChatMessageRepository; } - public startTransaction(): Promise { + public async startTransaction(): Promise { this._queryRunner = this.appDataSource.createQueryRunner(); - this._queryRunner.startTransaction(); - return; + await this._queryRunner.startTransaction(); } public async commitTransaction(): Promise { diff --git a/backend/src/decorators/gclid-decorator.ts b/backend/src/decorators/gclid-decorator.ts index c8807e720..9acd0a9a2 100644 --- a/backend/src/decorators/gclid-decorator.ts +++ b/backend/src/decorators/gclid-decorator.ts @@ -1,7 +1,7 @@ import { createParamDecorator, ExecutionContext } from '@nestjs/common'; import { IRequestWithCognitoInfo } from '../authorization/cognito-decoded.interface.js'; -export const GCLlId = createParamDecorator((_data: unknown, ctx: ExecutionContext): string => { +export const GCLlId = createParamDecorator((_data: unknown, ctx: ExecutionContext): string | null => { const request: IRequestWithCognitoInfo = ctx.switchToHttp().getRequest(); if (request.headers) { return (request.headers.gclid as string | undefined) ?? null; diff --git a/backend/src/decorators/master-password.decorator.ts b/backend/src/decorators/master-password.decorator.ts index 472cbfec7..aac8a46c9 100644 --- a/backend/src/decorators/master-password.decorator.ts +++ b/backend/src/decorators/master-password.decorator.ts @@ -1,7 +1,7 @@ import { createParamDecorator, ExecutionContext } from '@nestjs/common'; import { IRequestWithCognitoInfo } from '../authorization/cognito-decoded.interface.js'; -export const MasterPassword = createParamDecorator((_data: unknown, ctx: ExecutionContext): string => { +export const MasterPassword = createParamDecorator((_data: unknown, ctx: ExecutionContext): string | null => { const request: IRequestWithCognitoInfo = ctx.switchToHttp().getRequest(); const masterPwd = request.headers.masterpwd as string | undefined; return masterPwd ? masterPwd : null; diff --git a/backend/src/decorators/query-table-name.decorator.ts b/backend/src/decorators/query-table-name.decorator.ts index f52027ba4..6fe46441c 100644 --- a/backend/src/decorators/query-table-name.decorator.ts +++ b/backend/src/decorators/query-table-name.decorator.ts @@ -6,8 +6,9 @@ import { isObjectPropertyExists } from '../helpers/validators/is-object-property export const QueryTableName = createParamDecorator((_data: unknown, ctx: ExecutionContext): string => { const request: IRequestWithCognitoInfo = ctx.switchToHttp().getRequest(); const query = request.query; - if (isObjectPropertyExists(query, 'tableName') && query.tableName.length > 0) { - return query.tableName; + const tableName = query.tableName; + if (isObjectPropertyExists(query, 'tableName') && typeof tableName === 'string' && tableName.length > 0) { + return tableName; } throw new BadRequestException(Messages.TABLE_NAME_MISSING); }); diff --git a/backend/src/decorators/query-uuid.decorator.ts b/backend/src/decorators/query-uuid.decorator.ts index 34229a609..876d0c81f 100644 --- a/backend/src/decorators/query-uuid.decorator.ts +++ b/backend/src/decorators/query-uuid.decorator.ts @@ -10,7 +10,7 @@ export const QueryUuid = createParamDecorator((paramName: string, ctx: Execution if (isObjectPropertyExists(query, paramName)) { // eslint-disable-next-line security/detect-object-injection const uuId = query[paramName]; - if (ValidationHelper.isValidUUID(uuId) || ValidationHelper.isValidNanoId(uuId)) { + if (typeof uuId === 'string' && (ValidationHelper.isValidUUID(uuId) || ValidationHelper.isValidNanoId(uuId))) { return uuId; } throw new BadRequestException(Messages.UUID_INVALID); diff --git a/backend/src/decorators/slug-uuid.decorator.ts b/backend/src/decorators/slug-uuid.decorator.ts index 1eeff7eaa..4ed6099dd 100644 --- a/backend/src/decorators/slug-uuid.decorator.ts +++ b/backend/src/decorators/slug-uuid.decorator.ts @@ -41,9 +41,9 @@ export const SlugUuid = createParamDecorator( throw new BadRequestException(Messages.UUID_INVALID); } // eslint-disable-next-line security/detect-object-injection - const uuId: string = request.params?.[parameterName]; + const uuId: string | undefined = request.params?.[parameterName]; - if (ValidationHelper.isValidUUID(uuId) || ValidationHelper.isValidNanoId(uuId)) { + if (uuId && (ValidationHelper.isValidUUID(uuId) || ValidationHelper.isValidNanoId(uuId))) { return uuId; } throw new BadRequestException(Messages.UUID_INVALID); diff --git a/backend/src/decorators/slug-verification.decorator.ts b/backend/src/decorators/slug-verification.decorator.ts index 93ecb559a..7b5d17ddf 100644 --- a/backend/src/decorators/slug-verification.decorator.ts +++ b/backend/src/decorators/slug-verification.decorator.ts @@ -8,8 +8,8 @@ export const VerificationString = createParamDecorator( (paramName: SlugVerificationType, ctx: ExecutionContext): string => { const request: IRequestWithCognitoInfo = ctx.switchToHttp().getRequest(); // eslint-disable-next-line security/detect-object-injection - const verificationString: string = request.params?.[paramName] || request.params?.slug; - const isValidString = ValidationHelper.isValidVerificationString(verificationString); + const verificationString: string | undefined = request.params?.[paramName] || request.params?.slug; + const isValidString = !!verificationString && ValidationHelper.isValidVerificationString(verificationString); if (isValidString) { return verificationString; } diff --git a/backend/src/entities/ai/ai.service.ts b/backend/src/entities/ai/ai.service.ts index d261d9116..f790a0a18 100644 --- a/backend/src/entities/ai/ai.service.ts +++ b/backend/src/entities/ai/ai.service.ts @@ -326,7 +326,10 @@ IMPORTANT: } } settings.columns_view = filteredColumnsView; - settings.ordering = this.mapOrdering(tableSettings.ordering); + const mappedOrdering = this.mapOrdering(tableSettings.ordering); + if (mappedOrdering !== null) { + settings.ordering = mappedOrdering; + } settings.ordering_field = validColumnNames.includes(tableSettings.ordering_field) ? tableSettings.ordering_field : null; diff --git a/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts b/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts index 538b06cc5..a060ebd3a 100644 --- a/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts +++ b/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts @@ -61,7 +61,7 @@ export class RequestInfoFromTableWithAIUseCaseV7 const systemPrompt = createDatabaseQuerySystemPrompt( tableName, foundConnection.type as ConnectionTypesEnum, - foundConnection.schema, + foundConnection.schema ?? undefined, ); let chatIdForHeader: string | null = null; @@ -362,9 +362,9 @@ export class RequestInfoFromTableWithAIUseCaseV7 throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); } - let userEmail: string; + let userEmail = ''; if (isConnectionTypeAgent(foundConnection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(user_id); + userEmail = (await this._dbContext.userRepository.getUserEmailOrReturnNull(user_id)) ?? ''; } const connectionProperties = diff --git a/backend/src/entities/cedar-authorization/cedar-authorization.service.ts b/backend/src/entities/cedar-authorization/cedar-authorization.service.ts index 2ee0f30a0..260a84b67 100644 --- a/backend/src/entities/cedar-authorization/cedar-authorization.service.ts +++ b/backend/src/entities/cedar-authorization/cedar-authorization.service.ts @@ -44,20 +44,24 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On switch (actionPrefix) { case 'connection': + if (!connectionId) return false; resourceType = CedarResourceType.Connection; resourceId = connectionId; break; case 'group': + if (!groupId) return false; resourceType = CedarResourceType.Group; - connectionId = await this.getConnectionIdForGroup(groupId); + connectionId = (await this.getConnectionIdForGroup(groupId)) ?? undefined; if (!connectionId) return false; resourceId = groupId; break; case 'table': + if (!connectionId) return false; resourceType = CedarResourceType.Table; resourceId = `${connectionId}/${tableName}`; break; case 'actionEvent': { + if (!connectionId) return false; if (!tableName || !actionEventId) return false; resourceType = CedarResourceType.ActionEvent; resourceId = `${connectionId}/${tableName}/${actionEventId}`; @@ -74,6 +78,7 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On ); } case 'dashboard': { + if (!connectionId) return false; resourceType = CedarResourceType.Dashboard; const needsSentinel = action === CedarAction.DashboardCreate || !dashboardId; const effectiveDashboardId = needsSentinel ? '__new__' : dashboardId; @@ -90,6 +95,7 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On ); } case 'panel': { + if (!connectionId) return false; resourceType = CedarResourceType.Panel; const needsSentinel = action === CedarAction.PanelCreate || !panelId; const effectivePanelId = needsSentinel ? '__new__' : panelId; @@ -109,6 +115,7 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On return false; } + if (!connectionId) return false; return this.evaluate(userId, connectionId, action, resourceType, resourceId, tableName, dashboardId, undefined); } @@ -257,7 +264,7 @@ export class CedarAuthorizationService implements ICedarAuthorizationService, On } private loadPoliciesPerGroup(userGroups: Array): string[] { - return userGroups.map((g) => g.cedarPolicy).filter(Boolean); + return userGroups.map((g) => g.cedarPolicy).filter((policy): policy is string => Boolean(policy)); } private async assertUserNotSuspended(userId: string): Promise { diff --git a/backend/src/entities/cedar-authorization/cedar-permissions.service.ts b/backend/src/entities/cedar-authorization/cedar-permissions.service.ts index 69edf0ba3..544bd8903 100644 --- a/backend/src/entities/cedar-authorization/cedar-permissions.service.ts +++ b/backend/src/entities/cedar-authorization/cedar-permissions.service.ts @@ -77,10 +77,12 @@ export class CedarPermissionsService implements IUserAccessRepository { for (const group of allGroups) { const connId = group.connection?.id; if (!connId) continue; - if (!groupsByConnection.has(connId)) { - groupsByConnection.set(connId, []); + let connectionGroups = groupsByConnection.get(connId); + if (!connectionGroups) { + connectionGroups = []; + groupsByConnection.set(connId, connectionGroups); } - groupsByConnection.get(connId).push(group); + connectionGroups.push(group); } for (const connectionId of connectionIds) { @@ -90,7 +92,7 @@ export class CedarPermissionsService implements IUserAccessRepository { continue; } - const policies = userGroups.map((g) => g.cedarPolicy).filter(Boolean); + const policies = userGroups.map((g) => g.cedarPolicy).filter((policy): policy is string => Boolean(policy)); if (policies.length === 0) { result.set(connectionId, AccessLevelEnum.none); continue; @@ -258,7 +260,7 @@ export class CedarPermissionsService implements IUserAccessRepository { cognitoUserName: string, connectionId: string, tableName: string, - _masterPwd: string, + _masterPwd?: string, ): Promise { const ctx = await this.loadContext(connectionId, cognitoUserName); if (!ctx) return false; @@ -507,7 +509,7 @@ export class CedarPermissionsService implements IUserAccessRepository { const userGroups = await this.globalDbContext.groupRepository.findAllUserGroupsInConnection(connectionId, userId); if (userGroups.length === 0) return null; - const policies = userGroups.map((g) => g.cedarPolicy).filter(Boolean); + const policies = userGroups.map((g) => g.cedarPolicy).filter((policy): policy is string => Boolean(policy)); if (policies.length === 0) return null; return { userGroups, policies }; diff --git a/backend/src/entities/company-info/application/data-structures/found-company-info.ds.ts b/backend/src/entities/company-info/application/data-structures/found-company-info.ds.ts index c396799ec..96ffb7d10 100644 --- a/backend/src/entities/company-info/application/data-structures/found-company-info.ds.ts +++ b/backend/src/entities/company-info/application/data-structures/found-company-info.ds.ts @@ -29,7 +29,7 @@ export class FoundUserCompanyInfoDs { is_payment_method_added?: boolean; @ApiProperty({ required: false }) - is2faEnabled: boolean; + is2faEnabled?: boolean; @ApiProperty() show_test_connections: boolean; @@ -37,14 +37,14 @@ export class FoundUserCompanyInfoDs { @ApiProperty({ required: false }) custom_domain: string | null; - @ApiProperty({ required: false, type: FoundCompanyImageInfo }) - logo: FoundCompanyImageInfo; + @ApiProperty({ required: false, type: FoundCompanyImageInfo, nullable: true }) + logo: FoundCompanyImageInfo | null; - @ApiProperty({ required: false, type: FoundCompanyImageInfo }) - favicon: FoundCompanyImageInfo; + @ApiProperty({ required: false, type: FoundCompanyImageInfo, nullable: true }) + favicon: FoundCompanyImageInfo | null; - @ApiProperty({ required: false }) - tab_title: string; + @ApiProperty({ required: false, nullable: true }) + tab_title: string | null; } export class FoundUserFullCompanyInfoDs extends FoundUserCompanyInfoDs { diff --git a/backend/src/entities/company-info/application/dto/found-company-logo.ro.ts b/backend/src/entities/company-info/application/dto/found-company-logo.ro.ts index 5c71c379a..8a5df715b 100644 --- a/backend/src/entities/company-info/application/dto/found-company-logo.ro.ts +++ b/backend/src/entities/company-info/application/dto/found-company-logo.ro.ts @@ -8,11 +8,11 @@ export class FoundCompanyImageInfo { } export class FoundCompanyLogoRO { - @ApiProperty({ type: FoundCompanyImageInfo }) - logo: FoundCompanyImageInfo; + @ApiProperty({ type: FoundCompanyImageInfo, nullable: true }) + logo: FoundCompanyImageInfo | null; } export class FoundCompanyFaviconRO { - @ApiProperty({ type: FoundCompanyImageInfo }) - favicon: FoundCompanyImageInfo; + @ApiProperty({ type: FoundCompanyImageInfo, nullable: true }) + favicon: FoundCompanyImageInfo | null; } diff --git a/backend/src/entities/company-info/application/dto/found-company-white-label-properties.ro.ts b/backend/src/entities/company-info/application/dto/found-company-white-label-properties.ro.ts index 44805009c..285f95843 100644 --- a/backend/src/entities/company-info/application/dto/found-company-white-label-properties.ro.ts +++ b/backend/src/entities/company-info/application/dto/found-company-white-label-properties.ro.ts @@ -3,15 +3,15 @@ import { SubscriptionLevelEnum } from '../../../../enums/subscription-level.enum import { FoundCompanyImageInfo } from './found-company-logo.ro.js'; export class FoundCompanyWhiteLabelPropertiesRO { - @ApiProperty({ type: FoundCompanyImageInfo, required: false }) - logo: FoundCompanyImageInfo; + @ApiProperty({ type: FoundCompanyImageInfo, required: false, nullable: true }) + logo: FoundCompanyImageInfo | null; - @ApiProperty({ type: FoundCompanyImageInfo, required: false }) - favicon: FoundCompanyImageInfo; + @ApiProperty({ type: FoundCompanyImageInfo, required: false, nullable: true }) + favicon: FoundCompanyImageInfo | null; - @ApiProperty({ type: String, required: false }) - tab_title: string; + @ApiProperty({ type: String, required: false, nullable: true }) + tab_title: string | null; - @ApiProperty({ enum: SubscriptionLevelEnum }) - subscriptionLevel: SubscriptionLevelEnum; + @ApiProperty({ enum: SubscriptionLevelEnum, nullable: true }) + subscriptionLevel: SubscriptionLevelEnum | null; } diff --git a/backend/src/entities/company-info/company-info-helper.service.ts b/backend/src/entities/company-info/company-info-helper.service.ts index fa70390c7..55444fbe2 100644 --- a/backend/src/entities/company-info/company-info-helper.service.ts +++ b/backend/src/entities/company-info/company-info-helper.service.ts @@ -27,7 +27,7 @@ export class CompanyInfoHelperService { this._dbContext.invitationInCompanyRepository.countNonExpiredInvitationsInCompany(companyId), ]); - if (companyInformationFromSaaS.subscriptionLevel === SubscriptionLevelEnum.FREE_PLAN) { + if (companyInformationFromSaaS?.subscriptionLevel === SubscriptionLevelEnum.FREE_PLAN) { return countUsersInCompany + countInvitationsInCompany < Constants.FREE_PLAN_USERS_COUNT; } return true; diff --git a/backend/src/entities/company-info/invitation-in-company/invitation-in-company.entity.ts b/backend/src/entities/company-info/invitation-in-company/invitation-in-company.entity.ts index 5fa0333e5..652fde43d 100644 --- a/backend/src/entities/company-info/invitation-in-company/invitation-in-company.entity.ts +++ b/backend/src/entities/company-info/invitation-in-company/invitation-in-company.entity.ts @@ -17,16 +17,16 @@ export class InvitationInCompanyEntity { @PrimaryGeneratedColumn('uuid') id: string; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) verification_string: string | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) groupId: string | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) inviterId: string | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) invitedUserEmail: string | null; @Column('enum', { diff --git a/backend/src/entities/company-info/use-cases/find-company-white-label-properties.use.case.ts b/backend/src/entities/company-info/use-cases/find-company-white-label-properties.use.case.ts index d9a4037cb..506600ff2 100644 --- a/backend/src/entities/company-info/use-cases/find-company-white-label-properties.use.case.ts +++ b/backend/src/entities/company-info/use-cases/find-company-white-label-properties.use.case.ts @@ -2,6 +2,7 @@ import { Inject, Injectable, NotFoundException } from '@nestjs/common'; 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 { SubscriptionLevelEnum } from '../../../enums/subscription-level.enum.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { isSaaS } from '../../../helpers/app/is-saas.js'; import { SaasCompanyGatewayService } from '../../../microservices/gateways/saas-gateway.ts/saas-company-gateway.service.js'; @@ -27,13 +28,13 @@ export class FindCompanyWhiteLabelPropertiesUseCase throw new NotFoundException(Messages.COMPANY_NOT_FOUND); } - let companySubscriptionLevel = null; + let companySubscriptionLevel: SubscriptionLevelEnum | null = null; if (isSaaS()) { const companyInfoFromSaas = await this.saasCompanyGatewayService.getCompanyInfo(companyId); if (!companyInfoFromSaas) { throw new NotFoundException(Messages.COMPANY_NOT_FOUND); } - companySubscriptionLevel = companyInfoFromSaas.subscriptionLevel; + companySubscriptionLevel = companyInfoFromSaas.subscriptionLevel ?? null; } return { diff --git a/backend/src/entities/company-info/use-cases/get-all-users-in-company.use.case.ts b/backend/src/entities/company-info/use-cases/get-all-users-in-company.use.case.ts index ff1aca115..40aa574ab 100644 --- a/backend/src/entities/company-info/use-cases/get-all-users-in-company.use.case.ts +++ b/backend/src/entities/company-info/use-cases/get-all-users-in-company.use.case.ts @@ -45,6 +45,9 @@ export class GetAllUsersInCompanyUseCase user.id, ); const userInfo = buildSimpleUserInfoDs(user); + if (!userInfo) { + return; + } const userRO: SimpleFoundUserInCompanyInfoDs = { ...userInfo, user_membership: usersConnectionsWithGroups.map((connection) => { diff --git a/backend/src/entities/company-info/use-cases/toggle-test-connections-company-display-mode.use.case.ts b/backend/src/entities/company-info/use-cases/toggle-test-connections-company-display-mode.use.case.ts index c97cdf30b..b8407215f 100644 --- a/backend/src/entities/company-info/use-cases/toggle-test-connections-company-display-mode.use.case.ts +++ b/backend/src/entities/company-info/use-cases/toggle-test-connections-company-display-mode.use.case.ts @@ -27,6 +27,9 @@ export class ToggleCompanyTestConnectionsDisplayModeUseCase } const companyToUpdate = await this._dbContext.companyInfoRepository.findOne({ where: { id: foundCompanyInfo.id } }); + if (!companyToUpdate) { + throw new NotFoundException(Messages.COMPANY_NOT_FOUND); + } companyToUpdate.show_test_connections = displayMode; await this._dbContext.companyInfoRepository.save(companyToUpdate); return { diff --git a/backend/src/entities/company-info/use-cases/update-company-name.use.case.ts b/backend/src/entities/company-info/use-cases/update-company-name.use.case.ts index e74669b68..7afda6fad 100644 --- a/backend/src/entities/company-info/use-cases/update-company-name.use.case.ts +++ b/backend/src/entities/company-info/use-cases/update-company-name.use.case.ts @@ -1,7 +1,8 @@ -import { Inject, Injectable, Scope } from '@nestjs/common'; +import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; 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 { Messages } from '../../../exceptions/text/messages.js'; import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js'; import { UpdateCompanyNameDS } from '../application/data-structures/update-company-name.ds.js'; import { IUpdateCompanyName } from './company-info-use-cases.interface.js'; @@ -21,6 +22,9 @@ export class UpdateCompanyNameUseCase protected async implementation(inputData: UpdateCompanyNameDS): Promise { const { companyId, name } = inputData; const foundCompany = await this._dbContext.companyInfoRepository.findOneBy({ id: companyId }); + if (!foundCompany) { + throw new NotFoundException(Messages.COMPANY_NOT_FOUND); + } foundCompany.name = name; await this._dbContext.companyInfoRepository.save(foundCompany); return { diff --git a/backend/src/entities/company-info/use-cases/update-users-company-roles.use.case.ts b/backend/src/entities/company-info/use-cases/update-users-company-roles.use.case.ts index 3a613302b..e7338005b 100644 --- a/backend/src/entities/company-info/use-cases/update-users-company-roles.use.case.ts +++ b/backend/src/entities/company-info/use-cases/update-users-company-roles.use.case.ts @@ -50,16 +50,33 @@ export class UpdateUsersCompanyRolesUseCase const clearUsersWithNewRoles: Array<{ userId: string; role: UserRoleEnum }> = companyUsersToUpdateIds.map( (userId) => { + const receivedUser = users.find((user) => user.userId === userId); + if (!receivedUser) { + throw new HttpException( + { + message: Messages.NO_USERS_FOUND_TO_UPDATE_ROLES, + }, + HttpStatus.NOT_FOUND, + ); + } return { userId: userId, - role: users.find((user) => user.userId === userId).role, + role: receivedUser.role, }; }, ); const usersWithUpdatedRoles = companyUsersToUpdate.map((user) => { - const newUserRole = clearUsersWithNewRoles.find((userWithNewRole) => userWithNewRole.userId === user.id).role; - user.role = newUserRole; + const userWithNewRole = clearUsersWithNewRoles.find((userWithNewRole) => userWithNewRole.userId === user.id); + if (!userWithNewRole) { + throw new HttpException( + { + message: Messages.NO_USERS_FOUND_TO_UPDATE_ROLES, + }, + HttpStatus.NOT_FOUND, + ); + } + user.role = userWithNewRole.role; return user; }); diff --git a/backend/src/entities/company-info/use-cases/verify-invite-user-in-company.use.case.ts b/backend/src/entities/company-info/use-cases/verify-invite-user-in-company.use.case.ts index bfb1e7ca6..fbf07c39e 100644 --- a/backend/src/entities/company-info/use-cases/verify-invite-user-in-company.use.case.ts +++ b/backend/src/entities/company-info/use-cases/verify-invite-user-in-company.use.case.ts @@ -43,6 +43,14 @@ export class VerifyInviteUserInCompanyAndConnectionGroupUseCase groupId, role, } = foundInvitation; + if (!foundInvitation.invitedUserEmail) { + throw new HttpException( + { + message: Messages.VERIFICATION_LINK_INCORRECT, + }, + HttpStatus.BAD_REQUEST, + ); + } const invitedUserEmail = foundInvitation.invitedUserEmail.toLowerCase(); const foundUser = users.find((user) => user.email === invitedUserEmail); if (foundUser?.isActive) { @@ -71,7 +79,7 @@ export class VerifyInviteUserInCompanyAndConnectionGroupUseCase const savedUser = await this._dbContext.userRepository.saveUserEntity(newUser); if (groupId) { const foundGroup = await this._dbContext.groupRepository.findGroupByIdWithConnectionAndUsers(groupId); - if (!foundGroup) { + if (!foundGroup || !foundGroup.users) { throw new HttpException( { message: Messages.GROUP_NOT_FOUND, diff --git a/backend/src/entities/company-info/utils/build-found-company-info-ds.ts b/backend/src/entities/company-info/utils/build-found-company-info-ds.ts index e399ef3b9..f72c62e2e 100644 --- a/backend/src/entities/company-info/utils/build-found-company-info-ds.ts +++ b/backend/src/entities/company-info/utils/build-found-company-info-ds.ts @@ -1,5 +1,4 @@ import { FoundSassCompanyInfoDS } from '../../../microservices/gateways/saas-gateway.ts/data-structures/found-saas-company-info.ds.js'; -import { FoundSimpleConnectionInfoDS } from '../../connection/application/data-structures/found-connections.ds.js'; import { UserRoleEnum } from '../../user/enums/user-role.enum.js'; import { buildSimpleUserInfoDs } from '../../user/utils/build-created-user.ds.js'; import { @@ -26,7 +25,7 @@ export function buildFoundCompanyFullInfoDs( companyCustomDomain, userRole, ) as any; - const connectionsRO: Array = companyInfoFromCore.connections.map((connection) => { + const connectionsRO = companyInfoFromCore.connections.map((connection) => { return { id: connection.id, createdAt: connection.createdAt, @@ -40,7 +39,7 @@ export function buildFoundCompanyFullInfoDs( isMain: group.isMain, title: group.title, cedarPolicy: group.cedarPolicy, - users: group.users.map((user) => buildSimpleUserInfoDs(user)).filter((user) => !!user), + users: (group.users ?? []).map((user) => buildSimpleUserInfoDs(user)).filter((user) => !!user), }; }), }; @@ -64,7 +63,7 @@ export function buildFoundCompanyFullInfoDs( export function buildFoundCompanyInfoDs( companyInfoFromCore: CompanyInfoEntity, companyInfoFromSaas: FoundSassCompanyInfoDS | null, - companyCustomDomain: string, + companyCustomDomain: string | null, userRole?: UserRoleEnum, ): FoundUserCompanyInfoDs { if (!companyInfoFromSaas) { diff --git a/backend/src/entities/connection-properties/application/data-structures/create-connection-properties.ds.ts b/backend/src/entities/connection-properties/application/data-structures/create-connection-properties.ds.ts index 49c933f20..802d828d9 100644 --- a/backend/src/entities/connection-properties/application/data-structures/create-connection-properties.ds.ts +++ b/backend/src/entities/connection-properties/application/data-structures/create-connection-properties.ds.ts @@ -1,12 +1,12 @@ export class CreateConnectionPropertiesDs { - userId: string; + userId: string | null; connectionId: string; - master_password: string; + master_password: string | null; hidden_tables?: Array; logo_url?: string; primary_color?: string; secondary_color?: string; - hostname?: string; + hostname?: string | null; company_name?: string; tables_audit?: boolean; human_readable_table_names?: boolean; @@ -17,5 +17,5 @@ export class CreateConnectionPropertiesDs { tables: Array; category_color: string; category_id: string; - }>; + }> | null; } diff --git a/backend/src/entities/connection-properties/application/data-structures/found-connection-properties.ds.ts b/backend/src/entities/connection-properties/application/data-structures/found-connection-properties.ds.ts index 799d8cc6a..ddd1e077c 100644 --- a/backend/src/entities/connection-properties/application/data-structures/found-connection-properties.ds.ts +++ b/backend/src/entities/connection-properties/application/data-structures/found-connection-properties.ds.ts @@ -5,14 +5,14 @@ export class FoundConnectionPropertiesDs { @ApiProperty() id: string; - @ApiProperty({ isArray: true, type: String }) - hidden_tables: Array; + @ApiProperty({ isArray: true, type: String, nullable: true }) + hidden_tables: Array | null; - @ApiProperty() - connectionId: string; + @ApiProperty({ required: false }) + connectionId?: string; - @ApiProperty() - logo_url: string; + @ApiProperty({ nullable: true, type: String }) + logo_url: string | null; @ApiProperty() primary_color: string; @@ -20,11 +20,11 @@ export class FoundConnectionPropertiesDs { @ApiProperty() secondary_color: string; - @ApiProperty() - hostname: string; + @ApiProperty({ nullable: true, type: String }) + hostname: string | null; - @ApiProperty() - company_name: string; + @ApiProperty({ nullable: true, type: String }) + company_name: string | null; @ApiProperty() tables_audit: boolean; @@ -35,8 +35,8 @@ export class FoundConnectionPropertiesDs { @ApiProperty() allow_ai_requests: boolean; - @ApiProperty() - default_showing_table: string; + @ApiProperty({ nullable: true, type: String }) + default_showing_table: string | null; @ApiProperty({ isArray: true }) table_categories: Array; diff --git a/backend/src/entities/connection-properties/connection-properties.entity.ts b/backend/src/entities/connection-properties/connection-properties.entity.ts index fbcaea08f..0732020ce 100644 --- a/backend/src/entities/connection-properties/connection-properties.entity.ts +++ b/backend/src/entities/connection-properties/connection-properties.entity.ts @@ -10,7 +10,7 @@ export class ConnectionPropertiesEntity { @Column('varchar', { array: true, default: null }) hidden_tables: string[] | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) logo_url: string | null; @Column({ default: '' }) @@ -19,10 +19,10 @@ export class ConnectionPropertiesEntity { @Column({ default: '' }) secondary_color: string; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) hostname: string | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) company_name: string | null; @Column({ default: true, type: 'boolean' }) @@ -34,7 +34,7 @@ export class ConnectionPropertiesEntity { @Column({ default: true, type: 'boolean' }) allow_ai_requests: boolean; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) default_showing_table: string | null; @OneToOne( diff --git a/backend/src/entities/connection-properties/use-cases/connection-properties-use.cases.interface.ts b/backend/src/entities/connection-properties/use-cases/connection-properties-use.cases.interface.ts index d44926874..ebc6e1414 100644 --- a/backend/src/entities/connection-properties/use-cases/connection-properties-use.cases.interface.ts +++ b/backend/src/entities/connection-properties/use-cases/connection-properties-use.cases.interface.ts @@ -3,7 +3,7 @@ import { CreateConnectionPropertiesDs } from '../application/data-structures/cre import { FoundConnectionPropertiesDs } from '../application/data-structures/found-connection-properties.ds.js'; export interface IFindConnectionProperties { - execute(connectionId: string, inTransaction: InTransactionEnum): Promise; + execute(connectionId: string, inTransaction: InTransactionEnum): Promise; } export interface ICreateConnectionProperties { diff --git a/backend/src/entities/connection-properties/use-cases/create-connection-properties.use.case.ts b/backend/src/entities/connection-properties/use-cases/create-connection-properties.use.case.ts index 2069037b9..75b1022c3 100644 --- a/backend/src/entities/connection-properties/use-cases/create-connection-properties.use.case.ts +++ b/backend/src/entities/connection-properties/use-cases/create-connection-properties.use.case.ts @@ -1,7 +1,9 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { HttpException } from '@nestjs/common/exceptions/http.exception.js'; 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 { Messages } from '../../../exceptions/text/messages.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; import { CreateConnectionPropertiesDs } from '../application/data-structures/create-connection-properties.ds.js'; import { FoundConnectionPropertiesDs } from '../application/data-structures/found-connection-properties.ds.js'; @@ -26,8 +28,16 @@ export class CreateConnectionPropertiesUseCase const { connectionId, master_password, table_categories } = inputData; let foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( connectionId, - master_password, + master_password ?? '', ); + if (!foundConnection) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } await validateCreateConnectionPropertiesDs(inputData, foundConnection); const newConnectionProperties = buildConnectionPropertiesEntity(inputData, foundConnection); const createdConnectionProperties = diff --git a/backend/src/entities/connection-properties/use-cases/find-connection-properties-use.case.ts b/backend/src/entities/connection-properties/use-cases/find-connection-properties-use.case.ts index f82aea0fd..e1abbe294 100644 --- a/backend/src/entities/connection-properties/use-cases/find-connection-properties-use.case.ts +++ b/backend/src/entities/connection-properties/use-cases/find-connection-properties-use.case.ts @@ -18,7 +18,7 @@ export class FindConnectionPropertiesUseCase super(); } - protected async implementation(connectionId: string): Promise { + protected async implementation(connectionId: string): Promise { const foundConnectionProperties = await this._dbContext.connectionPropertiesRepository.findConnectionProperties(connectionId); if (!foundConnectionProperties) { diff --git a/backend/src/entities/connection-properties/use-cases/update-connection-properties.use.case.ts b/backend/src/entities/connection-properties/use-cases/update-connection-properties.use.case.ts index 0c974c4f2..49a32b9d4 100644 --- a/backend/src/entities/connection-properties/use-cases/update-connection-properties.use.case.ts +++ b/backend/src/entities/connection-properties/use-cases/update-connection-properties.use.case.ts @@ -31,8 +31,16 @@ export class UpdateConnectionPropertiesUseCase const { connectionId, master_password, table_categories } = inputData; const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( connectionId, - master_password, + master_password ?? '', ); + if (!foundConnection) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } await validateCreateConnectionPropertiesDs(inputData, foundConnection); const connectionPropertiesToUpdate = await this._dbContext.connectionPropertiesRepository.findConnectionProperties(connectionId); diff --git a/backend/src/entities/connection-properties/utils/build-connection-properties-entity.ts b/backend/src/entities/connection-properties/utils/build-connection-properties-entity.ts index 4e487721a..d11d473ba 100644 --- a/backend/src/entities/connection-properties/utils/build-connection-properties-entity.ts +++ b/backend/src/entities/connection-properties/utils/build-connection-properties-entity.ts @@ -9,15 +9,15 @@ export function buildConnectionPropertiesEntity( const { hidden_tables } = propertiesInfo; const newConnectionProperties = new ConnectionPropertiesEntity(); newConnectionProperties.connection = connection; - newConnectionProperties.hidden_tables = hidden_tables; - newConnectionProperties.logo_url = propertiesInfo.logo_url; - newConnectionProperties.primary_color = propertiesInfo.primary_color; - newConnectionProperties.secondary_color = propertiesInfo.secondary_color; - newConnectionProperties.hostname = propertiesInfo.hostname; - newConnectionProperties.company_name = propertiesInfo.company_name; - newConnectionProperties.tables_audit = propertiesInfo.tables_audit; - newConnectionProperties.human_readable_table_names = propertiesInfo.human_readable_table_names; - newConnectionProperties.allow_ai_requests = propertiesInfo.allow_ai_requests; - newConnectionProperties.default_showing_table = propertiesInfo.default_showing_table; + newConnectionProperties.hidden_tables = hidden_tables ?? null; + newConnectionProperties.logo_url = propertiesInfo.logo_url ?? null; + newConnectionProperties.primary_color = propertiesInfo.primary_color ?? ''; + newConnectionProperties.secondary_color = propertiesInfo.secondary_color ?? ''; + newConnectionProperties.hostname = propertiesInfo.hostname ?? null; + newConnectionProperties.company_name = propertiesInfo.company_name ?? null; + newConnectionProperties.tables_audit = propertiesInfo.tables_audit ?? true; + newConnectionProperties.human_readable_table_names = propertiesInfo.human_readable_table_names ?? true; + newConnectionProperties.allow_ai_requests = propertiesInfo.allow_ai_requests ?? true; + newConnectionProperties.default_showing_table = propertiesInfo.default_showing_table ?? null; return newConnectionProperties; } diff --git a/backend/src/entities/connection-properties/utils/build-update-connection-properties-object.ts b/backend/src/entities/connection-properties/utils/build-update-connection-properties-object.ts index 02386da3f..a06a011c0 100644 --- a/backend/src/entities/connection-properties/utils/build-update-connection-properties-object.ts +++ b/backend/src/entities/connection-properties/utils/build-update-connection-properties-object.ts @@ -35,7 +35,7 @@ export interface IUpdateConnectionPropertiesObject { logo_url?: string; primary_color?: string; secondary_color?: string; - hostname?: string; + hostname?: string | null; company_name?: string; tables_audit?: boolean; human_readable_table_names?: boolean; diff --git a/backend/src/entities/connection-properties/utils/validate-create-connection-properties-ds.ts b/backend/src/entities/connection-properties/utils/validate-create-connection-properties-ds.ts index 19070e2c0..3fa6d9a2f 100644 --- a/backend/src/entities/connection-properties/utils/validate-create-connection-properties-ds.ts +++ b/backend/src/entities/connection-properties/utils/validate-create-connection-properties-ds.ts @@ -24,7 +24,7 @@ export async function validateCreateConnectionPropertiesDs( errors.push(Messages.TABLE_WITH_NAME_NOT_EXISTS(hiddenTable)); } } - if (hidden_tables.includes(default_showing_table)) { + if (default_showing_table && hidden_tables.includes(default_showing_table)) { errors.push(Messages.CANT_SHOW_TABLE_AND_EXCLUDE(default_showing_table)); } } diff --git a/backend/src/entities/connection/application/data-structures/found-connections.ds.ts b/backend/src/entities/connection/application/data-structures/found-connections.ds.ts index 289c5a927..41c9014ad 100644 --- a/backend/src/entities/connection/application/data-structures/found-connections.ds.ts +++ b/backend/src/entities/connection/application/data-structures/found-connections.ds.ts @@ -13,28 +13,28 @@ export class FoundDirectConnectionsDs { title?: string; @ApiProperty() - masterEncryption: boolean; + masterEncryption?: boolean; @ApiProperty({ enum: ConnectionTypesEnum }) - type?: ConnectionTypesEnum; + type?: ConnectionTypesEnum | null; @ApiProperty() - host?: string; + host?: string | null; @ApiProperty() port?: number | null; @ApiProperty() - username?: string; + username?: string | null; @ApiProperty() - database?: string; + database?: string | null; @ApiProperty() - schema?: string; + schema?: string | null; @ApiProperty({ required: false }) - sid?: string; + sid?: string | null; @ApiProperty({ required: false }) createdAt?: Date; @@ -46,16 +46,16 @@ export class FoundDirectConnectionsDs { ssh?: boolean; @ApiProperty({ required: false }) - sshHost?: string; + sshHost?: string | null; @ApiProperty({ required: false }) - sshPort?: number; + sshPort?: number | null; @ApiProperty() - ssl?: boolean; + ssl?: boolean | null; @ApiProperty({ required: false }) - cert?: string; + cert?: string | null; @ApiProperty({ required: false }) author?: string; @@ -67,7 +67,7 @@ export class FoundDirectConnectionsDs { azure_encryption?: boolean; @ApiProperty() - signing_key: string; + signing_key?: string | null; @ApiProperty({ required: false }) authSource?: string; @@ -82,7 +82,7 @@ export class FoundDirectConnectionsDs { connection_properties?: ConnectionPropertiesEntity; @ApiProperty() - isFrozen: boolean; + isFrozen?: boolean; } export class FoundDirectConnectionsNonePermissionDs { diff --git a/backend/src/entities/connection/application/data-structures/found-one-connection.ds.ts b/backend/src/entities/connection/application/data-structures/found-one-connection.ds.ts index a572c58f6..f6a94782e 100644 --- a/backend/src/entities/connection/application/data-structures/found-one-connection.ds.ts +++ b/backend/src/entities/connection/application/data-structures/found-one-connection.ds.ts @@ -18,5 +18,5 @@ export class FoundOneConnectionDs { groupManagement: boolean; @ApiProperty({ type: FoundConnectionPropertiesDs }) - connectionProperties: FoundConnectionPropertiesDs; + connectionProperties: FoundConnectionPropertiesDs | null; } diff --git a/backend/src/entities/connection/application/data-structures/found-permissions-in-connection.ds.ts b/backend/src/entities/connection/application/data-structures/found-permissions-in-connection.ds.ts index eb87bad1a..cd6dcfa76 100644 --- a/backend/src/entities/connection/application/data-structures/found-permissions-in-connection.ds.ts +++ b/backend/src/entities/connection/application/data-structures/found-permissions-in-connection.ds.ts @@ -39,7 +39,7 @@ export class FoundTablesWithPermissionsDs { tableName: string; @ApiProperty() - display_name: string; + display_name: string | null; @ApiProperty({ type: FoundTablePermissionsDs }) accessLevel: FoundTablePermissionsDs; diff --git a/backend/src/entities/connection/application/dto/created-connection.dto.ts b/backend/src/entities/connection/application/dto/created-connection.dto.ts index 1679fc82f..9f0ea75b8 100644 --- a/backend/src/entities/connection/application/dto/created-connection.dto.ts +++ b/backend/src/entities/connection/application/dto/created-connection.dto.ts @@ -13,7 +13,7 @@ export class CreatedGroupInConnectionDTO { isMain: boolean; @ApiProperty({ isArray: true, type: FoundUserDto }) - users: Array; + users?: Array; } export class CreatedConnectionDTO { @@ -21,52 +21,52 @@ export class CreatedConnectionDTO { id: string; @ApiProperty() - title: string; + title?: string; @ApiProperty() - masterEncryption: boolean; + masterEncryption?: boolean; @ApiProperty({ enum: ConnectionTypesEnum }) - type: ConnectionTypesEnum; + type?: ConnectionTypesEnum | null; @ApiProperty() - host: string; + host?: string | null; @ApiProperty() - port: number; + port?: number; @ApiProperty() - username: string; + username?: string | null; @ApiProperty() - database: string; + database?: string | null; @ApiProperty() - schema: string; + schema?: string | null; @ApiProperty() - sid: string; + sid?: string | null; @ApiProperty() - ssh: boolean; + ssh?: boolean; @ApiProperty() - sshHost: string; + sshHost?: string | null; @ApiProperty() - sshPort: number; + sshPort?: number | null; @ApiProperty() - sshUsername: string; + sshUsername?: string | null; @ApiProperty() - ssl: boolean; + ssl?: boolean | null; @ApiProperty() - cert: string; + cert?: string | null; @ApiProperty() - azure_encryption: boolean; + azure_encryption?: boolean; @ApiProperty() token: string | null; @@ -84,16 +84,16 @@ export class CreatedConnectionDTO { isFrozen: boolean; @ApiProperty() - author: string; + author?: string; @ApiProperty() - authSource: string; + authSource?: string | null; @ApiProperty() - dataCenter: string | null; + dataCenter?: string | null; @ApiProperty() - master_hash: string; + master_hash?: string | null; @ApiProperty({ isArray: true, type: CreatedGroupInConnectionDTO }) groups: Array; diff --git a/backend/src/entities/connection/connection.controller.ts b/backend/src/entities/connection/connection.controller.ts index 4b06ca9d9..3b1d11231 100644 --- a/backend/src/entities/connection/connection.controller.ts +++ b/backend/src/entities/connection/connection.controller.ts @@ -224,7 +224,7 @@ export class ConnectionController { @MasterPassword() masterPwd: string, @UserId() userId: string, ): Promise { - let foundConnection: FoundOneConnectionDs = null; + let foundConnection: FoundOneConnectionDs | null = null; try { const findOneConnectionInput: FindOneConnectionDs = { connectionId: connectionId, @@ -381,7 +381,11 @@ export class ConnectionController { const isTest = isTestConnectionUtil(deleteResult); if (!isTest) { const userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); - const slackMessage = Messages.USER_DELETED_CONNECTION(userEmail, reasonData.reason, reasonData.message); + const slackMessage = Messages.USER_DELETED_CONNECTION( + userEmail ?? '', + reasonData.reason ?? '', + reasonData.message ?? '', + ); await slackPostMessage(slackMessage); } await this.amplitudeService.formAndSendLogRecord( diff --git a/backend/src/entities/connection/connection.entity.ts b/backend/src/entities/connection/connection.entity.ts index ffec4d569..b58e28b7a 100644 --- a/backend/src/entities/connection/connection.entity.ts +++ b/backend/src/entities/connection/connection.entity.ts @@ -40,30 +40,30 @@ export class ConnectionEntity { title?: string; @Column({ default: false, type: 'boolean' }) - masterEncryption: boolean; + masterEncryption?: boolean; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) type?: ConnectionTypesEnum | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) host?: string | null; @Column({ default: null }) port?: number; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) username?: string | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) password?: string | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) database?: string | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) schema?: string | null; - @Column({ default: null, length: 255 }) + @Column({ type: 'varchar', default: null, length: 255 }) sid?: string | null; @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) @@ -75,29 +75,29 @@ export class ConnectionEntity { @Column({ default: false, type: 'boolean' }) ssh?: boolean; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) privateSSHKey?: string | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) sshHost?: string | null; - @Column({ default: null }) + @Column({ type: 'int', default: null }) sshPort?: number | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) sshUsername?: string | null; @Column({ default: false, type: 'boolean' }) ssl?: boolean | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) cert?: string | null; @Column({ default: false, type: 'boolean' }) isTestConnection: boolean; @Column({ default: false, type: 'boolean' }) - azure_encryption: boolean; + azure_encryption?: boolean; @Column({ default: false, type: 'boolean' }) is_frozen: boolean; @@ -105,16 +105,16 @@ export class ConnectionEntity { @Column({ default: 0 }) saved_table_info: number; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) signing_key: string | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) authSource?: string | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) dataCenter?: string | null; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) master_hash?: string | null; /** diff --git a/backend/src/entities/connection/use-cases/create-connection.use.case.ts b/backend/src/entities/connection/use-cases/create-connection.use.case.ts index a35868be4..44a0e1445 100644 --- a/backend/src/entities/connection/use-cases/create-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/create-connection.use.case.ts @@ -39,7 +39,7 @@ export class CreateConnectionUseCase const { creation_info: { authorId, masterPwd }, } = createConnectionData; - const connectionAuthor: UserEntity = await this._dbContext.userRepository.findOneUserById(authorId); + const connectionAuthor: UserEntity | null = await this._dbContext.userRepository.findOneUserById(authorId); if (!connectionAuthor) { throw new InternalServerErrorException(Messages.USER_NOT_FOUND); @@ -93,7 +93,7 @@ export class CreateConnectionUseCase connectionCopy = Encryptor.decryptConnectionCredentials(connectionCopy, masterPwd); } - let token: string; + let token: string | null = null; if (isConnectionTypeAgent(savedConnection.type)) { token = await this._dbContext.agentRepository.createNewAgentForConnectionAndReturnToken(savedConnection); } @@ -117,8 +117,10 @@ export class CreateConnectionUseCase const connection = await this._dbContext.connectionRepository.findOne({ where: { id: savedConnection.id }, }); - connection.company = foundUserCompany; - await this._dbContext.connectionRepository.saveUpdatedConnection(connection); + if (connection) { + connection.company = foundUserCompany; + await this._dbContext.connectionRepository.saveUpdatedConnection(connection); + } } await slackPostMessage( Messages.USER_CREATED_CONNECTION(connectionAuthor.email, createConnectionData.connection_parameters.type), diff --git a/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts b/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts index 1e7f7be00..563fa13d5 100644 --- a/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts @@ -30,10 +30,16 @@ export class CreateGroupInConnectionUseCase creation_info: { cognitoUserName }, } = inputData; const connectionToUpdate = await this._dbContext.connectionRepository.findConnectionWithGroups(connectionId); + if (!connectionToUpdate) { + throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + } if (connectionToUpdate.groups.find((group) => group.title === title)) { throw new BadRequestException(Messages.GROUP_NAME_UNIQUE); } const foundUser = await this._dbContext.userRepository.findOneUserById(cognitoUserName); + if (!foundUser) { + throw new BadRequestException(Messages.USER_NOT_FOUND); + } const newGroupEntity = buildNewGroupEntityForConnectionWithUser(connectionToUpdate, foundUser, title); const savedGroup = await this._dbContext.groupRepository.saveNewOrUpdatedGroup(newGroupEntity); savedGroup.cedarPolicy = generateCedarPolicyForGroup(connectionId, false, { diff --git a/backend/src/entities/connection/use-cases/find-one-connection.use.case.ts b/backend/src/entities/connection/use-cases/find-one-connection.use.case.ts index c9f87b362..b75de8976 100644 --- a/backend/src/entities/connection/use-cases/find-one-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/find-one-connection.use.case.ts @@ -51,7 +51,7 @@ export class FindOneConnectionUseCase } if (connection.masterEncryption && inputData.masterPwd) { - const isMaterPwdValid = await Encryptor.verifyUserPassword(inputData.masterPwd, connection.master_hash); + const isMaterPwdValid = await Encryptor.verifyUserPassword(inputData.masterPwd, connection.master_hash ?? ''); if (!isMaterPwdValid) { throw new HttpException( { @@ -101,7 +101,7 @@ export class FindOneConnectionUseCase inputData.connectionId, accessLevel, ); - const connectionProperties: FoundConnectionPropertiesDs = connection.connection_properties + const connectionProperties: FoundConnectionPropertiesDs | null = connection.connection_properties ? buildFoundConnectionPropertiesDs(connection.connection_properties) : null; return { diff --git a/backend/src/entities/connection/use-cases/get-connection-diagram.use.case.ts b/backend/src/entities/connection/use-cases/get-connection-diagram.use.case.ts index 720f1515c..922256f58 100644 --- a/backend/src/entities/connection/use-cases/get-connection-diagram.use.case.ts +++ b/backend/src/entities/connection/use-cases/get-connection-diagram.use.case.ts @@ -43,7 +43,7 @@ export class GetConnectionDiagramUseCase const dao = getDataAccessObject(connection); const userEmail = isConnectionTypeAgent(connection.type) - ? await this._dbContext.userRepository.getUserEmailOrReturnNull(userId) + ? ((await this._dbContext.userRepository.getUserEmailOrReturnNull(userId)) ?? undefined) : undefined; await validateSchemaCache(dao, userEmail); @@ -77,9 +77,9 @@ export class GetConnectionDiagramUseCase userEmail: string | undefined, ): Promise { const [structure, primaryColumns, foreignKeys] = await Promise.all([ - this.safe>(() => dao.getTableStructure(tableName, userEmail), []), - this.safe>(() => dao.getTablePrimaryColumns(tableName, userEmail), []), - this.safe>(() => dao.getTableForeignKeys(tableName, userEmail), []), + this.safe>(() => dao.getTableStructure(tableName, userEmail ?? ''), []), + this.safe>(() => dao.getTablePrimaryColumns(tableName, userEmail ?? ''), []), + this.safe>(() => dao.getTableForeignKeys(tableName, userEmail ?? ''), []), ]); return { tableName, structure, primaryColumns, foreignKeys }; } diff --git a/backend/src/entities/connection/use-cases/get-permissions-for-group-in-connection.use.case.ts b/backend/src/entities/connection/use-cases/get-permissions-for-group-in-connection.use.case.ts index b5493a465..74f7cf552 100644 --- a/backend/src/entities/connection/use-cases/get-permissions-for-group-in-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/get-permissions-for-group-in-connection.use.case.ts @@ -1,9 +1,10 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; 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 { Messages } from '../../../exceptions/text/messages.js'; import { parseCedarPolicyToClassicalPermissions } from '../../cedar-authorization/cedar-policy-parser.js'; import { TablePermissionDs } from '../../permission/application/data-structures/create-permissions.ds.js'; import { FoundPermissionsInConnectionDs } from '../application/data-structures/found-permissions-in-connection.ds.js'; @@ -46,6 +47,9 @@ export class GetPermissionsForGroupInConnectionUseCase inputData.connectionId, inputData.masterPwd, ); + if (!connection) { + throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + } const dao = getDataAccessObject(connection); const tables: Array = (await dao.getTablesFromDB()).map((table) => table.tableName); diff --git a/backend/src/entities/connection/use-cases/get-user-permissions-for-group-in-connection.use.case.ts b/backend/src/entities/connection/use-cases/get-user-permissions-for-group-in-connection.use.case.ts index 41e8a2647..c905846ee 100644 --- a/backend/src/entities/connection/use-cases/get-user-permissions-for-group-in-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/get-user-permissions-for-group-in-connection.use.case.ts @@ -1,8 +1,9 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; 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 { Messages } from '../../../exceptions/text/messages.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { TablePermissionDs } from '../../permission/application/data-structures/create-permissions.ds.js'; import { FoundPermissionsInConnectionDs } from '../application/data-structures/found-permissions-in-connection.ds.js'; @@ -28,22 +29,17 @@ export class GetUserPermissionsForGroupInConnectionUseCase cognitoUserName, connectionId, ); - const userGroupAccessLevel = await this.cedarPermissions.getGroupAccessLevel( - cognitoUserName, - groupId, - ); + const userGroupAccessLevel = await this.cedarPermissions.getGroupAccessLevel(cognitoUserName, groupId); const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); + if (!connection) { + throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + } const dao = getDataAccessObject(connection); const tables: Array = (await dao.getTablesFromDB()).map((table) => table.tableName); const tablesWithAccessLevels: Array = await Promise.all( tables.map(async (table) => { - return await this.cedarPermissions.getUserTablePermissions( - cognitoUserName, - connectionId, - table, - masterPwd, - ); + return await this.cedarPermissions.getUserTablePermissions(cognitoUserName, connectionId, table, masterPwd); }), ); const allTableSettingsInConnection = await this._dbContext.tableSettingsRepository.findTableSettingsInConnection( diff --git a/backend/src/entities/connection/use-cases/preview-connection-diagram.use.case.ts b/backend/src/entities/connection/use-cases/preview-connection-diagram.use.case.ts index 84d8421f1..2a47e3cb1 100644 --- a/backend/src/entities/connection/use-cases/preview-connection-diagram.use.case.ts +++ b/backend/src/entities/connection/use-cases/preview-connection-diagram.use.case.ts @@ -44,7 +44,7 @@ export class PreviewConnectionDiagramUseCase const dao = getDataAccessObject(connection); const userEmail = isConnectionTypeAgent(connection.type) - ? await this._dbContext.userRepository.getUserEmailOrReturnNull(userId) + ? ((await this._dbContext.userRepository.getUserEmailOrReturnNull(userId)) ?? undefined) : undefined; await validateSchemaCache(dao, userEmail); @@ -86,9 +86,9 @@ export class PreviewConnectionDiagramUseCase userEmail: string | undefined, ): Promise { const [structure, primaryColumns, foreignKeys] = await Promise.all([ - this.safe>(() => dao.getTableStructure(tableName, userEmail), []), - this.safe>(() => dao.getTablePrimaryColumns(tableName, userEmail), []), - this.safe>(() => dao.getTableForeignKeys(tableName, userEmail), []), + this.safe>(() => dao.getTableStructure(tableName, userEmail ?? ''), []), + this.safe>(() => dao.getTablePrimaryColumns(tableName, userEmail ?? ''), []), + this.safe>(() => dao.getTableForeignKeys(tableName, userEmail ?? ''), []), ]); return { tableName, structure, primaryColumns, foreignKeys }; } diff --git a/backend/src/entities/connection/use-cases/restore-connection-use.case.ts b/backend/src/entities/connection/use-cases/restore-connection-use.case.ts index eea6d3251..cfa84cff8 100644 --- a/backend/src/entities/connection/use-cases/restore-connection-use.case.ts +++ b/backend/src/entities/connection/use-cases/restore-connection-use.case.ts @@ -61,7 +61,7 @@ export class RestoreConnectionUseCase ); } - const isTestConnection = isHostTest(connectionData.connection_parameters.host); + const isTestConnection = isHostTest(connectionData.connection_parameters.host ?? ''); const updatedConnection = await updateConnectionEntityForRestoration( foundConnection, connectionData, @@ -74,6 +74,14 @@ export class RestoreConnectionUseCase const foundConnectionAfterSave = await this._dbContext.connectionRepository.findOne({ where: { id: savedConnection.id }, }); + if (!foundConnectionAfterSave) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.BAD_REQUEST, + ); + } await decryptConnectionCredentialsAsync(foundConnectionAfterSave); const token = updatedConnection.agent?.token || null; return { diff --git a/backend/src/entities/connection/use-cases/test-connection.use.case.ts b/backend/src/entities/connection/use-cases/test-connection.use.case.ts index 6d5042f67..80f783664 100644 --- a/backend/src/entities/connection/use-cases/test-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/test-connection.use.case.ts @@ -102,7 +102,7 @@ export class TestConnectionUseCase !connectionData.password && (connectionData.host !== toUpdate.host || connectionData.port !== toUpdate.port) && !isConnectionTypeAgent(connectionData.type) && - !isRedisConnectionUrl(connectionData.host) + !isRedisConnectionUrl(connectionData.host ?? '') ) { return { result: false, @@ -135,7 +135,7 @@ export class TestConnectionUseCase if ( !connectionData.password && !isConnectionTypeAgent(connectionData.type) && - !isRedisConnectionUrl(connectionData.host) + !isRedisConnectionUrl(connectionData.host ?? '') ) { return { result: false, @@ -165,7 +165,7 @@ export class TestConnectionUseCase authorId: string, connectionType: ConnectionTypesEnum, ): Promise { - let testResult: TestConnectionResultDs; + let testResult: TestConnectionResultDs | undefined; try { testResult = await dao.testConnect(); return testResult; @@ -191,7 +191,7 @@ export class TestConnectionUseCase } finally { if (testResult?.result) { const foundUser = await this._dbContext.userRepository.findOneUserById(authorId); - await slackPostMessage(Messages.USER_SUCCESSFULLY_TESTED_CONNECTION(foundUser?.email, connectionType)); + await slackPostMessage(Messages.USER_SUCCESSFULLY_TESTED_CONNECTION(foundUser?.email ?? '', connectionType)); } } } diff --git a/backend/src/entities/connection/use-cases/unfreeze-connection.use.case.ts b/backend/src/entities/connection/use-cases/unfreeze-connection.use.case.ts index d7a26b9b3..ccacb4297 100644 --- a/backend/src/entities/connection/use-cases/unfreeze-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/unfreeze-connection.use.case.ts @@ -1,7 +1,8 @@ -import { Inject, Injectable, Scope } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable, Scope } from '@nestjs/common'; 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 { Messages } from '../../../exceptions/text/messages.js'; import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js'; import { UnfreezeConnectionDs } from '../application/data-structures/unfreeze-connection.ds.js'; import { IUnfreezeConnection } from './use-cases.interfaces.js'; @@ -23,6 +24,9 @@ export class UnfreezeConnectionUseCase const { connectionId } = inputData; const connection = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId } }); + if (!connection) { + throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + } // if (isSaaS()) { // const userCompany = await this._dbContext.companyInfoRepository.findCompanyInfoByUserId(userId); diff --git a/backend/src/entities/connection/use-cases/update-connection-master-password.use.case.ts b/backend/src/entities/connection/use-cases/update-connection-master-password.use.case.ts index 91d72fb50..bb85d38cc 100644 --- a/backend/src/entities/connection/use-cases/update-connection-master-password.use.case.ts +++ b/backend/src/entities/connection/use-cases/update-connection-master-password.use.case.ts @@ -1,7 +1,8 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable } from '@nestjs/common'; 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 { Messages } from '../../../exceptions/text/messages.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js'; import { UpdateMasterPasswordDs } from '../application/data-structures/update-master-password.ds.js'; @@ -22,6 +23,9 @@ export class UpdateConnectionMasterPasswordUseCase protected async implementation(inputData: UpdateMasterPasswordDs): Promise { const { connectionId, newMasterPwd, oldMasterPwd } = inputData; let connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, oldMasterPwd); + if (!connection) { + throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + } connection = Encryptor.encryptConnectionCredentials(connection, newMasterPwd); connection.master_hash = await Encryptor.hashUserPassword(newMasterPwd); const updatedConnection = await this._dbContext.connectionRepository.saveNewConnection(connection); diff --git a/backend/src/entities/connection/use-cases/update-connection-title.use.case.ts b/backend/src/entities/connection/use-cases/update-connection-title.use.case.ts index 3c31e14ae..7cb37f6ee 100644 --- a/backend/src/entities/connection/use-cases/update-connection-title.use.case.ts +++ b/backend/src/entities/connection/use-cases/update-connection-title.use.case.ts @@ -1,7 +1,8 @@ -import { Inject, Injectable, Scope } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable, Scope } from '@nestjs/common'; 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 { Messages } from '../../../exceptions/text/messages.js'; import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js'; import { UpdateConnectionTitleDs } from '../application/data-structures/update-connection-title.ds.js'; import { IUpdateConnectionTitle } from './use-cases.interfaces.js'; @@ -22,6 +23,9 @@ export class UpdateConnectionTitleUseCase const { connectionId, title } = inputData; const connection = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId } }); + if (!connection) { + throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + } connection.title = title; await this._dbContext.connectionRepository.save(connection); return { success: true }; diff --git a/backend/src/entities/connection/use-cases/update-connection.use.case.ts b/backend/src/entities/connection/use-cases/update-connection.use.case.ts index 967b90b93..fb0a73d7f 100644 --- a/backend/src/entities/connection/use-cases/update-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/update-connection.use.case.ts @@ -43,9 +43,17 @@ export class UpdateConnectionUseCase connectionId, masterPwd, ); + if (!foundConnectionToUpdate) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } const testConnectionsHosts = Constants.getTestConnectionsHostNamesArr(); - if (testConnectionsHosts.includes(foundConnectionToUpdate.host)) { + if (testConnectionsHosts.includes(foundConnectionToUpdate.host ?? '')) { throw new HttpException( { message: Messages.TEST_CONNECTIONS_UPDATE_NOT_ALLOWED, diff --git a/backend/src/entities/connection/use-cases/validate-connection-master-password.use.case.ts b/backend/src/entities/connection/use-cases/validate-connection-master-password.use.case.ts index 79d1bdb39..07781bf30 100644 --- a/backend/src/entities/connection/use-cases/validate-connection-master-password.use.case.ts +++ b/backend/src/entities/connection/use-cases/validate-connection-master-password.use.case.ts @@ -24,7 +24,7 @@ export class ValidateConnectionMasterPasswordUseCase validateConnectionMasterPasswordData: ValidateConnectionMasterPasswordDs, ): Promise { const { connectionId, masterPassword } = validateConnectionMasterPasswordData; - const connection: ConnectionEntity = await this._dbContext.connectionRepository.findOne({ + const connection: ConnectionEntity | null = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId }, }); diff --git a/backend/src/entities/connection/utils/build-connection-entity.ts b/backend/src/entities/connection/utils/build-connection-entity.ts index 5f53e8a1d..d6cc88d01 100644 --- a/backend/src/entities/connection/utils/build-connection-entity.ts +++ b/backend/src/entities/connection/utils/build-connection-entity.ts @@ -39,9 +39,9 @@ export async function buildConnectionEntity( connection.type = type; connection.ssh = ssh; connection.ssl = ssl; - connection.azure_encryption = azure_encryption; + connection.azure_encryption = azure_encryption ?? false; connection.author = connectionAuthor; - connection.masterEncryption = masterEncryption; + connection.masterEncryption = masterEncryption ?? false; connection.host = host; connection.port = port; connection.username = username; diff --git a/backend/src/entities/connection/utils/build-found-user-group-in-connection-dto.util.ts b/backend/src/entities/connection/utils/build-found-user-group-in-connection-dto.util.ts index b9fb49a22..8f2d6df5a 100644 --- a/backend/src/entities/connection/utils/build-found-user-group-in-connection-dto.util.ts +++ b/backend/src/entities/connection/utils/build-found-user-group-in-connection-dto.util.ts @@ -1,5 +1,6 @@ import { AccessLevelEnum } from '../../../enums/access-level.enum.js'; import { GroupEntity } from '../../group/group.entity.js'; +import { SimpleFoundUserInfoDs } from '../../user/dto/found-user.dto.js'; import { buildSimpleUserInfoDs } from '../../user/utils/build-created-user.ds.js'; import { FoundUserGroupsInConnectionDTO } from '../application/dto/found-user-groups-in-connection.dto.js'; @@ -13,7 +14,11 @@ export function buildFoundUserGroupInConnectionDto( title: group.title, isMain: group.isMain, cedarPolicy: group.cedarPolicy, - users: group.users?.length ? group.users.map((user) => buildSimpleUserInfoDs(user)) : undefined, + users: group.users?.length + ? group.users + .map((user) => buildSimpleUserInfoDs(user)) + .filter((user): user is SimpleFoundUserInfoDs => user !== null) + : undefined, }, accessLevel, }; diff --git a/backend/src/entities/connection/utils/decrypt-connection-credentials-async.ts b/backend/src/entities/connection/utils/decrypt-connection-credentials-async.ts index 70393b147..35c0a4e11 100644 --- a/backend/src/entities/connection/utils/decrypt-connection-credentials-async.ts +++ b/backend/src/entities/connection/utils/decrypt-connection-credentials-async.ts @@ -25,7 +25,7 @@ export async function decryptConnectionCredentialsAsync(connection: ConnectionEn connection.authSource = foundTestConnectionByType.authSource; connection.sid = foundTestConnectionByType.sid; connection.schema = foundTestConnectionByType.schema; - connection.azure_encryption = foundTestConnectionByType.azure_encryption; + connection.azure_encryption = foundTestConnectionByType.azure_encryption ?? false; } } else if (!isConnectionTypeAgent(connection.type)) { connection.host = await Encryptor.decryptDataAsync(connection.host); diff --git a/backend/src/entities/connection/utils/is-host-allowed.ts b/backend/src/entities/connection/utils/is-host-allowed.ts index 607880128..1bc77414c 100644 --- a/backend/src/entities/connection/utils/is-host-allowed.ts +++ b/backend/src/entities/connection/utils/is-host-allowed.ts @@ -26,11 +26,11 @@ export async function isHostAllowed(connectionData: HostCheckData): Promise((resolve, reject) => { const testHosts = Constants.getTestConnectionsHostNamesArr(); if (!connectionData.ssh) { - dns.lookup(connectionData.host, (err, address) => { + dns.lookup(connectionData.host ?? '', (err, address) => { if (err) { return reject(err); } - if (ipRangeCheck(address, Constants.FORBIDDEN_HOSTS) && !testHosts.includes(connectionData.host)) { + if (ipRangeCheck(address, Constants.FORBIDDEN_HOSTS) && !testHosts.includes(connectionData.host ?? '')) { resolve(false); } else { resolve(true); @@ -41,7 +41,7 @@ export async function isHostAllowed(connectionData: HostCheckData): Promise = new Set([ ConnectionTypesEnum.agent_clickhouse, ]); -export function isSqlConnectionType(type: ConnectionTypesEnum | string): boolean { - return SQL_CONNECTION_TYPES.has(type); +export function isSqlConnectionType(type: ConnectionTypesEnum | string | null | undefined): boolean { + return type ? SQL_CONNECTION_TYPES.has(type) : false; } diff --git a/backend/src/entities/connection/utils/is-test-connection-util.ts b/backend/src/entities/connection/utils/is-test-connection-util.ts index 77a7098bd..f512cb697 100644 --- a/backend/src/entities/connection/utils/is-test-connection-util.ts +++ b/backend/src/entities/connection/utils/is-test-connection-util.ts @@ -6,7 +6,7 @@ export function isTestConnectionUtil(connection: ConnectionEntity | CreatedConne if (connection.isTestConnection) { return true; } - return isHostTest(connection.host); + return isHostTest(connection.host ?? ''); } export function isHostTest(hostname: string): boolean { diff --git a/backend/src/entities/connection/utils/process-aws-connection.util.ts b/backend/src/entities/connection/utils/process-aws-connection.util.ts index 671ef3afc..cc215dd50 100644 --- a/backend/src/entities/connection/utils/process-aws-connection.util.ts +++ b/backend/src/entities/connection/utils/process-aws-connection.util.ts @@ -7,6 +7,9 @@ export async function processAWSConnection(createConnectionData: CreateConnectio return createConnectionData; } const { host } = createConnectionData.connection_parameters; + if (!host) { + return createConnectionData; + } if (host.endsWith('.rds.amazonaws.com')) { createConnectionData.connection_parameters.ssl = true; diff --git a/backend/src/entities/connection/utils/update-connection-entity-for-restoration.ts b/backend/src/entities/connection/utils/update-connection-entity-for-restoration.ts index c6640ba64..e6b94c65f 100644 --- a/backend/src/entities/connection/utils/update-connection-entity-for-restoration.ts +++ b/backend/src/entities/connection/utils/update-connection-entity-for-restoration.ts @@ -18,7 +18,7 @@ export async function updateConnectionEntityForRestoration( toUpdate.ssl = connection_parameters.ssl; toUpdate.isTestConnection = isTestConnection; if (!isConnectionTypeAgent(connection_parameters.type)) { - toUpdate.masterEncryption = connection_parameters.masterEncryption; + toUpdate.masterEncryption = connection_parameters.masterEncryption ?? false; toUpdate.host = connection_parameters.host; toUpdate.port = connection_parameters.port; toUpdate.username = @@ -49,7 +49,7 @@ export async function updateConnectionEntityForRestoration( : connection_parameters.sshUsername; toUpdate.cert = connection_parameters.cert; toUpdate.schema = connection_parameters.schema; - toUpdate.azure_encryption = connection_parameters.azure_encryption; + toUpdate.azure_encryption = connection_parameters.azure_encryption ?? false; toUpdate.authSource = connection_parameters.masterEncryption && masterPwd ? Encryptor.encryptDataMasterPwd(connection_parameters.authSource, masterPwd) diff --git a/backend/src/entities/connection/utils/validate-create-connection-data.ts b/backend/src/entities/connection/utils/validate-create-connection-data.ts index bb4fc3944..ce0c56fd8 100644 --- a/backend/src/entities/connection/utils/validate-create-connection-data.ts +++ b/backend/src/entities/connection/utils/validate-create-connection-data.ts @@ -52,7 +52,7 @@ export async function validateCreateConnectionData( } } - if (port < 0 || port > 65535 || !port) errors.push(Messages.PORT_MISSING); + if (!port || port < 0 || port > 65535) errors.push(Messages.PORT_MISSING); if (typeof port !== 'number') errors.push(Messages.PORT_FORMAT_INCORRECT); if (typeof ssh !== 'boolean') errors.push(Messages.SSH_FORMAT_INCORRECT); if (ssh) { diff --git a/backend/src/entities/custom-field/use-cases/create-custom-fields.use.case.ts b/backend/src/entities/custom-field/use-cases/create-custom-fields.use.case.ts index c84fdb1e3..9444e7fe5 100644 --- a/backend/src/entities/custom-field/use-cases/create-custom-fields.use.case.ts +++ b/backend/src/entities/custom-field/use-cases/create-custom-fields.use.case.ts @@ -1,7 +1,8 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, NotFoundException } from '@nestjs/common'; 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 { Messages } from '../../../exceptions/text/messages.js'; import { CreateTableSettingsDs } from '../../table-settings/application/data-structures/create-table-settings.ds.js'; import { FoundTableSettingsDs } from '../../table-settings/application/data-structures/found-table-settings.ds.js'; import { TableSettingsEntity } from '../../table-settings/common-table-settings/table-settings.entity.js'; @@ -30,6 +31,9 @@ export class CreateCustomFieldsUseCase connectionId, masterPwd, ); + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } await validateCreateCustomFieldDto(createFieldDto, foundConnection, tableName); const foundTableSettingToUpdate: TableSettingsEntity = await this._dbContext.tableSettingsRepository.findTableSettingsWithCustomFields(connectionId, tableName); diff --git a/backend/src/entities/custom-field/use-cases/update-custom-field.use.case.ts b/backend/src/entities/custom-field/use-cases/update-custom-field.use.case.ts index eb735ef58..769255421 100644 --- a/backend/src/entities/custom-field/use-cases/update-custom-field.use.case.ts +++ b/backend/src/entities/custom-field/use-cases/update-custom-field.use.case.ts @@ -29,6 +29,14 @@ export class UpdateCustomFieldUseCase connectionId, masterPwd, ); + if (!foundConnection) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } await validateCreateCustomFieldDto(updateFieldDto, foundConnection, tableName); const fieldToUpdate = await this._dbContext.customFieldsRepository.findCustomFieldById(updateFieldDto.id); if (!fieldToUpdate) { diff --git a/backend/src/entities/custom-field/utils/validate-create-custom-field-dto.ts b/backend/src/entities/custom-field/utils/validate-create-custom-field-dto.ts index 966216140..a5a0d9d91 100644 --- a/backend/src/entities/custom-field/utils/validate-create-custom-field-dto.ts +++ b/backend/src/entities/custom-field/utils/validate-create-custom-field-dto.ts @@ -30,7 +30,7 @@ export async function validateCreateCustomFieldDto( HttpStatus.BAD_REQUEST, ); } - const tableStructure = await dao.getTableStructure(tableName, undefined); + const tableStructure = await dao.getTableStructure(tableName, ''); const tableColumns = tableStructure.map((el) => el.column_name); for (const field of tableFieldsFromTemplate) { const fieldIndex = tableColumns.indexOf(field); diff --git a/backend/src/entities/demo-data/demo-data.service.ts b/backend/src/entities/demo-data/demo-data.service.ts index e731deeb5..2fafc29dd 100644 --- a/backend/src/entities/demo-data/demo-data.service.ts +++ b/backend/src/entities/demo-data/demo-data.service.ts @@ -6,6 +6,7 @@ import { AccessLevelEnum } from '../../enums/access-level.enum.js'; import { FilterCriteriaEnum } from '../../enums/filter-criteria.enum.js'; import { TableActionEventEnum } from '../../enums/table-action-event-enum.js'; import { TableActionTypeEnum } from '../../enums/table-action-type.enum.js'; +import { WidgetTypeEnum } from '../../enums/widget-type.enum.js'; import { isTest } from '../../helpers/app/is-test.js'; import { Constants } from '../../helpers/constants/constants.js'; import { getErrorMessage } from '../../helpers/get-error-message.js'; @@ -45,6 +46,7 @@ export class DemoDataService { } catch (error) { console.error(`Error during demo data creation for user with ID ${userId}:`, error); await slackPostMessage(`Error during demo data creation for user with ID ${userId}: ${getErrorMessage(error)}`); + return []; } } @@ -293,7 +295,7 @@ export class DemoDataService { const createEventWidgetsData: Array = [ { field_name: 'organizer_id', - widget_type: '' as any, + widget_type: undefined, name: 'Organizer', description: '', widget_params: null, @@ -301,7 +303,7 @@ export class DemoDataService { }, { field_name: 'space_id', - widget_type: '' as any, + widget_type: undefined, name: 'Space', description: '', widget_params: null, @@ -309,7 +311,7 @@ export class DemoDataService { }, { field_name: 'start_time', - widget_type: '' as any, + widget_type: undefined, name: 'Starts at', description: '', widget_params: null, @@ -317,7 +319,7 @@ export class DemoDataService { }, { field_name: 'end_time', - widget_type: '' as any, + widget_type: undefined, name: 'Ends at', description: '', widget_params: null, @@ -325,7 +327,7 @@ export class DemoDataService { }, { field_name: 'title', - widget_type: '' as any, + widget_type: undefined, name: 'Event name', description: '', widget_params: null, @@ -333,7 +335,7 @@ export class DemoDataService { }, { field_name: 'image_url', - widget_type: 'Image' as any, + widget_type: WidgetTypeEnum.Image, name: 'Poster', description: '', widget_params: @@ -353,7 +355,7 @@ export class DemoDataService { const createEventAttendeeWidgetsData: Array = [ { field_name: 'event_id', - widget_type: '' as any, + widget_type: undefined, name: 'Event', description: '', widget_params: null, @@ -361,7 +363,7 @@ export class DemoDataService { }, { field_name: 'user_id', - widget_type: '' as any, + widget_type: undefined, name: 'User', description: '', widget_params: null, @@ -369,7 +371,7 @@ export class DemoDataService { }, { field_name: 'status', - widget_type: 'Select' as any, + widget_type: WidgetTypeEnum.Select, name: '', description: '', widget_params: @@ -390,7 +392,7 @@ export class DemoDataService { const createLocationWidgetsData: Array = [ { field_name: 'country', - widget_type: 'Country' as any, + widget_type: WidgetTypeEnum.Country, name: '', description: '', widget_params: @@ -411,7 +413,7 @@ export class DemoDataService { const createMembershipWidgetsData: Array = [ { field_name: 'user_id', - widget_type: '' as any, + widget_type: undefined, name: 'User', description: '', widget_params: '', @@ -419,7 +421,7 @@ export class DemoDataService { }, { field_name: 'space_id', - widget_type: '' as any, + widget_type: undefined, name: 'Space', description: '', widget_params: '', @@ -439,7 +441,7 @@ export class DemoDataService { const createSpaceWidgetsData: Array = [ { field_name: 'type', - widget_type: 'Select' as any, + widget_type: WidgetTypeEnum.Select, name: '', description: '', widget_params: @@ -448,7 +450,7 @@ export class DemoDataService { }, { field_name: 'location_id', - widget_type: '' as any, + widget_type: undefined, name: 'Location', description: '', widget_params: null, @@ -456,7 +458,7 @@ export class DemoDataService { }, { field_name: 'price_per_hour', - widget_type: 'Money' as any, + widget_type: WidgetTypeEnum.Money, name: '', description: '', widget_params: @@ -476,7 +478,7 @@ export class DemoDataService { const createUserWidgetsData: Array = [ { field_name: 'role', - widget_type: 'Select' as any, + widget_type: WidgetTypeEnum.Select, name: '', description: '', widget_params: @@ -485,7 +487,7 @@ export class DemoDataService { }, { field_name: 'phone', - widget_type: 'Phone' as any, + widget_type: WidgetTypeEnum.Phone, name: '', description: '', widget_params: @@ -923,7 +925,7 @@ export class DemoDataService { const createCertificatesWidgetsData: Array = [ { field_name: 'user_id', - widget_type: '' as any, + widget_type: undefined, name: 'User', description: '', widget_params: null, @@ -931,7 +933,7 @@ export class DemoDataService { }, { field_name: 'certificate_url', - widget_type: 'URL' as any, + widget_type: WidgetTypeEnum.URL, name: '', description: '', widget_params: @@ -940,7 +942,7 @@ export class DemoDataService { }, { field_name: 'course_id', - widget_type: '' as any, + widget_type: undefined, name: 'Course', description: "hi i'm a description", widget_params: null, @@ -959,7 +961,7 @@ export class DemoDataService { const createCourseMentorsWidgetsData: Array = [ { field_name: 'course_id', - widget_type: '' as any, + widget_type: undefined, name: 'Course', description: '', widget_params: '', @@ -967,7 +969,7 @@ export class DemoDataService { }, { field_name: 'user_id', - widget_type: '' as any, + widget_type: undefined, name: 'User', description: '', widget_params: null, @@ -987,7 +989,7 @@ export class DemoDataService { const createCourseModulesWidgetsData: Array = [ { field_name: 'course_id', - widget_type: '' as any, + widget_type: undefined, name: 'Course', description: '', widget_params: '', @@ -1007,7 +1009,7 @@ export class DemoDataService { const createCoursesWidgetsData: Array = [ { field_name: 'price', - widget_type: 'Money' as any, + widget_type: WidgetTypeEnum.Money, name: '', description: '', widget_params: @@ -1028,7 +1030,7 @@ export class DemoDataService { const createEnrollmentsWidgetsData: Array = [ { field_name: 'user_id', - widget_type: '' as any, + widget_type: undefined, name: 'User', description: '', widget_params: null, @@ -1036,7 +1038,7 @@ export class DemoDataService { }, { field_name: 'course_id', - widget_type: '' as any, + widget_type: undefined, name: 'Course', description: '', widget_params: null, @@ -1044,7 +1046,7 @@ export class DemoDataService { }, { field_name: 'progress', - widget_type: 'Range' as any, + widget_type: WidgetTypeEnum.Range, name: '', description: '', widget_params: @@ -1065,7 +1067,7 @@ export class DemoDataService { const createLessonsWidgetsData: Array = [ { field_name: 'video_url', - widget_type: 'URL' as any, + widget_type: WidgetTypeEnum.URL, name: '', description: '', widget_params: @@ -1074,7 +1076,7 @@ export class DemoDataService { }, { field_name: 'module_id', - widget_type: '' as any, + widget_type: undefined, name: 'Module', description: '', widget_params: null, @@ -1082,7 +1084,7 @@ export class DemoDataService { }, { field_name: 'content_url', - widget_type: 'URL' as any, + widget_type: WidgetTypeEnum.URL, name: '', description: '', widget_params: @@ -1103,7 +1105,7 @@ export class DemoDataService { const createQuizAttemptsWidgetsData: Array = [ { field_name: 'quiz_id', - widget_type: '' as any, + widget_type: undefined, name: 'Quiz', description: '', widget_params: '', @@ -1111,7 +1113,7 @@ export class DemoDataService { }, { field_name: 'user_id', - widget_type: '' as any, + widget_type: undefined, name: 'User', description: '', widget_params: null, @@ -1131,7 +1133,7 @@ export class DemoDataService { const createQuizzesWidgetsData: Array = [ { field_name: 'lesson_id', - widget_type: '' as any, + widget_type: undefined, name: 'Lesson', description: '', widget_params: '', @@ -1151,7 +1153,7 @@ export class DemoDataService { const createUsersWidgetsData: Array = [ { field_name: 'role', - widget_type: 'Select' as any, + widget_type: WidgetTypeEnum.Select, name: '', description: '', widget_params: @@ -1160,7 +1162,7 @@ export class DemoDataService { }, { field_name: 'password_hash', - widget_type: 'Password' as any, + widget_type: WidgetTypeEnum.Password, name: 'Password', description: '', widget_params: diff --git a/backend/src/entities/email/email-config/email-config.service.ts b/backend/src/entities/email/email-config/email-config.service.ts index cd3440421..5bfbf2131 100644 --- a/backend/src/entities/email/email-config/email-config.service.ts +++ b/backend/src/entities/email/email-config/email-config.service.ts @@ -10,12 +10,12 @@ export class EmailConfigService implements IEmailConfigService { return configString; } return { - host: host, + host: host ?? '', port: port, secure: nonSecure, auth: { - user: username, - pass: password, + user: username ?? '', + pass: password ?? '', }, socketTimeout: 4 * 1000, connectionTimeout: 4 * 1000, diff --git a/backend/src/entities/email/email/email.generator.ts b/backend/src/entities/email/email/email.generator.ts index a3c96c7eb..361c0fdd2 100644 --- a/backend/src/entities/email/email/email.generator.ts +++ b/backend/src/entities/email/email/email.generator.ts @@ -2,7 +2,8 @@ import { AbstractEmailLetter } from './abstract-email-letter.js'; import { IEmailGenerator, IMessage } from './email.interface.js'; export class EmailGenerator implements IEmailGenerator { - generateEmail(email: AbstractEmailLetter): IMessage { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + generateEmail(email: AbstractEmailLetter): IMessage { return email.getEmail(); } } diff --git a/backend/src/entities/email/email/email.interface.ts b/backend/src/entities/email/email/email.interface.ts index c2b17adf0..92c1322b3 100644 --- a/backend/src/entities/email/email/email.interface.ts +++ b/backend/src/entities/email/email/email.interface.ts @@ -9,5 +9,6 @@ export interface IMessage { } export interface IEmailGenerator { - generateEmail(email: AbstractEmailLetter): IMessage; + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + generateEmail(email: AbstractEmailLetter): IMessage; } diff --git a/backend/src/entities/email/email/email.service.ts b/backend/src/entities/email/email/email.service.ts index c5e13d4b1..afd86794f 100644 --- a/backend/src/entities/email/email/email.service.ts +++ b/backend/src/entities/email/email/email.service.ts @@ -36,11 +36,12 @@ export class EmailService { ) {} public async sendEmailToUser(letterContent: IMessage): Promise { - if (isTest()) return; + if (isTest()) return null; const mailResult = await this.sendEmailWithTimeout(letterContent); if (mailResult) { return mailResult; } + return null; } public async sendEmailActionToUser( @@ -84,7 +85,7 @@ export class EmailService { public async sendRemindersToUsers(userEmails: Array): Promise> { const queue = new PQueue({ concurrency: 3 }); - const mailingResults: Array = []; + const mailingResults: Array = []; for (const email of userEmails) { try { @@ -107,11 +108,11 @@ export class EmailService { public async send2faEnabledInCompany( userEmails: Array, companyName: string, - ): Promise> { + ): Promise> { try { const queue = new PQueue({ concurrency: 3 }); - const mailingResults: Array = await Promise.all( + const mailingResults: Array = await Promise.all( userEmails.map(async (email: string) => { return await queue.add(async () => { return await this.send2faEnabledInCompanyToUser(email, companyName); @@ -121,10 +122,11 @@ export class EmailService { return mailingResults; } catch (error) { this.logger.error(error); + return []; } } - public async sendInvitedInNewGroup(email: string, groupTitle: string): Promise { + public async sendInvitedInNewGroup(email: string, groupTitle: string): Promise { const currentYear = new Date().getFullYear(); const letterContent: IMessage = { from: this.emailFrom, @@ -168,7 +170,7 @@ export class EmailService { email: string, verificationString: string, customCompanyDomain: string | null, - ): Promise { + ): Promise { const domain = customCompanyDomain ? customCompanyDomain : Constants.APP_DOMAIN_ADDRESS; const link = `${domain}/external/user/email/verify/${verificationString}`; const currentYear = new Date().getFullYear(); @@ -182,7 +184,7 @@ export class EmailService { return await this.sendEmailToUser(letterContent); } - public async sendEmailChanged(email: string): Promise { + public async sendEmailChanged(email: string): Promise { const currentYear = new Date().getFullYear(); const letterContent: IMessage = { from: this.emailFrom, @@ -198,7 +200,7 @@ export class EmailService { email: string, requestString: string, customCompanyDomain: string | null, - ): Promise { + ): Promise { const currentYear = new Date().getFullYear(); const domain = customCompanyDomain ? customCompanyDomain : Constants.APP_DOMAIN_ADDRESS; const linkToConfirm = `${domain}/external/user/email/change/verify/${requestString}`; @@ -216,7 +218,7 @@ export class EmailService { email: string, requestString: string, customCompanyDomain: string | null, - ): Promise { + ): Promise { const currentYear = new Date().getFullYear(); const domain = customCompanyDomain ? customCompanyDomain : Constants.APP_DOMAIN_ADDRESS; const linkToConfirm = `${domain}/external/user/password/reset/verify/${requestString}`; @@ -257,7 +259,7 @@ export class EmailService { private async send2faEnabledInCompanyToUser( email: string, companyName: string, - ): Promise { + ): Promise { const letterContent: IMessage = { from: this.emailFrom, to: email, @@ -271,7 +273,7 @@ export class EmailService { } private async sendEmailWithTimeout(letterContent: IMessage): Promise { - return new Promise(async (resolve) => { + return new Promise(async (resolve) => { setTimeout(() => { resolve(null); }, 4000); @@ -287,7 +289,7 @@ export class EmailService { } private buildMailingResults( - results: Array, + results: Array, ): Array { return results.map((result) => { if (!result) { diff --git a/backend/src/entities/group/group.entity.ts b/backend/src/entities/group/group.entity.ts index 658c52b36..d868a4c02 100644 --- a/backend/src/entities/group/group.entity.ts +++ b/backend/src/entities/group/group.entity.ts @@ -34,5 +34,5 @@ export class GroupEntity { }, ) @JoinColumn() - connection: Relation; + connection?: Relation; } diff --git a/backend/src/entities/group/use-cases/remove-user-from-group.use.case.ts b/backend/src/entities/group/use-cases/remove-user-from-group.use.case.ts index c9fb1f094..e708c95bd 100644 --- a/backend/src/entities/group/use-cases/remove-user-from-group.use.case.ts +++ b/backend/src/entities/group/use-cases/remove-user-from-group.use.case.ts @@ -33,6 +33,14 @@ export class RemoveUserFromGroupUseCase ); } const groupToUpdate = await this._dbContext.groupRepository.findGroupByIdWithConnectionAndUsers(groupId); + if (!groupToUpdate || !groupToUpdate.users) { + throw new HttpException( + { + message: Messages.GROUP_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } if (groupToUpdate.isMain && groupToUpdate.users.length <= 1) { throw new HttpException( { diff --git a/backend/src/entities/group/use-cases/saas-add-user-in-group-v2.use.case.ts b/backend/src/entities/group/use-cases/saas-add-user-in-group-v2.use.case.ts index c8d9e00d7..6fdb04660 100644 --- a/backend/src/entities/group/use-cases/saas-add-user-in-group-v2.use.case.ts +++ b/backend/src/entities/group/use-cases/saas-add-user-in-group-v2.use.case.ts @@ -26,6 +26,14 @@ export class AddUserInGroupUseCase const { groupId } = inputData; const email = inputData.email.toLowerCase(); const foundGroup = await this._dbContext.groupRepository.findGroupByIdWithConnectionAndUsers(groupId); + if (!foundGroup || !foundGroup.users) { + throw new HttpException( + { + message: Messages.GROUP_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } const foundConnection = await this._dbContext.connectionRepository.getConnectionByGroupIdWithCompanyAndUsersInCompany(groupId); diff --git a/backend/src/entities/group/use-cases/update-group-title.use.case.ts b/backend/src/entities/group/use-cases/update-group-title.use.case.ts index 004d4f439..10925f558 100644 --- a/backend/src/entities/group/use-cases/update-group-title.use.case.ts +++ b/backend/src/entities/group/use-cases/update-group-title.use.case.ts @@ -22,7 +22,7 @@ export class UpdateGroupTitleUseCase protected async implementation(groupData: UpdateGroupTitleDto): Promise { const { groupId, title } = groupData; const groupToUpdate = await this._dbContext.groupRepository.findGroupByIdWithConnectionAndUsers(groupId); - if (!groupToUpdate) { + if (!groupToUpdate || !groupToUpdate.connection) { throw new HttpException( { message: Messages.GROUP_NOT_FOUND, @@ -33,6 +33,14 @@ export class UpdateGroupTitleUseCase const connectionWithGroups = await this._dbContext.connectionRepository.findConnectionWithGroups( groupToUpdate.connection.id, ); + if (!connectionWithGroups) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } if (connectionWithGroups.groups.find((group) => group.title === title)) { throw new BadRequestException(Messages.GROUP_NAME_UNIQUE); diff --git a/backend/src/entities/group/utils/biuld-found-group-response.dto.ts b/backend/src/entities/group/utils/biuld-found-group-response.dto.ts index 63804fef3..5bf75f6ee 100644 --- a/backend/src/entities/group/utils/biuld-found-group-response.dto.ts +++ b/backend/src/entities/group/utils/biuld-found-group-response.dto.ts @@ -7,6 +7,6 @@ export function buildFoundGroupResponseDto(group: GroupEntity): FoundGroupRespon id: group.id, title: group.title, isMain: group.isMain, - users: group.users?.map((user) => buildSimpleUserInfoDs(user)), + users: group.users?.map((user) => buildSimpleUserInfoDs(user)).filter((user) => !!user), }; } diff --git a/backend/src/entities/group/utils/build-remove-user-from-group-result.ds.ts b/backend/src/entities/group/utils/build-remove-user-from-group-result.ds.ts index 104fb76a2..9dbeb980c 100644 --- a/backend/src/entities/group/utils/build-remove-user-from-group-result.ds.ts +++ b/backend/src/entities/group/utils/build-remove-user-from-group-result.ds.ts @@ -7,7 +7,7 @@ export function buildRemoveUserFromGroupResultDs(group: GroupEntity): RemoveUser id: group.id, isMain: group.isMain, title: group.title, - users: group.users.map((u: UserEntity) => { + users: (group.users ?? []).map((u: UserEntity) => { return { id: u.id, email: u.email, diff --git a/backend/src/entities/s3-widget/use-cases/get-s3-file-url.use.case.ts b/backend/src/entities/s3-widget/use-cases/get-s3-file-url.use.case.ts index ac686ea10..37eab0ea0 100644 --- a/backend/src/entities/s3-widget/use-cases/get-s3-file-url.use.case.ts +++ b/backend/src/entities/s3-widget/use-cases/get-s3-file-url.use.case.ts @@ -9,7 +9,8 @@ import { BaseType } from '../../../common/data-injection.tokens.js'; import { WidgetTypeEnum } from '../../../enums/widget-type.enum.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; -import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; +import { buildCommonTableSettingsInput } from '../../table/utils/build-common-table-settings-input.util.js'; +import { getUserEmailForAgent } from '../../table/utils/validate-connection.util.js'; import { BucketFileUrlResponseDs, GetBucketFileUrlDs } from '../application/data-structures/s3-operation.ds.js'; import { BucketWidgetParams } from '../application/data-structures/s3-widget-params.ds.js'; import { S3HelperService } from '../s3-helper.service.js'; @@ -48,15 +49,15 @@ export class GetS3FileUrlUseCase throw new HttpException({ message: 'S3 widget not configured for this field' }, HttpStatus.BAD_REQUEST); } + if (!widget.widget_params) { + throw new HttpException({ message: 'S3 widget params are not configured' }, HttpStatus.BAD_REQUEST); + } const params: BucketWidgetParams = typeof widget.widget_params === 'string' ? JSON5.parse(widget.widget_params) : widget.widget_params; // Fetch the row from database to get the actual file key const dao = getDataAccessObject(connection); - let userEmail: string; - if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); - } + const userEmail = await getUserEmailForAgent(connection, userId, this._dbContext.userRepository); const tableSettings = await this._dbContext.tableSettingsRepository.findTableSettingsPure(connectionId, tableName); const personalTableSettings = await this._dbContext.personalTableSettingsRepository.findUserTableSettings( @@ -67,7 +68,7 @@ export class GetS3FileUrlUseCase const rowData = await dao.getRowByPrimaryKey( tableName, rowPrimaryKey, - buildDAOsTableSettingsDs(tableSettings, personalTableSettings), + buildDAOsTableSettingsDs(buildCommonTableSettingsInput(tableSettings), personalTableSettings), userEmail, ); diff --git a/backend/src/entities/s3-widget/use-cases/get-s3-upload-url.use.case.ts b/backend/src/entities/s3-widget/use-cases/get-s3-upload-url.use.case.ts index cf656ad1e..e7ca40614 100644 --- a/backend/src/entities/s3-widget/use-cases/get-s3-upload-url.use.case.ts +++ b/backend/src/entities/s3-widget/use-cases/get-s3-upload-url.use.case.ts @@ -40,6 +40,9 @@ export class GetS3UploadUrlUseCase throw new HttpException({ message: 'S3 widget not configured for this field' }, HttpStatus.BAD_REQUEST); } + if (!widget.widget_params) { + throw new HttpException({ message: 'S3 widget params are not configured' }, HttpStatus.BAD_REQUEST); + } const params: BucketWidgetParams = typeof widget.widget_params === 'string' ? JSON5.parse(widget.widget_params) : widget.widget_params; diff --git a/backend/src/entities/shared-jobs/shared-jobs.service.ts b/backend/src/entities/shared-jobs/shared-jobs.service.ts index e02a5359b..76c61170f 100644 --- a/backend/src/entities/shared-jobs/shared-jobs.service.ts +++ b/backend/src/entities/shared-jobs/shared-jobs.service.ts @@ -75,9 +75,9 @@ export class SharedJobsService { tablesToScan.map((table) => queue.add(async () => { try { - const structure = await dao.getTableStructure(table.tableName, null); - const primaryColumns = await dao.getTablePrimaryColumns(table.tableName, null); - const foreignKeys = await dao.getTableForeignKeys(table.tableName, null); + const structure = await dao.getTableStructure(table.tableName, ''); + const primaryColumns = await dao.getTablePrimaryColumns(table.tableName, ''); + const foreignKeys = await dao.getTableForeignKeys(table.tableName, ''); message(`Inspected structure of table "${table.tableName}"`); return { table_name: table.tableName, @@ -133,7 +133,7 @@ export class SharedJobsService { normalizedSettings.map((setting) => validationQueue.add(async () => { const validateSettingsDS = buildValidateTableSettingsDS(setting); - const errors = await dao.validateSettings(validateSettingsDS, setting.table_name, undefined); + const errors = await dao.validateSettings(validateSettingsDS, setting.table_name, ''); if (errors.length > 0) { message(`Validation failed for table "${setting.table_name}", skipping`); console.error(`Validation errors for table "${setting.table_name}":`, errors); @@ -158,8 +158,8 @@ export class SharedJobsService { widgetEntity.widget_type = widget.widget_type; widgetEntity.widget_params = widget.widget_params || null; widgetEntity.widget_options = widget.widget_options || null; - widgetEntity.name = widget.name || null; - widgetEntity.description = widget.description || null; + widgetEntity.name = widget.name || undefined; + widgetEntity.description = widget.description || undefined; widgetEntity.settings = savedSetting; widgetsToSave.push(widgetEntity); message( @@ -212,9 +212,10 @@ export class SharedJobsService { connection: ConnectionEntity, ): Array { aiSettings.forEach((setting) => { - delete setting.id; + const mutableSetting = setting as Partial; + delete mutableSetting.id; setting.connection_id = connection; - delete setting.table_widgets; + delete mutableSetting.table_widgets; }); return aiSettings; } @@ -224,7 +225,17 @@ export class SharedJobsService { connection: ConnectionEntity, dao: IDataAccessObject | IDataAccessObjectAgent, ): Promise { - const { data } = await dao.getRowsFromTable(tableName, {} as any, 1, 10, null, null, null, null, null); + const { data } = await dao.getRowsFromTable( + tableName, + {} as any, + 1, + 10, + '', + [], + { fields: [], value: '' }, + null, + '', + ); if (data && data.length > 0) { const columnNames: Set = new Set(); data.forEach((row) => { diff --git a/backend/src/entities/table-actions/table-action-events-module/action-event.entity.ts b/backend/src/entities/table-actions/table-action-events-module/action-event.entity.ts index 3e5d5a135..16a3b23db 100644 --- a/backend/src/entities/table-actions/table-action-events-module/action-event.entity.ts +++ b/backend/src/entities/table-actions/table-action-events-module/action-event.entity.ts @@ -23,11 +23,11 @@ export class ActionEventsEntity { }) type!: TableActionTypeEnum; - @Column({ default: null }) - title: string; + @Column({ type: 'varchar', default: null }) + title: string | null; - @Column({ default: null }) - icon: string; + @Column({ type: 'varchar', default: null }) + icon: string | null; @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) created_at: Date; diff --git a/backend/src/entities/table-actions/table-action-events-module/utils/build-action-event-with-rule.util.ts b/backend/src/entities/table-actions/table-action-events-module/utils/build-action-event-with-rule.util.ts index aa2d35607..1c5c15449 100644 --- a/backend/src/entities/table-actions/table-action-events-module/utils/build-action-event-with-rule.util.ts +++ b/backend/src/entities/table-actions/table-action-events-module/utils/build-action-event-with-rule.util.ts @@ -11,7 +11,7 @@ export function buildActionEventWithRule( newActionEvent.action_rule = actionRule; newActionEvent.event = event; newActionEvent.title = event_title; - newActionEvent.icon = icon; + newActionEvent.icon = icon ?? null; newActionEvent.type = type; newActionEvent.require_confirmation = require_confirmation; return newActionEvent; diff --git a/backend/src/entities/table-actions/table-action-rules-module/application/data-structures/create-action-rules.ds.ts b/backend/src/entities/table-actions/table-action-rules-module/application/data-structures/create-action-rules.ds.ts index 1aad70922..5888ebf1f 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/application/data-structures/create-action-rules.ds.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/application/data-structures/create-action-rules.ds.ts @@ -3,16 +3,16 @@ import { TableActionMethodEnum } from '../../../../../enums/table-action-method- import { TableActionTypeEnum } from '../../../../../enums/table-action-type.enum.js'; export class CreateTableActionData { - action_url: string; + action_url?: string; action_method: TableActionMethodEnum; - action_slack_url: string; - action_emails: Array; + action_slack_url?: string; + action_emails?: Array; } export class CreateTableActionEventDS { event: TableActionEventEnum; - event_title: string; - icon: string; + event_title: string | null; + icon?: string | null; require_confirmation: boolean; type: TableActionTypeEnum; } diff --git a/backend/src/entities/table-actions/table-action-rules-module/application/dto/activated-table-actions.dto.ts b/backend/src/entities/table-actions/table-action-rules-module/application/dto/activated-table-actions.dto.ts index e0f00c142..d54005c83 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/application/dto/activated-table-actions.dto.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/application/dto/activated-table-actions.dto.ts @@ -10,8 +10,8 @@ export class ActionActivationResultsStatusesDTO { } export class ActivatedTableActionsDTO { - @ApiProperty({ required: false, type: String }) - location?: string | undefined; + @ApiProperty({ required: false, type: String, nullable: true }) + location?: string | null; @ApiProperty({ type: ActionActivationResultsStatusesDTO, isArray: true }) activationResults: Array; diff --git a/backend/src/entities/table-actions/table-action-rules-module/application/dto/found-action-rules-with-actions-and-events.dto.ts b/backend/src/entities/table-actions/table-action-rules-module/application/dto/found-action-rules-with-actions-and-events.dto.ts index 11cc5f21c..43b57b3eb 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/application/dto/found-action-rules-with-actions-and-events.dto.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/application/dto/found-action-rules-with-actions-and-events.dto.ts @@ -33,8 +33,8 @@ export class FoundActionEventDTO { @ApiProperty({ enum: TableActionTypeEnum }) type: TableActionTypeEnum; - @ApiProperty() - title: string; + @ApiProperty({ type: String, nullable: true }) + title: string | null; @ApiProperty({ type: String, nullable: true }) icon: string | null; diff --git a/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts b/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts index 93f1e96f6..37ec2f330 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts @@ -4,6 +4,7 @@ import { IGlobalDatabaseContext } from '../../../../common/application/global-da import { BaseType } from '../../../../common/data-injection.tokens.js'; import { LogOperationTypeEnum } from '../../../../enums/log-operation-type.enum.js'; import { OperationResultStatusEnum } from '../../../../enums/operation-result-status.enum.js'; +import { TableActionEventEnum } from '../../../../enums/table-action-event-enum.js'; import { Messages } from '../../../../exceptions/text/messages.js'; import { TableLogsService } from '../../../table-logs/table-logs.service.js'; import { TableActionActivationService } from '../../table-actions-module/table-action-activation.service.js'; @@ -49,8 +50,16 @@ export class ActivateActionsInEventUseCase connectionId, masterPwd, ); + if (!foundConnection) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } - let locationFromResult: string = null; + let locationFromResult: string | null = null; const activationResults: Array<{ actionId: string; result: OperationResultStatusEnum }> = []; for (const action of foundActionsWithCustomEvents) { @@ -63,7 +72,7 @@ export class ActivateActionsInEventUseCase request_body, userId, tableName, - null, + TableActionEventEnum.CUSTOM, ); operationResult = receivedOperationResult; primaryKeyValuesArray = receivedPrimaryKeysObj; diff --git a/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts b/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts index d62c361da..7fd8cd5ec 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts @@ -39,6 +39,9 @@ export class CreateActionRuleUseCase await this.validateTableActionDataOrThrowException(table_actions_data); const foundConnection = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId } }); + if (!foundConnection) { + throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + } const newActionRule = buildEmptyActionRule(rule_data, foundConnection); const savedActionRule = await this._dbContext.actionRulesRepository.saveNewOrUpdatedActionRule(newActionRule); @@ -66,9 +69,9 @@ export class CreateActionRuleUseCase ): Promise { const companyWithUsers = await this._dbContext.companyInfoRepository.findUserCompanyWithUsers(userId); const usersInCompanyEmails = companyWithUsers.users.map((user) => user.email); - const emailsFromEmailActions: Array = table_actions_data.reduce((acc, table_action) => { + const emailsFromEmailActions: Array = table_actions_data.reduce((acc: Array, table_action) => { if (table_action.action_method === TableActionMethodEnum.EMAIL) { - return acc.concat(table_action.action_emails); + return acc.concat(table_action.action_emails ?? []); } return acc; }, []); @@ -78,7 +81,7 @@ export class CreateActionRuleUseCase } const emailsNotVerified = emailsFromEmailActions.filter((email) => { const foundUser = companyWithUsers.users.find((user) => user.email === email); - if (foundUser.id === userId) { + if (!foundUser || foundUser.id === userId) { return false; } return !foundUser.isActive; @@ -97,6 +100,9 @@ export class CreateActionRuleUseCase connectionId, masterPwd, ); + if (!foundConnection) { + throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + } const dao = getDataAccessObject(foundConnection); const tablesInConnection = await dao.getTablesFromDB(); const tableNamesInConnection = tablesInConnection.map((table) => table.tableName); @@ -128,7 +134,7 @@ export class CreateActionRuleUseCase throw new BadRequestException(Messages.INVALID_ACTION_METHOD(action.action_method)); } if (action.action_method === TableActionMethodEnum.URL) { - if (!isTest() && (!action.action_url || !ValidationHelper.isValidUrl(action.action_url))) { + if (!action.action_url || (!isTest() && !ValidationHelper.isValidUrl(action.action_url))) { throw new BadRequestException(Messages.URL_INVALID); } const isUrlAllowed = await isActionUrlHostAllowed(action.action_url); diff --git a/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts b/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts index 57a0da52d..0be911651 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts @@ -75,10 +75,13 @@ export class UpdateRuleUseCase const updatedTableActions = await Promise.all( tableActionsToUpdate.map((tableAction) => { const foundTableAction = table_actions.find((action) => action.id === tableAction.action_id); + if (!foundTableAction) { + throw new BadRequestException(Messages.TABLE_ACTION_NOT_FOUND); + } foundTableAction.method = tableAction.action_method; - foundTableAction.url = tableAction.action_url; - foundTableAction.slack_url = tableAction.action_slack_url; - foundTableAction.emails = tableAction.action_emails; + foundTableAction.url = tableAction.action_url ?? null; + foundTableAction.slack_url = tableAction.action_slack_url ?? null; + foundTableAction.emails = tableAction.action_emails ?? []; return this._dbContext.tableActionRepository.saveNewOrUpdatedTableAction(foundTableAction); }), ); @@ -101,9 +104,12 @@ export class UpdateRuleUseCase const updatedActionEvents = await Promise.all( actionEventsToUpdate.map((actionEvent) => { const foundActionEvent = action_events.find((event) => event.id === actionEvent.event_id); + if (!foundActionEvent) { + throw new BadRequestException(Messages.ACTION_EVENT_NOT_FOUND); + } foundActionEvent.event = actionEvent.event; foundActionEvent.title = actionEvent.event_title; - foundActionEvent.icon = actionEvent.icon; + foundActionEvent.icon = actionEvent.icon ?? null; foundActionEvent.type = actionEvent.type; foundActionEvent.require_confirmation = actionEvent.require_confirmation; return this._dbContext.actionEventsRepository.saveNewOrUpdatedActionEvent(foundActionEvent); @@ -130,9 +136,9 @@ export class UpdateRuleUseCase ): Promise { const companyWithUsers = await this._dbContext.companyInfoRepository.findUserCompanyWithUsers(userId); const usersInCompanyEmails = companyWithUsers.users.map((user) => user.email); - const emailsFromEmailActions: Array = table_actions_data.reduce((acc, table_action) => { + const emailsFromEmailActions: Array = table_actions_data.reduce((acc: Array, table_action) => { if (table_action.action_method === TableActionMethodEnum.EMAIL) { - return acc.concat(table_action.action_emails); + return acc.concat(table_action.action_emails ?? []); } return acc; }, []); @@ -142,7 +148,7 @@ export class UpdateRuleUseCase } const emailsNotVerified = emailsFromEmailActions.filter((email) => { const foundUser = companyWithUsers.users.find((user) => user.email === email); - if (foundUser.id === userId) { + if (!foundUser || foundUser.id === userId) { return false; } return !foundUser.isActive; @@ -161,6 +167,9 @@ export class UpdateRuleUseCase connectionId, masterPwd, ); + if (!foundConnection) { + throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + } const dao = getDataAccessObject(foundConnection); const tablesInConnection = await dao.getTablesFromDB(); const tableNamesInConnection = tablesInConnection.map((table) => table.tableName); @@ -194,7 +203,7 @@ export class UpdateRuleUseCase } if (action.action_method === TableActionMethodEnum.URL) { - if (!isTest() && (!action.action_url || !ValidationHelper.isValidUrl(action.action_url))) { + if (!action.action_url || (!isTest() && !ValidationHelper.isValidUrl(action.action_url))) { throw new BadRequestException(Messages.URL_INVALID); } const isUrlAllowed = await isActionUrlHostAllowed(action.action_url); diff --git a/backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts b/backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts index 855809762..abb4287d7 100644 --- a/backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts +++ b/backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts @@ -1,4 +1,4 @@ -import { HttpException, Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import axios, { AxiosResponse } from 'axios'; @@ -7,6 +7,7 @@ import { Repository } from 'typeorm'; import { OperationResultStatusEnum } from '../../../enums/operation-result-status.enum.js'; import { TableActionEventEnum } from '../../../enums/table-action-event-enum.js'; import { TableActionMethodEnum } from '../../../enums/table-action-method-enum.js'; +import { Messages } from '../../../exceptions/text/messages.js'; import { isSaaS } from '../../../helpers/app/is-saas.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; import { actionSlackPostMessage } from '../../../helpers/slack/action-slack-post-message.js'; @@ -26,7 +27,7 @@ export type ActionActivationResult = { export type UserInfoMessageData = { userId: string; email: string; - userName: string; + userName: string | null; }; @Injectable() @@ -48,6 +49,14 @@ export class TableActionActivationService { const foundUser = await this.userRepository.findOne({ where: { id: userId }, }); + if (!foundUser) { + throw new HttpException( + { + message: Messages.USER_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } const userInfoMessageData: UserInfoMessageData = { userId, email: foundUser.email, @@ -103,6 +112,9 @@ export class TableActionActivationService { const slackMessage = this.generateMessageContent(userInfo, triggerOperation, tableName, primaryKeyValuesArray); try { + if (!tableAction.slack_url) { + throw new Error('Slack URL is not configured for this action'); + } await actionSlackPostMessage(slackMessage, tableAction.slack_url); operationResult = OperationResultStatusEnum.successfully; } catch (_e) { @@ -181,9 +193,25 @@ export class TableActionActivationService { $$_actionId: tableAction.id, $$_tableName: tableName, }); + if (!foundConnection.signing_key) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.BAD_REQUEST, + ); + } const autoadminSignatureHeader = Encryptor.hashDataHMACexternalKey(foundConnection.signing_key, actionRequestBody); - let result: AxiosResponse; + if (!tableAction.url) { + throw new HttpException( + { + message: Messages.URL_INVALID, + }, + HttpStatus.BAD_REQUEST, + ); + } + let result: AxiosResponse | undefined; try { result = await axios.post(tableAction.url, actionRequestBody, { headers: { 'Rocketadmin-Signature': autoadminSignatureHeader, 'Content-Type': 'application/json' }, @@ -191,9 +219,9 @@ export class TableActionActivationService { validateStatus: (status) => status <= 599, }); if (!isSaaS()) { - console.info('HTTP action result data', result.data); - console.info('HTTP action result status', result.status); - console.info('HTTP action result headers', result.headers); + console.info('HTTP action result data', result?.data); + console.info('HTTP action result status', result?.status); + console.info('HTTP action result headers', result?.headers); } } catch (error) { if (axios.isAxiosError(error)) { @@ -213,6 +241,14 @@ export class TableActionActivationService { } throw error; } + if (!result) { + throw new HttpException( + { + message: 'An error occurred', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } const operationStatusCode = result.status; if (operationStatusCode >= 200 && operationStatusCode < 300) { operationResult = OperationResultStatusEnum.successfully; @@ -284,7 +320,7 @@ export class TableActionActivationService { tableName: string, ): Promise>> { const dataAccessObject = getDataAccessObject(foundConnection); - const tablePrimaryKeys = await dataAccessObject.getTablePrimaryColumns(tableName, null); + const tablePrimaryKeys = await dataAccessObject.getTablePrimaryColumns(tableName, ''); const primaryKeyValuesArray: Array> = []; for (const primaryKeyInBody of request_body) { const pKeysObj: Record = {}; @@ -323,7 +359,7 @@ export class TableActionActivationService { private generateMessageContent( userInfo: UserInfoMessageData, - triggerOperation: TableActionEventEnum, + triggerOperation: TableActionEventEnum | undefined, tableName: string, primaryKeyValuesArray: Array>, ): string { diff --git a/backend/src/entities/table-actions/table-actions-module/table-action.entity.ts b/backend/src/entities/table-actions/table-actions-module/table-action.entity.ts index 1e8edeb3f..6cbecb9de 100644 --- a/backend/src/entities/table-actions/table-actions-module/table-action.entity.ts +++ b/backend/src/entities/table-actions/table-actions-module/table-action.entity.ts @@ -20,8 +20,8 @@ export class TableActionEntity { @PrimaryGeneratedColumn('uuid') id: string; - @Column({ default: null }) - url: string; + @Column({ type: 'varchar', default: null }) + url: string | null; @Column('enum', { nullable: false, @@ -30,8 +30,8 @@ export class TableActionEntity { }) method!: TableActionMethodEnum; - @Column({ default: null }) - slack_url: string; + @Column({ type: 'varchar', default: null }) + slack_url: string | null; @Column('varchar', { array: true, default: {} }) emails: string[]; diff --git a/backend/src/entities/table-actions/table-actions-module/utils/build-table-action-with-rule.util.ts b/backend/src/entities/table-actions/table-actions-module/utils/build-table-action-with-rule.util.ts index 779fe26aa..440b9c406 100644 --- a/backend/src/entities/table-actions/table-actions-module/utils/build-table-action-with-rule.util.ts +++ b/backend/src/entities/table-actions/table-actions-module/utils/build-table-action-with-rule.util.ts @@ -9,9 +9,9 @@ export function buildTableActionWithRule( const { action_emails, action_method, action_url, action_slack_url } = tableActionData; const newTableAction = new TableActionEntity(); newTableAction.action_rule = actionRule; - newTableAction.emails = action_emails; + newTableAction.emails = action_emails ?? []; newTableAction.method = action_method; - newTableAction.url = action_url; - newTableAction.slack_url = action_slack_url; + newTableAction.url = action_url ?? null; + newTableAction.slack_url = action_slack_url ?? null; return newTableAction; } diff --git a/backend/src/entities/table-categories/dto/found-table-categories-with-tables.ro.ts b/backend/src/entities/table-categories/dto/found-table-categories-with-tables.ro.ts index 34ab37382..96f12d0f4 100644 --- a/backend/src/entities/table-categories/dto/found-table-categories-with-tables.ro.ts +++ b/backend/src/entities/table-categories/dto/found-table-categories-with-tables.ro.ts @@ -8,8 +8,8 @@ export class FoundTableCategoriesWithTablesRo { @ApiProperty({ type: String }) category_name: string; - @ApiProperty({ type: String }) - category_color: string; + @ApiProperty({ type: String, nullable: true }) + category_color: string | null; @ApiProperty({ isArray: true, type: FoundTableDs }) tables: Array; diff --git a/backend/src/entities/table-categories/use-cases/create-or-update-table-categories.use.case.ts b/backend/src/entities/table-categories/use-cases/create-or-update-table-categories.use.case.ts index 4a0f2f69e..4b65cfed3 100644 --- a/backend/src/entities/table-categories/use-cases/create-or-update-table-categories.use.case.ts +++ b/backend/src/entities/table-categories/use-cases/create-or-update-table-categories.use.case.ts @@ -1,7 +1,8 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, NotFoundException } from '@nestjs/common'; 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 { Messages } from '../../../exceptions/text/messages.js'; import { CreateOrUpdateTableCategoriesDS } from '../data-sctructures/create-or-update-table-categories.ds.js'; import { FoundTableCategoryRo } from '../dto/found-table-category.ro.js'; import { buildFoundTableCategoryRo } from '../utils/build-found-table-category.ro.js'; @@ -30,6 +31,9 @@ export class CreateOrUpdateTableCategoriesUseCase connectionId, master_password, ); + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } await validateTableCategories(filteredCategories, foundConnection); diff --git a/backend/src/entities/table-categories/use-cases/find-table-categories-with-tables.use.case.ts b/backend/src/entities/table-categories/use-cases/find-table-categories-with-tables.use.case.ts index 06dece9e1..a8da3d74f 100644 --- a/backend/src/entities/table-categories/use-cases/find-table-categories-with-tables.use.case.ts +++ b/backend/src/entities/table-categories/use-cases/find-table-categories-with-tables.use.case.ts @@ -9,12 +9,12 @@ import { ExceptionOperations } from '../../../exceptions/custom-exceptions/excep import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; -import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { ConnectionEntity } from '../../connection/connection.entity.js'; import { ITableAndViewPermissionData } from '../../permission/permission.interface.js'; import { FindTablesDs } from '../../table/application/data-structures/find-tables.ds.js'; import { FoundTableDs } from '../../table/application/data-structures/found-table.ds.js'; +import { getUserEmailForAgent } from '../../table/utils/validate-connection.util.js'; import { TableSettingsEntity } from '../../table-settings/common-table-settings/table-settings.entity.js'; import { FoundTableCategoriesWithTablesRo } from '../dto/found-table-categories-with-tables.ro.js'; import { IFindTableCategoriesWithTables } from './table-categories-use-cases.interface.js'; @@ -34,7 +34,7 @@ export class FindTableCategoriesWithTablesUseCase protected async implementation(inputData: FindTablesDs): Promise> { const { connectionId, masterPwd, userId } = inputData; - let connection: ConnectionEntity; + let connection: ConnectionEntity | null = null; try { connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); } catch (error) { @@ -67,11 +67,7 @@ export class FindTableCategoriesWithTablesUseCase ); } const dao = getDataAccessObject(connection); - let userEmail: string; - - if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); - } + const userEmail = await getUserEmailForAgent(connection, userId, this._dbContext.userRepository); let tables: Array; try { tables = await dao.getTablesFromDB(userEmail); @@ -92,9 +88,10 @@ export class FindTableCategoriesWithTablesUseCase })); const excludedTables = await this._dbContext.connectionPropertiesRepository.findConnectionProperties(connectionId); let tablesRO = await this.addDisplayNamesForTables(connectionId, tablesWithPermissions); - if (excludedTables?.hidden_tables?.length) { + const hiddenTables = excludedTables?.hidden_tables; + if (hiddenTables?.length) { tablesRO = tablesRO.filter((tableRO) => { - return !excludedTables.hidden_tables.includes(tableRO.table); + return !hiddenTables.includes(tableRO.table); }); } @@ -110,7 +107,7 @@ export class FindTableCategoriesWithTablesUseCase if (storedAllTablesCategory) { allTablesOrdered = storedAllTablesCategory.tables .map((tableName) => tablesRO.find((tableRO) => tableRO.table === tableName)) - .filter(Boolean); + .filter((tableRO): tableRO is FoundTableDs => Boolean(tableRO)); const storedTableNames = new Set(storedAllTablesCategory.tables); const newTables = tablesRO.filter((tableRO) => !storedTableNames.has(tableRO.table)); @@ -133,7 +130,7 @@ export class FindTableCategoriesWithTablesUseCase const foundTableCategoriesRO = otherCategories.map((category) => { const tablesInCategory = category.tables .map((tableName) => tablesRO.find((tableRO) => tableRO.table === tableName)) - .filter(Boolean); + .filter((tableRO): tableRO is FoundTableDs => Boolean(tableRO)); return { category_id: category.category_id, category_color: category.category_color, diff --git a/backend/src/entities/table-filters/application/data-structures/create-table-filters.ds.ts b/backend/src/entities/table-filters/application/data-structures/create-table-filters.ds.ts index 4a8a1753b..cf8110f20 100644 --- a/backend/src/entities/table-filters/application/data-structures/create-table-filters.ds.ts +++ b/backend/src/entities/table-filters/application/data-structures/create-table-filters.ds.ts @@ -8,7 +8,7 @@ export class CreateTableFilterDs { table_name: string; connection_id: string; filters: Record; - masterPwd: string; + masterPwd: string | null; filter_name: string; dynamic_filtered_column: DynamicTableFiltersDs | null; } diff --git a/backend/src/entities/table-filters/application/response-objects/create-table-filters.dto.ts b/backend/src/entities/table-filters/application/response-objects/create-table-filters.dto.ts index bc4d51754..9bf427ef1 100644 --- a/backend/src/entities/table-filters/application/response-objects/create-table-filters.dto.ts +++ b/backend/src/entities/table-filters/application/response-objects/create-table-filters.dto.ts @@ -59,8 +59,8 @@ export class CreateTableFilterDto { @IsObject() filters: Record; - @ApiProperty({ type: DynamicTableFilterDto, required: false }) + @ApiProperty({ type: DynamicTableFilterDto, required: false, nullable: true }) @IsOptional() @ValidateNested() - dynamic_column: DynamicTableFilterDto; + dynamic_column: DynamicTableFilterDto | null; } diff --git a/backend/src/entities/table-filters/use-cases/create-table-filters.use.case.ts b/backend/src/entities/table-filters/use-cases/create-table-filters.use.case.ts index b7a699b66..d7788a379 100644 --- a/backend/src/entities/table-filters/use-cases/create-table-filters.use.case.ts +++ b/backend/src/entities/table-filters/use-cases/create-table-filters.use.case.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable, Scope } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; 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'; @@ -28,8 +28,11 @@ export class CreateTableFiltersUseCase const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( connection_id, - masterPwd, + masterPwd ?? '', ); + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } if (foundConnection.is_frozen) { throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); } diff --git a/backend/src/entities/table-filters/use-cases/update-table-filters.use.case.ts b/backend/src/entities/table-filters/use-cases/update-table-filters.use.case.ts index 6218c63e5..e977420d0 100644 --- a/backend/src/entities/table-filters/use-cases/update-table-filters.use.case.ts +++ b/backend/src/entities/table-filters/use-cases/update-table-filters.use.case.ts @@ -29,6 +29,9 @@ export class UpdateTableFiltersUseCase connection_id, masterPwd, ); + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } if (foundConnection.is_frozen) { throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); } diff --git a/backend/src/entities/table-filters/utils/build-created-table-filters-response-object.util.ts b/backend/src/entities/table-filters/utils/build-created-table-filters-response-object.util.ts index d16090175..a759bbd1b 100644 --- a/backend/src/entities/table-filters/utils/build-created-table-filters-response-object.util.ts +++ b/backend/src/entities/table-filters/utils/build-created-table-filters-response-object.util.ts @@ -2,19 +2,19 @@ import { CreatedTableFilterRO } from '../application/response-objects/created-ta import { TableFiltersEntity } from '../table-filters.entity.js'; export function buildCreatedTableFilterRO(tableFilters: TableFiltersEntity): CreatedTableFilterRO { - const dynamicColumnExists: boolean = - tableFilters.dynamic_filter_column_name !== null && tableFilters.dynamic_filter_comparator !== null; + const { dynamic_filter_column_name, dynamic_filter_comparator } = tableFilters; return { id: tableFilters.id, table_name: tableFilters.table_name, - name: tableFilters.name, + name: tableFilters.name ?? '', filters: tableFilters.filters, - dynamic_column: dynamicColumnExists - ? { - column_name: tableFilters.dynamic_filter_column_name, - comparator: tableFilters.dynamic_filter_comparator, - } - : null, + dynamic_column: + dynamic_filter_column_name !== null && dynamic_filter_comparator !== null + ? { + column_name: dynamic_filter_column_name, + comparator: dynamic_filter_comparator, + } + : null, createdAt: tableFilters.createdAt, updatedAt: tableFilters.updatedAt, }; diff --git a/backend/src/entities/table-filters/utils/validate-table-filters-data.util.ts b/backend/src/entities/table-filters/utils/validate-table-filters-data.util.ts index 1af0c08be..e12245558 100644 --- a/backend/src/entities/table-filters/utils/validate-table-filters-data.util.ts +++ b/backend/src/entities/table-filters/utils/validate-table-filters-data.util.ts @@ -25,7 +25,7 @@ export async function validateFiltersData( if (errors.length > 0) { return errors; } - const tableStructure = await dao.getTableStructure(table_name, null); + const tableStructure = await dao.getTableStructure(table_name, ''); const tableColumnNames = tableStructure.map((el) => el.column_name); for (const column_name in filters) { diff --git a/backend/src/entities/table-logs/application/data-structures/create-log-record.ds.ts b/backend/src/entities/table-logs/application/data-structures/create-log-record.ds.ts index 9512e89f9..8f01f4fe3 100644 --- a/backend/src/entities/table-logs/application/data-structures/create-log-record.ds.ts +++ b/backend/src/entities/table-logs/application/data-structures/create-log-record.ds.ts @@ -11,5 +11,5 @@ export class CreateLogRecordDs { table_name: string; userId: string; affected_primary_key?: string | Record; - operation_custom_action_name?: string; + operation_custom_action_name?: string | null; } diff --git a/backend/src/entities/table-logs/repository/table-logs-repository.interface.ts b/backend/src/entities/table-logs/repository/table-logs-repository.interface.ts index 150d5da7d..24edbf23b 100644 --- a/backend/src/entities/table-logs/repository/table-logs-repository.interface.ts +++ b/backend/src/entities/table-logs/repository/table-logs-repository.interface.ts @@ -19,13 +19,13 @@ export interface ITableLogsRepository { export interface IFindLogsOptions { connectionId: string; currentUserId: string; - dateFrom: Date; - dateTo: Date; + dateFrom: Date | null; + dateTo: Date | null; order: QueryOrderingEnum; page: number; perPage: number; searchedEmail: string; - tableName: string; + tableName: string | null; userConnectionEdit: boolean; userInGroupsIds: Array; logOperationType: LogOperationTypeEnum; diff --git a/backend/src/entities/table-logs/table-logs.entity.ts b/backend/src/entities/table-logs/table-logs.entity.ts index 9cfbfb699..67521f2b0 100644 --- a/backend/src/entities/table-logs/table-logs.entity.ts +++ b/backend/src/entities/table-logs/table-logs.entity.ts @@ -37,7 +37,7 @@ export class TableLogsEntity { @Column({ default: null }) email: string; - @Column({ default: null }) + @Column({ type: 'varchar', default: null }) operation_custom_action_name: string | null; @Column('enum', { diff --git a/backend/src/entities/table-logs/table-logs.service.ts b/backend/src/entities/table-logs/table-logs.service.ts index d8945d4e4..108bf4e7c 100644 --- a/backend/src/entities/table-logs/table-logs.service.ts +++ b/backend/src/entities/table-logs/table-logs.service.ts @@ -30,13 +30,16 @@ export class TableLogsService { private readonly connectionPropertiesRepository: Repository, ) {} - public async crateAndSaveNewLogUtil(logData: CreateLogRecordDs): Promise { + public async crateAndSaveNewLogUtil(logData: CreateLogRecordDs): Promise { const { userId, connection, table_name, old_data, row } = logData; const isAuditEnabled = await this.isTableAuditEnabledInConnectionProperties(connection.id); if (!isAuditEnabled) { - return; + return null; } const foundUser = await this.userRepository.findOne({ where: { id: userId } }); + if (!foundUser) { + return null; + } const { email } = foundUser; const tableSettingsQb = this.tableSettingsRepository .createQueryBuilder('tableLogs') @@ -126,12 +129,15 @@ export class TableLogsService { connection: ConnectionEntity, table_name: string, operationType: LogOperationTypeEnum, - ): Promise> { + ): Promise | null> { const isAuditEnabled = await this.isTableAuditEnabledInConnectionProperties(connection.id); if (!isAuditEnabled) { - return; + return null; } const foundUser = await this.userRepository.findOne({ where: { id: userId } }); + if (!foundUser) { + return null; + } const { email } = foundUser; const tableSettingsQb = this.tableSettingsRepository .createQueryBuilder('tableLogs') diff --git a/backend/src/entities/table-logs/use-cases/export-logs-as-csv.use.case.ts b/backend/src/entities/table-logs/use-cases/export-logs-as-csv.use.case.ts index 5fc2263c5..6c671c0b5 100644 --- a/backend/src/entities/table-logs/use-cases/export-logs-as-csv.use.case.ts +++ b/backend/src/entities/table-logs/use-cases/export-logs-as-csv.use.case.ts @@ -9,9 +9,9 @@ import { QueryOrderingEnum } from '../../../enums/query-ordering.enum.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { Constants } from '../../../helpers/constants/constants.js'; import { validateStringWithEnum } from '../../../helpers/validators/validate-string-with-enum.js'; +import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { FindLogsDs } from '../application/data-structures/find-logs.ds.js'; import { IFindLogsOptions } from '../repository/table-logs-repository.interface.js'; -import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { IExportLogsAsCsv } from './use-cases.interface.js'; @Injectable() @@ -76,8 +76,8 @@ export class ExportLogsAsCsvUseCase extends AbstractUseCase im perPage = limit; } - let searchedDateFrom: Date = null; - let searchedDateTo: Date = null; + let searchedDateFrom: Date | null = null; + let searchedDateTo: Date | null = null; if (dateFrom && dateTo) { let dateFromParsed = Date.parse(dateFrom); let dateToParsed = Date.parse(dateTo); diff --git a/backend/src/entities/table-schema/ai/run-schema-change-ai-loop.ts b/backend/src/entities/table-schema/ai/run-schema-change-ai-loop.ts index 291d429a8..d3ed21e95 100644 --- a/backend/src/entities/table-schema/ai/run-schema-change-ai-loop.ts +++ b/backend/src/entities/table-schema/ai/run-schema-change-ai-loop.ts @@ -229,8 +229,8 @@ async function executeInspectionToolCalls( if (tc.name === GET_TABLE_STRUCTURE_TOOL_NAME) { const tableName = tc.arguments.tableName as string; if (!tableName) throw new Error('Missing argument "tableName"'); - const structure = await dao.getTableStructure(tableName, userEmail); - const foreignKeys = await dao.getTableForeignKeys(tableName, userEmail); + const structure = await dao.getTableStructure(tableName, userEmail ?? ''); + const foreignKeys = await dao.getTableForeignKeys(tableName, userEmail ?? ''); result = encodeToToon({ tableName, structure, foreignKeys }); } else if (TERMINAL_PROPOSAL_TOOL_NAMES.has(tc.name)) { result = encodeError({ diff --git a/backend/src/entities/table-schema/use-cases/approve-and-apply-schema-change.use-case.ts b/backend/src/entities/table-schema/use-cases/approve-and-apply-schema-change.use-case.ts index f595ecd00..421ed2a63 100644 --- a/backend/src/entities/table-schema/use-cases/approve-and-apply-schema-change.use-case.ts +++ b/backend/src/entities/table-schema/use-cases/approve-and-apply-schema-change.use-case.ts @@ -77,7 +77,7 @@ export class ApproveAndApplySchemaChangeUseCase const connection = await this._dbContext.connectionRepository.findAndDecryptConnection( change.connectionId, - masterPassword, + masterPassword ?? '', ); if (!connection) { throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); diff --git a/backend/src/entities/table-schema/use-cases/approve-batch-schema-changes.use-case.ts b/backend/src/entities/table-schema/use-cases/approve-batch-schema-changes.use-case.ts index 401d038a0..76848198d 100644 --- a/backend/src/entities/table-schema/use-cases/approve-batch-schema-changes.use-case.ts +++ b/backend/src/entities/table-schema/use-cases/approve-batch-schema-changes.use-case.ts @@ -81,7 +81,7 @@ export class ApproveBatchSchemaChangesUseCase const connection = await this._dbContext.connectionRepository.findAndDecryptConnection( items[0].connectionId, - masterPassword, + masterPassword ?? '', ); if (!connection) { throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); diff --git a/backend/src/entities/table-schema/use-cases/generate-schema-change.use-case.ts b/backend/src/entities/table-schema/use-cases/generate-schema-change.use-case.ts index 8e72a9be2..30d365d6d 100644 --- a/backend/src/entities/table-schema/use-cases/generate-schema-change.use-case.ts +++ b/backend/src/entities/table-schema/use-cases/generate-schema-change.use-case.ts @@ -67,7 +67,7 @@ export class GenerateSchemaChangeUseCase const connection = await this._dbContext.connectionRepository.findAndDecryptConnection( connectionId, - masterPassword, + masterPassword ?? '', ); if (!connection) { throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); diff --git a/backend/src/entities/table-schema/use-cases/rollback-batch-schema-changes.use-case.ts b/backend/src/entities/table-schema/use-cases/rollback-batch-schema-changes.use-case.ts index 642b2cf48..c0124ee19 100644 --- a/backend/src/entities/table-schema/use-cases/rollback-batch-schema-changes.use-case.ts +++ b/backend/src/entities/table-schema/use-cases/rollback-batch-schema-changes.use-case.ts @@ -61,7 +61,7 @@ export class RollbackBatchSchemaChangesUseCase const connection = await this._dbContext.connectionRepository.findAndDecryptConnection( items[0].connectionId, - masterPassword, + masterPassword ?? '', ); if (!connection) { throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); diff --git a/backend/src/entities/table-schema/use-cases/rollback-schema-change.use-case.ts b/backend/src/entities/table-schema/use-cases/rollback-schema-change.use-case.ts index 61fe4cd4a..62cc88f5c 100644 --- a/backend/src/entities/table-schema/use-cases/rollback-schema-change.use-case.ts +++ b/backend/src/entities/table-schema/use-cases/rollback-schema-change.use-case.ts @@ -55,7 +55,7 @@ export class RollbackSchemaChangeUseCase const connection = await this._dbContext.connectionRepository.findAndDecryptConnection( change.connectionId, - masterPassword, + masterPassword ?? '', ); if (!connection) { throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); diff --git a/backend/src/entities/table-schema/utils/execute-schema-change.ts b/backend/src/entities/table-schema/utils/execute-schema-change.ts index 3b09df054..035db2de0 100644 --- a/backend/src/entities/table-schema/utils/execute-schema-change.ts +++ b/backend/src/entities/table-schema/utils/execute-schema-change.ts @@ -75,5 +75,5 @@ export async function executeSchemaChange(opts: ExecuteSchemaChangeOptions): Pro } const dao = getDataAccessObject(connection); - await dao.executeRawQuery(sql, targetTableName, null); + await dao.executeRawQuery(sql, targetTableName, ''); } diff --git a/backend/src/entities/table-settings/application/data-structures/create-table-settings.ds.ts b/backend/src/entities/table-settings/application/data-structures/create-table-settings.ds.ts index 3922b96d6..2212c308a 100644 --- a/backend/src/entities/table-settings/application/data-structures/create-table-settings.ds.ts +++ b/backend/src/entities/table-settings/application/data-structures/create-table-settings.ds.ts @@ -5,7 +5,7 @@ import { TableWidgetEntity } from '../../../widget/table-widget.entity.js'; export class CreateTableSettingsDs { @ApiProperty({ isArray: true, type: 'string' }) - autocomplete_columns: Array; + autocomplete_columns?: Array; connection_id: string; @@ -13,52 +13,52 @@ export class CreateTableSettingsDs { custom_fields?: Array; @ApiProperty() - display_name: string; + display_name?: string | null; @ApiProperty({ isArray: true, type: 'string' }) - excluded_fields: Array; + excluded_fields?: Array; @ApiProperty({ isArray: true, type: 'string' }) - identification_fields: Array; + identification_fields?: Array; @ApiProperty() - identity_column: string; + identity_column?: string | null; - masterPwd: string; + masterPwd?: string | null; @ApiProperty({ isArray: true, type: 'string' }) - readonly_fields: Array; + readonly_fields?: Array; @ApiProperty({ isArray: true, type: 'string' }) - search_fields: Array; + search_fields?: Array; @ApiProperty({ isArray: true, type: 'string' }) - sortable_by: Array; + sortable_by?: Array; @ApiProperty({ isArray: true, type: 'string' }) - sensitive_fields: Array; + sensitive_fields?: Array | null; table_name: string; @ApiProperty({ isArray: true }) table_widgets?: Array; - userId: string; + userId?: string | null; @ApiProperty({ isArray: true }) table_actions?: Array; @ApiProperty() - can_delete: boolean; + can_delete?: boolean; @ApiProperty() - can_update: boolean; + can_update?: boolean; @ApiProperty() - can_add: boolean; + can_add?: boolean; @ApiProperty() - icon: string; + icon?: string | null; @ApiProperty() allow_csv_export: boolean; diff --git a/backend/src/entities/table-settings/application/data-structures/found-table-settings.ds.ts b/backend/src/entities/table-settings/application/data-structures/found-table-settings.ds.ts index 5809d8b36..df0a30489 100644 --- a/backend/src/entities/table-settings/application/data-structures/found-table-settings.ds.ts +++ b/backend/src/entities/table-settings/application/data-structures/found-table-settings.ds.ts @@ -10,8 +10,8 @@ export class FoundTableSettingsDs { @ApiProperty() table_name: string; - @ApiProperty() - display_name: string; + @ApiProperty({ nullable: true }) + display_name: string | null; @ApiProperty({ isArray: true, type: String }) search_fields: Array; @@ -22,14 +22,14 @@ export class FoundTableSettingsDs { @ApiProperty({ isArray: true, type: String }) identification_fields: Array; - @ApiProperty() - identity_column: string; + @ApiProperty({ nullable: true }) + identity_column: string | null; @ApiProperty({ isArray: true, type: String }) readonly_fields: Array; - @ApiProperty({ isArray: true, type: String }) - sensitive_fields: Array; + @ApiProperty({ isArray: true, type: String, nullable: true }) + sensitive_fields: Array | null; @ApiProperty({ isArray: true, type: String }) sortable_by: Array; @@ -37,8 +37,8 @@ export class FoundTableSettingsDs { @ApiProperty({ isArray: true, type: String }) autocomplete_columns: Array; - @ApiProperty({ isArray: true, type: String }) - columns_view: Array; + @ApiProperty({ isArray: true, type: String, nullable: true }) + columns_view: Array | null; @ApiProperty() connection_id: string; @@ -61,8 +61,8 @@ export class FoundTableSettingsDs { @ApiProperty() can_update: boolean; - @ApiProperty() - icon: string; + @ApiProperty({ nullable: true }) + icon: string | null; @ApiProperty() allow_csv_export: boolean; @@ -73,12 +73,12 @@ export class FoundTableSettingsDs { @ApiProperty({ isArray: true, type: 'string' }) list_fields: string[]; - @ApiProperty() - list_per_page: number; + @ApiProperty({ nullable: true }) + list_per_page: number | null; @ApiProperty() ordering: string; - @ApiProperty() - ordering_field: string; + @ApiProperty({ nullable: true }) + ordering_field: string | null; } diff --git a/backend/src/entities/table-settings/common-table-settings/table-settings.controller.ts b/backend/src/entities/table-settings/common-table-settings/table-settings.controller.ts index fc165a3d1..fc9130712 100644 --- a/backend/src/entities/table-settings/common-table-settings/table-settings.controller.ts +++ b/backend/src/entities/table-settings/common-table-settings/table-settings.controller.ts @@ -277,7 +277,7 @@ export class TableSettingsController { return await this.deleteTableSettingsUseCase.execute(inputData, InTransactionEnum.ON); } - private validateParameters(tableSettingsDTO: CreateTableSettingsDto): Array { + private validateParameters(tableSettingsDTO: CreateTableSettingsDs): Array { const errors = []; if (!tableSettingsDTO.table_name) { errors.push(Messages.TABLE_NAME_MISSING); diff --git a/backend/src/entities/table-settings/common-table-settings/table-settings.entity.ts b/backend/src/entities/table-settings/common-table-settings/table-settings.entity.ts index 680bbb33e..360df0b1c 100644 --- a/backend/src/entities/table-settings/common-table-settings/table-settings.entity.ts +++ b/backend/src/entities/table-settings/common-table-settings/table-settings.entity.ts @@ -26,8 +26,8 @@ export class TableSettingsEntity { @Column({ default: null }) table_name: string; - @Column({ default: null }) - display_name: string; + @Column({ type: 'varchar', default: null }) + display_name: string | null; @Column('varchar', { array: true, default: {} }) search_fields: string[]; @@ -42,7 +42,7 @@ export class TableSettingsEntity { identification_fields: string[]; @Column('int', { default: null }) - list_per_page: number; + list_per_page: number | null; @Column('enum', { nullable: true, @@ -52,10 +52,10 @@ export class TableSettingsEntity { ordering!: QueryOrderingEnum; @Column('varchar', { default: null }) - ordering_field: string; + ordering_field: string | null; - @Column({ default: null }) - identity_column: string; + @Column({ type: 'varchar', default: null }) + identity_column: string | null; @Column('varchar', { array: true, default: {} }) readonly_fields: string[]; @@ -67,7 +67,7 @@ export class TableSettingsEntity { autocomplete_columns: string[]; @Column('varchar', { array: true, default: null }) - columns_view: string[]; + columns_view: string[] | null; @Column({ default: true, type: 'boolean' }) can_delete: boolean; @@ -85,10 +85,10 @@ export class TableSettingsEntity { allow_csv_import: boolean; @Column('varchar', { array: true, default: null }) - sensitive_fields: string[]; + sensitive_fields: string[] | null; @Column('varchar', { default: null }) - icon: string; + icon: string | null; @CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) created_at: Date; diff --git a/backend/src/entities/table-settings/common-table-settings/use-cases/create-table-settings.use.case.ts b/backend/src/entities/table-settings/common-table-settings/use-cases/create-table-settings.use.case.ts index 72d0b6c7e..8271c4683 100644 --- a/backend/src/entities/table-settings/common-table-settings/use-cases/create-table-settings.use.case.ts +++ b/backend/src/entities/table-settings/common-table-settings/use-cases/create-table-settings.use.case.ts @@ -6,6 +6,7 @@ import { buildValidateTableSettingsDS } from '@rocketadmin/shared-code/dist/src/ 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 { Messages } from '../../../../exceptions/text/messages.js'; import { toPrettyErrorsMsg } from '../../../../helpers/to-pretty-errors-msg.js'; import { CreateTableSettingsDs } from '../../application/data-structures/create-table-settings.ds.js'; import { FoundTableSettingsDs } from '../../application/data-structures/found-table-settings.ds.js'; @@ -29,11 +30,19 @@ export class CreateTableSettingsUseCase const { connection_id, masterPwd, table_name } = inputData; const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( connection_id, - masterPwd, + masterPwd ?? '', ); + if (!foundConnection) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } const dao = getDataAccessObject(foundConnection); const tableSettingsDs: ValidateTableSettingsDS = buildValidateTableSettingsDS(inputData); - const errors: Array = await dao.validateSettings(tableSettingsDs, table_name, undefined); + const errors: Array = await dao.validateSettings(tableSettingsDs, table_name, ''); if (errors.length > 0) { throw new HttpException( { diff --git a/backend/src/entities/table-settings/common-table-settings/use-cases/update-table-settings.use.case.ts b/backend/src/entities/table-settings/common-table-settings/use-cases/update-table-settings.use.case.ts index 98435ff57..34388bc9e 100644 --- a/backend/src/entities/table-settings/common-table-settings/use-cases/update-table-settings.use.case.ts +++ b/backend/src/entities/table-settings/common-table-settings/use-cases/update-table-settings.use.case.ts @@ -30,11 +30,19 @@ export class UpdateTableSettingsUseCase const { connection_id, masterPwd, table_name } = inputData; const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( connection_id, - masterPwd, + masterPwd ?? '', ); + if (!foundConnection) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } const dao = getDataAccessObject(foundConnection); const tableSettingsDs: ValidateTableSettingsDS = buildValidateTableSettingsDS(inputData); - const errors: Array = await dao.validateSettings(tableSettingsDs, table_name, undefined); + const errors: Array = await dao.validateSettings(tableSettingsDs, table_name, ''); if (errors.length > 0) { throw new HttpException( { diff --git a/backend/src/entities/table-settings/common-table-settings/utils/build-new-table-settings-entity.ts b/backend/src/entities/table-settings/common-table-settings/utils/build-new-table-settings-entity.ts index 19ffe5ec1..dedb2d92e 100644 --- a/backend/src/entities/table-settings/common-table-settings/utils/build-new-table-settings-entity.ts +++ b/backend/src/entities/table-settings/common-table-settings/utils/build-new-table-settings-entity.ts @@ -35,29 +35,29 @@ export function buildNewTableSettingsEntity( columns_view, } = settings; newSettings.connection_id = connection; - newSettings.display_name = display_name; + newSettings.display_name = display_name ?? null; newSettings.table_name = table_name; - newSettings.search_fields = search_fields; - newSettings.excluded_fields = excluded_fields; - newSettings.readonly_fields = readonly_fields; - newSettings.sortable_by = sortable_by; - newSettings.autocomplete_columns = autocomplete_columns; - newSettings.custom_fields = custom_fields; - newSettings.table_widgets = table_widgets; - newSettings.identification_fields = identification_fields; - newSettings.sensitive_fields = sensitive_fields; - newSettings.identity_column = identity_column; - newSettings.table_actions = table_actions; - newSettings.can_add = can_add; - newSettings.can_delete = can_delete; - newSettings.can_update = can_update; - newSettings.icon = icon; + newSettings.search_fields = search_fields ?? []; + newSettings.excluded_fields = excluded_fields ?? []; + newSettings.readonly_fields = readonly_fields ?? []; + newSettings.sortable_by = sortable_by ?? []; + newSettings.autocomplete_columns = autocomplete_columns ?? []; + newSettings.custom_fields = custom_fields ?? []; + newSettings.table_widgets = table_widgets ?? []; + newSettings.identification_fields = identification_fields ?? []; + newSettings.sensitive_fields = sensitive_fields ?? null; + newSettings.identity_column = identity_column ?? null; + newSettings.table_actions = table_actions ?? []; + newSettings.can_add = can_add ?? true; + newSettings.can_delete = can_delete ?? true; + newSettings.can_update = can_update ?? true; + newSettings.icon = icon ?? null; newSettings.allow_csv_export = allow_csv_export; newSettings.allow_csv_import = allow_csv_import; - newSettings.list_per_page = list_per_page; - newSettings.list_fields = list_fields; + newSettings.list_per_page = list_per_page ?? null; + newSettings.list_fields = list_fields ?? []; newSettings.ordering = ordering as QueryOrderingEnum; - newSettings.ordering_field = ordering_field; - newSettings.columns_view = columns_view; + newSettings.ordering_field = ordering_field ?? null; + newSettings.columns_view = columns_view ?? null; return newSettings; } diff --git a/backend/src/entities/table-settings/personal-table-settings/use-cases/create-update-personal-table-settings.use.case.ts b/backend/src/entities/table-settings/personal-table-settings/use-cases/create-update-personal-table-settings.use.case.ts index 69d551a0a..966d08c19 100644 --- a/backend/src/entities/table-settings/personal-table-settings/use-cases/create-update-personal-table-settings.use.case.ts +++ b/backend/src/entities/table-settings/personal-table-settings/use-cases/create-update-personal-table-settings.use.case.ts @@ -1,8 +1,9 @@ -import { BadRequestException, Inject, Injectable, Scope } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; 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 { Messages } from '../../../../exceptions/text/messages.js'; import { ConnectionEntity } from '../../../connection/connection.entity.js'; import { CreatePersonalTableSettingsDs, @@ -36,6 +37,9 @@ export class CreateUpdatePersonalTableSettingsUseCase connection_id, master_password, ); + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } await this.validatePersonalTableSettingsData(table_settings_data, foundConnection, table_name); @@ -82,7 +86,7 @@ export class CreateUpdatePersonalTableSettingsUseCase ): Promise { const { columns_view, list_fields, list_per_page, ordering, ordering_field } = settingsData; const dao = getDataAccessObject(connection); - const tableStructure = await dao.getTableStructure(tableName, null); + const tableStructure = await dao.getTableStructure(tableName, ''); const tableColumnNames = tableStructure.map((col) => col.column_name); const errors = []; diff --git a/backend/src/entities/table/application/data-structures/found-table-rows.ds.ts b/backend/src/entities/table/application/data-structures/found-table-rows.ds.ts index d9a249dde..302753fb2 100644 --- a/backend/src/entities/table/application/data-structures/found-table-rows.ds.ts +++ b/backend/src/entities/table/application/data-structures/found-table-rows.ds.ts @@ -15,13 +15,13 @@ export class TableSettingsInRowsDS { sortable_by: Array; @ApiProperty({ enum: QueryOrderingEnum }) - ordering: QueryOrderingEnum; + ordering?: QueryOrderingEnum; @ApiProperty() - ordering_field: string; + ordering_field?: string; @ApiProperty() - identity_column: string; + identity_column: string | null; @ApiProperty({ isArray: true }) list_fields: Array; @@ -58,13 +58,13 @@ export class FoundTableRowsDs { sortable_by: Array; @ApiProperty() - ordering_field: string; + ordering_field?: string; @ApiProperty({ enum: QueryOrderingEnum }) - ordering: QueryOrderingEnum; + ordering?: QueryOrderingEnum; @ApiProperty({ isArray: true }) - columns_view: Array; + columns_view?: Array; @ApiProperty({ isArray: true, type: FullTableStructureDs }) structure: Array; @@ -79,7 +79,7 @@ export class FoundTableRowsDs { widgets: Array; @ApiProperty() - identity_column: string; + identity_column: string | null; @ApiProperty() table_permissions: TablePermissionDs; diff --git a/backend/src/entities/table/application/data-structures/found-table-structure.ds.ts b/backend/src/entities/table/application/data-structures/found-table-structure.ds.ts index 49ba607a5..a0de5d9c9 100644 --- a/backend/src/entities/table/application/data-structures/found-table-structure.ds.ts +++ b/backend/src/entities/table/application/data-structures/found-table-structure.ds.ts @@ -5,13 +5,13 @@ export class FullTableStructureDs { column_name: string; @ApiProperty() - column_default: string | number; + column_default: string | number | null; @ApiProperty() data_type: string; @ApiProperty() - data_type_params: string; + data_type_params?: string; @ApiProperty() isExcluded: boolean; @@ -26,5 +26,5 @@ export class FullTableStructureDs { allow_null: boolean; @ApiProperty() - character_maximum_length: number; + character_maximum_length: number | null; } diff --git a/backend/src/entities/table/application/data-structures/found-table.ds.ts b/backend/src/entities/table/application/data-structures/found-table.ds.ts index 8a3f9ca26..7e8a47c11 100644 --- a/backend/src/entities/table/application/data-structures/found-table.ds.ts +++ b/backend/src/entities/table/application/data-structures/found-table.ds.ts @@ -4,7 +4,7 @@ import { FoundTableCategoryRo } from '../../../table-categories/dto/found-table- export class FoundTableDs { @ApiProperty() - display_name?: string; + display_name?: string | null; @ApiProperty() table: string; @@ -13,7 +13,7 @@ export class FoundTableDs { isView: boolean; @ApiProperty() - icon: string; + icon?: string | null; @ApiProperty() permissions: TableAccessLevelsDs; diff --git a/backend/src/entities/table/table-datastructures.ts b/backend/src/entities/table/table-datastructures.ts index a0b0600af..d6f9f37a0 100644 --- a/backend/src/entities/table/table-datastructures.ts +++ b/backend/src/entities/table/table-datastructures.ts @@ -2,6 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { FilterCriteriaEnum } from '../../enums/filter-criteria.enum.js'; import { TableAccessLevelsDs } from '../permission/application/data-structures/create-permissions.ds.js'; import { FoundActionEventDTO } from '../table-actions/table-action-rules-module/application/dto/found-action-rules-with-actions-and-events.dto.js'; +import { TableWidgetEntity } from '../widget/table-widget.entity.js'; import { TableWidgetRO } from '../widget/table-widget.interface.js'; import { TableSettingsInRowsDS } from './application/data-structures/found-table-rows.ds.js'; @@ -95,7 +96,7 @@ export class TableStructureDs { list_fields?: Array; @ApiProperty() - display_name: string; + display_name: string | null; @ApiProperty({ isArray: true }) excluded_fields: Array; @@ -106,7 +107,7 @@ export class ReferencedByTableInfoDs { table_name: string; @ApiProperty() - display_name: string; + display_name: string | null; @ApiProperty() column_name: string; @@ -136,8 +137,8 @@ export class TableRowRODs { @ApiProperty({ isArray: true }) readonly_fields: Array; - @ApiProperty({ isArray: true, type: TableWidgetRO }) - table_widgets: Array; + @ApiProperty({ isArray: true, type: TableWidgetEntity }) + table_widgets: Array; @ApiProperty({ isArray: true }) list_fields: Array; @@ -152,10 +153,10 @@ export class TableRowRODs { table_actions?: Array; @ApiProperty() - identity_column: string; + identity_column: string | null; @ApiProperty() - display_name: string; + display_name: string | null; @ApiProperty() table_access_level?: TableAccessLevelsDs; diff --git a/backend/src/entities/table/table.controller.ts b/backend/src/entities/table/table.controller.ts index 1748502b7..03c1e5bbd 100644 --- a/backend/src/entities/table/table.controller.ts +++ b/backend/src/entities/table/table.controller.ts @@ -219,8 +219,8 @@ export class TableController { HttpStatus.BAD_REQUEST, ); } - let parsedPage: number; - let parsedPerPage: number; + let parsedPage = 0; + let parsedPerPage = 0; if (page && perPage) { parsedPage = parseInt(page, 10); parsedPerPage = parseInt(perPage, 10); @@ -292,8 +292,8 @@ export class TableController { HttpStatus.BAD_REQUEST, ); } - let parsedPage: number; - let parsedPerPage: number; + let parsedPage = 0; + let parsedPerPage = 0; if (page && perPage) { parsedPage = parseInt(page, 10); parsedPerPage = parseInt(perPage, 10); @@ -743,8 +743,8 @@ export class TableController { HttpStatus.BAD_REQUEST, ); } - let parsedPage: number; - let parsedPerPage: number; + let parsedPage = 0; + let parsedPerPage = 0; if (page && perPage) { parsedPage = parseInt(page, 10); parsedPerPage = parseInt(perPage, 10); @@ -834,9 +834,17 @@ export class TableController { ): Promise>> { const primaryKeys = []; const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); - let userEmail: string; + if (!connection) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.BAD_REQUEST, + ); + } + let userEmail = ''; if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); + userEmail = (await this._dbContext.userRepository.getUserEmailOrReturnNull(userId)) ?? ''; } const dao = getDataAccessObject(connection); if (uncached) { diff --git a/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts b/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts index 1a8a0ff2c..f2d291987 100644 --- a/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts +++ b/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts @@ -21,6 +21,7 @@ import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { AddRowInTableDs } from '../application/data-structures/add-row-in-table.ds.js'; import { ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js'; import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js'; +import { buildCommonTableSettingsInput } from '../utils/build-common-table-settings-input.util.js'; import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js'; import { convertHexDataInRowUtil } from '../utils/convert-hex-data-in-row.util.js'; import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js'; @@ -162,8 +163,11 @@ export class AddRowInTableUseCase extends AbstractUseCase = {}; - let addedRowPrimaryKey: Record; - const builtDAOsTableSettings = buildDAOsTableSettingsDs(tableSettings, personalTableSettings); + let addedRowPrimaryKey: Record = {}; + const builtDAOsTableSettings = buildDAOsTableSettingsDs( + buildCommonTableSettingsInput(tableSettings), + personalTableSettings, + ); try { row = await hashPasswordsInRowUtil(row, tableWidgets); row = processUuidsInRowUtil(row, tableWidgets); @@ -181,7 +185,10 @@ export class AddRowInTableUseCase extends AbstractUseCase 0 ? personalTableSettings.list_fields : [], + list_fields: + personalTableSettings?.list_fields && personalTableSettings.list_fields.length > 0 + ? personalTableSettings.list_fields + : [], identity_column: tableSettings?.identity_column ? tableSettings.identity_column : null, referenced_table_names_and_columns: referencedTableNamesAndColumnsWithTablesDisplayNames, excluded_fields: tableSettings?.excluded_fields ? tableSettings.excluded_fields : [], @@ -233,5 +240,11 @@ export class AddRowInTableUseCase extends AbstractUseCase> = await Promise.all( primaryKeys.map((primaryKey) => dao.getRowByPrimaryKey(tableName, primaryKey, builtDAOsTableSettings, userEmail)), ); diff --git a/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts b/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts index c1280b9d0..e9c5beddf 100644 --- a/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts +++ b/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts @@ -20,6 +20,7 @@ import { TableActionActivationService } from '../../table-actions/table-actions- import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { DeleteRowFromTableDs } from '../application/data-structures/delete-row-from-table.ds.js'; import { DeletedRowFromTableDs } from '../application/data-structures/deleted-row-from-table.ds.js'; +import { buildCommonTableSettingsInput } from '../utils/build-common-table-settings-input.util.js'; import { convertHexDataInPrimaryKeyUtil } from '../utils/convert-hex-data-in-primary-key.util.js'; import { getUserEmailForAgent, validateConnection } from '../utils/validate-connection.util.js'; import { IDeleteRowFromTable } from './table-use-cases.interface.js'; @@ -114,7 +115,10 @@ export class DeleteRowFromTableUseCase tableName, ); - const builtTableSettings = buildDAOsTableSettingsDs(tableSettings, personalTableSettings); + const builtTableSettings = buildDAOsTableSettingsDs( + buildCommonTableSettingsInput(tableSettings), + personalTableSettings, + ); let oldRowData: Record; try { diff --git a/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts b/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts index b1fd27fae..80d3a3803 100644 --- a/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts +++ b/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts @@ -14,6 +14,7 @@ import { AmplitudeService } from '../../amplitude/amplitude.service.js'; import { isTestConnectionUtil } from '../../connection/utils/is-test-connection-util.js'; import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { DeleteRowsFromTableDs } from '../application/data-structures/delete-row-from-table.ds.js'; +import { buildCommonTableSettingsInput } from '../utils/build-common-table-settings-input.util.js'; import { convertHexDataInPrimaryKeyUtil } from '../utils/convert-hex-data-in-primary-key.util.js'; import { findObjectsWithProperties } from '../utils/find-objects-with-properties.js'; import { getUserEmailForAgent, validateConnection } from '../utils/validate-connection.util.js'; @@ -23,7 +24,7 @@ type DeleteRowsFromTableResult = { operationStatusResult: OperationResultStatusEnum; row: Record; old_data: Record; - error: string; + error: string | null; affected_primary_key: string; }; @@ -107,7 +108,10 @@ export class DeleteRowsFromTableUseCase } }); let oldRowsData: Array>; - const builtDAOsTableSettings = buildDAOsTableSettingsDs(tableSettings, personalTableSettings); + const builtDAOsTableSettings = buildDAOsTableSettingsDs( + buildCommonTableSettingsInput(tableSettings), + personalTableSettings, + ); try { oldRowsData = await dao.bulkGetRowsFromTableByPrimaryKeys( tableName, @@ -132,7 +136,7 @@ export class DeleteRowsFromTableUseCase deleteOperationsResults.push({ operationStatusResult: OperationResultStatusEnum.successfully, row: primaryKey, - old_data: findObjectsWithProperties(oldRowsData, primaryKey).at(0), + old_data: findObjectsWithProperties(oldRowsData, primaryKey).at(0) ?? {}, error: null, affected_primary_key: primaryKey as unknown as string, }); @@ -143,7 +147,7 @@ export class DeleteRowsFromTableUseCase deleteOperationsResults.push({ operationStatusResult: OperationResultStatusEnum.unsuccessfully, row: primaryKey, - old_data: findObjectsWithProperties(oldRowsData, primaryKey).at(0), + old_data: findObjectsWithProperties(oldRowsData, primaryKey).at(0) ?? {}, error: getErrorMessage(error), affected_primary_key: primaryKey as unknown as string, }); @@ -161,7 +165,7 @@ export class DeleteRowsFromTableUseCase await this.amplitudeService.formAndSendLogRecord( isTest ? AmplitudeEventTypeEnum.tableRowDeletedTest : AmplitudeEventTypeEnum.tableRowDeleted, userId, - { operationCount: createdLogs.length }, + { operationCount: createdLogs?.length ?? 0 }, ); } } diff --git a/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts b/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts index 1a3e0fb49..fc63534a3 100644 --- a/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts +++ b/backend/src/entities/table/use-cases/export-csv-from-table.use.case.ts @@ -16,6 +16,7 @@ import { slackPostMessage } from '../../../helpers/slack/slack-post-message.js'; import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { GetTableRowsDs } from '../application/data-structures/get-table-rows.ds.js'; import { FilteringFieldsDs } from '../table-datastructures.js'; +import { buildCommonTableSettingsInput } from '../utils/build-common-table-settings-input.util.js'; import { findFilteringFieldsUtil, parseFilteringFieldsFromBodyData } from '../utils/find-filtering-fields.util.js'; import { findOrderingFieldUtil } from '../utils/find-ordering-field.util.js'; import { isHexString } from '../utils/is-hex-string.js'; @@ -69,11 +70,14 @@ export class ExportCSVFromTableUseCase const filteringFields: Array = isObjectEmpty(filters) ? findFilteringFieldsUtil(query, tableStructure) - : parseFilteringFieldsFromBodyData(filters, tableStructure); + : parseFilteringFieldsFromBodyData(filters ?? {}, tableStructure); const orderingField = findOrderingFieldUtil(query, tableStructure, tableSettings); - const builtDAOsTableSettings = buildDAOsTableSettingsDs(tableSettings, personalTableSettings); + const builtDAOsTableSettings = buildDAOsTableSettingsDs( + buildCommonTableSettingsInput(tableSettings), + personalTableSettings, + ); if (orderingField) { builtDAOsTableSettings.ordering_field = orderingField.field; diff --git a/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts b/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts index 5252217b5..79f98cd2c 100644 --- a/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts +++ b/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts @@ -41,7 +41,7 @@ export class FindTablesInConnectionV2UseCase protected async implementation(inputData: FindTablesDs): Promise { const { connectionId, hiddenTablesOption, masterPwd, userId } = inputData; - let connection: ConnectionEntity; + let connection: ConnectionEntity | null = null; try { connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); } catch (error) { @@ -74,12 +74,12 @@ export class FindTablesInConnectionV2UseCase ); } const dao = getDataAccessObject(connection); - let userEmail: string; + let userEmail = ''; let operationResult = false; if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); + userEmail = (await this._dbContext.userRepository.getUserEmailOrReturnNull(userId)) ?? ''; } - let tables: Array; + let tables: Array = []; try { tables = await dao.getTablesFromDB(userEmail); operationResult = true; @@ -119,10 +119,11 @@ export class FindTablesInConnectionV2UseCase await this._dbContext.connectionPropertiesRepository.findConnectionPropertiesWithTablesCategories(connectionId); const tableSettings = await this._dbContext.tableSettingsRepository.findTableSettingsInConnectionPure(connectionId); let tablesRO = addDisplayNamesForTables(tableSettings, tablesWithPermissions); - if (foundConnectionProperties && foundConnectionProperties.hidden_tables.length > 0) { + if (foundConnectionProperties?.hidden_tables && foundConnectionProperties.hidden_tables.length > 0) { + const hiddenTables = foundConnectionProperties.hidden_tables; if (!hiddenTablesOption) { tablesRO = tablesRO.filter((tableRO) => { - return !foundConnectionProperties.hidden_tables.includes(tableRO.table); + return !hiddenTables.includes(tableRO.table); }); } else { const userConnectionEdit = await this.cedarPermissions.checkUserConnectionEdit(userId, connectionId); diff --git a/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts b/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts index 8ad454bbd..25634cc15 100644 --- a/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts +++ b/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts @@ -42,7 +42,7 @@ export class FindTablesInConnectionUseCase protected async implementation(inputData: FindTablesDs): Promise> { const { connectionId, hiddenTablesOption, masterPwd, userId } = inputData; - let connection: ConnectionEntity; + let connection: ConnectionEntity | null = null; try { connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); } catch (error) { @@ -75,15 +75,15 @@ export class FindTablesInConnectionUseCase ); } const dao = getDataAccessObject(connection); - let userEmail: string; + let userEmail = ''; let operationResult = false; if (isConnectionTypeAgent(connection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); + userEmail = (await this._dbContext.userRepository.getUserEmailOrReturnNull(userId)) ?? ''; } await validateSchemaCache(dao, userEmail); - let tables: Array; + let tables: Array = []; try { tables = await dao.getTablesFromDB(userEmail); operationResult = true; @@ -123,9 +123,10 @@ export class FindTablesInConnectionUseCase const tableSettings = await this._dbContext.tableSettingsRepository.findTableSettingsInConnectionPure(connectionId); let tablesRO = addDisplayNamesForTables(tableSettings, tablesWithPermissions); if (excludedTables?.hidden_tables?.length) { + const hiddenTables = excludedTables.hidden_tables; if (!hiddenTablesOption) { tablesRO = tablesRO.filter((tableRO) => { - return !excludedTables.hidden_tables.includes(tableRO.table); + return !hiddenTables.includes(tableRO.table); }); } else { const userConnectionEdit = await this.cedarPermissions.checkUserConnectionEdit(userId, connectionId); diff --git a/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts b/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts index 382bdc6a8..2b1c2d356 100644 --- a/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts +++ b/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts @@ -17,6 +17,7 @@ import { buildActionEventDto } from '../../table-actions/table-action-rules-modu import { GetRowByPrimaryKeyDs } from '../application/data-structures/get-row-by-primary-key.ds.js'; import { ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js'; import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js'; +import { buildCommonTableSettingsInput } from '../utils/build-common-table-settings-input.util.js'; import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js'; import { convertHexDataInPrimaryKeyUtil } from '../utils/convert-hex-data-in-primary-key.util.js'; import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js'; @@ -131,7 +132,10 @@ export class GetRowByPrimaryKeyUseCase ); } let rowData: Record; - const builtDAOsTableSettings = buildDAOsTableSettingsDs(tableSettings, personalTableSettings); + const builtDAOsTableSettings = buildDAOsTableSettingsDs( + buildCommonTableSettingsInput(tableSettings), + personalTableSettings, + ); try { rowData = await dao.getRowByPrimaryKey(tableName, primaryKey, builtDAOsTableSettings, userEmail); } catch (e) { diff --git a/backend/src/entities/table/use-cases/get-table-rows.use.case.ts b/backend/src/entities/table/use-cases/get-table-rows.use.case.ts index e01ed4fef..f81e42fcc 100644 --- a/backend/src/entities/table/use-cases/get-table-rows.use.case.ts +++ b/backend/src/entities/table/use-cases/get-table-rows.use.case.ts @@ -30,10 +30,11 @@ import { buildCreatedTableFilterRO } from '../../table-filters/utils/build-creat import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { TableSettingsEntity } from '../../table-settings/common-table-settings/table-settings.entity.js'; import { PersonalTableSettingsEntity } from '../../table-settings/personal-table-settings/personal-table-settings.entity.js'; -import { FoundTableRowsDs } from '../application/data-structures/found-table-rows.ds.js'; +import { AutocompleteFieldsDs, FoundTableRowsDs } from '../application/data-structures/found-table-rows.ds.js'; import { GetTableRowsDs } from '../application/data-structures/get-table-rows.ds.js'; import { FilteringFieldsDs } from '../table-datastructures.js'; import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js'; +import { buildCommonTableSettingsInput } from '../utils/build-common-table-settings-input.util.js'; import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js'; import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js'; import { filterForeignKeysByReadPermission } from '../utils/filter-foreign-keys-by-permission.util.js'; @@ -108,7 +109,7 @@ export class GetTableRowsUseCase extends AbstractUseCase = isObjectEmpty(filters) ? findFilteringFieldsUtil(query, tableStructure) - : parseFilteringFieldsFromBodyData(filters, tableStructure); + : parseFilteringFieldsFromBodyData(filters ?? {}, tableStructure); const orderingField = findOrderingFieldUtil(query, tableStructure, tableSettings); @@ -126,12 +127,15 @@ export class GetTableRowsUseCase extends AbstractUseCase { const foreignTableSettings = foreignTableSettingsMap.get(foreignKey.referenced_table_name); - const builtDAOsForeignTableSettings = buildDAOsTableSettingsDs(foreignTableSettings, {} as any); + const builtDAOsForeignTableSettings = buildDAOsTableSettingsDs( + buildCommonTableSettingsInput(foreignTableSettings), + null, + ); const identityColumns = await this.getBatchedIdentityColumns( Array.from(values), foreignKey, diff --git a/backend/src/entities/table/use-cases/update-row-in-table.use.case.ts b/backend/src/entities/table/use-cases/update-row-in-table.use.case.ts index 17b3ef5fe..62b52fcb4 100644 --- a/backend/src/entities/table/use-cases/update-row-in-table.use.case.ts +++ b/backend/src/entities/table/use-cases/update-row-in-table.use.case.ts @@ -25,6 +25,7 @@ import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { UpdateRowInTableDs } from '../application/data-structures/update-row-in-table.ds.js'; import { ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js'; import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js'; +import { buildCommonTableSettingsInput } from '../utils/build-common-table-settings-input.util.js'; import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js'; import { convertHexDataInRowUtil } from '../utils/convert-hex-data-in-row.util.js'; import { extractForeignKeysFromWidgets } from '../utils/extract-foreign-keys-from-widgets.util.js'; @@ -103,7 +104,10 @@ export class UpdateRowInTableUseCase dao.getReferencedTableNamesAndColumns(tableName, userEmail), ]); - const builtDAOsTableSettings = buildDAOsTableSettingsDs(tableSettings, personalTableSettings); + const builtDAOsTableSettings = buildDAOsTableSettingsDs( + buildCommonTableSettingsInput(tableSettings), + personalTableSettings, + ); await filterReferencedTablesByPermission( referencedTableNamesAndColumns, @@ -228,7 +232,10 @@ export class UpdateRowInTableUseCase table_widgets: tableWidgets, display_name: tableSettings?.display_name ? tableSettings.display_name : null, readonly_fields: tableSettings?.readonly_fields ? tableSettings.readonly_fields : [], - list_fields: personalTableSettings?.list_fields?.length > 0 ? personalTableSettings.list_fields : [], + list_fields: + personalTableSettings?.list_fields && personalTableSettings.list_fields.length > 0 + ? personalTableSettings.list_fields + : [], identity_column: tableSettings?.identity_column ? tableSettings.identity_column : null, referenced_table_names_and_columns: referencedTableNamesAndColumnsWithTablesDisplayNames, excluded_fields: tableSettings?.excluded_fields ? tableSettings.excluded_fields : [], diff --git a/backend/src/entities/table/utils/attach-foreign-column-names.util.ts b/backend/src/entities/table/utils/attach-foreign-column-names.util.ts index 59aa59846..d9fd865a2 100644 --- a/backend/src/entities/table/utils/attach-foreign-column-names.util.ts +++ b/backend/src/entities/table/utils/attach-foreign-column-names.util.ts @@ -20,8 +20,9 @@ export async function attachForeignColumnNames( ]); let columnNames = foreignTableStructure.map((el) => el.column_name); - if (foreignTableSettings?.autocomplete_columns?.length > 0) { - columnNames = columnNames.filter((el) => foreignTableSettings.autocomplete_columns.includes(el)); + const autocompleteColumns = foreignTableSettings?.autocomplete_columns; + if (autocompleteColumns && autocompleteColumns.length > 0) { + columnNames = columnNames.filter((el) => autocompleteColumns.includes(el)); } return { diff --git a/backend/src/entities/table/utils/build-common-table-settings-input.util.ts b/backend/src/entities/table/utils/build-common-table-settings-input.util.ts new file mode 100644 index 000000000..dd3d9e3ff --- /dev/null +++ b/backend/src/entities/table/utils/build-common-table-settings-input.util.ts @@ -0,0 +1,51 @@ +import { QueryOrderingEnum } from '../../../enums/query-ordering.enum.js'; +import { TableSettingsEntity } from '../../table-settings/common-table-settings/table-settings.entity.js'; + +export type CommonTableSettingsInput = { + table_name: string; + display_name: string; + search_fields: string[]; + excluded_fields: string[]; + identification_fields: string[]; + identity_column: string; + readonly_fields: string[]; + sortable_by: string[]; + autocomplete_columns: string[]; + columns_view: string[]; + can_delete: boolean; + can_update: boolean; + can_add: boolean; + sensitive_fields: string[]; + ordering: QueryOrderingEnum; + ordering_field: string; + list_per_page: number; + list_fields: string[]; +}; + +export function buildCommonTableSettingsInput( + tableSettings: TableSettingsEntity | null | undefined, +): CommonTableSettingsInput | null { + if (!tableSettings) { + return null; + } + return { + table_name: tableSettings.table_name, + display_name: tableSettings.display_name ?? '', + search_fields: tableSettings.search_fields ?? [], + excluded_fields: tableSettings.excluded_fields ?? [], + identification_fields: tableSettings.identification_fields ?? [], + identity_column: tableSettings.identity_column ?? '', + readonly_fields: tableSettings.readonly_fields ?? [], + sortable_by: tableSettings.sortable_by ?? [], + autocomplete_columns: tableSettings.autocomplete_columns ?? [], + columns_view: tableSettings.columns_view ?? [], + can_delete: tableSettings.can_delete, + can_update: tableSettings.can_update, + can_add: tableSettings.can_add, + sensitive_fields: tableSettings.sensitive_fields ?? [], + ordering: tableSettings.ordering, + ordering_field: tableSettings.ordering_field ?? '', + list_per_page: tableSettings.list_per_page ?? 0, + list_fields: tableSettings.list_fields ?? [], + }; +} diff --git a/backend/src/entities/table/utils/form-full-table-structure.ts b/backend/src/entities/table/utils/form-full-table-structure.ts index 79b19d1da..cd4768856 100644 --- a/backend/src/entities/table/utils/form-full-table-structure.ts +++ b/backend/src/entities/table/utils/form-full-table-structure.ts @@ -17,7 +17,7 @@ export function formFullTableStructure( data_type_params: data_type_params || undefined, isExcluded: excluded_fields?.includes(column_name) || false, isSearched: search_fields?.includes(column_name) || false, - auto_increment: checkFieldAutoincrement(column_default, extra), + auto_increment: checkFieldAutoincrement(column_default ?? '', extra), allow_null, character_maximum_length, }), diff --git a/backend/src/entities/table/utils/hash-passwords-in-row.util.ts b/backend/src/entities/table/utils/hash-passwords-in-row.util.ts index 6679497de..069b867ac 100644 --- a/backend/src/entities/table/utils/hash-passwords-in-row.util.ts +++ b/backend/src/entities/table/utils/hash-passwords-in-row.util.ts @@ -13,6 +13,9 @@ export async function hashPasswordsInRowUtil( for (const widget of passwordWidgets) { try { + if (!widget.widget_params) { + continue; + } const widgetParams = JSON5.parse(widget.widget_params) as unknown as IPasswordWidgetParams; const fieldValue = row[widget.field_name]; diff --git a/backend/src/entities/table/utils/process-referenced-tables.util.ts b/backend/src/entities/table/utils/process-referenced-tables.util.ts index 2c9010a01..a38dcf95e 100644 --- a/backend/src/entities/table/utils/process-referenced-tables.util.ts +++ b/backend/src/entities/table/utils/process-referenced-tables.util.ts @@ -23,7 +23,7 @@ export async function filterReferencedTablesByPermission( return canRead ? ref : null; }), ) - ).filter(Boolean); + ).filter((ref): ref is { table_name: string; column_name: string } => ref !== null); }), ); } @@ -31,7 +31,7 @@ export async function filterReferencedTablesByPermission( export async function enrichReferencedTablesWithDisplayNames( referencedTables: Array, connectionId: string, - findTableSettings: (connectionId: string, tableName: string) => Promise<{ display_name?: string } | null>, + findTableSettings: (connectionId: string, tableName: string) => Promise<{ display_name?: string | null } | null>, ): Promise> { const allTableNames = new Set(); for (const rt of referencedTables) { diff --git a/backend/src/entities/table/utils/process-uuids-in-row-util.ts b/backend/src/entities/table/utils/process-uuids-in-row-util.ts index cd32306b2..9f04c3230 100644 --- a/backend/src/entities/table/utils/process-uuids-in-row-util.ts +++ b/backend/src/entities/table/utils/process-uuids-in-row-util.ts @@ -11,6 +11,9 @@ export function processUuidsInRowUtil( for (const widget of uuidWidgets) { try { + if (!widget.widget_params) { + continue; + } const widgetParams = JSON5.parse(widget.widget_params); if (row[widget.field_name] && Buffer.isBuffer(widgetParams)) { row[widget.field_name] = uuidStringify(widgetParams); diff --git a/backend/src/entities/table/utils/save-table-info-in-database-orchestrator.util.ts b/backend/src/entities/table/utils/save-table-info-in-database-orchestrator.util.ts index e90093444..ec5e36a51 100644 --- a/backend/src/entities/table/utils/save-table-info-in-database-orchestrator.util.ts +++ b/backend/src/entities/table/utils/save-table-info-in-database-orchestrator.util.ts @@ -18,10 +18,10 @@ export async function saveTableInfoInDatabase( if (!foundConnection) { return; } - const decryptedConnection = await dbContext.connectionRepository.findAndDecryptConnection( - connectionId, - masterPwd, - ); + const decryptedConnection = await dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); + if (!decryptedConnection) { + return; + } const tableNames: Array = tables.map((table) => table.tableName); const queue = new PQueue({ concurrency: 2 }); const dao = getDataAccessObject(decryptedConnection); @@ -31,7 +31,7 @@ export async function saveTableInfoInDatabase( }> = (await Promise.all( tableNames.map(async (tableName) => { return await queue.add(async () => { - const structure = await dao.getTableStructure(tableName, undefined); + const structure = await dao.getTableStructure(tableName, ''); return { tableName: tableName, structure: structure, diff --git a/backend/src/entities/table/utils/save-tables-info-in-database.util.ts b/backend/src/entities/table/utils/save-tables-info-in-database.util.ts index da76184a1..7137dee6a 100644 --- a/backend/src/entities/table/utils/save-tables-info-in-database.util.ts +++ b/backend/src/entities/table/utils/save-tables-info-in-database.util.ts @@ -14,7 +14,7 @@ export function buildTableFieldInfoEntity( newTableFieldInfoEntity.character_maximum_length = structure.character_maximum_length.toString(); } newTableFieldInfoEntity.allow_null = structure.allow_null; - newTableFieldInfoEntity.column_default = structure.column_default; + newTableFieldInfoEntity.column_default = structure.column_default ?? ''; newTableFieldInfoEntity.data_type_params = structure.data_type_params; newTableFieldInfoEntity.udt_name = structure.udt_name; newTableFieldInfoEntity.table_info = tableInfo; diff --git a/backend/src/entities/table/utils/validate-connection.util.ts b/backend/src/entities/table/utils/validate-connection.util.ts index 9fe935a85..49307d120 100644 --- a/backend/src/entities/table/utils/validate-connection.util.ts +++ b/backend/src/entities/table/utils/validate-connection.util.ts @@ -21,10 +21,10 @@ export function validateConnection(connection: ConnectionEntity | null): asserts export async function getUserEmailForAgent( connection: ConnectionEntity, userId: string, - userRepository: { getUserEmailOrReturnNull(userId: string): Promise }, -): Promise { + userRepository: { getUserEmailOrReturnNull(userId: string): Promise }, +): Promise { if (isConnectionTypeAgent(connection.type)) { - return userRepository.getUserEmailOrReturnNull(userId); + return (await userRepository.getUserEmailOrReturnNull(userId)) ?? ''; } - return undefined; + return ''; } diff --git a/backend/src/entities/table/utils/validate-table-row.util.ts b/backend/src/entities/table/utils/validate-table-row.util.ts index ff3f7bc84..e3945ce61 100644 --- a/backend/src/entities/table/utils/validate-table-row.util.ts +++ b/backend/src/entities/table/utils/validate-table-row.util.ts @@ -7,10 +7,13 @@ export function validateTableRowUtil(row: Record, structure: Ar for (let i = 0; i < structure.length; i++) { try { - const index = keys.indexOf(structure.at(i).column_name); + const column = structure[i]; + const index = keys.indexOf(column.column_name); + const columnDefault = column.column_default; if ( - (index >= 0 && structure.at(i).column_default != null && structure.at(i).column_default.includes('nextval')) || - (index >= 0 && structure.at(i).column_default != null && structure.at(i).column_default.includes('generate')) + index >= 0 && + typeof columnDefault === 'string' && + (columnDefault.includes('nextval') || columnDefault.includes('generate')) ) { errors.push(Messages.CANNOT_ADD_AUTOGENERATED_VALUE); } diff --git a/backend/src/entities/user-actions/use-cases/check-users-actions-and-mailing-users.use.case.ts b/backend/src/entities/user-actions/use-cases/check-users-actions-and-mailing-users.use.case.ts index 846caf30e..124a99c43 100644 --- a/backend/src/entities/user-actions/use-cases/check-users-actions-and-mailing-users.use.case.ts +++ b/backend/src/entities/user-actions/use-cases/check-users-actions-and-mailing-users.use.case.ts @@ -65,11 +65,14 @@ export class CheckUsersActionsAndMailingUsersUseCase implements ICheckUsersActio } } const foundUser = await this.userRepository.findOne({ where: { id: u.id } }); + if (!foundUser) { + return; + } const newUserActionEntity = buildNewConnectionNotFinishedEmailSentAction(foundUser); await this.userActionRepository.save(newUserActionEntity); } - private async findUserActionWithoutSentMail(userId: string): Promise { + private async findUserActionWithoutSentMail(userId: string): Promise { const actionQb = this.userActionRepository .createQueryBuilder('user_action') .leftJoinAndSelect('user_action.user', 'user') diff --git a/backend/src/entities/user-actions/use-cases/check-users-logs-and-update-actions.use.case.ts b/backend/src/entities/user-actions/use-cases/check-users-logs-and-update-actions.use.case.ts index adbda134e..5eb541544 100644 --- a/backend/src/entities/user-actions/use-cases/check-users-logs-and-update-actions.use.case.ts +++ b/backend/src/entities/user-actions/use-cases/check-users-logs-and-update-actions.use.case.ts @@ -74,7 +74,7 @@ export class CheckUsersLogsAndUpdateActionsUseCase implements ICheckUsersLogsAnd return !!userLogsCount; } - private async findNonFinishedConnectionCreationUserAction(userId: string): Promise { + private async findNonFinishedConnectionCreationUserAction(userId: string): Promise { const actionQb = this.userActionRepository .createQueryBuilder('user_action') .leftJoin('user_action.user', 'user') diff --git a/backend/src/entities/user-secret/application/data-structures/created-secret.ds.ts b/backend/src/entities/user-secret/application/data-structures/created-secret.ds.ts index 1f194c072..12269dc64 100644 --- a/backend/src/entities/user-secret/application/data-structures/created-secret.ds.ts +++ b/backend/src/entities/user-secret/application/data-structures/created-secret.ds.ts @@ -4,7 +4,7 @@ export class CreatedSecretDS { companyId: string; createdAt: Date; updatedAt: Date; - lastAccessedAt?: Date; - expiresAt?: Date; + lastAccessedAt?: Date | null; + expiresAt?: Date | null; masterEncryption: boolean; } diff --git a/backend/src/entities/user-secret/application/data-structures/found-secret.ds.ts b/backend/src/entities/user-secret/application/data-structures/found-secret.ds.ts index b60557ad4..7607b6b96 100644 --- a/backend/src/entities/user-secret/application/data-structures/found-secret.ds.ts +++ b/backend/src/entities/user-secret/application/data-structures/found-secret.ds.ts @@ -4,7 +4,7 @@ export class FoundSecretDS { companyId: string; createdAt: Date; updatedAt: Date; - lastAccessedAt?: Date; - expiresAt?: Date; + lastAccessedAt?: Date | null; + expiresAt?: Date | null; masterEncryption: boolean; } diff --git a/backend/src/entities/user-secret/application/data-structures/get-secrets.ds.ts b/backend/src/entities/user-secret/application/data-structures/get-secrets.ds.ts index f1371af11..5c939fca1 100644 --- a/backend/src/entities/user-secret/application/data-structures/get-secrets.ds.ts +++ b/backend/src/entities/user-secret/application/data-structures/get-secrets.ds.ts @@ -13,8 +13,8 @@ export class SecretListItemDS { companyId: string; createdAt: Date; updatedAt: Date; - lastAccessedAt?: Date; - expiresAt?: Date; + lastAccessedAt?: Date | null; + expiresAt?: Date | null; masterEncryption: boolean; } diff --git a/backend/src/entities/user-secret/application/data-structures/update-secret.ds.ts b/backend/src/entities/user-secret/application/data-structures/update-secret.ds.ts index 62df0875f..04fce3140 100644 --- a/backend/src/entities/user-secret/application/data-structures/update-secret.ds.ts +++ b/backend/src/entities/user-secret/application/data-structures/update-secret.ds.ts @@ -12,7 +12,7 @@ export class UpdatedSecretDS { companyId: string; createdAt: Date; updatedAt: Date; - lastAccessedAt?: Date; - expiresAt?: Date; + lastAccessedAt?: Date | null; + expiresAt?: Date | null; masterEncryption: boolean; } diff --git a/backend/src/entities/user-secret/application/dto/found-secret.dto.ts b/backend/src/entities/user-secret/application/dto/found-secret.dto.ts index 3d4312a16..33a6a91ed 100644 --- a/backend/src/entities/user-secret/application/dto/found-secret.dto.ts +++ b/backend/src/entities/user-secret/application/dto/found-secret.dto.ts @@ -42,7 +42,7 @@ export class FoundSecretDto { description: 'Date when the secret was last accessed', example: '2025-01-25T09:15:00.000Z', }) - lastAccessedAt?: Date; + lastAccessedAt?: Date | null; @ApiProperty({ type: Date, @@ -50,7 +50,7 @@ export class FoundSecretDto { description: 'Date when the secret expires (null if no expiration)', example: '2025-12-31T23:59:59.000Z', }) - expiresAt?: Date; + expiresAt?: Date | null; @ApiProperty({ type: Boolean, diff --git a/backend/src/entities/user-secret/application/dto/secret-list.dto.ts b/backend/src/entities/user-secret/application/dto/secret-list.dto.ts index 671fe3c0b..5b361164c 100644 --- a/backend/src/entities/user-secret/application/dto/secret-list.dto.ts +++ b/backend/src/entities/user-secret/application/dto/secret-list.dto.ts @@ -43,7 +43,7 @@ export class SecretListItemDto { description: 'Date when the secret was last accessed', example: '2025-01-25T09:15:00.000Z', }) - lastAccessedAt?: Date; + lastAccessedAt?: Date | null; @ApiProperty({ type: Date, @@ -51,7 +51,7 @@ export class SecretListItemDto { description: 'Date when the secret expires (null if no expiration)', example: '2025-12-31T23:59:59.000Z', }) - expiresAt?: Date; + expiresAt?: Date | null; @ApiProperty({ type: Boolean, diff --git a/backend/src/entities/user-secret/use-cases/update-secret.use.case.ts b/backend/src/entities/user-secret/use-cases/update-secret.use.case.ts index b7293806c..cc5d9ecb6 100644 --- a/backend/src/entities/user-secret/use-cases/update-secret.use.case.ts +++ b/backend/src/entities/user-secret/use-cases/update-secret.use.case.ts @@ -45,6 +45,9 @@ export class UpdateSecretUseCase extends AbstractUseCase SecretAccessLogEntity, diff --git a/backend/src/entities/user-sign-in-audit/dto/create-sign-in-audit-record.ds.ts b/backend/src/entities/user-sign-in-audit/dto/create-sign-in-audit-record.ds.ts index 25013458d..69c4a1b12 100644 --- a/backend/src/entities/user-sign-in-audit/dto/create-sign-in-audit-record.ds.ts +++ b/backend/src/entities/user-sign-in-audit/dto/create-sign-in-audit-record.ds.ts @@ -3,7 +3,7 @@ import { SignInStatusEnum } from '../enums/sign-in-status.enum.js'; export class CreateSignInAuditRecordDs { email: string; - userId?: string; + userId?: string | null; status: SignInStatusEnum; signInMethod: SignInMethodEnum; ipAddress?: string; diff --git a/backend/src/entities/user-sign-in-audit/dto/found-sign-in-audit-record.ds.ts b/backend/src/entities/user-sign-in-audit/dto/found-sign-in-audit-record.ds.ts index 276c80fb7..5e7a8d05d 100644 --- a/backend/src/entities/user-sign-in-audit/dto/found-sign-in-audit-record.ds.ts +++ b/backend/src/entities/user-sign-in-audit/dto/found-sign-in-audit-record.ds.ts @@ -6,8 +6,8 @@ export class FoundSignInAuditRecordDs { @ApiProperty() id: string; - @ApiProperty() - email: string; + @ApiProperty({ nullable: true }) + email: string | null; @ApiProperty({ enum: SignInStatusEnum }) status: SignInStatusEnum; @@ -15,18 +15,18 @@ export class FoundSignInAuditRecordDs { @ApiProperty({ enum: SignInMethodEnum }) signInMethod: SignInMethodEnum; - @ApiProperty() - ipAddress: string; + @ApiProperty({ nullable: true }) + ipAddress: string | null; - @ApiProperty() - userAgent: string; + @ApiProperty({ nullable: true }) + userAgent: string | null; - @ApiProperty() - failureReason: string; + @ApiProperty({ nullable: true }) + failureReason: string | null; @ApiProperty() createdAt: Date; - @ApiProperty() - userId: string; + @ApiProperty({ nullable: true }) + userId: string | null; } diff --git a/backend/src/entities/user-sign-in-audit/repository/sign-in-audit-custom-repository-extension.ts b/backend/src/entities/user-sign-in-audit/repository/sign-in-audit-custom-repository-extension.ts index 118d91a07..1d3b1a4f6 100644 --- a/backend/src/entities/user-sign-in-audit/repository/sign-in-audit-custom-repository-extension.ts +++ b/backend/src/entities/user-sign-in-audit/repository/sign-in-audit-custom-repository-extension.ts @@ -12,7 +12,7 @@ export const signInAuditCustomRepositoryExtension: ISignInAuditRepository = { const { email, userId, status, signInMethod, ipAddress, userAgent, failureReason } = data; const newRecord = new SignInAuditEntity(); - newRecord.email = email?.toLowerCase(); + newRecord.email = email?.toLowerCase() ?? null; newRecord.status = status; newRecord.signInMethod = signInMethod; newRecord.ipAddress = ipAddress || null; diff --git a/backend/src/entities/user-sign-in-audit/repository/sign-in-audit-repository.interface.ts b/backend/src/entities/user-sign-in-audit/repository/sign-in-audit-repository.interface.ts index a3e6446d4..3c9d775a2 100644 --- a/backend/src/entities/user-sign-in-audit/repository/sign-in-audit-repository.interface.ts +++ b/backend/src/entities/user-sign-in-audit/repository/sign-in-audit-repository.interface.ts @@ -20,8 +20,8 @@ export interface IFindSignInAuditLogsOptions { order: QueryOrderingEnum; page: number; perPage: number; - dateFrom?: Date; - dateTo?: Date; + dateFrom?: Date | null; + dateTo?: Date | null; searchedEmail?: string; status?: SignInStatusEnum; signInMethod?: SignInMethodEnum; diff --git a/backend/src/entities/user-sign-in-audit/sign-in-audit.entity.ts b/backend/src/entities/user-sign-in-audit/sign-in-audit.entity.ts index 2ac04e000..7b1797d83 100644 --- a/backend/src/entities/user-sign-in-audit/sign-in-audit.entity.ts +++ b/backend/src/entities/user-sign-in-audit/sign-in-audit.entity.ts @@ -8,8 +8,8 @@ export class SignInAuditEntity { @PrimaryGeneratedColumn('uuid') id: string; - @Column({ default: null }) - email: string; + @Column({ type: 'varchar', default: null }) + email: string | null; @Column('enum', { nullable: false, @@ -25,14 +25,14 @@ export class SignInAuditEntity { }) signInMethod: SignInMethodEnum; - @Column({ default: null }) - ipAddress: string; + @Column({ type: 'varchar', default: null }) + ipAddress: string | null; - @Column({ default: null }) - userAgent: string; + @Column({ type: 'varchar', default: null }) + userAgent: string | null; - @Column({ default: null }) - failureReason: string; + @Column({ type: 'varchar', default: null }) + failureReason: string | null; @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) createdAt: Date; @@ -41,6 +41,6 @@ export class SignInAuditEntity { @JoinColumn({ name: 'userId' }) user: Relation; - @Column({ nullable: true }) - userId: string; + @Column({ type: 'uuid', nullable: true }) + userId: string | null; } diff --git a/backend/src/entities/user-sign-in-audit/sign-in-audit.service.ts b/backend/src/entities/user-sign-in-audit/sign-in-audit.service.ts index 4d0def328..fff3c4bad 100644 --- a/backend/src/entities/user-sign-in-audit/sign-in-audit.service.ts +++ b/backend/src/entities/user-sign-in-audit/sign-in-audit.service.ts @@ -20,7 +20,7 @@ export class SignInAuditService { const { email, userId, status, signInMethod, ipAddress, userAgent, failureReason } = data; const newRecord = new SignInAuditEntity(); - newRecord.email = email?.toLowerCase(); + newRecord.email = email?.toLowerCase() ?? null; newRecord.status = status; newRecord.signInMethod = signInMethod; newRecord.ipAddress = ipAddress || null; diff --git a/backend/src/entities/user-sign-in-audit/use-cases/find-sign-in-audit-logs.use.case.ts b/backend/src/entities/user-sign-in-audit/use-cases/find-sign-in-audit-logs.use.case.ts index 54b7996f8..91838cf56 100644 --- a/backend/src/entities/user-sign-in-audit/use-cases/find-sign-in-audit-logs.use.case.ts +++ b/backend/src/entities/user-sign-in-audit/use-cases/find-sign-in-audit-logs.use.case.ts @@ -85,8 +85,8 @@ export class FindSignInAuditLogsUseCase perPage = Constants.DEFAULT_LOG_ROWS_LIMIT; } - let searchedDateFrom: Date = null; - let searchedDateTo: Date = null; + let searchedDateFrom: Date | null = null; + let searchedDateTo: Date | null = null; if (dateFrom && dateTo) { let dateFromParsed = Date.parse(dateFrom); let dateToParsed = Date.parse(dateTo); diff --git a/backend/src/entities/user/application/data-structures/found-user-in-group.ds.ts b/backend/src/entities/user/application/data-structures/found-user-in-group.ds.ts index 9cd264099..fc4ac6df4 100644 --- a/backend/src/entities/user/application/data-structures/found-user-in-group.ds.ts +++ b/backend/src/entities/user/application/data-structures/found-user-in-group.ds.ts @@ -14,12 +14,12 @@ export class FoundUserInGroupDs { @ApiProperty() createdAt: Date; - @ApiProperty() - name: string; + @ApiProperty({ nullable: true, type: String }) + name: string | null; @ApiProperty() suspended: boolean; @ApiProperty() - externalRegistrationProvider: ExternalRegistrationProviderEnum; + externalRegistrationProvider: ExternalRegistrationProviderEnum | null; } diff --git a/backend/src/entities/user/application/data-structures/register-user-ds.ts b/backend/src/entities/user/application/data-structures/register-user-ds.ts index 7f73330f5..cd4021477 100644 --- a/backend/src/entities/user/application/data-structures/register-user-ds.ts +++ b/backend/src/entities/user/application/data-structures/register-user-ds.ts @@ -1,11 +1,11 @@ import { UserRoleEnum } from '../../enums/user-role.enum.js'; export class RegisterUserDs { - gclidValue: string; + gclidValue: string | null; email: string; - password: string; + password: string | null; isActive: boolean; - name: string; + name: string | null; role?: UserRoleEnum; samlNameId?: string; } diff --git a/backend/src/entities/user/application/data-structures/registered-user.ds.ts b/backend/src/entities/user/application/data-structures/registered-user.ds.ts index 9d102847e..832dc2a2e 100644 --- a/backend/src/entities/user/application/data-structures/registered-user.ds.ts +++ b/backend/src/entities/user/application/data-structures/registered-user.ds.ts @@ -12,9 +12,9 @@ export class RegisteredUserDs { @ApiProperty() token: IToken; - @ApiProperty() - name: string; + @ApiProperty({ nullable: true, type: String }) + name: string | null; @ApiProperty() - externalRegistrationProvider: ExternalRegistrationProviderEnum; + externalRegistrationProvider: ExternalRegistrationProviderEnum | null; } diff --git a/backend/src/entities/user/application/data-structures/save-user-settings.ds.ts b/backend/src/entities/user/application/data-structures/save-user-settings.ds.ts index 95883b425..108ae1125 100644 --- a/backend/src/entities/user/application/data-structures/save-user-settings.ds.ts +++ b/backend/src/entities/user/application/data-structures/save-user-settings.ds.ts @@ -1,4 +1,4 @@ export class SaveUserSettingsDs { - userSettings: string; + userSettings: string | null; userId: string; } diff --git a/backend/src/entities/user/application/data-structures/usual-login.ds.ts b/backend/src/entities/user/application/data-structures/usual-login.ds.ts index 1416cab9e..e589a70c2 100644 --- a/backend/src/entities/user/application/data-structures/usual-login.ds.ts +++ b/backend/src/entities/user/application/data-structures/usual-login.ds.ts @@ -10,7 +10,7 @@ export class UsualLoginDs { @ApiProperty() companyId: string; - gclidValue: string; + gclidValue: string | null; request_domain: string; diff --git a/backend/src/entities/user/dto/found-user.dto.ts b/backend/src/entities/user/dto/found-user.dto.ts index 2f16d482e..780022221 100644 --- a/backend/src/entities/user/dto/found-user.dto.ts +++ b/backend/src/entities/user/dto/found-user.dto.ts @@ -26,8 +26,8 @@ export class FoundUserDto { @ApiProperty({ required: false, type: String }) intercom_hash?: string; - @ApiProperty() - name: string; + @ApiProperty({ nullable: true, type: String }) + name: string | null; @ApiProperty() is_2fa_enabled: boolean; @@ -39,7 +39,7 @@ export class FoundUserDto { company?: CompanyIdDS; @ApiProperty({ enum: ExternalRegistrationProviderEnum }) - externalRegistrationProvider: ExternalRegistrationProviderEnum; + externalRegistrationProvider: ExternalRegistrationProviderEnum | null; @ApiProperty({ default: true }) show_test_connections: boolean; @@ -61,8 +61,8 @@ export class SimpleFoundUserInfoDs { @ApiProperty() suspended: boolean; - @ApiProperty() - name: string; + @ApiProperty({ nullable: true, type: String }) + name: string | null; @ApiProperty() is_2fa_enabled: boolean; @@ -71,7 +71,7 @@ export class SimpleFoundUserInfoDs { role: UserRoleEnum; @ApiProperty({ enum: ExternalRegistrationProviderEnum }) - externalRegistrationProvider: ExternalRegistrationProviderEnum; + externalRegistrationProvider: ExternalRegistrationProviderEnum | null; } export class UserGroupMembershipDto { @@ -86,11 +86,11 @@ export class UserConnectionMembershipDto { @ApiProperty() id: string; - @ApiProperty() - title: string; + @ApiProperty({ required: false }) + title?: string; - @ApiProperty() - database: string; + @ApiProperty({ required: false, nullable: true }) + database?: string | null; @ApiProperty({ type: UserConnectionMembershipDto, isArray: true }) groups: Array; diff --git a/backend/src/entities/user/repository/user-custom-repository-extension.ts b/backend/src/entities/user/repository/user-custom-repository-extension.ts index 2bee574d6..38fc8ab22 100644 --- a/backend/src/entities/user/repository/user-custom-repository-extension.ts +++ b/backend/src/entities/user/repository/user-custom-repository-extension.ts @@ -18,7 +18,7 @@ export const userCustomRepositoryExtension: IUserRepository = { async saveRegisteringUser( userData: RegisterUserDs, - externalRegistrationProvider: ExternalRegistrationProviderEnum = null, + externalRegistrationProvider: ExternalRegistrationProviderEnum | null = null, ): Promise { const newUser: UserEntity = new UserEntity(); newUser.gclid = userData.gclidValue; @@ -47,8 +47,8 @@ export const userCustomRepositoryExtension: IUserRepository = { findOneUserByEmail( email: string, - externalRegistrationProvider: ExternalRegistrationProviderEnum = null, - samlNameId: string = null, + externalRegistrationProvider: ExternalRegistrationProviderEnum | null = null, + samlNameId: string | null = null, ): Promise { const userQb = this.createQueryBuilder('user').where('user.email = :userEmail', { userEmail: email?.toLowerCase(), @@ -101,9 +101,8 @@ export const userCustomRepositoryExtension: IUserRepository = { }); const foundUsers = await usersQb.getMany(); return foundUsers.map((user: UserEntity) => { - delete user.connections; - delete user.groups; - return user; + const { connections: _connections, groups: _groups, ...userWithoutRelations } = user; + return userWithoutRelations; }); }, @@ -142,7 +141,7 @@ export const userCustomRepositoryExtension: IUserRepository = { return await usersQB.getMany(); }, - async getUserEmailOrReturnNull(userId: string): Promise { + async getUserEmailOrReturnNull(userId: string): Promise { const userQB = this.createQueryBuilder('user').where('user.id = :userId', { userId: userId }); const user = await userQB.getOne(); return user?.email ? user.email.toLowerCase() : null; @@ -182,7 +181,7 @@ export const userCustomRepositoryExtension: IUserRepository = { return await userQb.getOne(); }, - async findOneUserByEmailAndCompanyId(userEmail: string, companyId: string): Promise { + async findOneUserByEmailAndCompanyId(userEmail: string, companyId: string | null): Promise { const userQb = this.createQueryBuilder('user') .leftJoinAndSelect('user.company', 'company') .where('user.email = :userEmail', { userEmail: userEmail?.toLowerCase() }); @@ -211,7 +210,7 @@ export const userCustomRepositoryExtension: IUserRepository = { async findAllUsersWithEmail( email: string, - externalRegistrationProvider: ExternalRegistrationProviderEnum = null, + externalRegistrationProvider: ExternalRegistrationProviderEnum | null = null, ): Promise> { const usersQb = this.createQueryBuilder('user') .leftJoinAndSelect('user.company', 'company') diff --git a/backend/src/entities/user/repository/user.repository.interface.ts b/backend/src/entities/user/repository/user.repository.interface.ts index 8f0cbe0a5..47c42d7df 100644 --- a/backend/src/entities/user/repository/user.repository.interface.ts +++ b/backend/src/entities/user/repository/user.repository.interface.ts @@ -6,7 +6,7 @@ import { UserEntity } from '../user.entity.js'; export interface IUserRepository { createUser(userData: CreateUserDs): Promise; - findOneUserById(userId: string): Promise; + findOneUserById(userId: string): Promise; findOneUserWithUserAction(userId: string): Promise; @@ -14,7 +14,7 @@ export interface IUserRepository { email: string, externalRegistrationProvider?: ExternalRegistrationProviderEnum, samlNameId?: string, - ): Promise; + ): Promise; findUserWithConnections(userId: string): Promise; @@ -35,7 +35,7 @@ export interface IUserRepository { getUsersWithNotNullGCLIDsInTwoWeeks(): Promise>; - getUserEmailOrReturnNull(userId: string): Promise; + getUserEmailOrReturnNull(userId: string): Promise; getTrue(): Promise; @@ -43,7 +43,7 @@ export interface IUserRepository { findOneUserByGitHubId(gitHubId: number): Promise; - findOneUserByEmailAndCompanyId(email: string, companyId: string): Promise; + findOneUserByEmailAndCompanyId(email: string, companyId: string | null): Promise; findOneUserByIdAndCompanyId(userId: string, companyId: string): Promise; diff --git a/backend/src/entities/user/use-cases/otp-login-use.case.ts b/backend/src/entities/user/use-cases/otp-login-use.case.ts index 4655dd477..76375eee0 100644 --- a/backend/src/entities/user/use-cases/otp-login-use.case.ts +++ b/backend/src/entities/user/use-cases/otp-login-use.case.ts @@ -29,6 +29,9 @@ export class OtpLoginUseCase extends AbstractUseCase implem if (!foundUser) { throw new NotFoundException(Messages.USER_NOT_FOUND); } + if (!foundUser.otpSecretKey) { + throw new BadRequestException(Messages.OTP_NOT_ENABLED); + } const isValid = verifySync({ token: otpToken, secret: foundUser.otpSecretKey, @@ -56,8 +59,8 @@ export class OtpLoginUseCase extends AbstractUseCase implem email: string, userId: string | null, status: SignInStatusEnum, - ipAddress: string, - userAgent: string, + ipAddress?: string, + userAgent?: string, failureReason?: string, ): Promise { try { diff --git a/backend/src/entities/user/use-cases/request-change-user-email.use.case.ts b/backend/src/entities/user/use-cases/request-change-user-email.use.case.ts index 3269b31d0..a5bebf94f 100644 --- a/backend/src/entities/user/use-cases/request-change-user-email.use.case.ts +++ b/backend/src/entities/user/use-cases/request-change-user-email.use.case.ts @@ -24,6 +24,14 @@ export class RequestChangeUserEmailUseCase protected async implementation(userId: string): Promise { const foundUser = await this._dbContext.userRepository.findOneUserById(userId); + if (!foundUser) { + throw new HttpException( + { + message: Messages.USER_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } if (!foundUser.isActive) { throw new HttpException( { @@ -40,7 +48,7 @@ export class RequestChangeUserEmailUseCase rawToken, companyCustomDomain, ); - const resultMessage = mailingResult.messageId + const resultMessage = mailingResult?.messageId ? Messages.EMAIL_CHANGE_REQUESTED_SUCCESSFULLY : Messages.EMAIL_CHANGE_REQUESTED; return { message: resultMessage }; diff --git a/backend/src/entities/user/use-cases/request-reset-user-password.use.case.ts b/backend/src/entities/user/use-cases/request-reset-user-password.use.case.ts index a6b077acd..99eedabf0 100644 --- a/backend/src/entities/user/use-cases/request-reset-user-password.use.case.ts +++ b/backend/src/entities/user/use-cases/request-reset-user-password.use.case.ts @@ -43,7 +43,7 @@ export class RequestResetUserPasswordUseCase rawToken, companyCustomDomain, ); - const resultMessage = mailingResult.messageId + const resultMessage = mailingResult?.messageId ? Messages.PASSWORD_RESET_REQUESTED_SUCCESSFULLY : Messages.PASSWORD_RESET_REQUESTED; return { message: resultMessage }; diff --git a/backend/src/entities/user/use-cases/usual-login-use.case.ts b/backend/src/entities/user/use-cases/usual-login-use.case.ts index a25ae6047..2c26bb64a 100644 --- a/backend/src/entities/user/use-cases/usual-login-use.case.ts +++ b/backend/src/entities/user/use-cases/usual-login-use.case.ts @@ -33,7 +33,7 @@ export class UsualLoginUseCase extends AbstractUseCase imp const { request_domain, ipAddress, userAgent } = userData; let { companyId } = userData; const email = userData.email.toLowerCase(); - let user: UserEntity = null; + let user: UserEntity | null = null; if (companyId) { user = await this._dbContext.userRepository.findOneUserByEmailAndCompanyId(email, companyId); @@ -129,8 +129,8 @@ export class UsualLoginUseCase extends AbstractUseCase imp email: string, userId: string | null, status: SignInStatusEnum, - ipAddress: string, - userAgent: string, + ipAddress?: string, + userAgent?: string, failureReason?: string, ): Promise { try { diff --git a/backend/src/entities/user/use-cases/verify-user-email.use.case.ts b/backend/src/entities/user/use-cases/verify-user-email.use.case.ts index 6bad79b19..bd4dfd83d 100644 --- a/backend/src/entities/user/use-cases/verify-user-email.use.case.ts +++ b/backend/src/entities/user/use-cases/verify-user-email.use.case.ts @@ -29,6 +29,14 @@ export class VerifyUserEmailUseCase extends AbstractUseCase 'CURRENT_TIMESTAMP' }) createdAt: Date; diff --git a/backend/src/entities/user/user-session-settings/reposiotory/user-session-settings-custom-repository.extension.ts b/backend/src/entities/user/user-session-settings/reposiotory/user-session-settings-custom-repository.extension.ts index ed2acbfa6..702dfa5b7 100644 --- a/backend/src/entities/user/user-session-settings/reposiotory/user-session-settings-custom-repository.extension.ts +++ b/backend/src/entities/user/user-session-settings/reposiotory/user-session-settings-custom-repository.extension.ts @@ -2,14 +2,14 @@ import { UserSessionSettingsEntity } from '../user-session-settings.entity.js'; import { IUserSessionSettings } from './user-session-settings-repository.interface.js'; export const userSessionSettingsRepositoryExtension: IUserSessionSettings = { - async getUserSettingsByUserId(userId: string): Promise { + async getUserSettingsByUserId(userId: string): Promise { const qb = this.createQueryBuilder('user_session_settings').where('user_session_settings.userId = :userId', { userId: userId, }); return await qb.getOne(); }, - async createOrUpdateUserSettings(userId: string, userSettings: string): Promise { + async createOrUpdateUserSettings(userId: string, userSettings: string | null): Promise { const qb = this.createQueryBuilder('user_session_settings').where('user_session_settings.userId = :userId', { userId: userId, }); diff --git a/backend/src/entities/user/user-session-settings/reposiotory/user-session-settings-repository.interface.ts b/backend/src/entities/user/user-session-settings/reposiotory/user-session-settings-repository.interface.ts index 24103ee9b..4339a16cf 100644 --- a/backend/src/entities/user/user-session-settings/reposiotory/user-session-settings-repository.interface.ts +++ b/backend/src/entities/user/user-session-settings/reposiotory/user-session-settings-repository.interface.ts @@ -1,7 +1,7 @@ import { UserSessionSettingsEntity } from '../user-session-settings.entity.js'; export interface IUserSessionSettings { - getUserSettingsByUserId(userId: string): Promise; + getUserSettingsByUserId(userId: string): Promise; - createOrUpdateUserSettings(userId: string, userSettings: string): Promise; + createOrUpdateUserSettings(userId: string, userSettings: string | null): Promise; } diff --git a/backend/src/entities/user/user-session-settings/user-session-settings.entity.ts b/backend/src/entities/user/user-session-settings/user-session-settings.entity.ts index c12426640..6aa3a24f7 100644 --- a/backend/src/entities/user/user-session-settings/user-session-settings.entity.ts +++ b/backend/src/entities/user/user-session-settings/user-session-settings.entity.ts @@ -9,7 +9,7 @@ export class UserSessionSettingsEntity { userId: string; @Column({ default: null, type: 'json' }) - userSettings: string; + userSettings: string | null; @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) createdAt: Date; diff --git a/backend/src/entities/user/user.controller.ts b/backend/src/entities/user/user.controller.ts index d087c24a6..a48f32b46 100644 --- a/backend/src/entities/user/user.controller.ts +++ b/backend/src/entities/user/user.controller.ts @@ -43,6 +43,7 @@ import { OtpSecretDS } from './application/data-structures/otp-secret.ds.js'; import { OtpDisablingResultDS, OtpValidationResultDS } from './application/data-structures/otp-validation-result.ds.js'; import { RegisteredUserDs } from './application/data-structures/registered-user.ds.js'; import { ResetUsualUserPasswordDs } from './application/data-structures/reset-usual-user-password.ds.js'; +import { SaveUserSettingsDs } from './application/data-structures/save-user-settings.ds.js'; import { UsualLoginDs } from './application/data-structures/usual-login.ds.js'; import { DeleteUserAccountDTO } from './dto/delete-user-account-request.dto.js'; import { EmailDto } from './dto/email.dto.js'; @@ -484,7 +485,7 @@ export class UserController { async saveUserSessionSettings( @Body() userSettings: UserSettingsDataRequestDto, @UserId() userId: string, - ): Promise { + ): Promise { return await this.saveUserSessionSettingsUseCase.execute( { userId, userSettings: userSettings.userSettings }, InTransactionEnum.OFF, @@ -498,7 +499,7 @@ export class UserController { type: UserSettingsDataRequestDto, }) @Get('user/settings/') - async getUserSessionSettings(@UserId() userId: string): Promise { + async getUserSessionSettings(@UserId() userId: string): Promise { return await this.getUserSessionSettingsUseCase.execute(userId, InTransactionEnum.OFF); } diff --git a/backend/src/entities/user/user.entity.ts b/backend/src/entities/user/user.entity.ts index 3ebb8bc09..6bb9bddea 100644 --- a/backend/src/entities/user/user.entity.ts +++ b/backend/src/entities/user/user.entity.ts @@ -38,11 +38,11 @@ export class UserEntity { @Column({ default: null }) email: string; - @Column({ default: null }) - password: string; + @Column({ type: 'varchar', default: null }) + password: string | null; - @Column({ default: null }) - name: string; + @Column({ type: 'varchar', default: null }) + name: string | null; @Column({ default: false, type: 'boolean' }) suspended: boolean; @@ -77,14 +77,14 @@ export class UserEntity { @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) createdAt: Date; - @Column({ default: null }) - gclid: string; + @Column({ type: 'varchar', default: null }) + gclid: string | null; @Column({ default: false, type: 'boolean' }) isOTPEnabled: boolean; - @Column({ default: null }) - otpSecretKey: string; + @Column({ type: 'varchar', default: null }) + otpSecretKey: string | null; @OneToMany( (_) => ConnectionEntity, @@ -189,10 +189,10 @@ export class UserEntity { enum: ExternalRegistrationProviderEnum, default: null, }) - externalRegistrationProvider: ExternalRegistrationProviderEnum; + externalRegistrationProvider: ExternalRegistrationProviderEnum | null; - @Column({ default: null }) - samlNameId: string; + @Column({ type: 'varchar', default: null }) + samlNameId: string | null; @Column({ default: true, type: 'boolean' }) showTestConnections: boolean; diff --git a/backend/src/entities/user/utils/build-connection-entities-from-test-dtos.ts b/backend/src/entities/user/utils/build-connection-entities-from-test-dtos.ts index 7fea598c9..6fe305cf3 100644 --- a/backend/src/entities/user/utils/build-connection-entities-from-test-dtos.ts +++ b/backend/src/entities/user/utils/build-connection-entities-from-test-dtos.ts @@ -23,7 +23,7 @@ export function buildConnectionEntitiesFromTestDtos(dtos: Array): const exp = new Date(today); exp.setTime(today.getTime() + 60 * 60 * 1000 * 24 * 7); const jwtSecret = appConfig.auth.jwtSecret; + if (!jwtSecret) { + throw new Error('JWT_SECRET is not configured'); + } const token = jwt.sign( { id: user.id, email: user.email, exp: Math.floor(exp.getTime() / 1000), - scope: scope ? scope : undefined, + scope: scope && scope.length ? scope : undefined, }, jwtSecret, ); @@ -30,6 +33,9 @@ export function generateTemporaryJwtToken(user: UserEntity): IToken { const exp = new Date(today); exp.setTime(today.getTime() + 1000 * 60 * 4); const jwtSecret = appConfig.auth.temporaryJwtSecret; + if (!jwtSecret) { + throw new Error('TEMPORARY_JWT_SECRET is not configured'); + } const token = jwt.sign( { id: user.id, diff --git a/backend/src/entities/user/utils/is-jwt-scope-need.util.ts b/backend/src/entities/user/utils/is-jwt-scope-need.util.ts index 92fb20bbb..010bce410 100644 --- a/backend/src/entities/user/utils/is-jwt-scope-need.util.ts +++ b/backend/src/entities/user/utils/is-jwt-scope-need.util.ts @@ -2,10 +2,10 @@ import { CompanyInfoEntity } from '../../company-info/company-info.entity.js'; import { JwtScopesEnum } from '../enums/jwt-scopes.enum.js'; import { UserEntity } from '../user.entity.js'; -function isJwt2faScopeNeed(user: UserEntity, userCompany: CompanyInfoEntity): boolean { - return userCompany?.is2faEnabled && !user.isOTPEnabled; +function isJwt2faScopeNeed(user: UserEntity, userCompany: CompanyInfoEntity | null): boolean { + return !!userCompany?.is2faEnabled && !user.isOTPEnabled; } -export function get2FaScope(user: UserEntity, userCompany: CompanyInfoEntity): Array { - return isJwt2faScopeNeed(user, userCompany) ? [JwtScopesEnum.TWO_FA_ENABLE] : null; +export function get2FaScope(user: UserEntity, userCompany: CompanyInfoEntity | null): Array { + return isJwt2faScopeNeed(user, userCompany) ? [JwtScopesEnum.TWO_FA_ENABLE] : []; } diff --git a/backend/src/entities/visualizations/panel-position/use-cases/generate-panel-position-with-ai.use.case.ts b/backend/src/entities/visualizations/panel-position/use-cases/generate-panel-position-with-ai.use.case.ts index 962d05d08..d43730ee0 100644 --- a/backend/src/entities/visualizations/panel-position/use-cases/generate-panel-position-with-ai.use.case.ts +++ b/backend/src/entities/visualizations/panel-position/use-cases/generate-panel-position-with-ai.use.case.ts @@ -88,9 +88,9 @@ export class GeneratePanelPositionWithAiUseCase const dao = getDataAccessObject(foundConnection); - let userEmail: string; + let userEmail = ''; if (isConnectionTypeAgent(foundConnection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); + userEmail = (await this._dbContext.userRepository.getUserEmailOrReturnNull(userId)) ?? ''; } const tools = createDashboardGenerationTools(); @@ -344,7 +344,7 @@ IMPORTANT GUIDELINES: const correctionPrompt = this.buildQueryCorrectionPrompt( currentQuery, - explainResult.success ? explainResult.result : explainResult.error, + (explainResult.success ? explainResult.result : explainResult.error) ?? '', !explainResult.success, connectionType, generatedPanel.name, diff --git a/backend/src/entities/visualizations/panel-position/use-cases/generate-table-dashboard-with-ai.use.case.ts b/backend/src/entities/visualizations/panel-position/use-cases/generate-table-dashboard-with-ai.use.case.ts index fdff0b07a..b728b6945 100644 --- a/backend/src/entities/visualizations/panel-position/use-cases/generate-table-dashboard-with-ai.use.case.ts +++ b/backend/src/entities/visualizations/panel-position/use-cases/generate-table-dashboard-with-ai.use.case.ts @@ -103,9 +103,9 @@ export class GenerateTableDashboardWithAiUseCase const dao = getDataAccessObject(foundConnection); - let userEmail: string; + let userEmail = ''; if (isConnectionTypeAgent(foundConnection.type)) { - userEmail = await this._dbContext.userRepository.getUserEmailOrReturnNull(userId); + userEmail = (await this._dbContext.userRepository.getUserEmailOrReturnNull(userId)) ?? ''; } const tools = createDashboardGenerationTools(); @@ -416,7 +416,7 @@ IMPORTANT GUIDELINES: const correctionPrompt = this.buildQueryCorrectionPrompt( currentQuery, - explainResult.success ? explainResult.result : explainResult.error, + (explainResult.success ? explainResult.result : explainResult.error) ?? '', !explainResult.success, connectionType, generatedPanel.name, diff --git a/backend/src/entities/visualizations/panel/use-cases/execute-panel.use.case.ts b/backend/src/entities/visualizations/panel/use-cases/execute-panel.use.case.ts index c5f77e454..fadbbe172 100644 --- a/backend/src/entities/visualizations/panel/use-cases/execute-panel.use.case.ts +++ b/backend/src/entities/visualizations/panel/use-cases/execute-panel.use.case.ts @@ -70,7 +70,7 @@ export class ExecuteSavedDbQueryUseCase const startTime = Date.now(); - const executionResult = await dao.executeRawQuery(foundQuery.query_text, tableName, userEmail); + const executionResult = await dao.executeRawQuery(foundQuery.query_text, tableName, userEmail ?? ''); const processedResult = this.processQueryResult(executionResult, foundConnection.type as ConnectionTypesEnum); const executionTime = Date.now() - startTime; diff --git a/backend/src/entities/visualizations/panel/use-cases/test-db-query.use.case.ts b/backend/src/entities/visualizations/panel/use-cases/test-db-query.use.case.ts index ff1f5dd9c..930f3538f 100644 --- a/backend/src/entities/visualizations/panel/use-cases/test-db-query.use.case.ts +++ b/backend/src/entities/visualizations/panel/use-cases/test-db-query.use.case.ts @@ -61,7 +61,7 @@ export class TestDbQueryUseCase extends AbstractUseCase = await validateCreateWidgetsDs(widgets, userId, foundConnection, tableName, null); + if (!foundConnection) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } + const errors: Array = await validateCreateWidgetsDs(widgets, userId, foundConnection, tableName, ''); if (errors.length > 0) { throw new HttpException( { diff --git a/backend/src/entities/widget/use-cases/find-table-widgets.use.case.ts b/backend/src/entities/widget/use-cases/find-table-widgets.use.case.ts index 13e8aca5e..eaebfc348 100644 --- a/backend/src/entities/widget/use-cases/find-table-widgets.use.case.ts +++ b/backend/src/entities/widget/use-cases/find-table-widgets.use.case.ts @@ -25,7 +25,15 @@ export class FindTableWidgetsUseCase protected async implementation(inputData: FindTableWidgetsDs): Promise> { const { connectionId, masterPwd, tableName, userId } = inputData; const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); - const tablesInConnection = await findTablesInConnectionUtil(connection, userId, null); + if (!connection) { + throw new HttpException( + { + message: Messages.CONNECTION_NOT_FOUND, + }, + HttpStatus.NOT_FOUND, + ); + } + const tablesInConnection = await findTablesInConnectionUtil(connection, userId, ''); if (!tablesInConnection.includes(tableName)) { throw new HttpException( { diff --git a/backend/src/entities/widget/utils/validate-create-widgets-ds.ts b/backend/src/entities/widget/utils/validate-create-widgets-ds.ts index 8d2919c41..7de791199 100644 --- a/backend/src/entities/widget/utils/validate-create-widgets-ds.ts +++ b/backend/src/entities/widget/utils/validate-create-widgets-ds.ts @@ -64,11 +64,15 @@ export async function validateCreateWidgetsDs( } if (widget_type && widget_type === WidgetTypeEnum.Foreign_key) { + if (!widgetDS.widget_params) { + errors.push(Messages.WIDGET_REQUIRED_PARAMETER_MISSING('widget_params')); + return errors; + } const widget_params: ForeignKeyDSInfo = JSON5.parse(widgetDS.widget_params); for (const key in widget_params) { if (!Constants.FOREIGN_KEY_FIELDS.includes(key)) { - errors.push(Messages.WIDGET_PARAMETER_UNSUPPORTED(key, widgetDS.widget_type)); + errors.push(Messages.WIDGET_PARAMETER_UNSUPPORTED(key, widget_type)); continue; } if (!getPropertyValueByDescriptor(widget_params, key) && key !== 'constraint_name') { @@ -97,6 +101,10 @@ export async function validateCreateWidgetsDs( if (widget_type && widget_type === WidgetTypeEnum.S3) { const rawParams = widgetDS.widget_params; + if (!rawParams) { + errors.push(Messages.WIDGET_REQUIRED_PARAMETER_MISSING('widget_params')); + return errors; + } const widget_params: Record = typeof rawParams === 'string' ? JSON5.parse(rawParams) : (rawParams as Record); diff --git a/backend/src/enums/widget-type.enum.ts b/backend/src/enums/widget-type.enum.ts index a8b6af74d..8929a6898 100644 --- a/backend/src/enums/widget-type.enum.ts +++ b/backend/src/enums/widget-type.enum.ts @@ -24,4 +24,5 @@ export enum WidgetTypeEnum { S3 = 'S3', Email = 'Email', Binary = 'Binary', + Money = 'Money', } diff --git a/backend/src/exceptions/custom-exceptions/validation-exception.ts b/backend/src/exceptions/custom-exceptions/validation-exception.ts index 0d73bfbb1..2c29ec3cf 100644 --- a/backend/src/exceptions/custom-exceptions/validation-exception.ts +++ b/backend/src/exceptions/custom-exceptions/validation-exception.ts @@ -11,7 +11,7 @@ export class ValidationException extends HttpException { originalMessage = originalMessage .map((error) => { return `Property "${error.property}" validation failed with following errors: ${Object.values( - error.constraints, + error.constraints ?? {}, ).join(', ')}`; }) .join('.\n'); diff --git a/backend/src/exceptions/text/messages.ts b/backend/src/exceptions/text/messages.ts index 193c26721..7c25fe413 100644 --- a/backend/src/exceptions/text/messages.ts +++ b/backend/src/exceptions/text/messages.ts @@ -242,6 +242,7 @@ export const Messages = { } missing`, ROW_PRIMARY_KEY_NOT_FOUND: 'Row with this primary key not found', RULE_NOT_FOUND: 'Rule not found', + ACTION_EVENT_NOT_FOUND: 'Action event not found', SAAS_COMPANY_NOT_REGISTERED_WITH_USER_INVITATION: `Failed to invite user in SaaS. Please contact our support team.`, SAAS_UPDATE_USERS_ROLES_FAILED_UNHANDLED_ERROR: `Failed to update users roles in SaaS. Please contact our support team.`, SAAS_DELETE_COMPANY_FAILED_UNHANDLED_ERROR: `Failed to delete company in SaaS. Please contact our support team.`, diff --git a/backend/src/exceptions/utils/processing-messages-replace.ts b/backend/src/exceptions/utils/processing-messages-replace.ts index bef7270bc..2328c3dc7 100644 --- a/backend/src/exceptions/utils/processing-messages-replace.ts +++ b/backend/src/exceptions/utils/processing-messages-replace.ts @@ -29,7 +29,7 @@ export const PROCESSING_MESSAGES_REPLACE = { const regex = /(?<=\()[^)]+(?=\))/g; const referencesWordIndex = words.indexOf('references'); const tableName = words.at(referencesWordIndex + 1); - const relatedColumnName = words.at(referencesWordIndex + 2).match(regex); + const relatedColumnName = words.at(referencesWordIndex + 2)?.match(regex); const message = ` You tried to change a record in a table ${tableName}, but another table references on ${relatedColumnName} field in this table. Before the operation, you need to update/delete the associated record in that table or set up in that table an option with operation for the associated entry ("Cascade option"). diff --git a/backend/src/guards/action-event-trigger.guard.ts b/backend/src/guards/action-event-trigger.guard.ts index 4a5ae1c60..1ee3812b7 100644 --- a/backend/src/guards/action-event-trigger.guard.ts +++ b/backend/src/guards/action-event-trigger.guard.ts @@ -26,8 +26,11 @@ export class ActionEventTriggerGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - const connectionId: string = request.params?.connectionId; - const eventId: string = request.params?.eventId; + if (!cognitoUserName) { + throw new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS); + } + const connectionId: string | undefined = request.params?.connectionId; + const eventId: string | undefined = request.params?.eventId; if (!connectionId || (!validateUuidByRegex(connectionId) && !ValidationHelper.isValidNanoId(connectionId))) { throw new BadRequestException(Messages.CONNECTION_ID_MISSING); diff --git a/backend/src/guards/company-admin.guard.ts b/backend/src/guards/company-admin.guard.ts index 35702a20c..2f26572fa 100644 --- a/backend/src/guards/company-admin.guard.ts +++ b/backend/src/guards/company-admin.guard.ts @@ -18,8 +18,12 @@ export class CompanyAdminGuard implements CanActivate { canActivate(context: ExecutionContext): boolean | Promise | Observable { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); - const userId: string = request.decoded.sub; - let companyId: string = request.params?.companyId || request.params?.slug; + const userId: string | undefined = request.decoded.sub; + if (!userId) { + resolve(false); + return; + } + let companyId: string | undefined = request.params?.companyId || request.params?.slug; if (!companyId || !validateUuidByRegex(companyId)) { companyId = request.body?.companyId; } diff --git a/backend/src/guards/company-user.guard.ts b/backend/src/guards/company-user.guard.ts index e29b7df9b..a6b5dc2d0 100644 --- a/backend/src/guards/company-user.guard.ts +++ b/backend/src/guards/company-user.guard.ts @@ -18,8 +18,12 @@ export class CompanyUserGuard implements CanActivate { canActivate(context: ExecutionContext): boolean | Promise | Observable { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); - const userId: string = request.decoded.sub; - let companyId: string = request.params?.companyId || request.params?.slug; + const userId: string | undefined = request.decoded.sub; + if (!userId) { + resolve(false); + return; + } + let companyId: string | undefined = request.params?.companyId || request.params?.slug; if (!companyId || !validateUuidByRegex(companyId)) { companyId = request.body?.companyId; } diff --git a/backend/src/guards/connection-diagram.guard.ts b/backend/src/guards/connection-diagram.guard.ts index 6e84ff5ab..fd54d0574 100644 --- a/backend/src/guards/connection-diagram.guard.ts +++ b/backend/src/guards/connection-diagram.guard.ts @@ -15,7 +15,11 @@ export class ConnectionDiagramGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - let connectionId: string = request.query.connectionId; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + let connectionId: string | undefined = request.query.connectionId; if (!connectionId || (!validateUuidByRegex(connectionId) && !ValidationHelper.isValidNanoId(connectionId))) { connectionId = request.params?.slug || request.params?.connectionId; } diff --git a/backend/src/guards/connection-edit.guard.ts b/backend/src/guards/connection-edit.guard.ts index d0d603e7e..2b0320a59 100644 --- a/backend/src/guards/connection-edit.guard.ts +++ b/backend/src/guards/connection-edit.guard.ts @@ -15,7 +15,11 @@ export class ConnectionEditGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - let connectionId: string = request.query.connectionId; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + let connectionId: string | undefined = request.query.connectionId; if (!connectionId || (!validateUuidByRegex(connectionId) && !ValidationHelper.isValidNanoId(connectionId))) { connectionId = request.params?.slug || request.params?.connectionId; } diff --git a/backend/src/guards/connection-read.guard.ts b/backend/src/guards/connection-read.guard.ts index 0fdf08b62..cb60170ef 100644 --- a/backend/src/guards/connection-read.guard.ts +++ b/backend/src/guards/connection-read.guard.ts @@ -15,7 +15,11 @@ export class ConnectionReadGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - let connectionId: string = request.params?.slug || request.params?.connectionId; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + let connectionId: string | undefined = request.params?.slug || request.params?.connectionId; if (!connectionId || (!validateUuidByRegex(connectionId) && !ValidationHelper.isValidNanoId(connectionId))) { connectionId = request.query.connectionId; } diff --git a/backend/src/guards/dashboard-create.guard.ts b/backend/src/guards/dashboard-create.guard.ts index 757e886bf..566c259f8 100644 --- a/backend/src/guards/dashboard-create.guard.ts +++ b/backend/src/guards/dashboard-create.guard.ts @@ -15,7 +15,11 @@ export class DashboardCreateGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - let connectionId: string = request.params?.slug || request.params?.connectionId; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + let connectionId: string | undefined = request.params?.slug || request.params?.connectionId; if (!connectionId || (!validateUuidByRegex(connectionId) && !ValidationHelper.isValidNanoId(connectionId))) { connectionId = request.query.connectionId; } diff --git a/backend/src/guards/dashboard-edit.guard.ts b/backend/src/guards/dashboard-edit.guard.ts index 045e87431..33100a7ea 100644 --- a/backend/src/guards/dashboard-edit.guard.ts +++ b/backend/src/guards/dashboard-edit.guard.ts @@ -15,7 +15,11 @@ export class DashboardEditGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - let connectionId: string = request.query.connectionId; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + let connectionId: string | undefined = request.query.connectionId; if (!connectionId || (!validateUuidByRegex(connectionId) && !ValidationHelper.isValidNanoId(connectionId))) { connectionId = request.params?.slug || request.params?.connectionId; } @@ -24,7 +28,7 @@ export class DashboardEditGuard implements CanActivate { return; } - const dashboardId: string = request.params?.dashboardId; + const dashboardId: string | undefined = request.params?.dashboardId; const action = request.method === 'DELETE' ? CedarAction.DashboardDelete : CedarAction.DashboardEdit; try { diff --git a/backend/src/guards/dashboard-read.guard.ts b/backend/src/guards/dashboard-read.guard.ts index 7afe33f3a..ce1714a24 100644 --- a/backend/src/guards/dashboard-read.guard.ts +++ b/backend/src/guards/dashboard-read.guard.ts @@ -28,7 +28,11 @@ export class DashboardReadGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - let connectionId: string = request.params?.slug || request.params?.connectionId; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + let connectionId: string | undefined = request.params?.slug || request.params?.connectionId; if (!connectionId || (!validateUuidByRegex(connectionId) && !ValidationHelper.isValidNanoId(connectionId))) { connectionId = request.query.connectionId; } @@ -37,7 +41,7 @@ export class DashboardReadGuard implements CanActivate { return; } - const dashboardId: string = request.params?.dashboardId; + const dashboardId: string | undefined = request.params?.dashboardId; try { if (!dashboardId) { diff --git a/backend/src/guards/group-edit.guard.ts b/backend/src/guards/group-edit.guard.ts index 3a3d93219..a4aaf52f4 100644 --- a/backend/src/guards/group-edit.guard.ts +++ b/backend/src/guards/group-edit.guard.ts @@ -14,7 +14,11 @@ export class GroupEditGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - let groupId: string = request.params?.groupId || request.params?.slug; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + let groupId: string | undefined = request.params?.groupId || request.params?.slug; if (!groupId || !validateUuidByRegex(groupId)) { groupId = request.body.groupId; } diff --git a/backend/src/guards/group-read.guard.ts b/backend/src/guards/group-read.guard.ts index 1868b148b..ac321394c 100644 --- a/backend/src/guards/group-read.guard.ts +++ b/backend/src/guards/group-read.guard.ts @@ -14,7 +14,11 @@ export class GroupReadGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - let groupId: string = request.params?.groupId || request.params?.slug; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + let groupId: string | undefined = request.params?.groupId || request.params?.slug; if (!groupId || !validateUuidByRegex(groupId)) { groupId = request.body.groupId; } diff --git a/backend/src/guards/paid-feature.guard.ts b/backend/src/guards/paid-feature.guard.ts index 8b47755af..c311c5c20 100644 --- a/backend/src/guards/paid-feature.guard.ts +++ b/backend/src/guards/paid-feature.guard.ts @@ -21,8 +21,12 @@ export class PaidFeatureGuard implements CanActivate { canActivate(context: ExecutionContext): boolean | Promise | Observable { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); - const userId: string = request.decoded.sub; - let companyId: string = request.params?.companyId || request.params?.slug; + const userId: string | undefined = request.decoded.sub; + if (!userId) { + reject(new BadRequestException(Messages.COMPANY_ID_MISSING)); + return; + } + let companyId: string | undefined = request.params?.companyId || request.params?.slug; if (!companyId || !validateUuidByRegex(companyId)) { companyId = request.body?.companyId; } diff --git a/backend/src/guards/panel-edit.guard.ts b/backend/src/guards/panel-edit.guard.ts index bd6adc8ed..3b7464fad 100644 --- a/backend/src/guards/panel-edit.guard.ts +++ b/backend/src/guards/panel-edit.guard.ts @@ -15,7 +15,11 @@ export class PanelEditGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - let connectionId: string = request.params?.slug || request.params?.connectionId; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + let connectionId: string | undefined = request.params?.slug || request.params?.connectionId; if (!connectionId || (!validateUuidByRegex(connectionId) && !ValidationHelper.isValidNanoId(connectionId))) { connectionId = request.query.connectionId; } @@ -24,7 +28,7 @@ export class PanelEditGuard implements CanActivate { return; } - const panelId: string = request.params?.queryId; + const panelId: string | undefined = request.params?.queryId; let action: CedarAction; if (request.method === 'DELETE') { diff --git a/backend/src/guards/panel-read.guard.ts b/backend/src/guards/panel-read.guard.ts index a7d37c262..4ca7c3ab6 100644 --- a/backend/src/guards/panel-read.guard.ts +++ b/backend/src/guards/panel-read.guard.ts @@ -28,7 +28,11 @@ export class PanelReadGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - let connectionId: string = request.params?.slug || request.params?.connectionId; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + let connectionId: string | undefined = request.params?.slug || request.params?.connectionId; if (!connectionId || (!validateUuidByRegex(connectionId) && !ValidationHelper.isValidNanoId(connectionId))) { connectionId = request.query.connectionId; } @@ -37,7 +41,7 @@ export class PanelReadGuard implements CanActivate { return; } - const panelId: string = request.params?.queryId; + const panelId: string | undefined = request.params?.queryId; try { // For list-all requests (no panelId), verify user belongs to the connection. diff --git a/backend/src/guards/schema-change-batch-ownership.guard.ts b/backend/src/guards/schema-change-batch-ownership.guard.ts index 26ac198b3..013a28de3 100644 --- a/backend/src/guards/schema-change-batch-ownership.guard.ts +++ b/backend/src/guards/schema-change-batch-ownership.guard.ts @@ -26,7 +26,10 @@ export class SchemaChangeBatchOwnershipGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const userId = request.decoded.sub; - const batchId: string = request.params?.batchId; + if (!userId) { + throw new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS); + } + const batchId: string | undefined = request.params?.batchId; if (!batchId || !ValidationHelper.isValidUUID(batchId)) { throw new BadRequestException('Invalid or missing batchId.'); diff --git a/backend/src/guards/schema-change-ownership.guard.ts b/backend/src/guards/schema-change-ownership.guard.ts index e1ce87a1a..e95a88e3e 100644 --- a/backend/src/guards/schema-change-ownership.guard.ts +++ b/backend/src/guards/schema-change-ownership.guard.ts @@ -26,7 +26,10 @@ export class SchemaChangeOwnershipGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const userId = request.decoded.sub; - const changeId: string = request.params?.changeId; + if (!userId) { + throw new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS); + } + const changeId: string | undefined = request.params?.changeId; if (!changeId || !ValidationHelper.isValidUUID(changeId)) { throw new BadRequestException('Invalid or missing changeId.'); diff --git a/backend/src/guards/table-add.guard.ts b/backend/src/guards/table-add.guard.ts index bdf10d568..dfb823948 100644 --- a/backend/src/guards/table-add.guard.ts +++ b/backend/src/guards/table-add.guard.ts @@ -15,8 +15,12 @@ export class TableAddGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - const connectionId: string = request.params?.slug || request.params?.connectionId; - const tableName: string = request.query?.tableName; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + const connectionId: string | undefined = request.params?.slug || request.params?.connectionId; + const tableName: string | undefined = request.query?.tableName; if (!tableName) { reject(new BadRequestException(Messages.TABLE_NAME_MISSING)); return; diff --git a/backend/src/guards/table-ai-request.guard.ts b/backend/src/guards/table-ai-request.guard.ts index aa8758b08..2a1402f66 100644 --- a/backend/src/guards/table-ai-request.guard.ts +++ b/backend/src/guards/table-ai-request.guard.ts @@ -15,8 +15,12 @@ export class TableAiRequestGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - const connectionId: string = request.params?.slug || request.params?.connectionId; - const tableName: string = request.query?.tableName; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + const connectionId: string | undefined = request.params?.slug || request.params?.connectionId; + const tableName: string | undefined = request.query?.tableName; if (!tableName) { reject(new BadRequestException(Messages.TABLE_NAME_MISSING)); return; diff --git a/backend/src/guards/table-delete.guard.ts b/backend/src/guards/table-delete.guard.ts index acd33b45a..4f8577c77 100644 --- a/backend/src/guards/table-delete.guard.ts +++ b/backend/src/guards/table-delete.guard.ts @@ -15,8 +15,12 @@ export class TableDeleteGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - const connectionId: string = request.params?.slug || request.params?.connectionId; - const tableName: string = request.query?.tableName; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + const connectionId: string | undefined = request.params?.slug || request.params?.connectionId; + const tableName: string | undefined = request.query?.tableName; if (!tableName) { reject(new BadRequestException(Messages.TABLE_NAME_MISSING)); return; diff --git a/backend/src/guards/table-edit.guard.ts b/backend/src/guards/table-edit.guard.ts index 412fc917b..6d6da2643 100644 --- a/backend/src/guards/table-edit.guard.ts +++ b/backend/src/guards/table-edit.guard.ts @@ -15,8 +15,12 @@ export class TableEditGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - const connectionId: string = request.params?.slug || request.params?.connectionId; - const tableName: string = request.query?.tableName; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + const connectionId: string | undefined = request.params?.slug || request.params?.connectionId; + const tableName: string | undefined = request.query?.tableName; if (!tableName) { reject(new BadRequestException(Messages.TABLE_NAME_MISSING)); return; diff --git a/backend/src/guards/table-read.guard.ts b/backend/src/guards/table-read.guard.ts index 5a68ae258..9337d9467 100644 --- a/backend/src/guards/table-read.guard.ts +++ b/backend/src/guards/table-read.guard.ts @@ -15,8 +15,12 @@ export class TableReadGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - const connectionId: string = request.params?.slug || request.params?.connectionId; - const tableName: string = request.query?.tableName; + if (!cognitoUserName) { + reject(new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS)); + return; + } + const connectionId: string | undefined = request.params?.slug || request.params?.connectionId; + const tableName: string | undefined = request.query?.tableName; if (!tableName) { reject(new BadRequestException(Messages.TABLE_NAME_MISSING)); return; diff --git a/backend/src/guards/tables-receive.guard.ts b/backend/src/guards/tables-receive.guard.ts index 60446dc00..8d72099d3 100644 --- a/backend/src/guards/tables-receive.guard.ts +++ b/backend/src/guards/tables-receive.guard.ts @@ -18,7 +18,11 @@ export class TablesReceiveGuard implements CanActivate { return new Promise(async (resolve, reject) => { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; - const connectionId: string = request.params?.slug || request.params?.connectionId; + if (!cognitoUserName) { + reject(new BadRequestException(Messages.CONNECTION_NOT_FOUND)); + return; + } + const connectionId: string | undefined = request.params?.slug || request.params?.connectionId; if (!connectionId || (!validateUuidByRegex(connectionId) && !ValidationHelper.isValidNanoId(connectionId))) { reject(new BadRequestException(Messages.CONNECTION_ID_MISSING)); return; diff --git a/backend/src/helpers/check-field-autoincrement.ts b/backend/src/helpers/check-field-autoincrement.ts index f67a1c0a6..0c7f0718d 100644 --- a/backend/src/helpers/check-field-autoincrement.ts +++ b/backend/src/helpers/check-field-autoincrement.ts @@ -1,4 +1,4 @@ -export function checkFieldAutoincrement(defaultValue: string, extra: string = null): boolean { +export function checkFieldAutoincrement(defaultValue: string | null, extra: string | null = null): boolean { let result = false; if ( defaultValue?.toLowerCase().includes('nextval') || diff --git a/backend/src/helpers/constants/constants.ts b/backend/src/helpers/constants/constants.ts index 6300a1141..75bacacb2 100644 --- a/backend/src/helpers/constants/constants.ts +++ b/backend/src/helpers/constants/constants.ts @@ -225,7 +225,7 @@ export const Constants = { return []; } - const testConnections: Array = Constants.getTestConnectionsFromDSN() || []; + const testConnections: Array = Constants.getTestConnectionsFromDSN() || []; if (!testConnections.length) { testConnections.push( Constants.TEST_CONNECTION_TO_ORACLE as CreateConnectionDto, @@ -237,14 +237,17 @@ export const Constants = { ); } - return testConnections.filter((dto) => { + return testConnections.filter((dto): dto is CreateConnectionDto => { + if (!dto) { + return false; + } const values = Object.values(dto); const nullElementIndex = values.indexOf(null); return nullElementIndex < 0; }); }, - getTestConnectionsFromDSN: (): Array => { + getTestConnectionsFromDSN: (): Array | null => { if (!isSaaS()) { return []; } diff --git a/backend/src/helpers/encryption/encryptor.ts b/backend/src/helpers/encryption/encryptor.ts index e767176f8..9c2127b08 100644 --- a/backend/src/helpers/encryption/encryptor.ts +++ b/backend/src/helpers/encryption/encryptor.ts @@ -17,7 +17,11 @@ const KEY_LENGTH = 32; export class Encryptor { static getPrivateKey(): string { - return appConfig.auth.privateKey; + const privateKey = appConfig.auth.privateKey; + if (!privateKey) { + throw new Error('Encryption private key is not configured.'); + } + return privateKey; } private static deriveKey(passphrase: string, salt: Buffer): Buffer { @@ -95,7 +99,9 @@ export class Encryptor { return encryptedData.startsWith('$v2:'); } - static encryptData(data: string): string { + static encryptData(data: string): string; + static encryptData(data: string | null | undefined): string | null | undefined; + static encryptData(data: string | null | undefined): string | null | undefined { if (data === null || data === undefined) { return data; } @@ -108,7 +114,9 @@ export class Encryptor { } } - static decryptData(encryptedData: string): string { + static decryptData(encryptedData: string): string; + static decryptData(encryptedData: string | null | undefined): string | null | undefined; + static decryptData(encryptedData: string | null | undefined): string | null | undefined { if (encryptedData === null || encryptedData === undefined) { return encryptedData; } @@ -125,7 +133,9 @@ export class Encryptor { } } - static async decryptDataAsync(encryptedData: string): Promise { + static async decryptDataAsync(encryptedData: string): Promise; + static async decryptDataAsync(encryptedData: string | null | undefined): Promise; + static async decryptDataAsync(encryptedData: string | null | undefined): Promise { if (encryptedData === null || encryptedData === undefined) { return encryptedData; } @@ -142,14 +152,18 @@ export class Encryptor { } } - static encryptDataMasterPwd(data: string, masterPwd: string): string { + static encryptDataMasterPwd(data: string, masterPwd: string): string; + static encryptDataMasterPwd(data: string | null | undefined, masterPwd: string): string | null | undefined; + static encryptDataMasterPwd(data: string | null | undefined, masterPwd: string): string | null | undefined { if (data === null || data === undefined) { return data; } return Encryptor.encryptDataV2(data, masterPwd); } - static decryptDataMasterPwd(encryptedData: string, masterPwd: string): string { + static decryptDataMasterPwd(encryptedData: string, masterPwd: string): string; + static decryptDataMasterPwd(encryptedData: string | null | undefined, masterPwd: string): string | null | undefined; + static decryptDataMasterPwd(encryptedData: string | null | undefined, masterPwd: string): string | null | undefined { if (encryptedData === null || encryptedData === undefined) { return encryptedData; } @@ -333,9 +347,16 @@ export class Encryptor { }); } - static async verifyUserPassword(receivedPassword: string, hashedPassword: string): Promise { + static async verifyUserPassword( + receivedPassword: string, + hashedPassword: string | null | undefined, + ): Promise { return new Promise((resolve, reject) => { try { + if (!hashedPassword) { + resolve(false); + return; + } const passwordHashParts: Array = hashedPassword.split('$'); const alg = passwordHashParts[0]; const iterations = parseInt(passwordHashParts[1], 10); diff --git a/backend/src/helpers/is-connection-entity-agent.ts b/backend/src/helpers/is-connection-entity-agent.ts index 99709e18e..b6af43963 100644 --- a/backend/src/helpers/is-connection-entity-agent.ts +++ b/backend/src/helpers/is-connection-entity-agent.ts @@ -21,7 +21,7 @@ export function isConnectionEntityAgent(connection: ConnectionEntity | CreateCon return agentTypes.includes(connection.type as ConnectionTypesEnum); } -export function isConnectionTypeAgent(type: ConnectionTypesEnum | string): boolean { +export function isConnectionTypeAgent(type: ConnectionTypesEnum | string | null | undefined): boolean { const connectionTypes = [ ConnectionTypeTestEnum.agent_postgres, ConnectionTypeTestEnum.agent_mysql, diff --git a/backend/src/helpers/operate-values-between-curlies.ts b/backend/src/helpers/operate-values-between-curlies.ts index 18a9e151f..04df24ddc 100644 --- a/backend/src/helpers/operate-values-between-curlies.ts +++ b/backend/src/helpers/operate-values-between-curlies.ts @@ -3,7 +3,7 @@ import { safeRegex } from 'safe-regex2'; export function getValuesBetweenCurlies(str: string): Array { const valuesArr = []; const regExp = /{{([^}}]+)}}/g; - let tmpText: RegExpExecArray; + let tmpText: RegExpExecArray | null; while ((tmpText = regExp.exec(str))) { valuesArr.push(tmpText[1]); } @@ -15,8 +15,9 @@ export function replaceTextInCurlies(str: string, replaceArr: Array, rep // added safe regexp check // eslint-disable-next-line security/detect-non-literal-regexp const regExp = new RegExp('{{' + replaceArr.at(i) + '}}', 'gi'); - if (safeRegex(regExp)) { - str = str.replace(regExp, replaceWithArr.at(i)); + const replacement = replaceWithArr.at(i); + if (safeRegex(regExp) && replacement !== undefined) { + str = str.replace(regExp, replacement); } } return str; diff --git a/backend/src/helpers/parsers/string-connection-to-database-parsers.ts b/backend/src/helpers/parsers/string-connection-to-database-parsers.ts index decf84a6c..63c2e3d89 100644 --- a/backend/src/helpers/parsers/string-connection-to-database-parsers.ts +++ b/backend/src/helpers/parsers/string-connection-to-database-parsers.ts @@ -5,11 +5,11 @@ import { CreateConnectionDto } from '../../entities/connection/application/dto/c export const parseTestPostgresConnectionString = (connectionString: string): Partial => { const url = new URL(connectionString); const config: Partial = {}; - config.host = url.hostname || null; + config.host = url.hostname || undefined; config.port = parseInt(url.port, 10) || 5432; - config.username = url.username || null; - config.password = url.password || null; - config.database = url.pathname.split('/')[1] || null; + config.username = url.username || undefined; + config.password = url.password || undefined; + config.database = url.pathname.split('/')[1] || undefined; config.schema = 'public'; config.type = ConnectionTypesEnum.postgres; config.title = 'School of fish'; @@ -17,7 +17,7 @@ export const parseTestPostgresConnectionString = (connectionString: string): Par config.ssh = url.searchParams.get('ssh') === 'true'; config.privateSSHKey = url.searchParams.get('privateSSHKey') || undefined; config.sshHost = url.searchParams.get('sshHost') || undefined; - config.sshPort = parseInt(url.searchParams.get('sshPort'), 10) || undefined; + config.sshPort = parseInt(url.searchParams.get('sshPort') ?? '', 10) || undefined; config.sshUsername = url.searchParams.get('sshUsername') || undefined; config.ssl = url.searchParams.get('ssl') === 'true'; config.cert = url.searchParams.get('cert') || undefined; @@ -28,18 +28,18 @@ export const parseTestPostgresConnectionString = (connectionString: string): Par export const parseTestMySQLConnectionString = (connectionString: string): Partial => { const url = new URL(connectionString); const config: Partial = {}; - config.host = url.hostname || null; + config.host = url.hostname || undefined; config.port = parseInt(url.port, 10) || 3306; - config.username = url.username || null; - config.password = url.password || null; - config.database = url.pathname.split('/')[1] || null; - config.type = ConnectionTypesEnum.mysql || null; + config.username = url.username || undefined; + config.password = url.password || undefined; + config.database = url.pathname.split('/')[1] || undefined; + config.type = ConnectionTypesEnum.mysql; config.title = 'Coworking hub'; config.isTestConnection = true; config.ssh = url.searchParams.get('ssh') === 'true'; config.privateSSHKey = url.searchParams.get('privateSSHKey') || undefined; config.sshHost = url.searchParams.get('sshHost') || undefined; - config.sshPort = parseInt(url.searchParams.get('sshPort'), 10) || undefined; + config.sshPort = parseInt(url.searchParams.get('sshPort') ?? '', 10) || undefined; config.sshUsername = url.searchParams.get('sshUsername') || undefined; config.ssl = url.searchParams.get('ssl') === 'true'; config.cert = url.searchParams.get('cert') || undefined; @@ -50,18 +50,18 @@ export const parseTestMySQLConnectionString = (connectionString: string): Partia export const parseTestMSSQLConnectionString = (connectionString: string): Partial => { const url = new URL(connectionString); const config: Partial = {}; - config.host = url.hostname || null; + config.host = url.hostname || undefined; config.port = parseInt(url.port, 10) || 1433; - config.username = url.username || null; - config.password = url.password || null; - config.database = url.pathname.split('/')[1] || null; + config.username = url.username || undefined; + config.password = url.password || undefined; + config.database = url.pathname.split('/')[1] || undefined; config.type = ConnectionTypesEnum.mssql; config.title = 'Online shop'; config.isTestConnection = true; config.ssh = url.searchParams.get('ssh') === 'true'; config.privateSSHKey = url.searchParams.get('privateSSHKey') || undefined; config.sshHost = url.searchParams.get('sshHost') || undefined; - config.sshPort = parseInt(url.searchParams.get('sshPort'), 10) || undefined; + config.sshPort = parseInt(url.searchParams.get('sshPort') ?? '', 10) || undefined; config.sshUsername = url.searchParams.get('sshUsername') || undefined; config.ssl = url.searchParams.get('ssl') === 'true'; config.cert = url.searchParams.get('cert') || undefined; @@ -74,11 +74,11 @@ export const parseTestMSSQLConnectionString = (connectionString: string): Partia export const parseTestOracleDBConnectionString = (connectionString: string): Partial => { const url = new URL(connectionString); const config: Partial = {}; - config.host = url.hostname || null; + config.host = url.hostname || undefined; config.port = parseInt(url.port, 10) || 1521; - config.username = url.username || null; - config.password = url.password || null; - config.sid = url.pathname.split('/')[1] || null; + config.username = url.username || undefined; + config.password = url.password || undefined; + config.sid = url.pathname.split('/')[1] || undefined; const params = new URLSearchParams(url.search); config.database = params.get('database') || undefined; config.type = ConnectionTypesEnum.oracledb; @@ -88,7 +88,7 @@ export const parseTestOracleDBConnectionString = (connectionString: string): Par config.ssh = params.get('ssh') === 'true'; config.privateSSHKey = params.get('privateSSHKey') || undefined; config.sshHost = params.get('sshHost') || undefined; - config.sshPort = parseInt(params.get('sshPort'), 10) || undefined; + config.sshPort = parseInt(params.get('sshPort') ?? '', 10) || undefined; config.sshUsername = params.get('sshUsername') || undefined; config.ssl = params.get('ssl') === 'true'; config.cert = params.get('cert') || undefined; @@ -99,11 +99,11 @@ export const parseTestOracleDBConnectionString = (connectionString: string): Par export const parseTestMongoDBConnectionString = (connectionString: string): Partial => { const url = new URL(connectionString); const config: Partial = {}; - config.host = url.hostname || null; + config.host = url.hostname || undefined; config.port = parseInt(url.port, 10) || 27017; - config.username = url.username || null; - config.password = url.password || null; - config.database = url.pathname.split('/')[1] || null; + config.username = url.username || undefined; + config.password = url.password || undefined; + config.database = url.pathname.split('/')[1] || undefined; config.type = ConnectionTypesEnum.mongodb; config.title = 'Movie theater'; const authSource = url.searchParams.get('authSource'); @@ -114,7 +114,7 @@ export const parseTestMongoDBConnectionString = (connectionString: string): Part config.ssh = url.searchParams.get('ssh') === 'true'; config.privateSSHKey = url.searchParams.get('privateSSHKey') || undefined; config.sshHost = url.searchParams.get('sshHost') || undefined; - config.sshPort = parseInt(url.searchParams.get('sshPort'), 10) || undefined; + config.sshPort = parseInt(url.searchParams.get('sshPort') ?? '', 10) || undefined; config.sshUsername = url.searchParams.get('sshUsername') || undefined; config.ssl = url.searchParams.get('ssl') === 'true'; config.cert = url.searchParams.get('cert') || undefined; @@ -128,8 +128,8 @@ export const parseTestDynamoDBConnectionString = (connectionString: string): Par const [username, password] = credentials.split(':'); const url = new URL(nestedUrl); const config: Partial = {}; - config.username = username || null; - config.password = password || null; + config.username = username || undefined; + config.password = password || undefined; const host = `${url.protocol}//${url.hostname}:${url.port}`; config.host = host; config.type = ConnectionTypesEnum.dynamodb; diff --git a/backend/src/helpers/validators/validation-helper.ts b/backend/src/helpers/validators/validation-helper.ts index b64bc3943..69f853759 100644 --- a/backend/src/helpers/validators/validation-helper.ts +++ b/backend/src/helpers/validators/validation-helper.ts @@ -42,7 +42,7 @@ export class ValidationHelper { if (typeof jsonString !== 'string') { return false; } - validator.isJSON(jsonString); + return validator.isJSON(jsonString); } public static isValidJWT(token: string): boolean { @@ -74,7 +74,7 @@ export class ValidationHelper { return false; } for (let i = 0; i < id.length; i++) { - if (!validChars.includes(id.at(i))) { + if (!validChars.includes(id[i])) { return false; } } diff --git a/backend/src/interceptors/sentry.interceptor.ts b/backend/src/interceptors/sentry.interceptor.ts index 0895d6cf0..b4c5b8946 100644 --- a/backend/src/interceptors/sentry.interceptor.ts +++ b/backend/src/interceptors/sentry.interceptor.ts @@ -30,6 +30,7 @@ export class SentryInterceptor implements NestInterceptor { ); } catch (e) { console.error(e); + return next.handle(); } } } diff --git a/backend/src/microservices/gateways/saas-gateway.ts/base-saas-gateway.service.ts b/backend/src/microservices/gateways/saas-gateway.ts/base-saas-gateway.service.ts index 58fe4caf7..e0867361d 100644 --- a/backend/src/microservices/gateways/saas-gateway.ts/base-saas-gateway.service.ts +++ b/backend/src/microservices/gateways/saas-gateway.ts/base-saas-gateway.service.ts @@ -17,7 +17,7 @@ export class BaseSaasGatewayService { async sendRequestToSaaS( patch: string, method: SaaSRequestMethod, - body: Record, + body: Record | null, ): Promise { try { if (!isSaaS()) { diff --git a/backend/src/microservices/gateways/saas-gateway.ts/saas-company-gateway.service.ts b/backend/src/microservices/gateways/saas-gateway.ts/saas-company-gateway.service.ts index 5b125c41a..ad7f1757d 100644 --- a/backend/src/microservices/gateways/saas-gateway.ts/saas-company-gateway.service.ts +++ b/backend/src/microservices/gateways/saas-gateway.ts/saas-company-gateway.service.ts @@ -10,6 +10,9 @@ import { FoundSassCompanyInfoDS } from './data-structures/found-saas-company-inf export class SaasCompanyGatewayService extends BaseSaasGatewayService { public async getCompanyInfo(companyId: string): Promise { const result = await this.sendRequestToSaaS(`/webhook/company/${companyId}/`, 'GET', null); + if (!result) { + return null; + } if (this.isDataFoundSassCompanyInfoDS(result.body)) { return result.body; } @@ -18,6 +21,9 @@ export class SaasCompanyGatewayService extends BaseSaasGatewayService { public async deleteCompany(companyId: string): Promise { const result = await this.sendRequestToSaaS(`/webhook/company/${companyId}/`, 'DELETE', null); + if (!result) { + return null; + } if (result.status > 299) { throw new HttpException( { @@ -37,6 +43,9 @@ export class SaasCompanyGatewayService extends BaseSaasGatewayService { public async getCompanyIdByCustomDomain(customCompanyDomain: string): Promise { const result = await this.sendRequestToSaaS(`/webhook/company/domain/${customCompanyDomain}/`, 'GET', null); + if (!result) { + return null; + } if (result.status > 299) { throw new HttpException( { @@ -57,6 +66,9 @@ export class SaasCompanyGatewayService extends BaseSaasGatewayService { return null; } const result = await this.sendRequestToSaaS(`/webhook/company/${companyId}/domain/`, 'GET', null); + if (!result) { + return null; + } if (result.status > 299) { throw new HttpException( { @@ -77,6 +89,9 @@ export class SaasCompanyGatewayService extends BaseSaasGatewayService { return null; } const result = await this.sendRequestToSaaS(`/webhook/company/${companyId}/recount/`, 'POST', null); + if (!result) { + return null; + } if (result.status > 299) { throw new HttpException( { diff --git a/backend/src/microservices/saas-microservice/data-structures/found-connection-info.ro.ts b/backend/src/microservices/saas-microservice/data-structures/found-connection-info.ro.ts index 4e6de6902..594889205 100644 --- a/backend/src/microservices/saas-microservice/data-structures/found-connection-info.ro.ts +++ b/backend/src/microservices/saas-microservice/data-structures/found-connection-info.ro.ts @@ -6,31 +6,31 @@ export class FoundConnectionInfoRO { id: string; @ApiProperty() - title: string; + title?: string; @ApiProperty({ enum: ConnectionTypesEnum }) - type: ConnectionTypesEnum; + type?: ConnectionTypesEnum | null; @ApiProperty() - host: string; + host?: string | null; @ApiProperty() - port: number; + port?: number; @ApiProperty() - database: string; + database?: string | null; @ApiProperty() - schema: string; + schema?: string | null; @ApiProperty() - sid: string; + sid?: string | null; @ApiProperty() - ssh: boolean; + ssh?: boolean; @ApiProperty() - ssl: boolean; + ssl?: boolean | null; @ApiProperty() createdAt: Date; @@ -45,14 +45,14 @@ export class FoundConnectionInfoRO { is_frozen: boolean; @ApiProperty() - masterEncryption: boolean; + masterEncryption?: boolean; @ApiProperty() - azure_encryption: boolean; + azure_encryption?: boolean; @ApiProperty() - authSource: string; + authSource?: string | null; @ApiProperty() - dataCenter: string | null; + dataCenter?: string | null; } diff --git a/backend/src/microservices/saas-microservice/data-structures/found-user-info.ro.ts b/backend/src/microservices/saas-microservice/data-structures/found-user-info.ro.ts new file mode 100644 index 000000000..886f45093 --- /dev/null +++ b/backend/src/microservices/saas-microservice/data-structures/found-user-info.ro.ts @@ -0,0 +1,5 @@ +import { UserEntity } from '../../../entities/user/user.entity.js'; + +type DataKeys = { [K in keyof T]: T[K] extends (...args: never[]) => unknown ? never : K }[keyof T]; +export type FoundUserInfoRO = Omit>, 'password'>; +export type FoundUserInfoWithoutCompanyRO = Omit; diff --git a/backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts b/backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts index 54ad58a8e..3262ceb98 100644 --- a/backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts +++ b/backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts @@ -5,19 +5,19 @@ export class HostedConnectionCredentialsRO { connectionId: string; @ApiProperty() - host: string; + host?: string | null; @ApiProperty() - port: number; + port?: number; @ApiProperty() - database: string; + database?: string | null; @ApiProperty() - username: string; + username?: string | null; @ApiProperty() - password: string; + password?: string | null; @ApiProperty() is_frozen: boolean; diff --git a/backend/src/microservices/saas-microservice/saas.controller.ts b/backend/src/microservices/saas-microservice/saas.controller.ts index 38bfa3ab1..fdea7c1ae 100644 --- a/backend/src/microservices/saas-microservice/saas.controller.ts +++ b/backend/src/microservices/saas-microservice/saas.controller.ts @@ -28,6 +28,7 @@ import { CreatedConnectionResponse, SuccessResponse } from './data-structures/co 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 { FoundConnectionInfoRO } from './data-structures/found-connection-info.ro.js'; +import { FoundUserInfoRO, FoundUserInfoWithoutCompanyRO } from './data-structures/found-user-info.ro.js'; import { GetConnectionsInfoByIdsDS } from './data-structures/get-connections-info-by-ids.ds.js'; import { GetHostedConnectionCredentialsDto } from './data-structures/get-hosted-connection-credentials.dto.js'; import { HostedConnectionCredentialsRO } from './data-structures/hosted-connection-credentials.ro.js'; @@ -130,7 +131,7 @@ export class SaasController { status: 200, }) @Get('/user/:userId') - async getUserInfo(@Param('userId') userId: string, @Query('companyId') companyId: string): Promise { + async getUserInfo(@Param('userId') userId: string, @Query('companyId') companyId: string): Promise { return await this.getUserInfoUseCase.execute({ userId, companyId }); } @@ -143,7 +144,7 @@ export class SaasController { async getUsersInfoByEmail( @Param('userEmail') userEmail: string, @Query('externalProvider') externalProvider: ExternalRegistrationProviderEnum, - ): Promise> { + ): Promise> { return await this.getUsersInfosByEmailUseCase.execute({ userEmail, externalProvider }); } diff --git a/backend/src/microservices/saas-microservice/use-cases/create-connection-for-hosted-db.use.case.ts b/backend/src/microservices/saas-microservice/use-cases/create-connection-for-hosted-db.use.case.ts index faa6806f4..0ee0a23c5 100644 --- a/backend/src/microservices/saas-microservice/use-cases/create-connection-for-hosted-db.use.case.ts +++ b/backend/src/microservices/saas-microservice/use-cases/create-connection-for-hosted-db.use.case.ts @@ -7,6 +7,7 @@ import { BaseType } from '../../../common/data-injection.tokens.js'; import { generateCedarPolicyForGroup } from '../../../entities/cedar-authorization/cedar-policy-generator.js'; import { ConnectionEntity } from '../../../entities/connection/connection.entity.js'; import { readSslCertificate } from '../../../entities/connection/ssl-certificate/read-certificate.js'; +import { GroupEntity } from '../../../entities/group/group.entity.js'; import { AccessLevelEnum } from '../../../enums/access-level.enum.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { slackPostMessage } from '../../../helpers/slack/slack-post-message.js'; @@ -89,7 +90,8 @@ export class CreateConnectionForHostedDbUseCase tables: [], }); await this._dbContext.groupRepository.saveNewOrUpdatedGroup(createdAdminGroup); - delete createdAdminGroup.connection; + const groupWithoutConnection: Partial = createdAdminGroup; + delete groupWithoutConnection.connection; await this._dbContext.userRepository.saveUserEntity(connectionAuthor); savedConnection.groups = [createdAdminGroup]; @@ -99,8 +101,10 @@ export class CreateConnectionForHostedDbUseCase const connectionToUpdate = await this._dbContext.connectionRepository.findOne({ where: { id: savedConnection.id }, }); - connectionToUpdate.company = foundCompany; - await this._dbContext.connectionRepository.saveUpdatedConnection(connectionToUpdate); + if (connectionToUpdate) { + connectionToUpdate.company = foundCompany; + await this._dbContext.connectionRepository.saveUpdatedConnection(connectionToUpdate); + } } await slackPostMessage(Messages.USER_CREATED_CONNECTION(connectionAuthor.email, ConnectionTypesEnum.postgres)); diff --git a/backend/src/microservices/saas-microservice/use-cases/delete-connection-for-hosted-db.use.case.ts b/backend/src/microservices/saas-microservice/use-cases/delete-connection-for-hosted-db.use.case.ts index 84e2025c9..45ee6671a 100644 --- a/backend/src/microservices/saas-microservice/use-cases/delete-connection-for-hosted-db.use.case.ts +++ b/backend/src/microservices/saas-microservice/use-cases/delete-connection-for-hosted-db.use.case.ts @@ -2,9 +2,9 @@ import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; 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 { Messages } from '../../../exceptions/text/messages.js'; import { CreatedConnectionDTO } from '../../../entities/connection/application/dto/created-connection.dto.js'; import { buildCreatedConnectionDs } from '../../../entities/connection/utils/build-created-connection.ds.js'; +import { Messages } from '../../../exceptions/text/messages.js'; import { DeleteConnectionForHostedDbDto } from '../data-structures/delete-connection-for-hosted-db.dto.js'; import { IDeleteConnectionForHostedDb } from './saas-use-cases.interface.js'; @@ -25,13 +25,14 @@ export class DeleteConnectionForHostedDbUseCase const connectionToDelete = await this._dbContext.connectionRepository.findAndDecryptConnection( hostedDatabaseId, - null, + '', ); if (!connectionToDelete) { throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); } - const foundCompany = await this._dbContext.companyInfoRepository.findCompanyInfoByCompanyIdWithoutConnections(companyId); + const foundCompany = + await this._dbContext.companyInfoRepository.findCompanyInfoByCompanyIdWithoutConnections(companyId); if (!foundCompany) { throw new NotFoundException(Messages.COMPANY_NOT_FOUND); } diff --git a/backend/src/microservices/saas-microservice/use-cases/get-user-info.use.case.ts b/backend/src/microservices/saas-microservice/use-cases/get-user-info.use.case.ts index 00f9b7a35..a84d6ec40 100644 --- a/backend/src/microservices/saas-microservice/use-cases/get-user-info.use.case.ts +++ b/backend/src/microservices/saas-microservice/use-cases/get-user-info.use.case.ts @@ -2,13 +2,14 @@ import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; 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 { UserEntity } from '../../../entities/user/user.entity.js'; import { Messages } from '../../../exceptions/text/messages.js'; +import { FoundUserInfoRO } from '../data-structures/found-user-info.ro.js'; import { GetUserInfoByIdDS } from '../data-structures/get-user-info.ds.js'; +import { buildFoundUserInfoRO } from '../utils/build-found-user-info-ro.js'; import { IGetUserInfo } from './saas-use-cases.interface.js'; @Injectable() -export class GetUserInfoUseCase extends AbstractUseCase implements IGetUserInfo { +export class GetUserInfoUseCase extends AbstractUseCase implements IGetUserInfo { constructor( @Inject(BaseType.GLOBAL_DB_CONTEXT) protected _dbContext: IGlobalDatabaseContext, @@ -16,15 +17,11 @@ export class GetUserInfoUseCase extends AbstractUseCase { + protected async implementation(inputData: GetUserInfoByIdDS): Promise { const { userId, companyId } = inputData; - let foundUser: UserEntity; - if (companyId) { - foundUser = await this._dbContext.userRepository.findOneUserByIdAndCompanyId(userId, companyId); - } else { - foundUser = await this._dbContext.userRepository.findOneUserById(userId); - } - delete foundUser.password; + const foundUser = companyId + ? await this._dbContext.userRepository.findOneUserByIdAndCompanyId(userId, companyId) + : await this._dbContext.userRepository.findOneUserById(userId); if (!foundUser) { throw new HttpException( { @@ -33,6 +30,6 @@ export class GetUserInfoUseCase extends AbstractUseCase> + extends AbstractUseCase> implements ISaasGetUsersInfosByEmail { constructor( @@ -17,16 +19,12 @@ export class GetUsersInfosByEmailUseCase super(); } - protected async implementation(usersData: GetUsersInfosByEmailDS): Promise> { + protected async implementation(usersData: GetUsersInfosByEmailDS): Promise> { const { userEmail, externalProvider } = usersData; const foundUsers: Array = await this._dbContext.userRepository.findAllUsersWithEmail( userEmail, externalProvider, ); - return foundUsers.map((user) => { - delete user.password; - delete user.company; - return user; - }); + return foundUsers.map((user) => buildFoundUserInfoWithoutCompanyRO(user)); } } diff --git a/backend/src/microservices/saas-microservice/use-cases/login-with-github.use.case.ts b/backend/src/microservices/saas-microservice/use-cases/login-with-github.use.case.ts index 701ebee9a..afcbefd44 100644 --- a/backend/src/microservices/saas-microservice/use-cases/login-with-github.use.case.ts +++ b/backend/src/microservices/saas-microservice/use-cases/login-with-github.use.case.ts @@ -83,14 +83,14 @@ export class LoginUserWithGithubUseCase email: string, userId: string | null, status: SignInStatusEnum, - ipAddress: string, - userAgent: string, + ipAddress: string | undefined, + userAgent: string | undefined, failureReason?: string, ): Promise { try { await this.signInAuditService.createSignInAuditRecord({ email, - userId, + userId: userId ?? undefined, status, signInMethod: SignInMethodEnum.GITHUB, ipAddress, diff --git a/backend/src/microservices/saas-microservice/use-cases/login-with-google.use.case.ts b/backend/src/microservices/saas-microservice/use-cases/login-with-google.use.case.ts index 0efaf4c47..9286aff5c 100644 --- a/backend/src/microservices/saas-microservice/use-cases/login-with-google.use.case.ts +++ b/backend/src/microservices/saas-microservice/use-cases/login-with-google.use.case.ts @@ -29,7 +29,7 @@ export class LoginWithGoogleUseCase protected async implementation(inputData: SaasRegisterUserWithGoogleDS): Promise { const { email, name, glidCookieValue, ipAddress, userAgent } = inputData; - const foundUser: UserEntity = await this._dbContext.userRepository.findOneUserByEmail( + const foundUser: UserEntity | null = await this._dbContext.userRepository.findOneUserByEmail( email, ExternalRegistrationProviderEnum.GOOGLE, ); @@ -61,14 +61,14 @@ export class LoginWithGoogleUseCase email: string, userId: string | null, status: SignInStatusEnum, - ipAddress: string, - userAgent: string, + ipAddress: string | undefined, + userAgent: string | undefined, failureReason?: string, ): Promise { try { await this.signInAuditService.createSignInAuditRecord({ email, - userId, + userId: userId ?? undefined, status, signInMethod: SignInMethodEnum.GOOGLE, ipAddress, diff --git a/backend/src/microservices/saas-microservice/use-cases/register-demo-user-account.use.case.ts b/backend/src/microservices/saas-microservice/use-cases/register-demo-user-account.use.case.ts index b68c5e6e6..cbef8eabb 100644 --- a/backend/src/microservices/saas-microservice/use-cases/register-demo-user-account.use.case.ts +++ b/backend/src/microservices/saas-microservice/use-cases/register-demo-user-account.use.case.ts @@ -71,7 +71,7 @@ export class SaasRegisterDemoUserAccountUseCase createdAt: savedUser.createdAt, isActive: savedUser.isActive, email: savedUser.email, - intercom_hash: null, + intercom_hash: undefined, name: savedUser.name, role: savedUser.role, is_2fa_enabled: false, diff --git a/backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts b/backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts index 979a397f5..907593f73 100644 --- a/backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts +++ b/backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts @@ -9,12 +9,13 @@ 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 { FoundConnectionInfoRO } from '../data-structures/found-connection-info.ro.js'; +import { FoundUserInfoRO, FoundUserInfoWithoutCompanyRO } from '../data-structures/found-user-info.ro.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 { GetHostedConnectionCredentialsDto } from '../data-structures/get-hosted-connection-credentials.dto.js'; -import { HostedConnectionCredentialsRO } from '../data-structures/hosted-connection-credentials.ro.js'; import { GetUserInfoByIdDS } from '../data-structures/get-user-info.ds.js'; import { GetUsersInfosByEmailDS } from '../data-structures/get-users-infos-by-email.ds.js'; +import { HostedConnectionCredentialsRO } from '../data-structures/hosted-connection-credentials.ro.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'; @@ -28,11 +29,11 @@ export interface ICompanyRegistration { } export interface IGetUserInfo { - execute(userData: GetUserInfoByIdDS): Promise; + execute(userData: GetUserInfoByIdDS): Promise; } export interface ISaasGetUsersInfosByEmail { - execute(userData: GetUsersInfosByEmailDS): Promise; + execute(userData: GetUsersInfosByEmailDS): Promise; } export interface ISaasRegisterUser { diff --git a/backend/src/microservices/saas-microservice/use-cases/saas-usual-register-user.use.case.ts b/backend/src/microservices/saas-microservice/use-cases/saas-usual-register-user.use.case.ts index cefc52480..b6959b18b 100644 --- a/backend/src/microservices/saas-microservice/use-cases/saas-usual-register-user.use.case.ts +++ b/backend/src/microservices/saas-microservice/use-cases/saas-usual-register-user.use.case.ts @@ -73,7 +73,7 @@ export class SaasUsualRegisterUseCase createdAt: savedUser.createdAt, isActive: savedUser.isActive, email: savedUser.email, - intercom_hash: null, + intercom_hash: undefined, name: savedUser.name, role: savedUser.role, is_2fa_enabled: false, @@ -87,7 +87,7 @@ export class SaasUsualRegisterUseCase savedUser: UserEntity, testConnections: Array, companyId: string, - companyName: string, + companyName: string | undefined, ): Promise { if (!companyName) { companyName = 'New Company'; diff --git a/backend/src/microservices/saas-microservice/utils/build-found-user-info-ro.ts b/backend/src/microservices/saas-microservice/utils/build-found-user-info-ro.ts new file mode 100644 index 000000000..46c1eac33 --- /dev/null +++ b/backend/src/microservices/saas-microservice/utils/build-found-user-info-ro.ts @@ -0,0 +1,12 @@ +import { UserEntity } from '../../../entities/user/user.entity.js'; +import { FoundUserInfoRO, FoundUserInfoWithoutCompanyRO } from '../data-structures/found-user-info.ro.js'; + +export function buildFoundUserInfoRO(user: UserEntity): FoundUserInfoRO { + const { password: _password, ...userInfo } = user; + return userInfo; +} + +export function buildFoundUserInfoWithoutCompanyRO(user: UserEntity): FoundUserInfoWithoutCompanyRO { + const { password: _password, company: _company, ...userInfo } = user; + return userInfo; +} diff --git a/backend/src/selfhosted-operations/application/use-cases/create-initial-user.use.case.ts b/backend/src/selfhosted-operations/application/use-cases/create-initial-user.use.case.ts index 23542cf96..ae12c4569 100644 --- a/backend/src/selfhosted-operations/application/use-cases/create-initial-user.use.case.ts +++ b/backend/src/selfhosted-operations/application/use-cases/create-initial-user.use.case.ts @@ -55,6 +55,10 @@ export class CreateInitialUserUseCase savedUser.company = savedCompanyInfo; const finalUser = await this._dbContext.userRepository.saveUserEntity(savedUser); - return buildSimpleUserInfoDs(finalUser); + const userInfo = buildSimpleUserInfoDs(finalUser); + if (!userInfo) { + throw new BadRequestException(Messages.USER_NOT_FOUND); + } + return userInfo; } } diff --git a/backend/src/shared/config/app-config.ts b/backend/src/shared/config/app-config.ts index b90058127..a3bfd33c5 100644 --- a/backend/src/shared/config/app-config.ts +++ b/backend/src/shared/config/app-config.ts @@ -322,21 +322,21 @@ export class AppConfig { } private parseTypeORMUrl(url: string): { - host: string; + host: string | undefined; port: number; - username: string; - password: string; - database: string; + username: string | undefined; + password: string | undefined; + database: string | undefined; ssl: any; } { const parsingResult = parse.parse(url); const { host, port, user, password, database, ssl } = parsingResult; return { - host, - port: parseInt(port, 10), - username: user, - password, - database, + host: host ?? undefined, + port: parseInt(port ?? '', 10), + username: user ?? undefined, + password: password ?? undefined, + database: database ?? undefined, ssl, }; } diff --git a/backend/src/shared/database/database.providers.ts b/backend/src/shared/database/database.providers.ts index 2836d0a1b..8e9c9773e 100644 --- a/backend/src/shared/database/database.providers.ts +++ b/backend/src/shared/database/database.providers.ts @@ -2,7 +2,7 @@ import { DataSource } from 'typeorm'; import { BaseType } from '../../common/data-injection.tokens.js'; import { configService } from '../config/config.service.js'; -let appDataSourceCache: DataSource = null; +let appDataSourceCache: DataSource | null = null; export const databaseProviders = [ { diff --git a/backend/src/shared/services/turnstile.service.ts b/backend/src/shared/services/turnstile.service.ts index abbeb3a4f..c49decc47 100644 --- a/backend/src/shared/services/turnstile.service.ts +++ b/backend/src/shared/services/turnstile.service.ts @@ -26,6 +26,10 @@ export class TurnstileService { throw new BadRequestException('Turnstile token is required.'); } + if (!secretKey) { + throw new BadRequestException('Turnstile secret key is not configured.'); + } + const formData = new URLSearchParams(); formData.append('secret', secretKey); formData.append('response', token); diff --git a/backend/tsconfig.src.json b/backend/tsconfig.src.json index af95aca28..f0d297eab 100644 --- a/backend/tsconfig.src.json +++ b/backend/tsconfig.src.json @@ -4,6 +4,7 @@ "noImplicitAny": true, "strictBindCallApply": true, "strictFunctionTypes": true, + "strictNullChecks": true, "alwaysStrict": true }, "include": ["src"], From f2a00eceec8c4e96f6f376b0418015fb1384d088 Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Tue, 2 Jun 2026 08:24:30 +0000 Subject: [PATCH 2/3] refactor: improve error handling in TablesReceiveGuard --- backend/src/guards/tables-receive.guard.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/src/guards/tables-receive.guard.ts b/backend/src/guards/tables-receive.guard.ts index 8d72099d3..212b4fe2e 100644 --- a/backend/src/guards/tables-receive.guard.ts +++ b/backend/src/guards/tables-receive.guard.ts @@ -1,4 +1,11 @@ -import { BadRequestException, CanActivate, ExecutionContext, Inject, Injectable } from '@nestjs/common'; +import { + BadRequestException, + CanActivate, + ExecutionContext, + Inject, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; import { Observable } from 'rxjs'; import { IRequestWithCognitoInfo } from '../authorization/cognito-decoded.interface.js'; import { IGlobalDatabaseContext } from '../common/application/global-database-context.interface.js'; @@ -19,7 +26,7 @@ export class TablesReceiveGuard implements CanActivate { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const cognitoUserName = request.decoded.sub; if (!cognitoUserName) { - reject(new BadRequestException(Messages.CONNECTION_NOT_FOUND)); + reject(new UnauthorizedException(Messages.DONT_HAVE_PERMISSIONS)); return; } const connectionId: string | undefined = request.params?.slug || request.params?.connectionId; From c0cd2bcc0c0e4020662471cfa24fa35a8f60b621 Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Tue, 2 Jun 2026 08:41:43 +0000 Subject: [PATCH 3/3] refactor: update error handling in PaidFeatureGuard for unauthorized access --- backend/src/guards/paid-feature.guard.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/src/guards/paid-feature.guard.ts b/backend/src/guards/paid-feature.guard.ts index c311c5c20..543f51e24 100644 --- a/backend/src/guards/paid-feature.guard.ts +++ b/backend/src/guards/paid-feature.guard.ts @@ -1,4 +1,11 @@ -import { BadRequestException, CanActivate, ExecutionContext, Inject, Injectable } from '@nestjs/common'; +import { + BadRequestException, + CanActivate, + ExecutionContext, + Inject, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; import { Observable } from 'rxjs'; import { IRequestWithCognitoInfo } from '../authorization/cognito-decoded.interface.js'; import { IGlobalDatabaseContext } from '../common/application/global-database-context.interface.js'; @@ -23,7 +30,7 @@ export class PaidFeatureGuard implements CanActivate { const request: IRequestWithCognitoInfo = context.switchToHttp().getRequest(); const userId: string | undefined = request.decoded.sub; if (!userId) { - reject(new BadRequestException(Messages.COMPANY_ID_MISSING)); + reject(new UnauthorizedException(Messages.DONT_HAVE_PERMISSIONS)); return; } let companyId: string | undefined = request.params?.companyId || request.params?.slug;