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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/server/src/enterprise/auth/AuthenticationStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum AuthenticationStrategy {
JWT = 'jwt',
API_KEY = 'apiKey',
PUBLIC = 'public',
SESSION = 'session'
}
41 changes: 41 additions & 0 deletions packages/server/src/enterprise/middleware/policy/index.ts
Original file line number Diff line number Diff line change
@@ -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 })
}
201 changes: 201 additions & 0 deletions packages/server/src/enterprise/rbac/Entitlements.ts
Original file line number Diff line number Diff line change
@@ -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 = <T extends EntitlementStructure>(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 = <T extends EntitlementStructure>(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> = T extends string
? T
: T extends Record<string, any>
? { [K in keyof T]: ExtractLeafStrings<T[K]> }[keyof T]
: never

// Type union of all entitlement strings
export type Entitlement = ExtractLeafStrings<typeof Entitlements>
Loading