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