diff --git a/server/data-migrations/1743058812003-UpdateGlobalDataSources.ts b/server/data-migrations/1743058812003-UpdateGlobalDataSources.ts new file mode 100644 index 0000000000..06591ea8d8 --- /dev/null +++ b/server/data-migrations/1743058812003-UpdateGlobalDataSources.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateGlobalDataSources1743058812003 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // Step 1: Set app_version_id to NULL for existing global data sources + await queryRunner.query(` + UPDATE data_sources + SET app_version_id = NULL + WHERE scope = 'global' + `); + + // Step 2: Add a check constraint to ensure app_version_id is NULL for global data sources + await queryRunner.query(` + ALTER TABLE data_sources + ADD CONSTRAINT chk_global_data_source_app_version_id + CHECK (scope != 'global' OR app_version_id IS NULL) + `); + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/server/src/entities/user.entity.ts b/server/src/entities/user.entity.ts index 31b7a6888c..5bdecb2b43 100644 --- a/server/src/entities/user.entity.ts +++ b/server/src/entities/user.entity.ts @@ -28,6 +28,7 @@ import { OnboardingDetails } from './onboarding_details.entity'; import { OnboardingStatus } from '@modules/onboarding/constants'; import { AiConversation } from './ai_conversation.entity'; import { AiResponseVote } from './ai_response_vote.entity'; +import { USER_ROLE } from '@modules/group-permissions/constants'; @Entity({ name: 'users' }) export class User extends BaseEntity { @@ -189,4 +190,5 @@ export class User extends BaseEntity { isPasswordLogin: boolean; isSSOLogin: boolean; sessionId: string; + roleGroup: USER_ROLE; } diff --git a/server/src/main.ts b/server/src/main.ts index 4c94d64ac0..9442945cbb 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -106,6 +106,8 @@ function setSecurityHeaders(app, configService) { 'https://esm.sh', 'www.googletagmanager.com', ], + 'object-src': ["'self'", 'data:'], + 'media-src': ["'self'", 'data:'], 'default-src': [ 'maps.googleapis.com', 'storage.googleapis.com', @@ -116,7 +118,7 @@ function setSecurityHeaders(app, configService) { 'blob:', 'www.googletagmanager.com', ], - 'connect-src': ['ws://' + domain, "'self'", '*'], + 'connect-src': ['ws://' + domain, "'self'", '*', 'data:'], 'frame-ancestors': ['*'], 'frame-src': ['*'], }, diff --git a/server/src/modules/app/decorators/user-permission.decorator.ts b/server/src/modules/app/decorators/user-permission.decorator.ts new file mode 100644 index 0000000000..e2e3721fd5 --- /dev/null +++ b/server/src/modules/app/decorators/user-permission.decorator.ts @@ -0,0 +1,9 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { UserPermissions } from '@modules/ability/types'; + +export const UserPermissionsDecorator = createParamDecorator( + (data: unknown, ctx: ExecutionContext): UserPermissions => { + const request = ctx.switchToHttp().getRequest(); + return request.tj_user_permissions as UserPermissions; + } +); diff --git a/server/src/modules/apps/services/app-import-export.service.ts b/server/src/modules/apps/services/app-import-export.service.ts index c80b0ea653..abc2abce3e 100644 --- a/server/src/modules/apps/services/app-import-export.service.ts +++ b/server/src/modules/apps/services/app-import-export.service.ts @@ -1222,8 +1222,7 @@ export class AppImportExportService { name: dataSource.name, kind: dataSource.kind, type: DataSourceTypes.DEFAULT, - appVersionId, - scope: 'global', + scope: 'global', // No appVersionId for global data sources pluginId: plugin.id, }); await manager.save(newDataSource); @@ -1238,8 +1237,7 @@ export class AppImportExportService { name: dataSource.name, kind: dataSource.kind, type: DataSourceTypes.DEFAULT, - appVersionId, - scope: 'global', + scope: 'global', // No appVersionId for global data sources pluginId: null, }); await manager.save(newDataSource); diff --git a/server/src/modules/folder-apps/controller.ts b/server/src/modules/folder-apps/controller.ts index 82f1bb22e2..f40c5f75d3 100644 --- a/server/src/modules/folder-apps/controller.ts +++ b/server/src/modules/folder-apps/controller.ts @@ -2,12 +2,15 @@ import { Controller, Param, Post, Put, UseGuards, Get, Query, Body } from '@nest import { decamelizeKeys } from 'humps'; import { JwtAuthGuard } from '@modules/session/guards/jwt-auth.guard'; import { FolderAppsService } from './service'; -import { User } from '@modules/app/decorators/user.decorator'; +import { User, UserEntity } from '@modules/app/decorators/user.decorator'; import { FeatureAbilityGuard } from './ability/guard'; import { InitModule } from '@modules/app/decorators/init-module'; import { InitFeature } from '@modules/app/decorators/init-feature.decorator'; import { MODULES } from '@modules/app/constants/modules'; import { FEATURE_KEY } from './constants'; +import { UserPermissionsDecorator } from '@modules/app/decorators/user-permission.decorator'; +import { UserPermissions } from '@modules/ability/types'; +import { USER_ROLE } from '@modules/group-permissions/constants'; @InitModule(MODULES.FOLDER_APPS) @UseGuards(JwtAuthGuard, FeatureAbilityGuard) @Controller('folder-apps') @@ -16,7 +19,8 @@ export class FolderAppsController { @InitFeature(FEATURE_KEY.GET_FOLDERS) @Get() - async index(@User() user, @Query() query) { + async index(@User() user: UserEntity, @Query() query, @UserPermissionsDecorator() userPermissions: UserPermissions) { + user.roleGroup = userPermissions.isEndUser ? USER_ROLE.END_USER : undefined; return await this.folderAppsService.getFolders(user, query); } diff --git a/server/src/modules/folder-apps/service.ts b/server/src/modules/folder-apps/service.ts index aff2bb9306..1299af06dd 100644 --- a/server/src/modules/folder-apps/service.ts +++ b/server/src/modules/folder-apps/service.ts @@ -8,6 +8,8 @@ import { FolderAppsUtilService } from './util.service'; import { IFolderAppsService } from './interfaces/IService'; import { MODULES } from '@modules/app/constants/modules'; import { AbilityService } from '@modules/ability/interfaces/IService'; +import { User } from '@entities/user.entity'; +import { USER_ROLE } from '@modules/group-permissions/constants'; @Injectable() export class FolderAppsService implements IFolderAppsService { constructor( @@ -47,7 +49,7 @@ export class FolderAppsService implements IFolderAppsService { return await manager.delete(FolderApp, { folderId, appId }); }); } - async getFolders(user, query) { + async getFolders(user: User, query) { return dbTransactionWrap(async (manager: EntityManager) => { const type = query.type; const searchKey = query.searchKey; @@ -78,7 +80,12 @@ export class FolderAppsService implements IFolderAppsService { allFolderList[index].generateCount(); } }); - return decamelizeKeys({ folders: allFolderList }); + return decamelizeKeys({ + folders: + user.roleGroup === USER_ROLE.END_USER + ? allFolderList.filter((folder) => folder.folderApps.length > 0) + : allFolderList, + }); }); } }