@payez/next-mvp 4.0.23 → 4.0.25

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.
@@ -45,13 +45,6 @@ export declare function createBetterAuthInstance(idpConfig: IDPClientConfig): im
45
45
  refreshCache: false;
46
46
  };
47
47
  };
48
- databaseHooks: {
49
- session: {
50
- create: {
51
- after: (session: any) => Promise<void>;
52
- };
53
- };
54
- };
55
48
  advanced: {
56
49
  cookiePrefix: string;
57
50
  cookies: {
@@ -105,3 +98,13 @@ export declare function getBetterAuthHandler(): Promise<{
105
98
  GET: (req: Request) => Promise<Response>;
106
99
  POST: (req: Request) => Promise<Response>;
107
100
  } | null>;
101
+ /**
102
+ * Exchange OAuth identity for IDP tokens and store in the BA Redis session.
103
+ *
104
+ * Call this from the OAuth callback route AFTER better-auth has processed the
105
+ * callback and created the session. Reads the session token from the Set-Cookie
106
+ * header of the response to find the BA Redis key.
107
+ *
108
+ * This replaces the old databaseHooks approach which doesn't fire in stateless mode.
109
+ */
110
+ export declare function exchangeOAuthForIdpTokens(sessionToken: string, provider?: string): Promise<boolean>;
@@ -16,6 +16,7 @@ exports.createBetterAuthInstance = createBetterAuthInstance;
16
16
  exports.isBetterAuthEnabled = isBetterAuthEnabled;
17
17
  exports.getBetterAuthInstance = getBetterAuthInstance;
18
18
  exports.getBetterAuthHandler = getBetterAuthHandler;
19
+ exports.exchangeOAuthForIdpTokens = exchangeOAuthForIdpTokens;
19
20
  require("server-only");
20
21
  const better_auth_1 = require("better-auth");
21
22
  const next_js_1 = require("better-auth/next-js");
@@ -49,17 +50,20 @@ function buildBetterAuthProviders(config) {
49
50
  function createBetterAuthInstance(idpConfig) {
50
51
  const appSlug = idpConfig.clientSlug || (0, app_slug_1.getAppSlug)();
51
52
  // Resolve base URL: BETTER_AUTH_URL env > IDP config > localhost fallback
52
- const baseURL = process.env.BETTER_AUTH_URL
53
+ // Must include /api/auth since that's where the catch-all route is mounted
54
+ const rawBaseURL = process.env.BETTER_AUTH_URL
53
55
  || idpConfig.baseClientUrl
54
56
  || `http://localhost:${process.env.PORT || '3000'}`;
57
+ const baseURL = rawBaseURL.replace(/\/+$/, '') + '/api/auth';
55
58
  return (0, better_auth_1.betterAuth)({
56
59
  baseURL,
57
60
  secret: idpConfig.nextAuthSecret,
58
61
  socialProviders: buildBetterAuthProviders(idpConfig),
59
62
  // Trust the app's own origin + any configured base URL
60
63
  trustedOrigins: [
64
+ rawBaseURL,
61
65
  baseURL,
62
- ...(idpConfig.baseClientUrl && idpConfig.baseClientUrl !== baseURL ? [idpConfig.baseClientUrl] : []),
66
+ ...(idpConfig.baseClientUrl ? [idpConfig.baseClientUrl] : []),
63
67
  'http://localhost:3000',
64
68
  'http://localhost:3400',
65
69
  'http://localhost:3600',
@@ -100,90 +104,6 @@ function createBetterAuthInstance(idpConfig) {
100
104
  refreshCache: false,
101
105
  },
102
106
  },
103
- // After social login, exchange Google identity for IDP tokens and store in Redis
104
- databaseHooks: {
105
- session: {
106
- create: {
107
- after: async (session) => {
108
- try {
109
- const userId = session.userId;
110
- const token = session.token;
111
- if (!userId || !token)
112
- return;
113
- // Look up user from Better Auth's memory/DB to get email
114
- // The user was just created/found by Better Auth during OAuth
115
- const baKey = `ba:${appSlug}:${token}`;
116
- const baRaw = await (0, redis_1.getRedis)().get(baKey).catch(() => null);
117
- const baData = baRaw ? JSON.parse(baRaw) : null;
118
- const email = baData?.user?.email;
119
- const name = baData?.user?.name;
120
- const image = baData?.user?.image;
121
- if (!email) {
122
- console.warn('[BETTER_AUTH] Session created but no email found for IDP token exchange');
123
- return;
124
- }
125
- // Call IDP oauth-callback to get IDP tokens
126
- const idpUrl = process.env.IDP_URL || '';
127
- if (!idpUrl) {
128
- console.warn('[BETTER_AUTH] No IDP URL configured, skipping token exchange');
129
- return;
130
- }
131
- const oauthRes = await fetch(`${idpUrl}/api/ExternalAuth/oauth-callback`, {
132
- method: 'POST',
133
- headers: { 'Content-Type': 'application/json' },
134
- body: JSON.stringify({
135
- provider: 'google',
136
- provider_account_id: userId,
137
- email,
138
- name,
139
- image,
140
- client_id: idpConfig.clientSlug || String(idpConfig.clientId),
141
- }),
142
- });
143
- const oauthResText = await oauthRes.text();
144
- console.log('[BETTER_AUTH] IDP oauth-callback response:', oauthRes.status, oauthResText.substring(0, 500));
145
- if (!oauthRes.ok) {
146
- console.error('[BETTER_AUTH] IDP oauth-callback failed:', oauthRes.status);
147
- return;
148
- }
149
- let idpData;
150
- try {
151
- idpData = JSON.parse(oauthResText);
152
- }
153
- catch {
154
- return;
155
- }
156
- const result = idpData?.data?.result || idpData?.data || idpData;
157
- if (!result?.access_token) {
158
- console.warn('[BETTER_AUTH] IDP oauth-callback returned no access_token. Keys:', Object.keys(result || {}));
159
- return;
160
- }
161
- // Store IDP tokens in the BA Redis session
162
- if (baData) {
163
- const requiresTwoFactor = result.user?.requiresTwoFactor ?? result.requiresTwoFactor ?? false;
164
- baData.idpTokens = {
165
- idpAccessToken: result.access_token,
166
- idpRefreshToken: result.refresh_token,
167
- idpAccessTokenExpires: result.expires_in
168
- ? Date.now() + result.expires_in * 1000
169
- : Date.now() + 15 * 60 * 1000,
170
- userId: String(result.user?.user_id || result.user?.id || result.user_id || userId),
171
- email: result.user?.email || result.email || email,
172
- name: result.user?.full_name || result.user?.name || result.name || name,
173
- roles: result.user?.roles || result.roles || [],
174
- mfaVerified: !requiresTwoFactor,
175
- };
176
- await (0, redis_1.getRedis)().setex(baKey, 7 * 24 * 60 * 60, JSON.stringify(baData));
177
- console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
178
- }
179
- }
180
- catch (err) {
181
- console.error('[BETTER_AUTH] Post-login IDP exchange failed:', err instanceof Error ? err.message : String(err));
182
- }
183
- },
184
- },
185
- },
186
- },
187
107
  // Cookie prefix must match slim-middleware expectations ({slug}.session-token)
188
108
  advanced: {
189
109
  cookiePrefix: appSlug,
@@ -248,3 +168,92 @@ async function getBetterAuthHandler() {
248
168
  const auth = await getBetterAuthInstance();
249
169
  return (0, next_js_2.toNextJsHandler)(auth);
250
170
  }
171
+ /**
172
+ * Exchange OAuth identity for IDP tokens and store in the BA Redis session.
173
+ *
174
+ * Call this from the OAuth callback route AFTER better-auth has processed the
175
+ * callback and created the session. Reads the session token from the Set-Cookie
176
+ * header of the response to find the BA Redis key.
177
+ *
178
+ * This replaces the old databaseHooks approach which doesn't fire in stateless mode.
179
+ */
180
+ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
181
+ try {
182
+ const config = await (0, idp_client_config_1.getIDPClientConfig)();
183
+ const appSlug = config.clientSlug || (0, app_slug_1.getAppSlug)();
184
+ const baKey = `ba:${appSlug}:${sessionToken}`;
185
+ // Read the BA session from Redis
186
+ const baRaw = await (0, redis_1.getRedis)().get(baKey).catch(() => null);
187
+ if (!baRaw) {
188
+ console.warn('[BETTER_AUTH] exchangeOAuthForIdpTokens: session not found in Redis for token', sessionToken.substring(0, 10));
189
+ return false;
190
+ }
191
+ const baData = JSON.parse(baRaw);
192
+ const email = baData?.user?.email;
193
+ const name = baData?.user?.name;
194
+ const image = baData?.user?.image;
195
+ const baUserId = baData?.session?.userId || baData?.user?.id;
196
+ if (!email) {
197
+ console.warn('[BETTER_AUTH] exchangeOAuthForIdpTokens: no email in session');
198
+ return false;
199
+ }
200
+ // Call IDP oauth-callback
201
+ const idpUrl = process.env.IDP_URL || '';
202
+ if (!idpUrl) {
203
+ console.warn('[BETTER_AUTH] No IDP_URL configured, skipping token exchange');
204
+ return false;
205
+ }
206
+ console.log('[BETTER_AUTH] Exchanging OAuth identity for IDP tokens:', email);
207
+ const oauthRes = await fetch(`${idpUrl}/api/ExternalAuth/oauth-callback`, {
208
+ method: 'POST',
209
+ headers: { 'Content-Type': 'application/json' },
210
+ body: JSON.stringify({
211
+ provider,
212
+ provider_account_id: baUserId,
213
+ email,
214
+ name,
215
+ image,
216
+ client_id: config.clientSlug || String(config.clientId),
217
+ }),
218
+ });
219
+ const oauthResText = await oauthRes.text();
220
+ console.log('[BETTER_AUTH] IDP oauth-callback response:', oauthRes.status, oauthResText.substring(0, 500));
221
+ if (!oauthRes.ok) {
222
+ console.error('[BETTER_AUTH] IDP oauth-callback failed:', oauthRes.status);
223
+ return false;
224
+ }
225
+ let idpData;
226
+ try {
227
+ idpData = JSON.parse(oauthResText);
228
+ }
229
+ catch {
230
+ return false;
231
+ }
232
+ const result = idpData?.data?.result || idpData?.data || idpData;
233
+ if (!result?.access_token) {
234
+ console.warn('[BETTER_AUTH] IDP oauth-callback returned no access_token. Keys:', Object.keys(result || {}));
235
+ return false;
236
+ }
237
+ // Store IDP tokens in the BA Redis session
238
+ const requiresTwoFactor = result.user?.requiresTwoFactor ?? result.requiresTwoFactor ?? false;
239
+ baData.idpTokens = {
240
+ idpAccessToken: result.access_token,
241
+ idpRefreshToken: result.refresh_token,
242
+ idpAccessTokenExpires: result.expires_in
243
+ ? Date.now() + result.expires_in * 1000
244
+ : Date.now() + 15 * 60 * 1000,
245
+ userId: String(result.user?.user_id || result.user?.id || result.user_id || baUserId),
246
+ email: result.user?.email || result.email || email,
247
+ name: result.user?.full_name || result.user?.name || result.name || name,
248
+ roles: result.user?.roles || result.roles || [],
249
+ mfaVerified: !requiresTwoFactor,
250
+ };
251
+ await (0, redis_1.getRedis)().setex(baKey, 7 * 24 * 60 * 60, JSON.stringify(baData));
252
+ console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
253
+ return true;
254
+ }
255
+ catch (err) {
256
+ console.error('[BETTER_AUTH] IDP token exchange failed:', err instanceof Error ? err.message : String(err));
257
+ return false;
258
+ }
259
+ }
@@ -956,14 +956,6 @@ export declare const useSession: () => {
956
956
  code?: string | undefined;
957
957
  message?: string | undefined;
958
958
  }, FetchOptions["throw"] extends true ? true : false>>;
959
- /**
960
- * Sign in with a specific OAuth provider via direct redirect.
961
- *
962
- * better-auth exposes per-provider endpoints at /api/auth/sign-in/{provider}
963
- * (e.g. /api/auth/sign-in/google). The generic signIn.social() method POSTs
964
- * to /api/auth/sign-in/social which doesn't exist — use this instead.
965
- */
966
- export declare function signInWithProvider(provider: string, callbackURL?: string): void;
967
959
  /**
968
960
  * NextAuth-compatible useSession wrapper.
969
961
  *
@@ -10,7 +10,6 @@
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.signOut = exports.signIn = exports.useSession = exports.authClient = void 0;
13
- exports.signInWithProvider = signInWithProvider;
14
13
  exports.useSessionCompat = useSessionCompat;
15
14
  exports.signOutCompat = signOutCompat;
16
15
  const react_1 = require("better-auth/react");
@@ -20,20 +19,6 @@ exports.authClient = (0, react_1.createAuthClient)({
20
19
  });
21
20
  // Convenience exports
22
21
  exports.useSession = exports.authClient.useSession, exports.signIn = exports.authClient.signIn, exports.signOut = exports.authClient.signOut;
23
- /**
24
- * Sign in with a specific OAuth provider via direct redirect.
25
- *
26
- * better-auth exposes per-provider endpoints at /api/auth/sign-in/{provider}
27
- * (e.g. /api/auth/sign-in/google). The generic signIn.social() method POSTs
28
- * to /api/auth/sign-in/social which doesn't exist — use this instead.
29
- */
30
- function signInWithProvider(provider, callbackURL) {
31
- const params = new URLSearchParams();
32
- if (callbackURL)
33
- params.set('callbackURL', callbackURL);
34
- const qs = params.toString();
35
- window.location.href = `/api/auth/sign-in/${provider}${qs ? '?' + qs : ''}`;
36
- }
37
22
  /**
38
23
  * NextAuth-compatible useSession wrapper.
39
24
  *
@@ -44,7 +44,7 @@ function MobileNavDrawer({ isOpen, onClose, navItems, customSections, basePath =
44
44
  onSignIn();
45
45
  }
46
46
  else {
47
- (0, better_auth_client_1.signInWithProvider)('google', signInCallbackUrl);
47
+ better_auth_client_1.authClient.signIn.social({ provider: 'google', callbackURL: signInCallbackUrl });
48
48
  }
49
49
  };
50
50
  const handleSectionItemClick = (item) => {
@@ -28,13 +28,6 @@ export declare function getAuthInstance(): Promise<import("better-auth/types").A
28
28
  refreshCache: false;
29
29
  };
30
30
  };
31
- databaseHooks: {
32
- session: {
33
- create: {
34
- after: (session: any) => Promise<void>;
35
- };
36
- };
37
- };
38
31
  advanced: {
39
32
  cookiePrefix: string;
40
33
  cookies: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payez/next-mvp",
3
- "version": "4.0.23",
3
+ "version": "4.0.25",
4
4
  "sideEffects": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -58,9 +58,11 @@ export function createBetterAuthInstance(idpConfig: IDPClientConfig) {
58
58
  const appSlug = idpConfig.clientSlug || getAppSlug();
59
59
 
60
60
  // Resolve base URL: BETTER_AUTH_URL env > IDP config > localhost fallback
61
- const baseURL = process.env.BETTER_AUTH_URL
61
+ // Must include /api/auth since that's where the catch-all route is mounted
62
+ const rawBaseURL = process.env.BETTER_AUTH_URL
62
63
  || idpConfig.baseClientUrl
63
64
  || `http://localhost:${process.env.PORT || '3000'}`;
65
+ const baseURL = rawBaseURL.replace(/\/+$/, '') + '/api/auth';
64
66
 
65
67
  return betterAuth({
66
68
  baseURL,
@@ -70,8 +72,9 @@ export function createBetterAuthInstance(idpConfig: IDPClientConfig) {
70
72
 
71
73
  // Trust the app's own origin + any configured base URL
72
74
  trustedOrigins: [
75
+ rawBaseURL,
73
76
  baseURL,
74
- ...(idpConfig.baseClientUrl && idpConfig.baseClientUrl !== baseURL ? [idpConfig.baseClientUrl] : []),
77
+ ...(idpConfig.baseClientUrl ? [idpConfig.baseClientUrl] : []),
75
78
  'http://localhost:3000',
76
79
  'http://localhost:3400',
77
80
  'http://localhost:3600',
@@ -109,93 +112,6 @@ export function createBetterAuthInstance(idpConfig: IDPClientConfig) {
109
112
  },
110
113
  },
111
114
 
112
- // After social login, exchange Google identity for IDP tokens and store in Redis
113
- databaseHooks: {
114
- session: {
115
- create: {
116
- after: async (session: any) => {
117
- try {
118
- const userId = session.userId;
119
- const token = session.token;
120
- if (!userId || !token) return;
121
-
122
- // Look up user from Better Auth's memory/DB to get email
123
- // The user was just created/found by Better Auth during OAuth
124
- const baKey = `ba:${appSlug}:${token}`;
125
- const baRaw = await getRedis().get(baKey).catch(() => null);
126
- const baData = baRaw ? JSON.parse(baRaw) : null;
127
- const email = baData?.user?.email;
128
- const name = baData?.user?.name;
129
- const image = baData?.user?.image;
130
-
131
- if (!email) {
132
- console.warn('[BETTER_AUTH] Session created but no email found for IDP token exchange');
133
- return;
134
- }
135
-
136
- // Call IDP oauth-callback to get IDP tokens
137
- const idpUrl = process.env.IDP_URL || '';
138
- if (!idpUrl) {
139
- console.warn('[BETTER_AUTH] No IDP URL configured, skipping token exchange');
140
- return;
141
- }
142
-
143
- const oauthRes = await fetch(`${idpUrl}/api/ExternalAuth/oauth-callback`, {
144
- method: 'POST',
145
- headers: { 'Content-Type': 'application/json' },
146
- body: JSON.stringify({
147
- provider: 'google',
148
- provider_account_id: userId,
149
- email,
150
- name,
151
- image,
152
- client_id: idpConfig.clientSlug || String(idpConfig.clientId),
153
- }),
154
- });
155
-
156
- const oauthResText = await oauthRes.text();
157
- console.log('[BETTER_AUTH] IDP oauth-callback response:', oauthRes.status, oauthResText.substring(0, 500));
158
-
159
- if (!oauthRes.ok) {
160
- console.error('[BETTER_AUTH] IDP oauth-callback failed:', oauthRes.status);
161
- return;
162
- }
163
-
164
- let idpData: any;
165
- try { idpData = JSON.parse(oauthResText); } catch { return; }
166
- const result = idpData?.data?.result || idpData?.data || idpData;
167
-
168
- if (!result?.access_token) {
169
- console.warn('[BETTER_AUTH] IDP oauth-callback returned no access_token. Keys:', Object.keys(result || {}));
170
- return;
171
- }
172
-
173
- // Store IDP tokens in the BA Redis session
174
- if (baData) {
175
- const requiresTwoFactor = result.user?.requiresTwoFactor ?? result.requiresTwoFactor ?? false;
176
- baData.idpTokens = {
177
- idpAccessToken: result.access_token,
178
- idpRefreshToken: result.refresh_token,
179
- idpAccessTokenExpires: result.expires_in
180
- ? Date.now() + result.expires_in * 1000
181
- : Date.now() + 15 * 60 * 1000,
182
- userId: String(result.user?.user_id || result.user?.id || result.user_id || userId),
183
- email: result.user?.email || result.email || email,
184
- name: result.user?.full_name || result.user?.name || result.name || name,
185
- roles: result.user?.roles || result.roles || [],
186
- mfaVerified: !requiresTwoFactor,
187
- };
188
- await getRedis().setex(baKey, 7 * 24 * 60 * 60, JSON.stringify(baData));
189
- console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
190
- }
191
- } catch (err) {
192
- console.error('[BETTER_AUTH] Post-login IDP exchange failed:', err instanceof Error ? err.message : String(err));
193
- }
194
- },
195
- },
196
- },
197
- },
198
-
199
115
  // Cookie prefix must match slim-middleware expectations ({slug}.session-token)
200
116
  advanced: {
201
117
  cookiePrefix: appSlug,
@@ -269,3 +185,101 @@ export async function getBetterAuthHandler(): Promise<{ GET: (req: Request) => P
269
185
  const auth = await getBetterAuthInstance();
270
186
  return toNextJsHandler(auth);
271
187
  }
188
+
189
+ /**
190
+ * Exchange OAuth identity for IDP tokens and store in the BA Redis session.
191
+ *
192
+ * Call this from the OAuth callback route AFTER better-auth has processed the
193
+ * callback and created the session. Reads the session token from the Set-Cookie
194
+ * header of the response to find the BA Redis key.
195
+ *
196
+ * This replaces the old databaseHooks approach which doesn't fire in stateless mode.
197
+ */
198
+ export async function exchangeOAuthForIdpTokens(
199
+ sessionToken: string,
200
+ provider: string = 'google'
201
+ ): Promise<boolean> {
202
+ try {
203
+ const config = await getIDPClientConfig();
204
+ const appSlug = config.clientSlug || getAppSlug();
205
+ const baKey = `ba:${appSlug}:${sessionToken}`;
206
+
207
+ // Read the BA session from Redis
208
+ const baRaw = await getRedis().get(baKey).catch(() => null);
209
+ if (!baRaw) {
210
+ console.warn('[BETTER_AUTH] exchangeOAuthForIdpTokens: session not found in Redis for token', sessionToken.substring(0, 10));
211
+ return false;
212
+ }
213
+
214
+ const baData = JSON.parse(baRaw);
215
+ const email = baData?.user?.email;
216
+ const name = baData?.user?.name;
217
+ const image = baData?.user?.image;
218
+ const baUserId = baData?.session?.userId || baData?.user?.id;
219
+
220
+ if (!email) {
221
+ console.warn('[BETTER_AUTH] exchangeOAuthForIdpTokens: no email in session');
222
+ return false;
223
+ }
224
+
225
+ // Call IDP oauth-callback
226
+ const idpUrl = process.env.IDP_URL || '';
227
+ if (!idpUrl) {
228
+ console.warn('[BETTER_AUTH] No IDP_URL configured, skipping token exchange');
229
+ return false;
230
+ }
231
+
232
+ console.log('[BETTER_AUTH] Exchanging OAuth identity for IDP tokens:', email);
233
+
234
+ const oauthRes = await fetch(`${idpUrl}/api/ExternalAuth/oauth-callback`, {
235
+ method: 'POST',
236
+ headers: { 'Content-Type': 'application/json' },
237
+ body: JSON.stringify({
238
+ provider,
239
+ provider_account_id: baUserId,
240
+ email,
241
+ name,
242
+ image,
243
+ client_id: config.clientSlug || String(config.clientId),
244
+ }),
245
+ });
246
+
247
+ const oauthResText = await oauthRes.text();
248
+ console.log('[BETTER_AUTH] IDP oauth-callback response:', oauthRes.status, oauthResText.substring(0, 500));
249
+
250
+ if (!oauthRes.ok) {
251
+ console.error('[BETTER_AUTH] IDP oauth-callback failed:', oauthRes.status);
252
+ return false;
253
+ }
254
+
255
+ let idpData: any;
256
+ try { idpData = JSON.parse(oauthResText); } catch { return false; }
257
+ const result = idpData?.data?.result || idpData?.data || idpData;
258
+
259
+ if (!result?.access_token) {
260
+ console.warn('[BETTER_AUTH] IDP oauth-callback returned no access_token. Keys:', Object.keys(result || {}));
261
+ return false;
262
+ }
263
+
264
+ // Store IDP tokens in the BA Redis session
265
+ const requiresTwoFactor = result.user?.requiresTwoFactor ?? result.requiresTwoFactor ?? false;
266
+ baData.idpTokens = {
267
+ idpAccessToken: result.access_token,
268
+ idpRefreshToken: result.refresh_token,
269
+ idpAccessTokenExpires: result.expires_in
270
+ ? Date.now() + result.expires_in * 1000
271
+ : Date.now() + 15 * 60 * 1000,
272
+ userId: String(result.user?.user_id || result.user?.id || result.user_id || baUserId),
273
+ email: result.user?.email || result.email || email,
274
+ name: result.user?.full_name || result.user?.name || result.name || name,
275
+ roles: result.user?.roles || result.roles || [],
276
+ mfaVerified: !requiresTwoFactor,
277
+ };
278
+ await getRedis().setex(baKey, 7 * 24 * 60 * 60, JSON.stringify(baData));
279
+ console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
280
+ return true;
281
+ } catch (err) {
282
+ console.error('[BETTER_AUTH] IDP token exchange failed:', err instanceof Error ? err.message : String(err));
283
+ return false;
284
+ }
285
+ }
@@ -18,19 +18,6 @@ export const authClient = createAuthClient({
18
18
  // Convenience exports
19
19
  export const { useSession, signIn, signOut } = authClient;
20
20
 
21
- /**
22
- * Sign in with a specific OAuth provider via direct redirect.
23
- *
24
- * better-auth exposes per-provider endpoints at /api/auth/sign-in/{provider}
25
- * (e.g. /api/auth/sign-in/google). The generic signIn.social() method POSTs
26
- * to /api/auth/sign-in/social which doesn't exist — use this instead.
27
- */
28
- export function signInWithProvider(provider: string, callbackURL?: string) {
29
- const params = new URLSearchParams();
30
- if (callbackURL) params.set('callbackURL', callbackURL);
31
- const qs = params.toString();
32
- window.location.href = `/api/auth/sign-in/${provider}${qs ? '?' + qs : ''}`;
33
- }
34
21
 
35
22
  /**
36
23
  * NextAuth-compatible useSession wrapper.
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect, useCallback } from 'react';
4
- import { authClient, signInWithProvider } from '../../client/better-auth-client';
4
+ import { authClient } from '../../client/better-auth-client';
5
5
  import { usePathname } from 'next/navigation';
6
6
  import Image from 'next/image';
7
7
  import Link from 'next/link';
@@ -90,7 +90,7 @@ export function MobileNavDrawer({
90
90
  if (onSignIn) {
91
91
  onSignIn();
92
92
  } else {
93
- signInWithProvider('google', signInCallbackUrl);
93
+ authClient.signIn.social({ provider: 'google', callbackURL: signInCallbackUrl });
94
94
  }
95
95
  };
96
96