@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
@@ -4,11 +4,6 @@ import * as App from '@zaiusinc/app-sdk';
4
4
  import { logger } from '@zaiusinc/app-sdk';
5
5
  import { ToolFunction } from '../function/ToolFunction';
6
6
 
7
- /**
8
- * Default OptiID authentication requirement that will be enforced for all tools
9
- */
10
- const DEFAULT_OPTIID_AUTH = new AuthRequirement('OptiID', 'default', true);
11
-
12
7
 
13
8
 
14
9
  /**
@@ -48,7 +43,7 @@ export class Tool<TAuthData> {
48
43
  * @param parameters Function parameters
49
44
  * @param endpoint API endpoint
50
45
  * @param handler Function implementing the tool
51
- * @param authRequirements Authentication requirements (mandatory - OptiID enforced)
46
+ * @param authRequirements Authentication requirements (optional)
52
47
  */
53
48
  public constructor(
54
49
  public name: string,
@@ -56,7 +51,7 @@ export class Tool<TAuthData> {
56
51
  public parameters: Parameter[],
57
52
  public endpoint: string,
58
53
  public handler: (functionContext: ToolFunction, params: unknown, authData?: TAuthData) => Promise<unknown>,
59
- public authRequirements: AuthRequirement[] = [DEFAULT_OPTIID_AUTH]
54
+ public authRequirements?: AuthRequirement[]
60
55
  ) {}
61
56
 
62
57
  /**
@@ -68,10 +63,13 @@ export class Tool<TAuthData> {
68
63
  description: this.description,
69
64
  parameters: this.parameters.map((p) => p.toJSON()),
70
65
  endpoint: this.endpoint,
71
- http_method: this.httpMethod,
72
- auth_requirements: this.authRequirements.map((auth) => auth.toJSON())
66
+ http_method: this.httpMethod
73
67
  };
74
68
 
69
+ if (this.authRequirements && this.authRequirements.length > 0) {
70
+ result.auth_requirements = this.authRequirements.map((auth) => auth.toJSON());
71
+ }
72
+
75
73
  return result;
76
74
  }
77
75
  }
@@ -81,19 +79,18 @@ export class ToolsService {
81
79
  private interactions: Map<string, Interaction<any>> = new Map();
82
80
 
83
81
  /**
84
- * Enforce OptiID authentication for tools by ensuring OptiID auth requirement is present
85
- * @param authRequirements Original authentication requirements
86
- * @returns Enforced authentication requirements with OptiID
82
+ * Extract Bearer token from Authorization header
83
+ * @param headers Request headers (Map-like object or Headers object with get method)
84
+ * @returns Bearer token string or undefined
87
85
  */
88
- private enforceOptiIdAuth(authRequirements?: AuthRequirement[]): AuthRequirement[] {
89
- const hasOptiIdProvider = authRequirements
90
- && authRequirements.some((auth) => auth.provider.toLowerCase() === 'optiid');
86
+ public extractBearerToken(headers: App.Headers): string | undefined {
87
+ let bearerToken: string | undefined;
91
88
 
92
- if (hasOptiIdProvider) {
93
- return authRequirements;
89
+ const authHeader = headers ? headers.get('authorization') : undefined;
90
+ if (authHeader && authHeader.startsWith('Bearer ')) {
91
+ bearerToken = authHeader.substring(7).trim();
94
92
  }
95
-
96
- return [...(authRequirements || []), DEFAULT_OPTIID_AUTH];
93
+ return bearerToken;
97
94
  }
98
95
 
99
96
  /**
@@ -113,9 +110,7 @@ export class ToolsService {
113
110
  endpoint: string,
114
111
  authRequirements?: AuthRequirement[]
115
112
  ): void {
116
- // Enforce OptiID authentication for all tools
117
- const enforcedAuthRequirements = this.enforceOptiIdAuth(authRequirements);
118
- const func = new Tool<TAuthData>(name, description, parameters, endpoint, handler, enforcedAuthRequirements);
113
+ const func = new Tool<TAuthData>(name, description, parameters, endpoint, handler, authRequirements);
119
114
  this.functions.set(endpoint, func);
120
115
  }
121
116
 
@@ -48,10 +48,10 @@ export class Parameter {
48
48
  export class OptiIdAuthDataCredentials {
49
49
 
50
50
  public constructor(
51
- public customer_id: string,
52
- public instance_id: string,
53
- public access_token: string,
54
- public product_sku: string
51
+ public customerId: string,
52
+ public instanceId: string,
53
+ public accessToken: string,
54
+ public productSku: string
55
55
  ) {}
56
56
  }
57
57
 
@@ -1,31 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,127 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"TokenVerifier.js","sourceRoot":"","sources":["../../src/auth/TokenVerifier.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=TokenVerifier.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TokenVerifier.test.d.ts","sourceRoot":"","sources":["../../src/auth/TokenVerifier.test.ts"],"names":[],"mappings":""}
@@ -1,114 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"TokenVerifier.test.js","sourceRoot":"","sources":["../../src/auth/TokenVerifier.test.ts"],"names":[],"mappings":"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"}
@@ -1,152 +0,0 @@
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
- });
150
- });
151
-
152
- });