From 7df8608207bed0f1e1a30d606bcdc70389881098 Mon Sep 17 00:00:00 2001 From: Andrea Zucca Date: Wed, 10 Jun 2026 15:44:30 +0200 Subject: [PATCH] feat: enhance context data generation with system user handling --- .../__tests__/generateContextData.test.ts | 47 ++++++++++++++++++- .../flowerbase/src/utils/context/helpers.ts | 17 ++++++- .../flowerbase/src/utils/context/index.ts | 8 ++-- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/packages/flowerbase/src/utils/__tests__/generateContextData.test.ts b/packages/flowerbase/src/utils/__tests__/generateContextData.test.ts index 2c3afc6..a3cc479 100644 --- a/packages/flowerbase/src/utils/__tests__/generateContextData.test.ts +++ b/packages/flowerbase/src/utils/__tests__/generateContextData.test.ts @@ -4,7 +4,7 @@ import { User } from '../../auth/dtos' import { Functions } from '../../features/functions/interface' import { Rules } from '../../features/rules/interface' import { services } from '../../services' -import { generateContextData } from '../context/helpers' +import { contextUserForRun, generateContextData, REALM_SYSTEM_USER } from '../context/helpers' const originalEnv = process.env @@ -113,4 +113,49 @@ describe('generateContextData', () => { const binaryObject = Binary.fromBase64Binary(base64, 0) expect(binaryObject).toBeInstanceOf(BSON.Binary) }) + + it('exposes Realm system user and runningAsSystem when run_as_system is enabled', () => { + const mockApp = Fastify() + const systemFunction = { ...currentFunction, run_as_system: true } + const { context } = generateContextData({ + services, + app: mockApp, + functionsList: mockFunctions, + currentFunction: systemFunction, + GenerateContext: GenerateContextMock, + GenerateContextSync: GenerateContextSyncMock, + user: REALM_SYSTEM_USER, + rules: mockRules + }) + + expect(context.user).toEqual(REALM_SYSTEM_USER) + expect(context.runningAsSystem()).toBe(true) + }) + + it('runningAsSystem is false for regular function execution', () => { + const mockApp = Fastify() + const { context } = generateContextData({ + services, + app: mockApp, + functionsList: mockFunctions, + currentFunction, + GenerateContext: GenerateContextMock, + GenerateContextSync: GenerateContextSyncMock, + user: { id: 'user-1', type: 'normal', data: {}, custom_data: {}, identities: [] }, + rules: mockRules + }) + + expect(context.runningAsSystem()).toBe(false) + }) +}) + +describe('contextUserForRun', () => { + it('returns Realm system user for empty user with run_as_system', () => { + expect(contextUserForRun({}, true)).toEqual(REALM_SYSTEM_USER) + }) + + it('returns the user unchanged when not running as system', () => { + const user = { id: 'user-1' } + expect(contextUserForRun(user, false)).toBe(user) + }) }) diff --git a/packages/flowerbase/src/utils/context/helpers.ts b/packages/flowerbase/src/utils/context/helpers.ts index 3c24a06..231f323 100644 --- a/packages/flowerbase/src/utils/context/helpers.ts +++ b/packages/flowerbase/src/utils/context/helpers.ts @@ -1,10 +1,24 @@ import { mongodb } from '@fastify/mongodb' import { EJSON } from 'bson' import * as jwt from 'jsonwebtoken' -import { Arguments } from '../../auth/dtos' +import { Arguments, User } from '../../auth/dtos' import { Function } from '../../features/functions/interface' import { GenerateContextDataParams } from './interface' +/** Realm-compatible system user for run_as_system execution with no authenticated caller. */ +export const REALM_SYSTEM_USER = { + type: 'system', + data: {}, + custom_data: {}, + identities: [] +} as const + +const isEmptyUser = (user: User) => + user != null && typeof user === 'object' && !Array.isArray(user) && Object.keys(user).length === 0 + +export const contextUserForRun = (user: User, runAsSystem: boolean): User => + runAsSystem && isEmptyUser(user) ? REALM_SYSTEM_USER : user + type JwtUtils = { encode: ( signingMethod: string, @@ -207,6 +221,7 @@ export const generateContextData = ({ action: '' }, user, + runningAsSystem: () => Boolean(currentFunction.run_as_system), environment: { tag: process.env.NODE_ENV }, diff --git a/packages/flowerbase/src/utils/context/index.ts b/packages/flowerbase/src/utils/context/index.ts index c952a6a..634a316 100644 --- a/packages/flowerbase/src/utils/context/index.ts +++ b/packages/flowerbase/src/utils/context/index.ts @@ -6,7 +6,7 @@ import vm from 'vm' import { EJSON } from 'bson' import { StateManager } from '../../state' import { Function as AppFunction } from '../../features/functions/interface' -import { generateContextData } from './helpers' +import { contextUserForRun, generateContextData } from './helpers' import { GenerateContextParams } from './interface' const dynamicImport = new Function('specifier', 'return import(specifier)') as ( @@ -323,11 +323,12 @@ export async function GenerateContext({ const functionsQueue = StateManager.select("functionsQueue") const effectiveRunAsSystem = Boolean(runAsSystem || currentFunction.run_as_system) const functionToRun = { ...currentFunction, run_as_system: effectiveRunAsSystem } + const contextUser = contextUserForRun(user, effectiveRunAsSystem) const run = async () => { const contextData = generateContextData({ - user, + user: contextUser, services, app, rules, @@ -443,8 +444,9 @@ export function GenerateContextSync({ const effectiveRunAsSystem = Boolean(runAsSystem || currentFunction.run_as_system) const functionToRun = { ...currentFunction, run_as_system: effectiveRunAsSystem } + const contextUser = contextUserForRun(user, effectiveRunAsSystem) const contextData = generateContextData({ - user, + user: contextUser, services, app, rules,