@optimizely-opal/opal-tool-ocp-sdk 0.0.0-OCP-1487.4 → 0.0.0-OCP-1487.6
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.
- package/README.md +103 -14
- package/dist/auth/AuthUtils.d.ts +12 -28
- package/dist/auth/AuthUtils.d.ts.map +1 -1
- package/dist/auth/AuthUtils.js +94 -50
- package/dist/auth/AuthUtils.js.map +1 -1
- package/dist/auth/AuthUtils.test.js +535 -403
- package/dist/auth/AuthUtils.test.js.map +1 -1
- package/dist/decorator/Decorator.d.ts +1 -0
- package/dist/decorator/Decorator.d.ts.map +1 -1
- package/dist/decorator/Decorator.js +1 -1
- package/dist/decorator/Decorator.js.map +1 -1
- package/dist/decorator/Decorator.test.js +33 -11
- package/dist/decorator/Decorator.test.js.map +1 -1
- package/dist/function/GlobalToolFunction.d.ts +0 -1
- package/dist/function/GlobalToolFunction.d.ts.map +1 -1
- package/dist/function/GlobalToolFunction.js +1 -10
- package/dist/function/GlobalToolFunction.js.map +1 -1
- package/dist/function/ToolFunction.d.ts +0 -1
- package/dist/function/ToolFunction.d.ts.map +1 -1
- package/dist/function/ToolFunction.js +1 -13
- package/dist/function/ToolFunction.js.map +1 -1
- package/dist/service/Service.d.ts.map +1 -1
- package/dist/service/Service.js.map +1 -1
- package/package.json +1 -1
- package/src/auth/AuthUtils.test.ts +638 -495
- package/src/auth/AuthUtils.ts +99 -48
- package/src/decorator/Decorator.test.ts +52 -10
- package/src/decorator/Decorator.ts +3 -1
- package/src/function/GlobalToolFunction.ts +2 -14
- package/src/function/ToolFunction.ts +2 -18
- package/src/service/Service.ts +4 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
2
2
|
import { getAppContext, logger } from '@zaiusinc/app-sdk';
|
|
3
3
|
import { getTokenVerifier } from './TokenVerifier';
|
|
4
|
-
import {
|
|
4
|
+
import { authorizeRegularRequest, authorizeGlobalRequest } from './AuthUtils';
|
|
5
5
|
|
|
6
6
|
// Mock the dependencies
|
|
7
7
|
jest.mock('./TokenVerifier', () => ({
|
|
@@ -19,568 +19,711 @@ jest.mock('@zaiusinc/app-sdk', () => ({
|
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
21
|
describe('AuthUtils', () => {
|
|
22
|
+
let mockTokenVerifier: any;
|
|
22
23
|
let mockGetTokenVerifier: jest.MockedFunction<typeof getTokenVerifier>;
|
|
23
24
|
let mockGetAppContext: jest.MockedFunction<typeof getAppContext>;
|
|
24
|
-
let mockTokenVerifier: jest.Mocked<{
|
|
25
|
-
verify: (token: string) => Promise<any>;
|
|
26
|
-
}>;
|
|
27
25
|
|
|
28
26
|
beforeEach(() => {
|
|
29
27
|
jest.clearAllMocks();
|
|
30
28
|
|
|
31
|
-
//
|
|
29
|
+
// Setup token verifier mock
|
|
32
30
|
mockTokenVerifier = {
|
|
33
31
|
verify: jest.fn(),
|
|
34
32
|
};
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
mockGetTokenVerifier
|
|
38
|
-
mockGetAppContext = jest.mocked(getAppContext);
|
|
34
|
+
mockGetTokenVerifier = getTokenVerifier as jest.MockedFunction<typeof getTokenVerifier>;
|
|
35
|
+
mockGetTokenVerifier.mockResolvedValue(mockTokenVerifier);
|
|
39
36
|
|
|
40
|
-
|
|
37
|
+
// Setup app context mock with default organization
|
|
38
|
+
mockGetAppContext = getAppContext as jest.MockedFunction<typeof getAppContext>;
|
|
41
39
|
mockGetAppContext.mockReturnValue({
|
|
42
40
|
account: {
|
|
43
|
-
organizationId: '
|
|
41
|
+
organizationId: 'test-org-123'
|
|
44
42
|
}
|
|
45
43
|
} as any);
|
|
46
44
|
});
|
|
47
45
|
|
|
48
|
-
describe('
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
const validToken = 'valid-access-token';
|
|
46
|
+
describe('authorizeRegularRequest', () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
// Default to successful token verification
|
|
52
49
|
mockTokenVerifier.verify.mockResolvedValue(true);
|
|
53
|
-
|
|
54
|
-
// Act
|
|
55
|
-
const result = await AuthUtils.validateAccessToken(validToken);
|
|
56
|
-
|
|
57
|
-
// Assert
|
|
58
|
-
expect(result).toBe(true);
|
|
59
|
-
expect(mockGetTokenVerifier).toHaveBeenCalledTimes(1);
|
|
60
|
-
expect(mockTokenVerifier.verify).toHaveBeenCalledWith(validToken);
|
|
61
|
-
expect(logger.error).not.toHaveBeenCalled();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should return false for invalid token', async () => {
|
|
65
|
-
// Arrange
|
|
66
|
-
const invalidToken = 'invalid-access-token';
|
|
67
|
-
mockTokenVerifier.verify.mockResolvedValue(false);
|
|
68
|
-
|
|
69
|
-
// Act
|
|
70
|
-
const result = await AuthUtils.validateAccessToken(invalidToken);
|
|
71
|
-
|
|
72
|
-
// Assert
|
|
73
|
-
expect(result).toBe(false);
|
|
74
|
-
expect(mockGetTokenVerifier).toHaveBeenCalledTimes(1);
|
|
75
|
-
expect(mockTokenVerifier.verify).toHaveBeenCalledWith(invalidToken);
|
|
76
|
-
expect(logger.error).not.toHaveBeenCalled();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should return false for undefined token', async () => {
|
|
80
|
-
// Act
|
|
81
|
-
const result = await AuthUtils.validateAccessToken(undefined);
|
|
82
|
-
|
|
83
|
-
// Assert
|
|
84
|
-
expect(result).toBe(false);
|
|
85
|
-
expect(mockGetTokenVerifier).not.toHaveBeenCalled();
|
|
86
|
-
expect(mockTokenVerifier.verify).not.toHaveBeenCalled();
|
|
87
|
-
expect(logger.error).not.toHaveBeenCalled();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('should return false for null token', async () => {
|
|
91
|
-
// Act
|
|
92
|
-
const result = await AuthUtils.validateAccessToken(null as any);
|
|
93
|
-
|
|
94
|
-
// Assert
|
|
95
|
-
expect(result).toBe(false);
|
|
96
|
-
expect(mockGetTokenVerifier).not.toHaveBeenCalled();
|
|
97
|
-
expect(mockTokenVerifier.verify).not.toHaveBeenCalled();
|
|
98
|
-
expect(logger.error).not.toHaveBeenCalled();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should return false for empty string token', async () => {
|
|
102
|
-
// Act
|
|
103
|
-
const result = await AuthUtils.validateAccessToken('');
|
|
104
|
-
|
|
105
|
-
// Assert
|
|
106
|
-
expect(result).toBe(false);
|
|
107
|
-
expect(mockGetTokenVerifier).not.toHaveBeenCalled();
|
|
108
|
-
expect(mockTokenVerifier.verify).not.toHaveBeenCalled();
|
|
109
|
-
expect(logger.error).not.toHaveBeenCalled();
|
|
110
50
|
});
|
|
111
51
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const error = new Error('Failed to get token verifier');
|
|
116
|
-
mockGetTokenVerifier.mockRejectedValue(error);
|
|
52
|
+
describe('when request is for discovery endpoint', () => {
|
|
53
|
+
it('should return true without authentication', async () => {
|
|
54
|
+
const request = { path: '/discovery' };
|
|
117
55
|
|
|
118
|
-
|
|
119
|
-
const result = await AuthUtils.validateAccessToken(validToken);
|
|
56
|
+
const result = await authorizeRegularRequest(request);
|
|
120
57
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
expect(logger.error).toHaveBeenCalledWith('OptiID token validation failed:', error);
|
|
58
|
+
expect(result).toBe(true);
|
|
59
|
+
expect(mockGetTokenVerifier).not.toHaveBeenCalled();
|
|
60
|
+
expect(mockTokenVerifier.verify).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
126
62
|
});
|
|
127
63
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const error = new Error('Token verification failed');
|
|
132
|
-
mockTokenVerifier.verify.mockRejectedValue(error);
|
|
64
|
+
describe('when request is for ready endpoint', () => {
|
|
65
|
+
it('should return true without authentication', async () => {
|
|
66
|
+
const request = { path: '/ready' };
|
|
133
67
|
|
|
134
|
-
|
|
135
|
-
const result = await AuthUtils.validateAccessToken(validToken);
|
|
68
|
+
const result = await authorizeRegularRequest(request);
|
|
136
69
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
expect(logger.error).toHaveBeenCalledWith('OptiID token validation failed:', error);
|
|
70
|
+
expect(result).toBe(true);
|
|
71
|
+
expect(mockGetTokenVerifier).not.toHaveBeenCalled();
|
|
72
|
+
expect(mockTokenVerifier.verify).not.toHaveBeenCalled();
|
|
73
|
+
});
|
|
142
74
|
});
|
|
143
75
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
describe('extractAuthData', () => {
|
|
159
|
-
const createValidRequest = (): any => ({
|
|
160
|
-
bodyJSON: {
|
|
161
|
-
auth: {
|
|
162
|
-
provider: 'OptiID',
|
|
163
|
-
credentials: {
|
|
164
|
-
access_token: 'valid-access-token',
|
|
165
|
-
customer_id: 'org-123',
|
|
166
|
-
instance_id: 'instance-456',
|
|
167
|
-
product_sku: 'OPAL'
|
|
76
|
+
describe('when request has valid authentication', () => {
|
|
77
|
+
it('should return true for valid OptiID token with matching organization', async () => {
|
|
78
|
+
const request = {
|
|
79
|
+
path: '/some-tool',
|
|
80
|
+
bodyJSON: {
|
|
81
|
+
auth: {
|
|
82
|
+
provider: 'OptiID',
|
|
83
|
+
credentials: {
|
|
84
|
+
customer_id: 'test-org-123',
|
|
85
|
+
access_token: 'valid-token-123'
|
|
86
|
+
}
|
|
87
|
+
}
|
|
168
88
|
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const request = createValidRequest();
|
|
190
|
-
request.bodyJSON.auth.provider = 'optiid'; // lowercase
|
|
191
|
-
|
|
192
|
-
// Act
|
|
193
|
-
const result = AuthUtils.extractAuthData(request);
|
|
194
|
-
|
|
195
|
-
// Assert
|
|
196
|
-
expect(result).not.toBeNull();
|
|
197
|
-
expect(result?.authData).toBe(request.bodyJSON.auth);
|
|
198
|
-
expect(result?.accessToken).toBe('valid-access-token');
|
|
199
|
-
expect(logger.error).not.toHaveBeenCalled();
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('should handle mixed case provider name', () => {
|
|
203
|
-
// Arrange
|
|
204
|
-
const request = createValidRequest();
|
|
205
|
-
request.bodyJSON.auth.provider = 'OpTiId'; // mixed case
|
|
206
|
-
|
|
207
|
-
// Act
|
|
208
|
-
const result = AuthUtils.extractAuthData(request);
|
|
209
|
-
|
|
210
|
-
// Assert
|
|
211
|
-
expect(result).not.toBeNull();
|
|
212
|
-
expect(result?.authData).toBe(request.bodyJSON.auth);
|
|
213
|
-
expect(result?.accessToken).toBe('valid-access-token');
|
|
214
|
-
expect(logger.error).not.toHaveBeenCalled();
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('should return null when access token is missing', () => {
|
|
218
|
-
// Arrange
|
|
219
|
-
const request = createValidRequest();
|
|
220
|
-
delete request.bodyJSON.auth.credentials.access_token;
|
|
221
|
-
|
|
222
|
-
// Act
|
|
223
|
-
const result = AuthUtils.extractAuthData(request);
|
|
224
|
-
|
|
225
|
-
// Assert
|
|
226
|
-
expect(result).toBeNull();
|
|
227
|
-
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should return null when access token is undefined', () => {
|
|
231
|
-
// Arrange
|
|
232
|
-
const request = createValidRequest();
|
|
233
|
-
request.bodyJSON.auth.credentials.access_token = undefined;
|
|
234
|
-
|
|
235
|
-
// Act
|
|
236
|
-
const result = AuthUtils.extractAuthData(request);
|
|
237
|
-
|
|
238
|
-
// Assert
|
|
239
|
-
expect(result).toBeNull();
|
|
240
|
-
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('should return null when access token is empty string', () => {
|
|
244
|
-
// Arrange
|
|
245
|
-
const request = createValidRequest();
|
|
246
|
-
request.bodyJSON.auth.credentials.access_token = '';
|
|
247
|
-
|
|
248
|
-
// Act
|
|
249
|
-
const result = AuthUtils.extractAuthData(request);
|
|
250
|
-
|
|
251
|
-
// Assert
|
|
252
|
-
expect(result).toBeNull();
|
|
253
|
-
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('should return null when provider is not OptiID', () => {
|
|
257
|
-
// Arrange
|
|
258
|
-
const request = createValidRequest();
|
|
259
|
-
request.bodyJSON.auth.provider = 'SomeOtherProvider';
|
|
260
|
-
|
|
261
|
-
// Act
|
|
262
|
-
const result = AuthUtils.extractAuthData(request);
|
|
263
|
-
|
|
264
|
-
// Assert
|
|
265
|
-
expect(result).toBeNull();
|
|
266
|
-
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('should return null when provider is missing', () => {
|
|
270
|
-
// Arrange
|
|
271
|
-
const request = createValidRequest();
|
|
272
|
-
delete request.bodyJSON.auth.provider;
|
|
273
|
-
|
|
274
|
-
// Act
|
|
275
|
-
const result = AuthUtils.extractAuthData(request);
|
|
276
|
-
|
|
277
|
-
// Assert
|
|
278
|
-
expect(result).toBeNull();
|
|
279
|
-
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it('should return null when auth structure is missing', () => {
|
|
283
|
-
// Arrange
|
|
284
|
-
const request = {
|
|
285
|
-
bodyJSON: {
|
|
286
|
-
parameters: { some: 'data' }
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
// Act
|
|
291
|
-
const result = AuthUtils.extractAuthData(request);
|
|
292
|
-
|
|
293
|
-
// Assert
|
|
294
|
-
expect(result).toBeNull();
|
|
295
|
-
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it('should return null when credentials structure is missing', () => {
|
|
299
|
-
// Arrange
|
|
300
|
-
const request = {
|
|
301
|
-
bodyJSON: {
|
|
302
|
-
auth: {
|
|
303
|
-
provider: 'OptiID'
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const result = await authorizeRegularRequest(request);
|
|
92
|
+
|
|
93
|
+
expect(result).toBe(true);
|
|
94
|
+
expect(mockGetTokenVerifier).toHaveBeenCalled();
|
|
95
|
+
expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-token-123');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should handle case-insensitive provider names', async () => {
|
|
99
|
+
const request = {
|
|
100
|
+
path: '/some-tool',
|
|
101
|
+
bodyJSON: {
|
|
102
|
+
auth: {
|
|
103
|
+
provider: 'optiid', // lowercase
|
|
104
|
+
credentials: {
|
|
105
|
+
customer_id: 'test-org-123',
|
|
106
|
+
access_token: 'valid-token-123'
|
|
107
|
+
}
|
|
108
|
+
}
|
|
304
109
|
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const result = await authorizeRegularRequest(request);
|
|
113
|
+
|
|
114
|
+
expect(result).toBe(true);
|
|
115
|
+
expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-token-123');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should handle mixed case provider names', async () => {
|
|
119
|
+
const request = {
|
|
120
|
+
path: '/some-tool',
|
|
121
|
+
bodyJSON: {
|
|
122
|
+
auth: {
|
|
123
|
+
provider: 'OpTiId', // mixed case
|
|
124
|
+
credentials: {
|
|
125
|
+
customer_id: 'test-org-123',
|
|
126
|
+
access_token: 'valid-token-123'
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const result = await authorizeRegularRequest(request);
|
|
133
|
+
|
|
134
|
+
expect(result).toBe(true);
|
|
135
|
+
expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-token-123');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('when authentication fails', () => {
|
|
140
|
+
it('should return false when auth data is missing', async () => {
|
|
141
|
+
const request = {
|
|
142
|
+
path: '/some-tool',
|
|
143
|
+
bodyJSON: {}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const result = await authorizeRegularRequest(request);
|
|
147
|
+
|
|
148
|
+
expect(result).toBe(false);
|
|
149
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
150
|
+
expect(mockTokenVerifier.verify).not.toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should return false when bodyJSON is missing', async () => {
|
|
154
|
+
const request = {
|
|
155
|
+
path: '/some-tool'
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const result = await authorizeRegularRequest(request);
|
|
159
|
+
|
|
160
|
+
expect(result).toBe(false);
|
|
161
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should return false when provider is not OptiID', async () => {
|
|
165
|
+
const request = {
|
|
166
|
+
path: '/some-tool',
|
|
167
|
+
bodyJSON: {
|
|
168
|
+
auth: {
|
|
169
|
+
provider: 'SomeOtherProvider',
|
|
170
|
+
credentials: {
|
|
171
|
+
customer_id: 'test-org-123',
|
|
172
|
+
access_token: 'some-token'
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const result = await authorizeRegularRequest(request);
|
|
179
|
+
|
|
180
|
+
expect(result).toBe(false);
|
|
181
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should return false when access token is missing', async () => {
|
|
185
|
+
const request = {
|
|
186
|
+
path: '/some-tool',
|
|
187
|
+
bodyJSON: {
|
|
188
|
+
auth: {
|
|
189
|
+
provider: 'OptiID',
|
|
190
|
+
credentials: {
|
|
191
|
+
customer_id: 'test-org-123'
|
|
192
|
+
// access_token missing
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const result = await authorizeRegularRequest(request);
|
|
199
|
+
|
|
200
|
+
expect(result).toBe(false);
|
|
201
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should return false when access token is empty string', async () => {
|
|
205
|
+
const request = {
|
|
206
|
+
path: '/some-tool',
|
|
207
|
+
bodyJSON: {
|
|
208
|
+
auth: {
|
|
209
|
+
provider: 'OptiID',
|
|
210
|
+
credentials: {
|
|
211
|
+
customer_id: 'test-org-123',
|
|
212
|
+
access_token: ''
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const result = await authorizeRegularRequest(request);
|
|
219
|
+
|
|
220
|
+
expect(result).toBe(false);
|
|
221
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should return false when access token is undefined', async () => {
|
|
225
|
+
const request = {
|
|
226
|
+
path: '/some-tool',
|
|
227
|
+
bodyJSON: {
|
|
228
|
+
auth: {
|
|
229
|
+
provider: 'OptiID',
|
|
230
|
+
credentials: {
|
|
231
|
+
customer_id: 'test-org-123',
|
|
232
|
+
access_token: undefined
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const result = await authorizeRegularRequest(request);
|
|
239
|
+
|
|
240
|
+
expect(result).toBe(false);
|
|
241
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('when organization validation fails', () => {
|
|
246
|
+
it('should return false when customer_id does not match app organization', async () => {
|
|
247
|
+
const request = {
|
|
248
|
+
path: '/some-tool',
|
|
249
|
+
bodyJSON: {
|
|
250
|
+
auth: {
|
|
251
|
+
provider: 'OptiID',
|
|
252
|
+
credentials: {
|
|
253
|
+
customer_id: 'different-org-456',
|
|
254
|
+
access_token: 'valid-token-123'
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const result = await authorizeRegularRequest(request);
|
|
261
|
+
|
|
262
|
+
expect(result).toBe(false);
|
|
263
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
264
|
+
'Invalid organisation ID: expected test-org-123, received different-org-456'
|
|
265
|
+
);
|
|
266
|
+
expect(mockTokenVerifier.verify).not.toHaveBeenCalled();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should return false when customer_id is missing', async () => {
|
|
270
|
+
const request = {
|
|
271
|
+
path: '/some-tool',
|
|
272
|
+
bodyJSON: {
|
|
273
|
+
auth: {
|
|
274
|
+
provider: 'OptiID',
|
|
275
|
+
credentials: {
|
|
276
|
+
access_token: 'valid-token-123'
|
|
277
|
+
// customer_id missing
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const result = await authorizeRegularRequest(request);
|
|
284
|
+
|
|
285
|
+
expect(result).toBe(false);
|
|
286
|
+
expect(logger.error).toHaveBeenCalledWith('Organisation ID is required but not provided');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should return false when customer_id is empty string', async () => {
|
|
290
|
+
const request = {
|
|
291
|
+
path: '/some-tool',
|
|
292
|
+
bodyJSON: {
|
|
293
|
+
auth: {
|
|
294
|
+
provider: 'OptiID',
|
|
295
|
+
credentials: {
|
|
296
|
+
customer_id: '',
|
|
297
|
+
access_token: 'valid-token-123'
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const result = await authorizeRegularRequest(request);
|
|
304
|
+
|
|
305
|
+
expect(result).toBe(false);
|
|
306
|
+
expect(logger.error).toHaveBeenCalledWith('Organisation ID is required but not provided');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should handle case when app context has no account', async () => {
|
|
310
|
+
mockGetAppContext.mockReturnValue({} as any);
|
|
311
|
+
|
|
312
|
+
const request = {
|
|
313
|
+
path: '/some-tool',
|
|
314
|
+
bodyJSON: {
|
|
315
|
+
auth: {
|
|
316
|
+
provider: 'OptiID',
|
|
317
|
+
credentials: {
|
|
318
|
+
customer_id: 'some-org-123',
|
|
319
|
+
access_token: 'valid-token-123'
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const result = await authorizeRegularRequest(request);
|
|
326
|
+
|
|
327
|
+
expect(result).toBe(false);
|
|
328
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
329
|
+
'Invalid organisation ID: expected undefined, received some-org-123'
|
|
330
|
+
);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should handle case when app context is null', async () => {
|
|
334
|
+
mockGetAppContext.mockReturnValue(null as any);
|
|
335
|
+
|
|
336
|
+
const request = {
|
|
337
|
+
path: '/some-tool',
|
|
338
|
+
bodyJSON: {
|
|
339
|
+
auth: {
|
|
340
|
+
provider: 'OptiID',
|
|
341
|
+
credentials: {
|
|
342
|
+
customer_id: 'some-org-123',
|
|
343
|
+
access_token: 'valid-token-123'
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const result = await authorizeRegularRequest(request);
|
|
350
|
+
|
|
351
|
+
expect(result).toBe(false);
|
|
352
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
353
|
+
'Invalid organisation ID: expected undefined, received some-org-123'
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe('when token validation fails', () => {
|
|
359
|
+
it('should return false when token verifier returns false', async () => {
|
|
360
|
+
mockTokenVerifier.verify.mockResolvedValue(false);
|
|
361
|
+
|
|
362
|
+
const request = {
|
|
363
|
+
path: '/some-tool',
|
|
364
|
+
bodyJSON: {
|
|
365
|
+
auth: {
|
|
366
|
+
provider: 'OptiID',
|
|
367
|
+
credentials: {
|
|
368
|
+
customer_id: 'test-org-123',
|
|
369
|
+
access_token: 'invalid-token'
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const result = await authorizeRegularRequest(request);
|
|
376
|
+
|
|
377
|
+
expect(result).toBe(false);
|
|
378
|
+
expect(mockTokenVerifier.verify).toHaveBeenCalledWith('invalid-token');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should return false when token verification throws an error', async () => {
|
|
382
|
+
const verificationError = new Error('Token verification failed');
|
|
383
|
+
mockTokenVerifier.verify.mockRejectedValue(verificationError);
|
|
384
|
+
|
|
385
|
+
const request = {
|
|
386
|
+
path: '/some-tool',
|
|
387
|
+
bodyJSON: {
|
|
388
|
+
auth: {
|
|
389
|
+
provider: 'OptiID',
|
|
390
|
+
credentials: {
|
|
391
|
+
customer_id: 'test-org-123',
|
|
392
|
+
access_token: 'error-token'
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const result = await authorizeRegularRequest(request);
|
|
399
|
+
|
|
400
|
+
expect(result).toBe(false);
|
|
401
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token validation failed:', verificationError);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should return false when getTokenVerifier throws an error', async () => {
|
|
405
|
+
const verifierError = new Error('Failed to get token verifier');
|
|
406
|
+
mockGetTokenVerifier.mockRejectedValue(verifierError);
|
|
407
|
+
|
|
408
|
+
const request = {
|
|
409
|
+
path: '/some-tool',
|
|
410
|
+
bodyJSON: {
|
|
411
|
+
auth: {
|
|
412
|
+
provider: 'OptiID',
|
|
413
|
+
credentials: {
|
|
414
|
+
customer_id: 'test-org-123',
|
|
415
|
+
access_token: 'some-token'
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
};
|
|
336
420
|
|
|
337
|
-
|
|
338
|
-
// Act
|
|
339
|
-
const result = AuthUtils.extractAuthData(undefined);
|
|
421
|
+
const result = await authorizeRegularRequest(request);
|
|
340
422
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
423
|
+
expect(result).toBe(false);
|
|
424
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token validation failed:', verifierError);
|
|
425
|
+
});
|
|
344
426
|
});
|
|
345
427
|
});
|
|
346
428
|
|
|
347
|
-
describe('
|
|
429
|
+
describe('authorizeGlobalRequest', () => {
|
|
348
430
|
beforeEach(() => {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
organizationId: 'app-org-123'
|
|
352
|
-
}
|
|
353
|
-
} as any);
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it('should return true when customer ID matches app organization ID', () => {
|
|
357
|
-
// Act
|
|
358
|
-
const result = AuthUtils.validateOrganizationId('app-org-123');
|
|
359
|
-
|
|
360
|
-
// Assert
|
|
361
|
-
expect(result).toBe(true);
|
|
362
|
-
expect(mockGetAppContext).toHaveBeenCalledTimes(1);
|
|
363
|
-
expect(logger.error).not.toHaveBeenCalled();
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it('should return false when customer ID does not match app organization ID', () => {
|
|
367
|
-
// Act
|
|
368
|
-
const result = AuthUtils.validateOrganizationId('different-org-456');
|
|
369
|
-
|
|
370
|
-
// Assert
|
|
371
|
-
expect(result).toBe(false);
|
|
372
|
-
expect(mockGetAppContext).toHaveBeenCalledTimes(1);
|
|
373
|
-
expect(logger.error).toHaveBeenCalledWith(
|
|
374
|
-
'Invalid organisation ID: expected app-org-123, received different-org-456'
|
|
375
|
-
);
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it('should return false when customer ID is undefined', () => {
|
|
379
|
-
// Act
|
|
380
|
-
const result = AuthUtils.validateOrganizationId(undefined);
|
|
381
|
-
|
|
382
|
-
// Assert
|
|
383
|
-
expect(result).toBe(false);
|
|
384
|
-
expect(mockGetAppContext).not.toHaveBeenCalled();
|
|
385
|
-
expect(logger.error).toHaveBeenCalledWith('Organisation ID is required but not provided');
|
|
431
|
+
// Default to successful token verification
|
|
432
|
+
mockTokenVerifier.verify.mockResolvedValue(true);
|
|
386
433
|
});
|
|
387
434
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
// Assert
|
|
393
|
-
expect(result).toBe(false);
|
|
394
|
-
expect(mockGetAppContext).not.toHaveBeenCalled();
|
|
395
|
-
expect(logger.error).toHaveBeenCalledWith('Organisation ID is required but not provided');
|
|
396
|
-
});
|
|
435
|
+
describe('when request is for discovery endpoint', () => {
|
|
436
|
+
it('should return true without authentication', async () => {
|
|
437
|
+
const request = { path: '/discovery' };
|
|
397
438
|
|
|
398
|
-
|
|
399
|
-
// Act
|
|
400
|
-
const result = AuthUtils.validateOrganizationId('');
|
|
439
|
+
const result = await authorizeGlobalRequest(request);
|
|
401
440
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
441
|
+
expect(result).toBe(true);
|
|
442
|
+
expect(mockGetTokenVerifier).not.toHaveBeenCalled();
|
|
443
|
+
expect(mockTokenVerifier.verify).not.toHaveBeenCalled();
|
|
444
|
+
});
|
|
406
445
|
});
|
|
407
446
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
447
|
+
describe('when request is for ready endpoint', () => {
|
|
448
|
+
it('should return true without authentication', async () => {
|
|
449
|
+
const request = { path: '/ready' };
|
|
411
450
|
|
|
412
|
-
|
|
413
|
-
const result = AuthUtils.validateOrganizationId('some-org-123');
|
|
451
|
+
const result = await authorizeGlobalRequest(request);
|
|
414
452
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
'Invalid organisation ID: expected undefined, received some-org-123'
|
|
420
|
-
);
|
|
453
|
+
expect(result).toBe(true);
|
|
454
|
+
expect(mockGetTokenVerifier).not.toHaveBeenCalled();
|
|
455
|
+
expect(mockTokenVerifier.verify).not.toHaveBeenCalled();
|
|
456
|
+
});
|
|
421
457
|
});
|
|
422
458
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
459
|
+
describe('when request has valid authentication', () => {
|
|
460
|
+
it('should return true for valid OptiID token regardless of organization', async () => {
|
|
461
|
+
const request = {
|
|
462
|
+
path: '/global-tool',
|
|
463
|
+
bodyJSON: {
|
|
464
|
+
auth: {
|
|
465
|
+
provider: 'OptiID',
|
|
466
|
+
credentials: {
|
|
467
|
+
customer_id: 'different-org-456', // Different from app org
|
|
468
|
+
access_token: 'valid-token-123'
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const result = await authorizeGlobalRequest(request);
|
|
475
|
+
|
|
476
|
+
expect(result).toBe(true);
|
|
477
|
+
expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-token-123');
|
|
478
|
+
// Should not log organization validation errors for global requests
|
|
479
|
+
expect(logger.error).not.toHaveBeenCalledWith(
|
|
480
|
+
expect.stringContaining('Invalid organisation ID')
|
|
481
|
+
);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('should return true even without customer_id', async () => {
|
|
485
|
+
const request = {
|
|
486
|
+
path: '/global-tool',
|
|
487
|
+
bodyJSON: {
|
|
488
|
+
auth: {
|
|
489
|
+
provider: 'OptiID',
|
|
490
|
+
credentials: {
|
|
491
|
+
access_token: 'valid-token-123'
|
|
492
|
+
// No customer_id - should be fine for global functions
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const result = await authorizeGlobalRequest(request);
|
|
499
|
+
|
|
500
|
+
expect(result).toBe(true);
|
|
501
|
+
expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-token-123');
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should handle case-insensitive provider names', async () => {
|
|
505
|
+
const request = {
|
|
506
|
+
path: '/global-tool',
|
|
507
|
+
bodyJSON: {
|
|
508
|
+
auth: {
|
|
509
|
+
provider: 'optiid',
|
|
510
|
+
credentials: {
|
|
511
|
+
customer_id: 'any-org',
|
|
512
|
+
access_token: 'valid-token-123'
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const result = await authorizeGlobalRequest(request);
|
|
519
|
+
|
|
520
|
+
expect(result).toBe(true);
|
|
521
|
+
expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-token-123');
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
describe('when authentication fails', () => {
|
|
526
|
+
it('should return false when auth data is missing', async () => {
|
|
527
|
+
const request = {
|
|
528
|
+
path: '/global-tool',
|
|
529
|
+
bodyJSON: {}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
const result = await authorizeGlobalRequest(request);
|
|
533
|
+
|
|
534
|
+
expect(result).toBe(false);
|
|
535
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('should return false when provider is not OptiID', async () => {
|
|
539
|
+
const request = {
|
|
540
|
+
path: '/global-tool',
|
|
541
|
+
bodyJSON: {
|
|
542
|
+
auth: {
|
|
543
|
+
provider: 'SomeOtherProvider',
|
|
544
|
+
credentials: {
|
|
545
|
+
customer_id: 'any-org',
|
|
546
|
+
access_token: 'some-token'
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const result = await authorizeGlobalRequest(request);
|
|
553
|
+
|
|
554
|
+
expect(result).toBe(false);
|
|
555
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('should return false when access token is missing', async () => {
|
|
559
|
+
const request = {
|
|
560
|
+
path: '/global-tool',
|
|
561
|
+
bodyJSON: {
|
|
562
|
+
auth: {
|
|
563
|
+
provider: 'OptiID',
|
|
564
|
+
credentials: {
|
|
565
|
+
customer_id: 'any-org'
|
|
566
|
+
// access_token missing
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const result = await authorizeGlobalRequest(request);
|
|
573
|
+
|
|
574
|
+
expect(result).toBe(false);
|
|
575
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('should return false when access token is empty', async () => {
|
|
579
|
+
const request = {
|
|
580
|
+
path: '/global-tool',
|
|
581
|
+
bodyJSON: {
|
|
582
|
+
auth: {
|
|
583
|
+
provider: 'OptiID',
|
|
584
|
+
credentials: {
|
|
585
|
+
customer_id: 'any-org',
|
|
586
|
+
access_token: ''
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
};
|
|
443
591
|
|
|
444
|
-
|
|
445
|
-
const result = AuthUtils.validateOrganizationId('some-org-123');
|
|
592
|
+
const result = await authorizeGlobalRequest(request);
|
|
446
593
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
expect(logger.error).toHaveBeenCalledWith(
|
|
451
|
-
'Invalid organisation ID: expected undefined, received some-org-123'
|
|
452
|
-
);
|
|
594
|
+
expect(result).toBe(false);
|
|
595
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
596
|
+
});
|
|
453
597
|
});
|
|
454
598
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
account: {
|
|
459
|
-
organizationId: 'App-Org-123' // different case
|
|
460
|
-
}
|
|
461
|
-
} as any);
|
|
462
|
-
|
|
463
|
-
// Act
|
|
464
|
-
const result = AuthUtils.validateOrganizationId('app-org-123');
|
|
599
|
+
describe('when token validation fails', () => {
|
|
600
|
+
it('should return false when token verifier returns false', async () => {
|
|
601
|
+
mockTokenVerifier.verify.mockResolvedValue(false);
|
|
465
602
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
603
|
+
const request = {
|
|
604
|
+
path: '/global-tool',
|
|
605
|
+
bodyJSON: {
|
|
606
|
+
auth: {
|
|
607
|
+
provider: 'OptiID',
|
|
608
|
+
credentials: {
|
|
609
|
+
customer_id: 'any-org',
|
|
610
|
+
access_token: 'invalid-token'
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const result = await authorizeGlobalRequest(request);
|
|
617
|
+
|
|
618
|
+
expect(result).toBe(false);
|
|
619
|
+
expect(mockTokenVerifier.verify).toHaveBeenCalledWith('invalid-token');
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it('should return false when token verification throws an error', async () => {
|
|
623
|
+
const verificationError = new Error('Global token verification failed');
|
|
624
|
+
mockTokenVerifier.verify.mockRejectedValue(verificationError);
|
|
625
|
+
|
|
626
|
+
const request = {
|
|
627
|
+
path: '/global-tool',
|
|
628
|
+
bodyJSON: {
|
|
629
|
+
auth: {
|
|
630
|
+
provider: 'OptiID',
|
|
631
|
+
credentials: {
|
|
632
|
+
customer_id: 'any-org',
|
|
633
|
+
access_token: 'error-token'
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
const result = await authorizeGlobalRequest(request);
|
|
640
|
+
|
|
641
|
+
expect(result).toBe(false);
|
|
642
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token validation failed:', verificationError);
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
describe('organization validation differences from authorizeRegularRequest', () => {
|
|
647
|
+
it('should NOT validate organization ID and allow any customer_id', async () => {
|
|
648
|
+
const request = {
|
|
649
|
+
path: '/global-tool',
|
|
650
|
+
bodyJSON: {
|
|
651
|
+
auth: {
|
|
652
|
+
provider: 'OptiID',
|
|
653
|
+
credentials: {
|
|
654
|
+
customer_id: 'completely-different-org',
|
|
655
|
+
access_token: 'valid-token-123'
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
const result = await authorizeGlobalRequest(request);
|
|
662
|
+
|
|
663
|
+
expect(result).toBe(true);
|
|
664
|
+
expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-token-123');
|
|
665
|
+
// Should NOT log organization validation errors
|
|
666
|
+
expect(logger.error).not.toHaveBeenCalledWith(
|
|
667
|
+
expect.stringContaining('Invalid organisation ID')
|
|
668
|
+
);
|
|
669
|
+
expect(logger.error).not.toHaveBeenCalledWith(
|
|
670
|
+
expect.stringContaining('Organisation ID is required')
|
|
671
|
+
);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it('should work even when app context has no organization', async () => {
|
|
675
|
+
mockGetAppContext.mockReturnValue({} as any);
|
|
676
|
+
|
|
677
|
+
const request = {
|
|
678
|
+
path: '/global-tool',
|
|
679
|
+
bodyJSON: {
|
|
680
|
+
auth: {
|
|
681
|
+
provider: 'OptiID',
|
|
682
|
+
credentials: {
|
|
683
|
+
customer_id: 'any-org',
|
|
684
|
+
access_token: 'valid-token-123'
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
473
689
|
|
|
474
|
-
|
|
475
|
-
// Act
|
|
476
|
-
const result = AuthUtils.validateOrganizationId(' ');
|
|
690
|
+
const result = await authorizeGlobalRequest(request);
|
|
477
691
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
expect(logger.error).toHaveBeenCalledWith(
|
|
482
|
-
'Invalid organisation ID: expected app-org-123, received '
|
|
483
|
-
);
|
|
692
|
+
expect(result).toBe(true);
|
|
693
|
+
expect(mockTokenVerifier.verify).toHaveBeenCalledWith('valid-token-123');
|
|
694
|
+
});
|
|
484
695
|
});
|
|
485
696
|
});
|
|
486
697
|
|
|
487
|
-
describe('
|
|
488
|
-
it('should handle
|
|
489
|
-
// Arrange
|
|
698
|
+
describe('edge cases and error handling', () => {
|
|
699
|
+
it('should handle requests with null bodyJSON', async () => {
|
|
490
700
|
const request = {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
provider: 'OptiID',
|
|
494
|
-
credentials: {
|
|
495
|
-
access_token: 'valid-access-token',
|
|
496
|
-
customer_id: 'app-org-123',
|
|
497
|
-
instance_id: 'instance-456',
|
|
498
|
-
product_sku: 'OPAL'
|
|
499
|
-
}
|
|
500
|
-
} as OptiIdAuthData
|
|
501
|
-
}
|
|
701
|
+
path: '/some-tool',
|
|
702
|
+
bodyJSON: null
|
|
502
703
|
};
|
|
503
704
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
organizationId: 'app-org-123'
|
|
507
|
-
}
|
|
508
|
-
} as any);
|
|
705
|
+
const result1 = await authorizeRegularRequest(request);
|
|
706
|
+
const result2 = await authorizeGlobalRequest(request);
|
|
509
707
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const authInfo = AuthUtils.extractAuthData(request);
|
|
514
|
-
const isValidOrg = authInfo
|
|
515
|
-
? AuthUtils.validateOrganizationId(authInfo.authData.credentials?.customer_id)
|
|
516
|
-
: false;
|
|
517
|
-
const isValidToken = authInfo ? await AuthUtils.validateAccessToken(authInfo.accessToken) : false;
|
|
518
|
-
|
|
519
|
-
// Assert
|
|
520
|
-
expect(authInfo).not.toBeNull();
|
|
521
|
-
expect(isValidOrg).toBe(true);
|
|
522
|
-
expect(isValidToken).toBe(true);
|
|
523
|
-
expect(logger.error).not.toHaveBeenCalled();
|
|
708
|
+
expect(result1).toBe(false);
|
|
709
|
+
expect(result2).toBe(false);
|
|
710
|
+
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
524
711
|
});
|
|
525
712
|
|
|
526
|
-
it('should handle
|
|
527
|
-
// Arrange
|
|
713
|
+
it('should handle malformed auth objects', async () => {
|
|
528
714
|
const request = {
|
|
715
|
+
path: '/some-tool',
|
|
529
716
|
bodyJSON: {
|
|
530
|
-
auth:
|
|
531
|
-
provider: 'SomeOtherProvider',
|
|
532
|
-
credentials: {
|
|
533
|
-
access_token: 'valid-access-token',
|
|
534
|
-
customer_id: 'app-org-123',
|
|
535
|
-
instance_id: 'instance-456',
|
|
536
|
-
product_sku: 'OPAL'
|
|
537
|
-
}
|
|
538
|
-
} as OptiIdAuthData
|
|
717
|
+
auth: 'invalid-auth-format'
|
|
539
718
|
}
|
|
540
719
|
};
|
|
541
720
|
|
|
542
|
-
|
|
543
|
-
const
|
|
721
|
+
const result1 = await authorizeRegularRequest(request);
|
|
722
|
+
const result2 = await authorizeGlobalRequest(request);
|
|
544
723
|
|
|
545
|
-
|
|
546
|
-
expect(
|
|
724
|
+
expect(result1).toBe(false);
|
|
725
|
+
expect(result2).toBe(false);
|
|
547
726
|
expect(logger.error).toHaveBeenCalledWith('OptiID token is required but not provided');
|
|
548
727
|
});
|
|
549
|
-
|
|
550
|
-
it('should handle complete authentication flow for organization mismatch', async () => {
|
|
551
|
-
// Arrange
|
|
552
|
-
const request = {
|
|
553
|
-
bodyJSON: {
|
|
554
|
-
auth: {
|
|
555
|
-
provider: 'OptiID',
|
|
556
|
-
credentials: {
|
|
557
|
-
access_token: 'valid-access-token',
|
|
558
|
-
customer_id: 'different-org-456',
|
|
559
|
-
instance_id: 'instance-456',
|
|
560
|
-
product_sku: 'OPAL'
|
|
561
|
-
}
|
|
562
|
-
} as OptiIdAuthData
|
|
563
|
-
}
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
mockGetAppContext.mockReturnValue({
|
|
567
|
-
account: {
|
|
568
|
-
organizationId: 'app-org-123'
|
|
569
|
-
}
|
|
570
|
-
} as any);
|
|
571
|
-
|
|
572
|
-
// Act
|
|
573
|
-
const authInfo = AuthUtils.extractAuthData(request);
|
|
574
|
-
const isValidOrg = authInfo
|
|
575
|
-
? AuthUtils.validateOrganizationId(authInfo.authData.credentials?.customer_id)
|
|
576
|
-
: false;
|
|
577
|
-
|
|
578
|
-
// Assert
|
|
579
|
-
expect(authInfo).not.toBeNull();
|
|
580
|
-
expect(isValidOrg).toBe(false);
|
|
581
|
-
expect(logger.error).toHaveBeenCalledWith(
|
|
582
|
-
'Invalid organisation ID: expected app-org-123, received different-org-456'
|
|
583
|
-
);
|
|
584
|
-
});
|
|
585
728
|
});
|
|
586
729
|
});
|