@optimizely-opal/opal-tool-ocp-sdk 1.0.0 → 1.1.0-beta.2

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 (44) hide show
  1. package/README.md +51 -13
  2. package/dist/auth/AuthUtils.d.ts +2 -5
  3. package/dist/auth/AuthUtils.d.ts.map +1 -1
  4. package/dist/auth/AuthUtils.js +5 -5
  5. package/dist/auth/AuthUtils.js.map +1 -1
  6. package/dist/decorator/Decorator.test.js +4 -4
  7. package/dist/decorator/Decorator.test.js.map +1 -1
  8. package/dist/function/GlobalToolFunction.d.ts +4 -1
  9. package/dist/function/GlobalToolFunction.d.ts.map +1 -1
  10. package/dist/function/GlobalToolFunction.js +27 -21
  11. package/dist/function/GlobalToolFunction.js.map +1 -1
  12. package/dist/function/GlobalToolFunction.test.js +114 -193
  13. package/dist/function/GlobalToolFunction.test.js.map +1 -1
  14. package/dist/function/ToolFunction.d.ts +4 -1
  15. package/dist/function/ToolFunction.d.ts.map +1 -1
  16. package/dist/function/ToolFunction.js +20 -21
  17. package/dist/function/ToolFunction.js.map +1 -1
  18. package/dist/function/ToolFunction.test.js +73 -263
  19. package/dist/function/ToolFunction.test.js.map +1 -1
  20. package/dist/service/Service.d.ts +20 -19
  21. package/dist/service/Service.d.ts.map +1 -1
  22. package/dist/service/Service.js +47 -72
  23. package/dist/service/Service.js.map +1 -1
  24. package/dist/service/Service.test.js +229 -133
  25. package/dist/service/Service.test.js.map +1 -1
  26. package/dist/types/Models.d.ts +18 -7
  27. package/dist/types/Models.d.ts.map +1 -1
  28. package/dist/types/Models.js +1 -29
  29. package/dist/types/Models.js.map +1 -1
  30. package/dist/utils/ErrorFormatter.d.ts +9 -0
  31. package/dist/utils/ErrorFormatter.d.ts.map +1 -0
  32. package/dist/utils/ErrorFormatter.js +25 -0
  33. package/dist/utils/ErrorFormatter.js.map +1 -0
  34. package/package.json +1 -1
  35. package/src/auth/AuthUtils.ts +10 -10
  36. package/src/decorator/Decorator.test.ts +4 -4
  37. package/src/function/GlobalToolFunction.test.ts +113 -213
  38. package/src/function/GlobalToolFunction.ts +31 -31
  39. package/src/function/ToolFunction.test.ts +78 -285
  40. package/src/function/ToolFunction.ts +24 -30
  41. package/src/service/Service.test.ts +238 -174
  42. package/src/service/Service.ts +68 -92
  43. package/src/types/Models.ts +24 -15
  44. package/src/utils/ErrorFormatter.ts +31 -0
@@ -2,11 +2,14 @@ import { ToolFunction } from './ToolFunction';
2
2
  import { toolsService } from '../service/Service';
3
3
  import { Response, getAppContext } from '@zaiusinc/app-sdk';
4
4
  import { getTokenVerifier } from '../auth/TokenVerifier';
5
+ import { ToolError } from '../types/ToolError';
6
+ import { authenticateRegularRequest } from '../auth/AuthUtils';
5
7
 
6
8
  // Mock the dependencies
7
9
  jest.mock('../service/Service', () => ({
8
10
  toolsService: {
9
11
  processRequest: jest.fn(),
12
+ requiresOptiIdAuth: jest.fn(),
10
13
  },
11
14
  }));
12
15
 
@@ -14,6 +17,11 @@ jest.mock('../auth/TokenVerifier', () => ({
14
17
  getTokenVerifier: jest.fn(),
15
18
  }));
16
19
 
20
+ jest.mock('../auth/AuthUtils', () => ({
21
+ authenticateRegularRequest: jest.fn().mockResolvedValue(undefined),
22
+ authenticateInternalRequest: jest.fn().mockResolvedValue(undefined),
23
+ }));
24
+
17
25
  jest.mock('@zaiusinc/app-sdk', () => ({
18
26
  Function: class {
19
27
  protected request: any;
@@ -257,172 +265,16 @@ describe('ToolFunction', () => {
257
265
  });
258
266
 
259
267
  describe('perform', () => {
260
- it('should execute successfully with valid token and matching organization', async () => {
261
- // Setup mock token verifier to return true for valid token
262
- mockTokenVerifier.verify.mockResolvedValue(true);
268
+ it('should call processRequest and return its result', async () => {
269
+ // Setup mock to return a response
263
270
  mockProcessRequest.mockResolvedValue(mockResponse);
264
271
 
265
272
  const result = await toolFunction.perform();
266
273
 
267
274
  expect(result).toBe(mockResponse);
268
- expect(mockGetTokenVerifier).toHaveBeenCalled();
269
- expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-access-token');
270
- expect(mockGetAppContext).toHaveBeenCalled();
275
+ // processRequest is called with request and context (no authValidator)
271
276
  expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest, toolFunction);
272
277
  });
273
-
274
- it('should return 403 response with invalid token', async () => {
275
- // Setup mock token verifier to return false
276
- mockTokenVerifier.verify.mockResolvedValue(false);
277
-
278
- const result = await toolFunction.perform();
279
-
280
- expect(result.status).toBe(403);
281
- expect(result.bodyJSON).toEqual({
282
- title: 'Forbidden',
283
- status: 403,
284
- detail: 'Invalid OptiID access token',
285
- instance: '/test'
286
- });
287
- expect(mockGetTokenVerifier).toHaveBeenCalled();
288
- expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-access-token');
289
- expect(mockProcessRequest).not.toHaveBeenCalled();
290
- });
291
-
292
- it('should return 403 response when organization ID does not match', async () => {
293
- // Update mock request with different customer_id
294
- const requestWithDifferentOrgId = {
295
- ...mockRequest,
296
- bodyJSON: {
297
- ...mockRequest.bodyJSON,
298
- auth: {
299
- ...mockRequest.bodyJSON.auth,
300
- credentials: {
301
- ...mockRequest.bodyJSON.auth.credentials,
302
- customer_id: 'different-org-123'
303
- }
304
- }
305
- }
306
- };
307
-
308
- const toolFunctionWithDifferentOrgId = new TestToolFunction(requestWithDifferentOrgId);
309
-
310
- const result = await toolFunctionWithDifferentOrgId.perform();
311
-
312
- expect(result.status).toBe(403);
313
- expect(result.bodyJSON).toEqual({
314
- title: 'Forbidden',
315
- status: 403,
316
- detail: 'Organization ID does not match',
317
- instance: '/test'
318
- });
319
- expect(mockGetAppContext).toHaveBeenCalled();
320
- expect(mockProcessRequest).not.toHaveBeenCalled();
321
- });
322
-
323
- it('should return 403 response when access token is missing', async () => {
324
- // Create request without access token
325
- const requestWithoutToken = {
326
- ...mockRequest,
327
- bodyJSON: {
328
- ...mockRequest.bodyJSON,
329
- auth: {
330
- ...mockRequest.bodyJSON.auth,
331
- credentials: {
332
- ...mockRequest.bodyJSON.auth.credentials,
333
- access_token: undefined
334
- }
335
- }
336
- }
337
- };
338
-
339
- const toolFunctionWithoutToken = new TestToolFunction(requestWithoutToken);
340
-
341
- const result = await toolFunctionWithoutToken.perform();
342
-
343
- expect(result.status).toBe(403);
344
- expect(result.bodyJSON).toEqual({
345
- title: 'Forbidden',
346
- status: 403,
347
- detail: 'OptiID access token is required',
348
- instance: '/test'
349
- });
350
- expect(mockGetTokenVerifier).not.toHaveBeenCalled();
351
- expect(mockProcessRequest).not.toHaveBeenCalled();
352
- });
353
-
354
- it('should return 403 response when organisation id is missing', async () => {
355
- // Create request without customer_id
356
- const requestWithoutCustomerId = {
357
- ...mockRequest,
358
- bodyJSON: {
359
- ...mockRequest.bodyJSON,
360
- auth: {
361
- ...mockRequest.bodyJSON.auth,
362
- credentials: {
363
- ...mockRequest.bodyJSON.auth.credentials,
364
- customer_id: undefined
365
- }
366
- }
367
- }
368
- };
369
-
370
- const toolFunctionWithoutCustomerId = new TestToolFunction(requestWithoutCustomerId);
371
-
372
- const result = await toolFunctionWithoutCustomerId.perform();
373
-
374
- expect(result.status).toBe(403);
375
- expect(result.bodyJSON).toEqual({
376
- title: 'Forbidden',
377
- status: 403,
378
- detail: 'Organization ID is required',
379
- instance: '/test'
380
- });
381
- expect(mockGetTokenVerifier).not.toHaveBeenCalled();
382
- expect(mockProcessRequest).not.toHaveBeenCalled();
383
- });
384
-
385
- it('should return 403 response when auth structure is missing', async () => {
386
- // Create request without auth structure
387
- const requestWithoutAuth = {
388
- ...mockRequest,
389
- bodyJSON: {
390
- parameters: mockRequest.bodyJSON.parameters,
391
- environment: mockRequest.bodyJSON.environment
392
- }
393
- };
394
-
395
- const toolFunctionWithoutAuth = new TestToolFunction(requestWithoutAuth);
396
-
397
- const result = await toolFunctionWithoutAuth.perform();
398
-
399
- expect(result.status).toBe(403);
400
- expect(result.bodyJSON).toEqual({
401
- title: 'Forbidden',
402
- status: 403,
403
- detail: 'Authentication data is required',
404
- instance: '/test'
405
- });
406
- expect(mockGetTokenVerifier).not.toHaveBeenCalled();
407
- expect(mockProcessRequest).not.toHaveBeenCalled();
408
- });
409
-
410
- it('should return 403 response when token verifier initialization fails', async () => {
411
- // Setup mock to fail during token verifier initialization
412
- mockGetTokenVerifier.mockRejectedValue(new Error('Failed to initialize token verifier'));
413
-
414
- const result = await toolFunction.perform();
415
-
416
- expect(result.status).toBe(403);
417
- expect(result.bodyJSON).toEqual({
418
- title: 'Forbidden',
419
- status: 403,
420
- detail: 'Token verification failed',
421
- instance: '/test'
422
- });
423
- expect(mockGetTokenVerifier).toHaveBeenCalled();
424
- expect(mockProcessRequest).not.toHaveBeenCalled();
425
- });
426
278
  });
427
279
 
428
280
  describe('inheritance', () => {
@@ -437,21 +289,18 @@ describe('ToolFunction', () => {
437
289
  });
438
290
  });
439
291
 
440
- describe('internal request authentication', () => {
292
+ describe('system paths routing', () => {
441
293
  beforeEach(() => {
442
- // Reset mocks before each test
443
294
  jest.clearAllMocks();
444
- setupAuthMocks();
445
295
  });
446
296
 
447
- it('should use internal authentication for /overrides endpoint', async () => {
297
+ it('should forward /overrides to processRequest directly', async () => {
448
298
  const overridesRequest = {
449
299
  path: '/overrides',
450
300
  method: 'DELETE',
451
301
  bodyJSON: {},
452
302
  headers: {
453
303
  get: jest.fn().mockImplementation((name: string) => {
454
- if (name === 'Authorization' || name === 'authorization') return 'internal-token';
455
304
  if (name === 'x-opal-thread-id') return 'test-thread-id';
456
305
  return null;
457
306
  })
@@ -463,39 +312,14 @@ describe('ToolFunction', () => {
463
312
  const result = await toolFunctionOverrides.perform();
464
313
 
465
314
  expect(result).toBe(mockResponse);
315
+ // /overrides is forwarded directly to processRequest (auth handled there)
466
316
  expect(mockProcessRequest).toHaveBeenCalledWith(overridesRequest, toolFunctionOverrides);
467
317
  });
468
318
 
469
- it('should throw ToolError when internal authentication fails for /overrides endpoint', async () => {
470
- const overridesRequest = {
471
- path: '/overrides',
472
- method: 'DELETE',
473
- bodyJSON: {},
474
- headers: {
475
- get: jest.fn().mockImplementation((name: string) => {
476
- if (name === 'x-opal-thread-id') return 'test-thread-id';
477
- return null; // No Authorization header
478
- })
479
- }
480
- };
481
-
482
- const toolFunctionOverrides = new TestToolFunction(overridesRequest);
483
- const result = await toolFunctionOverrides.perform();
484
-
485
- expect(result.status).toBe(401);
486
- expect(result.bodyJSON).toEqual({
487
- title: 'Unauthorized',
488
- status: 401,
489
- detail: 'Internal request authentication failed',
490
- instance: '/overrides'
491
- });
492
- expect(mockProcessRequest).not.toHaveBeenCalled();
493
- });
494
-
495
- it('should use regular authentication for non-overrides endpoints', async () => {
496
- const regularRequest = {
497
- ...mockRequest,
498
- path: '/regular-tool',
319
+ it('should forward /discovery to processRequest directly', async () => {
320
+ const discoveryRequest = {
321
+ path: '/discovery',
322
+ method: 'GET',
499
323
  headers: {
500
324
  get: jest.fn().mockImplementation((name: string) => {
501
325
  if (name === 'x-opal-thread-id') return 'test-thread-id';
@@ -505,136 +329,101 @@ describe('ToolFunction', () => {
505
329
  };
506
330
 
507
331
  mockProcessRequest.mockResolvedValue(mockResponse);
508
- const toolFunctionRegular = new TestToolFunction(regularRequest);
509
- const result = await toolFunctionRegular.perform();
332
+ const toolFunctionDiscovery = new TestToolFunction(discoveryRequest);
333
+ const result = await toolFunctionDiscovery.perform();
510
334
 
511
335
  expect(result).toBe(mockResponse);
512
- expect(mockProcessRequest).toHaveBeenCalledWith(regularRequest, toolFunctionRegular);
336
+ expect(mockProcessRequest).toHaveBeenCalledWith(discoveryRequest, toolFunctionDiscovery);
513
337
  });
514
338
 
515
- it('should handle internal authentication with valid Authorization header', async () => {
516
- const overridesRequest = {
517
- path: '/overrides',
518
- method: 'PATCH',
519
- bodyJSON: {
520
- functions: [
521
- {
522
- name: 'test_tool',
523
- description: 'Updated description'
524
- }
525
- ]
526
- },
339
+ it('should forward tool endpoints to processRequest', async () => {
340
+ const toolRequest = {
341
+ ...mockRequest,
342
+ path: '/some-tool',
527
343
  headers: {
528
344
  get: jest.fn().mockImplementation((name: string) => {
529
- if (name === 'Authorization') return 'valid-internal-token';
530
345
  if (name === 'x-opal-thread-id') return 'test-thread-id';
531
346
  return null;
532
347
  })
533
348
  }
534
349
  };
535
350
 
351
+ // Tool does not require OptiID auth
352
+ (toolsService.requiresOptiIdAuth as jest.Mock).mockReturnValue(false);
536
353
  mockProcessRequest.mockResolvedValue(mockResponse);
537
- const toolFunctionOverrides = new TestToolFunction(overridesRequest);
538
- const result = await toolFunctionOverrides.perform();
539
354
 
540
- expect(result).toBe(mockResponse);
541
- expect(mockProcessRequest).toHaveBeenCalledWith(overridesRequest, toolFunctionOverrides);
542
- });
543
-
544
- it('should handle internal authentication with lowercase authorization header', async () => {
545
- const overridesRequest = {
546
- path: '/overrides',
547
- method: 'PATCH',
548
- bodyJSON: {
549
- functions: [
550
- {
551
- name: 'test_tool',
552
- description: 'Updated description'
553
- }
554
- ]
555
- },
556
- headers: {
557
- get: jest.fn().mockImplementation((name: string) => {
558
- if (name === 'authorization') return 'valid-internal-token';
559
- if (name === 'x-opal-thread-id') return 'test-thread-id';
560
- return null;
561
- })
562
- }
563
- };
564
-
565
- mockProcessRequest.mockResolvedValue(mockResponse);
566
- const toolFunctionOverrides = new TestToolFunction(overridesRequest);
567
- const result = await toolFunctionOverrides.perform();
355
+ const toolFunctionTest = new TestToolFunction(toolRequest);
356
+ const result = await toolFunctionTest.perform();
568
357
 
569
358
  expect(result).toBe(mockResponse);
570
- expect(mockProcessRequest).toHaveBeenCalledWith(overridesRequest, toolFunctionOverrides);
359
+ expect(mockProcessRequest).toHaveBeenCalledWith(toolRequest, toolFunctionTest);
571
360
  });
361
+ });
572
362
 
573
- it('should fail internal authentication when token verification fails', async () => {
574
- // Mock token verifier to return false for invalid token
575
- mockTokenVerifier.verify.mockResolvedValue(false);
363
+ describe('error formatting', () => {
364
+ beforeEach(() => {
365
+ jest.clearAllMocks();
366
+ });
576
367
 
577
- const overridesRequest = {
578
- path: '/overrides',
579
- method: 'DELETE',
580
- bodyJSON: {},
368
+ it('should format ToolError as RFC 9457 response when processRequest throws ToolError', async () => {
369
+ const toolRequest = {
370
+ ...mockRequest,
371
+ path: '/some-tool',
581
372
  headers: {
582
373
  get: jest.fn().mockImplementation((name: string) => {
583
- if (name === 'Authorization') return 'invalid-token';
584
374
  if (name === 'x-opal-thread-id') return 'test-thread-id';
585
375
  return null;
586
376
  })
587
377
  }
588
378
  };
589
379
 
590
- const toolFunctionOverrides = new TestToolFunction(overridesRequest);
591
- const result = await toolFunctionOverrides.perform();
380
+ const toolError = new ToolError('Resource not found', 404, 'The requested resource does not exist');
381
+ mockProcessRequest.mockRejectedValue(toolError);
382
+ (toolsService.requiresOptiIdAuth as jest.Mock).mockReturnValue(false);
592
383
 
593
- expect(result.status).toBe(401);
384
+ const toolFunctionTest = new TestToolFunction(toolRequest);
385
+ const result = await toolFunctionTest.perform();
386
+
387
+ expect(result.status).toBe(404);
594
388
  expect(result.bodyJSON).toEqual({
595
- title: 'Unauthorized',
596
- status: 401,
597
- detail: 'Internal request authentication failed',
598
- instance: '/overrides'
389
+ title: 'Resource not found',
390
+ status: 404,
391
+ detail: 'The requested resource does not exist',
392
+ instance: '/some-tool'
599
393
  });
600
- expect(mockProcessRequest).not.toHaveBeenCalled();
601
394
  });
602
395
 
603
- it('should fail internal authentication when token verifier throws error', async () => {
604
- // Mock token verifier to throw an error
605
- mockTokenVerifier.verify.mockRejectedValue(new Error('Token service unavailable'));
606
-
607
- const overridesRequest = {
608
- path: '/overrides',
609
- method: 'DELETE',
610
- bodyJSON: {},
396
+ it('should format generic Error as 500 RFC 9457 response when processRequest throws', async () => {
397
+ const toolRequest = {
398
+ ...mockRequest,
399
+ path: '/some-tool',
611
400
  headers: {
612
401
  get: jest.fn().mockImplementation((name: string) => {
613
- if (name === 'Authorization') return 'some-token';
614
402
  if (name === 'x-opal-thread-id') return 'test-thread-id';
615
403
  return null;
616
404
  })
617
405
  }
618
406
  };
619
407
 
620
- const toolFunctionOverrides = new TestToolFunction(overridesRequest);
621
- const result = await toolFunctionOverrides.perform();
408
+ mockProcessRequest.mockRejectedValue(new Error('Database connection failed'));
409
+ (toolsService.requiresOptiIdAuth as jest.Mock).mockReturnValue(false);
410
+
411
+ const toolFunctionTest = new TestToolFunction(toolRequest);
412
+ const result = await toolFunctionTest.perform();
622
413
 
623
- expect(result.status).toBe(401);
414
+ expect(result.status).toBe(500);
624
415
  expect(result.bodyJSON).toEqual({
625
- title: 'Unauthorized',
626
- status: 401,
627
- detail: 'Internal request authentication failed',
628
- instance: '/overrides'
416
+ title: 'Internal Server Error',
417
+ status: 500,
418
+ detail: 'Database connection failed',
419
+ instance: '/some-tool'
629
420
  });
630
- expect(mockProcessRequest).not.toHaveBeenCalled();
631
421
  });
632
422
 
633
- it('should fail internal authentication when headers object is missing', async () => {
634
- const overridesRequest = {
635
- path: '/overrides',
636
- method: 'DELETE',
637
- bodyJSON: {},
423
+ it('should format ToolError from authorization failure as RFC 9457 response', async () => {
424
+ const toolRequest = {
425
+ ...mockRequest,
426
+ path: '/some-tool',
638
427
  headers: {
639
428
  get: jest.fn().mockImplementation((name: string) => {
640
429
  if (name === 'x-opal-thread-id') return 'test-thread-id';
@@ -643,15 +432,19 @@ describe('ToolFunction', () => {
643
432
  }
644
433
  };
645
434
 
646
- const toolFunctionOverrides = new TestToolFunction(overridesRequest);
647
- const result = await toolFunctionOverrides.perform();
435
+ const authError = new ToolError('Unauthorized', 403, 'Invalid access token');
436
+ (toolsService.requiresOptiIdAuth as jest.Mock).mockReturnValue(true);
437
+ jest.mocked(authenticateRegularRequest).mockRejectedValue(authError);
438
+
439
+ const toolFunctionTest = new TestToolFunction(toolRequest);
440
+ const result = await toolFunctionTest.perform();
648
441
 
649
- expect(result.status).toBe(401);
442
+ expect(result.status).toBe(403);
650
443
  expect(result.bodyJSON).toEqual({
651
444
  title: 'Unauthorized',
652
- status: 401,
653
- detail: 'Internal request authentication failed',
654
- instance: '/overrides'
445
+ status: 403,
446
+ detail: 'Invalid access token',
447
+ instance: '/some-tool'
655
448
  });
656
449
  expect(mockProcessRequest).not.toHaveBeenCalled();
657
450
  });
@@ -1,9 +1,9 @@
1
- import { Function, Response, Headers, amendLogContext } from '@zaiusinc/app-sdk';
1
+ import { Function, Response, amendLogContext } from '@zaiusinc/app-sdk';
2
2
  import { authenticateRegularRequest, authenticateInternalRequest } from '../auth/AuthUtils';
3
3
  import { toolsService } from '../service/Service';
4
4
  import { ToolLogger } from '../logging/ToolLogger';
5
- import { ToolError } from '../types/ToolError';
6
5
  import { ReadyResponse } from '../types/Models';
6
+ import { formatErrorResponse } from '../utils/ErrorFormatter';
7
7
 
8
8
  /**
9
9
  * Abstract base class for tool-based function execution
@@ -44,29 +44,6 @@ export abstract class ToolFunction extends Function {
44
44
  * @returns Response as the HTTP response
45
45
  */
46
46
  private async handleRequest(): Promise<Response> {
47
- try {
48
- await this.authorizeRequest();
49
- } catch (error) {
50
- if (error instanceof ToolError) {
51
- return new Response(
52
- error.status,
53
- error.toProblemDetails(this.request.path),
54
- new Headers([['content-type', 'application/problem+json']])
55
- );
56
- }
57
- // Fallback for unexpected errors
58
- return new Response(
59
- 500,
60
- {
61
- title: 'Internal Server Error',
62
- status: 500,
63
- detail: 'An unexpected error occurred during authentication',
64
- instance: this.request.path
65
- },
66
- new Headers([['content-type', 'application/problem+json']])
67
- );
68
- }
69
-
70
47
  if (this.request.path === '/ready') {
71
48
  const readyResult = await this.ready();
72
49
  const readyResponse = typeof readyResult === 'boolean'
@@ -75,21 +52,38 @@ export abstract class ToolFunction extends Function {
75
52
  return new Response(200, readyResponse);
76
53
  }
77
54
 
78
- // Pass 'this' as context so decorated methods can use the existing instance
79
- return toolsService.processRequest(this.request, this);
55
+ try {
56
+ await this.authorizeRequest();
57
+
58
+ // Pass 'this' as context so decorated methods can use the existing instance
59
+ return await toolsService.processRequest(this.request, this);
60
+ } catch (error) {
61
+ return formatErrorResponse(error, this.request.path);
62
+ }
80
63
  }
81
64
 
82
65
  /**
83
- * Authenticate the incoming request by validating the OptiID token and organization ID
66
+ * Authenticate the incoming request based on the endpoint
67
+ * - /discovery: No auth required
68
+ * - /overrides: Internal auth (header-based token)
69
+ * - Tools/interactions: Regular auth if OptiID is required
84
70
  *
85
71
  * @throws {ToolError} If authentication fails
86
72
  */
87
73
  private async authorizeRequest(): Promise<void> {
74
+ // Discovery endpoint doesn't require auth
75
+ if (this.request.path === '/discovery') {
76
+ return;
77
+ }
78
+
88
79
  // Use internal authentication for overrides endpoint (header-based token)
89
80
  if (this.request.path === '/overrides') {
90
81
  await authenticateInternalRequest(this.request);
91
- } else {
92
- // Use regular authentication for other endpoints (body-based token with org validation)
82
+ return;
83
+ }
84
+
85
+ // Tool/interaction endpoints - authenticate only if OptiID is required
86
+ if (toolsService.requiresOptiIdAuth(this.request.path)) {
93
87
  await authenticateRegularRequest(this.request);
94
88
  }
95
89
  }