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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/server/src/controllers/chatflows/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<void> {
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<void> {
console.log('[FixTemplateSourceChatflowWorkspace] Down migration is a no-op')
}
}
5 changes: 4 additions & 1 deletion packages/server/src/database/migrations/postgres/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
]
Loading