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