better-auth-studio 1.0.79-beta.4 → 1.0.79-beta.40

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/routes.js CHANGED
@@ -7,6 +7,7 @@ import { hex } from '@better-auth/utils/hex';
7
7
  import { scryptAsync } from '@noble/hashes/scrypt.js';
8
8
  import { Router } from 'express';
9
9
  import { createJiti } from 'jiti';
10
+ import { createRequire } from 'module';
10
11
  import { createMockAccount, createMockSession, createMockUser, createMockVerification, getAuthAdapter, } from './auth-adapter.js';
11
12
  import { possiblePaths } from './config.js';
12
13
  import { getAuthData } from './data.js';
@@ -57,6 +58,20 @@ function getStudioVersion() {
57
58
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
58
59
  return packageJson.version || '1.0.0';
59
60
  }
61
+ const nodeModulesPath = join(process.cwd(), 'node_modules/better-auth-studio/package.json');
62
+ if (existsSync(nodeModulesPath)) {
63
+ const packageJson = JSON.parse(readFileSync(nodeModulesPath, 'utf-8'));
64
+ return packageJson.version || '1.0.0';
65
+ }
66
+ try {
67
+ const require = createRequire(import.meta.url);
68
+ const resolvedPath = require.resolve('better-auth-studio/package.json');
69
+ if (existsSync(resolvedPath)) {
70
+ const packageJson = JSON.parse(readFileSync(resolvedPath, 'utf-8'));
71
+ return packageJson.version || '1.0.0';
72
+ }
73
+ }
74
+ catch (_resolveError) { }
60
75
  }
61
76
  catch (_error) { }
62
77
  return '1.0.0';
@@ -252,7 +267,10 @@ async function findAuthConfigPath() {
252
267
  export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter, preloadedAuthOptions, accessConfig, authInstance) {
253
268
  const isSelfHosted = !!preloadedAdapter;
254
269
  const getAuthConfigSafe = async () => {
255
- if (isSelfHosted && preloadedAuthOptions) {
270
+ if (isSelfHosted) {
271
+ return preloadedAuthOptions || authConfig || null;
272
+ }
273
+ if (preloadedAuthOptions) {
256
274
  return preloadedAuthOptions;
257
275
  }
258
276
  try {
@@ -270,7 +288,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
270
288
  catch (_error) {
271
289
  // Ignors errors
272
290
  }
273
- return null;
291
+ return authConfig || null;
274
292
  };
275
293
  const router = Router();
276
294
  const base64UrlEncode = (value) => Buffer.from(value)
@@ -302,6 +320,10 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
302
320
  // For self-hosted studio, wrap the preloaded adapter to match expected interface
303
321
  return {
304
322
  ...preloadedAdapter,
323
+ findUnique: preloadedAdapter.findUnique?.bind(preloadedAdapter),
324
+ findOne: preloadedAdapter.findOne?.bind(preloadedAdapter) ||
325
+ preloadedAdapter.findUnique?.bind(preloadedAdapter),
326
+ findFirst: preloadedAdapter.findFirst?.bind(preloadedAdapter),
305
327
  findMany: preloadedAdapter.findMany?.bind(preloadedAdapter),
306
328
  create: preloadedAdapter.create?.bind(preloadedAdapter),
307
329
  update: preloadedAdapter.update?.bind(preloadedAdapter),
@@ -476,7 +498,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
476
498
  if (!email || !password) {
477
499
  return res.status(400).json({ success: false, message: 'Email and password required' });
478
500
  }
479
- const adapter = await getAuthAdapter();
501
+ const adapter = await getAuthAdapterWithConfig();
480
502
  let signInResult = null;
481
503
  let signInError = null;
482
504
  try {
@@ -517,7 +539,6 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
517
539
  message: 'Password not configured. Please reset your password.',
518
540
  });
519
541
  }
520
- console.log({ users, credentialAccount });
521
542
  const isValidPassword = await verifyPassword(password, credentialAccount.password);
522
543
  if (!isValidPassword) {
523
544
  return res.status(401).json({ success: false, message: 'Invalid credentials' });
@@ -528,7 +549,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
528
549
  if (!allowedRoles.includes(user.role)) {
529
550
  return res.status(403).json({
530
551
  success: false,
531
- message: `Access denied. Required role: ${allowedRoles.join(' or ')}`,
552
+ message: `Access denied.`,
532
553
  userRole: user.role || 'none',
533
554
  });
534
555
  }
@@ -577,7 +598,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
577
598
  if (!allowedRoles.includes(user.role)) {
578
599
  return res.status(403).json({
579
600
  success: false,
580
- message: `Access denied. Required role: ${allowedRoles.join(' or ')}`,
601
+ message: `Access denied.`,
581
602
  userRole: user.role || 'none',
582
603
  });
583
604
  }
@@ -807,6 +828,19 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
807
828
  }
808
829
  }
809
830
  catch (_error) { }
831
+ let studioVersion = '1.0.0';
832
+ try {
833
+ const response = await fetch('https://registry.npmjs.org/better-auth-studio/latest', {
834
+ signal: AbortSignal.timeout(2000), // 2 second timeout
835
+ });
836
+ if (response.ok) {
837
+ const data = (await response.json());
838
+ studioVersion = data.version || '1.0.0';
839
+ }
840
+ }
841
+ catch (_npmError) {
842
+ studioVersion = getStudioVersion();
843
+ }
810
844
  if (databaseType === 'unknown' && !isSelfHosted) {
811
845
  const authConfigPath = configPath || (await findAuthConfigPath());
812
846
  if (authConfigPath) {
@@ -883,7 +917,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
883
917
  disabledPaths: effectiveConfig.disabledPaths || [],
884
918
  telemetry: effectiveConfig.telemetry,
885
919
  studio: {
886
- version: getStudioVersion(),
920
+ version: studioVersion,
887
921
  nodeVersion: process.version,
888
922
  platform: process.platform,
889
923
  uptime: process.uptime(),
@@ -893,7 +927,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
893
927
  });
894
928
  router.get('/api/stats', async (_req, res) => {
895
929
  try {
896
- const stats = await getAuthData(authConfig, 'stats', undefined, configPath);
930
+ const stats = await getAuthData(authConfig, 'stats', undefined, configPath, preloadedAdapter);
897
931
  res.json(stats);
898
932
  }
899
933
  catch (_error) {
@@ -908,7 +942,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
908
942
  type: type,
909
943
  from: from,
910
944
  to: to,
911
- }, configPath);
945
+ }, configPath, preloadedAdapter);
912
946
  res.json(analytics);
913
947
  }
914
948
  catch (_error) {
@@ -925,26 +959,17 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
925
959
  let organizationPluginEnabled = false;
926
960
  let teamsPluginEnabled = false;
927
961
  try {
928
- let betterAuthConfig = preloadedAuthOptions;
929
- if (!betterAuthConfig && !isSelfHosted) {
930
- const authConfigPath = configPath || (await findAuthConfigPath());
931
- if (authConfigPath) {
932
- const { getConfig } = await import('./config.js');
933
- betterAuthConfig = await getConfig({
934
- cwd: process.cwd(),
935
- configPath: authConfigPath,
936
- shouldThrowOnError: false,
937
- noCache: true, // Disable cache for real-time plugin checks
938
- });
939
- }
940
- }
962
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
941
963
  if (betterAuthConfig) {
942
964
  const plugins = betterAuthConfig.plugins || [];
943
965
  const organizationPlugin = plugins.find((plugin) => plugin.id === 'organization');
944
966
  organizationPluginEnabled = !!organizationPlugin;
945
- teamsPluginEnabled = !!organizationPlugin?.options?.teams?.enabled;
946
967
  if (organizationPlugin) {
947
- teamsPluginEnabled = organizationPlugin.options?.teams?.enabled === true;
968
+ teamsPluginEnabled =
969
+ organizationPlugin.options?.teams?.enabled === true ||
970
+ organizationPlugin.teams?.enabled === true ||
971
+ organizationPlugin.config?.teams?.enabled === true ||
972
+ false;
948
973
  }
949
974
  }
950
975
  }
@@ -1471,7 +1496,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1471
1496
  }
1472
1497
  }
1473
1498
  catch (_adapterError) { }
1474
- const result = await getAuthData(authConfig, 'users', { page, limit, search }, configPath);
1499
+ const result = await getAuthData(authConfig, 'users', { page, limit, search }, configPath, preloadedAdapter);
1475
1500
  const transformedUsers = (result.data || []).map((user) => ({
1476
1501
  id: user.id,
1477
1502
  email: user.email,
@@ -1496,7 +1521,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1496
1521
  try {
1497
1522
  const page = parseInt(req.query.page, 10) || 1;
1498
1523
  const limit = parseInt(req.query.limit, 10) || 20;
1499
- const sessions = await getAuthData(authConfig, 'sessions', { page, limit }, configPath);
1524
+ const sessions = await getAuthData(authConfig, 'sessions', { page, limit }, configPath, preloadedAdapter);
1500
1525
  res.json(sessions);
1501
1526
  }
1502
1527
  catch (_error) {
@@ -1505,7 +1530,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1505
1530
  });
1506
1531
  router.get('/api/providers', async (_req, res) => {
1507
1532
  try {
1508
- const providers = await getAuthData(authConfig, 'providers', undefined, configPath);
1533
+ const providers = await getAuthData(authConfig, 'providers', undefined, configPath, preloadedAdapter);
1509
1534
  res.json(providers);
1510
1535
  }
1511
1536
  catch (_error) {
@@ -1515,7 +1540,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1515
1540
  router.delete('/api/users/:id', async (req, res) => {
1516
1541
  try {
1517
1542
  const { id } = req.params;
1518
- await getAuthData(authConfig, 'deleteUser', { id }, configPath);
1543
+ await getAuthData(authConfig, 'deleteUser', { id }, configPath, preloadedAdapter);
1519
1544
  res.json({ success: true });
1520
1545
  }
1521
1546
  catch (_error) {
@@ -1524,60 +1549,82 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1524
1549
  });
1525
1550
  router.get('/api/plugins', async (_req, res) => {
1526
1551
  try {
1527
- const authConfigPath = configPath
1528
- ? join(process.cwd(), configPath)
1529
- : await findAuthConfigPath();
1530
- if (!authConfigPath) {
1552
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
1553
+ if (betterAuthConfig) {
1554
+ const plugins = betterAuthConfig.plugins || [];
1555
+ const pluginInfo = plugins.map((plugin) => ({
1556
+ id: plugin.id,
1557
+ name: plugin.name || plugin.id,
1558
+ description: plugin.description || `${plugin.id} plugin for Better Auth`,
1559
+ enabled: true,
1560
+ }));
1531
1561
  return res.json({
1532
- plugins: [],
1533
- error: 'No auth config found',
1534
- configPath: null,
1562
+ plugins: pluginInfo,
1563
+ configPath: isSelfHosted ? null : configPath || null,
1564
+ totalPlugins: pluginInfo.length,
1535
1565
  });
1536
1566
  }
1537
- try {
1538
- let authModule;
1539
- try {
1540
- authModule = await safeImportAuthConfig(authConfigPath, true); // Disable cache for real-time plugin checks
1567
+ if (!isSelfHosted) {
1568
+ const authConfigPath = configPath
1569
+ ? join(process.cwd(), configPath)
1570
+ : await findAuthConfigPath();
1571
+ if (!authConfigPath) {
1572
+ return res.json({
1573
+ plugins: [],
1574
+ error: 'No auth config found',
1575
+ configPath: null,
1576
+ });
1541
1577
  }
1542
- catch (_importError) {
1543
- const content = readFileSync(authConfigPath, 'utf-8');
1544
- authModule = {
1545
- auth: {
1546
- options: {
1547
- _content: content,
1548
- plugins: [],
1578
+ try {
1579
+ let authModule;
1580
+ try {
1581
+ authModule = await safeImportAuthConfig(authConfigPath, true); // Disable cache for real-time plugin checks
1582
+ }
1583
+ catch (_importError) {
1584
+ const content = readFileSync(authConfigPath, 'utf-8');
1585
+ authModule = {
1586
+ auth: {
1587
+ options: {
1588
+ _content: content,
1589
+ plugins: [],
1590
+ },
1549
1591
  },
1550
- },
1551
- };
1592
+ };
1593
+ }
1594
+ const auth = authModule.auth || authModule.default;
1595
+ if (!auth) {
1596
+ return res.json({
1597
+ plugins: [],
1598
+ error: 'No auth export found',
1599
+ configPath: authConfigPath,
1600
+ });
1601
+ }
1602
+ const plugins = auth.options?.plugins || [];
1603
+ const pluginInfo = plugins.map((plugin) => ({
1604
+ id: plugin.id,
1605
+ name: plugin.name || plugin.id,
1606
+ description: plugin.description || `${plugin.id} plugin for Better Auth`,
1607
+ enabled: true,
1608
+ }));
1609
+ return res.json({
1610
+ plugins: pluginInfo,
1611
+ configPath: authConfigPath,
1612
+ totalPlugins: pluginInfo.length,
1613
+ });
1552
1614
  }
1553
- const auth = authModule.auth || authModule.default;
1554
- if (!auth) {
1615
+ catch (_error) {
1555
1616
  return res.json({
1556
1617
  plugins: [],
1557
- error: 'No auth export found',
1618
+ error: 'Failed to load auth config - import failed and regex extraction unavailable',
1558
1619
  configPath: authConfigPath,
1559
1620
  });
1560
1621
  }
1561
- const plugins = auth.options?.plugins || [];
1562
- const pluginInfo = plugins.map((plugin) => ({
1563
- id: plugin.id,
1564
- name: plugin.name || plugin.id,
1565
- description: plugin.description || `${plugin.id} plugin for Better Auth`,
1566
- enabled: true,
1567
- }));
1568
- res.json({
1569
- plugins: pluginInfo,
1570
- configPath: authConfigPath,
1571
- totalPlugins: pluginInfo.length,
1572
- });
1573
- }
1574
- catch (_error) {
1575
- res.json({
1576
- plugins: [],
1577
- error: 'Failed to load auth config - import failed and regex extraction unavailable',
1578
- configPath: authConfigPath,
1579
- });
1580
1622
  }
1623
+ return res.json({
1624
+ plugins: [],
1625
+ error: 'No auth config found',
1626
+ configPath: null,
1627
+ });
1581
1628
  }
1582
1629
  catch (_error) {
1583
1630
  res.status(500).json({ error: 'Failed to fetch plugins' });
@@ -1585,6 +1632,13 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1585
1632
  });
1586
1633
  router.get('/api/database/info', async (_req, res) => {
1587
1634
  try {
1635
+ if (isSelfHosted && preloadedAuthOptions) {
1636
+ const database = preloadedAuthOptions.database;
1637
+ return res.json({
1638
+ database: database,
1639
+ configPath: null,
1640
+ });
1641
+ }
1588
1642
  const authConfigPath = configPath || (await findAuthConfigPath());
1589
1643
  if (!authConfigPath) {
1590
1644
  return res.json({
@@ -1978,7 +2032,6 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1978
2032
  });
1979
2033
  }
1980
2034
  });
1981
- // Database Detection endpoint - Auto-detect database from installed packages
1982
2035
  router.get('/api/database/detect', async (_req, res) => {
1983
2036
  try {
1984
2037
  const detectedDb = await detectDatabaseWithDialect();
@@ -2047,7 +2100,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2047
2100
  });
2048
2101
  router.post('/api/admin/ban-user', async (req, res) => {
2049
2102
  try {
2050
- const auth = await getAuthConfigSafe();
2103
+ const auth = preloadedAuthOptions || (await getAuthConfigSafe());
2051
2104
  if (!auth) {
2052
2105
  return res.status(400).json({
2053
2106
  success: false,
@@ -2086,7 +2139,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2086
2139
  });
2087
2140
  router.post('/api/admin/unban-user', async (req, res) => {
2088
2141
  try {
2089
- const auth = await getAuthConfigSafe();
2142
+ const auth = preloadedAuthOptions || (await getAuthConfigSafe());
2090
2143
  if (!auth) {
2091
2144
  return res.status(400).json({
2092
2145
  success: false,
@@ -2125,7 +2178,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2125
2178
  });
2126
2179
  router.get('/api/admin/status', async (_req, res) => {
2127
2180
  try {
2128
- const betterAuthConfig = await getAuthConfigSafe();
2181
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
2129
2182
  if (!betterAuthConfig) {
2130
2183
  return res.json({
2131
2184
  enabled: false,
@@ -2149,8 +2202,6 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2149
2202
  });
2150
2203
  }
2151
2204
  });
2152
- // Database Schema Visualization endpoint
2153
- // Now uses loadContextTables to dynamically load schema from Better Auth context
2154
2205
  const CONTEXT_CORE_TABLES = new Set(['user', 'session', 'account', 'verification']);
2155
2206
  async function resolveSchemaConfigPath() {
2156
2207
  if (configPath) {
@@ -2160,16 +2211,23 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2160
2211
  }
2161
2212
  async function loadContextTables() {
2162
2213
  try {
2214
+ if (isSelfHosted && authInstance?.$context) {
2215
+ try {
2216
+ const context = await authInstance.$context;
2217
+ return context?.tables || null;
2218
+ }
2219
+ catch (_error) { }
2220
+ }
2163
2221
  const authConfigPath = await resolveSchemaConfigPath();
2164
2222
  if (!authConfigPath) {
2165
2223
  return null;
2166
2224
  }
2167
2225
  const authModule = await safeImportAuthConfig(authConfigPath);
2168
- const authInstance = authModule?.auth || authModule?.default;
2169
- if (!authInstance?.$context) {
2226
+ const fileAuthInstance = authModule?.auth || authModule?.default;
2227
+ if (!fileAuthInstance?.$context) {
2170
2228
  return null;
2171
2229
  }
2172
- const context = await authInstance.$context;
2230
+ const context = await fileAuthInstance.$context;
2173
2231
  return context?.tables || null;
2174
2232
  }
2175
2233
  catch (_error) {
@@ -2293,14 +2351,11 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2293
2351
  if (!schema) {
2294
2352
  return null;
2295
2353
  }
2296
- // Filter by plugin origin if plugins are specified
2297
2354
  if (selectedPlugins && selectedPlugins.length > 0) {
2298
2355
  schema.tables = schema.tables.filter((table) => {
2299
- // Include core tables
2300
2356
  if (CONTEXT_CORE_TABLES.has(table.name)) {
2301
2357
  return true;
2302
2358
  }
2303
- // Include tables from selected plugins
2304
2359
  return selectedPlugins.includes(table.origin);
2305
2360
  });
2306
2361
  }
@@ -2352,28 +2407,46 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2352
2407
  });
2353
2408
  router.get('/api/plugins/teams/status', async (_req, res) => {
2354
2409
  try {
2355
- const betterAuthConfig = await getAuthConfigSafe();
2410
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
2356
2411
  if (!betterAuthConfig) {
2357
2412
  return res.json({
2358
2413
  enabled: false,
2359
2414
  error: 'No auth config found',
2360
- configPath: null,
2415
+ configPath: isSelfHosted ? null : configPath || null,
2361
2416
  });
2362
2417
  }
2363
2418
  const plugins = betterAuthConfig.plugins || [];
2364
2419
  const organizationPlugin = plugins.find((plugin) => plugin.id === 'organization');
2365
2420
  if (organizationPlugin) {
2366
- const teamsEnabled = organizationPlugin.options?.teams?.enabled === true;
2421
+ let teamsEnabled = false;
2422
+ if (organizationPlugin.options?.teams?.enabled === true) {
2423
+ teamsEnabled = true;
2424
+ }
2425
+ else if (organizationPlugin.teams?.enabled === true) {
2426
+ teamsEnabled = true;
2427
+ }
2428
+ else if (organizationPlugin.config?.teams?.enabled === true) {
2429
+ teamsEnabled = true;
2430
+ }
2431
+ else if (organizationPlugin.options?.teams &&
2432
+ typeof organizationPlugin.options.teams === 'object') {
2433
+ teamsEnabled = organizationPlugin.options.teams.enabled === true;
2434
+ }
2435
+ else if (organizationPlugin.teams && typeof organizationPlugin.teams === 'object') {
2436
+ teamsEnabled = organizationPlugin.teams.enabled === true;
2437
+ }
2438
+ const teamSchema = organizationPlugin.schema;
2439
+ teamsEnabled = 'team' in teamSchema;
2367
2440
  return res.json({
2368
2441
  enabled: teamsEnabled,
2369
- configPath: configPath || null,
2442
+ configPath: isSelfHosted ? null : configPath || null,
2370
2443
  organizationPlugin: organizationPlugin || null,
2371
2444
  });
2372
2445
  }
2373
2446
  else {
2374
2447
  return res.json({
2375
2448
  enabled: false,
2376
- configPath: configPath || null,
2449
+ configPath: isSelfHosted ? null : configPath || null,
2377
2450
  organizationPlugin: null,
2378
2451
  error: 'Organization plugin not found',
2379
2452
  });
@@ -2648,6 +2721,26 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2648
2721
  if (!adapter.update) {
2649
2722
  return res.status(500).json({ error: 'Adapter update method not available' });
2650
2723
  }
2724
+ let invitation = null;
2725
+ try {
2726
+ invitation = await adapter.findOne({
2727
+ model: 'invitation',
2728
+ where: [{ field: 'id', value: id }],
2729
+ });
2730
+ }
2731
+ catch (_findError) {
2732
+ if (typeof adapter.findMany === 'function') {
2733
+ const invitations = await adapter.findMany({
2734
+ model: 'invitation',
2735
+ where: [{ field: 'id', value: id }],
2736
+ limit: 1,
2737
+ });
2738
+ invitation = invitations && invitations.length > 0 ? invitations[0] : null;
2739
+ }
2740
+ }
2741
+ if (!invitation) {
2742
+ return res.status(404).json({ error: 'Invitation not found' });
2743
+ }
2651
2744
  await adapter.update({
2652
2745
  model: 'invitation',
2653
2746
  where: [{ field: 'id', value: id }],
@@ -2658,8 +2751,245 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2658
2751
  });
2659
2752
  res.json({ success: true });
2660
2753
  }
2754
+ catch (error) {
2755
+ console.error('Error resending invitation:', error);
2756
+ const errorMessage = error instanceof Error ? error.message : 'Failed to resend invitation';
2757
+ res.status(500).json({
2758
+ error: 'Failed to resend invitation',
2759
+ details: isSelfHosted ? errorMessage : undefined,
2760
+ });
2761
+ }
2762
+ });
2763
+ router.get('/api/users/:userId/invitations', async (req, res) => {
2764
+ try {
2765
+ const { userId } = req.params;
2766
+ const adapter = await getAuthAdapterWithConfig();
2767
+ if (!adapter) {
2768
+ return res.status(500).json({ error: 'Auth adapter not available' });
2769
+ }
2770
+ let user;
2771
+ try {
2772
+ user = await adapter.findOne({
2773
+ model: 'user',
2774
+ where: [{ field: 'id', value: userId }],
2775
+ });
2776
+ }
2777
+ catch (error) {
2778
+ console.error('Error fetching user:', error);
2779
+ return res.status(500).json({
2780
+ error: 'Failed to fetch user',
2781
+ details: error?.message || String(error),
2782
+ });
2783
+ }
2784
+ if (!user || !user.email) {
2785
+ return res.json({ success: true, invitations: [] });
2786
+ }
2787
+ if (typeof adapter.findMany !== 'function') {
2788
+ return res.json({ success: true, invitations: [] });
2789
+ }
2790
+ let invitations;
2791
+ try {
2792
+ invitations = await adapter.findMany({
2793
+ model: 'invitation',
2794
+ where: [{ field: 'email', value: user.email }],
2795
+ });
2796
+ }
2797
+ catch (error) {
2798
+ console.error('Error fetching invitations:', error);
2799
+ return res.json({ success: true, invitations: [] });
2800
+ }
2801
+ if (!invitations || invitations.length === 0) {
2802
+ return res.json({ success: true, invitations: [] });
2803
+ }
2804
+ const transformedInvitations = await Promise.all(invitations.map(async (invitation) => {
2805
+ let organizationName = 'Unknown';
2806
+ let teamName;
2807
+ try {
2808
+ if (invitation.organizationId &&
2809
+ (typeof adapter.findOne === 'function' || typeof adapter.findUnique === 'function')) {
2810
+ try {
2811
+ const findMethod = adapter.findOne || adapter.findUnique;
2812
+ const org = await findMethod({
2813
+ model: 'organization',
2814
+ where: [{ field: 'id', value: invitation.organizationId }],
2815
+ });
2816
+ organizationName = org?.name || 'Unknown';
2817
+ }
2818
+ catch (_orgError) {
2819
+ // Ignore org fetch errors
2820
+ }
2821
+ }
2822
+ if (invitation.teamId &&
2823
+ (typeof adapter.findOne === 'function' || typeof adapter.findUnique === 'function')) {
2824
+ try {
2825
+ const findMethod = adapter.findOne || adapter.findUnique;
2826
+ const team = await findMethod({
2827
+ model: 'team',
2828
+ where: [{ field: 'id', value: invitation.teamId }],
2829
+ });
2830
+ teamName = team?.name;
2831
+ }
2832
+ catch (_teamError) { }
2833
+ }
2834
+ }
2835
+ catch (_error) { }
2836
+ return {
2837
+ id: invitation.id,
2838
+ email: invitation.email,
2839
+ role: invitation.role || 'member',
2840
+ status: invitation.status || 'pending',
2841
+ organizationId: invitation.organizationId,
2842
+ organizationName,
2843
+ teamId: invitation.teamId,
2844
+ teamName,
2845
+ inviterId: invitation.inviterId,
2846
+ expiresAt: invitation.expiresAt,
2847
+ createdAt: invitation.createdAt,
2848
+ };
2849
+ }));
2850
+ res.json({ success: true, invitations: transformedInvitations });
2851
+ }
2852
+ catch (error) {
2853
+ console.error('Error in /api/users/:userId/invitations:', error);
2854
+ res.status(500).json({
2855
+ error: 'Failed to fetch invitations',
2856
+ details: error?.message || String(error),
2857
+ });
2858
+ }
2859
+ });
2860
+ router.post('/api/invitations/:id/accept', async (req, res) => {
2861
+ try {
2862
+ const { id } = req.params;
2863
+ const { userId } = req.body;
2864
+ const adapter = await getAuthAdapterWithConfig();
2865
+ if (!adapter) {
2866
+ return res.status(500).json({ error: 'Auth adapter not available' });
2867
+ }
2868
+ if (!userId) {
2869
+ return res.status(400).json({ error: 'User ID is required' });
2870
+ }
2871
+ const invitation = await adapter.findOne({
2872
+ model: 'invitation',
2873
+ where: [{ field: 'id', value: id }],
2874
+ });
2875
+ if (!invitation) {
2876
+ return res.status(404).json({ error: 'Invitation not found' });
2877
+ }
2878
+ if (invitation.status !== 'pending') {
2879
+ return res.status(400).json({ error: 'Invitation is not pending' });
2880
+ }
2881
+ await adapter.update({
2882
+ model: 'invitation',
2883
+ where: [{ field: 'id', value: id }],
2884
+ update: {
2885
+ status: 'accepted',
2886
+ updatedAt: new Date().toISOString(),
2887
+ },
2888
+ });
2889
+ if (invitation.organizationId) {
2890
+ try {
2891
+ // Check if member already exists
2892
+ let existingMember = null;
2893
+ if (typeof adapter.findFirst === 'function') {
2894
+ existingMember = await adapter.findFirst({
2895
+ model: 'member',
2896
+ where: [
2897
+ { field: 'organizationId', value: invitation.organizationId },
2898
+ { field: 'userId', value: userId },
2899
+ ],
2900
+ });
2901
+ }
2902
+ else if (typeof adapter.findMany === 'function') {
2903
+ const members = await adapter.findMany({
2904
+ model: 'member',
2905
+ where: [
2906
+ { field: 'organizationId', value: invitation.organizationId },
2907
+ { field: 'userId', value: userId },
2908
+ ],
2909
+ });
2910
+ existingMember = members && members.length > 0 ? members[0] : null;
2911
+ }
2912
+ if (!existingMember) {
2913
+ await adapter.create({
2914
+ model: 'member',
2915
+ data: {
2916
+ organizationId: invitation.organizationId,
2917
+ userId: userId,
2918
+ role: invitation.role || 'member',
2919
+ createdAt: new Date().toISOString(),
2920
+ },
2921
+ });
2922
+ }
2923
+ }
2924
+ catch (error) {
2925
+ console.error('Error creating member:', error);
2926
+ // Ignore errors creating membership
2927
+ }
2928
+ }
2929
+ if (invitation.teamId) {
2930
+ try {
2931
+ let existingMember = null;
2932
+ if (typeof adapter.findFirst === 'function') {
2933
+ existingMember = await adapter.findFirst({
2934
+ model: 'teamMember',
2935
+ where: [
2936
+ { field: 'teamId', value: invitation.teamId },
2937
+ { field: 'userId', value: userId },
2938
+ ],
2939
+ });
2940
+ }
2941
+ else if (typeof adapter.findMany === 'function') {
2942
+ const members = await adapter.findMany({
2943
+ model: 'teamMember',
2944
+ where: [
2945
+ { field: 'teamId', value: invitation.teamId },
2946
+ { field: 'userId', value: userId },
2947
+ ],
2948
+ });
2949
+ existingMember = members && members.length > 0 ? members[0] : null;
2950
+ }
2951
+ if (!existingMember) {
2952
+ await adapter.create({
2953
+ model: 'teamMember',
2954
+ data: {
2955
+ teamId: invitation.teamId,
2956
+ userId: userId,
2957
+ createdAt: new Date().toISOString(),
2958
+ },
2959
+ });
2960
+ }
2961
+ }
2962
+ catch (error) {
2963
+ console.error('Error creating team member:', error);
2964
+ // Ignore errors creating team membership
2965
+ }
2966
+ }
2967
+ res.json({ success: true });
2968
+ }
2969
+ catch (error) {
2970
+ console.error('Failed to accept invitation:', error);
2971
+ res.status(500).json({ error: 'Failed to accept invitation' });
2972
+ }
2973
+ });
2974
+ router.post('/api/invitations/:id/reject', async (req, res) => {
2975
+ try {
2976
+ const { id } = req.params;
2977
+ const adapter = await getAuthAdapterWithConfig();
2978
+ if (!adapter) {
2979
+ return res.status(500).json({ error: 'Auth adapter not available' });
2980
+ }
2981
+ await adapter.update({
2982
+ model: 'invitation',
2983
+ where: [{ field: 'id', value: id }],
2984
+ update: {
2985
+ status: 'rejected',
2986
+ updatedAt: new Date().toISOString(),
2987
+ },
2988
+ });
2989
+ res.json({ success: true });
2990
+ }
2661
2991
  catch (_error) {
2662
- res.status(500).json({ error: 'Failed to resend invitation' });
2992
+ res.status(500).json({ error: 'Failed to reject invitation' });
2663
2993
  }
2664
2994
  });
2665
2995
  router.delete('/api/invitations/:id', async (req, res) => {
@@ -2689,7 +3019,13 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2689
3019
  router.post('/api/organizations/:orgId/invitations', async (req, res) => {
2690
3020
  try {
2691
3021
  const { orgId } = req.params;
2692
- const { email, role = 'member', inviterId } = req.body;
3022
+ const { email, role = 'member', inviterId, teamId } = req.body;
3023
+ if (!email || typeof email !== 'string') {
3024
+ return res.status(400).json({ error: 'Email is required' });
3025
+ }
3026
+ if (!orgId) {
3027
+ return res.status(400).json({ error: 'Organization ID is required' });
3028
+ }
2693
3029
  if (!inviterId) {
2694
3030
  return res.status(400).json({ error: 'Inviter ID is required' });
2695
3031
  }
@@ -2697,60 +3033,141 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2697
3033
  if (!adapter) {
2698
3034
  return res.status(500).json({ error: 'Auth adapter not available' });
2699
3035
  }
3036
+ if (!adapter.create) {
3037
+ return res.status(500).json({ error: 'Adapter create method not available' });
3038
+ }
3039
+ try {
3040
+ const organization = await adapter.findOne({
3041
+ model: 'organization',
3042
+ where: [{ field: 'id', value: orgId }],
3043
+ });
3044
+ if (!organization) {
3045
+ return res.status(404).json({ error: 'Organization not found' });
3046
+ }
3047
+ }
3048
+ catch (orgError) {
3049
+ try {
3050
+ if (typeof adapter.findMany === 'function') {
3051
+ const orgs = await adapter.findMany({
3052
+ model: 'organization',
3053
+ where: [{ field: 'id', value: orgId }],
3054
+ limit: 1,
3055
+ });
3056
+ if (!orgs || orgs.length === 0) {
3057
+ return res.status(404).json({ error: 'Organization not found' });
3058
+ }
3059
+ }
3060
+ }
3061
+ catch (_fallbackError) { }
3062
+ }
3063
+ try {
3064
+ let existingInvitation = null;
3065
+ if (typeof adapter.findFirst === 'function') {
3066
+ existingInvitation = await adapter.findFirst({
3067
+ model: 'invitation',
3068
+ where: [
3069
+ { field: 'email', value: email.toLowerCase() },
3070
+ { field: 'organizationId', value: orgId },
3071
+ { field: 'status', value: 'pending' },
3072
+ ],
3073
+ });
3074
+ }
3075
+ else if (typeof adapter.findMany === 'function') {
3076
+ const invitations = await adapter.findMany({
3077
+ model: 'invitation',
3078
+ where: [
3079
+ { field: 'email', value: email.toLowerCase() },
3080
+ { field: 'organizationId', value: orgId },
3081
+ { field: 'status', value: 'pending' },
3082
+ ],
3083
+ limit: 1,
3084
+ });
3085
+ existingInvitation = invitations && invitations.length > 0 ? invitations[0] : null;
3086
+ }
3087
+ if (existingInvitation) {
3088
+ return res
3089
+ .status(400)
3090
+ .json({ error: 'A pending invitation already exists for this email' });
3091
+ }
3092
+ }
3093
+ catch (_duplicateCheckError) { }
2700
3094
  const invitationData = {
2701
- email,
3095
+ email: email.toLowerCase(),
2702
3096
  role,
2703
3097
  organizationId: orgId,
2704
3098
  status: 'pending',
2705
3099
  expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
2706
- createdAt: new Date(),
2707
3100
  inviterId: inviterId,
2708
3101
  };
2709
- const invitation = {
2710
- id: `inv_${Date.now()}`,
2711
- ...invitationData,
2712
- };
2713
- if (!adapter.create) {
2714
- return res.status(500).json({ error: 'Adapter create method not available' });
3102
+ if (teamId) {
3103
+ invitationData.teamId = teamId;
2715
3104
  }
2716
- await adapter.create({
3105
+ const createdInvitation = await adapter.create({
2717
3106
  model: 'invitation',
2718
- data: {
2719
- organizationId: invitationData.organizationId,
2720
- email: invitationData.email,
2721
- role: invitationData.role,
2722
- status: invitationData.status,
2723
- inviterId: invitationData.inviterId,
2724
- expiresAt: invitationData.expiresAt,
2725
- createdAt: invitationData.createdAt,
2726
- },
3107
+ data: invitationData,
2727
3108
  });
2728
- res.json({ success: true, invitation });
3109
+ if (!createdInvitation) {
3110
+ return res.status(500).json({ error: 'Failed to create invitation' });
3111
+ }
3112
+ res.json({ success: true, invitation: createdInvitation });
2729
3113
  }
2730
- catch (_error) {
2731
- res.status(500).json({ error: 'Failed to create invitation' });
3114
+ catch (error) {
3115
+ console.error('Error creating invitation:', error);
3116
+ const errorMessage = error instanceof Error ? error.message : 'Failed to create invitation';
3117
+ res.status(500).json({
3118
+ error: 'Failed to create invitation',
3119
+ details: isSelfHosted ? errorMessage : undefined,
3120
+ });
2732
3121
  }
2733
3122
  });
2734
3123
  router.get('/api/organizations/:orgId/teams', async (req, res) => {
2735
3124
  try {
2736
3125
  const { orgId } = req.params;
2737
3126
  const adapter = await getAuthAdapterWithConfig();
2738
- if (adapter && typeof adapter.findMany === 'function') {
3127
+ if (!adapter) {
3128
+ return res.status(500).json({
3129
+ success: false,
3130
+ error: 'Auth adapter not available',
3131
+ teams: [],
3132
+ });
3133
+ }
3134
+ if (typeof adapter.findMany !== 'function') {
3135
+ return res.status(500).json({
3136
+ success: false,
3137
+ error: 'Adapter findMany method not available',
3138
+ teams: [],
3139
+ });
3140
+ }
3141
+ try {
3142
+ let teams = [];
2739
3143
  try {
2740
- const teams = await adapter.findMany({
3144
+ teams = await adapter.findMany({
2741
3145
  model: 'team',
2742
3146
  where: [{ field: 'organizationId', value: orgId }],
2743
3147
  limit: 10000,
2744
3148
  });
2745
- const transformedTeams = await Promise.all((teams || []).map(async (team) => {
2746
- if (!adapter.findMany) {
2747
- return null;
3149
+ }
3150
+ catch (whereError) {
3151
+ const allTeams = await adapter.findMany({
3152
+ model: 'team',
3153
+ limit: 10000,
3154
+ });
3155
+ teams = (allTeams || []).filter((team) => team.organizationId === orgId);
3156
+ }
3157
+ if (!teams || teams.length === 0) {
3158
+ return res.json({ success: true, teams: [] });
3159
+ }
3160
+ const transformedTeams = await Promise.all((teams || []).map(async (team) => {
3161
+ try {
3162
+ let memberCount = 0;
3163
+ if (adapter.findMany) {
3164
+ const teamMembers = await adapter.findMany({
3165
+ model: 'teamMember',
3166
+ where: [{ field: 'teamId', value: team.id }],
3167
+ limit: 10000,
3168
+ });
3169
+ memberCount = teamMembers ? teamMembers.length : 0;
2748
3170
  }
2749
- const teamMembers = await adapter.findMany({
2750
- model: 'teamMember',
2751
- where: [{ field: 'teamId', value: team.id }],
2752
- limit: 10000,
2753
- });
2754
3171
  return {
2755
3172
  id: team.id,
2756
3173
  name: team.name,
@@ -2758,18 +3175,38 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2758
3175
  metadata: team.metadata,
2759
3176
  createdAt: team.createdAt,
2760
3177
  updatedAt: team.updatedAt,
2761
- memberCount: teamMembers ? teamMembers.length : 0,
3178
+ memberCount: memberCount,
2762
3179
  };
2763
- }));
2764
- res.json({ success: true, teams: transformedTeams });
2765
- return;
2766
- }
2767
- catch (_error) { }
3180
+ }
3181
+ catch (_error) {
3182
+ return {
3183
+ id: team.id,
3184
+ name: team.name,
3185
+ organizationId: team.organizationId,
3186
+ metadata: team.metadata,
3187
+ createdAt: team.createdAt,
3188
+ updatedAt: team.updatedAt,
3189
+ memberCount: 0,
3190
+ };
3191
+ }
3192
+ }));
3193
+ const validTeams = transformedTeams.filter((team) => team !== null);
3194
+ return res.json({ success: true, teams: validTeams });
3195
+ }
3196
+ catch (error) {
3197
+ return res.json({
3198
+ success: true,
3199
+ teams: [],
3200
+ error: error?.message || 'Failed to fetch teams',
3201
+ });
2768
3202
  }
2769
- res.json({ success: true, teams: [] });
2770
3203
  }
2771
- catch (_error) {
2772
- res.status(500).json({ error: 'Failed to fetch teams' });
3204
+ catch (error) {
3205
+ res.status(500).json({
3206
+ success: false,
3207
+ error: 'Failed to fetch teams',
3208
+ message: error?.message || 'Unknown error',
3209
+ });
2773
3210
  }
2774
3211
  });
2775
3212
  router.post('/api/organizations/:orgId/teams', async (req, res) => {
@@ -2794,7 +3231,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2794
3231
  if (!adapter.create) {
2795
3232
  return res.status(500).json({ error: 'Adapter create method not available' });
2796
3233
  }
2797
- await adapter.create({
3234
+ const teamResult = await adapter.create({
2798
3235
  model: 'team',
2799
3236
  data: {
2800
3237
  name: teamData.name,
@@ -2803,6 +3240,9 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2803
3240
  updatedAt: teamData.updatedAt,
2804
3241
  },
2805
3242
  });
3243
+ if (!teamResult) {
3244
+ return res.status(500).json({ error: 'Failed to create team' });
3245
+ }
2806
3246
  res.json({ success: true, team });
2807
3247
  }
2808
3248
  catch (_error) {
@@ -2872,39 +3312,83 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2872
3312
  return res.status(400).json({ error: 'userIds array is required' });
2873
3313
  }
2874
3314
  const adapter = await getAuthAdapterWithConfig();
2875
- if (!adapter || !adapter.create) {
3315
+ if (!adapter) {
2876
3316
  return res.status(500).json({ error: 'Adapter not available' });
2877
3317
  }
3318
+ if (!adapter.create) {
3319
+ return res.status(500).json({ error: 'Adapter create method not available' });
3320
+ }
2878
3321
  const results = [];
2879
3322
  for (const userId of userIds) {
2880
3323
  try {
3324
+ let existingMember = null;
3325
+ if (adapter.findMany) {
3326
+ try {
3327
+ const existing = await adapter.findMany({
3328
+ model: 'teamMember',
3329
+ where: [
3330
+ { field: 'teamId', value: teamId },
3331
+ { field: 'userId', value: userId },
3332
+ ],
3333
+ limit: 1,
3334
+ });
3335
+ existingMember = existing && existing.length > 0 ? existing[0] : null;
3336
+ }
3337
+ catch (_findError) {
3338
+ // if where clause isn't working.
3339
+ try {
3340
+ const allMembers = await adapter.findMany({
3341
+ model: 'teamMember',
3342
+ limit: 10000,
3343
+ });
3344
+ existingMember = (allMembers || []).find((m) => m.teamId === teamId && m.userId === userId);
3345
+ }
3346
+ catch (_fallbackError) { }
3347
+ }
3348
+ }
3349
+ if (existingMember) {
3350
+ results.push({
3351
+ success: false,
3352
+ userId,
3353
+ error: 'User is already a member of this team',
3354
+ });
3355
+ continue;
3356
+ }
3357
+ const now = new Date();
2881
3358
  await adapter.create({
2882
3359
  model: 'teamMember',
2883
3360
  data: {
2884
3361
  teamId,
2885
3362
  userId,
2886
3363
  role: 'member',
2887
- createdAt: new Date(),
3364
+ createdAt: now,
3365
+ updatedAt: now,
2888
3366
  },
2889
3367
  });
2890
3368
  results.push({ success: true, userId });
2891
3369
  }
2892
3370
  catch (error) {
3371
+ const errorMessage = error?.message || error?.toString() || 'Unknown error';
2893
3372
  results.push({
2894
3373
  success: false,
2895
3374
  userId,
2896
- error: error instanceof Error ? error.message : 'Unknown error',
3375
+ error: errorMessage,
2897
3376
  });
2898
3377
  }
2899
3378
  }
3379
+ const successCount = results.filter((r) => r.success).length;
2900
3380
  res.json({
2901
- success: true,
2902
- message: `Added ${results.filter((r) => r.success).length} members`,
3381
+ success: results.some((r) => r.success),
3382
+ message: `Added ${successCount} member${successCount !== 1 ? 's' : ''}`,
2903
3383
  results,
2904
3384
  });
2905
3385
  }
2906
- catch (_error) {
2907
- res.status(500).json({ error: 'Failed to add team members' });
3386
+ catch (error) {
3387
+ res.status(500).json({
3388
+ success: false,
3389
+ error: 'Failed to add team members',
3390
+ message: error?.message || 'Unknown error',
3391
+ });
2908
3392
  }
2909
3393
  });
2910
3394
  router.delete('/api/team-members/:id', async (req, res) => {
@@ -2974,7 +3458,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2974
3458
  });
2975
3459
  router.get('/api/plugins/organization/status', async (_req, res) => {
2976
3460
  try {
2977
- const betterAuthConfig = await getAuthConfigSafe();
3461
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
2978
3462
  if (!betterAuthConfig) {
2979
3463
  return res.json({
2980
3464
  enabled: false,
@@ -3106,7 +3590,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
3106
3590
  try {
3107
3591
  const { id } = req.params;
3108
3592
  const userData = req.body;
3109
- const updatedUser = await getAuthData(authConfig, 'updateUser', { id, userData }, configPath);
3593
+ const updatedUser = await getAuthData(authConfig, 'updateUser', { id, userData }, configPath, preloadedAdapter);
3110
3594
  res.json({ success: true, user: updatedUser });
3111
3595
  }
3112
3596
  catch (_error) {
@@ -3497,6 +3981,92 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
3497
3981
  res.status(500).json({ success: false, error: 'Failed to fetch OAuth providers' });
3498
3982
  }
3499
3983
  });
3984
+ router.get('/api/tools/oauth/credentials', async (req, res) => {
3985
+ try {
3986
+ const { provider, origin } = req.query;
3987
+ if (!provider || typeof provider !== 'string') {
3988
+ return res.status(400).json({
3989
+ success: false,
3990
+ error: 'Provider is required',
3991
+ });
3992
+ }
3993
+ if (!origin || typeof origin !== 'string') {
3994
+ return res.status(400).json({
3995
+ success: false,
3996
+ error: 'Origin is required',
3997
+ });
3998
+ }
3999
+ // TODO: Import getOAuthCredentials at the top of this file:
4000
+ // import { getOAuthCredentials } from './path/to/your/oauth-config';
4001
+ // For now, we'll access it from a function that should be provided
4002
+ // This assumes getOAuthCredentials is available in the scope
4003
+ // You need to import it: import { getOAuthCredentials } from './your-oauth-config-file';
4004
+ // Placeholder - replace this with actual import at top of file
4005
+ const getOAuthCredentials = global.getOAuthCredentials;
4006
+ if (typeof getOAuthCredentials !== 'function') {
4007
+ return res.status(500).json({
4008
+ success: false,
4009
+ error: 'OAuth credentials function not configured. Please import getOAuthCredentials function.',
4010
+ });
4011
+ }
4012
+ const credentialsResult = getOAuthCredentials(provider, origin);
4013
+ // Handle null return (provider not found)
4014
+ if (credentialsResult === null) {
4015
+ return res.status(404).json({
4016
+ success: false,
4017
+ error: 'No credential found',
4018
+ });
4019
+ }
4020
+ // Handle error cases with proper messages as requested
4021
+ if (credentialsResult.error) {
4022
+ if (credentialsResult.error === 'NO_CREDENTIALS_FOUND') {
4023
+ return res.status(404).json({
4024
+ success: false,
4025
+ error: 'No credential found',
4026
+ });
4027
+ }
4028
+ else if (credentialsResult.error === 'INVALID_ORIGIN') {
4029
+ return res.status(400).json({
4030
+ success: false,
4031
+ error: 'Invalid origin. OAuth credentials are only available for localhost origins.',
4032
+ });
4033
+ }
4034
+ else {
4035
+ return res.status(400).json({
4036
+ success: false,
4037
+ error: credentialsResult.error || 'Failed to get OAuth credentials',
4038
+ });
4039
+ }
4040
+ }
4041
+ // Check if result exists and has required fields
4042
+ if (!credentialsResult.result) {
4043
+ return res.status(404).json({
4044
+ success: false,
4045
+ error: 'No credential found',
4046
+ });
4047
+ }
4048
+ const { clientId, clientSecret } = credentialsResult.result;
4049
+ if (!clientId || !clientSecret) {
4050
+ return res.status(404).json({
4051
+ success: false,
4052
+ error: 'No credential found',
4053
+ });
4054
+ }
4055
+ res.json({
4056
+ success: true,
4057
+ clientId,
4058
+ clientSecret,
4059
+ });
4060
+ }
4061
+ catch (error) {
4062
+ console.error('Failed to fetch OAuth credentials:', error);
4063
+ res.status(500).json({
4064
+ success: false,
4065
+ error: 'Failed to fetch OAuth credentials',
4066
+ details: error instanceof Error ? error.message : String(error),
4067
+ });
4068
+ }
4069
+ });
3500
4070
  router.post('/api/tools/oauth/test', async (req, res) => {
3501
4071
  try {
3502
4072
  const { provider } = req.body;