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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions packages/workflow-executor/src/adapters/agent-client-agent-port.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,19 @@ export default class AgentClientAgentPort implements AgentPort {
}

private createClient(user: StepUser) {
const token = jsonwebtoken.sign({ ...user, scope: 'step-execution' }, this.authSecret, {
expiresIn: '5m',
});
// snake_case aliases: Ruby/Python agents splat JWT claims into Caller.new (snake_case kwargs).
const token = jsonwebtoken.sign(
{
...user,
first_name: user.firstName,
last_name: user.lastName,
rendering_id: user.renderingId,
permission_level: user.permissionLevel,
scope: 'step-execution',
},
this.authSecret,
{ expiresIn: '5m' },
);

return createRemoteAgentClient({
url: this.agentUrl,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { StepUser } from '../../src/types/execution-context';

import { createRemoteAgentClient } from '@forestadmin/agent-client';
import jsonwebtoken from 'jsonwebtoken';

import AgentClientAgentPort from '../../src/adapters/agent-client-agent-port';
import { AgentProbeError, RecordNotFoundError } from '../../src/errors';
Expand Down Expand Up @@ -43,7 +44,7 @@

const mocks = createMockClient();
({ mockCollection, mockRelation, mockAction } = mocks);
mockedCreateRemoteAgentClient.mockReturnValue(mocks.client as any);

Check warning on line 47 in packages/workflow-executor/test/adapters/agent-client-agent-port.test.ts

View workflow job for this annotation

GitHub Actions / Linting & Testing (workflow-executor)

Unexpected any. Specify a different type

const schemaCache = new SchemaCache();
schemaCache.set('users', {
Expand Down Expand Up @@ -202,6 +203,29 @@
});
});

describe('agent JWT', () => {
it('signs both camelCase and snake_case identity claims for cross-runtime agents', async () => {
mockCollection.list.mockResolvedValue([{ id: 42 }]);

await port.getRecord({ collection: 'users', id: [42] }, user);

const { token } = mockedCreateRemoteAgentClient.mock.calls[0][0];
const payload = jsonwebtoken.verify(token, 'test-secret') as Record<string, unknown>;

expect(payload).toMatchObject({
firstName: 'Test',
lastName: 'User',
renderingId: 1,
permissionLevel: 'admin',
first_name: 'Test',
last_name: 'User',
rendering_id: 1,
permission_level: 'admin',
scope: 'step-execution',
});
});
});

describe('updateRecord', () => {
it('should forward the RecordId array to agent-client and return a RecordData', async () => {
mockCollection.update.mockResolvedValue({ id: 42, name: 'Bob' });
Expand Down
Loading