@forestadmin/mcp-server 0.1.0
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 +128 -0
- package/dist/__mocks__/version.d.ts +3 -0
- package/dist/__mocks__/version.js +7 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +14 -0
- package/dist/factory.d.ts +51 -0
- package/dist/factory.js +40 -0
- package/dist/forest-oauth-provider.d.ts +44 -0
- package/dist/forest-oauth-provider.js +253 -0
- package/dist/forest-oauth-provider.test.d.ts +2 -0
- package/dist/forest-oauth-provider.test.js +590 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +13 -0
- package/dist/mcp-paths.d.ts +5 -0
- package/dist/mcp-paths.js +11 -0
- package/dist/polyfills.d.ts +12 -0
- package/dist/polyfills.js +27 -0
- package/dist/schemas/filter.d.ts +4 -0
- package/dist/schemas/filter.js +70 -0
- package/dist/schemas/filter.test.d.ts +2 -0
- package/dist/schemas/filter.test.js +234 -0
- package/dist/server.d.ts +87 -0
- package/dist/server.js +341 -0
- package/dist/server.test.d.ts +2 -0
- package/dist/server.test.js +901 -0
- package/dist/test-utils/mock-server.d.ts +62 -0
- package/dist/test-utils/mock-server.js +187 -0
- package/dist/tools/list.d.ts +4 -0
- package/dist/tools/list.js +98 -0
- package/dist/tools/list.test.d.ts +2 -0
- package/dist/tools/list.test.js +385 -0
- package/dist/utils/activity-logs-creator.d.ts +9 -0
- package/dist/utils/activity-logs-creator.js +65 -0
- package/dist/utils/activity-logs-creator.test.d.ts +2 -0
- package/dist/utils/activity-logs-creator.test.js +239 -0
- package/dist/utils/agent-caller.d.ts +13 -0
- package/dist/utils/agent-caller.js +24 -0
- package/dist/utils/agent-caller.test.d.ts +2 -0
- package/dist/utils/agent-caller.test.js +102 -0
- package/dist/utils/error-parser.d.ts +10 -0
- package/dist/utils/error-parser.js +56 -0
- package/dist/utils/error-parser.test.d.ts +2 -0
- package/dist/utils/error-parser.test.js +124 -0
- package/dist/utils/schema-fetcher.d.ts +53 -0
- package/dist/utils/schema-fetcher.js +85 -0
- package/dist/utils/schema-fetcher.test.d.ts +2 -0
- package/dist/utils/schema-fetcher.test.js +212 -0
- package/dist/utils/sse-error-logger.d.ts +14 -0
- package/dist/utils/sse-error-logger.js +112 -0
- package/dist/utils/tool-with-logging.d.ts +44 -0
- package/dist/utils/tool-with-logging.js +66 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +43 -0
- package/package.json +49 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const forestadmin_client_1 = __importDefault(require("@forestadmin/forestadmin-client"));
|
|
7
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
|
+
const forest_oauth_provider_1 = __importDefault(require("./forest-oauth-provider"));
|
|
9
|
+
const mock_server_1 = __importDefault(require("./test-utils/mock-server"));
|
|
10
|
+
jest.mock('jsonwebtoken');
|
|
11
|
+
jest.mock('@forestadmin/forestadmin-client');
|
|
12
|
+
const mockCreateForestAdminClient = forestadmin_client_1.default;
|
|
13
|
+
const mockJwtDecode = jsonwebtoken_1.default.decode;
|
|
14
|
+
const mockJwtSign = jsonwebtoken_1.default.sign;
|
|
15
|
+
const TEST_ENV_SECRET = 'test-env-secret';
|
|
16
|
+
const TEST_AUTH_SECRET = 'test-auth-secret';
|
|
17
|
+
const TEST_FOREST_APP_URL = 'https://app.forestadmin.com';
|
|
18
|
+
function createProvider(forestServerUrl = 'https://api.forestadmin.com') {
|
|
19
|
+
return new forest_oauth_provider_1.default({
|
|
20
|
+
forestServerUrl,
|
|
21
|
+
forestAppUrl: TEST_FOREST_APP_URL,
|
|
22
|
+
envSecret: TEST_ENV_SECRET,
|
|
23
|
+
authSecret: TEST_AUTH_SECRET,
|
|
24
|
+
logger: console.info,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
describe('ForestOAuthProvider', () => {
|
|
28
|
+
let originalEnv;
|
|
29
|
+
let mockServer;
|
|
30
|
+
const originalFetch = global.fetch;
|
|
31
|
+
beforeAll(() => {
|
|
32
|
+
originalEnv = { ...process.env };
|
|
33
|
+
});
|
|
34
|
+
afterAll(() => {
|
|
35
|
+
process.env = originalEnv;
|
|
36
|
+
global.fetch = originalFetch;
|
|
37
|
+
});
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
process.env.FOREST_ENV_SECRET = TEST_ENV_SECRET;
|
|
40
|
+
process.env.FOREST_AUTH_SECRET = TEST_AUTH_SECRET;
|
|
41
|
+
mockServer = new mock_server_1.default();
|
|
42
|
+
});
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
mockServer.reset();
|
|
45
|
+
});
|
|
46
|
+
describe('constructor', () => {
|
|
47
|
+
it('should create instance with forestServerUrl', () => {
|
|
48
|
+
const customProvider = createProvider('https://custom.forestadmin.com');
|
|
49
|
+
expect(customProvider).toBeDefined();
|
|
50
|
+
});
|
|
51
|
+
it('should create instance with custom forestAppUrl', () => {
|
|
52
|
+
const customProvider = new forest_oauth_provider_1.default({
|
|
53
|
+
forestServerUrl: 'https://api.forestadmin.com',
|
|
54
|
+
forestAppUrl: 'https://custom-app.forestadmin.com',
|
|
55
|
+
envSecret: TEST_ENV_SECRET,
|
|
56
|
+
authSecret: TEST_AUTH_SECRET,
|
|
57
|
+
logger: console.info,
|
|
58
|
+
});
|
|
59
|
+
expect(customProvider).toBeDefined();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('initialize', () => {
|
|
63
|
+
it('should not throw when envSecret is empty string', async () => {
|
|
64
|
+
const customProvider = new forest_oauth_provider_1.default({
|
|
65
|
+
forestServerUrl: 'https://api.forestadmin.com',
|
|
66
|
+
forestAppUrl: TEST_FOREST_APP_URL,
|
|
67
|
+
envSecret: '',
|
|
68
|
+
authSecret: TEST_AUTH_SECRET,
|
|
69
|
+
logger: console.info,
|
|
70
|
+
});
|
|
71
|
+
await expect(customProvider.initialize()).resolves.not.toThrow();
|
|
72
|
+
});
|
|
73
|
+
it('should fetch environmentId from Forest Admin API', async () => {
|
|
74
|
+
mockServer.get('/liana/environment', {
|
|
75
|
+
data: { id: '98765', attributes: { api_endpoint: 'https://api.example.com' } },
|
|
76
|
+
});
|
|
77
|
+
global.fetch = mockServer.fetch;
|
|
78
|
+
const testProvider = createProvider();
|
|
79
|
+
await testProvider.initialize();
|
|
80
|
+
// Verify fetch was called with correct URL and headers
|
|
81
|
+
expect(mockServer.fetch).toHaveBeenCalledWith('https://api.forestadmin.com/liana/environment', expect.objectContaining({
|
|
82
|
+
method: 'GET',
|
|
83
|
+
headers: expect.objectContaining({
|
|
84
|
+
'forest-secret-key': 'test-env-secret',
|
|
85
|
+
'Content-Type': 'application/json',
|
|
86
|
+
}),
|
|
87
|
+
}));
|
|
88
|
+
});
|
|
89
|
+
it('should set environmentId after successful initialization', async () => {
|
|
90
|
+
mockServer.get('/liana/environment', {
|
|
91
|
+
data: { id: '54321', attributes: { api_endpoint: 'https://api.example.com' } },
|
|
92
|
+
});
|
|
93
|
+
global.fetch = mockServer.fetch;
|
|
94
|
+
const testProvider = createProvider();
|
|
95
|
+
await testProvider.initialize();
|
|
96
|
+
// Verify environmentId is set by checking authorize redirect includes it
|
|
97
|
+
const mockResponse = { redirect: jest.fn() };
|
|
98
|
+
const mockClient = {
|
|
99
|
+
client_id: 'test-client',
|
|
100
|
+
redirect_uris: ['https://example.com/callback'],
|
|
101
|
+
};
|
|
102
|
+
await testProvider.authorize(mockClient, {
|
|
103
|
+
redirectUri: 'https://example.com/callback',
|
|
104
|
+
codeChallenge: 'challenge',
|
|
105
|
+
state: 'state',
|
|
106
|
+
scopes: ['mcp:read'],
|
|
107
|
+
resource: new URL('https://localhost:3931'),
|
|
108
|
+
}, mockResponse);
|
|
109
|
+
const redirectUrl = new URL(mockResponse.redirect.mock.calls[0][0]);
|
|
110
|
+
expect(redirectUrl.searchParams.get('environmentId')).toBe('54321');
|
|
111
|
+
});
|
|
112
|
+
it('should handle non-OK response from Forest Admin API', async () => {
|
|
113
|
+
mockServer.get('/liana/environment', { error: 'Unauthorized' }, 401);
|
|
114
|
+
global.fetch = mockServer.fetch;
|
|
115
|
+
const loggerSpy = jest.fn();
|
|
116
|
+
const testProvider = new forest_oauth_provider_1.default({
|
|
117
|
+
forestServerUrl: 'https://api.forestadmin.com',
|
|
118
|
+
forestAppUrl: TEST_FOREST_APP_URL,
|
|
119
|
+
envSecret: TEST_ENV_SECRET,
|
|
120
|
+
authSecret: TEST_AUTH_SECRET,
|
|
121
|
+
logger: loggerSpy,
|
|
122
|
+
});
|
|
123
|
+
await testProvider.initialize();
|
|
124
|
+
expect(loggerSpy).toHaveBeenCalledWith('Warn', expect.stringContaining('Failed to fetch environmentId from Forest Admin API'));
|
|
125
|
+
});
|
|
126
|
+
it('should handle fetch network errors gracefully', async () => {
|
|
127
|
+
global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));
|
|
128
|
+
const loggerSpy = jest.fn();
|
|
129
|
+
const testProvider = new forest_oauth_provider_1.default({
|
|
130
|
+
forestServerUrl: 'https://api.forestadmin.com',
|
|
131
|
+
forestAppUrl: TEST_FOREST_APP_URL,
|
|
132
|
+
envSecret: TEST_ENV_SECRET,
|
|
133
|
+
authSecret: TEST_AUTH_SECRET,
|
|
134
|
+
logger: loggerSpy,
|
|
135
|
+
});
|
|
136
|
+
await testProvider.initialize();
|
|
137
|
+
expect(loggerSpy).toHaveBeenCalledWith('Warn', expect.stringContaining('Failed to fetch environmentId from Forest Admin API'));
|
|
138
|
+
});
|
|
139
|
+
it('should use correct forest server URL for API call', async () => {
|
|
140
|
+
mockServer.get('/liana/environment', {
|
|
141
|
+
data: { id: '11111', attributes: { api_endpoint: 'https://api.example.com' } },
|
|
142
|
+
});
|
|
143
|
+
global.fetch = mockServer.fetch;
|
|
144
|
+
const testProvider = createProvider('https://custom.forestadmin.com');
|
|
145
|
+
await testProvider.initialize();
|
|
146
|
+
expect(mockServer.fetch).toHaveBeenCalledWith('https://custom.forestadmin.com/liana/environment', expect.any(Object));
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe('clientsStore.getClient', () => {
|
|
150
|
+
it('should fetch client from Forest Admin API', async () => {
|
|
151
|
+
const clientData = {
|
|
152
|
+
client_id: 'test-client-123',
|
|
153
|
+
redirect_uris: ['https://example.com/callback'],
|
|
154
|
+
client_name: 'Test Client',
|
|
155
|
+
};
|
|
156
|
+
mockServer.get('/oauth/register/test-client-123', clientData);
|
|
157
|
+
global.fetch = mockServer.fetch;
|
|
158
|
+
const provider = createProvider();
|
|
159
|
+
const client = await provider.clientsStore.getClient('test-client-123');
|
|
160
|
+
expect(client).toEqual(clientData);
|
|
161
|
+
expect(mockServer.fetch).toHaveBeenCalledWith('https://api.forestadmin.com/oauth/register/test-client-123', expect.objectContaining({
|
|
162
|
+
method: 'GET',
|
|
163
|
+
headers: expect.objectContaining({
|
|
164
|
+
'Content-Type': 'application/json',
|
|
165
|
+
}),
|
|
166
|
+
}));
|
|
167
|
+
});
|
|
168
|
+
it('should return undefined when client is not found', async () => {
|
|
169
|
+
mockServer.get('/oauth/register/unknown-client', { error: 'Not found' }, 404);
|
|
170
|
+
global.fetch = mockServer.fetch;
|
|
171
|
+
const provider = createProvider();
|
|
172
|
+
const client = await provider.clientsStore.getClient('unknown-client');
|
|
173
|
+
expect(client).toBeUndefined();
|
|
174
|
+
});
|
|
175
|
+
it('should return undefined on server error', async () => {
|
|
176
|
+
mockServer.get('/oauth/register/error-client', { error: 'Internal error' }, 500);
|
|
177
|
+
global.fetch = mockServer.fetch;
|
|
178
|
+
const provider = createProvider();
|
|
179
|
+
const client = await provider.clientsStore.getClient('error-client');
|
|
180
|
+
expect(client).toBeUndefined();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
describe('authorize', () => {
|
|
184
|
+
let mockResponse;
|
|
185
|
+
let mockClient;
|
|
186
|
+
let initializedProvider;
|
|
187
|
+
beforeEach(async () => {
|
|
188
|
+
mockResponse = {
|
|
189
|
+
redirect: jest.fn(),
|
|
190
|
+
};
|
|
191
|
+
mockClient = {
|
|
192
|
+
client_id: 'test-client-id',
|
|
193
|
+
redirect_uris: ['https://example.com/callback'],
|
|
194
|
+
};
|
|
195
|
+
// Create provider and mock the fetch to set environmentId
|
|
196
|
+
initializedProvider = createProvider();
|
|
197
|
+
// Mock fetch to return a valid response
|
|
198
|
+
const mockFetch = jest.fn().mockResolvedValue({
|
|
199
|
+
ok: true,
|
|
200
|
+
json: () => Promise.resolve({
|
|
201
|
+
data: { id: '12345', attributes: { api_endpoint: 'https://api.example.com' } },
|
|
202
|
+
}),
|
|
203
|
+
});
|
|
204
|
+
global.fetch = mockFetch;
|
|
205
|
+
await initializedProvider.initialize();
|
|
206
|
+
});
|
|
207
|
+
afterEach(() => {
|
|
208
|
+
jest.restoreAllMocks();
|
|
209
|
+
});
|
|
210
|
+
it('should redirect to Forest Admin authentication URL', async () => {
|
|
211
|
+
await initializedProvider.authorize(mockClient, {
|
|
212
|
+
redirectUri: 'https://example.com/callback',
|
|
213
|
+
codeChallenge: 'test-code-challenge',
|
|
214
|
+
state: 'test-state',
|
|
215
|
+
scopes: ['mcp:read', 'profile'],
|
|
216
|
+
resource: new URL('https://localhost:3931'),
|
|
217
|
+
}, mockResponse);
|
|
218
|
+
expect(mockResponse.redirect).toHaveBeenCalledWith(expect.stringContaining('https://app.forestadmin.com/oauth/authorize'));
|
|
219
|
+
});
|
|
220
|
+
it('should include all required query parameters in redirect URL', async () => {
|
|
221
|
+
await initializedProvider.authorize(mockClient, {
|
|
222
|
+
redirectUri: 'https://example.com/callback',
|
|
223
|
+
codeChallenge: 'test-code-challenge',
|
|
224
|
+
state: 'test-state',
|
|
225
|
+
scopes: ['mcp:read', 'profile'],
|
|
226
|
+
resource: new URL('https://localhost:3931'),
|
|
227
|
+
}, mockResponse);
|
|
228
|
+
const redirectCall = mockResponse.redirect.mock.calls[0][0];
|
|
229
|
+
const url = new URL(redirectCall);
|
|
230
|
+
expect(url.hostname).toBe('app.forestadmin.com');
|
|
231
|
+
expect(url.pathname).toBe('/oauth/authorize');
|
|
232
|
+
expect(url.searchParams.get('redirect_uri')).toBe('https://example.com/callback');
|
|
233
|
+
expect(url.searchParams.get('code_challenge')).toBe('test-code-challenge');
|
|
234
|
+
expect(url.searchParams.get('code_challenge_method')).toBe('S256');
|
|
235
|
+
expect(url.searchParams.get('response_type')).toBe('code');
|
|
236
|
+
expect(url.searchParams.get('client_id')).toBe('test-client-id');
|
|
237
|
+
expect(url.searchParams.get('state')).toBe('test-state');
|
|
238
|
+
expect(url.searchParams.get('scope')).toBe('mcp:read+profile');
|
|
239
|
+
expect(url.searchParams.get('environmentId')).toBe('12345');
|
|
240
|
+
});
|
|
241
|
+
it('should redirect to error URL when environmentId is not set', async () => {
|
|
242
|
+
// Create a provider without initializing (environmentId is undefined)
|
|
243
|
+
const uninitializedProvider = createProvider();
|
|
244
|
+
await uninitializedProvider.authorize(mockClient, {
|
|
245
|
+
redirectUri: 'https://example.com/callback',
|
|
246
|
+
codeChallenge: 'test-code-challenge',
|
|
247
|
+
state: 'test-state',
|
|
248
|
+
scopes: ['mcp:read'],
|
|
249
|
+
resource: new URL('https://localhost:3931'),
|
|
250
|
+
}, mockResponse);
|
|
251
|
+
const redirectCall = mockResponse.redirect.mock.calls[0][0];
|
|
252
|
+
expect(redirectCall).toContain('https://example.com/callback');
|
|
253
|
+
expect(redirectCall).toContain('error=server_error');
|
|
254
|
+
});
|
|
255
|
+
it('should use custom forestAppUrl for redirect', async () => {
|
|
256
|
+
const customAppUrl = 'https://custom-app.forestadmin.com';
|
|
257
|
+
const customProvider = new forest_oauth_provider_1.default({
|
|
258
|
+
forestServerUrl: 'https://api.forestadmin.com',
|
|
259
|
+
forestAppUrl: customAppUrl,
|
|
260
|
+
envSecret: TEST_ENV_SECRET,
|
|
261
|
+
authSecret: TEST_AUTH_SECRET,
|
|
262
|
+
logger: console.info,
|
|
263
|
+
});
|
|
264
|
+
// Mock fetch to return a valid response for initialize
|
|
265
|
+
global.fetch = jest.fn().mockResolvedValue({
|
|
266
|
+
ok: true,
|
|
267
|
+
json: () => Promise.resolve({
|
|
268
|
+
data: { id: '12345', attributes: { api_endpoint: 'https://api.example.com' } },
|
|
269
|
+
}),
|
|
270
|
+
});
|
|
271
|
+
await customProvider.initialize();
|
|
272
|
+
await customProvider.authorize(mockClient, {
|
|
273
|
+
redirectUri: 'https://example.com/callback',
|
|
274
|
+
codeChallenge: 'test-code-challenge',
|
|
275
|
+
state: 'test-state',
|
|
276
|
+
scopes: ['mcp:read'],
|
|
277
|
+
resource: new URL('https://localhost:3931'),
|
|
278
|
+
}, mockResponse);
|
|
279
|
+
const redirectCall = mockResponse.redirect.mock.calls[0][0];
|
|
280
|
+
const url = new URL(redirectCall);
|
|
281
|
+
expect(url.hostname).toBe('custom-app.forestadmin.com');
|
|
282
|
+
expect(url.pathname).toBe('/oauth/authorize');
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
describe('exchangeAuthorizationCode', () => {
|
|
286
|
+
let mockClient;
|
|
287
|
+
let mockGetUserInfo;
|
|
288
|
+
beforeEach(() => {
|
|
289
|
+
mockClient = {
|
|
290
|
+
client_id: 'test-client-id',
|
|
291
|
+
redirect_uris: ['https://example.com/callback'],
|
|
292
|
+
scope: 'mcp:read mcp:write',
|
|
293
|
+
};
|
|
294
|
+
// Setup mock for forestAdminClient
|
|
295
|
+
mockGetUserInfo = jest.fn().mockResolvedValue({
|
|
296
|
+
id: 123,
|
|
297
|
+
email: 'user@example.com',
|
|
298
|
+
firstName: 'Test',
|
|
299
|
+
lastName: 'User',
|
|
300
|
+
team: 'Operations',
|
|
301
|
+
role: 'Admin',
|
|
302
|
+
tags: {},
|
|
303
|
+
renderingId: 456,
|
|
304
|
+
permissionLevel: 'admin',
|
|
305
|
+
});
|
|
306
|
+
mockCreateForestAdminClient.mockReturnValue({
|
|
307
|
+
authService: {
|
|
308
|
+
getUserInfo: mockGetUserInfo,
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
// Setup mock for jsonwebtoken - decode is called twice:
|
|
312
|
+
// first for access token, then for refresh token
|
|
313
|
+
const now = Math.floor(Date.now() / 1000);
|
|
314
|
+
mockJwtDecode
|
|
315
|
+
.mockReturnValueOnce({
|
|
316
|
+
meta: { renderingId: 456 },
|
|
317
|
+
exp: now + 3600, // expires in 1 hour
|
|
318
|
+
iat: now,
|
|
319
|
+
scope: 'mcp:read',
|
|
320
|
+
})
|
|
321
|
+
.mockReturnValueOnce({
|
|
322
|
+
exp: now + 604800, // expires in 7 days
|
|
323
|
+
iat: now,
|
|
324
|
+
});
|
|
325
|
+
mockJwtSign.mockReturnValue('mocked-jwt-token');
|
|
326
|
+
});
|
|
327
|
+
afterEach(() => {
|
|
328
|
+
jest.clearAllMocks();
|
|
329
|
+
});
|
|
330
|
+
it('should exchange authorization code with Forest Admin server', async () => {
|
|
331
|
+
mockServer
|
|
332
|
+
.get('/liana/environment', {
|
|
333
|
+
data: { id: '12345', attributes: { api_endpoint: 'https://api.example.com' } },
|
|
334
|
+
})
|
|
335
|
+
.post('/oauth/token', {
|
|
336
|
+
access_token: 'forest-access-token',
|
|
337
|
+
refresh_token: 'forest-refresh-token',
|
|
338
|
+
expires_in: 3600,
|
|
339
|
+
token_type: 'Bearer',
|
|
340
|
+
scope: 'mcp:read',
|
|
341
|
+
});
|
|
342
|
+
global.fetch = mockServer.fetch;
|
|
343
|
+
const provider = createProvider();
|
|
344
|
+
const result = await provider.exchangeAuthorizationCode(mockClient, 'auth-code-123', 'code-verifier-456', 'https://example.com/callback');
|
|
345
|
+
expect(mockServer.fetch).toHaveBeenCalledWith('https://api.forestadmin.com/oauth/token', expect.objectContaining({
|
|
346
|
+
method: 'POST',
|
|
347
|
+
headers: expect.objectContaining({
|
|
348
|
+
'Content-Type': 'application/json',
|
|
349
|
+
'forest-secret-key': 'test-env-secret',
|
|
350
|
+
}),
|
|
351
|
+
body: JSON.stringify({
|
|
352
|
+
grant_type: 'authorization_code',
|
|
353
|
+
code: 'auth-code-123',
|
|
354
|
+
redirect_uri: 'https://example.com/callback',
|
|
355
|
+
client_id: 'test-client-id',
|
|
356
|
+
code_verifier: 'code-verifier-456',
|
|
357
|
+
}),
|
|
358
|
+
}));
|
|
359
|
+
expect(result.access_token).toBe('mocked-jwt-token');
|
|
360
|
+
expect(result.refresh_token).toBe('mocked-jwt-token');
|
|
361
|
+
expect(result.token_type).toBe('Bearer');
|
|
362
|
+
// expires_in is calculated as exp - now, so it should be approximately 3600
|
|
363
|
+
expect(result.expires_in).toBeGreaterThan(3590);
|
|
364
|
+
expect(result.expires_in).toBeLessThanOrEqual(3600);
|
|
365
|
+
expect(result.scope).toBe('mcp:read');
|
|
366
|
+
expect(mockJwtDecode).toHaveBeenCalledWith('forest-access-token');
|
|
367
|
+
expect(mockJwtDecode).toHaveBeenCalledWith('forest-refresh-token');
|
|
368
|
+
expect(mockGetUserInfo).toHaveBeenCalledWith(456, 'forest-access-token');
|
|
369
|
+
// First call: access token - expiresIn is calculated as exp - now, so it's approximately 3600
|
|
370
|
+
expect(mockJwtSign).toHaveBeenCalledWith(expect.objectContaining({
|
|
371
|
+
id: 123,
|
|
372
|
+
email: 'user@example.com',
|
|
373
|
+
serverToken: 'forest-access-token',
|
|
374
|
+
}), 'test-auth-secret', { expiresIn: expect.any(Number) });
|
|
375
|
+
// Second call: refresh token - expiresIn is calculated as exp - now, so it's approximately 604800
|
|
376
|
+
expect(mockJwtSign).toHaveBeenCalledWith(expect.objectContaining({
|
|
377
|
+
type: 'refresh',
|
|
378
|
+
clientId: 'test-client-id',
|
|
379
|
+
userId: 123,
|
|
380
|
+
renderingId: 456,
|
|
381
|
+
serverRefreshToken: 'forest-refresh-token',
|
|
382
|
+
}), 'test-auth-secret', { expiresIn: expect.any(Number) });
|
|
383
|
+
});
|
|
384
|
+
it('should throw error when token exchange fails', async () => {
|
|
385
|
+
mockServer
|
|
386
|
+
.get('/liana/environment', {
|
|
387
|
+
data: { id: '12345', attributes: { api_endpoint: 'https://api.example.com' } },
|
|
388
|
+
})
|
|
389
|
+
.post('/oauth/token', { error: 'invalid_grant' }, 400);
|
|
390
|
+
global.fetch = mockServer.fetch;
|
|
391
|
+
const provider = createProvider();
|
|
392
|
+
await expect(provider.exchangeAuthorizationCode(mockClient, 'invalid-code', 'code-verifier', 'https://example.com/callback')).rejects.toThrow('Failed to exchange authorization code');
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
describe('exchangeRefreshToken', () => {
|
|
396
|
+
let mockClient;
|
|
397
|
+
let mockGetUserInfo;
|
|
398
|
+
beforeEach(() => {
|
|
399
|
+
mockClient = {
|
|
400
|
+
client_id: 'test-client-id',
|
|
401
|
+
redirect_uris: ['https://example.com/callback'],
|
|
402
|
+
scope: 'mcp:read mcp:write',
|
|
403
|
+
};
|
|
404
|
+
mockGetUserInfo = jest.fn().mockResolvedValue({
|
|
405
|
+
id: 123,
|
|
406
|
+
email: 'user@example.com',
|
|
407
|
+
firstName: 'Test',
|
|
408
|
+
lastName: 'User',
|
|
409
|
+
team: 'Operations',
|
|
410
|
+
role: 'Admin',
|
|
411
|
+
tags: {},
|
|
412
|
+
renderingId: 456,
|
|
413
|
+
permissionLevel: 'admin',
|
|
414
|
+
});
|
|
415
|
+
mockCreateForestAdminClient.mockReturnValue({
|
|
416
|
+
authService: {
|
|
417
|
+
getUserInfo: mockGetUserInfo,
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
mockJwtSign.mockReturnValue('new-mocked-jwt-token');
|
|
421
|
+
});
|
|
422
|
+
afterEach(() => {
|
|
423
|
+
jest.clearAllMocks();
|
|
424
|
+
});
|
|
425
|
+
it('should exchange refresh token for new tokens', async () => {
|
|
426
|
+
// Mock jwt.verify to return decoded refresh token
|
|
427
|
+
jsonwebtoken_1.default.verify.mockReturnValue({
|
|
428
|
+
type: 'refresh',
|
|
429
|
+
clientId: 'test-client-id',
|
|
430
|
+
userId: 123,
|
|
431
|
+
renderingId: 456,
|
|
432
|
+
serverRefreshToken: 'forest-refresh-token',
|
|
433
|
+
});
|
|
434
|
+
// Mock jwt.decode - called twice: first for access token, then for refresh token
|
|
435
|
+
const now = Math.floor(Date.now() / 1000);
|
|
436
|
+
mockJwtDecode
|
|
437
|
+
.mockReturnValueOnce({
|
|
438
|
+
meta: { renderingId: 456 },
|
|
439
|
+
exp: now + 3600, // expires in 1 hour
|
|
440
|
+
iat: now,
|
|
441
|
+
scope: 'mcp:read',
|
|
442
|
+
})
|
|
443
|
+
.mockReturnValueOnce({
|
|
444
|
+
exp: now + 604800, // expires in 7 days
|
|
445
|
+
iat: now,
|
|
446
|
+
});
|
|
447
|
+
mockServer.post('/oauth/token', {
|
|
448
|
+
access_token: 'new-forest-access-token',
|
|
449
|
+
refresh_token: 'new-forest-refresh-token',
|
|
450
|
+
expires_in: 3600,
|
|
451
|
+
token_type: 'Bearer',
|
|
452
|
+
scope: 'mcp:read',
|
|
453
|
+
});
|
|
454
|
+
global.fetch = mockServer.fetch;
|
|
455
|
+
const provider = createProvider();
|
|
456
|
+
const result = await provider.exchangeRefreshToken(mockClient, 'valid-refresh-token');
|
|
457
|
+
expect(result.access_token).toBe('new-mocked-jwt-token');
|
|
458
|
+
expect(result.refresh_token).toBe('new-mocked-jwt-token');
|
|
459
|
+
expect(result.token_type).toBe('Bearer');
|
|
460
|
+
// expires_in is calculated as exp - now, so it should be approximately 3600
|
|
461
|
+
expect(result.expires_in).toBeGreaterThan(3590);
|
|
462
|
+
expect(result.expires_in).toBeLessThanOrEqual(3600);
|
|
463
|
+
expect(mockServer.fetch).toHaveBeenCalledWith('https://api.forestadmin.com/oauth/token', expect.objectContaining({
|
|
464
|
+
method: 'POST',
|
|
465
|
+
body: JSON.stringify({
|
|
466
|
+
grant_type: 'refresh_token',
|
|
467
|
+
refresh_token: 'forest-refresh-token',
|
|
468
|
+
client_id: 'test-client-id',
|
|
469
|
+
}),
|
|
470
|
+
}));
|
|
471
|
+
});
|
|
472
|
+
it('should throw error for invalid refresh token', async () => {
|
|
473
|
+
jsonwebtoken_1.default.verify.mockImplementation(() => {
|
|
474
|
+
throw new Error('invalid signature');
|
|
475
|
+
});
|
|
476
|
+
const provider = createProvider();
|
|
477
|
+
await expect(provider.exchangeRefreshToken(mockClient, 'invalid-refresh-token')).rejects.toThrow('Invalid or expired refresh token');
|
|
478
|
+
});
|
|
479
|
+
it('should throw error when token type is not refresh', async () => {
|
|
480
|
+
jsonwebtoken_1.default.verify.mockReturnValue({
|
|
481
|
+
type: 'access',
|
|
482
|
+
clientId: 'test-client-id',
|
|
483
|
+
});
|
|
484
|
+
const provider = createProvider();
|
|
485
|
+
await expect(provider.exchangeRefreshToken(mockClient, 'access-token')).rejects.toThrow('Invalid token type');
|
|
486
|
+
});
|
|
487
|
+
it('should throw error when client_id does not match', async () => {
|
|
488
|
+
jsonwebtoken_1.default.verify.mockReturnValue({
|
|
489
|
+
type: 'refresh',
|
|
490
|
+
clientId: 'different-client-id',
|
|
491
|
+
userId: 123,
|
|
492
|
+
renderingId: 456,
|
|
493
|
+
serverRefreshToken: 'forest-refresh-token',
|
|
494
|
+
});
|
|
495
|
+
const provider = createProvider();
|
|
496
|
+
await expect(provider.exchangeRefreshToken(mockClient, 'refresh-token-for-different-client')).rejects.toThrow('Token was not issued to this client');
|
|
497
|
+
});
|
|
498
|
+
it('should throw error when Forest Admin refresh fails', async () => {
|
|
499
|
+
jsonwebtoken_1.default.verify.mockReturnValue({
|
|
500
|
+
type: 'refresh',
|
|
501
|
+
clientId: 'test-client-id',
|
|
502
|
+
userId: 123,
|
|
503
|
+
renderingId: 456,
|
|
504
|
+
serverRefreshToken: 'expired-forest-refresh-token',
|
|
505
|
+
});
|
|
506
|
+
mockServer.post('/oauth/token', { error: 'invalid_grant' }, 400);
|
|
507
|
+
global.fetch = mockServer.fetch;
|
|
508
|
+
const provider = createProvider();
|
|
509
|
+
await expect(provider.exchangeRefreshToken(mockClient, 'valid-refresh-token')).rejects.toThrow('Failed to refresh token');
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
describe('verifyAccessToken', () => {
|
|
513
|
+
it('should verify and decode a valid access token', async () => {
|
|
514
|
+
const mockDecoded = {
|
|
515
|
+
id: 123,
|
|
516
|
+
email: 'user@example.com',
|
|
517
|
+
renderingId: 456,
|
|
518
|
+
serverToken: 'forest-server-token',
|
|
519
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
520
|
+
iat: Math.floor(Date.now() / 1000),
|
|
521
|
+
};
|
|
522
|
+
jsonwebtoken_1.default.verify.mockReturnValue(mockDecoded);
|
|
523
|
+
const provider = createProvider();
|
|
524
|
+
const result = await provider.verifyAccessToken('valid-access-token');
|
|
525
|
+
expect(result.token).toBe('valid-access-token');
|
|
526
|
+
expect(result.clientId).toBe('123');
|
|
527
|
+
expect(result.expiresAt).toBe(mockDecoded.exp);
|
|
528
|
+
expect(result.scopes).toEqual(['mcp:read', 'mcp:write', 'mcp:action']);
|
|
529
|
+
expect(result.extra).toEqual({
|
|
530
|
+
userId: 123,
|
|
531
|
+
email: 'user@example.com',
|
|
532
|
+
renderingId: 456,
|
|
533
|
+
environmentApiEndpoint: undefined,
|
|
534
|
+
forestServerToken: 'forest-server-token',
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
it('should throw error for expired access token', async () => {
|
|
538
|
+
jsonwebtoken_1.default.verify.mockImplementation(() => {
|
|
539
|
+
throw new jsonwebtoken_1.default.TokenExpiredError('jwt expired', new Date());
|
|
540
|
+
});
|
|
541
|
+
const provider = createProvider();
|
|
542
|
+
await expect(provider.verifyAccessToken('expired-token')).rejects.toThrow('Access token has expired');
|
|
543
|
+
});
|
|
544
|
+
it('should throw error for invalid access token', async () => {
|
|
545
|
+
jsonwebtoken_1.default.verify.mockImplementation(() => {
|
|
546
|
+
throw new jsonwebtoken_1.default.JsonWebTokenError('invalid signature');
|
|
547
|
+
});
|
|
548
|
+
const provider = createProvider();
|
|
549
|
+
await expect(provider.verifyAccessToken('invalid-token')).rejects.toThrow('Invalid access token');
|
|
550
|
+
});
|
|
551
|
+
it('should throw error when using refresh token as access token', async () => {
|
|
552
|
+
jsonwebtoken_1.default.verify.mockReturnValue({
|
|
553
|
+
type: 'refresh',
|
|
554
|
+
clientId: 'test-client-id',
|
|
555
|
+
});
|
|
556
|
+
const provider = createProvider();
|
|
557
|
+
await expect(provider.verifyAccessToken('refresh-token')).rejects.toThrow('Cannot use refresh token as access token');
|
|
558
|
+
});
|
|
559
|
+
it('should include environmentApiEndpoint after initialize is called', async () => {
|
|
560
|
+
mockServer.get('/liana/environment', {
|
|
561
|
+
data: {
|
|
562
|
+
id: '12345',
|
|
563
|
+
attributes: { api_endpoint: 'https://api.example.com' },
|
|
564
|
+
},
|
|
565
|
+
});
|
|
566
|
+
global.fetch = mockServer.fetch;
|
|
567
|
+
const mockDecoded = {
|
|
568
|
+
id: 123,
|
|
569
|
+
email: 'user@example.com',
|
|
570
|
+
renderingId: 456,
|
|
571
|
+
serverToken: 'forest-server-token',
|
|
572
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
573
|
+
iat: Math.floor(Date.now() / 1000),
|
|
574
|
+
};
|
|
575
|
+
jsonwebtoken_1.default.verify.mockReturnValue(mockDecoded);
|
|
576
|
+
const provider = createProvider();
|
|
577
|
+
// Call initialize to fetch environment data
|
|
578
|
+
await provider.initialize();
|
|
579
|
+
const result = await provider.verifyAccessToken('valid-access-token');
|
|
580
|
+
expect(result.extra).toEqual({
|
|
581
|
+
userId: 123,
|
|
582
|
+
email: 'user@example.com',
|
|
583
|
+
renderingId: 456,
|
|
584
|
+
environmentApiEndpoint: 'https://api.example.com',
|
|
585
|
+
forestServerToken: 'forest-server-token',
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yZXN0LW9hdXRoLXByb3ZpZGVyLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZm9yZXN0LW9hdXRoLXByb3ZpZGVyLnRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFHQSx5RkFBc0U7QUFDdEUsZ0VBQXdDO0FBRXhDLG9GQUEwRDtBQUMxRCwyRUFBa0Q7QUFFbEQsSUFBSSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQztBQUMxQixJQUFJLENBQUMsSUFBSSxDQUFDLGlDQUFpQyxDQUFDLENBQUM7QUFFN0MsTUFBTSwyQkFBMkIsR0FBRyw0QkFFbkMsQ0FBQztBQUNGLE1BQU0sYUFBYSxHQUFHLHNCQUFZLENBQUMsTUFBbUIsQ0FBQztBQUN2RCxNQUFNLFdBQVcsR0FBRyxzQkFBWSxDQUFDLElBQWlCLENBQUM7QUFFbkQsTUFBTSxlQUFlLEdBQUcsaUJBQWlCLENBQUM7QUFDMUMsTUFBTSxnQkFBZ0IsR0FBRyxrQkFBa0IsQ0FBQztBQUM1QyxNQUFNLG1CQUFtQixHQUFHLDZCQUE2QixDQUFDO0FBRTFELFNBQVMsY0FBYyxDQUFDLGVBQWUsR0FBRyw2QkFBNkI7SUFDckUsT0FBTyxJQUFJLCtCQUFtQixDQUFDO1FBQzdCLGVBQWU7UUFDZixZQUFZLEVBQUUsbUJBQW1CO1FBQ2pDLFNBQVMsRUFBRSxlQUFlO1FBQzFCLFVBQVUsRUFBRSxnQkFBZ0I7UUFDNUIsTUFBTSxFQUFFLE9BQU8sQ0FBQyxJQUFJO0tBQ3JCLENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCxRQUFRLENBQUMscUJBQXFCLEVBQUUsR0FBRyxFQUFFO0lBQ25DLElBQUksV0FBOEIsQ0FBQztJQUNuQyxJQUFJLFVBQXNCLENBQUM7SUFDM0IsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQztJQUVuQyxTQUFTLENBQUMsR0FBRyxFQUFFO1FBQ2IsV0FBVyxHQUFHLEVBQUUsR0FBRyxPQUFPLENBQUMsR0FBRyxFQUFFLENB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
|
package/dist/index.d.ts
ADDED