librechat-data-provider 0.8.402 → 0.8.404
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/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react-query/index.es.js +1 -1
- package/dist/react-query/index.es.js.map +1 -1
- package/dist/types/accessPermissions.d.ts +744 -0
- package/dist/types/actions.d.ts +118 -0
- package/dist/types/api-endpoints.d.ts +150 -0
- package/dist/types/artifacts.d.ts +97 -0
- package/dist/types/azure.d.ts +22 -0
- package/dist/types/bedrock.d.ts +1220 -0
- package/dist/types/config.d.ts +14849 -0
- package/dist/types/config.spec.d.ts +1 -0
- package/dist/types/createPayload.d.ts +5 -0
- package/dist/types/data-service.d.ts +287 -0
- package/dist/types/feedback.d.ts +36 -0
- package/dist/types/file-config.d.ts +263 -0
- package/dist/types/file-config.spec.d.ts +1 -0
- package/dist/types/generate.d.ts +597 -0
- package/dist/types/headers-helpers.d.ts +2 -0
- package/{src/index.ts → dist/types/index.d.ts} +0 -15
- package/dist/types/keys.d.ts +92 -0
- package/dist/types/mcp.d.ts +2760 -0
- package/dist/types/messages.d.ts +10 -0
- package/dist/types/models.d.ts +1547 -0
- package/dist/types/parameterSettings.d.ts +69 -0
- package/dist/types/parsers.d.ts +110 -0
- package/dist/types/permissions.d.ts +522 -0
- package/dist/types/react-query/react-query-service.d.ts +85 -0
- package/dist/types/request.d.ts +25 -0
- package/dist/types/roles.d.ts +554 -0
- package/dist/types/roles.spec.d.ts +1 -0
- package/dist/types/schemas.d.ts +5110 -0
- package/dist/types/schemas.spec.d.ts +1 -0
- package/dist/types/types/agents.d.ts +433 -0
- package/dist/types/types/assistants.d.ts +547 -0
- package/dist/types/types/files.d.ts +172 -0
- package/dist/types/types/graph.d.ts +135 -0
- package/{src/types/mcpServers.ts → dist/types/types/mcpServers.d.ts} +12 -18
- package/dist/types/types/mutations.d.ts +209 -0
- package/dist/types/types/queries.d.ts +169 -0
- package/dist/types/types/runs.d.ts +36 -0
- package/dist/types/types/web.d.ts +520 -0
- package/dist/types/types.d.ts +503 -0
- package/dist/types/utils.d.ts +12 -0
- package/package.json +5 -1
- package/babel.config.js +0 -4
- package/check_updates.sh +0 -52
- package/jest.config.js +0 -19
- package/react-query/package-lock.json +0 -292
- package/react-query/package.json +0 -10
- package/rollup.config.js +0 -74
- package/server-rollup.config.js +0 -40
- package/specs/actions.spec.ts +0 -2533
- package/specs/api-endpoints-subdir.spec.ts +0 -140
- package/specs/api-endpoints.spec.ts +0 -74
- package/specs/azure.spec.ts +0 -844
- package/specs/bedrock.spec.ts +0 -862
- package/specs/filetypes.spec.ts +0 -175
- package/specs/generate.spec.ts +0 -770
- package/specs/headers-helpers.spec.ts +0 -24
- package/specs/mcp.spec.ts +0 -147
- package/specs/openapiSpecs.ts +0 -524
- package/specs/parsers.spec.ts +0 -601
- package/specs/request-interceptor.spec.ts +0 -304
- package/specs/utils.spec.ts +0 -196
- package/src/accessPermissions.ts +0 -346
- package/src/actions.ts +0 -813
- package/src/api-endpoints.ts +0 -440
- package/src/artifacts.ts +0 -3104
- package/src/azure.ts +0 -328
- package/src/bedrock.ts +0 -425
- package/src/config.spec.ts +0 -315
- package/src/config.ts +0 -2006
- package/src/createPayload.ts +0 -46
- package/src/data-service.ts +0 -1087
- package/src/feedback.ts +0 -141
- package/src/file-config.spec.ts +0 -1248
- package/src/file-config.ts +0 -764
- package/src/generate.ts +0 -634
- package/src/headers-helpers.ts +0 -13
- package/src/keys.ts +0 -99
- package/src/mcp.ts +0 -271
- package/src/messages.ts +0 -50
- package/src/models.ts +0 -69
- package/src/parameterSettings.ts +0 -1111
- package/src/parsers.ts +0 -563
- package/src/permissions.ts +0 -188
- package/src/react-query/react-query-service.ts +0 -566
- package/src/request.ts +0 -171
- package/src/roles.spec.ts +0 -132
- package/src/roles.ts +0 -225
- package/src/schemas.spec.ts +0 -355
- package/src/schemas.ts +0 -1234
- package/src/types/agents.ts +0 -470
- package/src/types/assistants.ts +0 -654
- package/src/types/files.ts +0 -191
- package/src/types/graph.ts +0 -145
- package/src/types/mutations.ts +0 -422
- package/src/types/queries.ts +0 -208
- package/src/types/runs.ts +0 -40
- package/src/types/web.ts +0 -588
- package/src/types.ts +0 -676
- package/src/utils.ts +0 -85
- package/tsconfig.json +0 -28
- package/tsconfig.spec.json +0 -10
- /package/{src/react-query/index.ts → dist/types/react-query/index.d.ts} +0 -0
- /package/{src/types/index.ts → dist/types/types/index.d.ts} +0 -0
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jest-environment @happy-dom/jest-environment
|
|
3
|
-
*/
|
|
4
|
-
import axios from 'axios';
|
|
5
|
-
import { setTokenHeader } from '../src/headers-helpers';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The response interceptor in request.ts registers at import time when
|
|
9
|
-
* `typeof window !== 'undefined'` (happy-dom provides window).
|
|
10
|
-
*
|
|
11
|
-
* We use axios's built-in request adapter mock to avoid real HTTP calls,
|
|
12
|
-
* and verify the interceptor's behavior by observing whether a 401 triggers
|
|
13
|
-
* a refresh POST or is immediately rejected.
|
|
14
|
-
*
|
|
15
|
-
* happy-dom is used instead of jsdom because it allows overriding
|
|
16
|
-
* window.location via Object.defineProperty, which jsdom 26+ blocks.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const mockAdapter = jest.fn();
|
|
20
|
-
let originalAdapter: typeof axios.defaults.adapter;
|
|
21
|
-
let savedLocation: Location;
|
|
22
|
-
|
|
23
|
-
beforeAll(async () => {
|
|
24
|
-
originalAdapter = axios.defaults.adapter;
|
|
25
|
-
axios.defaults.adapter = mockAdapter;
|
|
26
|
-
|
|
27
|
-
await import('../src/request');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
mockAdapter.mockReset();
|
|
32
|
-
savedLocation = window.location;
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
afterAll(() => {
|
|
36
|
-
axios.defaults.adapter = originalAdapter;
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
afterEach(() => {
|
|
40
|
-
delete axios.defaults.headers.common['Authorization'];
|
|
41
|
-
Object.defineProperty(window, 'location', {
|
|
42
|
-
value: savedLocation,
|
|
43
|
-
writable: true,
|
|
44
|
-
configurable: true,
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
function setWindowLocation(overrides: Partial<Location>) {
|
|
49
|
-
Object.defineProperty(window, 'location', {
|
|
50
|
-
value: { ...window.location, ...overrides },
|
|
51
|
-
writable: true,
|
|
52
|
-
configurable: true,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
describe('axios 401 interceptor — Authorization header guard', () => {
|
|
57
|
-
it('skips refresh and rejects when Authorization header is cleared', async () => {
|
|
58
|
-
expect.assertions(1);
|
|
59
|
-
setTokenHeader(undefined);
|
|
60
|
-
|
|
61
|
-
mockAdapter.mockRejectedValueOnce({
|
|
62
|
-
response: { status: 401 },
|
|
63
|
-
config: { url: '/api/messages', headers: {} },
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
await axios.get('/api/messages');
|
|
68
|
-
} catch {
|
|
69
|
-
// expected rejection
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
expect(mockAdapter).toHaveBeenCalledTimes(1);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('attempts refresh on shared link page even without Authorization header', async () => {
|
|
76
|
-
expect.assertions(2);
|
|
77
|
-
setTokenHeader(undefined);
|
|
78
|
-
|
|
79
|
-
setWindowLocation({
|
|
80
|
-
href: 'http://localhost/share/abc123',
|
|
81
|
-
pathname: '/share/abc123',
|
|
82
|
-
search: '',
|
|
83
|
-
hash: '',
|
|
84
|
-
} as Partial<Location>);
|
|
85
|
-
|
|
86
|
-
mockAdapter.mockRejectedValueOnce({
|
|
87
|
-
response: { status: 401 },
|
|
88
|
-
config: { url: '/api/share/abc123', headers: {} },
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
mockAdapter.mockResolvedValueOnce({
|
|
92
|
-
data: { token: 'new-token' },
|
|
93
|
-
status: 200,
|
|
94
|
-
headers: {},
|
|
95
|
-
config: {},
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
mockAdapter.mockResolvedValueOnce({
|
|
99
|
-
data: { sharedLink: {} },
|
|
100
|
-
status: 200,
|
|
101
|
-
headers: {},
|
|
102
|
-
config: {},
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
await axios.get('/api/share/abc123');
|
|
107
|
-
} catch {
|
|
108
|
-
// may reject depending on exact flow
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
expect(mockAdapter.mock.calls.length).toBe(3);
|
|
112
|
-
|
|
113
|
-
const refreshCall = mockAdapter.mock.calls[1];
|
|
114
|
-
expect(refreshCall[0].url).toContain('api/auth/refresh');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('does not bypass guard when share/ appears only in query params', async () => {
|
|
118
|
-
expect.assertions(1);
|
|
119
|
-
setTokenHeader(undefined);
|
|
120
|
-
|
|
121
|
-
setWindowLocation({
|
|
122
|
-
href: 'http://localhost/c/chat?ref=share/token',
|
|
123
|
-
pathname: '/c/chat',
|
|
124
|
-
search: '?ref=share/token',
|
|
125
|
-
hash: '',
|
|
126
|
-
} as Partial<Location>);
|
|
127
|
-
|
|
128
|
-
mockAdapter.mockRejectedValueOnce({
|
|
129
|
-
response: { status: 401 },
|
|
130
|
-
config: { url: '/api/messages', headers: {} },
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
await axios.get('/api/messages');
|
|
135
|
-
} catch {
|
|
136
|
-
// expected rejection
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
expect(mockAdapter).toHaveBeenCalledTimes(1);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('redirects to login with redirect_to when unauthenticated on share page and refresh fails', async () => {
|
|
143
|
-
expect.assertions(1);
|
|
144
|
-
setTokenHeader(undefined);
|
|
145
|
-
|
|
146
|
-
setWindowLocation({
|
|
147
|
-
href: 'http://localhost/share/abc123',
|
|
148
|
-
pathname: '/share/abc123',
|
|
149
|
-
search: '',
|
|
150
|
-
hash: '',
|
|
151
|
-
} as Partial<Location>);
|
|
152
|
-
|
|
153
|
-
mockAdapter.mockRejectedValueOnce({
|
|
154
|
-
response: { status: 401 },
|
|
155
|
-
config: { url: '/api/share/abc123', headers: {} },
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
mockAdapter.mockResolvedValueOnce({
|
|
159
|
-
data: { token: '' },
|
|
160
|
-
status: 200,
|
|
161
|
-
headers: {},
|
|
162
|
-
config: {},
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
await axios.get('/api/share/abc123');
|
|
167
|
-
} catch {
|
|
168
|
-
// expected rejection
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
expect(window.location.href).toBe('/login?redirect_to=%2Fshare%2Fabc123');
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('redirects to login with redirect_to when authenticated and refresh returns no token on share page', async () => {
|
|
175
|
-
expect.assertions(1);
|
|
176
|
-
setTokenHeader('some-token');
|
|
177
|
-
|
|
178
|
-
setWindowLocation({
|
|
179
|
-
href: 'http://localhost/share/abc123',
|
|
180
|
-
pathname: '/share/abc123',
|
|
181
|
-
search: '',
|
|
182
|
-
hash: '',
|
|
183
|
-
} as Partial<Location>);
|
|
184
|
-
|
|
185
|
-
mockAdapter.mockRejectedValueOnce({
|
|
186
|
-
response: { status: 401 },
|
|
187
|
-
config: { url: '/api/share/abc123', headers: {} },
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
mockAdapter.mockResolvedValueOnce({
|
|
191
|
-
data: { token: '' },
|
|
192
|
-
status: 200,
|
|
193
|
-
headers: {},
|
|
194
|
-
config: {},
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
await axios.get('/api/share/abc123');
|
|
199
|
-
} catch {
|
|
200
|
-
// expected rejection
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
expect(window.location.href).toBe('/login?redirect_to=%2Fshare%2Fabc123');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('redirects to login with redirect_to when refresh returns no token on regular page', async () => {
|
|
207
|
-
expect.assertions(1);
|
|
208
|
-
setTokenHeader('some-token');
|
|
209
|
-
|
|
210
|
-
setWindowLocation({
|
|
211
|
-
href: 'http://localhost/c/some-conversation',
|
|
212
|
-
pathname: '/c/some-conversation',
|
|
213
|
-
search: '',
|
|
214
|
-
hash: '',
|
|
215
|
-
} as Partial<Location>);
|
|
216
|
-
|
|
217
|
-
mockAdapter.mockRejectedValueOnce({
|
|
218
|
-
response: { status: 401 },
|
|
219
|
-
config: { url: '/api/messages', headers: {} },
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
mockAdapter.mockResolvedValueOnce({
|
|
223
|
-
data: { token: '' },
|
|
224
|
-
status: 200,
|
|
225
|
-
headers: {},
|
|
226
|
-
config: {},
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
await axios.get('/api/messages');
|
|
231
|
-
} catch {
|
|
232
|
-
// expected rejection
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
expect(window.location.href).toBe('/login?redirect_to=%2Fc%2Fsome-conversation');
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('redirects to plain /login without redirect_to when already on a login path', async () => {
|
|
239
|
-
expect.assertions(1);
|
|
240
|
-
setTokenHeader('some-token');
|
|
241
|
-
|
|
242
|
-
setWindowLocation({
|
|
243
|
-
href: 'http://localhost/login/2fa',
|
|
244
|
-
pathname: '/login/2fa',
|
|
245
|
-
search: '',
|
|
246
|
-
hash: '',
|
|
247
|
-
} as Partial<Location>);
|
|
248
|
-
|
|
249
|
-
mockAdapter.mockRejectedValueOnce({
|
|
250
|
-
response: { status: 401 },
|
|
251
|
-
config: { url: '/api/messages', headers: {} },
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
mockAdapter.mockResolvedValueOnce({
|
|
255
|
-
data: { token: '' },
|
|
256
|
-
status: 200,
|
|
257
|
-
headers: {},
|
|
258
|
-
config: {},
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
await axios.get('/api/messages');
|
|
263
|
-
} catch {
|
|
264
|
-
// expected rejection
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
expect(window.location.href).toBe('/login');
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it('attempts refresh when Authorization header is present', async () => {
|
|
271
|
-
expect.assertions(2);
|
|
272
|
-
setTokenHeader('valid-token');
|
|
273
|
-
|
|
274
|
-
mockAdapter.mockRejectedValueOnce({
|
|
275
|
-
response: { status: 401 },
|
|
276
|
-
config: { url: '/api/messages', headers: {}, _retry: false },
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
mockAdapter.mockResolvedValueOnce({
|
|
280
|
-
data: { token: 'new-token' },
|
|
281
|
-
status: 200,
|
|
282
|
-
headers: {},
|
|
283
|
-
config: {},
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
mockAdapter.mockResolvedValueOnce({
|
|
287
|
-
data: { messages: [] },
|
|
288
|
-
status: 200,
|
|
289
|
-
headers: {},
|
|
290
|
-
config: {},
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
try {
|
|
294
|
-
await axios.get('/api/messages');
|
|
295
|
-
} catch {
|
|
296
|
-
// may reject depending on exact flow
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
expect(mockAdapter.mock.calls.length).toBe(3);
|
|
300
|
-
|
|
301
|
-
const refreshCall = mockAdapter.mock.calls[1];
|
|
302
|
-
expect(refreshCall[0].url).toContain('api/auth/refresh');
|
|
303
|
-
});
|
|
304
|
-
});
|
package/specs/utils.spec.ts
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { extractEnvVariable, isSensitiveEnvVar } from '../src/utils';
|
|
2
|
-
|
|
3
|
-
describe('Environment Variable Extraction', () => {
|
|
4
|
-
const originalEnv = process.env;
|
|
5
|
-
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
process.env = {
|
|
8
|
-
...originalEnv,
|
|
9
|
-
TEST_API_KEY: 'test-api-key-value',
|
|
10
|
-
ANOTHER_VALUE: 'another-value',
|
|
11
|
-
};
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
process.env = originalEnv;
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe('extractEnvVariable (original tests)', () => {
|
|
19
|
-
test('should return the value of the environment variable', () => {
|
|
20
|
-
process.env.TEST_VAR = 'test_value';
|
|
21
|
-
expect(extractEnvVariable('${TEST_VAR}')).toBe('test_value');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('should return the original string if the envrionment variable is not defined correctly', () => {
|
|
25
|
-
process.env.TEST_VAR = 'test_value';
|
|
26
|
-
expect(extractEnvVariable('${ TEST_VAR }')).toBe('${ TEST_VAR }');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('should return the original string if environment variable is not set', () => {
|
|
30
|
-
expect(extractEnvVariable('${NON_EXISTENT_VAR}')).toBe('${NON_EXISTENT_VAR}');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('should return the original string if it does not contain an environment variable', () => {
|
|
34
|
-
expect(extractEnvVariable('some_string')).toBe('some_string');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('should handle empty strings', () => {
|
|
38
|
-
expect(extractEnvVariable('')).toBe('');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('should handle strings without variable format', () => {
|
|
42
|
-
expect(extractEnvVariable('no_var_here')).toBe('no_var_here');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
/** No longer the expected behavior; keeping for reference */
|
|
46
|
-
test.skip('should not process multiple variable formats', () => {
|
|
47
|
-
process.env.FIRST_VAR = 'first';
|
|
48
|
-
process.env.SECOND_VAR = 'second';
|
|
49
|
-
expect(extractEnvVariable('${FIRST_VAR} and ${SECOND_VAR}')).toBe(
|
|
50
|
-
'${FIRST_VAR} and ${SECOND_VAR}',
|
|
51
|
-
);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('extractEnvVariable function', () => {
|
|
56
|
-
it('should extract environment variables from exact matches', () => {
|
|
57
|
-
expect(extractEnvVariable('${TEST_API_KEY}')).toBe('test-api-key-value');
|
|
58
|
-
expect(extractEnvVariable('${ANOTHER_VALUE}')).toBe('another-value');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should extract environment variables from strings with prefixes', () => {
|
|
62
|
-
expect(extractEnvVariable('prefix-${TEST_API_KEY}')).toBe('prefix-test-api-key-value');
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should extract environment variables from strings with suffixes', () => {
|
|
66
|
-
expect(extractEnvVariable('${TEST_API_KEY}-suffix')).toBe('test-api-key-value-suffix');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should extract environment variables from strings with both prefixes and suffixes', () => {
|
|
70
|
-
expect(extractEnvVariable('prefix-${TEST_API_KEY}-suffix')).toBe(
|
|
71
|
-
'prefix-test-api-key-value-suffix',
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should not match invalid patterns', () => {
|
|
76
|
-
expect(extractEnvVariable('$TEST_API_KEY')).toBe('$TEST_API_KEY');
|
|
77
|
-
expect(extractEnvVariable('{TEST_API_KEY}')).toBe('{TEST_API_KEY}');
|
|
78
|
-
expect(extractEnvVariable('TEST_API_KEY')).toBe('TEST_API_KEY');
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe('extractEnvVariable', () => {
|
|
83
|
-
it('should extract environment variable values', () => {
|
|
84
|
-
expect(extractEnvVariable('${TEST_API_KEY}')).toBe('test-api-key-value');
|
|
85
|
-
expect(extractEnvVariable('${ANOTHER_VALUE}')).toBe('another-value');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should return the original string if environment variable is not found', () => {
|
|
89
|
-
expect(extractEnvVariable('${NON_EXISTENT_VAR}')).toBe('${NON_EXISTENT_VAR}');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should return the original string if no environment variable pattern is found', () => {
|
|
93
|
-
expect(extractEnvVariable('plain-string')).toBe('plain-string');
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe('extractEnvVariable space trimming', () => {
|
|
98
|
-
beforeEach(() => {
|
|
99
|
-
process.env.HELLO = 'world';
|
|
100
|
-
process.env.USER = 'testuser';
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should extract the value when string contains only an environment variable with surrounding whitespace', () => {
|
|
104
|
-
expect(extractEnvVariable(' ${HELLO} ')).toBe('world');
|
|
105
|
-
expect(extractEnvVariable(' ${HELLO} ')).toBe('world');
|
|
106
|
-
expect(extractEnvVariable('\t${HELLO}\n')).toBe('world');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should preserve content when variable is part of a larger string', () => {
|
|
110
|
-
expect(extractEnvVariable('Hello ${USER}!')).toBe('Hello testuser!');
|
|
111
|
-
expect(extractEnvVariable(' Hello ${USER}! ')).toBe('Hello testuser!');
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should not handle multiple variables', () => {
|
|
115
|
-
expect(extractEnvVariable('${HELLO} ${USER}')).toBe('${HELLO} ${USER}');
|
|
116
|
-
expect(extractEnvVariable(' ${HELLO} ${USER} ')).toBe('${HELLO} ${USER}');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('should handle undefined variables', () => {
|
|
120
|
-
expect(extractEnvVariable(' ${UNDEFINED_VAR} ')).toBe('${UNDEFINED_VAR}');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('should handle mixed content correctly', () => {
|
|
124
|
-
expect(extractEnvVariable('Welcome, ${USER}!\nYour message: ${HELLO}')).toBe(
|
|
125
|
-
'Welcome, testuser!\nYour message: world',
|
|
126
|
-
);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe('isSensitiveEnvVar', () => {
|
|
131
|
-
it('should flag infrastructure secrets', () => {
|
|
132
|
-
expect(isSensitiveEnvVar('JWT_SECRET')).toBe(true);
|
|
133
|
-
expect(isSensitiveEnvVar('JWT_REFRESH_SECRET')).toBe(true);
|
|
134
|
-
expect(isSensitiveEnvVar('CREDS_KEY')).toBe(true);
|
|
135
|
-
expect(isSensitiveEnvVar('CREDS_IV')).toBe(true);
|
|
136
|
-
expect(isSensitiveEnvVar('MEILI_MASTER_KEY')).toBe(true);
|
|
137
|
-
expect(isSensitiveEnvVar('MONGO_URI')).toBe(true);
|
|
138
|
-
expect(isSensitiveEnvVar('REDIS_URI')).toBe(true);
|
|
139
|
-
expect(isSensitiveEnvVar('REDIS_PASSWORD')).toBe(true);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should allow non-infrastructure vars through (including operator-configured secrets)', () => {
|
|
143
|
-
expect(isSensitiveEnvVar('OPENAI_API_KEY')).toBe(false);
|
|
144
|
-
expect(isSensitiveEnvVar('ANTHROPIC_API_KEY')).toBe(false);
|
|
145
|
-
expect(isSensitiveEnvVar('GOOGLE_KEY')).toBe(false);
|
|
146
|
-
expect(isSensitiveEnvVar('PROXY')).toBe(false);
|
|
147
|
-
expect(isSensitiveEnvVar('DEBUG_LOGGING')).toBe(false);
|
|
148
|
-
expect(isSensitiveEnvVar('DOMAIN_CLIENT')).toBe(false);
|
|
149
|
-
expect(isSensitiveEnvVar('APP_TITLE')).toBe(false);
|
|
150
|
-
expect(isSensitiveEnvVar('OPENID_CLIENT_SECRET')).toBe(false);
|
|
151
|
-
expect(isSensitiveEnvVar('DISCORD_CLIENT_SECRET')).toBe(false);
|
|
152
|
-
expect(isSensitiveEnvVar('MY_CUSTOM_SECRET')).toBe(false);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
describe('extractEnvVariable sensitive var blocklist', () => {
|
|
157
|
-
beforeEach(() => {
|
|
158
|
-
process.env.JWT_SECRET = 'super-secret-jwt';
|
|
159
|
-
process.env.JWT_REFRESH_SECRET = 'super-secret-refresh';
|
|
160
|
-
process.env.CREDS_KEY = 'encryption-key';
|
|
161
|
-
process.env.CREDS_IV = 'encryption-iv';
|
|
162
|
-
process.env.MEILI_MASTER_KEY = 'meili-key';
|
|
163
|
-
process.env.MONGO_URI = 'mongodb://user:pass@host/db';
|
|
164
|
-
process.env.REDIS_URI = 'redis://:pass@host:6379';
|
|
165
|
-
process.env.REDIS_PASSWORD = 'redis-pass';
|
|
166
|
-
process.env.OPENAI_API_KEY = 'sk-legit-key';
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('should refuse to resolve sensitive vars (single-match path)', () => {
|
|
170
|
-
expect(extractEnvVariable('${JWT_SECRET}')).toBe('${JWT_SECRET}');
|
|
171
|
-
expect(extractEnvVariable('${JWT_REFRESH_SECRET}')).toBe('${JWT_REFRESH_SECRET}');
|
|
172
|
-
expect(extractEnvVariable('${CREDS_KEY}')).toBe('${CREDS_KEY}');
|
|
173
|
-
expect(extractEnvVariable('${CREDS_IV}')).toBe('${CREDS_IV}');
|
|
174
|
-
expect(extractEnvVariable('${MEILI_MASTER_KEY}')).toBe('${MEILI_MASTER_KEY}');
|
|
175
|
-
expect(extractEnvVariable('${MONGO_URI}')).toBe('${MONGO_URI}');
|
|
176
|
-
expect(extractEnvVariable('${REDIS_URI}')).toBe('${REDIS_URI}');
|
|
177
|
-
expect(extractEnvVariable('${REDIS_PASSWORD}')).toBe('${REDIS_PASSWORD}');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should refuse to resolve sensitive vars in composite strings (multi-match path)', () => {
|
|
181
|
-
expect(extractEnvVariable('key=${JWT_SECRET}&more')).toBe('key=${JWT_SECRET}&more');
|
|
182
|
-
expect(extractEnvVariable('db=${MONGO_URI}/extra')).toBe('db=${MONGO_URI}/extra');
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('should still resolve non-sensitive vars normally', () => {
|
|
186
|
-
expect(extractEnvVariable('${OPENAI_API_KEY}')).toBe('sk-legit-key');
|
|
187
|
-
expect(extractEnvVariable('Bearer ${OPENAI_API_KEY}')).toBe('Bearer sk-legit-key');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should resolve non-sensitive vars while blocking sensitive ones in the same string', () => {
|
|
191
|
-
expect(extractEnvVariable('key=${OPENAI_API_KEY}&secret=${JWT_SECRET}')).toBe(
|
|
192
|
-
'key=sk-legit-key&secret=${JWT_SECRET}',
|
|
193
|
-
);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
});
|