Skip to content

Commit

Permalink
Update variables for organizations
Browse files Browse the repository at this point in the history
  • Loading branch information
maxtechera committed Sep 6, 2024
1 parent ad4a3b7 commit b4aa9dd
Show file tree
Hide file tree
Showing 23 changed files with 385 additions and 160 deletions.
6 changes: 0 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@
"version": "2.0.4",
"private": true,
"homepage": "https://flowiseai.com",
"workspaces": [
"packages/*",
"flowise",
"ui",
"components"
],
"scripts": {
"build": "turbo run build",
"build-force": "pnpm clean && turbo run build --force",
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/controllers/chat-messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ const removeAllChatMessages = async (req: Request, res: Response, next: NextFunc
)
}
const chatflowid = req.params.id
const chatflow = await chatflowsService.getChatflowById(req.params.id)
const chatflow = await chatflowsService.getChatflowById(req.params.id,req.user!)
if (!chatflow) {
return res.status(404).send(`Chatflow ${req.params.id} not found`)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/controllers/chatflows/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ const updateChatflow = async (req: Request, res: Response, next: NextFunction) =
if (typeof req.params === 'undefined' || !req.params.id) {
throw new InternalFlowiseError(StatusCodes.PRECONDITION_FAILED, `Error: chatflowsRouter.updateChatflow - id not provided!`)
}
const chatflow = await chatflowsService.getChatflowById(req.params.id, req.user)
const chatflow = await chatflowsService.getChatflowById(req.params.id, req.user!)
if (!chatflow) {
return res.status(404).send(`Chatflow ${req.params.id} not found`)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/controllers/flow-configs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const getSingleFlowConfig = async (req: Request, res: Response, next: NextFuncti
`Error: flowConfigsController.getSingleFlowConfig - id not provided!`
)
}
const apiResponse = await flowConfigsService.getSingleFlowConfig(req.params.id)
const apiResponse = await flowConfigsService.getSingleFlowConfig(req.params.id,req.user!)
return res.json(apiResponse)
} catch (error) {
next(error)
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/controllers/predictions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const createPrediction = async (req: Request, res: Response, next: NextFunction)
`Error: predictionsController.createPrediction - body not provided!`
)
}
const chatflow = await chatflowsService.getChatflowById(req.params.id, req.user)
const chatflow = await chatflowsService.getChatflowById(req.params.id, req.user!)
if (!chatflow) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${req.params.id} not found`)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/server/src/controllers/variables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const createVariable = async (req: Request, res: Response, next: NextFunction) =
const body = req.body
const newVariable = new Variable()
Object.assign(newVariable, body)
const apiResponse = await variablesService.createVariable(newVariable)
const apiResponse = await variablesService.createVariable(newVariable, req.user!)
return res.json(apiResponse)
} catch (error) {
next(error)
Expand All @@ -36,7 +36,7 @@ const deleteVariable = async (req: Request, res: Response, next: NextFunction) =

const getAllVariables = async (req: Request, res: Response, next: NextFunction) => {
try {
const apiResponse = await variablesService.getAllVariables()
const apiResponse = await variablesService.getAllVariables(req.user!)
return res.json(apiResponse)
} catch (error) {
next(error)
Expand Down
23 changes: 22 additions & 1 deletion packages/server/src/database/entities/Variable.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
/* eslint-disable */
import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn, Index } from 'typeorm'
import { IVariable } from '../../Interface'

export enum VariableVisibility {
PRIVATE = 'Private',
ORGANIZATION = 'Organization'
}


@Entity()
export class Variable implements IVariable {
@PrimaryGeneratedColumn('uuid')
Expand All @@ -23,4 +29,19 @@ export class Variable implements IVariable {
@Column({ type: 'timestamp' })
@UpdateDateColumn()
updatedDate: Date

@Index()
@Column({ type: 'uuid', nullable: true })
userId?: string

@Index()
@Column({ type: 'uuid', nullable: true })
organizationId?: string

@Column({
type: 'simple-array',
enum: VariableVisibility,
default: 'Private'
})
visibility?: VariableVisibility[]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class VariablesVisibility1725494523908 implements MigrationInterface {
name = 'VariablesVisibility1725494523908'

public async up(queryRunner: QueryRunner): Promise<void> {

await queryRunner.query(`ALTER TABLE "variable" ADD "userId" uuid`);
await queryRunner.query(`ALTER TABLE "variable" ADD "organizationId" uuid`);
await queryRunner.query(`ALTER TABLE "variable" ADD "visibility" text NOT NULL DEFAULT 'Private'`);
await queryRunner.query(`ALTER TABLE "variable" ALTER COLUMN "value" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "variable" ALTER COLUMN "type" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "variable" ALTER COLUMN "type" SET DEFAULT 'string'`);

await queryRunner.query(`CREATE INDEX "IDX_b1d1cfdcf7ea567c336a953e2b" ON "variable" ("userId") `);
await queryRunner.query(`CREATE INDEX "IDX_cb8b5ee5a4506ad331209882ef" ON "variable" ("organizationId") `);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "public"."IDX_cb8b5ee5a4506ad331209882ef"`);
await queryRunner.query(`DROP INDEX "public"."IDX_b1d1cfdcf7ea567c336a953e2b"`);
await queryRunner.query(`ALTER TABLE "variable" ALTER COLUMN "type" DROP DEFAULT`);
await queryRunner.query(`ALTER TABLE "variable" ALTER COLUMN "type" DROP NOT NULL`);
await queryRunner.query(`ALTER TABLE "variable" ALTER COLUMN "value" SET NOT NULL`);
await queryRunner.query(`ALTER TABLE "variable" DROP COLUMN "visibility"`);
await queryRunner.query(`ALTER TABLE "variable" DROP COLUMN "organizationId"`);
await queryRunner.query(`ALTER TABLE "variable" DROP COLUMN "userId"`);

}

}
4 changes: 3 additions & 1 deletion packages/server/src/database/migrations/postgres/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { UpdateVisibilityType1719248473069 } from './1719248473069-UpdateVisibil
import { CredentialsVisibility1721247848452 } from './1721247848452-CredentialsVisibility'
import { AddDescriptionToChatFlow1722099922876 } from './1722101786123-AddDescriptionToChatflow'
import { AddSoftDeleteChatflows1724275570313 } from './1724275570313-AddSoftDeleteChatflows'
import { VariablesVisibility1725494523908 } from './1725494523908-VariablesVisibility'

export const postgresMigrations = [
Init1693891895163,
Expand Down Expand Up @@ -73,5 +74,6 @@ export const postgresMigrations = [
UpdateVisibilityType1719248473069,
CredentialsVisibility1721247848452,
AddDescriptionToChatFlow1722099922876,
AddSoftDeleteChatflows1724275570313
AddSoftDeleteChatflows1724275570313,
VariablesVisibility1725494523908
]
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Request, Response, NextFunction } from 'express'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { EntityTarget } from 'typeorm'
import { EntityTarget, FindOptionsWhere, IsNull, Like } from 'typeorm'
import path from 'path'

// Cache for imported entities
Expand All @@ -24,7 +24,6 @@ const enforceAbility = (resourceName: string) => {

const { id: userId, roles: userRoles = [], organizationId } = req.user
const isAdmin = userRoles.includes('Admin')
console.log('isAdmin', { isAdmin, userRoles, organizationId, userId, user: req.user })
// Set up filter based on user role
let filter: any = { organizationId }
if (!isAdmin) {
Expand Down
10 changes: 5 additions & 5 deletions packages/server/src/routes/variables/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import express from 'express'
import variablesController from '../../controllers/variables'
import enforceAbility from '../../middlewares/authentication/enforceAbility'

const router = express.Router()

// CREATE
router.post('/', variablesController.createVariable)
router.post('/', enforceAbility('Variable'), variablesController.createVariable)

// READ
router.get('/', variablesController.getAllVariables)
router.get('/', enforceAbility('Variable'), variablesController.getAllVariables)

// UPDATE
router.put(['/', '/:id'], variablesController.updateVariable)
router.put(['/', '/:id'], enforceAbility('Variable'), variablesController.updateVariable)

// DELETE
router.delete(['/', '/:id'], variablesController.deleteVariable)
router.delete(['/', '/:id'], enforceAbility('Variable'), variablesController.deleteVariable)

export default router
18 changes: 9 additions & 9 deletions packages/server/src/services/chatflows/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,26 +213,26 @@ const getChatflowByApiKey = async (apiKeyId: string, keyonly?: unknown): Promise
}
}

const getChatflowById = async (chatflowId: string, user?: IUser): Promise<any> => {
const getChatflowById = async (chatflowId: string, user: IUser): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow)
.createQueryBuilder('chatFlow')
.where('chatFlow.id = :id', { id: chatflowId })
.getOne()

if (!dbResponse) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowId} not found in the database!`)
}

// Check if the chatflow is not public and the user is not an org manager
if (!dbResponse.isPublic && !user?.permissions?.includes('org:manage')) {
// Perform the check against userId
if (dbResponse.userId !== user?.id) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized to access this chatflow`)
}
}
const isUserOrgAdmin = user?.permissions?.includes('org:manage') && user?.organizationId === dbResponse.organizationId
const isUsersChatflow = dbResponse.userId === user?.id
const isChatflowPublic = dbResponse.isPublic
const hasChatflowOrgVisibility = dbResponse.visibility?.includes(ChatflowVisibility.ORGANIZATION)
const isUserInSameOrg = dbResponse.organizationId === user?.organizationId

if (!(isUsersChatflow || isChatflowPublic || isUserOrgAdmin || (hasChatflowOrgVisibility && isUserInSameOrg))) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized to access this chatflow`)
}
return dbResponse
} catch (error) {
throw new InternalFlowiseError(
Expand Down
6 changes: 3 additions & 3 deletions packages/server/src/services/flow-configs/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { StatusCodes } from 'http-status-codes'
import { findAvailableConfigs } from '../../utils'
import { IReactFlowObject } from '../../Interface'
import { IReactFlowObject, IUser } from '../../Interface'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import chatflowsService from '../chatflows'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { getErrorMessage } from '../../errors/utils'

const getSingleFlowConfig = async (chatflowId: string): Promise<any> => {
const getSingleFlowConfig = async (chatflowId: string, user: IUser): Promise<any> => {
try {
const appServer = getRunningExpressApp()
const chatflow = await chatflowsService.getChatflowById(chatflowId)
const chatflow = await chatflowsService.getChatflowById(chatflowId,user)
if (!chatflow) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowId} not found in the database!`)
}
Expand Down
45 changes: 40 additions & 5 deletions packages/server/src/services/variables/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { StatusCodes } from 'http-status-codes'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { Variable } from '../../database/entities/Variable'
import { Variable, VariableVisibility } from '../../database/entities/Variable'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { getErrorMessage } from '../../errors/utils'
import { IUser } from '../../Interface'
import { Any, FindOptionsWhere, IsNull, Like } from 'typeorm'

const createVariable = async (newVariable: Variable) => {
const createVariable = async (newVariable: Variable, user: IUser) => {
try {
const appServer = getRunningExpressApp()
newVariable.userId = user.id
newVariable.organizationId = user.organizationId
const variable = await appServer.AppDataSource.getRepository(Variable).create(newVariable)
const dbResponse = await appServer.AppDataSource.getRepository(Variable).save(variable)
return dbResponse
Expand All @@ -31,11 +35,42 @@ const deleteVariable = async (variableId: string): Promise<any> => {
}
}

const getAllVariables = async () => {
const getAllVariables = async (user: IUser) => {
try {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(Variable).find()
return dbResponse
const variableRepo = appServer.AppDataSource.getRepository(Variable)

const isAdmin = user?.roles?.includes('Admin')

let conditions:FindOptionsWhere<Variable> | FindOptionsWhere<Variable>[];

if (isAdmin) {
conditions = [
{ organizationId: user.organizationId },
{ userId: IsNull() }
]
} else {
conditions = [
{ userId: user.id },
{ userId: IsNull() },
{
organizationId: user.organizationId,
// @ts-ignore
visibility: Like(`%${VariableVisibility.ORGANIZATION}%`)
}
]
}

const variables = await variableRepo.find({ where: conditions })

// Deduplicate variables based on id
const uniqueVariables = Array.from(new Map(variables.map((item) => [item.id, item])).values())

// Add isOwner property
return uniqueVariables.map((variable) => ({
...variable,
isOwner: variable.userId === user.id
}))
} catch (error) {
throw new InternalFlowiseError(
StatusCodes.INTERNAL_SERVER_ERROR,
Expand Down
Loading

0 comments on commit b4aa9dd

Please sign in to comment.