@optimizely-opal/opal-tool-ocp-sdk 0.0.0-dev.5 → 0.0.0-devmg.12

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 (40) hide show
  1. package/README.md +106 -45
  2. package/dist/auth/TokenVerifier.d.ts +31 -0
  3. package/dist/auth/TokenVerifier.d.ts.map +1 -0
  4. package/dist/auth/TokenVerifier.js +128 -0
  5. package/dist/auth/TokenVerifier.js.map +1 -0
  6. package/dist/auth/TokenVerifier.test.d.ts +2 -0
  7. package/dist/auth/TokenVerifier.test.d.ts.map +1 -0
  8. package/dist/auth/TokenVerifier.test.js +116 -0
  9. package/dist/auth/TokenVerifier.test.js.map +1 -0
  10. package/dist/decorator/Decorator.test.js.map +1 -1
  11. package/dist/function/ToolFunction.d.ts +11 -7
  12. package/dist/function/ToolFunction.d.ts.map +1 -1
  13. package/dist/function/ToolFunction.js +53 -10
  14. package/dist/function/ToolFunction.js.map +1 -1
  15. package/dist/function/ToolFunction.test.js +225 -122
  16. package/dist/function/ToolFunction.test.js.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/service/Service.d.ts +14 -13
  22. package/dist/service/Service.d.ts.map +1 -1
  23. package/dist/service/Service.js +22 -16
  24. package/dist/service/Service.js.map +1 -1
  25. package/dist/service/Service.test.js +53 -36
  26. package/dist/service/Service.test.js.map +1 -1
  27. package/dist/types/Models.d.ts +5 -5
  28. package/dist/types/Models.d.ts.map +1 -1
  29. package/dist/types/Models.js +9 -9
  30. package/dist/types/Models.js.map +1 -1
  31. package/package.json +10 -3
  32. package/src/auth/TokenVerifier.test.ts +154 -0
  33. package/src/auth/TokenVerifier.ts +146 -0
  34. package/src/decorator/Decorator.test.ts +4 -4
  35. package/src/function/ToolFunction.test.ts +251 -128
  36. package/src/function/ToolFunction.ts +60 -11
  37. package/src/index.ts +1 -0
  38. package/src/service/Service.test.ts +55 -37
  39. package/src/service/Service.ts +29 -22
  40. package/src/types/Models.ts +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"Models.js","sourceRoot":"","sources":["../../src/types/Models.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Models.js","sourceRoot":"","sources":["../../src/types/Models.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "@optimizely-opal/opal-tool-ocp-sdk",
3
- "version": "0.0.0-dev.5",
3
+ "version": "0.0.0-devmg.12",
4
4
  "description": "OCP SDK for Opal tool",
5
5
  "scripts": {
6
+ "validate-deps": "node scripts/validate-deps.js",
7
+ "prebuild": "yarn run validate-deps",
6
8
  "build": "yarn tsc",
7
9
  "build-watch": "tsc -w",
8
10
  "lint": "npx eslint 'src/**/*.ts'",
@@ -49,10 +51,15 @@
49
51
  "rimraf": "^6.0.1",
50
52
  "ts-jest": "^29.3.2",
51
53
  "tslint": "^6.1.3",
52
- "typescript": "^5.8.2"
54
+ "typescript": "^5.8.2",
55
+ "@zaiusinc/node-sdk": "^2.0.0",
56
+ "@zaiusinc/app-sdk": "^2.2.4-devmg.2"
53
57
  },
54
58
  "dependencies": {
55
- "@zaiusinc/app-sdk": "2.2.2",
59
+ "jose": "^6.1.0",
56
60
  "reflect-metadata": "^0.2.2"
61
+ },
62
+ "peerDependencies": {
63
+ "@zaiusinc/app-sdk": "^2.2.4-devmg.2"
57
64
  }
58
65
  }
@@ -0,0 +1,154 @@
1
+ import { TokenVerifier } from './TokenVerifier';
2
+
3
+ // Test constants
4
+ const TEST_ISSUER = 'https://prep.login.optimizely.com/oauth2/default';
5
+ const TEST_JWKS_URI = 'https://prep.login.optimizely.com/oauth2/v1/keys';
6
+
7
+ // Mock fetch globally
8
+ global.fetch = jest.fn();
9
+
10
+ describe('TokenVerifier', () => {
11
+ beforeEach(() => {
12
+ // Reset fetch mock
13
+ (global.fetch as jest.Mock).mockReset();
14
+
15
+ // Reset singleton instance for each test
16
+ (TokenVerifier as any).instance = null;
17
+ });
18
+
19
+ describe('getInitializedInstance', () => {
20
+ it('should initialize successfully', async () => {
21
+ // Mock fetch for OAuth2 authorization server discovery
22
+ (global.fetch as jest.Mock).mockResolvedValue({
23
+ ok: true,
24
+ json: jest.fn().mockResolvedValue({
25
+ issuer: TEST_ISSUER,
26
+ jwks_uri: TEST_JWKS_URI
27
+ })
28
+ });
29
+
30
+ const tokenVerifier = await TokenVerifier.getInitializedInstance();
31
+ expect(tokenVerifier).toBeInstanceOf(TokenVerifier);
32
+ });
33
+
34
+ it('should throw error when discovery document is invalid', async () => {
35
+ (global.fetch as jest.Mock).mockResolvedValue({
36
+ ok: true,
37
+ json: jest.fn().mockResolvedValue({
38
+ // Missing issuer and jwks_uri
39
+ })
40
+ });
41
+
42
+ await expect(TokenVerifier.getInitializedInstance()).rejects.toThrow(
43
+ 'Invalid discovery document: missing issuer or jwks_uri'
44
+ );
45
+ });
46
+
47
+ it('should throw error when discovery endpoint is unreachable', async () => {
48
+ (global.fetch as jest.Mock).mockResolvedValue({
49
+ ok: false,
50
+ status: 404,
51
+ statusText: 'Not Found'
52
+ });
53
+
54
+ await expect(TokenVerifier.getInitializedInstance()).rejects.toThrow(
55
+ 'Failed to fetch discovery document: 404 Not Found'
56
+ );
57
+ });
58
+ });
59
+
60
+ describe('token verification', () => {
61
+ let tokenVerifier: TokenVerifier;
62
+
63
+ beforeEach(async () => {
64
+ // Mock successful initialization
65
+ (global.fetch as jest.Mock).mockResolvedValue({
66
+ ok: true,
67
+ json: jest.fn().mockResolvedValue({
68
+ issuer: TEST_ISSUER,
69
+ jwks_uri: TEST_JWKS_URI
70
+ })
71
+ });
72
+
73
+ tokenVerifier = await TokenVerifier.getInitializedInstance();
74
+ });
75
+
76
+ it('should throw error for empty token', async () => {
77
+ await expect(tokenVerifier.verify('')).rejects.toThrow(
78
+ 'Token cannot be null or empty'
79
+ );
80
+ });
81
+
82
+ it('should throw error for undefined token', async () => {
83
+ await expect(tokenVerifier.verify(undefined)).rejects.toThrow(
84
+ 'Token cannot be null or empty'
85
+ );
86
+ });
87
+
88
+ it('should throw error for whitespace-only token', async () => {
89
+ await expect(tokenVerifier.verify(' ')).rejects.toThrow(
90
+ 'Token cannot be null or empty'
91
+ );
92
+ });
93
+
94
+ it('should return false for invalid token format', async () => {
95
+ const result = await tokenVerifier.verify('invalid.jwt.token');
96
+ expect(result).toBe(false);
97
+ });
98
+ });
99
+
100
+ describe('singleton pattern', () => {
101
+ it('should return same instance when called multiple times', async () => {
102
+ // Mock successful initialization
103
+ (global.fetch as jest.Mock).mockResolvedValue({
104
+ ok: true,
105
+ json: jest.fn().mockResolvedValue({
106
+ issuer: TEST_ISSUER,
107
+ jwks_uri: TEST_JWKS_URI
108
+ })
109
+ });
110
+
111
+ const instance1 = await TokenVerifier.getInitializedInstance();
112
+ const instance2 = await TokenVerifier.getInitializedInstance();
113
+ expect(instance1).toBe(instance2);
114
+ });
115
+ it('should call correct prod OAuth2 authorization server discovery URL', async () => {
116
+ const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue({
117
+ ok: true,
118
+ json: jest.fn().mockResolvedValue({
119
+ issuer: TEST_ISSUER,
120
+ jwks_uri: TEST_JWKS_URI
121
+ })
122
+ } as unknown as Response);
123
+
124
+ await TokenVerifier.getInitializedInstance();
125
+
126
+ expect(fetchSpy).toHaveBeenCalledWith(
127
+ 'https://login.optimizely.com/oauth2/default/.well-known/oauth-authorization-server'
128
+ );
129
+ });
130
+
131
+ it('should call correct prep OAuth2 authorization server discovery URL', async () => {
132
+ // Set environment variable to staging
133
+ process.env.ENVIRONMENT = 'staging';
134
+
135
+ const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue({
136
+ ok: true,
137
+ json: jest.fn().mockResolvedValue({
138
+ issuer: TEST_ISSUER,
139
+ jwks_uri: TEST_JWKS_URI
140
+ })
141
+ } as unknown as Response);
142
+
143
+ await TokenVerifier.getInitializedInstance();
144
+
145
+ expect(fetchSpy).toHaveBeenCalledWith(
146
+ 'https://prep.login.optimizely.com/oauth2/default/.well-known/oauth-authorization-server'
147
+ );
148
+
149
+ // Clean up environment variable
150
+ delete process.env.ENVIRONMENT;
151
+ });
152
+ });
153
+
154
+ });
@@ -0,0 +1,146 @@
1
+ import { jwtVerify, createRemoteJWKSet } from 'jose';
2
+ import { logger } from '@zaiusinc/app-sdk';
3
+
4
+ /**
5
+ * Default JWKS cache expiration time in milliseconds (1 hour)
6
+ */
7
+ const DEFAULT_JWKS_EXPIRES_IN = 60 * 60 * 1000;
8
+
9
+ /**
10
+ * Default clock skew tolerance in seconds
11
+ */
12
+ const DEFAULT_LEEWAY = 30;
13
+
14
+ /**
15
+ * Expected JWT audience for token validation
16
+ */
17
+ const AUDIENCE = 'api://default';
18
+
19
+ /**
20
+ * Prep Base URL for Optimizely OAuth2 endpoints
21
+ */
22
+ const PREP_BASE_URL = 'https://prep.login.optimizely.com/oauth2/default';
23
+
24
+ /**
25
+ * Prod Base URL for Optimizely OAuth2 endpoints
26
+ */
27
+ const PROD_BASE_URL = 'https://login.optimizely.com/oauth2/default';
28
+
29
+ interface DiscoveryDocument {
30
+ issuer: string;
31
+ jwks_uri: string;
32
+ [key: string]: any;
33
+ }
34
+
35
+ export class TokenVerifier {
36
+ private static instance: TokenVerifier | null = null;
37
+ private jwksUri?: string;
38
+ private issuer?: string;
39
+ private jwks?: ReturnType<typeof createRemoteJWKSet>;
40
+ private initialized: boolean = false;
41
+
42
+ /**
43
+ * Verify the provided Optimizely JWT token string
44
+ * @param token JWT token string to verify
45
+ * @returns boolean true if verification successful, false otherwise
46
+ * @throws Error if token is null, empty, or verifier is not properly configured
47
+ */
48
+ public async verify(token: string | undefined): Promise<boolean> {
49
+ if (!token || token.trim().length === 0) {
50
+ throw new Error('Token cannot be null or empty');
51
+ }
52
+
53
+ return this.verifyToken(token);
54
+ }
55
+
56
+ private static getInstance(): TokenVerifier {
57
+ if (!TokenVerifier.instance) {
58
+ TokenVerifier.instance = new TokenVerifier();
59
+ }
60
+ return TokenVerifier.instance;
61
+ }
62
+
63
+ /**
64
+ * Get singleton instance of TokenVerifier and ensure it's initialized
65
+ * @returns Promise<TokenVerifier> - initialized singleton instance
66
+ */
67
+ public static async getInitializedInstance(): Promise<TokenVerifier> {
68
+ const instance = TokenVerifier.getInstance();
69
+ if (!instance.initialized) {
70
+ await instance.initialize();
71
+ }
72
+ return instance;
73
+ }
74
+
75
+ /**
76
+ * Initialize the TokenVerifier with discovery document from well-known endpoint
77
+ */
78
+ private async initialize(): Promise<void> {
79
+ if (this.initialized) {
80
+ return;
81
+ }
82
+
83
+ try {
84
+ // Use prep URL when environment variable is set to 'staging', otherwise use prod
85
+ const environment = process.env.ENVIRONMENT || 'production';
86
+ const baseUrl = environment === 'staging' ? PREP_BASE_URL : PROD_BASE_URL;
87
+ const discoveryDocument = await this.fetchDiscoveryDocument(baseUrl);
88
+ this.issuer = discoveryDocument.issuer;
89
+ this.jwksUri = discoveryDocument.jwks_uri;
90
+ this.jwks = createRemoteJWKSet(new URL(this.jwksUri), {
91
+ cacheMaxAge: DEFAULT_JWKS_EXPIRES_IN,
92
+ cooldownDuration: DEFAULT_JWKS_EXPIRES_IN
93
+ });
94
+ this.initialized = true;
95
+ logger.info('TokenVerifier environment ' + environment);
96
+ logger.info(`TokenVerifier initialized with issuer: ${this.issuer} (environment: ${environment})`);
97
+ } catch (error) {
98
+ logger.error('Failed to initialize TokenVerifier', error);
99
+ // Re-throw the original error to preserve specific error messages for tests
100
+ throw error;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Fetch discovery document from well-known endpoint
106
+ */
107
+ private async fetchDiscoveryDocument(baseUrl: string): Promise<DiscoveryDocument> {
108
+ const wellKnownUrl = `${baseUrl}/.well-known/oauth-authorization-server`;
109
+
110
+ const response = await fetch(wellKnownUrl);
111
+ if (!response.ok) {
112
+ throw new Error(`Failed to fetch discovery document: ${response.status} ${response.statusText}`);
113
+ }
114
+ const discoveryDocument = await response.json() as DiscoveryDocument;
115
+ if (!discoveryDocument.issuer || !discoveryDocument.jwks_uri) {
116
+ throw new Error('Invalid discovery document: missing issuer or jwks_uri');
117
+ }
118
+
119
+ return discoveryDocument;
120
+ }
121
+
122
+ private async verifyToken(token: string): Promise<boolean> {
123
+ if (!this.initialized) {
124
+ throw new Error('TokenVerifier not initialized. Call initialize() first.');
125
+ }
126
+
127
+ if (!this.jwks || !this.issuer) {
128
+ throw new Error('TokenVerifier not properly configured.');
129
+ }
130
+
131
+ try {
132
+ await jwtVerify(token, this.jwks, {
133
+ issuer: this.issuer,
134
+ audience: AUDIENCE,
135
+ clockTolerance: DEFAULT_LEEWAY,
136
+ });
137
+ return true;
138
+ } catch (error) {
139
+ logger.error('Token verification failed:', error);
140
+ return false;
141
+ }
142
+ }
143
+
144
+ }
145
+
146
+ export const getTokenVerifier = async (): Promise<TokenVerifier> => TokenVerifier.getInitializedInstance();
@@ -544,7 +544,7 @@ describe('Decorators', () => {
544
544
  const registeredHandler = registerToolCall![2];
545
545
 
546
546
  // Call the handler
547
- const result = await registeredHandler(undefined, {}, {});
547
+ const result = await registeredHandler(undefined as any, {}, {});
548
548
 
549
549
  // Should be able to call class methods through 'this'
550
550
  expect((result as any).result).toBe('class-method-called');
@@ -574,7 +574,7 @@ describe('Decorators', () => {
574
574
  const registeredHandler = registerInteractionCall![1];
575
575
 
576
576
  // Call the handler
577
- const result = await registeredHandler(undefined, {});
577
+ const result = await registeredHandler(undefined as any, {});
578
578
 
579
579
  // Should be able to call class methods through 'this'
580
580
  expect((result as any).message).toBe('interaction-processed');
@@ -606,7 +606,7 @@ describe('Decorators', () => {
606
606
  // Call the handler with an invalid functionContext (truthy but wrong type)
607
607
  // This should trigger the instanceof check and create a new instance instead
608
608
  const invalidContext = { notAnInstance: true, someOtherProperty: 'invalid' };
609
- const result = await registeredHandler(invalidContext, {}, {});
609
+ const result = await registeredHandler(invalidContext as any, {}, {});
610
610
 
611
611
  // Should still work by creating a new instance
612
612
  expect((result as any).result).toBe('class-method-called');
@@ -638,7 +638,7 @@ describe('Decorators', () => {
638
638
  // Call the handler with an invalid functionContext (truthy but wrong type)
639
639
  // This should trigger the instanceof check and create a new instance instead
640
640
  const invalidContext = { notAnInstance: true, someOtherProperty: 'invalid' };
641
- const result = await registeredHandler(invalidContext, {});
641
+ const result = await registeredHandler(invalidContext as any, {});
642
642
 
643
643
  // Should still work by creating a new instance
644
644
  expect((result as any).message).toBe('interaction-processed');