diff --git a/packages/server/src/controllers/chatflows/index.ts b/packages/server/src/controllers/chatflows/index.ts index 66e7609d78a..acc67fe2c0a 100644 --- a/packages/server/src/controllers/chatflows/index.ts +++ b/packages/server/src/controllers/chatflows/index.ts @@ -129,10 +129,17 @@ const getChatflowById = async (req: Request, res: Response, next: NextFunction) throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsController.getChatflowById - id not provided!`) } const apiResponse = await chatflowsService.getChatflowById(req.params.id) + const isAdmin = req.user?.roles?.includes('Admin') || req.user?.permissions?.includes('org:manage') const assignedWorkspaces = (req.user as { assignedWorkspaces?: Array<{ id: string }> } | undefined)?.assignedWorkspaces || [] - const hasWorkspaceAccess = apiResponse.workspaceId + + // Admins can access any chatflow within their organization regardless of workspace membership. + // Non-admins must have explicit workspace_user membership for the chatflow's workspace. + const hasWorkspaceAccess = isAdmin + ? apiResponse.organizationId === req.user?.activeOrganizationId + : apiResponse.workspaceId ? assignedWorkspaces.some((ws: { id: string }) => ws.id === apiResponse.workspaceId) : false + if (!hasWorkspaceAccess) { throw new InternalFlowiseError( StatusCodes.NOT_FOUND, diff --git a/packages/server/src/database/migrations/postgres/aai/1770000000003-FixTemplateSourceChatflowWorkspace.ts b/packages/server/src/database/migrations/postgres/aai/1770000000003-FixTemplateSourceChatflowWorkspace.ts new file mode 100644 index 00000000000..5974b9daa3f --- /dev/null +++ b/packages/server/src/database/migrations/postgres/aai/1770000000003-FixTemplateSourceChatflowWorkspace.ts @@ -0,0 +1,75 @@ +/* eslint-disable no-console */ +import { MigrationInterface, QueryRunner } from 'typeorm' + +/** + * Ensure that the source template chatflows identified by INITIAL_CHATFLOW_IDS / + * INITIAL_CHATFLOW_ID are assigned to the Default Workspace of their organization. + * + * Root cause: when the template chatflow is created via the normal POST /chatflows + * endpoint the workspaceId is set to whatever the admin's activeWorkspaceId was at + * that moment (often a Personal Workspace or an ad-hoc workspace). The + * getChatflowById controller then gates access by workspace_user membership, so + * any admin whose assignedWorkspaces does not include that workspace gets a + * misleading "not found" error even though the row exists. + * + * The controller is also fixed in this PR (admin bypass via org check), but this + * migration repairs the data so the template lives in a predictable, shared location. + * + * Idempotent: only moves chatflows that are NOT already in a Default Workspace. + */ +export class FixTemplateSourceChatflowWorkspace1770000000003 implements MigrationInterface { + name = 'FixTemplateSourceChatflowWorkspace1770000000003' + + public async up(queryRunner: QueryRunner): Promise { + console.log('[FixTemplateSourceChatflowWorkspace] Starting') + + // Read template IDs from environment (same logic as the server at runtime) + const rawIds = process.env.INITIAL_CHATFLOW_IDS ?? process.env.INITIAL_CHATFLOW_ID ?? '' + const templateIds = rawIds + .split(',') + .map((id) => id.trim()) + .filter(Boolean) + + if (!templateIds.length) { + console.log('[FixTemplateSourceChatflowWorkspace] No INITIAL_CHATFLOW_IDS configured — skipping') + return + } + + console.log(`[FixTemplateSourceChatflowWorkspace] Template IDs: ${templateIds.join(', ')}`) + + for (const templateId of templateIds) { + const result = await queryRunner.query( + ` + UPDATE chat_flow cf + SET "workspaceId" = dw.id, + "updatedDate" = NOW() + FROM workspace dw + WHERE dw."organizationId" = cf."organizationId" + AND dw.name = 'Default Workspace' + AND cf.id = $1 + AND cf."deletedDate" IS NULL + AND cf."workspaceId" NOT IN ( + SELECT id FROM workspace WHERE name = 'Default Workspace' + ) + RETURNING cf.id, cf.name, cf."workspaceId" AS new_workspace_id + `, + [templateId] + ) + + const moved = Array.isArray(result) ? result[0]?.length ?? 0 : 0 + if (moved > 0) { + console.log(`[FixTemplateSourceChatflowWorkspace] Moved template ${templateId} to Default Workspace`) + } else { + console.log( + `[FixTemplateSourceChatflowWorkspace] Template ${templateId} already in correct workspace or not found — no change` + ) + } + } + + console.log('[FixTemplateSourceChatflowWorkspace] Done') + } + + public async down(): Promise { + console.log('[FixTemplateSourceChatflowWorkspace] Down migration is a no-op') + } +} diff --git a/packages/server/src/database/migrations/postgres/index.ts b/packages/server/src/database/migrations/postgres/index.ts index 5b505f543bd..573adfc2614 100644 --- a/packages/server/src/database/migrations/postgres/index.ts +++ b/packages/server/src/database/migrations/postgres/index.ts @@ -98,6 +98,7 @@ import { UpdateFiddlerCredentialsVisibility1768413137117 } from './aai/176841313 import { MoveDefaultChatflowsToPersonalWorkspace1770000000000 } from './aai/1770000000000-MoveDefaultChatflowsToPersonalWorkspace' import { NormalizeLegacyCredentialNames1770000000001 } from './aai/1770000000001-NormalizeLegacyCredentialNames' import { FixBulkUpdateChatflowWorkspace1770000000002 } from './aai/1770000000002-FixBulkUpdateChatflowWorkspace' +import { FixTemplateSourceChatflowWorkspace1770000000003 } from './aai/1770000000003-FixTemplateSourceChatflowWorkspace' export const postgresMigrations = [ Init1693891895163, @@ -199,5 +200,7 @@ export const postgresMigrations = [ // AAI: Normalize legacy credential names in chat_flow.flowData and credential rows (e.g. JiraApi -> jiraApi) NormalizeLegacyCredentialNames1770000000001, // AAI: Fix chatflows re-assigned to Default Workspace by bulkUpdateChatflows workspace leak - FixBulkUpdateChatflowWorkspace1770000000002 + FixBulkUpdateChatflowWorkspace1770000000002, + // AAI: Move INITIAL_CHATFLOW_IDS source templates to Default Workspace for consistent admin access + FixTemplateSourceChatflowWorkspace1770000000003 ]