diff --git a/packages/server/src/enterprise/auth/AuthenticationStrategy.ts b/packages/server/src/enterprise/auth/AuthenticationStrategy.ts new file mode 100644 index 00000000000..f5903da505e --- /dev/null +++ b/packages/server/src/enterprise/auth/AuthenticationStrategy.ts @@ -0,0 +1,6 @@ +export enum AuthenticationStrategy { + JWT = 'jwt', + API_KEY = 'apiKey', + PUBLIC = 'public', + SESSION = 'session' +} diff --git a/packages/server/src/enterprise/middleware/policy/index.ts b/packages/server/src/enterprise/middleware/policy/index.ts new file mode 100644 index 00000000000..718dbacf8c4 --- /dev/null +++ b/packages/server/src/enterprise/middleware/policy/index.ts @@ -0,0 +1,41 @@ +import { NextFunction, Response } from 'express' +import { RegisteredRoute } from '../../../services/entitled-router' +import { AuthenticationStrategy } from '../../auth/AuthenticationStrategy' +import { StatusCodes } from 'http-status-codes' + +/** + * Authorizes a user against a specific policy. + * + * @param {any} user - The user object attached to the request, if any. + * @param {RegisteredRoute} policy - The matching policy for the current request. + * @param {Response} res - The Express response object. + * @param {NextFunction} next - The next middleware function in the stack. + */ +export const authorize = (user: any, policy: RegisteredRoute, res: Response, next: NextFunction) => { + // If the route is public, grant access immediately. + if (policy.authenticationStrategies.includes(AuthenticationStrategy.PUBLIC)) { + return next() + } + + // If the route is not public, a user must be authenticated. + if (!user) { + return res.status(401).json({ message: StatusCodes.FORBIDDEN }) + } + + //+Existing logic. Eventually API Keys and JWT clients should be scoped & have permissions checked too + if (user.isApiKeyValidated || user.isOrganizationAdmin) { + return next() + } + //-Existing Logic + + const userPermissions = user.permissions || [] + // Check if the user has at least one of the required entitlements for the route. + const hasPermission = policy.entitlements.some((p) => userPermissions.includes(p)) + + if (hasPermission) { + return next() + } + + // If none of the above conditions are met, the user is not authorized. + return res.status(403).json({ message: StatusCodes.UNAUTHORIZED }) +} diff --git a/packages/server/src/enterprise/rbac/Entitlements.ts b/packages/server/src/enterprise/rbac/Entitlements.ts new file mode 100644 index 00000000000..df3107d76cb --- /dev/null +++ b/packages/server/src/enterprise/rbac/Entitlements.ts @@ -0,0 +1,201 @@ +// Types for entitlement structure +export type EntitlementValue = string | symbol +type EntitlementNode = EntitlementValue | { [key: string]: EntitlementNode } +type EntitlementStructure = { [key: string]: EntitlementNode } + +// Helper function to create unique entitlement identifiers +const createEntitlementId = (path: string[]): string => { + return path.join('.') +} + +// Recursive function to build the entitlement tree with proper paths +const buildEntitlementTree = (structure: T, parentPath: string[] = []): T => { + const result: any = {} + + for (const [key, value] of Object.entries(structure)) { + const currentPath = [...parentPath, key] + + if (value === undefined || typeof value === 'string' || typeof value === 'symbol') { + // Leaf node - create entitlement identifier + result[key] = createEntitlementId(currentPath) + } else if (typeof value === 'object' && value !== null) { + // Branch node - recurse + result[key] = buildEntitlementTree(value as EntitlementStructure, currentPath) + } + } + + return result as T +} + +// Factory function to create entitlements +const createEntitlements = (structure: T): T => { + return buildEntitlementTree(structure) +} + +// Placeholder values for entitlement operations +const addUser = 'add-user' +const addLoader = 'add-loader' +const deleteLoader = 'delete-loader' +const config = 'config' +const create = 'create' +const custom = 'custom' +const customDelete = 'custom-delete' +const customShare = 'custom-share' +const del = 'delete' // 'delete' is a reserved word +const domains = 'domains' +const duplicate = 'duplicate' +const exp = 'export' // 'export' is a reserved word +const flowexport = 'flowexport' +const imp = 'import' // 'import' is a reserved word +const manage = 'manage' +const marketplace = 'marketplace' +const previewProcess = 'preview-process' +const run = 'run' +const share = 'share' +const toolexport = 'toolexport' +const unlinkUser = 'unlink-user' +const unspecified = 'unspecified' +const update = 'update' +const upsertConfig = 'upsert-config' +const view = 'view' + +// The canonical list of entitlements +const entitlements = { + agentflows: { + view, + create, + update, + duplicate, + delete: del, + export: exp, + import: imp, + config, + domains + }, + apikeys: { + create, + import: imp, + view, + update, + delete: del + }, + assistants: { + create, + delete: del, + update, + view + }, + chatflows: { + config, + create, + delete: del, + domains, + duplicate, + export: exp, + import: imp, + update, + view + }, + credentials: { + create, + delete: del, + update, + share, + view + }, + datasets: { + create, + delete: del, + update, + view + }, + documentStores: { + addLoader: addLoader, + create, + deleteLoader: deleteLoader, + delete: del, + previewProcess: previewProcess, + update, + view, + upsertConfig: upsertConfig + }, + evaluations: { + create, + delete: del, + run, + update, + view + }, + evaluators: { + create, + delete: del, + update, + view + }, + executions: { + create, + delete: del, + view + }, + loginActivity: { + view, + delete: del + }, + logs: { + view + }, + roles: { + manage + }, + sso: { + manage + }, + templates: { + marketplace, + flowexport, + toolexport, + custom, + customDelete, + customShare + }, + tools: { + create, + delete: del, + export: exp, + update, + view + }, + users: { + manage + }, + workspace: { + addUser, + create, + delete: del, + export: exp, + import: imp, + unlinkUser, + update, + view + }, + unspecified, + variables: { + create, + delete: del, + update, + view + } +} as const // <-- crucial for type safety + +// Create the Entitlements object +export const Entitlements = createEntitlements(entitlements) + +// Type to recursively extract all leaf string values from a nested object +export type ExtractLeafStrings = T extends string + ? T + : T extends Record + ? { [K in keyof T]: ExtractLeafStrings }[keyof T] + : never + +// Type union of all entitlement strings +export type Entitlement = ExtractLeafStrings \ No newline at end of file diff --git a/packages/server/src/enterprise/rbac/Permissions.ts b/packages/server/src/enterprise/rbac/Permissions.ts index e44f541a820..0198630113c 100644 --- a/packages/server/src/enterprise/rbac/Permissions.ts +++ b/packages/server/src/enterprise/rbac/Permissions.ts @@ -1,3 +1,5 @@ +import { Entitlement, Entitlements } from './Entitlements' + export class Permissions { private categories: PermissionCategory[] = [] constructor() { @@ -6,138 +8,140 @@ export class Permissions { // this.categories.push(auditCategory) const chatflowsCategory = new PermissionCategory('chatflows') - chatflowsCategory.addPermission(new Permission('chatflows:view', 'View')) - chatflowsCategory.addPermission(new Permission('chatflows:create', 'Create')) - chatflowsCategory.addPermission(new Permission('chatflows:update', 'Update')) - chatflowsCategory.addPermission(new Permission('chatflows:duplicate', 'Duplicate')) - chatflowsCategory.addPermission(new Permission('chatflows:delete', 'Delete')) - chatflowsCategory.addPermission(new Permission('chatflows:export', 'Export')) - chatflowsCategory.addPermission(new Permission('chatflows:import', 'Import')) - chatflowsCategory.addPermission(new Permission('chatflows:config', 'Edit Configuration')) - chatflowsCategory.addPermission(new Permission('chatflows:domains', 'Allowed Domains')) + chatflowsCategory.addPermission(new Permission(Entitlements.chatflows.view, 'View')) + chatflowsCategory.addPermission(new Permission(Entitlements.chatflows.create, 'Create')) + chatflowsCategory.addPermission(new Permission(Entitlements.chatflows.update, 'Update')) + chatflowsCategory.addPermission(new Permission(Entitlements.chatflows.duplicate, 'Duplicate')) + chatflowsCategory.addPermission(new Permission(Entitlements.chatflows.delete, 'Delete')) + chatflowsCategory.addPermission(new Permission(Entitlements.chatflows.export, 'Export')) + chatflowsCategory.addPermission(new Permission(Entitlements.chatflows.import, 'Import')) + chatflowsCategory.addPermission(new Permission(Entitlements.chatflows.config, 'Edit Configuration')) + chatflowsCategory.addPermission(new Permission(Entitlements.chatflows.domains, 'Allowed Domains')) this.categories.push(chatflowsCategory) const agentflowsCategory = new PermissionCategory('agentflows') - agentflowsCategory.addPermission(new Permission('agentflows:view', 'View')) - agentflowsCategory.addPermission(new Permission('agentflows:create', 'Create')) - agentflowsCategory.addPermission(new Permission('agentflows:update', 'Update')) - agentflowsCategory.addPermission(new Permission('agentflows:duplicate', 'Duplicate')) - agentflowsCategory.addPermission(new Permission('agentflows:delete', 'Delete')) - agentflowsCategory.addPermission(new Permission('agentflows:export', 'Export')) - agentflowsCategory.addPermission(new Permission('agentflows:import', 'Import')) - agentflowsCategory.addPermission(new Permission('agentflows:config', 'Edit Configuration')) - agentflowsCategory.addPermission(new Permission('agentflows:domains', 'Allowed Domains')) + agentflowsCategory.addPermission(new Permission(Entitlements.agentflows.view, 'View')) + agentflowsCategory.addPermission(new Permission(Entitlements.agentflows.create, 'Create')) + agentflowsCategory.addPermission(new Permission(Entitlements.agentflows.update, 'Update')) + agentflowsCategory.addPermission(new Permission(Entitlements.agentflows.duplicate, 'Duplicate')) + agentflowsCategory.addPermission(new Permission(Entitlements.agentflows.delete, 'Delete')) + agentflowsCategory.addPermission(new Permission(Entitlements.agentflows.export, 'Export')) + agentflowsCategory.addPermission(new Permission(Entitlements.agentflows.import, 'Import')) + agentflowsCategory.addPermission(new Permission(Entitlements.agentflows.config, 'Edit Configuration')) + agentflowsCategory.addPermission(new Permission(Entitlements.agentflows.domains, 'Allowed Domains')) this.categories.push(agentflowsCategory) const toolsCategory = new PermissionCategory('tools') - toolsCategory.addPermission(new Permission('tools:view', 'View')) - toolsCategory.addPermission(new Permission('tools:create', 'Create')) - toolsCategory.addPermission(new Permission('tools:update', 'Update')) - toolsCategory.addPermission(new Permission('tools:delete', 'Delete')) - toolsCategory.addPermission(new Permission('tools:export', 'Export')) + toolsCategory.addPermission(new Permission(Entitlements.tools.view, 'View')) + toolsCategory.addPermission(new Permission(Entitlements.tools.create, 'Create')) + toolsCategory.addPermission(new Permission(Entitlements.tools.update, 'Update')) + toolsCategory.addPermission(new Permission(Entitlements.tools.delete, 'Delete')) + toolsCategory.addPermission(new Permission(Entitlements.tools.export, 'Export')) this.categories.push(toolsCategory) const assistantsCategory = new PermissionCategory('assistants') - assistantsCategory.addPermission(new Permission('assistants:view', 'View')) - assistantsCategory.addPermission(new Permission('assistants:create', 'Create')) - assistantsCategory.addPermission(new Permission('assistants:update', 'Update')) - assistantsCategory.addPermission(new Permission('assistants:delete', 'Delete')) + assistantsCategory.addPermission(new Permission(Entitlements.assistants.view, 'View')) + assistantsCategory.addPermission(new Permission(Entitlements.assistants.create, 'Create')) + assistantsCategory.addPermission(new Permission(Entitlements.assistants.update, 'Update')) + assistantsCategory.addPermission(new Permission(Entitlements.assistants.delete, 'Delete')) this.categories.push(assistantsCategory) const credentialsCategory = new PermissionCategory('credentials') - credentialsCategory.addPermission(new Permission('credentials:view', 'View')) - credentialsCategory.addPermission(new Permission('credentials:create', 'Create')) - credentialsCategory.addPermission(new Permission('credentials:update', 'Update')) - credentialsCategory.addPermission(new Permission('credentials:delete', 'Delete')) - credentialsCategory.addPermission(new Permission('credentials:share', 'Share')) + credentialsCategory.addPermission(new Permission(Entitlements.credentials.view, 'View')) + credentialsCategory.addPermission(new Permission(Entitlements.credentials.create, 'Create')) + credentialsCategory.addPermission(new Permission(Entitlements.credentials.update, 'Update')) + credentialsCategory.addPermission(new Permission(Entitlements.credentials.delete, 'Delete')) + credentialsCategory.addPermission(new Permission(Entitlements.credentials.share, 'Share')) this.categories.push(credentialsCategory) const variablesCategory = new PermissionCategory('variables') - variablesCategory.addPermission(new Permission('variables:view', 'View')) - variablesCategory.addPermission(new Permission('variables:create', 'Create')) - variablesCategory.addPermission(new Permission('variables:update', 'Update')) - variablesCategory.addPermission(new Permission('variables:delete', 'Delete')) + variablesCategory.addPermission(new Permission(Entitlements.variables.view, 'View')) + variablesCategory.addPermission(new Permission(Entitlements.variables.create, 'Create')) + variablesCategory.addPermission(new Permission(Entitlements.variables.update, 'Update')) + variablesCategory.addPermission(new Permission(Entitlements.variables.delete, 'Delete')) this.categories.push(variablesCategory) const apikeysCategory = new PermissionCategory('apikeys') - apikeysCategory.addPermission(new Permission('apikeys:view', 'View')) - apikeysCategory.addPermission(new Permission('apikeys:create', 'Create')) - apikeysCategory.addPermission(new Permission('apikeys:update', 'Update')) - apikeysCategory.addPermission(new Permission('apikeys:delete', 'Delete')) - apikeysCategory.addPermission(new Permission('apikeys:import', 'Import')) + apikeysCategory.addPermission(new Permission(Entitlements.apikeys.view, 'View')) + apikeysCategory.addPermission(new Permission(Entitlements.apikeys.create, 'Create')) + apikeysCategory.addPermission(new Permission(Entitlements.apikeys.update, 'Update')) + apikeysCategory.addPermission(new Permission(Entitlements.apikeys.delete, 'Delete')) + apikeysCategory.addPermission(new Permission(Entitlements.apikeys.import, 'Import')) this.categories.push(apikeysCategory) const documentStoresCategory = new PermissionCategory('documentStores') - documentStoresCategory.addPermission(new Permission('documentStores:view', 'View')) - documentStoresCategory.addPermission(new Permission('documentStores:create', 'Create')) - documentStoresCategory.addPermission(new Permission('documentStores:update', 'Update')) - documentStoresCategory.addPermission(new Permission('documentStores:delete', 'Delete Document Store')) - documentStoresCategory.addPermission(new Permission('documentStores:add-loader', 'Add Document Loader')) - documentStoresCategory.addPermission(new Permission('documentStores:delete-loader', 'Delete Document Loader')) - documentStoresCategory.addPermission(new Permission('documentStores:preview-process', 'Preview & Process Document Chunks')) - documentStoresCategory.addPermission(new Permission('documentStores:upsert-config', 'Upsert Config')) + documentStoresCategory.addPermission(new Permission(Entitlements.documentStores.view, 'View')) + documentStoresCategory.addPermission(new Permission(Entitlements.documentStores.create, 'Create')) + documentStoresCategory.addPermission(new Permission(Entitlements.documentStores.update, 'Update')) + documentStoresCategory.addPermission(new Permission(Entitlements.documentStores.delete, 'Delete Document Store')) + documentStoresCategory.addPermission(new Permission(Entitlements.documentStores.addLoader, 'Add Document Loader')) + documentStoresCategory.addPermission(new Permission(Entitlements.documentStores.deleteLoader, 'Delete Document Loader')) + documentStoresCategory.addPermission( + new Permission(Entitlements.documentStores.previewProcess, 'Preview & Process Document Chunks') + ) + documentStoresCategory.addPermission(new Permission(Entitlements.documentStores.upsertConfig, 'Upsert Config')) this.categories.push(documentStoresCategory) const datasetsCategory = new PermissionCategory('datasets') - datasetsCategory.addPermission(new Permission('datasets:view', 'View')) - datasetsCategory.addPermission(new Permission('datasets:create', 'Create')) - datasetsCategory.addPermission(new Permission('datasets:update', 'Update')) - datasetsCategory.addPermission(new Permission('datasets:delete', 'Delete')) + datasetsCategory.addPermission(new Permission(Entitlements.datasets.view, 'View')) + datasetsCategory.addPermission(new Permission(Entitlements.datasets.create, 'Create')) + datasetsCategory.addPermission(new Permission(Entitlements.datasets.update, 'Update')) + datasetsCategory.addPermission(new Permission(Entitlements.datasets.delete, 'Delete')) this.categories.push(datasetsCategory) const executionsCategory = new PermissionCategory('executions') - executionsCategory.addPermission(new Permission('executions:view', 'View')) - executionsCategory.addPermission(new Permission('executions:delete', 'Delete')) + executionsCategory.addPermission(new Permission(Entitlements.executions.view, 'View')) + executionsCategory.addPermission(new Permission(Entitlements.executions.delete, 'Delete')) this.categories.push(executionsCategory) const evaluatorsCategory = new PermissionCategory('evaluators') - evaluatorsCategory.addPermission(new Permission('evaluators:view', 'View')) - evaluatorsCategory.addPermission(new Permission('evaluators:create', 'Create')) - evaluatorsCategory.addPermission(new Permission('evaluators:update', 'Update')) - evaluatorsCategory.addPermission(new Permission('evaluators:delete', 'Delete')) + evaluatorsCategory.addPermission(new Permission(Entitlements.evaluators.view, 'View')) + evaluatorsCategory.addPermission(new Permission(Entitlements.evaluators.create, 'Create')) + evaluatorsCategory.addPermission(new Permission(Entitlements.evaluators.update, 'Update')) + evaluatorsCategory.addPermission(new Permission(Entitlements.evaluators.delete, 'Delete')) this.categories.push(evaluatorsCategory) const evaluationsCategory = new PermissionCategory('evaluations') - evaluationsCategory.addPermission(new Permission('evaluations:view', 'View')) - evaluationsCategory.addPermission(new Permission('evaluations:create', 'Create')) - evaluationsCategory.addPermission(new Permission('evaluations:update', 'Update')) - evaluationsCategory.addPermission(new Permission('evaluations:delete', 'Delete')) - evaluationsCategory.addPermission(new Permission('evaluations:run', 'Run Again')) + evaluationsCategory.addPermission(new Permission(Entitlements.evaluations.view, 'View')) + evaluationsCategory.addPermission(new Permission(Entitlements.evaluations.create, 'Create')) + evaluationsCategory.addPermission(new Permission(Entitlements.evaluations.update, 'Update')) + evaluationsCategory.addPermission(new Permission(Entitlements.evaluations.delete, 'Delete')) + evaluationsCategory.addPermission(new Permission(Entitlements.evaluations.run, 'Run Again')) this.categories.push(evaluationsCategory) const templatesCategory = new PermissionCategory('templates') - templatesCategory.addPermission(new Permission('templates:marketplace', 'View Marketplace Templates')) - templatesCategory.addPermission(new Permission('templates:custom', 'View Custom Templates')) - templatesCategory.addPermission(new Permission('templates:custom-delete', 'Delete Custom Template')) - templatesCategory.addPermission(new Permission('templates:toolexport', 'Export Tool as Template')) - templatesCategory.addPermission(new Permission('templates:flowexport', 'Export Flow as Template')) - templatesCategory.addPermission(new Permission('templates:custom-share', 'Share Custom Templates')) + templatesCategory.addPermission(new Permission(Entitlements.templates.marketplace, 'View Marketplace Templates')) + templatesCategory.addPermission(new Permission(Entitlements.templates.custom, 'View Custom Templates')) + templatesCategory.addPermission(new Permission(Entitlements.templates.customDelete, 'Delete Custom Template')) + templatesCategory.addPermission(new Permission(Entitlements.templates.toolexport, 'Export Tool as Template')) + templatesCategory.addPermission(new Permission(Entitlements.templates.flowexport, 'Export Flow as Template')) + templatesCategory.addPermission(new Permission(Entitlements.templates.customShare, 'Share Custom Templates')) this.categories.push(templatesCategory) const workspaceCategory = new PermissionCategory('workspace') - workspaceCategory.addPermission(new Permission('workspace:view', 'View')) - workspaceCategory.addPermission(new Permission('workspace:create', 'Create')) - workspaceCategory.addPermission(new Permission('workspace:update', 'Update')) - workspaceCategory.addPermission(new Permission('workspace:add-user', 'Add User')) - workspaceCategory.addPermission(new Permission('workspace:unlink-user', 'Remove User')) - workspaceCategory.addPermission(new Permission('workspace:delete', 'Delete')) - workspaceCategory.addPermission(new Permission('workspace:export', 'Export Data within Workspace')) - workspaceCategory.addPermission(new Permission('workspace:import', 'Import Data within Workspace')) + workspaceCategory.addPermission(new Permission(Entitlements.workspace.view, 'View')) + workspaceCategory.addPermission(new Permission(Entitlements.workspace.create, 'Create')) + workspaceCategory.addPermission(new Permission(Entitlements.workspace.update, 'Update')) + workspaceCategory.addPermission(new Permission(Entitlements.workspace.addUser, 'Add User')) + workspaceCategory.addPermission(new Permission(Entitlements.workspace.unlinkUser, 'Remove User')) + workspaceCategory.addPermission(new Permission(Entitlements.workspace.delete, 'Delete')) + workspaceCategory.addPermission(new Permission(Entitlements.workspace.export, 'Export Data within Workspace')) + workspaceCategory.addPermission(new Permission(Entitlements.workspace.import, 'Import Data within Workspace')) this.categories.push(workspaceCategory) const adminCategory = new PermissionCategory('admin') - adminCategory.addPermission(new Permission('users:manage', 'Manage Users')) - adminCategory.addPermission(new Permission('roles:manage', 'Manage Roles')) - adminCategory.addPermission(new Permission('sso:manage', 'Manage SSO')) + adminCategory.addPermission(new Permission(Entitlements.users.manage, 'Manage Users')) + adminCategory.addPermission(new Permission(Entitlements.roles.manage, 'Manage Roles')) + adminCategory.addPermission(new Permission(Entitlements.sso.manage, 'Manage SSO')) this.categories.push(adminCategory) const logsCategory = new PermissionCategory('logs') - logsCategory.addPermission(new Permission('logs:view', 'View Logs', true)) + logsCategory.addPermission(new Permission(Entitlements.logs.view, 'View Logs', true)) this.categories.push(logsCategory) const loginActivityCategory = new PermissionCategory('loginActivity') - loginActivityCategory.addPermission(new Permission('loginActivity:view', 'View Login Activity', true)) - loginActivityCategory.addPermission(new Permission('loginActivity:delete', 'Delete Login Activity', true)) + loginActivityCategory.addPermission(new Permission(Entitlements.loginActivity.view, 'View Login Activity', true)) + loginActivityCategory.addPermission(new Permission(Entitlements.loginActivity.delete, 'Delete Login Activity', true)) this.categories.push(loginActivityCategory) } @@ -167,11 +171,11 @@ export class PermissionCategory { } export class Permission { - constructor(public name: string, public description: string, public isEnterprise: boolean = false) {} + constructor(public entitlement: Entitlement, public description: string, public isEnterprise: boolean = false) {} public toJSON() { return { - key: this.name, + key: this.entitlement, value: this.description, isEnterprise: this.isEnterprise } diff --git a/packages/server/src/enterprise/routes/account.route.ts b/packages/server/src/enterprise/routes/account.route.ts index fd90f6cfcf1..856c515c635 100644 --- a/packages/server/src/enterprise/routes/account.route.ts +++ b/packages/server/src/enterprise/routes/account.route.ts @@ -1,39 +1,46 @@ -import express from 'express' import { AccountController } from '../controllers/account.controller' import { IdentityManager } from '../../IdentityManager' -import { checkAnyPermission } from '../rbac/PermissionCheck' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../rbac/Entitlements' +import { AuthenticationStrategy } from '../auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() const accountController = new AccountController() -router.post('/register', accountController.register) +router.post('/register', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], accountController.register) // feature flag to workspace since only user who has workspaces can invite router.post( '/invite', + [Entitlements.workspace.addUser, Entitlements.users.manage], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], IdentityManager.checkFeatureByPlan('feat:workspaces'), - checkAnyPermission('workspace:add-user,users:manage'), accountController.invite ) -router.post('/login', accountController.login) +router.post('/login', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], accountController.login) -router.post('/logout', accountController.logout) +router.post('/logout', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], accountController.logout) -router.post('/verify', accountController.verify) +router.post('/verify', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], accountController.verify) -router.post('/resend-verification', accountController.resendVerificationEmail) +router.post('/resend-verification', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], accountController.resendVerificationEmail) -router.post('/forgot-password', accountController.forgotPassword) +router.post('/forgot-password', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], accountController.forgotPassword) -router.post('/reset-password', accountController.resetPassword) +router.post('/reset-password', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], accountController.resetPassword) -router.post('/cancel-subscription', accountController.cancelPreviousCloudSubscrption) +router.post( + '/cancel-subscription', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + accountController.cancelPreviousCloudSubscrption +) -router.post('/billing', accountController.createStripeCustomerPortalSession) +router.post('/billing', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], accountController.createStripeCustomerPortalSession) -router.get('/basic-auth', accountController.getBasicAuth) +router.get('/basic-auth', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], accountController.getBasicAuth) -router.post('/basic-auth', accountController.checkBasicAuth) +router.post('/basic-auth', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], accountController.checkBasicAuth) export default router diff --git a/packages/server/src/enterprise/routes/audit/index.ts b/packages/server/src/enterprise/routes/audit/index.ts index 5ddd7d54772..7467f13ba19 100644 --- a/packages/server/src/enterprise/routes/audit/index.ts +++ b/packages/server/src/enterprise/routes/audit/index.ts @@ -1,9 +1,21 @@ -import express from 'express' import auditController from '../../controllers/audit' -import { checkPermission } from '../../rbac/PermissionCheck' -const router = express.Router() +import { entitled } from '../../../services/entitled-router' +import { Entitlements } from '../../rbac/Entitlements' +import { AuthenticationStrategy } from '../../auth/AuthenticationStrategy' -router.post(['/', '/login-activity'], checkPermission('loginActivity:view'), auditController.fetchLoginActivity) -router.post(['/', '/login-activity/delete'], checkPermission('loginActivity:delete'), auditController.deleteLoginActivity) +const router = entitled.Router() + +router.post( + ['/', '/login-activity'], + [Entitlements.loginActivity.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + auditController.fetchLoginActivity +) +router.post( + ['/', '/login-activity/delete'], + [Entitlements.loginActivity.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + auditController.deleteLoginActivity +) export default router diff --git a/packages/server/src/enterprise/routes/auth/index.ts b/packages/server/src/enterprise/routes/auth/index.ts index 494b30ccb0a..bfdcb386dc1 100644 --- a/packages/server/src/enterprise/routes/auth/index.ts +++ b/packages/server/src/enterprise/routes/auth/index.ts @@ -1,10 +1,13 @@ -import express from 'express' import authController from '../../controllers/auth' -const router = express.Router() +import { entitled } from '../../../services/entitled-router' +import { Entitlements } from '../../rbac/Entitlements' +import { AuthenticationStrategy } from '../../auth/AuthenticationStrategy' + +const router = entitled.Router() // RBAC -router.get(['/', '/permissions'], authController.getAllPermissions) +router.get(['/', '/permissions'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], authController.getAllPermissions) -router.get(['/sso-success'], authController.ssoSuccess) +router.get(['/sso-success'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], authController.ssoSuccess) export default router diff --git a/packages/server/src/enterprise/routes/login-method.route.ts b/packages/server/src/enterprise/routes/login-method.route.ts index f1c3912e261..d95eae74639 100644 --- a/packages/server/src/enterprise/routes/login-method.route.ts +++ b/packages/server/src/enterprise/routes/login-method.route.ts @@ -1,18 +1,24 @@ -import express from 'express' import { LoginMethodController } from '../controllers/login-method.controller' -import { checkPermission } from '../rbac/PermissionCheck' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../rbac/Entitlements' +import { AuthenticationStrategy } from '../auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() const loginMethodController = new LoginMethodController() -router.get('/', loginMethodController.read) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], loginMethodController.read) -router.get('/default', loginMethodController.defaultMethods) +router.get('/default', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], loginMethodController.defaultMethods) -router.post('/', checkPermission('sso:manage'), loginMethodController.create) +router.post('/', [Entitlements.sso.manage], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], loginMethodController.create) -router.put('/', checkPermission('sso:manage'), loginMethodController.update) +router.put('/', [Entitlements.sso.manage], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], loginMethodController.update) -router.post('/test', checkPermission('sso:manage'), loginMethodController.testConfig) +router.post( + '/test', + [Entitlements.sso.manage], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + loginMethodController.testConfig +) export default router diff --git a/packages/server/src/enterprise/routes/organization-user.route.ts b/packages/server/src/enterprise/routes/organization-user.route.ts index 99241756e9d..718fabb8f1a 100644 --- a/packages/server/src/enterprise/routes/organization-user.route.ts +++ b/packages/server/src/enterprise/routes/organization-user.route.ts @@ -1,17 +1,36 @@ -import express from 'express' import { OrganizationUserController } from '../controllers/organization-user.controller' -import { checkPermission } from '../rbac/PermissionCheck' import { IdentityManager } from '../../IdentityManager' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../rbac/Entitlements' +import { AuthenticationStrategy } from '../auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() const organizationUserController = new OrganizationUserController() -router.get('/', organizationUserController.read) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], organizationUserController.read) -router.post('/', IdentityManager.checkFeatureByPlan('feat:users'), checkPermission('users:manage'), organizationUserController.create) +router.post( + '/', + [Entitlements.users.manage], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + IdentityManager.checkFeatureByPlan('feat:users'), + organizationUserController.create +) -router.put('/', IdentityManager.checkFeatureByPlan('feat:users'), checkPermission('users:manage'), organizationUserController.update) +router.put( + '/', + [Entitlements.users.manage], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + IdentityManager.checkFeatureByPlan('feat:users'), + organizationUserController.update +) -router.delete('/', IdentityManager.checkFeatureByPlan('feat:users'), checkPermission('users:manage'), organizationUserController.delete) +router.delete( + '/', + [Entitlements.users.manage], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + IdentityManager.checkFeatureByPlan('feat:users'), + organizationUserController.delete +) export default router diff --git a/packages/server/src/enterprise/routes/organization.route.ts b/packages/server/src/enterprise/routes/organization.route.ts index 52dc17c2646..11db725cdf0 100644 --- a/packages/server/src/enterprise/routes/organization.route.ts +++ b/packages/server/src/enterprise/routes/organization.route.ts @@ -1,27 +1,54 @@ -import express from 'express' import { OrganizationController } from '../controllers/organization.controller' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../rbac/Entitlements' +import { AuthenticationStrategy } from '../auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() const organizationController = new OrganizationController() -router.get('/', organizationController.read) - -router.post('/', organizationController.create) - -router.put('/', organizationController.update) - -router.get('/additional-seats-quantity', organizationController.getAdditionalSeatsQuantity) - -router.get('/customer-default-source', organizationController.getCustomerWithDefaultSource) - -router.get('/additional-seats-proration', organizationController.getAdditionalSeatsProration) - -router.post('/update-additional-seats', organizationController.updateAdditionalSeats) - -router.get('/plan-proration', organizationController.getPlanProration) - -router.post('/update-subscription-plan', organizationController.updateSubscriptionPlan) - -router.get('/get-current-usage', organizationController.getCurrentUsage) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], organizationController.read) + +router.post('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], organizationController.create) + +router.put('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], organizationController.update) + +router.get( + '/additional-seats-quantity', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + organizationController.getAdditionalSeatsQuantity +) + +router.get( + '/customer-default-source', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + organizationController.getCustomerWithDefaultSource +) + +router.get( + '/additional-seats-proration', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + organizationController.getAdditionalSeatsProration +) + +router.post( + '/update-additional-seats', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + organizationController.updateAdditionalSeats +) + +router.get('/plan-proration', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], organizationController.getPlanProration) + +router.post( + '/update-subscription-plan', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + organizationController.updateSubscriptionPlan +) + +router.get('/get-current-usage', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], organizationController.getCurrentUsage) export default router diff --git a/packages/server/src/enterprise/routes/role.route.ts b/packages/server/src/enterprise/routes/role.route.ts index 19225ba8c14..26a4d93e8ab 100644 --- a/packages/server/src/enterprise/routes/role.route.ts +++ b/packages/server/src/enterprise/routes/role.route.ts @@ -1,16 +1,17 @@ -import express from 'express' import { RoleController } from '../controllers/role.controller' -import { checkPermission } from '../rbac/PermissionCheck' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../rbac/Entitlements' +import { AuthenticationStrategy } from '../auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() const roleController = new RoleController() -router.get('/', roleController.read) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], roleController.read) -router.post('/', checkPermission('roles:manage'), roleController.create) +router.post('/', [Entitlements.roles.manage], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], roleController.create) -router.put('/', checkPermission('roles:manage'), roleController.update) +router.put('/', [Entitlements.roles.manage], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], roleController.update) -router.delete('/', checkPermission('roles:manage'), roleController.delete) +router.delete('/', [Entitlements.roles.manage], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], roleController.delete) export default router diff --git a/packages/server/src/enterprise/routes/user.route.ts b/packages/server/src/enterprise/routes/user.route.ts index dcfc50487e7..dc4e5cd7843 100644 --- a/packages/server/src/enterprise/routes/user.route.ts +++ b/packages/server/src/enterprise/routes/user.route.ts @@ -1,14 +1,16 @@ -import express from 'express' import { UserController } from '../controllers/user.controller' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../rbac/Entitlements' +import { AuthenticationStrategy } from '../auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() const userController = new UserController() -router.get('/', userController.read) -router.get('/test', userController.test) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], userController.read) +router.get('/test', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], userController.test) -router.post('/', userController.create) +router.post('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], userController.create) -router.put('/', userController.update) +router.put('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], userController.update) export default router diff --git a/packages/server/src/enterprise/routes/workspace-user.route.ts b/packages/server/src/enterprise/routes/workspace-user.route.ts index 12ec8244723..b84ab22ddd0 100644 --- a/packages/server/src/enterprise/routes/workspace-user.route.ts +++ b/packages/server/src/enterprise/routes/workspace-user.route.ts @@ -1,32 +1,36 @@ -import express from 'express' import { WorkspaceUserController } from '../controllers/workspace-user.controller' import { IdentityManager } from '../../IdentityManager' -import { checkPermission } from '../rbac/PermissionCheck' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../rbac/Entitlements' +import { AuthenticationStrategy } from '../auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() const workspaceUserController = new WorkspaceUserController() // no feature flag because user with lower plan can read invited workspaces with higher plan -router.get('/', workspaceUserController.read) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], workspaceUserController.read) router.post( '/', + [Entitlements.workspace.addUser], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], IdentityManager.checkFeatureByPlan('feat:workspaces'), - checkPermission('workspace:add-user'), workspaceUserController.create ) router.put( '/', + [Entitlements.workspace.addUser], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], IdentityManager.checkFeatureByPlan('feat:workspaces'), - checkPermission('workspace:add-user'), workspaceUserController.update ) router.delete( '/', + [Entitlements.workspace.unlinkUser], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], IdentityManager.checkFeatureByPlan('feat:workspaces'), - checkPermission('workspace:unlink-user'), workspaceUserController.delete ) diff --git a/packages/server/src/enterprise/routes/workspace.route.ts b/packages/server/src/enterprise/routes/workspace.route.ts index 2e27aa91b6f..cc8f2c9838f 100644 --- a/packages/server/src/enterprise/routes/workspace.route.ts +++ b/packages/server/src/enterprise/routes/workspace.route.ts @@ -1,37 +1,59 @@ -import express from 'express' import { WorkspaceController } from '../controllers/workspace.controller' import { IdentityManager } from '../../IdentityManager' -import { checkPermission } from '../rbac/PermissionCheck' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../rbac/Entitlements' +import { AuthenticationStrategy } from '../auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() const workspaceController = new WorkspaceController() -router.get('/', IdentityManager.checkFeatureByPlan('feat:workspaces'), checkPermission('workspace:view'), workspaceController.read) +router.get( + '/', + [Entitlements.workspace.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + IdentityManager.checkFeatureByPlan('feat:workspaces'), + workspaceController.read +) -router.post('/', IdentityManager.checkFeatureByPlan('feat:workspaces'), checkPermission('workspace:create'), workspaceController.create) +router.post( + '/', + [Entitlements.workspace.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + IdentityManager.checkFeatureByPlan('feat:workspaces'), + workspaceController.create +) // no feature flag because user with lower plan can switch to invited workspaces with higher plan -router.post('/switch', workspaceController.switchWorkspace) +router.post('/switch', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], workspaceController.switchWorkspace) -router.put('/', IdentityManager.checkFeatureByPlan('feat:workspaces'), checkPermission('workspace:update'), workspaceController.update) +router.put( + '/', + [Entitlements.workspace.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + IdentityManager.checkFeatureByPlan('feat:workspaces'), + workspaceController.update +) router.delete( ['/', '/:id'], + [Entitlements.workspace.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], IdentityManager.checkFeatureByPlan('feat:workspaces'), - checkPermission('workspace:delete'), workspaceController.delete ) router.get( ['/shared', '/shared/:id'], + [Entitlements.workspace.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], IdentityManager.checkFeatureByPlan('feat:workspaces'), - checkPermission('workspace:create'), workspaceController.getSharedWorkspacesForItem ) router.post( ['/shared', '/shared/:id'], + [Entitlements.workspace.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], IdentityManager.checkFeatureByPlan('feat:workspaces'), - checkPermission('workspace:create'), workspaceController.setSharedWorkspacesForItem ) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 40666c5c5fa..915ff4d03a3 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -15,7 +15,7 @@ import { AbortControllerPool } from './AbortControllerPool' import { RateLimiterManager } from './utils/rateLimit' import { getAllowedIframeOrigins, getCorsOptions, sanitizeMiddleware } from './utils/XSS' import { Telemetry } from './utils/telemetry' -import flowiseApiV1Router from './routes' +import { createV1APIs, printRoutes } from './routes' import errorHandlerMiddleware from './middlewares/errors' import { WHITELIST_URLS } from './utils/constants' import { initializeJwtCookieMiddleware, verifyToken } from './enterprise/middleware/passport' @@ -305,8 +305,9 @@ export class App { ) } } - - this.app.use('/api/v1', flowiseApiV1Router) + const router = createV1APIs() + this.app.use('/api/v1', router.getRouter()) + printRoutes(router, logger) // ---------------------------------------- // Configure number of proxies in Host Environment diff --git a/packages/server/src/routes/agentflowv2-generator/index.ts b/packages/server/src/routes/agentflowv2-generator/index.ts index 6d2895509b6..5d1a3a73413 100644 --- a/packages/server/src/routes/agentflowv2-generator/index.ts +++ b/packages/server/src/routes/agentflowv2-generator/index.ts @@ -1,7 +1,10 @@ -import express from 'express' import agentflowv2GeneratorController from '../../controllers/agentflowv2-generator' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -router.post('/generate', agentflowv2GeneratorController.generateAgentflowv2) +const router = entitled.Router() + +router.post('/generate', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], agentflowv2GeneratorController.generateAgentflowv2) export default router diff --git a/packages/server/src/routes/apikey/index.ts b/packages/server/src/routes/apikey/index.ts index ec9f1a2c9e5..592140ca4a9 100644 --- a/packages/server/src/routes/apikey/index.ts +++ b/packages/server/src/routes/apikey/index.ts @@ -1,19 +1,36 @@ -import express from 'express' import apikeyController from '../../controllers/apikey' -import { checkAnyPermission, checkPermission } from '../../enterprise/rbac/PermissionCheck' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE -router.post('/', checkPermission('apikeys:create'), apikeyController.createApiKey) -router.post('/import', checkPermission('apikeys:import'), apikeyController.importKeys) +router.post('/', [Entitlements.apikeys.create], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], apikeyController.createApiKey) +router.post( + '/import', + [Entitlements.apikeys.import], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + apikeyController.importKeys +) // READ -router.get('/', checkPermission('apikeys:view'), apikeyController.getAllApiKeys) +router.get('/', [Entitlements.apikeys.view], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], apikeyController.getAllApiKeys) // UPDATE -router.put(['/', '/:id'], checkAnyPermission('apikeys:create,apikeys:update'), apikeyController.updateApiKey) +router.put( + ['/', '/:id'], + [Entitlements.apikeys.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + apikeyController.updateApiKey +) // DELETE -router.delete(['/', '/:id'], checkPermission('apikeys:delete'), apikeyController.deleteApiKey) +router.delete( + ['/', '/:id'], + [Entitlements.apikeys.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + apikeyController.deleteApiKey +) export default router diff --git a/packages/server/src/routes/assistants/index.ts b/packages/server/src/routes/assistants/index.ts index 5599e895483..6f6f9fbf3e8 100644 --- a/packages/server/src/routes/assistants/index.ts +++ b/packages/server/src/routes/assistants/index.ts @@ -1,27 +1,58 @@ -import express from 'express' import assistantsController from '../../controllers/assistants' -import { checkPermission, checkAnyPermission } from '../../enterprise/rbac/PermissionCheck' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // CREATE -router.post('/', checkPermission('assistants:create'), assistantsController.createAssistant) +router.post( + '/', + [Entitlements.assistants.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + assistantsController.createAssistant +) // READ -router.get('/', checkPermission('assistants:view'), assistantsController.getAllAssistants) -router.get(['/', '/:id'], checkPermission('assistants:view'), assistantsController.getAssistantById) +router.get( + '/', + [Entitlements.assistants.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + assistantsController.getAllAssistants +) +router.get( + '/:id', + [Entitlements.assistants.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + assistantsController.getAssistantById +) // UPDATE -router.put(['/', '/:id'], checkAnyPermission('assistants:create,assistants:update'), assistantsController.updateAssistant) +router.put( + '/:id', + [Entitlements.assistants.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + assistantsController.updateAssistant +) // DELETE -router.delete(['/', '/:id'], checkPermission('assistants:delete'), assistantsController.deleteAssistant) +router.delete( + '/:id', + [Entitlements.assistants.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + assistantsController.deleteAssistant +) -router.get('/components/chatmodels', assistantsController.getChatModels) -router.get('/components/docstores', assistantsController.getDocumentStores) -router.get('/components/tools', assistantsController.getTools) +router.get('/components/chatmodels', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], assistantsController.getChatModels) +router.get('/components/docstores', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], assistantsController.getDocumentStores) +router.get('/components/tools', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], assistantsController.getTools) // Generate Assistant Instruction -router.post('/generate/instruction', assistantsController.generateAssistantInstruction) +router.post( + '/generate/instruction', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + assistantsController.generateAssistantInstruction +) export default router diff --git a/packages/server/src/routes/attachments/index.ts b/packages/server/src/routes/attachments/index.ts index 3a67ba67c5f..9ba37db19f8 100644 --- a/packages/server/src/routes/attachments/index.ts +++ b/packages/server/src/routes/attachments/index.ts @@ -1,10 +1,18 @@ -import express from 'express' import attachmentsController from '../../controllers/attachments' import { getMulterStorage } from '../../utils' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // CREATE -router.post('/:chatflowId/:chatId', getMulterStorage().array('files'), attachmentsController.createAttachment) +router.post( + '/:chatflowId/:chatId', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + getMulterStorage().array('files'), + attachmentsController.createAttachment +) export default router diff --git a/packages/server/src/routes/chat-messages/index.ts b/packages/server/src/routes/chat-messages/index.ts index ca90abcf79f..53339eda010 100644 --- a/packages/server/src/routes/chat-messages/index.ts +++ b/packages/server/src/routes/chat-messages/index.ts @@ -1,17 +1,25 @@ -import express from 'express' import chatMessageController from '../../controllers/chat-messages' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE -router.post(['/', '/:id'], chatMessageController.createChatMessage) +router.post(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], chatMessageController.createChatMessage) // READ -router.get(['/', '/:id'], chatMessageController.getAllChatMessages) +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], chatMessageController.getAllChatMessages) // UPDATE -router.put(['/abort/', '/abort/:chatflowid/:chatid'], chatMessageController.abortChatMessage) +router.put( + ['/abort/', '/abort/:chatflowid/:chatid'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + chatMessageController.abortChatMessage +) // DELETE -router.delete(['/', '/:id'], chatMessageController.removeAllChatMessages) +router.delete(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], chatMessageController.removeAllChatMessages) export default router diff --git a/packages/server/src/routes/chatflows-streaming/index.ts b/packages/server/src/routes/chatflows-streaming/index.ts index cc8dc257c1f..dd29d688ab3 100644 --- a/packages/server/src/routes/chatflows-streaming/index.ts +++ b/packages/server/src/routes/chatflows-streaming/index.ts @@ -1,9 +1,16 @@ -import express from 'express' import chatflowsController from '../../controllers/chatflows' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // READ -router.get(['/', '/:id'], chatflowsController.checkIfChatflowIsValidForStreaming) +router.get( + ['/', '/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + chatflowsController.checkIfChatflowIsValidForStreaming +) export default router diff --git a/packages/server/src/routes/chatflows-uploads/index.ts b/packages/server/src/routes/chatflows-uploads/index.ts index 591718c2b89..8483914378b 100644 --- a/packages/server/src/routes/chatflows-uploads/index.ts +++ b/packages/server/src/routes/chatflows-uploads/index.ts @@ -1,9 +1,11 @@ -import express from 'express' import chatflowsController from '../../controllers/chatflows' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // READ -router.get(['/', '/:id'], chatflowsController.checkIfChatflowIsValidForUploads) +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], chatflowsController.checkIfChatflowIsValidForUploads) export default router diff --git a/packages/server/src/routes/chatflows/index.ts b/packages/server/src/routes/chatflows/index.ts index 774f0928dcb..c9dbc569a60 100644 --- a/packages/server/src/routes/chatflows/index.ts +++ b/packages/server/src/routes/chatflows/index.ts @@ -1,23 +1,55 @@ -import express from 'express' import chatflowsController from '../../controllers/chatflows' -import { checkAnyPermission, checkPermission } from '../../enterprise/rbac/PermissionCheck' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE -router.post('/', checkAnyPermission('chatflows:create,chatflows:update'), chatflowsController.saveChatflow) +router.post( + '/', + [Entitlements.chatflows.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + chatflowsController.saveChatflow +) // READ -router.get('/', checkAnyPermission('chatflows:view,chatflows:update'), chatflowsController.getAllChatflows) -router.get(['/', '/:id'], checkAnyPermission('chatflows:view,chatflows:update,chatflows:delete'), chatflowsController.getChatflowById) -router.get(['/apikey/', '/apikey/:apikey'], chatflowsController.getChatflowByApiKey) +router.get( + '/', + [Entitlements.chatflows.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + chatflowsController.getAllChatflows +) +router.get( + '/:id', + [Entitlements.chatflows.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + chatflowsController.getChatflowById +) +router.get('/apikey/:apikey', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], chatflowsController.getChatflowByApiKey) // UPDATE -router.put(['/', '/:id'], checkAnyPermission('chatflows:create,chatflows:update'), chatflowsController.updateChatflow) +router.put( + '/:id', + [Entitlements.chatflows.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + chatflowsController.updateChatflow +) // DELETE -router.delete(['/', '/:id'], checkPermission('chatflows:delete'), chatflowsController.deleteChatflow) +router.delete( + '/:id', + [Entitlements.chatflows.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + chatflowsController.deleteChatflow +) // CHECK FOR CHANGE -router.get('/has-changed/:id/:lastUpdatedDateTime', chatflowsController.checkIfChatflowHasChanged) +router.get( + '/has-changed/:id/:lastUpdatedDateTime', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + chatflowsController.checkIfChatflowHasChanged +) export default router diff --git a/packages/server/src/routes/components-credentials-icon/index.ts b/packages/server/src/routes/components-credentials-icon/index.ts index 50d22134b2e..a16e225744d 100644 --- a/packages/server/src/routes/components-credentials-icon/index.ts +++ b/packages/server/src/routes/components-credentials-icon/index.ts @@ -1,11 +1,19 @@ -import express from 'express' import componentsCredentialsController from '../../controllers/components-credentials' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE // READ -router.get(['/', '/:name'], componentsCredentialsController.getSingleComponentsCredentialIcon) +router.get( + ['/', '/:name'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + componentsCredentialsController.getSingleComponentsCredentialIcon +) // UPDATE diff --git a/packages/server/src/routes/components-credentials/index.ts b/packages/server/src/routes/components-credentials/index.ts index 16aff2ff2b1..a8351dbe57d 100644 --- a/packages/server/src/routes/components-credentials/index.ts +++ b/packages/server/src/routes/components-credentials/index.ts @@ -1,9 +1,12 @@ -import express from 'express' import componentsCredentialsController from '../../controllers/components-credentials' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/', componentsCredentialsController.getAllComponentsCredentials) -router.get(['/', '/:name'], componentsCredentialsController.getComponentByName) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], componentsCredentialsController.getAllComponentsCredentials) +router.get(['/', '/:name'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], componentsCredentialsController.getComponentByName) export default router diff --git a/packages/server/src/routes/credentials/index.ts b/packages/server/src/routes/credentials/index.ts index 6e97547db07..7875acad0b5 100644 --- a/packages/server/src/routes/credentials/index.ts +++ b/packages/server/src/routes/credentials/index.ts @@ -1,19 +1,46 @@ -import express from 'express' import credentialsController from '../../controllers/credentials' -import { checkPermission, checkAnyPermission } from '../../enterprise/rbac/PermissionCheck' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE -router.post('/', checkPermission('credentials:create'), credentialsController.createCredential) +router.post( + '/', + [Entitlements.credentials.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + credentialsController.createCredential +) // READ -router.get('/', checkPermission('credentials:view'), credentialsController.getAllCredentials) -router.get(['/', '/:id'], checkPermission('credentials:view'), credentialsController.getCredentialById) +router.get( + '/', + [Entitlements.credentials.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + credentialsController.getAllCredentials +) +router.get( + '/:id', + [Entitlements.credentials.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + credentialsController.getCredentialById +) // UPDATE -router.put(['/', '/:id'], checkAnyPermission('credentials:create,credentials:update'), credentialsController.updateCredential) +router.put( + '/:id', + [Entitlements.credentials.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + credentialsController.updateCredential +) // DELETE -router.delete(['/', '/:id'], checkPermission('credentials:delete'), credentialsController.deleteCredentials) +router.delete( + '/:id', + [Entitlements.credentials.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + credentialsController.deleteCredentials +) export default router diff --git a/packages/server/src/routes/dataset/index.ts b/packages/server/src/routes/dataset/index.ts index 7b73e546ac4..e9f2b90450b 100644 --- a/packages/server/src/routes/dataset/index.ts +++ b/packages/server/src/routes/dataset/index.ts @@ -1,29 +1,81 @@ -import express from 'express' import datasetController from '../../controllers/dataset' -import { checkAnyPermission, checkPermission } from '../../enterprise/rbac/PermissionCheck' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // get all datasets -router.get('/', checkPermission('datasets:view'), datasetController.getAllDatasets) +router.get( + '/', + [Entitlements.datasets.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + datasetController.getAllDatasets +) // get new dataset -router.get(['/set', '/set/:id'], checkPermission('datasets:view'), datasetController.getDataset) +router.get( + ['/set', '/set/:id'], + [Entitlements.datasets.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + datasetController.getDataset +) // Create new dataset -router.post(['/set', '/set/:id'], checkPermission('datasets:create'), datasetController.createDataset) +router.post( + ['/set', '/set/:id'], + [Entitlements.datasets.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + datasetController.createDataset +) // Update dataset -router.put(['/set', '/set/:id'], checkAnyPermission('datasets:create,datasets:update'), datasetController.updateDataset) +router.put( + ['/set', '/set/:id'], + [Entitlements.datasets.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + datasetController.updateDataset +) // Delete dataset via id -router.delete(['/set', '/set/:id'], checkPermission('datasets:delete'), datasetController.deleteDataset) +router.delete( + ['/set', '/set/:id'], + [Entitlements.datasets.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + datasetController.deleteDataset +) // Create new row in a given dataset -router.post(['/rows', '/rows/:id'], checkPermission('datasets:create'), datasetController.addDatasetRow) +router.post( + ['/rows', '/rows/:id'], + [Entitlements.datasets.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + datasetController.addDatasetRow +) // Update row for a dataset -router.put(['/rows', '/rows/:id'], checkAnyPermission('datasets:create,datasets:update'), datasetController.updateDatasetRow) +router.put( + ['/rows', '/rows/:id'], + [Entitlements.datasets.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + datasetController.updateDatasetRow +) // Delete dataset row via id -router.delete(['/rows', '/rows/:id'], checkPermission('datasets:delete'), datasetController.deleteDatasetRow) +router.delete( + ['/rows', '/rows/:id'], + [Entitlements.datasets.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + datasetController.deleteDatasetRow +) // PATCH delete by ids -router.patch('/rows', checkPermission('datasets:delete'), datasetController.patchDeleteRows) +router.patch( + '/rows', + [Entitlements.datasets.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + datasetController.patchDeleteRows +) // Update row for a dataset -router.post(['/reorder', '/reorder'], checkAnyPermission('datasets:create,datasets:update'), datasetController.reorderDatasetRow) +router.post( + ['/reorder', '/reorder'], + [Entitlements.datasets.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + datasetController.reorderDatasetRow +) export default router diff --git a/packages/server/src/routes/documentstore/index.ts b/packages/server/src/routes/documentstore/index.ts index 41ce33bb859..07c2a462c41 100644 --- a/packages/server/src/routes/documentstore/index.ts +++ b/packages/server/src/routes/documentstore/index.ts @@ -1,84 +1,195 @@ -import express from 'express' -import { checkPermission, checkAnyPermission } from '../../enterprise/rbac/PermissionCheck' import documentStoreController from '../../controllers/documentstore' +import { entitled } from '../../services/entitled-router' import { getMulterStorage } from '../../utils' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() -router.post(['/upsert/', '/upsert/:id'], getMulterStorage().array('files'), documentStoreController.upsertDocStoreMiddleware) +router.post( + ['/upsert/', '/upsert/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.SESSION], + getMulterStorage().array('files'), + documentStoreController.upsertDocStoreMiddleware +) -router.post(['/refresh/', '/refresh/:id'], documentStoreController.refreshDocStoreMiddleware) +router.post( + ['/refresh/', '/refresh/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.SESSION], + documentStoreController.refreshDocStoreMiddleware +) /** Document Store Routes */ // Create document store -router.post('/store', checkPermission('documentStores:create'), documentStoreController.createDocumentStore) +router.post( + '/store', + [Entitlements.documentStores.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.createDocumentStore +) // List all stores -router.get('/store', checkPermission('documentStores:view'), documentStoreController.getAllDocumentStores) +router.get( + '/store', + [Entitlements.documentStores.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.getAllDocumentStores +) // Get specific store router.get( '/store/:id', - checkAnyPermission('documentStores:view,documentStores:update,documentStores:delete'), + [Entitlements.documentStores.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], documentStoreController.getDocumentStoreById ) // Update documentStore -router.put('/store/:id', checkAnyPermission('documentStores:create,documentStores:update'), documentStoreController.updateDocumentStore) +router.put( + '/store/:id', + [Entitlements.documentStores.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.updateDocumentStore +) // Delete documentStore -router.delete('/store/:id', checkPermission('documentStores:delete'), documentStoreController.deleteDocumentStore) +router.delete( + '/store/:id', + [Entitlements.documentStores.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.deleteDocumentStore +) // Get document store configs -router.get('/store-configs/:id/:loaderId', checkAnyPermission('documentStores:view'), documentStoreController.getDocStoreConfigs) +router.get( + '/store-configs/:id/:loaderId', + [Entitlements.documentStores.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.getDocStoreConfigs +) /** Component Nodes = Document Store - Loaders */ // Get all loaders -router.get('/components/loaders', checkPermission('documentStores:add-loader'), documentStoreController.getDocumentLoaders) +router.get( + '/components/loaders', + [Entitlements.documentStores.addLoader], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.getDocumentLoaders +) // delete loader from document store router.delete( '/loader/:id/:loaderId', - checkPermission('documentStores:delete-loader'), + [Entitlements.documentStores.deleteLoader], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], documentStoreController.deleteLoaderFromDocumentStore ) // chunking preview -router.post('/loader/preview', checkPermission('documentStores:preview-process'), documentStoreController.previewFileChunks) +router.post( + '/loader/preview', + [Entitlements.documentStores.previewProcess], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.previewFileChunks +) // saving process -router.post('/loader/save', checkPermission('documentStores:preview-process'), documentStoreController.saveProcessingLoader) +router.post( + '/loader/save', + [Entitlements.documentStores.previewProcess], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.saveProcessingLoader +) // chunking process -router.post('/loader/process/:loaderId', checkPermission('documentStores:preview-process'), documentStoreController.processLoader) +router.post( + '/loader/process/:loaderId', + [Entitlements.documentStores.previewProcess], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.processLoader +) /** Document Store - Loaders - Chunks */ // delete specific file chunk from the store router.delete( '/chunks/:storeId/:loaderId/:chunkId', - checkAnyPermission('documentStores:update,documentStores:delete'), + [Entitlements.documentStores.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], documentStoreController.deleteDocumentStoreFileChunk ) // edit specific file chunk from the store router.put( '/chunks/:storeId/:loaderId/:chunkId', - checkPermission('documentStores:update'), + [Entitlements.documentStores.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], documentStoreController.editDocumentStoreFileChunk ) // Get all file chunks from the store -router.get('/chunks/:storeId/:fileId/:pageNo', checkPermission('documentStores:view'), documentStoreController.getDocumentStoreFileChunks) +router.get( + '/chunks/:storeId/:fileId/:pageNo', + [Entitlements.documentStores.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.getDocumentStoreFileChunks +) // add chunks to the selected vector store -router.post('/vectorstore/insert', checkPermission('documentStores:upsert-config'), documentStoreController.insertIntoVectorStore) +router.post( + '/vectorstore/insert', + [Entitlements.documentStores.upsertConfig], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.insertIntoVectorStore +) // save the selected vector store -router.post('/vectorstore/save', checkPermission('documentStores:upsert-config'), documentStoreController.saveVectorStoreConfig) +router.post( + '/vectorstore/save', + [Entitlements.documentStores.upsertConfig], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.saveVectorStoreConfig +) // delete data from the selected vector store -router.delete('/vectorstore/:storeId', checkPermission('documentStores:upsert-config'), documentStoreController.deleteVectorStoreFromStore) +router.delete( + '/vectorstore/:storeId', + [Entitlements.documentStores.upsertConfig], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.deleteVectorStoreFromStore +) // query the vector store -router.post('/vectorstore/query', checkPermission('documentStores:view'), documentStoreController.queryVectorStore) +router.post( + '/vectorstore/query', + [Entitlements.documentStores.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.queryVectorStore +) // Get all embedding providers -router.get('/components/embeddings', checkPermission('documentStores:upsert-config'), documentStoreController.getEmbeddingProviders) +router.get( + '/components/embeddings', + [Entitlements.documentStores.upsertConfig], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.getEmbeddingProviders +) // Get all vector store providers -router.get('/components/vectorstore', checkPermission('documentStores:upsert-config'), documentStoreController.getVectorStoreProviders) +router.get( + '/components/vectorstore', + [Entitlements.documentStores.upsertConfig], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.getVectorStoreProviders +) // Get all Record Manager providers -router.get('/components/recordmanager', checkPermission('documentStores:upsert-config'), documentStoreController.getRecordManagerProviders) +router.get( + '/components/recordmanager', + [Entitlements.documentStores.upsertConfig], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.getRecordManagerProviders +) // update the selected vector store from the playground -router.post('/vectorstore/update', checkPermission('documentStores:upsert-config'), documentStoreController.updateVectorStoreConfigOnly) +router.post( + '/vectorstore/update', + [Entitlements.documentStores.upsertConfig], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + documentStoreController.updateVectorStoreConfigOnly +) // generate docstore tool description -router.post('/generate-tool-desc/:id', documentStoreController.generateDocStoreToolDesc) +router.post( + '/generate-tool-desc/:id', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + documentStoreController.generateDocStoreToolDesc +) export default router diff --git a/packages/server/src/routes/evaluations/index.ts b/packages/server/src/routes/evaluations/index.ts index 14bb7c7fac4..0128326f2dd 100644 --- a/packages/server/src/routes/evaluations/index.ts +++ b/packages/server/src/routes/evaluations/index.ts @@ -1,14 +1,52 @@ -import express from 'express' import evaluationsController from '../../controllers/evaluations' -import { checkPermission, checkAnyPermission } from '../../enterprise/rbac/PermissionCheck' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() + +router.get( + '/', + [Entitlements.evaluations.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluationsController.getAllEvaluations +) +router.get( + '/:id', + [Entitlements.evaluations.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluationsController.getEvaluation +) +router.delete( + '/:id', + [Entitlements.evaluations.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluationsController.deleteEvaluation +) +router.post( + '/', + [Entitlements.evaluations.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluationsController.createEvaluation +) +router.get('/is-outdated/:id', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], evaluationsController.isOutdated) +router.post( + '/run-again/:id', + [Entitlements.evaluations.run], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluationsController.runAgain +) +router.get( + '/versions/:id', + [Entitlements.evaluations.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluationsController.getVersions +) +router.patch( + '/', + [Entitlements.evaluations.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluationsController.patchDeleteEvaluations +) -router.get('/', checkPermission('evaluations:view'), evaluationsController.getAllEvaluations) -router.get('/:id', checkPermission('evaluations:view'), evaluationsController.getEvaluation) -router.delete('/:id', checkPermission('evaluations:delete'), evaluationsController.deleteEvaluation) -router.post('/', checkPermission('evaluations:create'), evaluationsController.createEvaluation) -router.get('/is-outdated/:id', evaluationsController.isOutdated) -router.post('/run-again/:id', checkAnyPermission('evaluations:create,evaluations:run'), evaluationsController.runAgain) -router.get('/versions/:id', checkPermission('evaluations:view'), evaluationsController.getVersions) -router.patch('/', checkPermission('evaluations:delete'), evaluationsController.patchDeleteEvaluations) export default router diff --git a/packages/server/src/routes/evaluator/index.ts b/packages/server/src/routes/evaluator/index.ts index 481cbaf8e08..215530c1a1c 100644 --- a/packages/server/src/routes/evaluator/index.ts +++ b/packages/server/src/routes/evaluator/index.ts @@ -1,17 +1,44 @@ -import express from 'express' import evaluatorsController from '../../controllers/evaluators' -import { checkPermission, checkAnyPermission } from '../../enterprise/rbac/PermissionCheck' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // get all datasets -router.get('/', checkPermission('evaluators:view'), evaluatorsController.getAllEvaluators) +router.get( + '/', + [Entitlements.evaluators.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluatorsController.getAllEvaluators +) // get new dataset -router.get(['/', '/:id'], checkPermission('evaluators:view'), evaluatorsController.getEvaluator) +router.get( + ['/', '/:id'], + [Entitlements.evaluators.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluatorsController.getEvaluator +) // Create new dataset -router.post(['/', '/:id'], checkPermission('evaluators:create'), evaluatorsController.createEvaluator) +router.post( + ['/', '/:id'], + [Entitlements.evaluators.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluatorsController.createEvaluator +) // Update dataset -router.put(['/', '/:id'], checkAnyPermission('evaluators:create,evaluators:update'), evaluatorsController.updateEvaluator) +router.put( + ['/', '/:id'], + [Entitlements.evaluators.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluatorsController.updateEvaluator +) // Delete dataset via id -router.delete(['/', '/:id'], checkPermission('evaluators:delete'), evaluatorsController.deleteEvaluator) +router.delete( + ['/', '/:id'], + [Entitlements.evaluators.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + evaluatorsController.deleteEvaluator +) export default router diff --git a/packages/server/src/routes/executions/index.ts b/packages/server/src/routes/executions/index.ts index 6106613a19c..a4664db207a 100644 --- a/packages/server/src/routes/executions/index.ts +++ b/packages/server/src/routes/executions/index.ts @@ -1,17 +1,39 @@ -import express from 'express' import executionController from '../../controllers/executions' -import { checkAnyPermission } from '../../enterprise/rbac/PermissionCheck' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/', checkAnyPermission('executions:view'), executionController.getAllExecutions) -router.get(['/', '/:id'], checkAnyPermission('executions:view'), executionController.getExecutionById) +router.get( + '/', + [Entitlements.executions.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + executionController.getAllExecutions +) +router.get( + '/:id', + [Entitlements.executions.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + executionController.getExecutionById +) // PUT -router.put(['/', '/:id'], executionController.updateExecution) +router.put(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.SESSION], executionController.updateExecution) // DELETE - single execution or multiple executions -router.delete('/:id', checkAnyPermission('executions:delete'), executionController.deleteExecutions) -router.delete('/', checkAnyPermission('executions:delete'), executionController.deleteExecutions) +router.delete( + '/:id', + [Entitlements.executions.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + executionController.deleteExecutions +) +router.delete( + '/', + [Entitlements.executions.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + executionController.deleteExecutions +) export default router diff --git a/packages/server/src/routes/export-import/index.ts b/packages/server/src/routes/export-import/index.ts index 17b28a7c346..1d006b3926b 100644 --- a/packages/server/src/routes/export-import/index.ts +++ b/packages/server/src/routes/export-import/index.ts @@ -1,10 +1,22 @@ -import express from 'express' import exportImportController from '../../controllers/export-import' -import { checkPermission } from '../../enterprise/rbac/PermissionCheck' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -router.post('/export', checkPermission('workspace:export'), exportImportController.exportData) +const router = entitled.Router() -router.post('/import', checkPermission('workspace:import'), exportImportController.importData) +router.post( + '/export', + [Entitlements.workspace.export], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + exportImportController.exportData +) + +router.post( + '/import', + [Entitlements.workspace.import], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + exportImportController.importData +) export default router diff --git a/packages/server/src/routes/feedback/index.ts b/packages/server/src/routes/feedback/index.ts index bcec7c74ad3..4ff0e77fab8 100644 --- a/packages/server/src/routes/feedback/index.ts +++ b/packages/server/src/routes/feedback/index.ts @@ -1,14 +1,27 @@ -import express from 'express' import feedbackController from '../../controllers/feedback' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE -router.post(['/', '/:id'], feedbackController.createChatMessageFeedbackForChatflow) +router.post( + ['/', '/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + feedbackController.createChatMessageFeedbackForChatflow +) // READ -router.get(['/', '/:id'], feedbackController.getAllChatMessageFeedback) +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], feedbackController.getAllChatMessageFeedback) // UPDATE -router.put(['/', '/:id'], feedbackController.updateChatMessageFeedbackForChatflow) +router.put( + ['/', '/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + feedbackController.updateChatMessageFeedbackForChatflow +) export default router diff --git a/packages/server/src/routes/fetch-links/index.ts b/packages/server/src/routes/fetch-links/index.ts index a02abd588c3..25e7191a2df 100644 --- a/packages/server/src/routes/fetch-links/index.ts +++ b/packages/server/src/routes/fetch-links/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import fetchLinksController from '../../controllers/fetch-links' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/', fetchLinksController.getAllLinks) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], fetchLinksController.getAllLinks) export default router diff --git a/packages/server/src/routes/files/index.ts b/packages/server/src/routes/files/index.ts index 3a48183d48e..4d3daafaaf6 100644 --- a/packages/server/src/routes/files/index.ts +++ b/packages/server/src/routes/files/index.ts @@ -1,11 +1,14 @@ -import express from 'express' import filesController from '../../controllers/files' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/', filesController.getAllFiles) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], filesController.getAllFiles) // DELETE -router.delete('/', filesController.deleteFile) +router.delete('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], filesController.deleteFile) export default router diff --git a/packages/server/src/routes/flow-config/index.ts b/packages/server/src/routes/flow-config/index.ts index bd841507a39..074d81c00da 100644 --- a/packages/server/src/routes/flow-config/index.ts +++ b/packages/server/src/routes/flow-config/index.ts @@ -1,11 +1,14 @@ -import express from 'express' import flowConfigsController from '../../controllers/flow-configs' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE // READ -router.get(['/', '/:id'], flowConfigsController.getSingleFlowConfig) +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], flowConfigsController.getSingleFlowConfig) // UPDATE diff --git a/packages/server/src/routes/get-upload-file/index.ts b/packages/server/src/routes/get-upload-file/index.ts index 319c73ec219..70b355c0fc3 100644 --- a/packages/server/src/routes/get-upload-file/index.ts +++ b/packages/server/src/routes/get-upload-file/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import getUploadFileController from '../../controllers/get-upload-file' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/', getUploadFileController.streamUploadedFile) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], getUploadFileController.streamUploadedFile) export default router diff --git a/packages/server/src/routes/get-upload-path/index.ts b/packages/server/src/routes/get-upload-path/index.ts index 48827c9a1a2..21039642112 100644 --- a/packages/server/src/routes/get-upload-path/index.ts +++ b/packages/server/src/routes/get-upload-path/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import getUploadPathController from '../../controllers/get-upload-path' -const router = express.Router() +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { entitled } from '../../services/entitled-router' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/', getUploadPathController.getPathForUploads) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], getUploadPathController.getPathForUploads) export default router diff --git a/packages/server/src/routes/index.ts b/packages/server/src/routes/index.ts index 4941a0076f9..607db3b7c8d 100644 --- a/packages/server/src/routes/index.ts +++ b/packages/server/src/routes/index.ts @@ -1,4 +1,3 @@ -import express from 'express' import apikeyRouter from './apikey' import assistantsRouter from './assistants' import attachmentsRouter from './attachments' @@ -67,75 +66,112 @@ import workspaceUserRouter from '../enterprise/routes/workspace-user.route' import accountRouter from '../enterprise/routes/account.route' import loginMethodRouter from '../enterprise/routes/login-method.route' import { IdentityManager } from '../IdentityManager' +import { entitled, EntitledRouter, RegisteredRoute } from '../services/entitled-router' +import * as fs from 'fs' +import { Logger } from 'winston' +import * as path from 'path' -const router = express.Router() +export const createV1APIs = () => { + const router = entitled.Router() + router.use('/ping', pingRouter) + router.use('/apikey', apikeyRouter) + router.use('/assistants', assistantsRouter) + router.use('/attachments', attachmentsRouter) + router.use('/chatflows', chatflowsRouter) + router.use('/chatflows-streaming', chatflowsStreamingRouter) + router.use('/chatmessage', chatMessageRouter) + router.use('/chatflows-uploads', chatflowsUploadsRouter) + router.use('/components-credentials', componentsCredentialsRouter) + router.use('/components-credentials-icon', componentsCredentialsIconRouter) + router.use('/credentials', credentialsRouter) + router.use('/datasets', IdentityManager.checkFeatureByPlan('feat:datasets'), datasetRouter) + router.use('/document-store', documentStoreRouter) + router.use('/evaluations', IdentityManager.checkFeatureByPlan('feat:evaluations'), evaluationsRouter) + router.use('/evaluators', IdentityManager.checkFeatureByPlan('feat:evaluators'), evaluatorsRouter) + router.use('/export-import', exportImportRouter) + router.use('/feedback', feedbackRouter) + router.use('/fetch-links', fetchLinksRouter) + router.use('/flow-config', flowConfigRouter) + router.use('/internal-chatmessage', internalChatmessagesRouter) + router.use('/internal-prediction', internalPredictionRouter) + router.use('/get-upload-file', getUploadFileRouter) + router.use('/get-upload-path', getUploadPathRouter) + router.use('/leads', leadsRouter) + router.use('/load-prompt', loadPromptRouter) + router.use('/marketplaces', marketplacesRouter) + router.use('/node-config', nodeConfigRouter) + router.use('/node-custom-function', nodeCustomFunctionRouter) + router.use('/node-icon', nodeIconRouter) + router.use('/node-load-method', nodeLoadMethodRouter) + router.use('/nodes', nodesRouter) + router.use('/oauth2-credential', oauth2Router) + router.use('/openai-assistants', openaiAssistantsRouter) + router.use('/openai-assistants-file', openaiAssistantsFileRouter) + router.use('/openai-assistants-vector-store', openaiAssistantsVectorStoreRouter) + router.use('/openai-realtime', openaiRealtimeRouter) + router.use('/prediction', predictionRouter) + router.use('/prompts-list', promptListsRouter) + router.use('/public-chatbotConfig', publicChatbotRouter) + router.use('/public-chatflows', publicChatflowsRouter) + router.use('/public-executions', publicExecutionsRouter) + router.use('/stats', statsRouter) + router.use('/tools', toolsRouter) + router.use('/variables', variablesRouter) + router.use('/vector', vectorRouter) + router.use('/verify', verifyRouter) + router.use('/version', versionRouter) + router.use('/upsert-history', upsertHistoryRouter) + router.use('/settings', settingsRouter) + router.use('/pricing', pricingRouter) + router.use('/nvidia-nim', nvidiaNimRouter) + router.use('/executions', executionsRouter) + router.use('/validation', validationRouter) + router.use('/agentflowv2-generator', agentflowv2GeneratorRouter) -router.use('/ping', pingRouter) -router.use('/apikey', apikeyRouter) -router.use('/assistants', assistantsRouter) -router.use('/attachments', attachmentsRouter) -router.use('/chatflows', chatflowsRouter) -router.use('/chatflows-streaming', chatflowsStreamingRouter) -router.use('/chatmessage', chatMessageRouter) -router.use('/chatflows-uploads', chatflowsUploadsRouter) -router.use('/components-credentials', componentsCredentialsRouter) -router.use('/components-credentials-icon', componentsCredentialsIconRouter) -router.use('/credentials', credentialsRouter) -router.use('/datasets', IdentityManager.checkFeatureByPlan('feat:datasets'), datasetRouter) -router.use('/document-store', documentStoreRouter) -router.use('/evaluations', IdentityManager.checkFeatureByPlan('feat:evaluations'), evaluationsRouter) -router.use('/evaluators', IdentityManager.checkFeatureByPlan('feat:evaluators'), evaluatorsRouter) -router.use('/export-import', exportImportRouter) -router.use('/feedback', feedbackRouter) -router.use('/fetch-links', fetchLinksRouter) -router.use('/flow-config', flowConfigRouter) -router.use('/internal-chatmessage', internalChatmessagesRouter) -router.use('/internal-prediction', internalPredictionRouter) -router.use('/get-upload-file', getUploadFileRouter) -router.use('/get-upload-path', getUploadPathRouter) -router.use('/leads', leadsRouter) -router.use('/load-prompt', loadPromptRouter) -router.use('/marketplaces', marketplacesRouter) -router.use('/node-config', nodeConfigRouter) -router.use('/node-custom-function', nodeCustomFunctionRouter) -router.use('/node-icon', nodeIconRouter) -router.use('/node-load-method', nodeLoadMethodRouter) -router.use('/nodes', nodesRouter) -router.use('/oauth2-credential', oauth2Router) -router.use('/openai-assistants', openaiAssistantsRouter) -router.use('/openai-assistants-file', openaiAssistantsFileRouter) -router.use('/openai-assistants-vector-store', openaiAssistantsVectorStoreRouter) -router.use('/openai-realtime', openaiRealtimeRouter) -router.use('/prediction', predictionRouter) -router.use('/prompts-list', promptListsRouter) -router.use('/public-chatbotConfig', publicChatbotRouter) -router.use('/public-chatflows', publicChatflowsRouter) -router.use('/public-executions', publicExecutionsRouter) -router.use('/stats', statsRouter) -router.use('/tools', toolsRouter) -router.use('/variables', variablesRouter) -router.use('/vector', vectorRouter) -router.use('/verify', verifyRouter) -router.use('/version', versionRouter) -router.use('/upsert-history', upsertHistoryRouter) -router.use('/settings', settingsRouter) -router.use('/pricing', pricingRouter) -router.use('/nvidia-nim', nvidiaNimRouter) -router.use('/executions', executionsRouter) -router.use('/validation', validationRouter) -router.use('/agentflowv2-generator', agentflowv2GeneratorRouter) + router.use('/auth', authRouter) + router.use('/audit', IdentityManager.checkFeatureByPlan('feat:login-activity'), auditRouter) + router.use('/user', userRouter) + router.use('/organization', organizationRouter) + router.use('/role', IdentityManager.checkFeatureByPlan('feat:roles'), roleRouter) + router.use('/organizationuser', organizationUserRoute) + router.use('/workspace', workspaceRouter) + router.use('/workspaceuser', workspaceUserRouter) + router.use('/account', accountRouter) + router.use('/loginmethod', loginMethodRouter) + router.use('/logs', IdentityManager.checkFeatureByPlan('feat:logs'), logsRouter) + router.use('/files', IdentityManager.checkFeatureByPlan('feat:files'), filesRouter) -router.use('/auth', authRouter) -router.use('/audit', IdentityManager.checkFeatureByPlan('feat:login-activity'), auditRouter) -router.use('/user', userRouter) -router.use('/organization', organizationRouter) -router.use('/role', IdentityManager.checkFeatureByPlan('feat:roles'), roleRouter) -router.use('/organizationuser', organizationUserRoute) -router.use('/workspace', workspaceRouter) -router.use('/workspaceuser', workspaceUserRouter) -router.use('/account', accountRouter) -router.use('/loginmethod', loginMethodRouter) -router.use('/logs', IdentityManager.checkFeatureByPlan('feat:logs'), logsRouter) -router.use('/files', IdentityManager.checkFeatureByPlan('feat:files'), filesRouter) + return router +} +export function printRoutes(router: EntitledRouter, logger: Logger) { + const registeredRoutes = router.getRegisteredRoutes() + if (registeredRoutes.length === 0) { + logger.warn('No registered routes found to write to CSV.') + return + } + exportRoutesToCSV(registeredRoutes, logger) +} -export default router +function exportRoutesToCSV(registeredRoutes: RegisteredRoute[], logger: Logger) { + try { + const escapeCsvField = (field: string) => `"${field.replace(/"/g, '""')}"` + + const header = 'Method,Path,Entitlements,AuthenticationStrategies\n' + const rows = registeredRoutes + .sort((a, b) => a.path.toString().localeCompare(b.path.toString())) + .map((route) => { + const method = route.method.toUpperCase() + const routePath = '/api/v1' + route.path.toString() + const entitlements = route.entitlements.join('; ') + const authStrategies = route.authenticationStrategies.join('; ') + return [method, routePath, escapeCsvField(entitlements), escapeCsvField(authStrategies)].join(',') + }) + .join('\n') + + const filePath = path.join(__dirname, '..', 'registered_routes.csv') + fs.writeFileSync(filePath, header + rows) + logger.info(`✅ Registered routes successfully written to ${filePath}`) + } catch (error) { + logger.error('❌ Error writing registered routes to CSV:', error) + } +} diff --git a/packages/server/src/routes/internal-chat-messages/index.ts b/packages/server/src/routes/internal-chat-messages/index.ts index 5dcf1e67505..d884fdf572f 100644 --- a/packages/server/src/routes/internal-chat-messages/index.ts +++ b/packages/server/src/routes/internal-chat-messages/index.ts @@ -1,11 +1,14 @@ -import express from 'express' import chatMessagesController from '../../controllers/chat-messages' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE // READ -router.get(['/', '/:id'], chatMessagesController.getAllInternalChatMessages) +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], chatMessagesController.getAllInternalChatMessages) // UPDATE diff --git a/packages/server/src/routes/internal-predictions/index.ts b/packages/server/src/routes/internal-predictions/index.ts index 8e39dce20bc..b89c2cbde05 100644 --- a/packages/server/src/routes/internal-predictions/index.ts +++ b/packages/server/src/routes/internal-predictions/index.ts @@ -1,8 +1,16 @@ -import express from 'express' import internalPredictionsController from '../../controllers/internal-predictions' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE -router.post(['/', '/:id'], internalPredictionsController.createInternalPrediction) +router.post( + ['/', '/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + internalPredictionsController.createInternalPrediction +) export default router diff --git a/packages/server/src/routes/leads/index.ts b/packages/server/src/routes/leads/index.ts index 64209eefd1d..d40f5463dce 100644 --- a/packages/server/src/routes/leads/index.ts +++ b/packages/server/src/routes/leads/index.ts @@ -1,11 +1,14 @@ -import express from 'express' import leadsController from '../../controllers/leads' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE -router.post('/', leadsController.createLeadInChatflow) +router.post('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], leadsController.createLeadInChatflow) // READ -router.get(['/', '/:id'], leadsController.getAllLeadsForChatflow) +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], leadsController.getAllLeadsForChatflow) export default router diff --git a/packages/server/src/routes/load-prompts/index.ts b/packages/server/src/routes/load-prompts/index.ts index a12afba0068..384381a4e42 100644 --- a/packages/server/src/routes/load-prompts/index.ts +++ b/packages/server/src/routes/load-prompts/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import loadPromptsController from '../../controllers/load-prompts' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE -router.post('/', loadPromptsController.createPrompt) +router.post('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], loadPromptsController.createPrompt) export default router diff --git a/packages/server/src/routes/log/index.ts b/packages/server/src/routes/log/index.ts index 290004f5a76..561d55dd1cd 100644 --- a/packages/server/src/routes/log/index.ts +++ b/packages/server/src/routes/log/index.ts @@ -1,9 +1,11 @@ -import express from 'express' import logController from '../../controllers/log' -import { checkAnyPermission } from '../../enterprise/rbac/PermissionCheck' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/', checkAnyPermission('logs:view'), logController.getLogs) +router.get('/', [Entitlements.logs.view], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], logController.getLogs) export default router diff --git a/packages/server/src/routes/marketplaces/index.ts b/packages/server/src/routes/marketplaces/index.ts index 050140358ff..bb9298f7199 100644 --- a/packages/server/src/routes/marketplaces/index.ts +++ b/packages/server/src/routes/marketplaces/index.ts @@ -1,17 +1,39 @@ -import express from 'express' import marketplacesController from '../../controllers/marketplaces' -import { checkPermission, checkAnyPermission } from '../../enterprise/rbac/PermissionCheck' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/templates', checkPermission('templates:marketplace'), marketplacesController.getAllTemplates) +router.get( + '/templates', + [Entitlements.templates.marketplace], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + marketplacesController.getAllTemplates +) -router.post('/custom', checkAnyPermission('templates:flowexport,templates:toolexport'), marketplacesController.saveCustomTemplate) +router.post( + '/custom', + [Entitlements.templates.flowexport, Entitlements.templates.toolexport], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + marketplacesController.saveCustomTemplate +) // READ -router.get('/custom', checkPermission('templates:custom'), marketplacesController.getAllCustomTemplates) +router.get( + '/custom', + [Entitlements.templates.custom], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + marketplacesController.getAllCustomTemplates +) // DELETE -router.delete(['/', '/custom/:id'], checkPermission('templates:custom-delete'), marketplacesController.deleteCustomTemplate) +router.delete( + ['/', '/custom/:id'], + [Entitlements.templates.customDelete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + marketplacesController.deleteCustomTemplate +) export default router diff --git a/packages/server/src/routes/node-configs/index.ts b/packages/server/src/routes/node-configs/index.ts index 4c44513888e..2b37751dbcd 100644 --- a/packages/server/src/routes/node-configs/index.ts +++ b/packages/server/src/routes/node-configs/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import nodeConfigsController from '../../controllers/node-configs' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE -router.post('/', nodeConfigsController.getAllNodeConfigs) +router.post('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nodeConfigsController.getAllNodeConfigs) export default router diff --git a/packages/server/src/routes/node-custom-functions/index.ts b/packages/server/src/routes/node-custom-functions/index.ts index 9fa33d42e2a..13151eef878 100644 --- a/packages/server/src/routes/node-custom-functions/index.ts +++ b/packages/server/src/routes/node-custom-functions/index.ts @@ -1,11 +1,14 @@ -import express from 'express' import nodesRouter from '../../controllers/nodes' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE // READ -router.post('/', nodesRouter.executeCustomFunction) +router.post('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nodesRouter.executeCustomFunction) // UPDATE diff --git a/packages/server/src/routes/node-icons/index.ts b/packages/server/src/routes/node-icons/index.ts index 3dc51b96cbf..d8816a4f3f5 100644 --- a/packages/server/src/routes/node-icons/index.ts +++ b/packages/server/src/routes/node-icons/index.ts @@ -1,11 +1,14 @@ -import express from 'express' import nodesController from '../../controllers/nodes' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE // READ -router.get(['/', '/:name'], nodesController.getSingleNodeIcon) +router.get(['/', '/:name'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nodesController.getSingleNodeIcon) // UPDATE diff --git a/packages/server/src/routes/node-load-methods/index.ts b/packages/server/src/routes/node-load-methods/index.ts index 317fd81cfa1..06bb3630cb5 100644 --- a/packages/server/src/routes/node-load-methods/index.ts +++ b/packages/server/src/routes/node-load-methods/index.ts @@ -1,7 +1,10 @@ -import express from 'express' import nodesRouter from '../../controllers/nodes' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -router.post(['/', '/:name'], nodesRouter.getSingleNodeAsyncOptions) +const router = entitled.Router() + +router.post(['/', '/:name'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nodesRouter.getSingleNodeAsyncOptions) export default router diff --git a/packages/server/src/routes/nodes/index.ts b/packages/server/src/routes/nodes/index.ts index 1c9e59c3352..7038da5d968 100644 --- a/packages/server/src/routes/nodes/index.ts +++ b/packages/server/src/routes/nodes/index.ts @@ -1,10 +1,13 @@ -import express from 'express' import nodesController from '../../controllers/nodes' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/', nodesController.getAllNodes) -router.get(['/', '/:name'], nodesController.getNodeByName) -router.get('/category/:name', nodesController.getNodesByCategory) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nodesController.getAllNodes) +router.get(['/', '/:name'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nodesController.getNodeByName) +router.get('/category/:name', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nodesController.getNodesByCategory) export default router diff --git a/packages/server/src/routes/nvidia-nim/index.ts b/packages/server/src/routes/nvidia-nim/index.ts index 473b57156e9..5ee947b1777 100644 --- a/packages/server/src/routes/nvidia-nim/index.ts +++ b/packages/server/src/routes/nvidia-nim/index.ts @@ -1,16 +1,19 @@ -import express from 'express' import nimController from '../../controllers/nvidia-nim' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/preload', nimController.preload) -router.get('/get-token', nimController.getToken) -router.get('/download-installer', nimController.downloadInstaller) -router.get('/list-running-containers', nimController.listRunningContainers) -router.post('/pull-image', nimController.pullImage) -router.post('/start-container', nimController.startContainer) -router.post('/stop-container', nimController.stopContainer) -router.post('/get-image', nimController.getImage) -router.post('/get-container', nimController.getContainer) +router.get('/preload', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nimController.preload) +router.get('/get-token', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nimController.getToken) +router.get('/download-installer', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nimController.downloadInstaller) +router.get('/list-running-containers', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nimController.listRunningContainers) +router.post('/pull-image', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nimController.pullImage) +router.post('/start-container', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nimController.startContainer) +router.post('/stop-container', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nimController.stopContainer) +router.post('/get-image', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nimController.getImage) +router.post('/get-container', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], nimController.getContainer) export default router diff --git a/packages/server/src/routes/oauth2/index.ts b/packages/server/src/routes/oauth2/index.ts index b5c5f571b76..89fde589edb 100644 --- a/packages/server/src/routes/oauth2/index.ts +++ b/packages/server/src/routes/oauth2/index.ts @@ -56,7 +56,6 @@ * - token_received_at: When token was received (ISO string) */ -import express from 'express' import axios from 'axios' import { Request, Response, NextFunction } from 'express' import { getRunningExpressApp } from '../../utils/getRunningExpressApp' @@ -65,96 +64,104 @@ import { decryptCredentialData, encryptCredentialData } from '../../utils' import { InternalFlowiseError } from '../../errors/internalFlowiseError' import { StatusCodes } from 'http-status-codes' import { generateSuccessPage, generateErrorPage } from './templates' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // Initiate OAuth2 authorization flow -router.post('/authorize/:credentialId', async (req: Request, res: Response, next: NextFunction) => { - try { - const { credentialId } = req.params - - const appServer = getRunningExpressApp() - const credentialRepository = appServer.AppDataSource.getRepository(Credential) - - // Find credential by ID - const credential = await credentialRepository.findOneBy({ - id: credentialId - }) - - if (!credential) { - return res.status(404).json({ - success: false, - message: 'Credential not found' +router.post( + '/authorize/:credentialId', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + async (req: Request, res: Response, next: NextFunction) => { + try { + const { credentialId } = req.params + + const appServer = getRunningExpressApp() + const credentialRepository = appServer.AppDataSource.getRepository(Credential) + + // Find credential by ID + const credential = await credentialRepository.findOneBy({ + id: credentialId }) - } - // Decrypt the credential data to get OAuth configuration - const decryptedData = await decryptCredentialData(credential.encryptedData) + if (!credential) { + return res.status(404).json({ + success: false, + message: 'Credential not found' + }) + } - const { - clientId, - authorizationUrl, - redirect_uri, - scope, - response_type = 'code', - response_mode = 'query', - additionalParameters = '' - } = decryptedData - - if (!clientId) { - return res.status(400).json({ - success: false, - message: 'Missing clientId in credential data' - }) - } + // Decrypt the credential data to get OAuth configuration + const decryptedData = await decryptCredentialData(credential.encryptedData) + + const { + clientId, + authorizationUrl, + redirect_uri, + scope, + response_type = 'code', + response_mode = 'query', + additionalParameters = '' + } = decryptedData + + if (!clientId) { + return res.status(400).json({ + success: false, + message: 'Missing clientId in credential data' + }) + } - if (!authorizationUrl) { - return res.status(400).json({ - success: false, - message: 'No authorizationUrl specified in credential data' - }) - } + if (!authorizationUrl) { + return res.status(400).json({ + success: false, + message: 'No authorizationUrl specified in credential data' + }) + } - const defaultRedirectUri = `${req.protocol}://${req.get('host')}/api/v1/oauth2-credential/callback` - const finalRedirectUri = redirect_uri || defaultRedirectUri + const defaultRedirectUri = `${req.protocol}://${req.get('host')}/api/v1/oauth2-credential/callback` + const finalRedirectUri = redirect_uri || defaultRedirectUri - const authParams = new URLSearchParams({ - client_id: clientId, - response_type, - response_mode, - state: credentialId, // Use credential ID as state parameter - redirect_uri: finalRedirectUri - }) + const authParams = new URLSearchParams({ + client_id: clientId, + response_type, + response_mode, + state: credentialId, // Use credential ID as state parameter + redirect_uri: finalRedirectUri + }) - if (scope) { - authParams.append('scope', scope) - } + if (scope) { + authParams.append('scope', scope) + } - let fullAuthorizationUrl = `${authorizationUrl}?${authParams.toString()}` + let fullAuthorizationUrl = `${authorizationUrl}?${authParams.toString()}` - if (additionalParameters) { - fullAuthorizationUrl += `&${additionalParameters.toString()}` - } + if (additionalParameters) { + fullAuthorizationUrl += `&${additionalParameters.toString()}` + } - res.json({ - success: true, - message: 'Authorization URL generated successfully', - credentialId, - authorizationUrl: fullAuthorizationUrl, - redirectUri: finalRedirectUri - }) - } catch (error) { - next( - new InternalFlowiseError( - StatusCodes.INTERNAL_SERVER_ERROR, - `OAuth2 authorization error: ${error instanceof Error ? error.message : 'Unknown error'}` + res.json({ + success: true, + message: 'Authorization URL generated successfully', + credentialId, + authorizationUrl: fullAuthorizationUrl, + redirectUri: finalRedirectUri + }) + } catch (error) { + next( + new InternalFlowiseError( + StatusCodes.INTERNAL_SERVER_ERROR, + `OAuth2 authorization error: ${error instanceof Error ? error.message : 'Unknown error'}` + ) ) - ) + } } -}) +) // OAuth2 callback endpoint -router.get('/callback', async (req: Request, res: Response) => { +router.get('/callback', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], async (req: Request, res: Response) => { try { const { code, state, error, error_description } = req.query @@ -304,119 +311,124 @@ router.get('/callback', async (req: Request, res: Response) => { }) // Refresh OAuth2 access token -router.post('/refresh/:credentialId', async (req: Request, res: Response, next: NextFunction) => { - try { - const { credentialId } = req.params - - const appServer = getRunningExpressApp() - const credentialRepository = appServer.AppDataSource.getRepository(Credential) - - const credential = await credentialRepository.findOneBy({ - id: credentialId - }) - - if (!credential) { - return res.status(404).json({ - success: false, - message: 'Credential not found' +router.post( + '/refresh/:credentialId', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + async (req: Request, res: Response, next: NextFunction) => { + try { + const { credentialId } = req.params + + const appServer = getRunningExpressApp() + const credentialRepository = appServer.AppDataSource.getRepository(Credential) + + const credential = await credentialRepository.findOneBy({ + id: credentialId }) - } - const decryptedData = await decryptCredentialData(credential.encryptedData) + if (!credential) { + return res.status(404).json({ + success: false, + message: 'Credential not found' + }) + } - const { clientId, clientSecret, refresh_token, accessTokenUrl, scope } = decryptedData + const decryptedData = await decryptCredentialData(credential.encryptedData) - if (!clientId || !clientSecret || !refresh_token) { - return res.status(400).json({ - success: false, - message: 'Missing required OAuth configuration: clientId, clientSecret, or refresh_token' - }) - } + const { clientId, clientSecret, refresh_token, accessTokenUrl, scope } = decryptedData - let tokenUrl = accessTokenUrl - if (!tokenUrl) { - return res.status(400).json({ - success: false, - message: 'No Access Token URL specified in credential data' - }) - } + if (!clientId || !clientSecret || !refresh_token) { + return res.status(400).json({ + success: false, + message: 'Missing required OAuth configuration: clientId, clientSecret, or refresh_token' + }) + } - const refreshRequestData: any = { - client_id: clientId, - client_secret: clientSecret, - grant_type: 'refresh_token', - refresh_token - } + let tokenUrl = accessTokenUrl + if (!tokenUrl) { + return res.status(400).json({ + success: false, + message: 'No Access Token URL specified in credential data' + }) + } - if (scope) { - refreshRequestData.scope = scope - } + const refreshRequestData: any = { + client_id: clientId, + client_secret: clientSecret, + grant_type: 'refresh_token', + refresh_token + } - const tokenResponse = await axios.post(tokenUrl, new URLSearchParams(refreshRequestData).toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Accept: 'application/json' + if (scope) { + refreshRequestData.scope = scope } - }) - // Extract token data from response - const tokenData = tokenResponse.data + const tokenResponse = await axios.post(tokenUrl, new URLSearchParams(refreshRequestData).toString(), { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json' + } + }) - // Update the credential data with new token information - const updatedCredentialData: any = { - ...decryptedData, - ...tokenData, - token_received_at: new Date().toISOString() - } + // Extract token data from response + const tokenData = tokenResponse.data - // Update refresh token if a new one was provided - if (tokenData.refresh_token) { - updatedCredentialData.refresh_token = tokenData.refresh_token - } + // Update the credential data with new token information + const updatedCredentialData: any = { + ...decryptedData, + ...tokenData, + token_received_at: new Date().toISOString() + } - // Calculate token expiry time - if (tokenData.expires_in) { - const expiryTime = new Date(Date.now() + tokenData.expires_in * 1000) - updatedCredentialData.expires_at = expiryTime.toISOString() - } + // Update refresh token if a new one was provided + if (tokenData.refresh_token) { + updatedCredentialData.refresh_token = tokenData.refresh_token + } - // Encrypt the updated credential data - const encryptedData = await encryptCredentialData(updatedCredentialData) + // Calculate token expiry time + if (tokenData.expires_in) { + const expiryTime = new Date(Date.now() + tokenData.expires_in * 1000) + updatedCredentialData.expires_at = expiryTime.toISOString() + } - // Update the credential in the database - await credentialRepository.update(credential.id, { - encryptedData, - updatedDate: new Date() - }) + // Encrypt the updated credential data + const encryptedData = await encryptCredentialData(updatedCredentialData) - // Return success response - res.json({ - success: true, - message: 'OAuth2 token refreshed successfully', - credentialId: credential.id, - tokenInfo: { - ...tokenData, - has_new_refresh_token: !!tokenData.refresh_token, - expires_at: updatedCredentialData.expires_at - } - }) - } catch (error) { - if (axios.isAxiosError(error)) { - const axiosError = error - return res.status(400).json({ - success: false, - message: `Token refresh failed: ${axiosError.response?.data?.error_description || axiosError.message}`, - details: axiosError.response?.data + // Update the credential in the database + await credentialRepository.update(credential.id, { + encryptedData, + updatedDate: new Date() }) - } - next( - new InternalFlowiseError( - StatusCodes.INTERNAL_SERVER_ERROR, - `OAuth2 token refresh error: ${error instanceof Error ? error.message : 'Unknown error'}` + // Return success response + res.json({ + success: true, + message: 'OAuth2 token refreshed successfully', + credentialId: credential.id, + tokenInfo: { + ...tokenData, + has_new_refresh_token: !!tokenData.refresh_token, + expires_at: updatedCredentialData.expires_at + } + }) + } catch (error) { + if (axios.isAxiosError(error)) { + const axiosError = error + return res.status(400).json({ + success: false, + message: `Token refresh failed: ${axiosError.response?.data?.error_description || axiosError.message}`, + details: axiosError.response?.data + }) + } + + next( + new InternalFlowiseError( + StatusCodes.INTERNAL_SERVER_ERROR, + `OAuth2 token refresh error: ${error instanceof Error ? error.message : 'Unknown error'}` + ) ) - ) + } } -}) +) export default router diff --git a/packages/server/src/routes/openai-assistants-files/index.ts b/packages/server/src/routes/openai-assistants-files/index.ts index 302f8c2f82e..70dfa8ddd86 100644 --- a/packages/server/src/routes/openai-assistants-files/index.ts +++ b/packages/server/src/routes/openai-assistants-files/index.ts @@ -1,10 +1,18 @@ -import express from 'express' import openaiAssistantsController from '../../controllers/openai-assistants' import { getMulterStorage } from '../../utils' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() -router.post('/download/', openaiAssistantsController.getFileFromAssistant) -router.post('/upload/', getMulterStorage().array('files'), openaiAssistantsController.uploadAssistantFiles) +router.post('/download/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], openaiAssistantsController.getFileFromAssistant) +router.post( + '/upload/', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + getMulterStorage().array('files'), + openaiAssistantsController.uploadAssistantFiles +) export default router diff --git a/packages/server/src/routes/openai-assistants-vector-store/index.ts b/packages/server/src/routes/openai-assistants-vector-store/index.ts index 0706c9eb4e6..d4a290ae2a4 100644 --- a/packages/server/src/routes/openai-assistants-vector-store/index.ts +++ b/packages/server/src/routes/openai-assistants-vector-store/index.ts @@ -1,28 +1,61 @@ -import express from 'express' import openaiAssistantsVectorStoreController from '../../controllers/openai-assistants-vector-store' import { getMulterStorage } from '../../utils' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // CREATE -router.post('/', openaiAssistantsVectorStoreController.createAssistantVectorStore) +router.post( + '/', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + openaiAssistantsVectorStoreController.createAssistantVectorStore +) // READ -router.get('/:id', openaiAssistantsVectorStoreController.getAssistantVectorStore) +router.get( + '/:id', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + openaiAssistantsVectorStoreController.getAssistantVectorStore +) // LIST -router.get('/', openaiAssistantsVectorStoreController.listAssistantVectorStore) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], openaiAssistantsVectorStoreController.listAssistantVectorStore) // UPDATE -router.put(['/', '/:id'], openaiAssistantsVectorStoreController.updateAssistantVectorStore) +router.put( + ['/', '/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + openaiAssistantsVectorStoreController.updateAssistantVectorStore +) // DELETE -router.delete(['/', '/:id'], openaiAssistantsVectorStoreController.deleteAssistantVectorStore) +router.delete( + ['/', '/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + openaiAssistantsVectorStoreController.deleteAssistantVectorStore +) // POST -router.post('/:id', getMulterStorage().array('files'), openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore) +router.post( + '/:id', + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + getMulterStorage().array('files'), + openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore +) // DELETE -router.patch(['/', '/:id'], openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore) +router.patch( + ['/', '/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore +) export default router diff --git a/packages/server/src/routes/openai-assistants/index.ts b/packages/server/src/routes/openai-assistants/index.ts index 1c82a921480..c0a7cf5fe13 100644 --- a/packages/server/src/routes/openai-assistants/index.ts +++ b/packages/server/src/routes/openai-assistants/index.ts @@ -1,15 +1,12 @@ -import express from 'express' import openaiAssistantsController from '../../controllers/openai-assistants' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -// CREATE +const router = entitled.Router() // READ -router.get('/', openaiAssistantsController.getAllOpenaiAssistants) -router.get(['/', '/:id'], openaiAssistantsController.getSingleOpenaiAssistant) - -// UPDATE - -// DELETE +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], openaiAssistantsController.getAllOpenaiAssistants) +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], openaiAssistantsController.getSingleOpenaiAssistant) export default router diff --git a/packages/server/src/routes/openai-realtime/index.ts b/packages/server/src/routes/openai-realtime/index.ts index 68ed0427da4..457d7a3a0cf 100644 --- a/packages/server/src/routes/openai-realtime/index.ts +++ b/packages/server/src/routes/openai-realtime/index.ts @@ -1,12 +1,14 @@ -import express from 'express' import openaiRealTimeController from '../../controllers/openai-realtime' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // GET -router.get(['/', '/:id'], openaiRealTimeController.getAgentTools) +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], openaiRealTimeController.getAgentTools) // EXECUTE -router.post(['/', '/:id'], openaiRealTimeController.executeAgentTool) +router.post(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], openaiRealTimeController.executeAgentTool) export default router diff --git a/packages/server/src/routes/ping/index.ts b/packages/server/src/routes/ping/index.ts index b026710e86c..96002135fbc 100644 --- a/packages/server/src/routes/ping/index.ts +++ b/packages/server/src/routes/ping/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import pingController from '../../controllers/ping' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // GET -router.get('/', pingController.getPing) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], pingController.getPing) export default router diff --git a/packages/server/src/routes/predictions/index.ts b/packages/server/src/routes/predictions/index.ts index 40f37eef17c..68c3117a03c 100644 --- a/packages/server/src/routes/predictions/index.ts +++ b/packages/server/src/routes/predictions/index.ts @@ -1,12 +1,16 @@ -import express from 'express' import predictionsController from '../../controllers/predictions' import { getMulterStorage } from '../../utils' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // CREATE router.post( ['/', '/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], getMulterStorage().array('files'), predictionsController.getRateLimiterMiddleware, predictionsController.createPrediction diff --git a/packages/server/src/routes/pricing/index.ts b/packages/server/src/routes/pricing/index.ts index ce82a6fdad1..49774c2a5c8 100644 --- a/packages/server/src/routes/pricing/index.ts +++ b/packages/server/src/routes/pricing/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import pricingController from '../../controllers/pricing' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // GET -router.get('/', pricingController.getPricing) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], pricingController.getPricing) export default router diff --git a/packages/server/src/routes/prompts-lists/index.ts b/packages/server/src/routes/prompts-lists/index.ts index 9b92c365cbb..574a5533799 100644 --- a/packages/server/src/routes/prompts-lists/index.ts +++ b/packages/server/src/routes/prompts-lists/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import promptsListController from '../../controllers/prompts-lists' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE -router.post('/', promptsListController.createPromptsList) +router.post('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], promptsListController.createPromptsList) export default router diff --git a/packages/server/src/routes/public-chatbots/index.ts b/packages/server/src/routes/public-chatbots/index.ts index 18ee9e4c4fd..59d54be3758 100644 --- a/packages/server/src/routes/public-chatbots/index.ts +++ b/packages/server/src/routes/public-chatbots/index.ts @@ -1,14 +1,11 @@ -import express from 'express' import chatflowsController from '../../controllers/chatflows' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -// CREATE +const router = entitled.Router() // READ -router.get(['/', '/:id'], chatflowsController.getSinglePublicChatbotConfig) - -// UPDATE - -// DELETE +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], chatflowsController.getSinglePublicChatbotConfig) export default router diff --git a/packages/server/src/routes/public-chatflows/index.ts b/packages/server/src/routes/public-chatflows/index.ts index 640fe3a6da1..7cf5c4ceeb7 100644 --- a/packages/server/src/routes/public-chatflows/index.ts +++ b/packages/server/src/routes/public-chatflows/index.ts @@ -1,14 +1,11 @@ -import express from 'express' import chatflowsController from '../../controllers/chatflows' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -// CREATE +const router = entitled.Router() // READ -router.get(['/', '/:id'], chatflowsController.getSinglePublicChatflow) - -// UPDATE - -// DELETE +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], chatflowsController.getSinglePublicChatflow) export default router diff --git a/packages/server/src/routes/public-executions/index.ts b/packages/server/src/routes/public-executions/index.ts index eaabe5a1cae..afa3711bab7 100644 --- a/packages/server/src/routes/public-executions/index.ts +++ b/packages/server/src/routes/public-executions/index.ts @@ -1,14 +1,11 @@ -import express from 'express' import executionController from '../../controllers/executions' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -// CREATE +const router = entitled.Router() // READ -router.get(['/', '/:id'], executionController.getPublicExecutionById) - -// UPDATE - -// DELETE +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], executionController.getPublicExecutionById) export default router diff --git a/packages/server/src/routes/settings/index.ts b/packages/server/src/routes/settings/index.ts index e311c76b4c9..d91b63aa4c0 100644 --- a/packages/server/src/routes/settings/index.ts +++ b/packages/server/src/routes/settings/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import settingsController from '../../controllers/settings' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // CREATE -router.get('/', settingsController.getSettingsList) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], settingsController.getSettingsList) export default router diff --git a/packages/server/src/routes/stats/index.ts b/packages/server/src/routes/stats/index.ts index 8ca64d3dab7..1b0335ebd13 100644 --- a/packages/server/src/routes/stats/index.ts +++ b/packages/server/src/routes/stats/index.ts @@ -1,9 +1,11 @@ -import express from 'express' import statsController from '../../controllers/stats' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // READ -router.get(['/', '/:id'], statsController.getChatflowStats) +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], statsController.getChatflowStats) export default router diff --git a/packages/server/src/routes/tools/index.ts b/packages/server/src/routes/tools/index.ts index 81ff9029242..a730d1b5867 100644 --- a/packages/server/src/routes/tools/index.ts +++ b/packages/server/src/routes/tools/index.ts @@ -1,20 +1,21 @@ -import express from 'express' import toolsController from '../../controllers/tools' -import { checkAnyPermission, checkPermission } from '../../enterprise/rbac/PermissionCheck' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // CREATE -router.post('/', checkPermission('tools:create'), toolsController.createTool) +router.post('/', [Entitlements.tools.create], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], toolsController.createTool) // READ -router.get('/', checkPermission('tools:view'), toolsController.getAllTools) -router.get(['/', '/:id'], checkAnyPermission('tools:view'), toolsController.getToolById) +router.get('/', [Entitlements.tools.view], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], toolsController.getAllTools) +router.get('/:id', [Entitlements.tools.view], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], toolsController.getToolById) // UPDATE -router.put(['/', '/:id'], checkAnyPermission('tools:update,tools:create'), toolsController.updateTool) +router.put('/:id', [Entitlements.tools.update], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], toolsController.updateTool) // DELETE -router.delete(['/', '/:id'], checkPermission('tools:delete'), toolsController.deleteTool) +router.delete('/:id', [Entitlements.tools.delete], [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], toolsController.deleteTool) export default router diff --git a/packages/server/src/routes/upsert-history/index.ts b/packages/server/src/routes/upsert-history/index.ts index 3e3c9c1a853..a8c3d276d40 100644 --- a/packages/server/src/routes/upsert-history/index.ts +++ b/packages/server/src/routes/upsert-history/index.ts @@ -1,15 +1,14 @@ -import express from 'express' import upsertHistoryController from '../../controllers/upsert-history' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -// CREATE +const router = entitled.Router() // READ -router.get(['/', '/:id'], upsertHistoryController.getAllUpsertHistory) +router.get(['/', '/:id'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], upsertHistoryController.getAllUpsertHistory) // PATCH -router.patch('/', upsertHistoryController.patchDeleteUpsertHistory) - -// DELETE +router.patch('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], upsertHistoryController.patchDeleteUpsertHistory) export default router diff --git a/packages/server/src/routes/validation/index.ts b/packages/server/src/routes/validation/index.ts index 14cefc8155b..c5552a27239 100644 --- a/packages/server/src/routes/validation/index.ts +++ b/packages/server/src/routes/validation/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import validationController from '../../controllers/validation' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/:id', validationController.checkFlowValidation) +router.get('/:id', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], validationController.checkFlowValidation) export default router diff --git a/packages/server/src/routes/variables/index.ts b/packages/server/src/routes/variables/index.ts index 20ab6e1356b..42fe9032e45 100644 --- a/packages/server/src/routes/variables/index.ts +++ b/packages/server/src/routes/variables/index.ts @@ -1,19 +1,40 @@ -import express from 'express' import variablesController from '../../controllers/variables' -import { checkAnyPermission, checkPermission } from '../../enterprise/rbac/PermissionCheck' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // CREATE -router.post('/', checkPermission('variables:create'), variablesController.createVariable) +router.post( + '/', + [Entitlements.variables.create], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + variablesController.createVariable +) // READ -router.get('/', checkPermission('variables:view'), variablesController.getAllVariables) +router.get( + '/', + [Entitlements.variables.view], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + variablesController.getAllVariables +) // UPDATE -router.put(['/', '/:id'], checkAnyPermission('variables:create,variables:update'), variablesController.updateVariable) +router.put( + '/:id', + [Entitlements.variables.update], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + variablesController.updateVariable +) // DELETE -router.delete(['/', '/:id'], checkPermission('variables:delete'), variablesController.deleteVariable) +router.delete( + '/:id', + [Entitlements.variables.delete], + [AuthenticationStrategy.JWT, AuthenticationStrategy.API_KEY], + variablesController.deleteVariable +) export default router diff --git a/packages/server/src/routes/vectors/index.ts b/packages/server/src/routes/vectors/index.ts index f03e0dc7f7e..d8ba2f3082e 100644 --- a/packages/server/src/routes/vectors/index.ts +++ b/packages/server/src/routes/vectors/index.ts @@ -1,16 +1,26 @@ -import express from 'express' import vectorsController from '../../controllers/vectors' import { getMulterStorage } from '../../utils' +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' -const router = express.Router() +const router = entitled.Router() // CREATE router.post( ['/upsert/', '/upsert/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], getMulterStorage().array('files'), vectorsController.getRateLimiterMiddleware, vectorsController.upsertVectorMiddleware ) -router.post(['/internal-upsert/', '/internal-upsert/:id'], getMulterStorage().array('files'), vectorsController.createInternalUpsert) +router.post( + ['/internal-upsert/', '/internal-upsert/:id'], + [Entitlements.unspecified], + [AuthenticationStrategy.PUBLIC], + getMulterStorage().array('files'), + vectorsController.createInternalUpsert +) export default router diff --git a/packages/server/src/routes/verify/index.ts b/packages/server/src/routes/verify/index.ts index b00a59aa3aa..64d056a3b8f 100644 --- a/packages/server/src/routes/verify/index.ts +++ b/packages/server/src/routes/verify/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import apikeyController from '../../controllers/apikey' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get(['/apikey/', '/apikey/:apikey'], apikeyController.verifyApiKey) +router.get(['/apikey/', '/apikey/:apikey'], [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], apikeyController.verifyApiKey) export default router diff --git a/packages/server/src/routes/versions/index.ts b/packages/server/src/routes/versions/index.ts index 8aa60a29030..f63aebbc569 100644 --- a/packages/server/src/routes/versions/index.ts +++ b/packages/server/src/routes/versions/index.ts @@ -1,8 +1,11 @@ -import express from 'express' import versionsController from '../../controllers/versions' -const router = express.Router() +import { entitled } from '../../services/entitled-router' +import { Entitlements } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' + +const router = entitled.Router() // READ -router.get('/', versionsController.getVersion) +router.get('/', [Entitlements.unspecified], [AuthenticationStrategy.PUBLIC], versionsController.getVersion) export default router diff --git a/packages/server/src/services/entitled-router/index.ts b/packages/server/src/services/entitled-router/index.ts new file mode 100644 index 00000000000..e20fee872db --- /dev/null +++ b/packages/server/src/services/entitled-router/index.ts @@ -0,0 +1,162 @@ +import { IRouter, NextFunction, Request, RequestHandler, Response, Router as ExpressRouter } from 'express' +import { PathParams } from 'express-serve-static-core' +import { Entitlement } from '../../enterprise/rbac/Entitlements' +import { AuthenticationStrategy } from '../../enterprise/auth/AuthenticationStrategy' +import { authorize } from '../../enterprise/middleware/policy' +import { Logger } from 'winston' + +export interface RegisteredRoute { + method: string + path: PathParams + entitlements: Entitlement[] + authenticationStrategies: AuthenticationStrategy[] +} + +/** + * Joins two PathParams into a single, normalized string path. + * This handles the complexity of PathParams being a string, RegExp, or an array of either. + * For logging and display, it converts all parts to a string representation. + * @param base The base path (e.g., from `router.use()`). + * @param sub The sub-path (e.g., from `router.get()`). + * @returns A normalized string path. + */ +function joinPathParams(base: PathParams, sub: PathParams): string[] { + const pathParamToString = (p: string | RegExp): string => (p instanceof RegExp ? `(regex: ${p.source})` : p) + const baseParts = Array.isArray(base) ? base.map(pathParamToString) : [pathParamToString(base)] + const subParts = Array.isArray(sub) ? sub.map(pathParamToString) : [pathParamToString(sub)] + + const combinedPaths: string[] = [] + + for (const basePath of baseParts) { + for (const subPath of subParts) { + // Don't append the sub-path if it's just the root ('/'). + if (subPath === '/') { + combinedPaths.push(basePath || '/') + continue + } + + // Join and normalize slashes. + const combined = `${basePath}/${subPath}` + combinedPaths.push(combined.replace(/\/+/g, '/').replace(/\/$/, '') || '/') + } + } + + return combinedPaths +} + +export class EntitledRouter { + readonly router: IRouter + private childRouters: { path: PathParams; router: EntitledRouter }[] = [] + protected registeredRoutes: RegisteredRoute[] = [] + + constructor() { + this.router = ExpressRouter() + } + + public getRegisteredRoutes(): RegisteredRoute[] { + const childRoutes = this.childRouters.flatMap(({ path: basePath, router: childRouter }) => { + // Get the child's local routes and prepend the parent's base path. + const localChildRoutes = childRouter.registeredRoutes + // Use flatMap to create a new route object for each path combination. + return localChildRoutes.flatMap((route) => { + const combinedPaths = joinPathParams(basePath, route.path) + return combinedPaths.map((p) => ({ ...route, path: p })) + }) + }) + return [...this.registeredRoutes, ...childRoutes] + } + /** + * Creates a policy enforcement middleware for a specific route. + * This returned middleware will be the first to execute for the route, + * ensuring the user is authorized before any other handlers are called. + * @param policy The route's registration information, including entitlements and auth strategies. + * @returns A request handler that enforces the policy. + */ + protected createPolicyMiddleware(policy: RegisteredRoute): RequestHandler { + return (req: Request, res: Response, next: NextFunction) => { + // The authorize function contains the logic to check the user against the policy. + return authorize(req.user, policy, res, next) + } + } + + public get( + path: PathParams, + entitlements: Entitlement[], + authenticationStrategies: AuthenticationStrategy[], + ...handlers: RequestHandler[] + ): void { + const routeInfo: RegisteredRoute = { method: 'get', path, entitlements, authenticationStrategies } + this.registeredRoutes.push(routeInfo) + this.router.get(path, this.createPolicyMiddleware(routeInfo), ...handlers) + } + + public post( + path: PathParams, + entitlements: Entitlement[], + authenticationStrategies: AuthenticationStrategy[], + ...handlers: RequestHandler[] + ): void { + const routeInfo: RegisteredRoute = { method: 'post', path, entitlements, authenticationStrategies } + this.registeredRoutes.push(routeInfo) + this.router.post(path, this.createPolicyMiddleware(routeInfo), ...handlers) + } + + public put( + path: PathParams, + entitlements: Entitlement[], + authenticationStrategies: AuthenticationStrategy[], + ...handlers: RequestHandler[] + ): void { + const routeInfo: RegisteredRoute = { method: 'put', path, entitlements, authenticationStrategies } + this.registeredRoutes.push(routeInfo) + this.router.put(path, this.createPolicyMiddleware(routeInfo), ...handlers) + } + + public delete( + path: PathParams, + entitlements: Entitlement[], + authenticationStrategies: AuthenticationStrategy[], + ...handlers: RequestHandler[] + ): void { + const routeInfo: RegisteredRoute = { method: 'delete', path, entitlements, authenticationStrategies } + this.registeredRoutes.push(routeInfo) + this.router.delete(path, this.createPolicyMiddleware(routeInfo), ...handlers) + } + + public patch( + path: PathParams, + entitlements: Entitlement[], + authenticationStrategies: AuthenticationStrategy[], + ...handlers: RequestHandler[] + ): void { + const routeInfo: RegisteredRoute = { method: 'patch', path, entitlements, authenticationStrategies } + this.registeredRoutes.push(routeInfo) + this.router.patch(path, this.createPolicyMiddleware(routeInfo), ...handlers) + } + + public getRouter(): IRouter { + return this.router + } + + public use(...args: any[]): this { + // The path is the first string argument. + const path = args.find((arg) => typeof arg === 'string') || '/' + + // Find all EntitledRouter instances in the arguments. + const childEntitledRouters = args.filter((arg) => arg instanceof EntitledRouter) + for (const childRouter of childEntitledRouters) { + this.childRouters.push({ path, router: childRouter }) + } + + // Replace EntitledRouter instances with their underlying Express router for the actual .use() call. + const expressArgs = args.map((arg) => (arg instanceof EntitledRouter ? arg.getRouter() : arg)) + this.router.use(...expressArgs) + return this + } +} + +function Router() { + return new EntitledRouter() +} + +export const entitled = { Router }