@optimizely-opal/opal-tool-ocp-sdk 0.0.0-beta.10 → 0.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +43 -9
  2. package/dist/function/ToolFunction.d.ts +7 -4
  3. package/dist/function/ToolFunction.d.ts.map +1 -1
  4. package/dist/function/ToolFunction.js +10 -39
  5. package/dist/function/ToolFunction.js.map +1 -1
  6. package/dist/function/ToolFunction.test.js +196 -177
  7. package/dist/function/ToolFunction.test.js.map +1 -1
  8. package/dist/index.d.ts +0 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +0 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/service/Service.d.ts +7 -7
  13. package/dist/service/Service.d.ts.map +1 -1
  14. package/dist/service/Service.js +16 -22
  15. package/dist/service/Service.js.map +1 -1
  16. package/dist/service/Service.test.js +3 -8
  17. package/dist/service/Service.test.js.map +1 -1
  18. package/dist/types/Models.d.ts +5 -5
  19. package/dist/types/Models.d.ts.map +1 -1
  20. package/dist/types/Models.js +9 -9
  21. package/dist/types/Models.js.map +1 -1
  22. package/package.json +3 -5
  23. package/src/function/ToolFunction.test.ts +214 -194
  24. package/src/function/ToolFunction.ts +11 -45
  25. package/src/index.ts +0 -1
  26. package/src/service/Service.test.ts +3 -8
  27. package/src/service/Service.ts +17 -22
  28. package/src/types/Models.ts +4 -4
  29. package/dist/auth/TokenVerifier.d.ts +0 -31
  30. package/dist/auth/TokenVerifier.d.ts.map +0 -1
  31. package/dist/auth/TokenVerifier.js +0 -127
  32. package/dist/auth/TokenVerifier.js.map +0 -1
  33. package/dist/auth/TokenVerifier.test.d.ts +0 -2
  34. package/dist/auth/TokenVerifier.test.d.ts.map +0 -1
  35. package/dist/auth/TokenVerifier.test.js +0 -114
  36. package/dist/auth/TokenVerifier.test.js.map +0 -1
  37. package/src/auth/TokenVerifier.test.ts +0 -152
  38. package/src/auth/TokenVerifier.ts +0 -145
@@ -1,23 +1,21 @@
1
1
  import { ToolFunction } from './ToolFunction';
2
2
  import { toolsService } from '../service/Service';
3
- import { Response, getAppContext } from '@zaiusinc/app-sdk';
4
- import { getTokenVerifier } from '../auth/TokenVerifier';
3
+ import { Response } from '@zaiusinc/app-sdk';
5
4
 
6
- // Mock the dependencies
5
+ // Mock the toolsService
7
6
  jest.mock('../service/Service', () => ({
8
7
  toolsService: {
9
8
  processRequest: jest.fn(),
9
+ extractBearerToken: jest.fn(),
10
10
  },
11
11
  }));
12
12
 
13
- jest.mock('../auth/TokenVerifier', () => ({
14
- getTokenVerifier: jest.fn(),
15
- }));
16
-
13
+ // Mock the Request and Response classes and Function base class
17
14
  jest.mock('@zaiusinc/app-sdk', () => ({
18
15
  Function: class {
19
16
  protected request: any;
20
17
  public constructor(_name?: string) {
18
+ // Mock constructor that accepts optional name parameter
21
19
  this.request = {};
22
20
  }
23
21
  },
@@ -29,34 +27,41 @@ jest.mock('@zaiusinc/app-sdk', () => ({
29
27
  bodyAsU8Array: new Uint8Array()
30
28
  })),
31
29
  amendLogContext: jest.fn(),
32
- getAppContext: jest.fn(),
33
- logger: {
34
- info: jest.fn(),
35
- error: jest.fn(),
36
- warn: jest.fn(),
37
- debug: jest.fn(),
38
- },
39
30
  }));
40
31
 
41
32
  // Create a concrete implementation for testing
42
33
  class TestToolFunction extends ToolFunction {
34
+ private mockValidateBearerToken: jest.MockedFunction<(token: string) => boolean>;
43
35
  private mockReady: jest.MockedFunction<() => Promise<boolean>>;
44
36
 
45
37
  public constructor(request?: any) {
46
- super(request || {});
38
+ super(request || {}); // Pass the request parameter properly
39
+ // Set the request directly without defaulting to empty object
47
40
  (this as any).request = request;
41
+
42
+ this.mockValidateBearerToken = jest.fn().mockReturnValue(true);
48
43
  this.mockReady = jest.fn().mockResolvedValue(true);
49
44
  }
50
45
 
46
+ // Override the concrete method with mock implementation for testing
47
+ protected validateBearerToken(bearerToken: string): boolean {
48
+ return this.mockValidateBearerToken(bearerToken);
49
+ }
50
+
51
51
  // Override the ready method with mock implementation for testing
52
52
  protected ready(): Promise<boolean> {
53
53
  return this.mockReady();
54
54
  }
55
55
 
56
+ // Expose request and validation mock for testing
56
57
  public getRequest() {
57
58
  return (this as any).request;
58
59
  }
59
60
 
61
+ public getMockValidateBearerToken() {
62
+ return this.mockValidateBearerToken;
63
+ }
64
+
60
65
  public getMockReady() {
61
66
  return this.mockReady;
62
67
  }
@@ -67,99 +72,35 @@ describe('ToolFunction', () => {
67
72
  let mockResponse: Response;
68
73
  let toolFunction: TestToolFunction;
69
74
  let mockProcessRequest: jest.MockedFunction<typeof toolsService.processRequest>;
70
- let mockGetTokenVerifier: jest.MockedFunction<typeof getTokenVerifier>;
71
- let mockGetAppContext: jest.MockedFunction<typeof getAppContext>;
72
- let mockTokenVerifier: jest.Mocked<{
73
- verify: (token: string) => Promise<any>;
74
- }>;
75
+ let mockExtractBearerToken: jest.MockedFunction<typeof toolsService.extractBearerToken>;
75
76
 
76
77
  beforeEach(() => {
77
78
  jest.clearAllMocks();
78
79
 
79
- // Create mock token verifier
80
- mockTokenVerifier = {
81
- verify: jest.fn(),
82
- };
83
-
84
- // Setup the mocks
85
- mockProcessRequest = jest.mocked(toolsService.processRequest);
86
- mockGetTokenVerifier = jest.mocked(getTokenVerifier);
87
- mockGetAppContext = jest.mocked(getAppContext);
88
-
89
- mockGetTokenVerifier.mockResolvedValue(mockTokenVerifier as any);
90
- mockGetAppContext.mockReturnValue({
91
- account: {
92
- organizationId: 'app-org-123'
93
- }
94
- } as any);
95
-
96
- // Create mock request with bodyJSON structure
80
+ // Create mock instances
97
81
  mockRequest = {
98
82
  headers: new Map(),
99
83
  method: 'POST',
100
- path: '/test',
101
- bodyJSON: {
102
- parameters: {
103
- task_id: 'task-123',
104
- content_id: 'content-456'
105
- },
106
- auth: {
107
- provider: 'OptiID',
108
- credentials: {
109
- token_type: 'Bearer',
110
- access_token: 'valid-access-token',
111
- org_sso_id: 'org-sso-123',
112
- user_id: 'user-456',
113
- instance_id: 'instance-789',
114
- customer_id: 'app-org-123',
115
- product_sku: 'OPAL'
116
- }
117
- },
118
- environment: {
119
- execution_mode: 'headless'
120
- }
121
- }
84
+ path: '/test'
122
85
  };
123
-
124
86
  mockResponse = {} as Response;
125
- toolFunction = new TestToolFunction(mockRequest);
126
- });
127
87
 
128
- // Helper function to create a ready request with valid auth
129
- const createReadyRequestWithAuth = () => ({
130
- headers: new Map(),
131
- method: 'GET',
132
- path: '/ready',
133
- bodyJSON: {
134
- auth: {
135
- provider: 'OptiID',
136
- credentials: {
137
- access_token: 'valid-token',
138
- customer_id: 'app-org-123'
139
- }
140
- }
141
- }
142
- });
88
+ // Setup the mocks
89
+ mockProcessRequest = jest.mocked(toolsService.processRequest);
90
+ mockExtractBearerToken = jest.mocked(toolsService.extractBearerToken);
143
91
 
144
- // Helper function to setup authorization mocks to pass
145
- const setupAuthMocks = () => {
146
- mockTokenVerifier.verify.mockResolvedValue(true);
147
- mockGetAppContext.mockReturnValue({
148
- account: {
149
- organizationId: 'app-org-123'
150
- }
151
- } as any);
152
- };
92
+ // Create test instance
93
+ toolFunction = new TestToolFunction(mockRequest);
94
+ });
153
95
 
154
96
  describe('/ready endpoint', () => {
155
- beforeEach(() => {
156
- setupAuthMocks();
157
- });
158
-
159
97
  it('should return ready: true when ready method returns true', async () => {
160
98
  // Arrange
161
- const readyRequest = createReadyRequestWithAuth();
162
-
99
+ const readyRequest = {
100
+ headers: new Map(),
101
+ method: 'GET',
102
+ path: '/ready'
103
+ };
163
104
  toolFunction = new TestToolFunction(readyRequest);
164
105
  toolFunction.getMockReady().mockResolvedValue(true);
165
106
 
@@ -174,8 +115,11 @@ describe('ToolFunction', () => {
174
115
 
175
116
  it('should return ready: false when ready method returns false', async () => {
176
117
  // Arrange
177
- const readyRequest = createReadyRequestWithAuth();
178
-
118
+ const readyRequest = {
119
+ headers: new Map(),
120
+ method: 'GET',
121
+ path: '/ready'
122
+ };
179
123
  toolFunction = new TestToolFunction(readyRequest);
180
124
  toolFunction.getMockReady().mockResolvedValue(false);
181
125
 
@@ -190,8 +134,11 @@ describe('ToolFunction', () => {
190
134
 
191
135
  it('should handle ready method throwing an error', async () => {
192
136
  // Arrange
193
- const readyRequest = createReadyRequestWithAuth();
194
-
137
+ const readyRequest = {
138
+ headers: new Map(),
139
+ method: 'GET',
140
+ path: '/ready'
141
+ };
195
142
  toolFunction = new TestToolFunction(readyRequest);
196
143
  toolFunction.getMockReady().mockRejectedValue(new Error('Ready check failed'));
197
144
 
@@ -201,6 +148,52 @@ describe('ToolFunction', () => {
201
148
  expect(mockProcessRequest).not.toHaveBeenCalled(); // Should not call service
202
149
  });
203
150
 
151
+ it('should handle /ready endpoint after bearer token validation passes', async () => {
152
+ // Arrange
153
+ const readyRequest = {
154
+ headers: new Map([['authorization', 'Bearer valid-token']]),
155
+ method: 'GET',
156
+ path: '/ready'
157
+ };
158
+ toolFunction = new TestToolFunction(readyRequest);
159
+ toolFunction.getMockReady().mockResolvedValue(true);
160
+ mockExtractBearerToken.mockReturnValue('valid-token');
161
+ toolFunction.getMockValidateBearerToken().mockReturnValue(true); // Make sure auth passes so we reach /ready
162
+
163
+ // Act
164
+ const result = await toolFunction.perform();
165
+
166
+ // Assert
167
+ expect(toolFunction.getMockReady()).toHaveBeenCalledTimes(1);
168
+ expect(result).toEqual(new Response(200, { ready: true }));
169
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(readyRequest.headers); // Auth is checked first
170
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith('valid-token'); // Auth validation happens
171
+ expect(mockProcessRequest).not.toHaveBeenCalled(); // Should not call service
172
+ });
173
+
174
+ it('should return 403 when bearer token validation fails even for /ready endpoint', async () => {
175
+ // Arrange
176
+ const readyRequest = {
177
+ headers: new Map([['authorization', 'Bearer invalid-token']]),
178
+ method: 'GET',
179
+ path: '/ready'
180
+ };
181
+ toolFunction = new TestToolFunction(readyRequest);
182
+ toolFunction.getMockReady().mockResolvedValue(true);
183
+ mockExtractBearerToken.mockReturnValue('invalid-token');
184
+ toolFunction.getMockValidateBearerToken().mockReturnValue(false); // Auth fails
185
+
186
+ // Act
187
+ const result = await toolFunction.perform();
188
+
189
+ // Assert
190
+ expect(result).toEqual(new Response(403, { error: 'Forbidden' }));
191
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(readyRequest.headers);
192
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith('invalid-token');
193
+ expect(toolFunction.getMockReady()).not.toHaveBeenCalled(); // Should not reach ready check
194
+ expect(mockProcessRequest).not.toHaveBeenCalled(); // Should not call service
195
+ });
196
+
204
197
  it('should use default ready implementation when not overridden', async () => {
205
198
  // Create a class that doesn't override ready method
206
199
  class DefaultReadyToolFunction extends ToolFunction {
@@ -215,7 +208,11 @@ describe('ToolFunction', () => {
215
208
  }
216
209
 
217
210
  // Arrange
218
- const readyRequest = createReadyRequestWithAuth();
211
+ const readyRequest = {
212
+ headers: new Map(),
213
+ method: 'GET',
214
+ path: '/ready'
215
+ };
219
216
  const defaultToolFunction = new DefaultReadyToolFunction(readyRequest);
220
217
 
221
218
  // Act
@@ -227,136 +224,159 @@ describe('ToolFunction', () => {
227
224
  });
228
225
  });
229
226
 
230
- describe('perform', () => {
231
- it('should execute successfully with valid token and matching organization', async () => {
232
- // Setup mock token verifier to return true for valid token
233
- mockTokenVerifier.verify.mockResolvedValue(true);
227
+ describe('bearer token validation', () => {
228
+ it('should extract bearer token from headers and validate it', async () => {
229
+ // Arrange
230
+ const bearerToken = 'valid-token-123';
231
+ mockExtractBearerToken.mockReturnValue(bearerToken);
232
+ toolFunction.getMockValidateBearerToken().mockReturnValue(true);
234
233
  mockProcessRequest.mockResolvedValue(mockResponse);
235
234
 
235
+ // Act
236
236
  const result = await toolFunction.perform();
237
237
 
238
- expect(result).toBe(mockResponse);
239
- expect(mockGetTokenVerifier).toHaveBeenCalled();
240
- expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-access-token');
241
- expect(mockGetAppContext).toHaveBeenCalled();
238
+ // Assert
239
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
240
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(bearerToken);
242
241
  expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest, toolFunction);
242
+ expect(result).toBe(mockResponse);
243
243
  });
244
244
 
245
- it('should return 403 response with invalid token', async () => {
246
- // Setup mock token verifier to return false
247
- mockTokenVerifier.verify.mockResolvedValue(false);
245
+ it('should return 403 Forbidden when bearer token validation fails', async () => {
246
+ // Arrange
247
+ const bearerToken = 'invalid-token-456';
248
+ mockExtractBearerToken.mockReturnValue(bearerToken);
249
+ toolFunction.getMockValidateBearerToken().mockReturnValue(false);
248
250
 
251
+ // Act
249
252
  const result = await toolFunction.perform();
250
253
 
251
- expect(result).toEqual(new Response(403, { error: 'Forbidden' }));
252
- expect(mockGetTokenVerifier).toHaveBeenCalled();
253
- expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-access-token');
254
+ // Assert
255
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
256
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(bearerToken);
254
257
  expect(mockProcessRequest).not.toHaveBeenCalled();
258
+ expect(result).toEqual(new Response(403, { error: 'Forbidden' }));
255
259
  });
256
260
 
257
- it('should return 403 response when organization ID does not match', async () => {
258
- // Update mock request with different customer_id
259
- const requestWithDifferentOrgId = {
260
- ...mockRequest,
261
- bodyJSON: {
262
- ...mockRequest.bodyJSON,
263
- auth: {
264
- ...mockRequest.bodyJSON.auth,
265
- credentials: {
266
- ...mockRequest.bodyJSON.auth.credentials,
267
- customer_id: 'different-org-123'
268
- }
269
- }
270
- }
271
- };
261
+ it('should handle complex bearer token validation scenarios', async () => {
262
+ // Arrange - simulate a token that should be valid based on some complex logic
263
+ const complexToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9l' +
264
+ 'IiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
265
+ mockExtractBearerToken.mockReturnValue(complexToken);
266
+ toolFunction.getMockValidateBearerToken().mockReturnValue(true);
267
+ mockProcessRequest.mockResolvedValue(mockResponse);
272
268
 
273
- const toolFunctionWithDifferentOrgId = new TestToolFunction(requestWithDifferentOrgId);
269
+ // Act
270
+ const result = await toolFunction.perform();
271
+
272
+ // Assert
273
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
274
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(complexToken);
275
+ expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest, toolFunction);
276
+ expect(result).toBe(mockResponse);
277
+ });
274
278
 
275
- const result = await toolFunctionWithDifferentOrgId.perform();
279
+ it('should handle bearer token validation throwing an error', async () => {
280
+ // Arrange
281
+ const bearerToken = 'malformed-token';
282
+ mockExtractBearerToken.mockReturnValue(bearerToken);
283
+ toolFunction.getMockValidateBearerToken().mockImplementation(() => {
284
+ throw new Error('Token validation error');
285
+ });
276
286
 
277
- expect(result).toEqual(new Response(403, { error: 'Forbidden' }));
278
- expect(mockGetAppContext).toHaveBeenCalled();
287
+ // Act & Assert
288
+ await expect(toolFunction.perform()).rejects.toThrow('Token validation error');
289
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
290
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(bearerToken);
279
291
  expect(mockProcessRequest).not.toHaveBeenCalled();
280
292
  });
281
293
 
282
- it('should return 403 response when access token is missing', async () => {
283
- // Create request without access token
284
- const requestWithoutToken = {
285
- ...mockRequest,
286
- bodyJSON: {
287
- ...mockRequest.bodyJSON,
288
- auth: {
289
- ...mockRequest.bodyJSON.auth,
290
- credentials: {
291
- ...mockRequest.bodyJSON.auth.credentials,
292
- access_token: undefined
293
- }
294
- }
295
- }
296
- };
297
-
298
- const toolFunctionWithoutToken = new TestToolFunction(requestWithoutToken);
294
+ it('should call validateBearerToken only once per request', async () => {
295
+ // Arrange
296
+ const bearerToken = 'test-token';
297
+ mockExtractBearerToken.mockReturnValue(bearerToken);
298
+ toolFunction.getMockValidateBearerToken().mockReturnValue(true);
299
+ mockProcessRequest.mockResolvedValue(mockResponse);
299
300
 
300
- const result = await toolFunctionWithoutToken.perform();
301
+ // Act
302
+ await toolFunction.perform();
301
303
 
302
- expect(result).toEqual(new Response(403, { error: 'Forbidden' }));
303
- expect(mockGetTokenVerifier).not.toHaveBeenCalled();
304
- expect(mockProcessRequest).not.toHaveBeenCalled();
304
+ // Assert
305
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledTimes(1);
306
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(bearerToken);
305
307
  });
306
308
 
307
- it('should return 403 response when organisation id is missing', async () => {
308
- // Create request without customer_id
309
- const requestWithoutCustomerId = {
310
- ...mockRequest,
311
- bodyJSON: {
312
- ...mockRequest.bodyJSON,
313
- auth: {
314
- ...mockRequest.bodyJSON.auth,
315
- credentials: {
316
- ...mockRequest.bodyJSON.auth.credentials,
317
- customer_id: undefined
318
- }
319
- }
320
- }
321
- };
322
-
323
- const toolFunctionWithoutCustomerId = new TestToolFunction(requestWithoutCustomerId);
309
+ it('should extract bearer token only once per request', async () => {
310
+ // Arrange
311
+ const bearerToken = 'test-token';
312
+ mockExtractBearerToken.mockReturnValue(bearerToken);
313
+ toolFunction.getMockValidateBearerToken().mockReturnValue(true);
314
+ mockProcessRequest.mockResolvedValue(mockResponse);
324
315
 
325
- const result = await toolFunctionWithoutCustomerId.perform();
316
+ // Act
317
+ await toolFunction.perform();
326
318
 
327
- expect(result).toEqual(new Response(403, { error: 'Forbidden' }));
328
- expect(mockGetTokenVerifier).not.toHaveBeenCalled();
329
- expect(mockProcessRequest).not.toHaveBeenCalled();
319
+ // Assert
320
+ expect(mockExtractBearerToken).toHaveBeenCalledTimes(1);
321
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
330
322
  });
331
323
 
332
- it('should return 403 response when auth structure is missing', async () => {
333
- // Create request without auth structure
334
- const requestWithoutAuth = {
335
- ...mockRequest,
336
- bodyJSON: {
337
- parameters: mockRequest.bodyJSON.parameters,
338
- environment: mockRequest.bodyJSON.environment
339
- }
340
- };
341
-
342
- const toolFunctionWithoutAuth = new TestToolFunction(requestWithoutAuth);
324
+ it('should skip validation when bearer token is undefined', async () => {
325
+ // Arrange
326
+ mockExtractBearerToken.mockReturnValue(undefined);
327
+ mockProcessRequest.mockResolvedValue(mockResponse);
343
328
 
344
- const result = await toolFunctionWithoutAuth.perform();
329
+ // Act
330
+ const result = await toolFunction.perform();
345
331
 
346
- expect(result).toEqual(new Response(403, { error: 'Forbidden' }));
347
- expect(mockGetTokenVerifier).not.toHaveBeenCalled();
348
- expect(mockProcessRequest).not.toHaveBeenCalled();
332
+ // Assert
333
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
334
+ expect(toolFunction.getMockValidateBearerToken()).not.toHaveBeenCalled();
335
+ expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest, toolFunction);
336
+ expect(result).toBe(mockResponse);
349
337
  });
350
338
 
351
- it('should return 403 response when token verifier initialization fails', async () => {
352
- // Setup mock to fail during token verifier initialization
353
- mockGetTokenVerifier.mockRejectedValue(new Error('Failed to initialize token verifier'));
339
+ it('should skip validation when bearer token is empty string', async () => {
340
+ // Arrange
341
+ mockExtractBearerToken.mockReturnValue('');
342
+ mockProcessRequest.mockResolvedValue(mockResponse);
354
343
 
344
+ // Act
355
345
  const result = await toolFunction.perform();
356
346
 
357
- expect(result).toEqual(new Response(403, { error: 'Forbidden' }));
358
- expect(mockGetTokenVerifier).toHaveBeenCalled();
359
- expect(mockProcessRequest).not.toHaveBeenCalled();
347
+ // Assert
348
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
349
+ expect(toolFunction.getMockValidateBearerToken()).not.toHaveBeenCalled();
350
+ expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest, toolFunction);
351
+ expect(result).toBe(mockResponse);
352
+ });
353
+
354
+ it('should use default validateBearerToken implementation when not overridden', async () => {
355
+ // Create a class that doesn't override validateBearerToken
356
+ class DefaultToolFunction extends ToolFunction {
357
+ public constructor(request?: any) {
358
+ super(request || {});
359
+ (this as any).request = request;
360
+ }
361
+
362
+ public getRequest() {
363
+ return (this as any).request;
364
+ }
365
+ }
366
+
367
+ // Arrange
368
+ const defaultToolFunction = new DefaultToolFunction(mockRequest);
369
+ const bearerToken = 'any-token';
370
+ mockExtractBearerToken.mockReturnValue(bearerToken);
371
+ mockProcessRequest.mockResolvedValue(mockResponse);
372
+
373
+ // Act
374
+ const result = await defaultToolFunction.perform();
375
+
376
+ // Assert - Default implementation should return true and allow request to proceed
377
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
378
+ expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest, defaultToolFunction);
379
+ expect(result).toBe(mockResponse);
360
380
  });
361
381
  });
362
382
 
@@ -1,7 +1,5 @@
1
- import { Function, Response, amendLogContext, getAppContext, logger } from '@zaiusinc/app-sdk';
1
+ import { Function, Response, amendLogContext } from '@zaiusinc/app-sdk';
2
2
  import { toolsService } from '../service/Service';
3
- import { getTokenVerifier } from '../auth/TokenVerifier';
4
- import { OptiIdAuthData } from '../types/Models';
5
3
 
6
4
  /**
7
5
  * Abstract base class for tool-based function execution
@@ -26,7 +24,8 @@ export abstract class ToolFunction extends Function {
26
24
  */
27
25
  public async perform(): Promise<Response> {
28
26
  amendLogContext({ opalThreadId: this.request.headers.get('x-opal-thread-id') || '' });
29
- if (!(await this.authorizeRequest())) {
27
+ const bearerToken = toolsService.extractBearerToken(this.request.headers);
28
+ if (bearerToken && !this.validateBearerToken(bearerToken)) {
30
29
  return new Response(403, { error: 'Forbidden' });
31
30
  }
32
31
 
@@ -39,48 +38,15 @@ export abstract class ToolFunction extends Function {
39
38
  }
40
39
 
41
40
  /**
42
- * Authenticate the incoming request by validating the OptiID token and organization ID
41
+ * Validates the bearer token for authorization.
43
42
  *
44
- * @throws true if authentication succeeds
43
+ * This method provides a default implementation that accepts all tokens.
44
+ * Subclasses can override this method to implement custom bearer token validation logic.
45
+ *
46
+ * @param _bearerToken - The bearer token extracted from the Authorization header
47
+ * @returns true if the token is valid and the request should proceed, false to return 403 Forbidden
45
48
  */
46
- private async authorizeRequest(): Promise<boolean> {
47
- if (this.request.path === '/discovery' || this.request.path === '/ready') {
48
- return true;
49
- }
50
- logger.debug('Authorizing request:', this.request.bodyJSON);
51
- const authData = this.request.bodyJSON?.auth as OptiIdAuthData;
52
- const accessToken = authData?.credentials?.access_token;
53
- if (!accessToken || authData?.provider?.toLowerCase() !== 'optiid') {
54
- logger.error('OptiID token is required but not provided');
55
- return false;
56
- }
57
-
58
- const customerId = authData.credentials?.customer_id;
59
- if (!customerId) {
60
- logger.error('Organisation ID is required but not provided');
61
- return false;
62
- }
63
-
64
- const appOrganisationId = getAppContext().account.organizationId;
65
- if (customerId !== appOrganisationId) {
66
- logger.error(`Invalid organisation ID: expected ${appOrganisationId}, received ${customerId}`);
67
- return false;
68
- }
69
-
70
- return await this.validateAccessToken(accessToken);
71
- }
72
-
73
- private async validateAccessToken(accessToken: string | undefined): Promise<boolean> {
74
- try {
75
- if (!accessToken) {
76
- return false;
77
- }
78
- const tokenVerifier = await getTokenVerifier();
79
- return await tokenVerifier.verify(accessToken);
80
- } catch (error) {
81
- logger.error('OptiID token validation failed:', error);
82
- return false;
83
- }
49
+ protected validateBearerToken(_bearerToken: string): boolean {
50
+ return true;
84
51
  }
85
-
86
52
  }
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export * from './function/ToolFunction';
2
2
  export * from './types/Models';
3
3
  export * from './decorator/Decorator';
4
- export * from './auth/TokenVerifier';
5
4
  export { Tool, Interaction, InteractionResult } from './service/Service';
@@ -122,8 +122,7 @@ describe('ToolsService', () => {
122
122
  description: mockTool.description,
123
123
  parameters: mockTool.parameters.map((p: Parameter) => p.toJSON()),
124
124
  endpoint: mockTool.endpoint,
125
- http_method: 'POST',
126
- auth_requirements: [{ provider: 'OptiID', scope_bundle: 'default', required: true }]
125
+ http_method: 'POST'
127
126
  });
128
127
  });
129
128
 
@@ -182,8 +181,7 @@ describe('ToolsService', () => {
182
181
  description: mockTool.description,
183
182
  parameters: mockTool.parameters.map((p: Parameter) => p.toJSON()),
184
183
  endpoint: mockTool.endpoint,
185
- http_method: 'POST',
186
- auth_requirements: [{ provider: 'OptiID', scope_bundle: 'default', required: true }]
184
+ http_method: 'POST'
187
185
  });
188
186
 
189
187
  expect(secondFunction).toEqual({
@@ -192,10 +190,7 @@ describe('ToolsService', () => {
192
190
  parameters: [],
193
191
  endpoint: '/second-tool',
194
192
  http_method: 'POST',
195
- auth_requirements: [
196
- { provider: 'oauth2', scope_bundle: 'calendar', required: true },
197
- { provider: 'OptiID', scope_bundle: 'default', required: true }
198
- ]
193
+ auth_requirements: authRequirements.map((auth) => auth.toJSON())
199
194
  });
200
195
  });
201
196
  });