better-auth-studio 1.0.79-beta.35 → 1.0.79-beta.37

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.
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" type="image/png" href="/logo.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Better Auth Studio</title>
8
- <script type="module" crossorigin src="/assets/main-DAv-RHMo.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/main-R1O9iElM.css">
8
+ <script type="module" crossorigin src="/assets/main-DEnyGsn2.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/main-BgI41EAt.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAeA,OAAO,EAA+B,MAAM,EAAE,MAAM,SAAS,CAAC;AAU9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AA0GnE,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAqLhG;AAeD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,EAClB,gBAAgB,CAAC,EAAE,GAAG,EACtB,oBAAoB,CAAC,EAAE,GAAG,EAC1B,YAAY,CAAC,EAAE,kBAAkB,EACjC,YAAY,CAAC,EAAE,GAAG,GACjB,MAAM,CA+6LR;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,IAAI,EAAE,GAAG,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC,GAAG,OAAO,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,GAAG,CAAC;IACV,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;CAChE,CAAC,CA+FD"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAeA,OAAO,EAA+B,MAAM,EAAE,MAAM,SAAS,CAAC;AAU9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AA0GnE,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAqLhG;AAeD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,EAClB,gBAAgB,CAAC,EAAE,GAAG,EACtB,oBAAoB,CAAC,EAAE,GAAG,EAC1B,YAAY,CAAC,EAAE,kBAAkB,EACjC,YAAY,CAAC,EAAE,GAAG,GACjB,MAAM,CAy2MR;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,IAAI,EAAE,GAAG,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC,GAAG,OAAO,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,GAAG,CAAC;IACV,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;CAChE,CAAC,CA+FD"}
package/dist/routes.js CHANGED
@@ -320,6 +320,10 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
320
320
  // For self-hosted studio, wrap the preloaded adapter to match expected interface
321
321
  return {
322
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),
323
327
  findMany: preloadedAdapter.findMany?.bind(preloadedAdapter),
324
328
  create: preloadedAdapter.create?.bind(preloadedAdapter),
325
329
  update: preloadedAdapter.update?.bind(preloadedAdapter),
@@ -2717,6 +2721,26 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2717
2721
  if (!adapter.update) {
2718
2722
  return res.status(500).json({ error: 'Adapter update method not available' });
2719
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
+ }
2720
2744
  await adapter.update({
2721
2745
  model: 'invitation',
2722
2746
  where: [{ field: 'id', value: id }],
@@ -2727,8 +2751,245 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2727
2751
  });
2728
2752
  res.json({ success: true });
2729
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
+ }
2730
2991
  catch (_error) {
2731
- res.status(500).json({ error: 'Failed to resend invitation' });
2992
+ res.status(500).json({ error: 'Failed to reject invitation' });
2732
2993
  }
2733
2994
  });
2734
2995
  router.delete('/api/invitations/:id', async (req, res) => {
@@ -2758,7 +3019,13 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2758
3019
  router.post('/api/organizations/:orgId/invitations', async (req, res) => {
2759
3020
  try {
2760
3021
  const { orgId } = req.params;
2761
- 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
+ }
2762
3029
  if (!inviterId) {
2763
3030
  return res.status(400).json({ error: 'Inviter ID is required' });
2764
3031
  }
@@ -2766,8 +3033,66 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2766
3033
  if (!adapter) {
2767
3034
  return res.status(500).json({ error: 'Auth adapter not available' });
2768
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) { }
2769
3094
  const invitationData = {
2770
- email,
3095
+ email: email.toLowerCase(),
2771
3096
  role,
2772
3097
  organizationId: orgId,
2773
3098
  status: 'pending',
@@ -2775,29 +3100,25 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
2775
3100
  createdAt: new Date(),
2776
3101
  inviterId: inviterId,
2777
3102
  };
2778
- const invitation = {
2779
- id: `inv_${Date.now()}`,
2780
- ...invitationData,
2781
- };
2782
- if (!adapter.create) {
2783
- return res.status(500).json({ error: 'Adapter create method not available' });
3103
+ if (teamId) {
3104
+ invitationData.teamId = teamId;
2784
3105
  }
2785
- await adapter.create({
3106
+ const createdInvitation = await adapter.create({
2786
3107
  model: 'invitation',
2787
- data: {
2788
- organizationId: invitationData.organizationId,
2789
- email: invitationData.email,
2790
- role: invitationData.role,
2791
- status: invitationData.status,
2792
- inviterId: invitationData.inviterId,
2793
- expiresAt: invitationData.expiresAt,
2794
- createdAt: invitationData.createdAt,
2795
- },
3108
+ data: invitationData,
2796
3109
  });
2797
- res.json({ success: true, invitation });
3110
+ if (!createdInvitation) {
3111
+ return res.status(500).json({ error: 'Failed to create invitation' });
3112
+ }
3113
+ res.json({ success: true, invitation: createdInvitation });
2798
3114
  }
2799
- catch (_error) {
2800
- res.status(500).json({ error: 'Failed to create invitation' });
3115
+ catch (error) {
3116
+ console.error('Error creating invitation:', error);
3117
+ const errorMessage = error instanceof Error ? error.message : 'Failed to create invitation';
3118
+ res.status(500).json({
3119
+ error: 'Failed to create invitation',
3120
+ details: isSelfHosted ? errorMessage : undefined,
3121
+ });
2801
3122
  }
2802
3123
  });
2803
3124
  router.get('/api/organizations/:orgId/teams', async (req, res) => {
@@ -3661,6 +3982,92 @@ export function createRoutes(authConfig, configPath, geoDbPath, preloadedAdapter
3661
3982
  res.status(500).json({ success: false, error: 'Failed to fetch OAuth providers' });
3662
3983
  }
3663
3984
  });
3985
+ router.get('/api/tools/oauth/credentials', async (req, res) => {
3986
+ try {
3987
+ const { provider, origin } = req.query;
3988
+ if (!provider || typeof provider !== 'string') {
3989
+ return res.status(400).json({
3990
+ success: false,
3991
+ error: 'Provider is required',
3992
+ });
3993
+ }
3994
+ if (!origin || typeof origin !== 'string') {
3995
+ return res.status(400).json({
3996
+ success: false,
3997
+ error: 'Origin is required',
3998
+ });
3999
+ }
4000
+ // TODO: Import getOAuthCredentials at the top of this file:
4001
+ // import { getOAuthCredentials } from './path/to/your/oauth-config';
4002
+ // For now, we'll access it from a function that should be provided
4003
+ // This assumes getOAuthCredentials is available in the scope
4004
+ // You need to import it: import { getOAuthCredentials } from './your-oauth-config-file';
4005
+ // Placeholder - replace this with actual import at top of file
4006
+ const getOAuthCredentials = global.getOAuthCredentials;
4007
+ if (typeof getOAuthCredentials !== 'function') {
4008
+ return res.status(500).json({
4009
+ success: false,
4010
+ error: 'OAuth credentials function not configured. Please import getOAuthCredentials function.',
4011
+ });
4012
+ }
4013
+ const credentialsResult = getOAuthCredentials(provider, origin);
4014
+ // Handle null return (provider not found)
4015
+ if (credentialsResult === null) {
4016
+ return res.status(404).json({
4017
+ success: false,
4018
+ error: 'No credential found',
4019
+ });
4020
+ }
4021
+ // Handle error cases with proper messages as requested
4022
+ if (credentialsResult.error) {
4023
+ if (credentialsResult.error === 'NO_CREDENTIALS_FOUND') {
4024
+ return res.status(404).json({
4025
+ success: false,
4026
+ error: 'No credential found',
4027
+ });
4028
+ }
4029
+ else if (credentialsResult.error === 'INVALID_ORIGIN') {
4030
+ return res.status(400).json({
4031
+ success: false,
4032
+ error: 'Invalid origin. OAuth credentials are only available for localhost origins.',
4033
+ });
4034
+ }
4035
+ else {
4036
+ return res.status(400).json({
4037
+ success: false,
4038
+ error: credentialsResult.error || 'Failed to get OAuth credentials',
4039
+ });
4040
+ }
4041
+ }
4042
+ // Check if result exists and has required fields
4043
+ if (!credentialsResult.result) {
4044
+ return res.status(404).json({
4045
+ success: false,
4046
+ error: 'No credential found',
4047
+ });
4048
+ }
4049
+ const { clientId, clientSecret } = credentialsResult.result;
4050
+ if (!clientId || !clientSecret) {
4051
+ return res.status(404).json({
4052
+ success: false,
4053
+ error: 'No credential found',
4054
+ });
4055
+ }
4056
+ res.json({
4057
+ success: true,
4058
+ clientId,
4059
+ clientSecret,
4060
+ });
4061
+ }
4062
+ catch (error) {
4063
+ console.error('Failed to fetch OAuth credentials:', error);
4064
+ res.status(500).json({
4065
+ success: false,
4066
+ error: 'Failed to fetch OAuth credentials',
4067
+ details: error instanceof Error ? error.message : String(error),
4068
+ });
4069
+ }
4070
+ });
3664
4071
  router.post('/api/tools/oauth/test', async (req, res) => {
3665
4072
  try {
3666
4073
  const { provider } = req.body;