@optimizely-opal/opal-tool-ocp-sdk 0.0.0-beta.7 โ†’ 0.0.0-beta.9

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 +9 -43
  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 +127 -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 +114 -0
  9. package/dist/auth/TokenVerifier.test.js.map +1 -0
  10. package/dist/function/ToolFunction.d.ts +4 -7
  11. package/dist/function/ToolFunction.d.ts.map +1 -1
  12. package/dist/function/ToolFunction.js +38 -10
  13. package/dist/function/ToolFunction.js.map +1 -1
  14. package/dist/function/ToolFunction.test.js +177 -196
  15. package/dist/function/ToolFunction.test.js.map +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/service/Service.d.ts +7 -7
  21. package/dist/service/Service.d.ts.map +1 -1
  22. package/dist/service/Service.js +22 -16
  23. package/dist/service/Service.js.map +1 -1
  24. package/dist/service/Service.test.js +8 -3
  25. package/dist/service/Service.test.js.map +1 -1
  26. package/dist/types/Models.d.ts +5 -5
  27. package/dist/types/Models.d.ts.map +1 -1
  28. package/dist/types/Models.js +9 -9
  29. package/dist/types/Models.js.map +1 -1
  30. package/package.json +5 -3
  31. package/src/auth/TokenVerifier.test.ts +152 -0
  32. package/src/auth/TokenVerifier.ts +145 -0
  33. package/src/function/ToolFunction.test.ts +194 -214
  34. package/src/function/ToolFunction.ts +45 -11
  35. package/src/index.ts +1 -0
  36. package/src/service/Service.test.ts +8 -3
  37. package/src/service/Service.ts +22 -17
  38. package/src/types/Models.ts +4 -4
package/README.md CHANGED
@@ -10,7 +10,7 @@ A TypeScript SDK for building Opal tools in Optimizely Connect Platform. This SD
10
10
  - ๐Ÿ”ง **Type-safe Development** - Full TypeScript support with comprehensive type definitions
11
11
  - ๐Ÿ—๏ธ **Abstract Base Classes** - Extend `ToolFunction` for standardized request processing
12
12
  - ๐Ÿ” **Authentication Support** - OptiID authentication
13
- - ๐Ÿ›ก๏ธ **Authorization Support** - Bearer token tool authorization
13
+ - ๐Ÿ›ก๏ธ **Authorization Support** - OptiID token tool authorization
14
14
  - ๐Ÿ“ **Parameter Validation** - Define and validate tool parameters with types
15
15
  - ๐Ÿงช **Comprehensive Testing** - Fully tested with Jest
16
16
 
@@ -115,7 +115,7 @@ export class MyToolFunction extends ToolFunction {
115
115
  Your function class inherits a `perform()` method from `ToolFunction` that serves as the main entry point for handling all incoming requests. When called, the SDK automatically:
116
116
 
117
117
  - **Routes requests** to your registered tools and interactions based on endpoints
118
- - **Handles authentication** and bearer token validation before calling your methods
118
+ - **Handles authentication** and OptiID token validation before calling your methods
119
119
  - **Provides discovery** at `/discovery` endpoint for OCP platform integration
120
120
  - **Returns proper HTTP responses** with correct status codes and JSON formatting
121
121
 
@@ -169,29 +169,18 @@ interface AuthRequirementConfig {
169
169
  }
170
170
  ```
171
171
 
172
- #### Bearer Token Support
172
+ #### OptiID Token Authorization
173
173
 
174
- The SDK automatically extracts Bearer tokens from the `authorization` header for tool authorization. When users register tools in Opal, they can specify a Bearer token that Opal will include in requests to validate the tool's identity.
174
+ The SDK automatically handles OptiID token validation for tool authorization. OptiID tokens provide both user authentication and authorization for tools, ensuring that only authenticated users with proper permissions can access your tools.
175
175
 
176
176
  **Token Validation:**
177
- - Bearer tokens are extracted from the `authorization: Bearer <token>` header
178
- - Empty strings and `undefined` tokens are treated as missing (no validation performed)
179
- - **Optionally override** the `validateBearerToken(token: string): boolean` method to implement custom validation logic
180
- - The default implementation returns `true` (accepts all tokens) - override if you need custom authorization
181
- - If validation fails, returns HTTP 403 Forbidden before reaching your handler methods
177
+ - The SDK extracts and validates OptiID tokens from the request body
178
+ - Validation includes verifying that requests come from the same organization
179
+ - If validation fails, returns HTTP 403 Unauthorized before reaching your handler methods
180
+ - No additional configuration needed - validation is handled automatically
182
181
 
183
182
  ```typescript
184
183
  export class MyToolFunction extends ToolFunction {
185
- // Optional: Override this method to implement custom bearer token validation
186
- protected override validateBearerToken(token: string): boolean {
187
- // If you don't need bearer token validation, don't override this method
188
- // The default implementation returns true
189
-
190
- // For custom validation, compare against expected token
191
- const expectedToken = process.env.OPAL_TOOL_TOKEN;
192
- return token === expectedToken;
193
- }
194
-
195
184
  @tool({
196
185
  name: 'secure_tool',
197
186
  description: 'Tool that validates requests from Opal',
@@ -215,15 +204,6 @@ export class MyToolFunction extends ToolFunction {
215
204
  }
216
205
  ```
217
206
 
218
- **Bearer Token Usage:**
219
- - Set by users when registering tools in Opal
220
- - Used to authorize tool requests from Opal instances
221
- - Validates that requests are coming from trusted Opal sources
222
- - **Optionally override** the `validateBearerToken(token: string): boolean` method for custom authorization
223
- - Default implementation accepts all tokens - override for security
224
- - If validation fails, returns HTTP 403 Forbidden before reaching your handler methods
225
-
226
-
227
207
  ## API Reference
228
208
 
229
209
  ### Handler Function Signatures
@@ -240,8 +220,6 @@ async handlerMethod(
240
220
  - **params**: The input parameters for tools, or interaction data for webhooks
241
221
  - **authData**: Available when OptiID user authentication is configured and successful
242
222
 
243
- **Note**: Bearer token validation is handled automatically by the base class. If a bearer token is present and fails validation, the request is rejected before reaching your handler methods.
244
-
245
223
  ### Decorators
246
224
 
247
225
  #### `@tool(config: ToolConfig)`
@@ -279,11 +257,10 @@ Abstract base class for OCP functions:
279
257
  export abstract class ToolFunction extends Function {
280
258
  protected ready(): Promise<boolean>;
281
259
  public async perform(): Promise<Response>;
282
- protected validateBearerToken(token: string): boolean;
283
260
  }
284
261
  ```
285
262
 
286
- Extend this class and implement your OCP function. The `perform` method automatically routes requests to registered tools and handles bearer token validation. **You can optionally override the `validateBearerToken` method** to define custom bearer token authorization for your tools.
263
+ Extend this class and implement your OCP function. The `perform` method automatically routes requests to registered tools.
287
264
 
288
265
  ### Models
289
266
 
@@ -407,11 +384,6 @@ yarn lint
407
384
  import { ToolFunction, tool, interaction, ParameterType, OptiIdAuthData, InteractionResult } from '@optimizely-opal/opal-ocp-sdk';
408
385
 
409
386
  export class AuthenticatedFunction extends ToolFunction {
410
- // Optional: Override this method for custom bearer token validation
411
- protected override validateBearerToken(token: string): boolean {
412
- const expectedToken = process.env.OPAL_TOOL_TOKEN;
413
- return token === expectedToken;
414
- }
415
387
 
416
388
  // OptiID authentication example
417
389
  @tool({
@@ -573,12 +545,6 @@ import { ToolFunction } from '@optimizely-opal/opal-ocp-sdk';
573
545
  import * from './tools';
574
546
 
575
547
  export class MyToolFunction extends ToolFunction {
576
-
577
- // Optional: Override this method for custom bearer token validation
578
- protected override validateBearerToken(token: string): boolean {
579
- const expectedToken = process.env.OPAL_TOOL_TOKEN;
580
- return token === expectedToken;
581
- }
582
548
  }
583
549
  ```
584
550
 
@@ -0,0 +1,31 @@
1
+ export declare class TokenVerifier {
2
+ private static instance;
3
+ private jwksUri?;
4
+ private issuer?;
5
+ private jwks?;
6
+ private initialized;
7
+ /**
8
+ * Verify the provided Optimizely JWT token string
9
+ * @param token JWT token string to verify
10
+ * @returns boolean true if verification successful, false otherwise
11
+ * @throws Error if token is null, empty, or verifier is not properly configured
12
+ */
13
+ verify(token: string | undefined): Promise<boolean>;
14
+ private static getInstance;
15
+ /**
16
+ * Get singleton instance of TokenVerifier and ensure it's initialized
17
+ * @returns Promise<TokenVerifier> - initialized singleton instance
18
+ */
19
+ static getInitializedInstance(): Promise<TokenVerifier>;
20
+ /**
21
+ * Initialize the TokenVerifier with discovery document from well-known endpoint
22
+ */
23
+ private initialize;
24
+ /**
25
+ * Fetch discovery document from well-known endpoint
26
+ */
27
+ private fetchDiscoveryDocument;
28
+ private verifyToken;
29
+ }
30
+ export declare const getTokenVerifier: () => Promise<TokenVerifier>;
31
+ //# sourceMappingURL=TokenVerifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenVerifier.d.ts","sourceRoot":"","sources":["../../src/auth/TokenVerifier.ts"],"names":[],"mappings":"AAkCA,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA8B;IACrD,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,IAAI,CAAC,CAAwC;IACrD,OAAO,CAAC,WAAW,CAAkB;IAErC;;;;;OAKG;IACU,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAQhE,OAAO,CAAC,MAAM,CAAC,WAAW;IAO1B;;;OAGG;WACiB,sBAAsB,IAAI,OAAO,CAAC,aAAa,CAAC;IAQpE;;OAEG;YACW,UAAU;IAyBxB;;OAEG;YACW,sBAAsB;YAetB,WAAW;CAsB1B;AAED,eAAO,MAAM,gBAAgB,QAAa,OAAO,CAAC,aAAa,CAA2C,CAAC"}
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTokenVerifier = exports.TokenVerifier = void 0;
4
+ const jose_1 = require("jose");
5
+ const app_sdk_1 = require("@zaiusinc/app-sdk");
6
+ /**
7
+ * Default JWKS cache expiration time in milliseconds (1 hour)
8
+ */
9
+ const DEFAULT_JWKS_EXPIRES_IN = 60 * 60 * 1000;
10
+ /**
11
+ * Default clock skew tolerance in seconds
12
+ */
13
+ const DEFAULT_LEEWAY = 30;
14
+ /**
15
+ * Expected JWT audience for token validation
16
+ */
17
+ const AUDIENCE = 'api://default';
18
+ /**
19
+ * Prep Base URL for Optimizely OAuth2 endpoints
20
+ */
21
+ const PREP_BASE_URL = 'https://prep.login.optimizely.com/oauth2/default';
22
+ /**
23
+ * Prod Base URL for Optimizely OAuth2 endpoints
24
+ */
25
+ const PROD_BASE_URL = 'https://login.optimizely.com/oauth2/default';
26
+ class TokenVerifier {
27
+ static instance = null;
28
+ jwksUri;
29
+ issuer;
30
+ jwks;
31
+ initialized = false;
32
+ /**
33
+ * Verify the provided Optimizely JWT token string
34
+ * @param token JWT token string to verify
35
+ * @returns boolean true if verification successful, false otherwise
36
+ * @throws Error if token is null, empty, or verifier is not properly configured
37
+ */
38
+ async verify(token) {
39
+ if (!token || token.trim().length === 0) {
40
+ throw new Error('Token cannot be null or empty');
41
+ }
42
+ return this.verifyToken(token);
43
+ }
44
+ static getInstance() {
45
+ if (!TokenVerifier.instance) {
46
+ TokenVerifier.instance = new TokenVerifier();
47
+ }
48
+ return TokenVerifier.instance;
49
+ }
50
+ /**
51
+ * Get singleton instance of TokenVerifier and ensure it's initialized
52
+ * @returns Promise<TokenVerifier> - initialized singleton instance
53
+ */
54
+ static async getInitializedInstance() {
55
+ const instance = TokenVerifier.getInstance();
56
+ if (!instance.initialized) {
57
+ await instance.initialize();
58
+ }
59
+ return instance;
60
+ }
61
+ /**
62
+ * Initialize the TokenVerifier with discovery document from well-known endpoint
63
+ */
64
+ async initialize() {
65
+ if (this.initialized) {
66
+ return;
67
+ }
68
+ try {
69
+ // Use prep URL when environment variable is set to 'staging', otherwise use prod
70
+ const environment = process.env.environment || 'production';
71
+ const baseUrl = environment === 'staging' ? PREP_BASE_URL : PROD_BASE_URL;
72
+ const discoveryDocument = await this.fetchDiscoveryDocument(baseUrl);
73
+ this.issuer = discoveryDocument.issuer;
74
+ this.jwksUri = discoveryDocument.jwks_uri;
75
+ this.jwks = (0, jose_1.createRemoteJWKSet)(new URL(this.jwksUri), {
76
+ cacheMaxAge: DEFAULT_JWKS_EXPIRES_IN,
77
+ cooldownDuration: DEFAULT_JWKS_EXPIRES_IN
78
+ });
79
+ this.initialized = true;
80
+ app_sdk_1.logger.info(`TokenVerifier initialized with issuer: ${this.issuer} (environment: ${environment})`);
81
+ }
82
+ catch (error) {
83
+ app_sdk_1.logger.error('Failed to initialize TokenVerifier', error);
84
+ // Re-throw the original error to preserve specific error messages for tests
85
+ throw error;
86
+ }
87
+ }
88
+ /**
89
+ * Fetch discovery document from well-known endpoint
90
+ */
91
+ async fetchDiscoveryDocument(baseUrl) {
92
+ const wellKnownUrl = `${baseUrl}/.well-known/oauth-authorization-server`;
93
+ const response = await fetch(wellKnownUrl);
94
+ if (!response.ok) {
95
+ throw new Error(`Failed to fetch discovery document: ${response.status} ${response.statusText}`);
96
+ }
97
+ const discoveryDocument = await response.json();
98
+ if (!discoveryDocument.issuer || !discoveryDocument.jwks_uri) {
99
+ throw new Error('Invalid discovery document: missing issuer or jwks_uri');
100
+ }
101
+ return discoveryDocument;
102
+ }
103
+ async verifyToken(token) {
104
+ if (!this.initialized) {
105
+ throw new Error('TokenVerifier not initialized. Call initialize() first.');
106
+ }
107
+ if (!this.jwks || !this.issuer) {
108
+ throw new Error('TokenVerifier not properly configured.');
109
+ }
110
+ try {
111
+ await (0, jose_1.jwtVerify)(token, this.jwks, {
112
+ issuer: this.issuer,
113
+ audience: AUDIENCE,
114
+ clockTolerance: DEFAULT_LEEWAY,
115
+ });
116
+ return true;
117
+ }
118
+ catch (error) {
119
+ app_sdk_1.logger.error('Token verification failed:', error);
120
+ return false;
121
+ }
122
+ }
123
+ }
124
+ exports.TokenVerifier = TokenVerifier;
125
+ const getTokenVerifier = async () => TokenVerifier.getInitializedInstance();
126
+ exports.getTokenVerifier = getTokenVerifier;
127
+ //# sourceMappingURL=TokenVerifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenVerifier.js","sourceRoot":"","sources":["../../src/auth/TokenVerifier.ts"],"names":[],"mappings":";;;AAAA,+BAAqD;AACrD,+CAA2C;AAE3C;;GAEG;AACH,MAAM,uBAAuB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE/C;;GAEG;AACH,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B;;GAEG;AACH,MAAM,QAAQ,GAAG,eAAe,CAAC;AAEjC;;GAEG;AACH,MAAM,aAAa,GAAG,kDAAkD,CAAC;AAEzE;;GAEG;AACH,MAAM,aAAa,GAAG,6CAA6C,CAAC;AAQpE,MAAa,aAAa;IAChB,MAAM,CAAC,QAAQ,GAAyB,IAAI,CAAC;IAC7C,OAAO,CAAU;IACjB,MAAM,CAAU;IAChB,IAAI,CAAyC;IAC7C,WAAW,GAAY,KAAK,CAAC;IAErC;;;;;OAKG;IACI,KAAK,CAAC,MAAM,CAAC,KAAyB;QAC3C,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACnD,CAAC;QAED,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAEO,MAAM,CAAC,WAAW;QACxB,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC;YAC5B,aAAa,CAAC,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAC;QAC/C,CAAC;QACD,OAAO,aAAa,CAAC,QAAQ,CAAC;IAChC,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,KAAK,CAAC,sBAAsB;QACxC,MAAM,QAAQ,GAAG,aAAa,CAAC,WAAW,EAAE,CAAC;QAC7C,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC1B,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAC;QAC9B,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,UAAU;QACtB,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,iFAAiF;YACjF,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,YAAY,CAAC;YAC5D,MAAM,OAAO,GAAG,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC;YAC1E,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;YACrE,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC;YACvC,IAAI,CAAC,OAAO,GAAG,iBAAiB,CAAC,QAAQ,CAAC;YAC1C,IAAI,CAAC,IAAI,GAAG,IAAA,yBAAkB,EAAC,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;gBACpD,WAAW,EAAE,uBAAuB;gBACpC,gBAAgB,EAAE,uBAAuB;aAC1C,CAAC,CAAC;YACH,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,gBAAM,CAAC,IAAI,CAAC,0CAA0C,IAAI,CAAC,MAAM,kBAAkB,WAAW,GAAG,CAAC,CAAC;QACrG,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,gBAAM,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;YAC1D,4EAA4E;YAC5E,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,sBAAsB,CAAC,OAAe;QAClD,MAAM,YAAY,GAAG,GAAG,OAAO,yCAAyC,CAAC;QAEzE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,CAAC;QAC3C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,uCAAuC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACnG,CAAC;QACD,MAAM,iBAAiB,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAuB,CAAC;QACrE,IAAI,CAAC,iBAAiB,CAAC,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC;YAC7D,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QAED,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,KAAa;QACrC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC7E,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAA,gBAAS,EAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE;gBAChC,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,QAAQ,EAAE,QAAQ;gBAClB,cAAc,EAAE,cAAc;aAC/B,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,gBAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;YAClD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;;AA1GH,sCA4GC;AAEM,MAAM,gBAAgB,GAAG,KAAK,IAA4B,EAAE,CAAC,aAAa,CAAC,sBAAsB,EAAE,CAAC;AAA9F,QAAA,gBAAgB,oBAA8E"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=TokenVerifier.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenVerifier.test.d.ts","sourceRoot":"","sources":["../../src/auth/TokenVerifier.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const TokenVerifier_1 = require("./TokenVerifier");
4
+ // Test constants
5
+ const TEST_ISSUER = 'https://prep.login.optimizely.com/oauth2/default';
6
+ const TEST_JWKS_URI = 'https://prep.login.optimizely.com/oauth2/v1/keys';
7
+ // Mock fetch globally
8
+ global.fetch = jest.fn();
9
+ describe('TokenVerifier', () => {
10
+ beforeEach(() => {
11
+ // Reset fetch mock
12
+ global.fetch.mockReset();
13
+ // Reset singleton instance for each test
14
+ TokenVerifier_1.TokenVerifier.instance = null;
15
+ });
16
+ describe('getInitializedInstance', () => {
17
+ it('should initialize successfully', async () => {
18
+ // Mock fetch for OAuth2 authorization server discovery
19
+ global.fetch.mockResolvedValue({
20
+ ok: true,
21
+ json: jest.fn().mockResolvedValue({
22
+ issuer: TEST_ISSUER,
23
+ jwks_uri: TEST_JWKS_URI
24
+ })
25
+ });
26
+ const tokenVerifier = await TokenVerifier_1.TokenVerifier.getInitializedInstance();
27
+ expect(tokenVerifier).toBeInstanceOf(TokenVerifier_1.TokenVerifier);
28
+ });
29
+ it('should throw error when discovery document is invalid', async () => {
30
+ global.fetch.mockResolvedValue({
31
+ ok: true,
32
+ json: jest.fn().mockResolvedValue({
33
+ // Missing issuer and jwks_uri
34
+ })
35
+ });
36
+ await expect(TokenVerifier_1.TokenVerifier.getInitializedInstance()).rejects.toThrow('Invalid discovery document: missing issuer or jwks_uri');
37
+ });
38
+ it('should throw error when discovery endpoint is unreachable', async () => {
39
+ global.fetch.mockResolvedValue({
40
+ ok: false,
41
+ status: 404,
42
+ statusText: 'Not Found'
43
+ });
44
+ await expect(TokenVerifier_1.TokenVerifier.getInitializedInstance()).rejects.toThrow('Failed to fetch discovery document: 404 Not Found');
45
+ });
46
+ });
47
+ describe('token verification', () => {
48
+ let tokenVerifier;
49
+ beforeEach(async () => {
50
+ // Mock successful initialization
51
+ global.fetch.mockResolvedValue({
52
+ ok: true,
53
+ json: jest.fn().mockResolvedValue({
54
+ issuer: TEST_ISSUER,
55
+ jwks_uri: TEST_JWKS_URI
56
+ })
57
+ });
58
+ tokenVerifier = await TokenVerifier_1.TokenVerifier.getInitializedInstance();
59
+ });
60
+ it('should throw error for empty token', async () => {
61
+ await expect(tokenVerifier.verify('')).rejects.toThrow('Token cannot be null or empty');
62
+ });
63
+ it('should throw error for undefined token', async () => {
64
+ await expect(tokenVerifier.verify(undefined)).rejects.toThrow('Token cannot be null or empty');
65
+ });
66
+ it('should throw error for whitespace-only token', async () => {
67
+ await expect(tokenVerifier.verify(' ')).rejects.toThrow('Token cannot be null or empty');
68
+ });
69
+ it('should return false for invalid token format', async () => {
70
+ const result = await tokenVerifier.verify('invalid.jwt.token');
71
+ expect(result).toBe(false);
72
+ });
73
+ });
74
+ describe('singleton pattern', () => {
75
+ it('should return same instance when called multiple times', async () => {
76
+ // Mock successful initialization
77
+ global.fetch.mockResolvedValue({
78
+ ok: true,
79
+ json: jest.fn().mockResolvedValue({
80
+ issuer: TEST_ISSUER,
81
+ jwks_uri: TEST_JWKS_URI
82
+ })
83
+ });
84
+ const instance1 = await TokenVerifier_1.TokenVerifier.getInitializedInstance();
85
+ const instance2 = await TokenVerifier_1.TokenVerifier.getInitializedInstance();
86
+ expect(instance1).toBe(instance2);
87
+ });
88
+ it('should call correct prod OAuth2 authorization server discovery URL', async () => {
89
+ const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue({
90
+ ok: true,
91
+ json: jest.fn().mockResolvedValue({
92
+ issuer: TEST_ISSUER,
93
+ jwks_uri: TEST_JWKS_URI
94
+ })
95
+ });
96
+ await TokenVerifier_1.TokenVerifier.getInitializedInstance();
97
+ expect(fetchSpy).toHaveBeenCalledWith('https://login.optimizely.com/oauth2/default/.well-known/oauth-authorization-server');
98
+ });
99
+ it('should call correct prep OAuth2 authorization server discovery URL', async () => {
100
+ // Set environment variable to staging
101
+ process.env.environment = 'staging';
102
+ const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue({
103
+ ok: true,
104
+ json: jest.fn().mockResolvedValue({
105
+ issuer: TEST_ISSUER,
106
+ jwks_uri: TEST_JWKS_URI
107
+ })
108
+ });
109
+ await TokenVerifier_1.TokenVerifier.getInitializedInstance();
110
+ expect(fetchSpy).toHaveBeenCalledWith('https://prep.login.optimizely.com/oauth2/default/.well-known/oauth-authorization-server');
111
+ });
112
+ });
113
+ });
114
+ //# sourceMappingURL=TokenVerifier.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenVerifier.test.js","sourceRoot":"","sources":["../../src/auth/TokenVerifier.test.ts"],"names":[],"mappings":"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"}
@@ -18,14 +18,11 @@ export declare abstract class ToolFunction extends Function {
18
18
  */
19
19
  perform(): Promise<Response>;
20
20
  /**
21
- * Validates the bearer token for authorization.
21
+ * Authenticate the incoming request by validating the OptiID token and organization ID
22
22
  *
23
- * This method provides a default implementation that accepts all tokens.
24
- * Subclasses can override this method to implement custom bearer token validation logic.
25
- *
26
- * @param _bearerToken - The bearer token extracted from the Authorization header
27
- * @returns true if the token is valid and the request should proceed, false to return 403 Forbidden
23
+ * @throws true if authentication succeeds
28
24
  */
29
- protected validateBearerToken(_bearerToken: string): boolean;
25
+ private authorizeRequest;
26
+ private validateAccessToken;
30
27
  }
31
28
  //# sourceMappingURL=ToolFunction.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ToolFunction.d.ts","sourceRoot":"","sources":["../../src/function/ToolFunction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAmB,MAAM,mBAAmB,CAAC;AAGxE;;;GAGG;AACH,8BAAsB,YAAa,SAAQ,QAAQ;IAEjD;;;;;OAKG;IACH,SAAS,CAAC,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAInC;;;;OAIG;IACU,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC;IAezC;;;;;;;;OAQG;IACH,SAAS,CAAC,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;CAG7D"}
1
+ {"version":3,"file":"ToolFunction.d.ts","sourceRoot":"","sources":["../../src/function/ToolFunction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAA0C,MAAM,mBAAmB,CAAC;AAK/F;;;GAGG;AACH,8BAAsB,YAAa,SAAQ,QAAQ;IAEjD;;;;;OAKG;IACH,SAAS,CAAC,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAInC;;;;OAIG;IACU,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC;IAczC;;;;OAIG;YACW,gBAAgB;YA2BhB,mBAAmB;CAalC"}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ToolFunction = void 0;
4
4
  const app_sdk_1 = require("@zaiusinc/app-sdk");
5
5
  const Service_1 = require("../service/Service");
6
+ const TokenVerifier_1 = require("../auth/TokenVerifier");
6
7
  /**
7
8
  * Abstract base class for tool-based function execution
8
9
  * Provides a standard interface for processing requests through registered tools
@@ -24,8 +25,7 @@ class ToolFunction extends app_sdk_1.Function {
24
25
  */
25
26
  async perform() {
26
27
  (0, app_sdk_1.amendLogContext)({ opalThreadId: this.request.headers.get('x-opal-thread-id') || '' });
27
- const bearerToken = Service_1.toolsService.extractBearerToken(this.request.headers);
28
- if (bearerToken && !this.validateBearerToken(bearerToken)) {
28
+ if (!(await this.authorizeRequest())) {
29
29
  return new app_sdk_1.Response(403, { error: 'Forbidden' });
30
30
  }
31
31
  if (this.request.path === '/ready') {
@@ -36,16 +36,44 @@ class ToolFunction extends app_sdk_1.Function {
36
36
  return Service_1.toolsService.processRequest(this.request, this);
37
37
  }
38
38
  /**
39
- * Validates the bearer token for authorization.
39
+ * Authenticate the incoming request by validating the OptiID token and organization ID
40
40
  *
41
- * This method provides a default implementation that accepts all tokens.
42
- * Subclasses can override this method to implement custom bearer token validation logic.
43
- *
44
- * @param _bearerToken - The bearer token extracted from the Authorization header
45
- * @returns true if the token is valid and the request should proceed, false to return 403 Forbidden
41
+ * @throws true if authentication succeeds
46
42
  */
47
- validateBearerToken(_bearerToken) {
48
- return true;
43
+ async authorizeRequest() {
44
+ if (this.request.path === '/discovery' || this.request.path === '/ready') {
45
+ return true;
46
+ }
47
+ const authData = this.request.bodyJSON?.auth;
48
+ const accessToken = authData?.credentials?.access_token;
49
+ if (!accessToken || authData?.provider?.toLowerCase() !== 'optiid') {
50
+ app_sdk_1.logger.error('OptiID token is required but not provided');
51
+ return false;
52
+ }
53
+ const customerId = authData.credentials?.customer_id;
54
+ if (!customerId) {
55
+ app_sdk_1.logger.error('Organisation ID is required but not provided');
56
+ return false;
57
+ }
58
+ const appOrganisationId = (0, app_sdk_1.getAppContext)().account.organizationId;
59
+ if (customerId !== appOrganisationId) {
60
+ app_sdk_1.logger.error(`Invalid organisation ID: expected ${appOrganisationId}, received ${customerId}`);
61
+ return false;
62
+ }
63
+ return await this.validateAccessToken(accessToken);
64
+ }
65
+ async validateAccessToken(accessToken) {
66
+ try {
67
+ if (!accessToken) {
68
+ return false;
69
+ }
70
+ const tokenVerifier = await (0, TokenVerifier_1.getTokenVerifier)();
71
+ return await tokenVerifier.verify(accessToken);
72
+ }
73
+ catch (error) {
74
+ app_sdk_1.logger.error('OptiID token validation failed:', error);
75
+ return false;
76
+ }
49
77
  }
50
78
  }
51
79
  exports.ToolFunction = ToolFunction;
@@ -1 +1 @@
1
- {"version":3,"file":"ToolFunction.js","sourceRoot":"","sources":["../../src/function/ToolFunction.ts"],"names":[],"mappings":";;;AAAA,+CAAwE;AACxE,gDAAkD;AAElD;;;GAGG;AACH,MAAsB,YAAa,SAAQ,kBAAQ;IAEjD;;;;;OAKG;IACO,KAAK;QACb,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,OAAO;QAClB,IAAA,yBAAe,EAAC,EAAE,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACtF,MAAM,WAAW,GAAG,sBAAY,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC1E,IAAI,WAAW,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,WAAW,CAAC,EAAE,CAAC;YAC1D,OAAO,IAAI,kBAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;QACnD,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACnC,OAAO,IAAI,kBAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,4EAA4E;QAC5E,OAAO,sBAAY,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;IAED;;;;;;;;OAQG;IACO,mBAAmB,CAAC,YAAoB;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AA5CD,oCA4CC"}
1
+ {"version":3,"file":"ToolFunction.js","sourceRoot":"","sources":["../../src/function/ToolFunction.ts"],"names":[],"mappings":";;;AAAA,+CAA+F;AAC/F,gDAAkD;AAClD,yDAAyD;AAGzD;;;GAGG;AACH,MAAsB,YAAa,SAAQ,kBAAQ;IAEjD;;;;;OAKG;IACO,KAAK;QACb,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,OAAO;QAClB,IAAA,yBAAe,EAAC,EAAE,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACtF,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC;YACrC,OAAO,IAAI,kBAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;QACnD,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACnC,OAAO,IAAI,kBAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,4EAA4E;QAC5E,OAAO,sBAAY,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,gBAAgB;QAC5B,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACzE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAsB,CAAC;QAC/D,MAAM,WAAW,GAAG,QAAQ,EAAE,WAAW,EAAE,YAAY,CAAC;QACxD,IAAI,CAAC,WAAW,IAAI,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,QAAQ,EAAE,CAAC;YACnE,gBAAM,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;YAC1D,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,UAAU,GAAG,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;QACrD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,gBAAM,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAC7D,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,iBAAiB,GAAG,IAAA,uBAAa,GAAE,CAAC,OAAO,CAAC,cAAc,CAAC;QACjE,IAAI,UAAU,KAAK,iBAAiB,EAAE,CAAC;YACrC,gBAAM,CAAC,KAAK,CAAC,qCAAqC,iBAAiB,cAAc,UAAU,EAAE,CAAC,CAAC;YAC/F,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,MAAM,IAAI,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;IACrD,CAAC;IAEO,KAAK,CAAC,mBAAmB,CAAC,WAA+B;QAC/D,IAAI,CAAC;YACH,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,aAAa,GAAG,MAAM,IAAA,gCAAgB,GAAE,CAAC;YAC/C,OAAO,MAAM,aAAa,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,gBAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;YACvD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CAEF;AA5ED,oCA4EC"}