@inkeep/agents-core 0.35.3 → 0.35.4
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/auth/auth.d.ts +3 -3
- package/dist/auth/auth.js +3 -2
- package/dist/{chunk-VBCCPAZK.js → chunk-3AK7OSAT.js} +20 -35
- package/dist/chunk-4RVJB4KV.js +59 -0
- package/dist/chunk-7GZHUB4J.js +63 -0
- package/dist/{chunk-ZYSTJ4XY.js → chunk-D3NGJEP2.js} +326 -132
- package/dist/{chunk-W3L4M7FO.js → chunk-DN4B564Y.js} +11 -72
- package/dist/chunk-FOK3JSQN.js +763 -0
- package/dist/chunk-KMLLKRUY.js +38 -0
- package/dist/chunk-SG75RA63.js +74 -0
- package/dist/{chunk-SPRTYWRV.js → chunk-SH5TYTTP.js} +1 -1
- package/dist/chunk-Z64UK4CA.js +35 -0
- package/dist/{client-CPYOMZF2.d.ts → client-HrEgt7wv.d.ts} +1 -1
- package/dist/client-exports.d.ts +2 -2
- package/dist/client-exports.js +3 -2
- package/dist/constants/schema-validation/index.d.ts +31 -0
- package/dist/constants/schema-validation/index.js +2 -0
- package/dist/credential-stores/index.d.ts +268 -0
- package/dist/credential-stores/index.js +1 -0
- package/dist/db/schema.d.ts +2 -2
- package/dist/db/test-client.d.ts +39 -0
- package/dist/db/test-client.js +1 -0
- package/dist/index.d.ts +48 -324
- package/dist/index.js +336 -1105
- package/dist/{schema-5N2lPWNV.d.ts → schema-CoC3tYFX.d.ts} +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/{utility-DbltUp2Q.d.ts → utility-C4QAannk.d.ts} +47 -1
- package/dist/utils/schema-conversion.js +1 -1
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +2 -2
- package/package.json +18 -1
|
@@ -0,0 +1,763 @@
|
|
|
1
|
+
import { getLogger } from './chunk-DN4B564Y.js';
|
|
2
|
+
import { CredentialStoreType } from './chunk-YFHT5M2R.js';
|
|
3
|
+
import { Nango } from '@nangohq/node';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
// src/credential-stores/CredentialStoreRegistry.ts
|
|
7
|
+
var CredentialStoreRegistry = class {
|
|
8
|
+
stores = /* @__PURE__ */ new Map();
|
|
9
|
+
logger = getLogger("credential-store-registry");
|
|
10
|
+
constructor(initialStores = []) {
|
|
11
|
+
for (const store of initialStores) {
|
|
12
|
+
this.add(store);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Add a credential store to the registry
|
|
17
|
+
*/
|
|
18
|
+
add(store) {
|
|
19
|
+
if (this.stores.has(store.id)) {
|
|
20
|
+
this.logger.warn(
|
|
21
|
+
{ storeId: store.id },
|
|
22
|
+
`Credential store ${store.id} already registered, replacing`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
this.stores.set(store.id, store);
|
|
26
|
+
this.logger.info(
|
|
27
|
+
{ storeId: store.id, storeType: store.type },
|
|
28
|
+
`Registered credential store: ${store.id} (type: ${store.type})`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get a credential store by ID
|
|
33
|
+
*/
|
|
34
|
+
get(id) {
|
|
35
|
+
return this.stores.get(id);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get all registered credential stores
|
|
39
|
+
*/
|
|
40
|
+
getAll() {
|
|
41
|
+
return Array.from(this.stores.values());
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get all credential store IDs
|
|
45
|
+
*/
|
|
46
|
+
getIds() {
|
|
47
|
+
return Array.from(this.stores.keys());
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if a credential store is registered
|
|
51
|
+
*/
|
|
52
|
+
has(id) {
|
|
53
|
+
return this.stores.has(id);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Remove a credential store
|
|
57
|
+
*/
|
|
58
|
+
remove(id) {
|
|
59
|
+
const removed = this.stores.delete(id);
|
|
60
|
+
if (removed) {
|
|
61
|
+
this.logger.info({ id }, `Removed credential store: ${id}`);
|
|
62
|
+
}
|
|
63
|
+
return removed;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get the number of registered stores
|
|
67
|
+
*/
|
|
68
|
+
size() {
|
|
69
|
+
return this.stores.size;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/credential-stores/keychain-store.ts
|
|
74
|
+
var KeyChainStore = class {
|
|
75
|
+
id;
|
|
76
|
+
type = CredentialStoreType.keychain;
|
|
77
|
+
service;
|
|
78
|
+
logger = getLogger("KeyChainStore");
|
|
79
|
+
keytarAvailable = false;
|
|
80
|
+
keytar = null;
|
|
81
|
+
initializationPromise;
|
|
82
|
+
constructor(id, servicePrefix = "inkeep-agent-framework") {
|
|
83
|
+
this.id = id;
|
|
84
|
+
this.service = `${servicePrefix}-${id}`;
|
|
85
|
+
this.initializationPromise = this.initializeKeytar();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Initialize keytar dynamically to handle optional availability
|
|
89
|
+
*/
|
|
90
|
+
async initializeKeytar() {
|
|
91
|
+
if (this.keytar) {
|
|
92
|
+
this.keytarAvailable = true;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
this.keytar = (await import('keytar')).default;
|
|
97
|
+
this.keytarAvailable = true;
|
|
98
|
+
this.logger.info(
|
|
99
|
+
{
|
|
100
|
+
storeId: this.id,
|
|
101
|
+
service: this.service
|
|
102
|
+
},
|
|
103
|
+
"Keytar initialized successfully"
|
|
104
|
+
);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.logger.warn(
|
|
107
|
+
{
|
|
108
|
+
storeId: this.id,
|
|
109
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
110
|
+
},
|
|
111
|
+
"Keytar not available - KeyChainStore will return null for all operations"
|
|
112
|
+
);
|
|
113
|
+
this.keytarAvailable = false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get a credential from the keychain
|
|
118
|
+
*/
|
|
119
|
+
async get(key) {
|
|
120
|
+
await this.initializationPromise;
|
|
121
|
+
if (!this.keytarAvailable || !this.keytar) {
|
|
122
|
+
this.logger.debug({ storeId: this.id, key }, "Keytar not available, returning null");
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const password = await this.keytar.getPassword(this.service, key);
|
|
127
|
+
if (password === null) {
|
|
128
|
+
this.logger.debug(
|
|
129
|
+
{ storeId: this.id, service: this.service, account: key },
|
|
130
|
+
"No credential found in keychain"
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return password;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
this.logger.error(
|
|
136
|
+
{
|
|
137
|
+
storeId: this.id,
|
|
138
|
+
service: this.service,
|
|
139
|
+
account: key,
|
|
140
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
141
|
+
},
|
|
142
|
+
"Error getting credential from keychain"
|
|
143
|
+
);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Set a credential in the keychain
|
|
149
|
+
* @param metadata - Optional metadata (ignored by keychain store)
|
|
150
|
+
*/
|
|
151
|
+
async set(key, value, _metadata) {
|
|
152
|
+
await this.initializationPromise;
|
|
153
|
+
if (!this.keytarAvailable || !this.keytar) {
|
|
154
|
+
this.logger.warn({ storeId: this.id, key }, "Keytar not available, cannot set credential");
|
|
155
|
+
throw new Error("Keytar not available - cannot store credentials in system keychain");
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
await this.keytar.setPassword(this.service, key, value);
|
|
159
|
+
this.logger.debug(
|
|
160
|
+
{ storeId: this.id, service: this.service, account: key },
|
|
161
|
+
"Credential stored in keychain"
|
|
162
|
+
);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
this.logger.error(
|
|
165
|
+
{
|
|
166
|
+
storeId: this.id,
|
|
167
|
+
service: this.service,
|
|
168
|
+
account: key,
|
|
169
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
170
|
+
},
|
|
171
|
+
"Error setting credential in keychain"
|
|
172
|
+
);
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Failed to store credential in keychain: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check if a credential exists in the keychain
|
|
180
|
+
*/
|
|
181
|
+
async has(key) {
|
|
182
|
+
const credential = await this.get(key);
|
|
183
|
+
return credential !== null;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if the credential store is available and functional
|
|
187
|
+
*/
|
|
188
|
+
async checkAvailability() {
|
|
189
|
+
await this.initializationPromise;
|
|
190
|
+
if (!this.keytarAvailable || !this.keytar) {
|
|
191
|
+
return {
|
|
192
|
+
available: false,
|
|
193
|
+
reason: "Keytar not available - cannot store credentials in system keychain"
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
available: true
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Delete a credential from the keychain
|
|
202
|
+
*/
|
|
203
|
+
async delete(key) {
|
|
204
|
+
await this.initializationPromise;
|
|
205
|
+
if (!this.keytarAvailable || !this.keytar) {
|
|
206
|
+
this.logger.warn({ storeId: this.id, key }, "Keytar not available, cannot delete credential");
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const result = await this.keytar.deletePassword(this.service, key);
|
|
211
|
+
if (result) {
|
|
212
|
+
this.logger.debug(
|
|
213
|
+
{ storeId: this.id, service: this.service, account: key },
|
|
214
|
+
"Credential deleted from keychain"
|
|
215
|
+
);
|
|
216
|
+
} else {
|
|
217
|
+
this.logger.debug(
|
|
218
|
+
{ storeId: this.id, service: this.service, account: key },
|
|
219
|
+
"Credential not found in keychain for deletion"
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
this.logger.error(
|
|
225
|
+
{
|
|
226
|
+
storeId: this.id,
|
|
227
|
+
service: this.service,
|
|
228
|
+
account: key,
|
|
229
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
230
|
+
},
|
|
231
|
+
"Error deleting credential from keychain"
|
|
232
|
+
);
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Find all credentials for this service
|
|
238
|
+
* Useful for debugging and listing stored credentials
|
|
239
|
+
*/
|
|
240
|
+
async findAllCredentials() {
|
|
241
|
+
await this.initializationPromise;
|
|
242
|
+
if (!this.keytarAvailable || !this.keytar) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const credentials = await this.keytar.findCredentials(this.service);
|
|
247
|
+
return credentials || [];
|
|
248
|
+
} catch (error) {
|
|
249
|
+
this.logger.error(
|
|
250
|
+
{
|
|
251
|
+
storeId: this.id,
|
|
252
|
+
service: this.service,
|
|
253
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
254
|
+
},
|
|
255
|
+
"Error finding credentials in keychain"
|
|
256
|
+
);
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Clear all credentials for this service
|
|
262
|
+
* WARNING: This will delete all credentials stored under this service
|
|
263
|
+
*/
|
|
264
|
+
async clearAll() {
|
|
265
|
+
const credentials = await this.findAllCredentials();
|
|
266
|
+
let deletedCount = 0;
|
|
267
|
+
for (const cred of credentials) {
|
|
268
|
+
const deleted = await this.delete(cred.account);
|
|
269
|
+
if (deleted) {
|
|
270
|
+
deletedCount++;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (deletedCount > 0) {
|
|
274
|
+
this.logger.info(
|
|
275
|
+
{
|
|
276
|
+
storeId: this.id,
|
|
277
|
+
service: this.service,
|
|
278
|
+
deletedCount
|
|
279
|
+
},
|
|
280
|
+
"Cleared all credentials from keychain"
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
return deletedCount;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
function createKeyChainStore(id, options) {
|
|
287
|
+
return new KeyChainStore(id, options?.servicePrefix);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/credential-stores/memory-store.ts
|
|
291
|
+
var InMemoryCredentialStore = class {
|
|
292
|
+
id;
|
|
293
|
+
type = CredentialStoreType.memory;
|
|
294
|
+
credentials = /* @__PURE__ */ new Map();
|
|
295
|
+
constructor(id = "memory-default") {
|
|
296
|
+
this.id = id;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Get a credential from the in memory store.
|
|
300
|
+
* If the key is not found in the in memory store then it is loaded from environment variables.
|
|
301
|
+
* If the key is not found in the environment variables or in the in memory store then returns null.
|
|
302
|
+
* @param key - The key of the credential to get
|
|
303
|
+
* @returns The credential value or null if not found
|
|
304
|
+
*/
|
|
305
|
+
async get(key) {
|
|
306
|
+
const credential = this.credentials.get(key);
|
|
307
|
+
if (!credential) {
|
|
308
|
+
const envValue = process.env[key];
|
|
309
|
+
if (envValue) {
|
|
310
|
+
this.credentials.set(key, envValue);
|
|
311
|
+
return envValue;
|
|
312
|
+
}
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
return credential;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Set a credential in the in memory store.
|
|
319
|
+
* @param key - The key of the credential to set
|
|
320
|
+
* @param value - The value of the credential to set
|
|
321
|
+
* @param metadata - Optional metadata (ignored by memory store)
|
|
322
|
+
*/
|
|
323
|
+
async set(key, value, _metadata) {
|
|
324
|
+
this.credentials.set(key, value);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Check if a credential exists in the in memory store.
|
|
328
|
+
* @param key - The key of the credential to check
|
|
329
|
+
* @returns True if the credential exists, false otherwise
|
|
330
|
+
*/
|
|
331
|
+
async has(key) {
|
|
332
|
+
return this.credentials.has(key);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Delete a credential from the in memory store.
|
|
336
|
+
* @param key - The key of the credential to delete
|
|
337
|
+
* @returns True if the credential was deleted, false otherwise
|
|
338
|
+
*/
|
|
339
|
+
async delete(key) {
|
|
340
|
+
return this.credentials.delete(key);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Check if the credential store is available and functional
|
|
344
|
+
*/
|
|
345
|
+
async checkAvailability() {
|
|
346
|
+
return {
|
|
347
|
+
available: true
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
var logger = getLogger("nango-credential-store");
|
|
352
|
+
var CredentialKeySchema = z.object({
|
|
353
|
+
connectionId: z.string().min(1, "connectionId must be a non-empty string"),
|
|
354
|
+
providerConfigKey: z.string().min(1, "providerConfigKey must be a non-empty string"),
|
|
355
|
+
integrationDisplayName: z.string().nullish()
|
|
356
|
+
});
|
|
357
|
+
function parseCredentialKey(key) {
|
|
358
|
+
try {
|
|
359
|
+
const parsed = JSON.parse(key);
|
|
360
|
+
return CredentialKeySchema.parse(parsed);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
logger.warn(
|
|
363
|
+
{
|
|
364
|
+
key: key.substring(0, 100),
|
|
365
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
366
|
+
},
|
|
367
|
+
"Failed to parse credential key"
|
|
368
|
+
);
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
var SUPPORTED_AUTH_MODES = [
|
|
373
|
+
"APP",
|
|
374
|
+
"API_KEY",
|
|
375
|
+
"BASIC",
|
|
376
|
+
"CUSTOM",
|
|
377
|
+
"JWT",
|
|
378
|
+
"NONE",
|
|
379
|
+
"OAUTH1",
|
|
380
|
+
"OAUTH2",
|
|
381
|
+
"OAUTH2_CC",
|
|
382
|
+
"TBA"
|
|
383
|
+
];
|
|
384
|
+
function isSupportedAuthMode(mode) {
|
|
385
|
+
return SUPPORTED_AUTH_MODES.includes(mode);
|
|
386
|
+
}
|
|
387
|
+
var NangoCredentialStore = class {
|
|
388
|
+
id;
|
|
389
|
+
type = CredentialStoreType.nango;
|
|
390
|
+
nangoConfig;
|
|
391
|
+
nangoClient;
|
|
392
|
+
constructor(id, config) {
|
|
393
|
+
this.id = id;
|
|
394
|
+
this.nangoConfig = config;
|
|
395
|
+
this.nangoClient = new Nango({
|
|
396
|
+
secretKey: this.nangoConfig.secretKey,
|
|
397
|
+
host: this.nangoConfig.apiUrl
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
getAccessToken(credentials) {
|
|
401
|
+
const { type } = credentials;
|
|
402
|
+
if (!isSupportedAuthMode(type)) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
const extractAccessTokenForBearerType = (tokenString) => {
|
|
406
|
+
if (tokenString && typeof tokenString === "string") {
|
|
407
|
+
try {
|
|
408
|
+
const parsedToken = JSON.parse(tokenString);
|
|
409
|
+
if (parsedToken.access_token && typeof parsedToken.access_token === "string") {
|
|
410
|
+
return parsedToken.access_token;
|
|
411
|
+
}
|
|
412
|
+
} catch {
|
|
413
|
+
}
|
|
414
|
+
return tokenString;
|
|
415
|
+
}
|
|
416
|
+
return void 0;
|
|
417
|
+
};
|
|
418
|
+
switch (type) {
|
|
419
|
+
case "API_KEY":
|
|
420
|
+
return {
|
|
421
|
+
token: extractAccessTokenForBearerType(
|
|
422
|
+
credentials.apiKey || credentials.api_key
|
|
423
|
+
)
|
|
424
|
+
};
|
|
425
|
+
case "APP":
|
|
426
|
+
return {
|
|
427
|
+
token: extractAccessTokenForBearerType(
|
|
428
|
+
credentials.accessToken || credentials.access_token
|
|
429
|
+
)
|
|
430
|
+
};
|
|
431
|
+
case "BASIC":
|
|
432
|
+
return {
|
|
433
|
+
username: credentials.username,
|
|
434
|
+
token: credentials.password
|
|
435
|
+
};
|
|
436
|
+
case "CUSTOM":
|
|
437
|
+
return credentials.raw;
|
|
438
|
+
case "JWT":
|
|
439
|
+
return {
|
|
440
|
+
token: extractAccessTokenForBearerType(credentials.token)
|
|
441
|
+
};
|
|
442
|
+
case "OAUTH1":
|
|
443
|
+
return {
|
|
444
|
+
token: credentials.oauth_token,
|
|
445
|
+
token_secret: credentials.oauth_token_secret
|
|
446
|
+
};
|
|
447
|
+
case "OAUTH2":
|
|
448
|
+
return {
|
|
449
|
+
token: extractAccessTokenForBearerType(credentials.access_token),
|
|
450
|
+
refresh_token: credentials.refresh_token,
|
|
451
|
+
expiresAt: credentials.expires_at
|
|
452
|
+
};
|
|
453
|
+
case "OAUTH2_CC":
|
|
454
|
+
return {
|
|
455
|
+
token: extractAccessTokenForBearerType(credentials.token),
|
|
456
|
+
client_certificate: credentials.client_certificate,
|
|
457
|
+
client_id: credentials.client_id,
|
|
458
|
+
client_private_key: credentials.client_private_key,
|
|
459
|
+
client_secret: credentials.client_secret
|
|
460
|
+
};
|
|
461
|
+
case "TBA":
|
|
462
|
+
return {
|
|
463
|
+
token: credentials.token_id,
|
|
464
|
+
token_secret: credentials.token_secret
|
|
465
|
+
};
|
|
466
|
+
default:
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
sanitizeMetadata(metadata) {
|
|
471
|
+
if (!metadata || typeof metadata !== "object") return {};
|
|
472
|
+
const result = {};
|
|
473
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
474
|
+
if (typeof key !== "string") continue;
|
|
475
|
+
if (typeof value === "string") {
|
|
476
|
+
result[key] = value;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return result;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Fetch a specific Nango integration
|
|
483
|
+
*/
|
|
484
|
+
async fetchNangoIntegration(uniqueKey) {
|
|
485
|
+
try {
|
|
486
|
+
const response = await this.nangoClient.getIntegration(
|
|
487
|
+
{ uniqueKey },
|
|
488
|
+
{ include: ["credentials"] }
|
|
489
|
+
);
|
|
490
|
+
const integration = response.data;
|
|
491
|
+
let areCredentialsSet = false;
|
|
492
|
+
if (integration.credentials?.type === "OAUTH2" || integration.credentials?.type === "OAUTH1" || integration.credentials?.type === "TBA") {
|
|
493
|
+
areCredentialsSet = !!(integration.credentials?.client_id && integration.credentials?.client_secret);
|
|
494
|
+
} else if (integration.credentials?.type === "APP") {
|
|
495
|
+
areCredentialsSet = !!(integration.credentials?.app_id && integration.credentials?.app_link);
|
|
496
|
+
} else {
|
|
497
|
+
areCredentialsSet = true;
|
|
498
|
+
}
|
|
499
|
+
const { credentials: _credentials, ...integrationWithoutCredentials } = integration;
|
|
500
|
+
return {
|
|
501
|
+
...integrationWithoutCredentials,
|
|
502
|
+
areCredentialsSet
|
|
503
|
+
};
|
|
504
|
+
} catch (error) {
|
|
505
|
+
if (error && typeof error === "object" && "status" in error && error.status === 404) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
logger.error(
|
|
509
|
+
{ error: error instanceof Error ? error.message : "Unknown error", uniqueKey },
|
|
510
|
+
`Failed to fetch integration ${uniqueKey}`
|
|
511
|
+
);
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Create an API key credential by setting up Nango integration and importing the connection
|
|
517
|
+
*/
|
|
518
|
+
async createNangoApiKeyConnection({
|
|
519
|
+
uniqueKey,
|
|
520
|
+
displayName,
|
|
521
|
+
apiKeyToSet,
|
|
522
|
+
metadata
|
|
523
|
+
}) {
|
|
524
|
+
const provider = "private-api-bearer";
|
|
525
|
+
try {
|
|
526
|
+
let integration;
|
|
527
|
+
try {
|
|
528
|
+
const response2 = await this.nangoClient.createIntegration({
|
|
529
|
+
provider,
|
|
530
|
+
unique_key: uniqueKey,
|
|
531
|
+
display_name: displayName
|
|
532
|
+
});
|
|
533
|
+
integration = response2.data;
|
|
534
|
+
} catch (error) {
|
|
535
|
+
const existingIntegration = await this.fetchNangoIntegration(uniqueKey);
|
|
536
|
+
if (existingIntegration) {
|
|
537
|
+
integration = existingIntegration;
|
|
538
|
+
} else {
|
|
539
|
+
console.log(`Integration creation failed for unexpected reasons`, error);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (!integration) {
|
|
543
|
+
throw new Error(`Integration '${uniqueKey}' not found`);
|
|
544
|
+
}
|
|
545
|
+
const importConnectionUrl = `${process.env.NANGO_SERVER_URL || "https://api.nango.dev"}/connections`;
|
|
546
|
+
const credentials = {
|
|
547
|
+
type: "API_KEY",
|
|
548
|
+
apiKey: apiKeyToSet
|
|
549
|
+
};
|
|
550
|
+
const body = {
|
|
551
|
+
provider_config_key: integration.unique_key,
|
|
552
|
+
connection_id: uniqueKey,
|
|
553
|
+
metadata,
|
|
554
|
+
credentials
|
|
555
|
+
};
|
|
556
|
+
const response = await fetch(importConnectionUrl, {
|
|
557
|
+
method: "POST",
|
|
558
|
+
headers: {
|
|
559
|
+
Authorization: `Bearer ${process.env.NANGO_SECRET_KEY}`,
|
|
560
|
+
"Content-Type": "application/json"
|
|
561
|
+
},
|
|
562
|
+
body: JSON.stringify(body)
|
|
563
|
+
});
|
|
564
|
+
if (!response.ok) {
|
|
565
|
+
const errorText = await response.text();
|
|
566
|
+
throw new Error(
|
|
567
|
+
`Failed to import connection: HTTP ${response.status} - ${response.statusText}. Response: ${errorText}`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
} catch (error) {
|
|
571
|
+
logger.error(
|
|
572
|
+
{
|
|
573
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
574
|
+
displayName
|
|
575
|
+
},
|
|
576
|
+
`Unexpected error creating API key credential '${displayName}'`
|
|
577
|
+
);
|
|
578
|
+
throw new Error(
|
|
579
|
+
`Failed to create API key credential '${displayName}': ${error instanceof Error ? error.message : "Unknown error"}`
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Fetch credentials from Nango API using connection information
|
|
585
|
+
* @param connectionId - The connection ID for the Nango connection
|
|
586
|
+
* @param providerConfigKey - The provider config key for the Nango connection
|
|
587
|
+
* @returns The credential data or null if the credentials are not found
|
|
588
|
+
*/
|
|
589
|
+
async fetchCredentialsFromNango({
|
|
590
|
+
connectionId,
|
|
591
|
+
providerConfigKey
|
|
592
|
+
}) {
|
|
593
|
+
try {
|
|
594
|
+
const nangoConnection = await this.nangoClient.getConnection(providerConfigKey, connectionId);
|
|
595
|
+
const tokenAndCredentials = this.getAccessToken(nangoConnection.credentials) ?? {};
|
|
596
|
+
const credentialData = {
|
|
597
|
+
...tokenAndCredentials,
|
|
598
|
+
connectionId,
|
|
599
|
+
providerConfigKey,
|
|
600
|
+
provider: nangoConnection.provider || "unknown",
|
|
601
|
+
secretKey: this.nangoConfig.secretKey,
|
|
602
|
+
metadata: this.sanitizeMetadata(nangoConnection.metadata ?? {})
|
|
603
|
+
};
|
|
604
|
+
return credentialData;
|
|
605
|
+
} catch (error) {
|
|
606
|
+
logger.error(
|
|
607
|
+
{
|
|
608
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
609
|
+
connectionId,
|
|
610
|
+
providerConfigKey
|
|
611
|
+
},
|
|
612
|
+
"Error fetching credentials from Nango"
|
|
613
|
+
);
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Get credentials by key - implements CredentialStore interface
|
|
619
|
+
* Key format: JSON string with connectionId and providerConfigKey
|
|
620
|
+
*/
|
|
621
|
+
async get(key) {
|
|
622
|
+
try {
|
|
623
|
+
const parsedKey = parseCredentialKey(key);
|
|
624
|
+
if (!parsedKey) {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
const { connectionId, providerConfigKey } = parsedKey;
|
|
628
|
+
const credentials = await this.fetchCredentialsFromNango({ connectionId, providerConfigKey });
|
|
629
|
+
if (!credentials) {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
const credentialString = JSON.stringify(credentials);
|
|
633
|
+
return credentialString;
|
|
634
|
+
} catch (error) {
|
|
635
|
+
logger.error(
|
|
636
|
+
{
|
|
637
|
+
storeId: this.id,
|
|
638
|
+
key: key.substring(0, 100),
|
|
639
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
640
|
+
},
|
|
641
|
+
"Error getting credentials from Nango"
|
|
642
|
+
);
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Set credentials - this is used to save bearer auth
|
|
648
|
+
* Key format: JSON string with connectionId and providerConfigKey
|
|
649
|
+
*/
|
|
650
|
+
async set(key, value, metadata = {}) {
|
|
651
|
+
const parsedKey = parseCredentialKey(key);
|
|
652
|
+
if (!parsedKey) {
|
|
653
|
+
throw new Error(`Invalid credential key: ${key}`);
|
|
654
|
+
}
|
|
655
|
+
const { connectionId, providerConfigKey, integrationDisplayName } = parsedKey;
|
|
656
|
+
await this.createNangoApiKeyConnection({
|
|
657
|
+
uniqueKey: connectionId,
|
|
658
|
+
displayName: integrationDisplayName ?? providerConfigKey,
|
|
659
|
+
apiKeyToSet: value,
|
|
660
|
+
metadata
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Check if credentials exist by attempting to fetch them
|
|
665
|
+
*/
|
|
666
|
+
async has(key) {
|
|
667
|
+
try {
|
|
668
|
+
const credentials = await this.get(key);
|
|
669
|
+
return credentials !== null;
|
|
670
|
+
} catch (error) {
|
|
671
|
+
logger.error(
|
|
672
|
+
{
|
|
673
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
674
|
+
key
|
|
675
|
+
},
|
|
676
|
+
"Error checking credentials existence"
|
|
677
|
+
);
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Delete credentials - not supported for Nango (revoke through Nango dashboard)
|
|
683
|
+
*/
|
|
684
|
+
async delete(key) {
|
|
685
|
+
try {
|
|
686
|
+
const parsedKey = parseCredentialKey(key);
|
|
687
|
+
if (!parsedKey) {
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
const { connectionId, providerConfigKey } = parsedKey;
|
|
691
|
+
await this.nangoClient.deleteConnection(providerConfigKey, connectionId);
|
|
692
|
+
return true;
|
|
693
|
+
} catch (error) {
|
|
694
|
+
logger.error(
|
|
695
|
+
{
|
|
696
|
+
storeId: this.id,
|
|
697
|
+
key: key.substring(0, 100),
|
|
698
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
699
|
+
},
|
|
700
|
+
"Error deleting credentials from Nango"
|
|
701
|
+
);
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Check if the credential store is available and functional
|
|
707
|
+
*/
|
|
708
|
+
async checkAvailability() {
|
|
709
|
+
if (!this.nangoConfig.secretKey) {
|
|
710
|
+
return {
|
|
711
|
+
available: false,
|
|
712
|
+
reason: "Nango secret key not configured"
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
if (this.nangoConfig.secretKey.includes("mock") || this.nangoConfig.secretKey === "your_nango_secret_key") {
|
|
716
|
+
return {
|
|
717
|
+
available: false,
|
|
718
|
+
reason: "Nango secret key appears to be a placeholder or mock value"
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
available: true
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
function createNangoCredentialStore(id, config) {
|
|
727
|
+
const nangoSecretKey = config?.secretKey || process.env.NANGO_SECRET_KEY;
|
|
728
|
+
if (!nangoSecretKey || nangoSecretKey === "your_nango_secret_key" || nangoSecretKey.includes("mock")) {
|
|
729
|
+
throw new Error(
|
|
730
|
+
"NANGO_SECRET_KEY environment variable is required and must be a real Nango secret key (not mock/placeholder)"
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
return new NangoCredentialStore(id, {
|
|
734
|
+
apiUrl: "https://api.nango.dev",
|
|
735
|
+
...config,
|
|
736
|
+
secretKey: nangoSecretKey
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// src/credential-stores/defaults.ts
|
|
741
|
+
function createDefaultCredentialStores() {
|
|
742
|
+
const stores = [];
|
|
743
|
+
stores.push(new InMemoryCredentialStore("memory-default"));
|
|
744
|
+
if (process.env.NANGO_SECRET_KEY) {
|
|
745
|
+
stores.push(
|
|
746
|
+
createNangoCredentialStore("nango-default", {
|
|
747
|
+
apiUrl: process.env.NANGO_SERVER_URL || "https://api.nango.dev",
|
|
748
|
+
secretKey: process.env.NANGO_SECRET_KEY
|
|
749
|
+
})
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
try {
|
|
753
|
+
stores.push(createKeyChainStore("keychain-default"));
|
|
754
|
+
} catch (error) {
|
|
755
|
+
console.warn(
|
|
756
|
+
"Failed to create keychain store:",
|
|
757
|
+
error instanceof Error ? error.message : error
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
return stores;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
export { CredentialStoreRegistry, InMemoryCredentialStore, KeyChainStore, NangoCredentialStore, createDefaultCredentialStores, createKeyChainStore, createNangoCredentialStore };
|