better-auth-studio 1.0.79-beta.5 → 1.0.79-beta.50

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 (45) hide show
  1. package/README.md +10 -0
  2. package/dist/adapters/hono.d.ts +7 -0
  3. package/dist/adapters/hono.d.ts.map +1 -0
  4. package/dist/adapters/hono.js +78 -0
  5. package/dist/adapters/hono.js.map +1 -0
  6. package/dist/cli/commands/init.d.ts +3 -1
  7. package/dist/cli/commands/init.d.ts.map +1 -1
  8. package/dist/cli/commands/init.js +28 -6
  9. package/dist/cli/commands/init.js.map +1 -1
  10. package/dist/cli.js +3 -2
  11. package/dist/cli.js.map +1 -1
  12. package/dist/core/handler.d.ts.map +1 -1
  13. package/dist/core/handler.js +167 -52
  14. package/dist/core/handler.js.map +1 -1
  15. package/dist/data.d.ts +1 -1
  16. package/dist/data.d.ts.map +1 -1
  17. package/dist/data.js +3 -7
  18. package/dist/data.js.map +1 -1
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/public/assets/main-BjWPiv1k.css +1 -0
  24. package/dist/public/assets/main-D8LhxAuO.js +1155 -0
  25. package/dist/public/index.html +2 -2
  26. package/dist/routes.d.ts.map +1 -1
  27. package/dist/routes.js +750 -147
  28. package/dist/routes.js.map +1 -1
  29. package/dist/studio.d.ts.map +1 -1
  30. package/dist/studio.js +15 -2
  31. package/dist/studio.js.map +1 -1
  32. package/package.json +8 -7
  33. package/public/assets/main-BjWPiv1k.css +1 -0
  34. package/public/assets/main-D8LhxAuO.js +1155 -0
  35. package/public/favicon.svg +6 -0
  36. package/public/index.html +14 -0
  37. package/public/logo.png +0 -0
  38. package/public/vite.svg +5 -0
  39. package/scripts/download-geolite2.js +35 -0
  40. package/scripts/generate-default-db.js +462 -0
  41. package/scripts/postinstall.js +96 -0
  42. package/data/GeoLite2-City.mmdb +0 -0
  43. package/data/GeoLite2-City.tar.gz +0 -1
  44. package/dist/public/assets/main-3NIBCudD.js +0 -1155
  45. package/dist/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),
@@ -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
  }
@@ -1045,7 +1070,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1045
1070
  router.put('/api/users/:userId', async (req, res) => {
1046
1071
  try {
1047
1072
  const { userId } = req.params;
1048
- const { name, email, role } = req.body;
1073
+ const { name, email, role, image } = req.body;
1049
1074
  const adapter = await getAuthAdapterWithConfig();
1050
1075
  if (!adapter || !adapter.update) {
1051
1076
  return res.status(500).json({ error: 'Auth adapter not available' });
@@ -1054,6 +1079,9 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1054
1079
  if (role !== undefined) {
1055
1080
  updateData.role = role;
1056
1081
  }
1082
+ if (image !== undefined) {
1083
+ updateData.image = image;
1084
+ }
1057
1085
  const user = await adapter.update({
1058
1086
  model: 'user',
1059
1087
  where: [{ field: 'id', value: userId }],
@@ -1073,9 +1101,36 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1073
1101
  return res.status(400).json({ error: 'Password is required' });
1074
1102
  }
1075
1103
  const adapter = await getAuthAdapterWithConfig();
1076
- if (!adapter || !adapter.update) {
1104
+ if (!adapter) {
1077
1105
  return res.status(500).json({ error: 'Auth adapter not available' });
1078
1106
  }
1107
+ if (!adapter.update) {
1108
+ return res.status(500).json({ error: 'Auth adapter update method not available' });
1109
+ }
1110
+ // Find the credential account first to get its unique id (fixes Prisma error)
1111
+ let credentialAccount = null;
1112
+ if (adapter.findFirst) {
1113
+ credentialAccount = await adapter.findFirst({
1114
+ model: 'account',
1115
+ where: [
1116
+ { field: 'userId', value: userId },
1117
+ { field: 'providerId', value: 'credential' },
1118
+ ],
1119
+ });
1120
+ }
1121
+ else if (adapter.findMany) {
1122
+ const accounts = await adapter.findMany({
1123
+ model: 'account',
1124
+ where: [
1125
+ { field: 'userId', value: userId },
1126
+ { field: 'providerId', value: 'credential' },
1127
+ ],
1128
+ });
1129
+ credentialAccount = accounts && accounts.length > 0 ? accounts[0] : null;
1130
+ }
1131
+ if (!credentialAccount) {
1132
+ return res.status(404).json({ error: 'Credential account not found' });
1133
+ }
1079
1134
  let hashedPassword = password;
1080
1135
  try {
1081
1136
  const salt = hex.encode(crypto.getRandomValues(new Uint8Array(16)));
@@ -1083,17 +1138,18 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1083
1138
  hashedPassword = `${salt}:${hex.encode(key)}`;
1084
1139
  }
1085
1140
  catch {
1086
- res.status(500).json({ error: 'Failed to hash password' });
1141
+ return res.status(500).json({ error: 'Failed to hash password' });
1087
1142
  }
1088
- const account = await adapter.update({
1143
+ // Update using the account's unique id to fix Prisma error
1144
+ const updatedAccount = await adapter.update({
1089
1145
  model: 'account',
1090
- where: [
1091
- { field: 'userId', value: userId },
1092
- { field: 'providerId', value: 'credential' },
1093
- ],
1094
- update: { password: hashedPassword },
1146
+ where: [{ field: 'id', value: credentialAccount.id }],
1147
+ update: {
1148
+ password: hashedPassword,
1149
+ updatedAt: new Date().toISOString(),
1150
+ },
1095
1151
  });
1096
- res.json({ success: true, account });
1152
+ res.json({ success: true, account: updatedAccount });
1097
1153
  }
1098
1154
  catch (error) {
1099
1155
  res.status(500).json({ error: 'Failed to update password', message: error?.message });
@@ -1471,7 +1527,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1471
1527
  }
1472
1528
  }
1473
1529
  catch (_adapterError) { }
1474
- const result = await getAuthData(authConfig, 'users', { page, limit, search }, configPath);
1530
+ const result = await getAuthData(authConfig, 'users', { page, limit, search }, configPath, preloadedAdapter);
1475
1531
  const transformedUsers = (result.data || []).map((user) => ({
1476
1532
  id: user.id,
1477
1533
  email: user.email,
@@ -1496,7 +1552,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1496
1552
  try {
1497
1553
  const page = parseInt(req.query.page, 10) || 1;
1498
1554
  const limit = parseInt(req.query.limit, 10) || 20;
1499
- const sessions = await getAuthData(authConfig, 'sessions', { page, limit }, configPath);
1555
+ const sessions = await getAuthData(authConfig, 'sessions', { page, limit }, configPath, preloadedAdapter);
1500
1556
  res.json(sessions);
1501
1557
  }
1502
1558
  catch (_error) {
@@ -1505,7 +1561,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1505
1561
  });
1506
1562
  router.get('/api/providers', async (_req, res) => {
1507
1563
  try {
1508
- const providers = await getAuthData(authConfig, 'providers', undefined, configPath);
1564
+ const providers = await getAuthData(authConfig, 'providers', undefined, configPath, preloadedAdapter);
1509
1565
  res.json(providers);
1510
1566
  }
1511
1567
  catch (_error) {
@@ -1515,7 +1571,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1515
1571
  router.delete('/api/users/:id', async (req, res) => {
1516
1572
  try {
1517
1573
  const { id } = req.params;
1518
- await getAuthData(authConfig, 'deleteUser', { id }, configPath);
1574
+ await getAuthData(authConfig, 'deleteUser', { id }, configPath, preloadedAdapter);
1519
1575
  res.json({ success: true });
1520
1576
  }
1521
1577
  catch (_error) {
@@ -1524,60 +1580,82 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1524
1580
  });
1525
1581
  router.get('/api/plugins', async (_req, res) => {
1526
1582
  try {
1527
- const authConfigPath = configPath
1528
- ? join(process.cwd(), configPath)
1529
- : await findAuthConfigPath();
1530
- if (!authConfigPath) {
1583
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
1584
+ if (betterAuthConfig) {
1585
+ const plugins = betterAuthConfig.plugins || [];
1586
+ const pluginInfo = plugins.map((plugin) => ({
1587
+ id: plugin.id,
1588
+ name: plugin.name || plugin.id,
1589
+ description: plugin.description || `${plugin.id} plugin for Better Auth`,
1590
+ enabled: true,
1591
+ }));
1531
1592
  return res.json({
1532
- plugins: [],
1533
- error: 'No auth config found',
1534
- configPath: null,
1593
+ plugins: pluginInfo,
1594
+ configPath: isSelfHosted ? null : configPath || null,
1595
+ totalPlugins: pluginInfo.length,
1535
1596
  });
1536
1597
  }
1537
- try {
1538
- let authModule;
1539
- try {
1540
- authModule = await safeImportAuthConfig(authConfigPath, true); // Disable cache for real-time plugin checks
1598
+ if (!isSelfHosted) {
1599
+ const authConfigPath = configPath
1600
+ ? join(process.cwd(), configPath)
1601
+ : await findAuthConfigPath();
1602
+ if (!authConfigPath) {
1603
+ return res.json({
1604
+ plugins: [],
1605
+ error: 'No auth config found',
1606
+ configPath: null,
1607
+ });
1541
1608
  }
1542
- catch (_importError) {
1543
- const content = readFileSync(authConfigPath, 'utf-8');
1544
- authModule = {
1545
- auth: {
1546
- options: {
1547
- _content: content,
1548
- plugins: [],
1609
+ try {
1610
+ let authModule;
1611
+ try {
1612
+ authModule = await safeImportAuthConfig(authConfigPath, true); // Disable cache for real-time plugin checks
1613
+ }
1614
+ catch (_importError) {
1615
+ const content = readFileSync(authConfigPath, 'utf-8');
1616
+ authModule = {
1617
+ auth: {
1618
+ options: {
1619
+ _content: content,
1620
+ plugins: [],
1621
+ },
1549
1622
  },
1550
- },
1551
- };
1623
+ };
1624
+ }
1625
+ const auth = authModule.auth || authModule.default;
1626
+ if (!auth) {
1627
+ return res.json({
1628
+ plugins: [],
1629
+ error: 'No auth export found',
1630
+ configPath: authConfigPath,
1631
+ });
1632
+ }
1633
+ const plugins = auth.options?.plugins || [];
1634
+ const pluginInfo = plugins.map((plugin) => ({
1635
+ id: plugin.id,
1636
+ name: plugin.name || plugin.id,
1637
+ description: plugin.description || `${plugin.id} plugin for Better Auth`,
1638
+ enabled: true,
1639
+ }));
1640
+ return res.json({
1641
+ plugins: pluginInfo,
1642
+ configPath: authConfigPath,
1643
+ totalPlugins: pluginInfo.length,
1644
+ });
1552
1645
  }
1553
- const auth = authModule.auth || authModule.default;
1554
- if (!auth) {
1646
+ catch (_error) {
1555
1647
  return res.json({
1556
1648
  plugins: [],
1557
- error: 'No auth export found',
1649
+ error: 'Failed to load auth config - import failed and regex extraction unavailable',
1558
1650
  configPath: authConfigPath,
1559
1651
  });
1560
1652
  }
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
1653
  }
1654
+ return res.json({
1655
+ plugins: [],
1656
+ error: 'No auth config found',
1657
+ configPath: null,
1658
+ });
1581
1659
  }
1582
1660
  catch (_error) {
1583
1661
  res.status(500).json({ error: 'Failed to fetch plugins' });
@@ -1585,6 +1663,13 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1585
1663
  });
1586
1664
  router.get('/api/database/info', async (_req, res) => {
1587
1665
  try {
1666
+ if (isSelfHosted && preloadedAuthOptions) {
1667
+ const database = preloadedAuthOptions.database;
1668
+ return res.json({
1669
+ database: database,
1670
+ configPath: null,
1671
+ });
1672
+ }
1588
1673
  const authConfigPath = configPath || (await findAuthConfigPath());
1589
1674
  if (!authConfigPath) {
1590
1675
  return res.json({
@@ -1978,7 +2063,6 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
1978
2063
  });
1979
2064
  }
1980
2065
  });
1981
- // Database Detection endpoint - Auto-detect database from installed packages
1982
2066
  router.get('/api/database/detect', async (_req, res) => {
1983
2067
  try {
1984
2068
  const detectedDb = await detectDatabaseWithDialect();
@@ -2047,7 +2131,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2047
2131
  });
2048
2132
  router.post('/api/admin/ban-user', async (req, res) => {
2049
2133
  try {
2050
- const auth = await getAuthConfigSafe();
2134
+ const auth = preloadedAuthOptions || (await getAuthConfigSafe());
2051
2135
  if (!auth) {
2052
2136
  return res.status(400).json({
2053
2137
  success: false,
@@ -2086,7 +2170,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2086
2170
  });
2087
2171
  router.post('/api/admin/unban-user', async (req, res) => {
2088
2172
  try {
2089
- const auth = await getAuthConfigSafe();
2173
+ const auth = preloadedAuthOptions || (await getAuthConfigSafe());
2090
2174
  if (!auth) {
2091
2175
  return res.status(400).json({
2092
2176
  success: false,
@@ -2125,7 +2209,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2125
2209
  });
2126
2210
  router.get('/api/admin/status', async (_req, res) => {
2127
2211
  try {
2128
- const betterAuthConfig = await getAuthConfigSafe();
2212
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
2129
2213
  if (!betterAuthConfig) {
2130
2214
  return res.json({
2131
2215
  enabled: false,
@@ -2149,8 +2233,6 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2149
2233
  });
2150
2234
  }
2151
2235
  });
2152
- // Database Schema Visualization endpoint
2153
- // Now uses loadContextTables to dynamically load schema from Better Auth context
2154
2236
  const CONTEXT_CORE_TABLES = new Set(['user', 'session', 'account', 'verification']);
2155
2237
  async function resolveSchemaConfigPath() {
2156
2238
  if (configPath) {
@@ -2160,16 +2242,23 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2160
2242
  }
2161
2243
  async function loadContextTables() {
2162
2244
  try {
2245
+ if (isSelfHosted && authInstance?.$context) {
2246
+ try {
2247
+ const context = await authInstance.$context;
2248
+ return context?.tables || null;
2249
+ }
2250
+ catch (_error) { }
2251
+ }
2163
2252
  const authConfigPath = await resolveSchemaConfigPath();
2164
2253
  if (!authConfigPath) {
2165
2254
  return null;
2166
2255
  }
2167
2256
  const authModule = await safeImportAuthConfig(authConfigPath);
2168
- const authInstance = authModule?.auth || authModule?.default;
2169
- if (!authInstance?.$context) {
2257
+ const fileAuthInstance = authModule?.auth || authModule?.default;
2258
+ if (!fileAuthInstance?.$context) {
2170
2259
  return null;
2171
2260
  }
2172
- const context = await authInstance.$context;
2261
+ const context = await fileAuthInstance.$context;
2173
2262
  return context?.tables || null;
2174
2263
  }
2175
2264
  catch (_error) {
@@ -2293,14 +2382,11 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2293
2382
  if (!schema) {
2294
2383
  return null;
2295
2384
  }
2296
- // Filter by plugin origin if plugins are specified
2297
2385
  if (selectedPlugins && selectedPlugins.length > 0) {
2298
2386
  schema.tables = schema.tables.filter((table) => {
2299
- // Include core tables
2300
2387
  if (CONTEXT_CORE_TABLES.has(table.name)) {
2301
2388
  return true;
2302
2389
  }
2303
- // Include tables from selected plugins
2304
2390
  return selectedPlugins.includes(table.origin);
2305
2391
  });
2306
2392
  }
@@ -2352,28 +2438,46 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2352
2438
  });
2353
2439
  router.get('/api/plugins/teams/status', async (_req, res) => {
2354
2440
  try {
2355
- const betterAuthConfig = await getAuthConfigSafe();
2441
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
2356
2442
  if (!betterAuthConfig) {
2357
2443
  return res.json({
2358
2444
  enabled: false,
2359
2445
  error: 'No auth config found',
2360
- configPath: null,
2446
+ configPath: isSelfHosted ? null : configPath || null,
2361
2447
  });
2362
2448
  }
2363
2449
  const plugins = betterAuthConfig.plugins || [];
2364
2450
  const organizationPlugin = plugins.find((plugin) => plugin.id === 'organization');
2365
2451
  if (organizationPlugin) {
2366
- const teamsEnabled = organizationPlugin.options?.teams?.enabled === true;
2452
+ let teamsEnabled = false;
2453
+ if (organizationPlugin.options?.teams?.enabled === true) {
2454
+ teamsEnabled = true;
2455
+ }
2456
+ else if (organizationPlugin.teams?.enabled === true) {
2457
+ teamsEnabled = true;
2458
+ }
2459
+ else if (organizationPlugin.config?.teams?.enabled === true) {
2460
+ teamsEnabled = true;
2461
+ }
2462
+ else if (organizationPlugin.options?.teams &&
2463
+ typeof organizationPlugin.options.teams === 'object') {
2464
+ teamsEnabled = organizationPlugin.options.teams.enabled === true;
2465
+ }
2466
+ else if (organizationPlugin.teams && typeof organizationPlugin.teams === 'object') {
2467
+ teamsEnabled = organizationPlugin.teams.enabled === true;
2468
+ }
2469
+ const teamSchema = organizationPlugin.schema;
2470
+ teamsEnabled = 'team' in teamSchema;
2367
2471
  return res.json({
2368
2472
  enabled: teamsEnabled,
2369
- configPath: configPath || null,
2473
+ configPath: isSelfHosted ? null : configPath || null,
2370
2474
  organizationPlugin: organizationPlugin || null,
2371
2475
  });
2372
2476
  }
2373
2477
  else {
2374
2478
  return res.json({
2375
2479
  enabled: false,
2376
- configPath: configPath || null,
2480
+ configPath: isSelfHosted ? null : configPath || null,
2377
2481
  organizationPlugin: null,
2378
2482
  error: 'Organization plugin not found',
2379
2483
  });
@@ -2648,6 +2752,26 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2648
2752
  if (!adapter.update) {
2649
2753
  return res.status(500).json({ error: 'Adapter update method not available' });
2650
2754
  }
2755
+ let invitation = null;
2756
+ try {
2757
+ invitation = await adapter.findOne({
2758
+ model: 'invitation',
2759
+ where: [{ field: 'id', value: id }],
2760
+ });
2761
+ }
2762
+ catch (_findError) {
2763
+ if (typeof adapter.findMany === 'function') {
2764
+ const invitations = await adapter.findMany({
2765
+ model: 'invitation',
2766
+ where: [{ field: 'id', value: id }],
2767
+ limit: 1,
2768
+ });
2769
+ invitation = invitations && invitations.length > 0 ? invitations[0] : null;
2770
+ }
2771
+ }
2772
+ if (!invitation) {
2773
+ return res.status(404).json({ error: 'Invitation not found' });
2774
+ }
2651
2775
  await adapter.update({
2652
2776
  model: 'invitation',
2653
2777
  where: [{ field: 'id', value: id }],
@@ -2658,8 +2782,245 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2658
2782
  });
2659
2783
  res.json({ success: true });
2660
2784
  }
2785
+ catch (error) {
2786
+ console.error('Error resending invitation:', error);
2787
+ const errorMessage = error instanceof Error ? error.message : 'Failed to resend invitation';
2788
+ res.status(500).json({
2789
+ error: 'Failed to resend invitation',
2790
+ details: isSelfHosted ? errorMessage : undefined,
2791
+ });
2792
+ }
2793
+ });
2794
+ router.get('/api/users/:userId/invitations', async (req, res) => {
2795
+ try {
2796
+ const { userId } = req.params;
2797
+ const adapter = await getAuthAdapterWithConfig();
2798
+ if (!adapter) {
2799
+ return res.status(500).json({ error: 'Auth adapter not available' });
2800
+ }
2801
+ let user;
2802
+ try {
2803
+ user = await adapter.findOne({
2804
+ model: 'user',
2805
+ where: [{ field: 'id', value: userId }],
2806
+ });
2807
+ }
2808
+ catch (error) {
2809
+ console.error('Error fetching user:', error);
2810
+ return res.status(500).json({
2811
+ error: 'Failed to fetch user',
2812
+ details: error?.message || String(error),
2813
+ });
2814
+ }
2815
+ if (!user || !user.email) {
2816
+ return res.json({ success: true, invitations: [] });
2817
+ }
2818
+ if (typeof adapter.findMany !== 'function') {
2819
+ return res.json({ success: true, invitations: [] });
2820
+ }
2821
+ let invitations;
2822
+ try {
2823
+ invitations = await adapter.findMany({
2824
+ model: 'invitation',
2825
+ where: [{ field: 'email', value: user.email }],
2826
+ });
2827
+ }
2828
+ catch (error) {
2829
+ console.error('Error fetching invitations:', error);
2830
+ return res.json({ success: true, invitations: [] });
2831
+ }
2832
+ if (!invitations || invitations.length === 0) {
2833
+ return res.json({ success: true, invitations: [] });
2834
+ }
2835
+ const transformedInvitations = await Promise.all(invitations.map(async (invitation) => {
2836
+ let organizationName = 'Unknown';
2837
+ let teamName;
2838
+ try {
2839
+ if (invitation.organizationId &&
2840
+ (typeof adapter.findOne === 'function' || typeof adapter.findUnique === 'function')) {
2841
+ try {
2842
+ const findMethod = adapter.findOne || adapter.findUnique;
2843
+ const org = await findMethod({
2844
+ model: 'organization',
2845
+ where: [{ field: 'id', value: invitation.organizationId }],
2846
+ });
2847
+ organizationName = org?.name || 'Unknown';
2848
+ }
2849
+ catch (_orgError) {
2850
+ // Ignore org fetch errors
2851
+ }
2852
+ }
2853
+ if (invitation.teamId &&
2854
+ (typeof adapter.findOne === 'function' || typeof adapter.findUnique === 'function')) {
2855
+ try {
2856
+ const findMethod = adapter.findOne || adapter.findUnique;
2857
+ const team = await findMethod({
2858
+ model: 'team',
2859
+ where: [{ field: 'id', value: invitation.teamId }],
2860
+ });
2861
+ teamName = team?.name;
2862
+ }
2863
+ catch (_teamError) { }
2864
+ }
2865
+ }
2866
+ catch (_error) { }
2867
+ return {
2868
+ id: invitation.id,
2869
+ email: invitation.email,
2870
+ role: invitation.role || 'member',
2871
+ status: invitation.status || 'pending',
2872
+ organizationId: invitation.organizationId,
2873
+ organizationName,
2874
+ teamId: invitation.teamId,
2875
+ teamName,
2876
+ inviterId: invitation.inviterId,
2877
+ expiresAt: invitation.expiresAt,
2878
+ createdAt: invitation.createdAt,
2879
+ };
2880
+ }));
2881
+ res.json({ success: true, invitations: transformedInvitations });
2882
+ }
2883
+ catch (error) {
2884
+ console.error('Error in /api/users/:userId/invitations:', error);
2885
+ res.status(500).json({
2886
+ error: 'Failed to fetch invitations',
2887
+ details: error?.message || String(error),
2888
+ });
2889
+ }
2890
+ });
2891
+ router.post('/api/invitations/:id/accept', async (req, res) => {
2892
+ try {
2893
+ const { id } = req.params;
2894
+ const { userId } = req.body;
2895
+ const adapter = await getAuthAdapterWithConfig();
2896
+ if (!adapter) {
2897
+ return res.status(500).json({ error: 'Auth adapter not available' });
2898
+ }
2899
+ if (!userId) {
2900
+ return res.status(400).json({ error: 'User ID is required' });
2901
+ }
2902
+ const invitation = await adapter.findOne({
2903
+ model: 'invitation',
2904
+ where: [{ field: 'id', value: id }],
2905
+ });
2906
+ if (!invitation) {
2907
+ return res.status(404).json({ error: 'Invitation not found' });
2908
+ }
2909
+ if (invitation.status !== 'pending') {
2910
+ return res.status(400).json({ error: 'Invitation is not pending' });
2911
+ }
2912
+ await adapter.update({
2913
+ model: 'invitation',
2914
+ where: [{ field: 'id', value: id }],
2915
+ update: {
2916
+ status: 'accepted',
2917
+ updatedAt: new Date().toISOString(),
2918
+ },
2919
+ });
2920
+ if (invitation.organizationId) {
2921
+ try {
2922
+ // Check if member already exists
2923
+ let existingMember = null;
2924
+ if (typeof adapter.findFirst === 'function') {
2925
+ existingMember = await adapter.findFirst({
2926
+ model: 'member',
2927
+ where: [
2928
+ { field: 'organizationId', value: invitation.organizationId },
2929
+ { field: 'userId', value: userId },
2930
+ ],
2931
+ });
2932
+ }
2933
+ else if (typeof adapter.findMany === 'function') {
2934
+ const members = await adapter.findMany({
2935
+ model: 'member',
2936
+ where: [
2937
+ { field: 'organizationId', value: invitation.organizationId },
2938
+ { field: 'userId', value: userId },
2939
+ ],
2940
+ });
2941
+ existingMember = members && members.length > 0 ? members[0] : null;
2942
+ }
2943
+ if (!existingMember) {
2944
+ await adapter.create({
2945
+ model: 'member',
2946
+ data: {
2947
+ organizationId: invitation.organizationId,
2948
+ userId: userId,
2949
+ role: invitation.role || 'member',
2950
+ createdAt: new Date().toISOString(),
2951
+ },
2952
+ });
2953
+ }
2954
+ }
2955
+ catch (error) {
2956
+ console.error('Error creating member:', error);
2957
+ // Ignore errors creating membership
2958
+ }
2959
+ }
2960
+ if (invitation.teamId) {
2961
+ try {
2962
+ let existingMember = null;
2963
+ if (typeof adapter.findFirst === 'function') {
2964
+ existingMember = await adapter.findFirst({
2965
+ model: 'teamMember',
2966
+ where: [
2967
+ { field: 'teamId', value: invitation.teamId },
2968
+ { field: 'userId', value: userId },
2969
+ ],
2970
+ });
2971
+ }
2972
+ else if (typeof adapter.findMany === 'function') {
2973
+ const members = await adapter.findMany({
2974
+ model: 'teamMember',
2975
+ where: [
2976
+ { field: 'teamId', value: invitation.teamId },
2977
+ { field: 'userId', value: userId },
2978
+ ],
2979
+ });
2980
+ existingMember = members && members.length > 0 ? members[0] : null;
2981
+ }
2982
+ if (!existingMember) {
2983
+ await adapter.create({
2984
+ model: 'teamMember',
2985
+ data: {
2986
+ teamId: invitation.teamId,
2987
+ userId: userId,
2988
+ createdAt: new Date().toISOString(),
2989
+ },
2990
+ });
2991
+ }
2992
+ }
2993
+ catch (error) {
2994
+ console.error('Error creating team member:', error);
2995
+ // Ignore errors creating team membership
2996
+ }
2997
+ }
2998
+ res.json({ success: true });
2999
+ }
3000
+ catch (error) {
3001
+ console.error('Failed to accept invitation:', error);
3002
+ res.status(500).json({ error: 'Failed to accept invitation' });
3003
+ }
3004
+ });
3005
+ router.post('/api/invitations/:id/reject', async (req, res) => {
3006
+ try {
3007
+ const { id } = req.params;
3008
+ const adapter = await getAuthAdapterWithConfig();
3009
+ if (!adapter) {
3010
+ return res.status(500).json({ error: 'Auth adapter not available' });
3011
+ }
3012
+ await adapter.update({
3013
+ model: 'invitation',
3014
+ where: [{ field: 'id', value: id }],
3015
+ update: {
3016
+ status: 'rejected',
3017
+ updatedAt: new Date().toISOString(),
3018
+ },
3019
+ });
3020
+ res.json({ success: true });
3021
+ }
2661
3022
  catch (_error) {
2662
- res.status(500).json({ error: 'Failed to resend invitation' });
3023
+ res.status(500).json({ error: 'Failed to reject invitation' });
2663
3024
  }
2664
3025
  });
2665
3026
  router.delete('/api/invitations/:id', async (req, res) => {
@@ -2689,7 +3050,13 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2689
3050
  router.post('/api/organizations/:orgId/invitations', async (req, res) => {
2690
3051
  try {
2691
3052
  const { orgId } = req.params;
2692
- const { email, role = 'member', inviterId } = req.body;
3053
+ const { email, role = 'member', inviterId, teamId } = req.body;
3054
+ if (!email || typeof email !== 'string') {
3055
+ return res.status(400).json({ error: 'Email is required' });
3056
+ }
3057
+ if (!orgId) {
3058
+ return res.status(400).json({ error: 'Organization ID is required' });
3059
+ }
2693
3060
  if (!inviterId) {
2694
3061
  return res.status(400).json({ error: 'Inviter ID is required' });
2695
3062
  }
@@ -2697,60 +3064,143 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2697
3064
  if (!adapter) {
2698
3065
  return res.status(500).json({ error: 'Auth adapter not available' });
2699
3066
  }
3067
+ if (!adapter.create) {
3068
+ return res.status(500).json({ error: 'Adapter create method not available' });
3069
+ }
3070
+ try {
3071
+ const organization = await adapter.findOne({
3072
+ model: 'organization',
3073
+ where: [{ field: 'id', value: orgId }],
3074
+ });
3075
+ if (!organization) {
3076
+ return res.status(404).json({ error: 'Organization not found' });
3077
+ }
3078
+ }
3079
+ catch (orgError) {
3080
+ try {
3081
+ if (typeof adapter.findMany === 'function') {
3082
+ const orgs = await adapter.findMany({
3083
+ model: 'organization',
3084
+ where: [{ field: 'id', value: orgId }],
3085
+ limit: 1,
3086
+ });
3087
+ if (!orgs || orgs.length === 0) {
3088
+ return res.status(404).json({ error: 'Organization not found' });
3089
+ }
3090
+ }
3091
+ }
3092
+ catch (_fallbackError) { }
3093
+ }
3094
+ try {
3095
+ let existingInvitation = null;
3096
+ if (typeof adapter.findFirst === 'function') {
3097
+ existingInvitation = await adapter.findFirst({
3098
+ model: 'invitation',
3099
+ where: [
3100
+ { field: 'email', value: email.toLowerCase() },
3101
+ { field: 'organizationId', value: orgId },
3102
+ { field: 'status', value: 'pending' },
3103
+ ],
3104
+ });
3105
+ }
3106
+ else if (typeof adapter.findMany === 'function') {
3107
+ const invitations = await adapter.findMany({
3108
+ model: 'invitation',
3109
+ where: [
3110
+ { field: 'email', value: email.toLowerCase() },
3111
+ { field: 'organizationId', value: orgId },
3112
+ { field: 'status', value: 'pending' },
3113
+ ],
3114
+ limit: 1,
3115
+ });
3116
+ existingInvitation = invitations && invitations.length > 0 ? invitations[0] : null;
3117
+ }
3118
+ if (existingInvitation) {
3119
+ return res
3120
+ .status(400)
3121
+ .json({ error: 'A pending invitation already exists for this email' });
3122
+ }
3123
+ }
3124
+ catch (_duplicateCheckError) { }
2700
3125
  const invitationData = {
2701
- email,
3126
+ email: email.toLowerCase(),
2702
3127
  role,
2703
3128
  organizationId: orgId,
2704
3129
  status: 'pending',
2705
3130
  expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
2706
- createdAt: new Date(),
2707
3131
  inviterId: inviterId,
2708
3132
  };
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' });
3133
+ if (teamId) {
3134
+ invitationData.teamId = teamId;
2715
3135
  }
2716
- await adapter.create({
3136
+ const createdInvitation = await adapter.create({
2717
3137
  model: 'invitation',
2718
3138
  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,
3139
+ ...invitationData,
2726
3140
  },
2727
3141
  });
2728
- res.json({ success: true, invitation });
3142
+ if (!createdInvitation) {
3143
+ return res.status(500).json({ error: 'Failed to create invitation' });
3144
+ }
3145
+ res.json({ success: true, invitation: createdInvitation });
2729
3146
  }
2730
- catch (_error) {
2731
- res.status(500).json({ error: 'Failed to create invitation' });
3147
+ catch (error) {
3148
+ console.error('Error creating invitation:', error);
3149
+ const errorMessage = error instanceof Error ? error.message : 'Failed to create invitation';
3150
+ res.status(500).json({
3151
+ error: 'Failed to create invitation',
3152
+ details: isSelfHosted ? errorMessage : undefined,
3153
+ });
2732
3154
  }
2733
3155
  });
2734
3156
  router.get('/api/organizations/:orgId/teams', async (req, res) => {
2735
3157
  try {
2736
3158
  const { orgId } = req.params;
2737
3159
  const adapter = await getAuthAdapterWithConfig();
2738
- if (adapter && typeof adapter.findMany === 'function') {
3160
+ if (!adapter) {
3161
+ return res.status(500).json({
3162
+ success: false,
3163
+ error: 'Auth adapter not available',
3164
+ teams: [],
3165
+ });
3166
+ }
3167
+ if (typeof adapter.findMany !== 'function') {
3168
+ return res.status(500).json({
3169
+ success: false,
3170
+ error: 'Adapter findMany method not available',
3171
+ teams: [],
3172
+ });
3173
+ }
3174
+ try {
3175
+ let teams = [];
2739
3176
  try {
2740
- const teams = await adapter.findMany({
3177
+ teams = await adapter.findMany({
2741
3178
  model: 'team',
2742
3179
  where: [{ field: 'organizationId', value: orgId }],
2743
3180
  limit: 10000,
2744
3181
  });
2745
- const transformedTeams = await Promise.all((teams || []).map(async (team) => {
2746
- if (!adapter.findMany) {
2747
- return null;
3182
+ }
3183
+ catch (whereError) {
3184
+ const allTeams = await adapter.findMany({
3185
+ model: 'team',
3186
+ limit: 10000,
3187
+ });
3188
+ teams = (allTeams || []).filter((team) => team.organizationId === orgId);
3189
+ }
3190
+ if (!teams || teams.length === 0) {
3191
+ return res.json({ success: true, teams: [] });
3192
+ }
3193
+ const transformedTeams = await Promise.all((teams || []).map(async (team) => {
3194
+ try {
3195
+ let memberCount = 0;
3196
+ if (adapter.findMany) {
3197
+ const teamMembers = await adapter.findMany({
3198
+ model: 'teamMember',
3199
+ where: [{ field: 'teamId', value: team.id }],
3200
+ limit: 10000,
3201
+ });
3202
+ memberCount = teamMembers ? teamMembers.length : 0;
2748
3203
  }
2749
- const teamMembers = await adapter.findMany({
2750
- model: 'teamMember',
2751
- where: [{ field: 'teamId', value: team.id }],
2752
- limit: 10000,
2753
- });
2754
3204
  return {
2755
3205
  id: team.id,
2756
3206
  name: team.name,
@@ -2758,18 +3208,38 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2758
3208
  metadata: team.metadata,
2759
3209
  createdAt: team.createdAt,
2760
3210
  updatedAt: team.updatedAt,
2761
- memberCount: teamMembers ? teamMembers.length : 0,
3211
+ memberCount: memberCount,
2762
3212
  };
2763
- }));
2764
- res.json({ success: true, teams: transformedTeams });
2765
- return;
2766
- }
2767
- catch (_error) { }
3213
+ }
3214
+ catch (_error) {
3215
+ return {
3216
+ id: team.id,
3217
+ name: team.name,
3218
+ organizationId: team.organizationId,
3219
+ metadata: team.metadata,
3220
+ createdAt: team.createdAt,
3221
+ updatedAt: team.updatedAt,
3222
+ memberCount: 0,
3223
+ };
3224
+ }
3225
+ }));
3226
+ const validTeams = transformedTeams.filter((team) => team !== null);
3227
+ return res.json({ success: true, teams: validTeams });
3228
+ }
3229
+ catch (error) {
3230
+ return res.json({
3231
+ success: true,
3232
+ teams: [],
3233
+ error: error?.message || 'Failed to fetch teams',
3234
+ });
2768
3235
  }
2769
- res.json({ success: true, teams: [] });
2770
3236
  }
2771
- catch (_error) {
2772
- res.status(500).json({ error: 'Failed to fetch teams' });
3237
+ catch (error) {
3238
+ res.status(500).json({
3239
+ success: false,
3240
+ error: 'Failed to fetch teams',
3241
+ message: error?.message || 'Unknown error',
3242
+ });
2773
3243
  }
2774
3244
  });
2775
3245
  router.post('/api/organizations/:orgId/teams', async (req, res) => {
@@ -2794,7 +3264,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2794
3264
  if (!adapter.create) {
2795
3265
  return res.status(500).json({ error: 'Adapter create method not available' });
2796
3266
  }
2797
- await adapter.create({
3267
+ const teamResult = await adapter.create({
2798
3268
  model: 'team',
2799
3269
  data: {
2800
3270
  name: teamData.name,
@@ -2803,6 +3273,9 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2803
3273
  updatedAt: teamData.updatedAt,
2804
3274
  },
2805
3275
  });
3276
+ if (!teamResult) {
3277
+ return res.status(500).json({ error: 'Failed to create team' });
3278
+ }
2806
3279
  res.json({ success: true, team });
2807
3280
  }
2808
3281
  catch (_error) {
@@ -2872,39 +3345,83 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2872
3345
  return res.status(400).json({ error: 'userIds array is required' });
2873
3346
  }
2874
3347
  const adapter = await getAuthAdapterWithConfig();
2875
- if (!adapter || !adapter.create) {
3348
+ if (!adapter) {
2876
3349
  return res.status(500).json({ error: 'Adapter not available' });
2877
3350
  }
3351
+ if (!adapter.create) {
3352
+ return res.status(500).json({ error: 'Adapter create method not available' });
3353
+ }
2878
3354
  const results = [];
2879
3355
  for (const userId of userIds) {
2880
3356
  try {
3357
+ let existingMember = null;
3358
+ if (adapter.findMany) {
3359
+ try {
3360
+ const existing = await adapter.findMany({
3361
+ model: 'teamMember',
3362
+ where: [
3363
+ { field: 'teamId', value: teamId },
3364
+ { field: 'userId', value: userId },
3365
+ ],
3366
+ limit: 1,
3367
+ });
3368
+ existingMember = existing && existing.length > 0 ? existing[0] : null;
3369
+ }
3370
+ catch (_findError) {
3371
+ // if where clause isn't working.
3372
+ try {
3373
+ const allMembers = await adapter.findMany({
3374
+ model: 'teamMember',
3375
+ limit: 10000,
3376
+ });
3377
+ existingMember = (allMembers || []).find((m) => m.teamId === teamId && m.userId === userId);
3378
+ }
3379
+ catch (_fallbackError) { }
3380
+ }
3381
+ }
3382
+ if (existingMember) {
3383
+ results.push({
3384
+ success: false,
3385
+ userId,
3386
+ error: 'User is already a member of this team',
3387
+ });
3388
+ continue;
3389
+ }
3390
+ const now = new Date();
2881
3391
  await adapter.create({
2882
3392
  model: 'teamMember',
2883
3393
  data: {
2884
3394
  teamId,
2885
3395
  userId,
2886
3396
  role: 'member',
2887
- createdAt: new Date(),
3397
+ createdAt: now,
3398
+ updatedAt: now,
2888
3399
  },
2889
3400
  });
2890
3401
  results.push({ success: true, userId });
2891
3402
  }
2892
3403
  catch (error) {
3404
+ const errorMessage = error?.message || error?.toString() || 'Unknown error';
2893
3405
  results.push({
2894
3406
  success: false,
2895
3407
  userId,
2896
- error: error instanceof Error ? error.message : 'Unknown error',
3408
+ error: errorMessage,
2897
3409
  });
2898
3410
  }
2899
3411
  }
3412
+ const successCount = results.filter((r) => r.success).length;
2900
3413
  res.json({
2901
- success: true,
2902
- message: `Added ${results.filter((r) => r.success).length} members`,
3414
+ success: results.some((r) => r.success),
3415
+ message: `Added ${successCount} member${successCount !== 1 ? 's' : ''}`,
2903
3416
  results,
2904
3417
  });
2905
3418
  }
2906
- catch (_error) {
2907
- res.status(500).json({ error: 'Failed to add team members' });
3419
+ catch (error) {
3420
+ res.status(500).json({
3421
+ success: false,
3422
+ error: 'Failed to add team members',
3423
+ message: error?.message || 'Unknown error',
3424
+ });
2908
3425
  }
2909
3426
  });
2910
3427
  router.delete('/api/team-members/:id', async (req, res) => {
@@ -2974,7 +3491,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2974
3491
  });
2975
3492
  router.get('/api/plugins/organization/status', async (_req, res) => {
2976
3493
  try {
2977
- const betterAuthConfig = await getAuthConfigSafe();
3494
+ const betterAuthConfig = preloadedAuthOptions || (await getAuthConfigSafe());
2978
3495
  if (!betterAuthConfig) {
2979
3496
  return res.json({
2980
3497
  enabled: false,
@@ -3106,7 +3623,7 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
3106
3623
  try {
3107
3624
  const { id } = req.params;
3108
3625
  const userData = req.body;
3109
- const updatedUser = await getAuthData(authConfig, 'updateUser', { id, userData }, configPath);
3626
+ const updatedUser = await getAuthData(authConfig, 'updateUser', { id, userData }, configPath, preloadedAdapter);
3110
3627
  res.json({ success: true, user: updatedUser });
3111
3628
  }
3112
3629
  catch (_error) {
@@ -3497,6 +4014,92 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
3497
4014
  res.status(500).json({ success: false, error: 'Failed to fetch OAuth providers' });
3498
4015
  }
3499
4016
  });
4017
+ router.get('/api/tools/oauth/credentials', async (req, res) => {
4018
+ try {
4019
+ const { provider, origin } = req.query;
4020
+ if (!provider || typeof provider !== 'string') {
4021
+ return res.status(400).json({
4022
+ success: false,
4023
+ error: 'Provider is required',
4024
+ });
4025
+ }
4026
+ if (!origin || typeof origin !== 'string') {
4027
+ return res.status(400).json({
4028
+ success: false,
4029
+ error: 'Origin is required',
4030
+ });
4031
+ }
4032
+ // TODO: Import getOAuthCredentials at the top of this file:
4033
+ // import { getOAuthCredentials } from './path/to/your/oauth-config';
4034
+ // For now, we'll access it from a function that should be provided
4035
+ // This assumes getOAuthCredentials is available in the scope
4036
+ // You need to import it: import { getOAuthCredentials } from './your-oauth-config-file';
4037
+ // Placeholder - replace this with actual import at top of file
4038
+ const getOAuthCredentials = global.getOAuthCredentials;
4039
+ if (typeof getOAuthCredentials !== 'function') {
4040
+ return res.status(500).json({
4041
+ success: false,
4042
+ error: 'OAuth credentials function not configured. Please import getOAuthCredentials function.',
4043
+ });
4044
+ }
4045
+ const credentialsResult = getOAuthCredentials(provider, origin);
4046
+ // Handle null return (provider not found)
4047
+ if (credentialsResult === null) {
4048
+ return res.status(404).json({
4049
+ success: false,
4050
+ error: 'No credential found',
4051
+ });
4052
+ }
4053
+ // Handle error cases with proper messages as requested
4054
+ if (credentialsResult.error) {
4055
+ if (credentialsResult.error === 'NO_CREDENTIALS_FOUND') {
4056
+ return res.status(404).json({
4057
+ success: false,
4058
+ error: 'No credential found',
4059
+ });
4060
+ }
4061
+ else if (credentialsResult.error === 'INVALID_ORIGIN') {
4062
+ return res.status(400).json({
4063
+ success: false,
4064
+ error: 'Invalid origin. OAuth credentials are only available for localhost origins.',
4065
+ });
4066
+ }
4067
+ else {
4068
+ return res.status(400).json({
4069
+ success: false,
4070
+ error: credentialsResult.error || 'Failed to get OAuth credentials',
4071
+ });
4072
+ }
4073
+ }
4074
+ // Check if result exists and has required fields
4075
+ if (!credentialsResult.result) {
4076
+ return res.status(404).json({
4077
+ success: false,
4078
+ error: 'No credential found',
4079
+ });
4080
+ }
4081
+ const { clientId, clientSecret } = credentialsResult.result;
4082
+ if (!clientId || !clientSecret) {
4083
+ return res.status(404).json({
4084
+ success: false,
4085
+ error: 'No credential found',
4086
+ });
4087
+ }
4088
+ res.json({
4089
+ success: true,
4090
+ clientId,
4091
+ clientSecret,
4092
+ });
4093
+ }
4094
+ catch (error) {
4095
+ console.error('Failed to fetch OAuth credentials:', error);
4096
+ res.status(500).json({
4097
+ success: false,
4098
+ error: 'Failed to fetch OAuth credentials',
4099
+ details: error instanceof Error ? error.message : String(error),
4100
+ });
4101
+ }
4102
+ });
3500
4103
  router.post('/api/tools/oauth/test', async (req, res) => {
3501
4104
  try {
3502
4105
  const { provider } = req.body;