better-auth-studio 1.0.79-beta.9 → 1.1.1-beta.1

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.
Files changed (72) hide show
  1. package/README.md +10 -0
  2. package/dist/adapters/astro.d.ts +22 -0
  3. package/dist/adapters/astro.d.ts.map +1 -0
  4. package/dist/adapters/astro.js +95 -0
  5. package/dist/adapters/astro.js.map +1 -0
  6. package/dist/adapters/elysia.d.ts +7 -0
  7. package/dist/adapters/elysia.d.ts.map +1 -0
  8. package/dist/adapters/elysia.js +101 -0
  9. package/dist/adapters/elysia.js.map +1 -0
  10. package/dist/adapters/hono.d.ts +7 -0
  11. package/dist/adapters/hono.d.ts.map +1 -0
  12. package/dist/adapters/hono.js +78 -0
  13. package/dist/adapters/hono.js.map +1 -0
  14. package/dist/adapters/nextjs.d.ts +1 -1
  15. package/dist/adapters/nextjs.d.ts.map +1 -1
  16. package/dist/adapters/nextjs.js +1 -1
  17. package/dist/adapters/nextjs.js.map +1 -1
  18. package/dist/adapters/nuxt.d.ts +19 -0
  19. package/dist/adapters/nuxt.d.ts.map +1 -0
  20. package/dist/adapters/nuxt.js +99 -0
  21. package/dist/adapters/nuxt.js.map +1 -0
  22. package/dist/adapters/remix.d.ts +26 -0
  23. package/dist/adapters/remix.d.ts.map +1 -0
  24. package/dist/adapters/remix.js +98 -0
  25. package/dist/adapters/remix.js.map +1 -0
  26. package/dist/adapters/solid-start.d.ts +30 -0
  27. package/dist/adapters/solid-start.d.ts.map +1 -0
  28. package/dist/adapters/solid-start.js +96 -0
  29. package/dist/adapters/solid-start.js.map +1 -0
  30. package/dist/adapters/svelte-kit.d.ts +36 -0
  31. package/dist/adapters/svelte-kit.d.ts.map +1 -0
  32. package/dist/adapters/svelte-kit.js +108 -0
  33. package/dist/adapters/svelte-kit.js.map +1 -0
  34. package/dist/adapters/tanstack-start.d.ts +32 -0
  35. package/dist/adapters/tanstack-start.d.ts.map +1 -0
  36. package/dist/adapters/tanstack-start.js +102 -0
  37. package/dist/adapters/tanstack-start.js.map +1 -0
  38. package/dist/cli/commands/init.d.ts +3 -1
  39. package/dist/cli/commands/init.d.ts.map +1 -1
  40. package/dist/cli/commands/init.js +124 -9
  41. package/dist/cli/commands/init.js.map +1 -1
  42. package/dist/cli.js +3 -2
  43. package/dist/cli.js.map +1 -1
  44. package/dist/core/handler.d.ts.map +1 -1
  45. package/dist/core/handler.js +214 -77
  46. package/dist/core/handler.js.map +1 -1
  47. package/dist/data.d.ts +1 -1
  48. package/dist/data.d.ts.map +1 -1
  49. package/dist/data.js +3 -7
  50. package/dist/data.js.map +1 -1
  51. package/dist/public/assets/main-COYGEnAm.js +1150 -0
  52. package/dist/public/assets/main-s8HrXBxq.css +1 -0
  53. package/dist/public/index.html +2 -2
  54. package/dist/routes.d.ts.map +1 -1
  55. package/dist/routes.js +758 -150
  56. package/dist/routes.js.map +1 -1
  57. package/dist/studio.d.ts.map +1 -1
  58. package/dist/studio.js +15 -2
  59. package/dist/studio.js.map +1 -1
  60. package/dist/utils/html-injector.js +44 -1
  61. package/dist/utils/html-injector.js.map +1 -1
  62. package/package.json +45 -34
  63. package/public/assets/main-COYGEnAm.js +1150 -0
  64. package/public/assets/main-s8HrXBxq.css +1 -0
  65. package/public/index.html +2 -2
  66. package/scripts/postinstall.js +21 -14
  67. package/data/GeoLite2-City.mmdb +0 -0
  68. package/data/GeoLite2-City.tar.gz +0 -1
  69. package/dist/public/assets/main-3NIBCudD.js +0 -1155
  70. package/dist/public/assets/main-DbXDm13A.css +0 -1
  71. package/public/assets/main-3NIBCudD.js +0 -1155
  72. package/public/assets/main-DbXDm13A.css +0 -1
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),
@@ -472,11 +494,13 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
472
494
  if (!authInstance) {
473
495
  return res.status(500).json({ success: false, message: 'Auth not configured' });
474
496
  }
475
- const { email, password } = req.body;
497
+ // Safety check: ensure body is an object before destructuring
498
+ const body = req.body || {};
499
+ const { email, password } = body;
476
500
  if (!email || !password) {
477
501
  return res.status(400).json({ success: false, message: 'Email and password required' });
478
502
  }
479
- const adapter = await getAuthAdapter();
503
+ const adapter = await getAuthAdapterWithConfig();
480
504
  let signInResult = null;
481
505
  let signInError = null;
482
506
  try {
@@ -517,7 +541,6 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
517
541
  message: 'Password not configured. Please reset your password.',
518
542
  });
519
543
  }
520
- console.log({ users, credentialAccount });
521
544
  const isValidPassword = await verifyPassword(password, credentialAccount.password);
522
545
  if (!isValidPassword) {
523
546
  return res.status(401).json({ success: false, message: 'Invalid credentials' });
@@ -528,7 +551,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
528
551
  if (!allowedRoles.includes(user.role)) {
529
552
  return res.status(403).json({
530
553
  success: false,
531
- message: `Access denied. Required role: ${allowedRoles.join(' or ')}`,
554
+ message: `Access denied.`,
532
555
  userRole: user.role || 'none',
533
556
  });
534
557
  }
@@ -577,7 +600,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
577
600
  if (!allowedRoles.includes(user.role)) {
578
601
  return res.status(403).json({
579
602
  success: false,
580
- message: `Access denied. Required role: ${allowedRoles.join(' or ')}`,
603
+ message: `Access denied.`,
581
604
  userRole: user.role || 'none',
582
605
  });
583
606
  }
@@ -755,7 +778,8 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
755
778
  });
756
779
  router.post('/api/geo/resolve', (req, res) => {
757
780
  try {
758
- const { ipAddress } = req.body;
781
+ const body = req.body || {};
782
+ const { ipAddress } = body;
759
783
  if (!ipAddress) {
760
784
  return res.status(400).json({
761
785
  success: false,
@@ -807,6 +831,19 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
807
831
  }
808
832
  }
809
833
  catch (_error) { }
834
+ let studioVersion = '1.0.0';
835
+ try {
836
+ const response = await fetch('https://registry.npmjs.org/better-auth-studio/latest', {
837
+ signal: AbortSignal.timeout(2000), // 2 second timeout
838
+ });
839
+ if (response.ok) {
840
+ const data = (await response.json());
841
+ studioVersion = data.version || '1.0.0';
842
+ }
843
+ }
844
+ catch (_npmError) {
845
+ studioVersion = getStudioVersion();
846
+ }
810
847
  if (databaseType === 'unknown' && !isSelfHosted) {
811
848
  const authConfigPath = configPath || (await findAuthConfigPath());
812
849
  if (authConfigPath) {
@@ -883,7 +920,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
883
920
  disabledPaths: effectiveConfig.disabledPaths || [],
884
921
  telemetry: effectiveConfig.telemetry,
885
922
  studio: {
886
- version: getStudioVersion(),
923
+ version: studioVersion,
887
924
  nodeVersion: process.version,
888
925
  platform: process.platform,
889
926
  uptime: process.uptime(),
@@ -893,7 +930,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
893
930
  });
894
931
  router.get('/api/stats', async (_req, res) => {
895
932
  try {
896
- const stats = await getAuthData(authConfig, 'stats', undefined, configPath);
933
+ const stats = await getAuthData(authConfig, 'stats', undefined, configPath, preloadedAdapter);
897
934
  res.json(stats);
898
935
  }
899
936
  catch (_error) {
@@ -908,7 +945,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
908
945
  type: type,
909
946
  from: from,
910
947
  to: to,
911
- }, configPath);
948
+ }, configPath, preloadedAdapter);
912
949
  res.json(analytics);
913
950
  }
914
951
  catch (_error) {
@@ -925,26 +962,17 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
925
962
  let organizationPluginEnabled = false;
926
963
  let teamsPluginEnabled = false;
927
964
  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
- }
965
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
941
966
  if (betterAuthConfig) {
942
967
  const plugins = betterAuthConfig.plugins || [];
943
968
  const organizationPlugin = plugins.find((plugin) => plugin.id === 'organization');
944
969
  organizationPluginEnabled = !!organizationPlugin;
945
- teamsPluginEnabled = !!organizationPlugin?.options?.teams?.enabled;
946
970
  if (organizationPlugin) {
947
- teamsPluginEnabled = organizationPlugin.options?.teams?.enabled === true;
971
+ teamsPluginEnabled =
972
+ organizationPlugin.options?.teams?.enabled === true ||
973
+ organizationPlugin.teams?.enabled === true ||
974
+ organizationPlugin.config?.teams?.enabled === true ||
975
+ false;
948
976
  }
949
977
  }
950
978
  }
@@ -1045,7 +1073,8 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1045
1073
  router.put('/api/users/:userId', async (req, res) => {
1046
1074
  try {
1047
1075
  const { userId } = req.params;
1048
- const { name, email, role } = req.body;
1076
+ const body = req.body || {};
1077
+ const { name, email, role, image } = body;
1049
1078
  const adapter = await getAuthAdapterWithConfig();
1050
1079
  if (!adapter || !adapter.update) {
1051
1080
  return res.status(500).json({ error: 'Auth adapter not available' });
@@ -1054,6 +1083,9 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1054
1083
  if (role !== undefined) {
1055
1084
  updateData.role = role;
1056
1085
  }
1086
+ if (image !== undefined) {
1087
+ updateData.image = image;
1088
+ }
1057
1089
  const user = await adapter.update({
1058
1090
  model: 'user',
1059
1091
  where: [{ field: 'id', value: userId }],
@@ -1068,14 +1100,42 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1068
1100
  router.put('/api/users/:userId/password', async (req, res) => {
1069
1101
  try {
1070
1102
  const { userId } = req.params;
1071
- const { password } = req.body;
1103
+ const body = req.body || {};
1104
+ const { password } = body;
1072
1105
  if (!password) {
1073
1106
  return res.status(400).json({ error: 'Password is required' });
1074
1107
  }
1075
1108
  const adapter = await getAuthAdapterWithConfig();
1076
- if (!adapter || !adapter.update) {
1109
+ if (!adapter) {
1077
1110
  return res.status(500).json({ error: 'Auth adapter not available' });
1078
1111
  }
1112
+ if (!adapter.update) {
1113
+ return res.status(500).json({ error: 'Auth adapter update method not available' });
1114
+ }
1115
+ // Find the credential account first to get its unique id (fixes Prisma error)
1116
+ let credentialAccount = null;
1117
+ if (adapter.findFirst) {
1118
+ credentialAccount = await adapter.findFirst({
1119
+ model: 'account',
1120
+ where: [
1121
+ { field: 'userId', value: userId },
1122
+ { field: 'providerId', value: 'credential' },
1123
+ ],
1124
+ });
1125
+ }
1126
+ else if (adapter.findMany) {
1127
+ const accounts = await adapter.findMany({
1128
+ model: 'account',
1129
+ where: [
1130
+ { field: 'userId', value: userId },
1131
+ { field: 'providerId', value: 'credential' },
1132
+ ],
1133
+ });
1134
+ credentialAccount = accounts && accounts.length > 0 ? accounts[0] : null;
1135
+ }
1136
+ if (!credentialAccount) {
1137
+ return res.status(404).json({ error: 'Credential account not found' });
1138
+ }
1079
1139
  let hashedPassword = password;
1080
1140
  try {
1081
1141
  const salt = hex.encode(crypto.getRandomValues(new Uint8Array(16)));
@@ -1083,17 +1143,18 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1083
1143
  hashedPassword = `${salt}:${hex.encode(key)}`;
1084
1144
  }
1085
1145
  catch {
1086
- res.status(500).json({ error: 'Failed to hash password' });
1146
+ return res.status(500).json({ error: 'Failed to hash password' });
1087
1147
  }
1088
- const account = await adapter.update({
1148
+ // Update using the account's unique id to fix Prisma error
1149
+ const updatedAccount = await adapter.update({
1089
1150
  model: 'account',
1090
- where: [
1091
- { field: 'userId', value: userId },
1092
- { field: 'providerId', value: 'credential' },
1093
- ],
1094
- update: { password: hashedPassword },
1151
+ where: [{ field: 'id', value: credentialAccount.id }],
1152
+ update: {
1153
+ password: hashedPassword,
1154
+ updatedAt: new Date().toISOString(),
1155
+ },
1095
1156
  });
1096
- res.json({ success: true, account });
1157
+ res.json({ success: true, account: updatedAccount });
1097
1158
  }
1098
1159
  catch (error) {
1099
1160
  res.status(500).json({ error: 'Failed to update password', message: error?.message });
@@ -1471,7 +1532,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1471
1532
  }
1472
1533
  }
1473
1534
  catch (_adapterError) { }
1474
- const result = await getAuthData(authConfig, 'users', { page, limit, search }, configPath);
1535
+ const result = await getAuthData(authConfig, 'users', { page, limit, search }, configPath, preloadedAdapter);
1475
1536
  const transformedUsers = (result.data || []).map((user) => ({
1476
1537
  id: user.id,
1477
1538
  email: user.email,
@@ -1496,7 +1557,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1496
1557
  try {
1497
1558
  const page = parseInt(req.query.page, 10) || 1;
1498
1559
  const limit = parseInt(req.query.limit, 10) || 20;
1499
- const sessions = await getAuthData(authConfig, 'sessions', { page, limit }, configPath);
1560
+ const sessions = await getAuthData(authConfig, 'sessions', { page, limit }, configPath, preloadedAdapter);
1500
1561
  res.json(sessions);
1501
1562
  }
1502
1563
  catch (_error) {
@@ -1505,7 +1566,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1505
1566
  });
1506
1567
  router.get('/api/providers', async (_req, res) => {
1507
1568
  try {
1508
- const providers = await getAuthData(authConfig, 'providers', undefined, configPath);
1569
+ const providers = await getAuthData(authConfig, 'providers', undefined, configPath, preloadedAdapter);
1509
1570
  res.json(providers);
1510
1571
  }
1511
1572
  catch (_error) {
@@ -1515,7 +1576,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1515
1576
  router.delete('/api/users/:id', async (req, res) => {
1516
1577
  try {
1517
1578
  const { id } = req.params;
1518
- await getAuthData(authConfig, 'deleteUser', { id }, configPath);
1579
+ await getAuthData(authConfig, 'deleteUser', { id }, configPath, preloadedAdapter);
1519
1580
  res.json({ success: true });
1520
1581
  }
1521
1582
  catch (_error) {
@@ -1524,60 +1585,82 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1524
1585
  });
1525
1586
  router.get('/api/plugins', async (_req, res) => {
1526
1587
  try {
1527
- const authConfigPath = configPath
1528
- ? join(process.cwd(), configPath)
1529
- : await findAuthConfigPath();
1530
- if (!authConfigPath) {
1588
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
1589
+ if (betterAuthConfig) {
1590
+ const plugins = betterAuthConfig.plugins || [];
1591
+ const pluginInfo = plugins.map((plugin) => ({
1592
+ id: plugin.id,
1593
+ name: plugin.name || plugin.id,
1594
+ description: plugin.description || `${plugin.id} plugin for Better Auth`,
1595
+ enabled: true,
1596
+ }));
1531
1597
  return res.json({
1532
- plugins: [],
1533
- error: 'No auth config found',
1534
- configPath: null,
1598
+ plugins: pluginInfo,
1599
+ configPath: isSelfHosted ? null : configPath || null,
1600
+ totalPlugins: pluginInfo.length,
1535
1601
  });
1536
1602
  }
1537
- try {
1538
- let authModule;
1539
- try {
1540
- authModule = await safeImportAuthConfig(authConfigPath, true); // Disable cache for real-time plugin checks
1603
+ if (!isSelfHosted) {
1604
+ const authConfigPath = configPath
1605
+ ? join(process.cwd(), configPath)
1606
+ : await findAuthConfigPath();
1607
+ if (!authConfigPath) {
1608
+ return res.json({
1609
+ plugins: [],
1610
+ error: 'No auth config found',
1611
+ configPath: null,
1612
+ });
1541
1613
  }
1542
- catch (_importError) {
1543
- const content = readFileSync(authConfigPath, 'utf-8');
1544
- authModule = {
1545
- auth: {
1546
- options: {
1547
- _content: content,
1548
- plugins: [],
1614
+ try {
1615
+ let authModule;
1616
+ try {
1617
+ authModule = await safeImportAuthConfig(authConfigPath, true); // Disable cache for real-time plugin checks
1618
+ }
1619
+ catch (_importError) {
1620
+ const content = readFileSync(authConfigPath, 'utf-8');
1621
+ authModule = {
1622
+ auth: {
1623
+ options: {
1624
+ _content: content,
1625
+ plugins: [],
1626
+ },
1549
1627
  },
1550
- },
1551
- };
1628
+ };
1629
+ }
1630
+ const auth = authModule.auth || authModule.default;
1631
+ if (!auth) {
1632
+ return res.json({
1633
+ plugins: [],
1634
+ error: 'No auth export found',
1635
+ configPath: authConfigPath,
1636
+ });
1637
+ }
1638
+ const plugins = auth.options?.plugins || [];
1639
+ const pluginInfo = plugins.map((plugin) => ({
1640
+ id: plugin.id,
1641
+ name: plugin.name || plugin.id,
1642
+ description: plugin.description || `${plugin.id} plugin for Better Auth`,
1643
+ enabled: true,
1644
+ }));
1645
+ return res.json({
1646
+ plugins: pluginInfo,
1647
+ configPath: authConfigPath,
1648
+ totalPlugins: pluginInfo.length,
1649
+ });
1552
1650
  }
1553
- const auth = authModule.auth || authModule.default;
1554
- if (!auth) {
1651
+ catch (_error) {
1555
1652
  return res.json({
1556
1653
  plugins: [],
1557
- error: 'No auth export found',
1654
+ error: 'Failed to load auth config - import failed and regex extraction unavailable',
1558
1655
  configPath: authConfigPath,
1559
1656
  });
1560
1657
  }
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
1658
  }
1659
+ return res.json({
1660
+ plugins: [],
1661
+ error: 'No auth config found',
1662
+ configPath: null,
1663
+ });
1581
1664
  }
1582
1665
  catch (_error) {
1583
1666
  res.status(500).json({ error: 'Failed to fetch plugins' });
@@ -1585,6 +1668,13 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1585
1668
  });
1586
1669
  router.get('/api/database/info', async (_req, res) => {
1587
1670
  try {
1671
+ if (isSelfHosted && preloadedAuthOptions) {
1672
+ const database = preloadedAuthOptions.database;
1673
+ return res.json({
1674
+ database: database,
1675
+ configPath: null,
1676
+ });
1677
+ }
1588
1678
  const authConfigPath = configPath || (await findAuthConfigPath());
1589
1679
  if (!authConfigPath) {
1590
1680
  return res.json({
@@ -1978,7 +2068,6 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1978
2068
  });
1979
2069
  }
1980
2070
  });
1981
- // Database Detection endpoint - Auto-detect database from installed packages
1982
2071
  router.get('/api/database/detect', async (_req, res) => {
1983
2072
  try {
1984
2073
  const detectedDb = await detectDatabaseWithDialect();
@@ -2047,7 +2136,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2047
2136
  });
2048
2137
  router.post('/api/admin/ban-user', async (req, res) => {
2049
2138
  try {
2050
- const auth = await getAuthConfigSafe();
2139
+ const auth = preloadedAuthOptions || (await getAuthConfigSafe());
2051
2140
  if (!auth) {
2052
2141
  return res.status(400).json({
2053
2142
  success: false,
@@ -2086,7 +2175,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2086
2175
  });
2087
2176
  router.post('/api/admin/unban-user', async (req, res) => {
2088
2177
  try {
2089
- const auth = await getAuthConfigSafe();
2178
+ const auth = preloadedAuthOptions || (await getAuthConfigSafe());
2090
2179
  if (!auth) {
2091
2180
  return res.status(400).json({
2092
2181
  success: false,
@@ -2125,7 +2214,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2125
2214
  });
2126
2215
  router.get('/api/admin/status', async (_req, res) => {
2127
2216
  try {
2128
- const betterAuthConfig = await getAuthConfigSafe();
2217
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
2129
2218
  if (!betterAuthConfig) {
2130
2219
  return res.json({
2131
2220
  enabled: false,
@@ -2149,8 +2238,6 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2149
2238
  });
2150
2239
  }
2151
2240
  });
2152
- // Database Schema Visualization endpoint
2153
- // Now uses loadContextTables to dynamically load schema from Better Auth context
2154
2241
  const CONTEXT_CORE_TABLES = new Set(['user', 'session', 'account', 'verification']);
2155
2242
  async function resolveSchemaConfigPath() {
2156
2243
  if (configPath) {
@@ -2160,16 +2247,23 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2160
2247
  }
2161
2248
  async function loadContextTables() {
2162
2249
  try {
2250
+ if (isSelfHosted && authInstance?.$context) {
2251
+ try {
2252
+ const context = await authInstance.$context;
2253
+ return context?.tables || null;
2254
+ }
2255
+ catch (_error) { }
2256
+ }
2163
2257
  const authConfigPath = await resolveSchemaConfigPath();
2164
2258
  if (!authConfigPath) {
2165
2259
  return null;
2166
2260
  }
2167
2261
  const authModule = await safeImportAuthConfig(authConfigPath);
2168
- const authInstance = authModule?.auth || authModule?.default;
2169
- if (!authInstance?.$context) {
2262
+ const fileAuthInstance = authModule?.auth || authModule?.default;
2263
+ if (!fileAuthInstance?.$context) {
2170
2264
  return null;
2171
2265
  }
2172
- const context = await authInstance.$context;
2266
+ const context = await fileAuthInstance.$context;
2173
2267
  return context?.tables || null;
2174
2268
  }
2175
2269
  catch (_error) {
@@ -2293,14 +2387,11 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2293
2387
  if (!schema) {
2294
2388
  return null;
2295
2389
  }
2296
- // Filter by plugin origin if plugins are specified
2297
2390
  if (selectedPlugins && selectedPlugins.length > 0) {
2298
2391
  schema.tables = schema.tables.filter((table) => {
2299
- // Include core tables
2300
2392
  if (CONTEXT_CORE_TABLES.has(table.name)) {
2301
2393
  return true;
2302
2394
  }
2303
- // Include tables from selected plugins
2304
2395
  return selectedPlugins.includes(table.origin);
2305
2396
  });
2306
2397
  }
@@ -2352,28 +2443,46 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2352
2443
  });
2353
2444
  router.get('/api/plugins/teams/status', async (_req, res) => {
2354
2445
  try {
2355
- const betterAuthConfig = await getAuthConfigSafe();
2446
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
2356
2447
  if (!betterAuthConfig) {
2357
2448
  return res.json({
2358
2449
  enabled: false,
2359
2450
  error: 'No auth config found',
2360
- configPath: null,
2451
+ configPath: isSelfHosted ? null : configPath || null,
2361
2452
  });
2362
2453
  }
2363
2454
  const plugins = betterAuthConfig.plugins || [];
2364
2455
  const organizationPlugin = plugins.find((plugin) => plugin.id === 'organization');
2365
2456
  if (organizationPlugin) {
2366
- const teamsEnabled = organizationPlugin.options?.teams?.enabled === true;
2457
+ let teamsEnabled = false;
2458
+ if (organizationPlugin.options?.teams?.enabled === true) {
2459
+ teamsEnabled = true;
2460
+ }
2461
+ else if (organizationPlugin.teams?.enabled === true) {
2462
+ teamsEnabled = true;
2463
+ }
2464
+ else if (organizationPlugin.config?.teams?.enabled === true) {
2465
+ teamsEnabled = true;
2466
+ }
2467
+ else if (organizationPlugin.options?.teams &&
2468
+ typeof organizationPlugin.options.teams === 'object') {
2469
+ teamsEnabled = organizationPlugin.options.teams.enabled === true;
2470
+ }
2471
+ else if (organizationPlugin.teams && typeof organizationPlugin.teams === 'object') {
2472
+ teamsEnabled = organizationPlugin.teams.enabled === true;
2473
+ }
2474
+ const teamSchema = organizationPlugin.schema;
2475
+ teamsEnabled = 'team' in teamSchema;
2367
2476
  return res.json({
2368
2477
  enabled: teamsEnabled,
2369
- configPath: configPath || null,
2478
+ configPath: isSelfHosted ? null : configPath || null,
2370
2479
  organizationPlugin: organizationPlugin || null,
2371
2480
  });
2372
2481
  }
2373
2482
  else {
2374
2483
  return res.json({
2375
2484
  enabled: false,
2376
- configPath: configPath || null,
2485
+ configPath: isSelfHosted ? null : configPath || null,
2377
2486
  organizationPlugin: null,
2378
2487
  error: 'Organization plugin not found',
2379
2488
  });
@@ -2648,6 +2757,26 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2648
2757
  if (!adapter.update) {
2649
2758
  return res.status(500).json({ error: 'Adapter update method not available' });
2650
2759
  }
2760
+ let invitation = null;
2761
+ try {
2762
+ invitation = await adapter.findOne({
2763
+ model: 'invitation',
2764
+ where: [{ field: 'id', value: id }],
2765
+ });
2766
+ }
2767
+ catch (_findError) {
2768
+ if (typeof adapter.findMany === 'function') {
2769
+ const invitations = await adapter.findMany({
2770
+ model: 'invitation',
2771
+ where: [{ field: 'id', value: id }],
2772
+ limit: 1,
2773
+ });
2774
+ invitation = invitations && invitations.length > 0 ? invitations[0] : null;
2775
+ }
2776
+ }
2777
+ if (!invitation) {
2778
+ return res.status(404).json({ error: 'Invitation not found' });
2779
+ }
2651
2780
  await adapter.update({
2652
2781
  model: 'invitation',
2653
2782
  where: [{ field: 'id', value: id }],
@@ -2658,8 +2787,245 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2658
2787
  });
2659
2788
  res.json({ success: true });
2660
2789
  }
2790
+ catch (error) {
2791
+ console.error('Error resending invitation:', error);
2792
+ const errorMessage = error instanceof Error ? error.message : 'Failed to resend invitation';
2793
+ res.status(500).json({
2794
+ error: 'Failed to resend invitation',
2795
+ details: isSelfHosted ? errorMessage : undefined,
2796
+ });
2797
+ }
2798
+ });
2799
+ router.get('/api/users/:userId/invitations', async (req, res) => {
2800
+ try {
2801
+ const { userId } = req.params;
2802
+ const adapter = await getAuthAdapterWithConfig();
2803
+ if (!adapter) {
2804
+ return res.status(500).json({ error: 'Auth adapter not available' });
2805
+ }
2806
+ let user;
2807
+ try {
2808
+ user = await adapter.findOne({
2809
+ model: 'user',
2810
+ where: [{ field: 'id', value: userId }],
2811
+ });
2812
+ }
2813
+ catch (error) {
2814
+ console.error('Error fetching user:', error);
2815
+ return res.status(500).json({
2816
+ error: 'Failed to fetch user',
2817
+ details: error?.message || String(error),
2818
+ });
2819
+ }
2820
+ if (!user || !user.email) {
2821
+ return res.json({ success: true, invitations: [] });
2822
+ }
2823
+ if (typeof adapter.findMany !== 'function') {
2824
+ return res.json({ success: true, invitations: [] });
2825
+ }
2826
+ let invitations;
2827
+ try {
2828
+ invitations = await adapter.findMany({
2829
+ model: 'invitation',
2830
+ where: [{ field: 'email', value: user.email }],
2831
+ });
2832
+ }
2833
+ catch (error) {
2834
+ console.error('Error fetching invitations:', error);
2835
+ return res.json({ success: true, invitations: [] });
2836
+ }
2837
+ if (!invitations || invitations.length === 0) {
2838
+ return res.json({ success: true, invitations: [] });
2839
+ }
2840
+ const transformedInvitations = await Promise.all(invitations.map(async (invitation) => {
2841
+ let organizationName = 'Unknown';
2842
+ let teamName;
2843
+ try {
2844
+ if (invitation.organizationId &&
2845
+ (typeof adapter.findOne === 'function' || typeof adapter.findUnique === 'function')) {
2846
+ try {
2847
+ const findMethod = adapter.findOne || adapter.findUnique;
2848
+ const org = await findMethod({
2849
+ model: 'organization',
2850
+ where: [{ field: 'id', value: invitation.organizationId }],
2851
+ });
2852
+ organizationName = org?.name || 'Unknown';
2853
+ }
2854
+ catch (_orgError) {
2855
+ // Ignore org fetch errors
2856
+ }
2857
+ }
2858
+ if (invitation.teamId &&
2859
+ (typeof adapter.findOne === 'function' || typeof adapter.findUnique === 'function')) {
2860
+ try {
2861
+ const findMethod = adapter.findOne || adapter.findUnique;
2862
+ const team = await findMethod({
2863
+ model: 'team',
2864
+ where: [{ field: 'id', value: invitation.teamId }],
2865
+ });
2866
+ teamName = team?.name;
2867
+ }
2868
+ catch (_teamError) { }
2869
+ }
2870
+ }
2871
+ catch (_error) { }
2872
+ return {
2873
+ id: invitation.id,
2874
+ email: invitation.email,
2875
+ role: invitation.role || 'member',
2876
+ status: invitation.status || 'pending',
2877
+ organizationId: invitation.organizationId,
2878
+ organizationName,
2879
+ teamId: invitation.teamId,
2880
+ teamName,
2881
+ inviterId: invitation.inviterId,
2882
+ expiresAt: invitation.expiresAt,
2883
+ createdAt: invitation.createdAt,
2884
+ };
2885
+ }));
2886
+ res.json({ success: true, invitations: transformedInvitations });
2887
+ }
2888
+ catch (error) {
2889
+ console.error('Error in /api/users/:userId/invitations:', error);
2890
+ res.status(500).json({
2891
+ error: 'Failed to fetch invitations',
2892
+ details: error?.message || String(error),
2893
+ });
2894
+ }
2895
+ });
2896
+ router.post('/api/invitations/:id/accept', async (req, res) => {
2897
+ try {
2898
+ const { id } = req.params;
2899
+ const { userId } = req.body;
2900
+ const adapter = await getAuthAdapterWithConfig();
2901
+ if (!adapter) {
2902
+ return res.status(500).json({ error: 'Auth adapter not available' });
2903
+ }
2904
+ if (!userId) {
2905
+ return res.status(400).json({ error: 'User ID is required' });
2906
+ }
2907
+ const invitation = await adapter.findOne({
2908
+ model: 'invitation',
2909
+ where: [{ field: 'id', value: id }],
2910
+ });
2911
+ if (!invitation) {
2912
+ return res.status(404).json({ error: 'Invitation not found' });
2913
+ }
2914
+ if (invitation.status !== 'pending') {
2915
+ return res.status(400).json({ error: 'Invitation is not pending' });
2916
+ }
2917
+ await adapter.update({
2918
+ model: 'invitation',
2919
+ where: [{ field: 'id', value: id }],
2920
+ update: {
2921
+ status: 'accepted',
2922
+ updatedAt: new Date().toISOString(),
2923
+ },
2924
+ });
2925
+ if (invitation.organizationId) {
2926
+ try {
2927
+ // Check if member already exists
2928
+ let existingMember = null;
2929
+ if (typeof adapter.findFirst === 'function') {
2930
+ existingMember = await adapter.findFirst({
2931
+ model: 'member',
2932
+ where: [
2933
+ { field: 'organizationId', value: invitation.organizationId },
2934
+ { field: 'userId', value: userId },
2935
+ ],
2936
+ });
2937
+ }
2938
+ else if (typeof adapter.findMany === 'function') {
2939
+ const members = await adapter.findMany({
2940
+ model: 'member',
2941
+ where: [
2942
+ { field: 'organizationId', value: invitation.organizationId },
2943
+ { field: 'userId', value: userId },
2944
+ ],
2945
+ });
2946
+ existingMember = members && members.length > 0 ? members[0] : null;
2947
+ }
2948
+ if (!existingMember) {
2949
+ await adapter.create({
2950
+ model: 'member',
2951
+ data: {
2952
+ organizationId: invitation.organizationId,
2953
+ userId: userId,
2954
+ role: invitation.role || 'member',
2955
+ createdAt: new Date().toISOString(),
2956
+ },
2957
+ });
2958
+ }
2959
+ }
2960
+ catch (error) {
2961
+ console.error('Error creating member:', error);
2962
+ // Ignore errors creating membership
2963
+ }
2964
+ }
2965
+ if (invitation.teamId) {
2966
+ try {
2967
+ let existingMember = null;
2968
+ if (typeof adapter.findFirst === 'function') {
2969
+ existingMember = await adapter.findFirst({
2970
+ model: 'teamMember',
2971
+ where: [
2972
+ { field: 'teamId', value: invitation.teamId },
2973
+ { field: 'userId', value: userId },
2974
+ ],
2975
+ });
2976
+ }
2977
+ else if (typeof adapter.findMany === 'function') {
2978
+ const members = await adapter.findMany({
2979
+ model: 'teamMember',
2980
+ where: [
2981
+ { field: 'teamId', value: invitation.teamId },
2982
+ { field: 'userId', value: userId },
2983
+ ],
2984
+ });
2985
+ existingMember = members && members.length > 0 ? members[0] : null;
2986
+ }
2987
+ if (!existingMember) {
2988
+ await adapter.create({
2989
+ model: 'teamMember',
2990
+ data: {
2991
+ teamId: invitation.teamId,
2992
+ userId: userId,
2993
+ createdAt: new Date().toISOString(),
2994
+ },
2995
+ });
2996
+ }
2997
+ }
2998
+ catch (error) {
2999
+ console.error('Error creating team member:', error);
3000
+ // Ignore errors creating team membership
3001
+ }
3002
+ }
3003
+ res.json({ success: true });
3004
+ }
3005
+ catch (error) {
3006
+ console.error('Failed to accept invitation:', error);
3007
+ res.status(500).json({ error: 'Failed to accept invitation' });
3008
+ }
3009
+ });
3010
+ router.post('/api/invitations/:id/reject', async (req, res) => {
3011
+ try {
3012
+ const { id } = req.params;
3013
+ const adapter = await getAuthAdapterWithConfig();
3014
+ if (!adapter) {
3015
+ return res.status(500).json({ error: 'Auth adapter not available' });
3016
+ }
3017
+ await adapter.update({
3018
+ model: 'invitation',
3019
+ where: [{ field: 'id', value: id }],
3020
+ update: {
3021
+ status: 'rejected',
3022
+ updatedAt: new Date().toISOString(),
3023
+ },
3024
+ });
3025
+ res.json({ success: true });
3026
+ }
2661
3027
  catch (_error) {
2662
- res.status(500).json({ error: 'Failed to resend invitation' });
3028
+ res.status(500).json({ error: 'Failed to reject invitation' });
2663
3029
  }
2664
3030
  });
2665
3031
  router.delete('/api/invitations/:id', async (req, res) => {
@@ -2689,7 +3055,13 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2689
3055
  router.post('/api/organizations/:orgId/invitations', async (req, res) => {
2690
3056
  try {
2691
3057
  const { orgId } = req.params;
2692
- const { email, role = 'member', inviterId } = req.body;
3058
+ const { email, role = 'member', inviterId, teamId } = req.body;
3059
+ if (!email || typeof email !== 'string') {
3060
+ return res.status(400).json({ error: 'Email is required' });
3061
+ }
3062
+ if (!orgId) {
3063
+ return res.status(400).json({ error: 'Organization ID is required' });
3064
+ }
2693
3065
  if (!inviterId) {
2694
3066
  return res.status(400).json({ error: 'Inviter ID is required' });
2695
3067
  }
@@ -2697,60 +3069,143 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2697
3069
  if (!adapter) {
2698
3070
  return res.status(500).json({ error: 'Auth adapter not available' });
2699
3071
  }
3072
+ if (!adapter.create) {
3073
+ return res.status(500).json({ error: 'Adapter create method not available' });
3074
+ }
3075
+ try {
3076
+ const organization = await adapter.findOne({
3077
+ model: 'organization',
3078
+ where: [{ field: 'id', value: orgId }],
3079
+ });
3080
+ if (!organization) {
3081
+ return res.status(404).json({ error: 'Organization not found' });
3082
+ }
3083
+ }
3084
+ catch (orgError) {
3085
+ try {
3086
+ if (typeof adapter.findMany === 'function') {
3087
+ const orgs = await adapter.findMany({
3088
+ model: 'organization',
3089
+ where: [{ field: 'id', value: orgId }],
3090
+ limit: 1,
3091
+ });
3092
+ if (!orgs || orgs.length === 0) {
3093
+ return res.status(404).json({ error: 'Organization not found' });
3094
+ }
3095
+ }
3096
+ }
3097
+ catch (_fallbackError) { }
3098
+ }
3099
+ try {
3100
+ let existingInvitation = null;
3101
+ if (typeof adapter.findFirst === 'function') {
3102
+ existingInvitation = await adapter.findFirst({
3103
+ model: 'invitation',
3104
+ where: [
3105
+ { field: 'email', value: email.toLowerCase() },
3106
+ { field: 'organizationId', value: orgId },
3107
+ { field: 'status', value: 'pending' },
3108
+ ],
3109
+ });
3110
+ }
3111
+ else if (typeof adapter.findMany === 'function') {
3112
+ const invitations = await adapter.findMany({
3113
+ model: 'invitation',
3114
+ where: [
3115
+ { field: 'email', value: email.toLowerCase() },
3116
+ { field: 'organizationId', value: orgId },
3117
+ { field: 'status', value: 'pending' },
3118
+ ],
3119
+ limit: 1,
3120
+ });
3121
+ existingInvitation = invitations && invitations.length > 0 ? invitations[0] : null;
3122
+ }
3123
+ if (existingInvitation) {
3124
+ return res
3125
+ .status(400)
3126
+ .json({ error: 'A pending invitation already exists for this email' });
3127
+ }
3128
+ }
3129
+ catch (_duplicateCheckError) { }
2700
3130
  const invitationData = {
2701
- email,
3131
+ email: email.toLowerCase(),
2702
3132
  role,
2703
3133
  organizationId: orgId,
2704
3134
  status: 'pending',
2705
3135
  expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
2706
- createdAt: new Date(),
2707
3136
  inviterId: inviterId,
2708
3137
  };
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' });
3138
+ if (teamId) {
3139
+ invitationData.teamId = teamId;
2715
3140
  }
2716
- await adapter.create({
3141
+ const createdInvitation = await adapter.create({
2717
3142
  model: 'invitation',
2718
3143
  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,
3144
+ ...invitationData,
2726
3145
  },
2727
3146
  });
2728
- res.json({ success: true, invitation });
3147
+ if (!createdInvitation) {
3148
+ return res.status(500).json({ error: 'Failed to create invitation' });
3149
+ }
3150
+ res.json({ success: true, invitation: createdInvitation });
2729
3151
  }
2730
- catch (_error) {
2731
- res.status(500).json({ error: 'Failed to create invitation' });
3152
+ catch (error) {
3153
+ console.error('Error creating invitation:', error);
3154
+ const errorMessage = error instanceof Error ? error.message : 'Failed to create invitation';
3155
+ res.status(500).json({
3156
+ error: 'Failed to create invitation',
3157
+ details: isSelfHosted ? errorMessage : undefined,
3158
+ });
2732
3159
  }
2733
3160
  });
2734
3161
  router.get('/api/organizations/:orgId/teams', async (req, res) => {
2735
3162
  try {
2736
3163
  const { orgId } = req.params;
2737
3164
  const adapter = await getAuthAdapterWithConfig();
2738
- if (adapter && typeof adapter.findMany === 'function') {
3165
+ if (!adapter) {
3166
+ return res.status(500).json({
3167
+ success: false,
3168
+ error: 'Auth adapter not available',
3169
+ teams: [],
3170
+ });
3171
+ }
3172
+ if (typeof adapter.findMany !== 'function') {
3173
+ return res.status(500).json({
3174
+ success: false,
3175
+ error: 'Adapter findMany method not available',
3176
+ teams: [],
3177
+ });
3178
+ }
3179
+ try {
3180
+ let teams = [];
2739
3181
  try {
2740
- const teams = await adapter.findMany({
3182
+ teams = await adapter.findMany({
2741
3183
  model: 'team',
2742
3184
  where: [{ field: 'organizationId', value: orgId }],
2743
3185
  limit: 10000,
2744
3186
  });
2745
- const transformedTeams = await Promise.all((teams || []).map(async (team) => {
2746
- if (!adapter.findMany) {
2747
- return null;
3187
+ }
3188
+ catch (whereError) {
3189
+ const allTeams = await adapter.findMany({
3190
+ model: 'team',
3191
+ limit: 10000,
3192
+ });
3193
+ teams = (allTeams || []).filter((team) => team.organizationId === orgId);
3194
+ }
3195
+ if (!teams || teams.length === 0) {
3196
+ return res.json({ success: true, teams: [] });
3197
+ }
3198
+ const transformedTeams = await Promise.all((teams || []).map(async (team) => {
3199
+ try {
3200
+ let memberCount = 0;
3201
+ if (adapter.findMany) {
3202
+ const teamMembers = await adapter.findMany({
3203
+ model: 'teamMember',
3204
+ where: [{ field: 'teamId', value: team.id }],
3205
+ limit: 10000,
3206
+ });
3207
+ memberCount = teamMembers ? teamMembers.length : 0;
2748
3208
  }
2749
- const teamMembers = await adapter.findMany({
2750
- model: 'teamMember',
2751
- where: [{ field: 'teamId', value: team.id }],
2752
- limit: 10000,
2753
- });
2754
3209
  return {
2755
3210
  id: team.id,
2756
3211
  name: team.name,
@@ -2758,18 +3213,38 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2758
3213
  metadata: team.metadata,
2759
3214
  createdAt: team.createdAt,
2760
3215
  updatedAt: team.updatedAt,
2761
- memberCount: teamMembers ? teamMembers.length : 0,
3216
+ memberCount: memberCount,
2762
3217
  };
2763
- }));
2764
- res.json({ success: true, teams: transformedTeams });
2765
- return;
2766
- }
2767
- catch (_error) { }
3218
+ }
3219
+ catch (_error) {
3220
+ return {
3221
+ id: team.id,
3222
+ name: team.name,
3223
+ organizationId: team.organizationId,
3224
+ metadata: team.metadata,
3225
+ createdAt: team.createdAt,
3226
+ updatedAt: team.updatedAt,
3227
+ memberCount: 0,
3228
+ };
3229
+ }
3230
+ }));
3231
+ const validTeams = transformedTeams.filter((team) => team !== null);
3232
+ return res.json({ success: true, teams: validTeams });
3233
+ }
3234
+ catch (error) {
3235
+ return res.json({
3236
+ success: true,
3237
+ teams: [],
3238
+ error: error?.message || 'Failed to fetch teams',
3239
+ });
2768
3240
  }
2769
- res.json({ success: true, teams: [] });
2770
3241
  }
2771
- catch (_error) {
2772
- res.status(500).json({ error: 'Failed to fetch teams' });
3242
+ catch (error) {
3243
+ res.status(500).json({
3244
+ success: false,
3245
+ error: 'Failed to fetch teams',
3246
+ message: error?.message || 'Unknown error',
3247
+ });
2773
3248
  }
2774
3249
  });
2775
3250
  router.post('/api/organizations/:orgId/teams', async (req, res) => {
@@ -2794,7 +3269,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2794
3269
  if (!adapter.create) {
2795
3270
  return res.status(500).json({ error: 'Adapter create method not available' });
2796
3271
  }
2797
- await adapter.create({
3272
+ const teamResult = await adapter.create({
2798
3273
  model: 'team',
2799
3274
  data: {
2800
3275
  name: teamData.name,
@@ -2803,6 +3278,9 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2803
3278
  updatedAt: teamData.updatedAt,
2804
3279
  },
2805
3280
  });
3281
+ if (!teamResult) {
3282
+ return res.status(500).json({ error: 'Failed to create team' });
3283
+ }
2806
3284
  res.json({ success: true, team });
2807
3285
  }
2808
3286
  catch (_error) {
@@ -2872,39 +3350,83 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2872
3350
  return res.status(400).json({ error: 'userIds array is required' });
2873
3351
  }
2874
3352
  const adapter = await getAuthAdapterWithConfig();
2875
- if (!adapter || !adapter.create) {
3353
+ if (!adapter) {
2876
3354
  return res.status(500).json({ error: 'Adapter not available' });
2877
3355
  }
3356
+ if (!adapter.create) {
3357
+ return res.status(500).json({ error: 'Adapter create method not available' });
3358
+ }
2878
3359
  const results = [];
2879
3360
  for (const userId of userIds) {
2880
3361
  try {
3362
+ let existingMember = null;
3363
+ if (adapter.findMany) {
3364
+ try {
3365
+ const existing = await adapter.findMany({
3366
+ model: 'teamMember',
3367
+ where: [
3368
+ { field: 'teamId', value: teamId },
3369
+ { field: 'userId', value: userId },
3370
+ ],
3371
+ limit: 1,
3372
+ });
3373
+ existingMember = existing && existing.length > 0 ? existing[0] : null;
3374
+ }
3375
+ catch (_findError) {
3376
+ // if where clause isn't working.
3377
+ try {
3378
+ const allMembers = await adapter.findMany({
3379
+ model: 'teamMember',
3380
+ limit: 10000,
3381
+ });
3382
+ existingMember = (allMembers || []).find((m) => m.teamId === teamId && m.userId === userId);
3383
+ }
3384
+ catch (_fallbackError) { }
3385
+ }
3386
+ }
3387
+ if (existingMember) {
3388
+ results.push({
3389
+ success: false,
3390
+ userId,
3391
+ error: 'User is already a member of this team',
3392
+ });
3393
+ continue;
3394
+ }
3395
+ const now = new Date();
2881
3396
  await adapter.create({
2882
3397
  model: 'teamMember',
2883
3398
  data: {
2884
3399
  teamId,
2885
3400
  userId,
2886
3401
  role: 'member',
2887
- createdAt: new Date(),
3402
+ createdAt: now,
3403
+ updatedAt: now,
2888
3404
  },
2889
3405
  });
2890
3406
  results.push({ success: true, userId });
2891
3407
  }
2892
3408
  catch (error) {
3409
+ const errorMessage = error?.message || error?.toString() || 'Unknown error';
2893
3410
  results.push({
2894
3411
  success: false,
2895
3412
  userId,
2896
- error: error instanceof Error ? error.message : 'Unknown error',
3413
+ error: errorMessage,
2897
3414
  });
2898
3415
  }
2899
3416
  }
3417
+ const successCount = results.filter((r) => r.success).length;
2900
3418
  res.json({
2901
- success: true,
2902
- message: `Added ${results.filter((r) => r.success).length} members`,
3419
+ success: results.some((r) => r.success),
3420
+ message: `Added ${successCount} member${successCount !== 1 ? 's' : ''}`,
2903
3421
  results,
2904
3422
  });
2905
3423
  }
2906
- catch (_error) {
2907
- res.status(500).json({ error: 'Failed to add team members' });
3424
+ catch (error) {
3425
+ res.status(500).json({
3426
+ success: false,
3427
+ error: 'Failed to add team members',
3428
+ message: error?.message || 'Unknown error',
3429
+ });
2908
3430
  }
2909
3431
  });
2910
3432
  router.delete('/api/team-members/:id', async (req, res) => {
@@ -2974,7 +3496,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2974
3496
  });
2975
3497
  router.get('/api/plugins/organization/status', async (_req, res) => {
2976
3498
  try {
2977
- const betterAuthConfig = await getAuthConfigSafe();
3499
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
2978
3500
  if (!betterAuthConfig) {
2979
3501
  return res.json({
2980
3502
  enabled: false,
@@ -3106,7 +3628,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
3106
3628
  try {
3107
3629
  const { id } = req.params;
3108
3630
  const userData = req.body;
3109
- const updatedUser = await getAuthData(authConfig, 'updateUser', { id, userData }, configPath);
3631
+ const updatedUser = await getAuthData(authConfig, 'updateUser', { id, userData }, configPath, preloadedAdapter);
3110
3632
  res.json({ success: true, user: updatedUser });
3111
3633
  }
3112
3634
  catch (_error) {
@@ -3497,6 +4019,92 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
3497
4019
  res.status(500).json({ success: false, error: 'Failed to fetch OAuth providers' });
3498
4020
  }
3499
4021
  });
4022
+ router.get('/api/tools/oauth/credentials', async (req, res) => {
4023
+ try {
4024
+ const { provider, origin } = req.query;
4025
+ if (!provider || typeof provider !== 'string') {
4026
+ return res.status(400).json({
4027
+ success: false,
4028
+ error: 'Provider is required',
4029
+ });
4030
+ }
4031
+ if (!origin || typeof origin !== 'string') {
4032
+ return res.status(400).json({
4033
+ success: false,
4034
+ error: 'Origin is required',
4035
+ });
4036
+ }
4037
+ // TODO: Import getOAuthCredentials at the top of this file:
4038
+ // import { getOAuthCredentials } from './path/to/your/oauth-config';
4039
+ // For now, we'll access it from a function that should be provided
4040
+ // This assumes getOAuthCredentials is available in the scope
4041
+ // You need to import it: import { getOAuthCredentials } from './your-oauth-config-file';
4042
+ // Placeholder - replace this with actual import at top of file
4043
+ const getOAuthCredentials = global.getOAuthCredentials;
4044
+ if (typeof getOAuthCredentials !== 'function') {
4045
+ return res.status(500).json({
4046
+ success: false,
4047
+ error: 'OAuth credentials function not configured. Please import getOAuthCredentials function.',
4048
+ });
4049
+ }
4050
+ const credentialsResult = getOAuthCredentials(provider, origin);
4051
+ // Handle null return (provider not found)
4052
+ if (credentialsResult === null) {
4053
+ return res.status(404).json({
4054
+ success: false,
4055
+ error: 'No credential found',
4056
+ });
4057
+ }
4058
+ // Handle error cases with proper messages as requested
4059
+ if (credentialsResult.error) {
4060
+ if (credentialsResult.error === 'NO_CREDENTIALS_FOUND') {
4061
+ return res.status(404).json({
4062
+ success: false,
4063
+ error: 'No credential found',
4064
+ });
4065
+ }
4066
+ else if (credentialsResult.error === 'INVALID_ORIGIN') {
4067
+ return res.status(400).json({
4068
+ success: false,
4069
+ error: 'Invalid origin. OAuth credentials are only available for localhost origins.',
4070
+ });
4071
+ }
4072
+ else {
4073
+ return res.status(400).json({
4074
+ success: false,
4075
+ error: credentialsResult.error || 'Failed to get OAuth credentials',
4076
+ });
4077
+ }
4078
+ }
4079
+ // Check if result exists and has required fields
4080
+ if (!credentialsResult.result) {
4081
+ return res.status(404).json({
4082
+ success: false,
4083
+ error: 'No credential found',
4084
+ });
4085
+ }
4086
+ const { clientId, clientSecret } = credentialsResult.result;
4087
+ if (!clientId || !clientSecret) {
4088
+ return res.status(404).json({
4089
+ success: false,
4090
+ error: 'No credential found',
4091
+ });
4092
+ }
4093
+ res.json({
4094
+ success: true,
4095
+ clientId,
4096
+ clientSecret,
4097
+ });
4098
+ }
4099
+ catch (error) {
4100
+ console.error('Failed to fetch OAuth credentials:', error);
4101
+ res.status(500).json({
4102
+ success: false,
4103
+ error: 'Failed to fetch OAuth credentials',
4104
+ details: error instanceof Error ? error.message : String(error),
4105
+ });
4106
+ }
4107
+ });
3500
4108
  router.post('/api/tools/oauth/test', async (req, res) => {
3501
4109
  try {
3502
4110
  const { provider } = req.body;