@payez/next-mvp 4.0.24 → 4.0.26

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");
@@ -103,90 +104,6 @@ function createBetterAuthInstance(idpConfig) {
103
104
  refreshCache: false,
104
105
  },
105
106
  },
106
- // After social login, exchange Google identity for IDP tokens and store in Redis
107
- databaseHooks: {
108
- session: {
109
- create: {
110
- after: async (session) => {
111
- try {
112
- const userId = session.userId;
113
- const token = session.token;
114
- if (!userId || !token)
115
- return;
116
- // Look up user from Better Auth's memory/DB to get email
117
- // The user was just created/found by Better Auth during OAuth
118
- const baKey = `ba:${appSlug}:${token}`;
119
- const baRaw = await (0, redis_1.getRedis)().get(baKey).catch(() => null);
120
- const baData = baRaw ? JSON.parse(baRaw) : null;
121
- const email = baData?.user?.email;
122
- const name = baData?.user?.name;
123
- const image = baData?.user?.image;
124
- if (!email) {
125
- console.warn('[BETTER_AUTH] Session created but no email found for IDP token exchange');
126
- return;
127
- }
128
- // Call IDP oauth-callback to get IDP tokens
129
- const idpUrl = process.env.IDP_URL || '';
130
- if (!idpUrl) {
131
- console.warn('[BETTER_AUTH] No IDP URL configured, skipping token exchange');
132
- return;
133
- }
134
- const oauthRes = await fetch(`${idpUrl}/api/ExternalAuth/oauth-callback`, {
135
- method: 'POST',
136
- headers: { 'Content-Type': 'application/json' },
137
- body: JSON.stringify({
138
- provider: 'google',
139
- provider_account_id: userId,
140
- email,
141
- name,
142
- image,
143
- client_id: idpConfig.clientSlug || String(idpConfig.clientId),
144
- }),
145
- });
146
- const oauthResText = await oauthRes.text();
147
- console.log('[BETTER_AUTH] IDP oauth-callback response:', oauthRes.status, oauthResText.substring(0, 500));
148
- if (!oauthRes.ok) {
149
- console.error('[BETTER_AUTH] IDP oauth-callback failed:', oauthRes.status);
150
- return;
151
- }
152
- let idpData;
153
- try {
154
- idpData = JSON.parse(oauthResText);
155
- }
156
- catch {
157
- return;
158
- }
159
- const result = idpData?.data?.result || idpData?.data || idpData;
160
- if (!result?.access_token) {
161
- console.warn('[BETTER_AUTH] IDP oauth-callback returned no access_token. Keys:', Object.keys(result || {}));
162
- return;
163
- }
164
- // Store IDP tokens in the BA Redis session
165
- if (baData) {
166
- const requiresTwoFactor = result.user?.requiresTwoFactor ?? result.requiresTwoFactor ?? false;
167
- baData.idpTokens = {
168
- idpAccessToken: result.access_token,
169
- idpRefreshToken: result.refresh_token,
170
- idpAccessTokenExpires: result.expires_in
171
- ? Date.now() + result.expires_in * 1000
172
- : Date.now() + 15 * 60 * 1000,
173
- userId: String(result.user?.user_id || result.user?.id || result.user_id || userId),
174
- email: result.user?.email || result.email || email,
175
- name: result.user?.full_name || result.user?.name || result.name || name,
176
- roles: result.user?.roles || result.roles || [],
177
- mfaVerified: !requiresTwoFactor,
178
- };
179
- await (0, redis_1.getRedis)().setex(baKey, 7 * 24 * 60 * 60, JSON.stringify(baData));
180
- console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
181
- }
182
- }
183
- catch (err) {
184
- console.error('[BETTER_AUTH] Post-login IDP exchange failed:', err instanceof Error ? err.message : String(err));
185
- }
186
- },
187
- },
188
- },
189
- },
190
107
  // Cookie prefix must match slim-middleware expectations ({slug}.session-token)
191
108
  advanced: {
192
109
  cookiePrefix: appSlug,
@@ -251,3 +168,92 @@ async function getBetterAuthHandler() {
251
168
  const auth = await getBetterAuthInstance();
252
169
  return (0, next_js_2.toNextJsHandler)(auth);
253
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
+ }
@@ -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: {
@@ -91,7 +91,7 @@ async function tryBetterAuthSession(requestCookies) {
91
91
  if (!result?.session || !result?.user) {
92
92
  return null;
93
93
  }
94
- // Read IDP tokens from BA Redis session (stored by post-login hook)
94
+ // Read IDP tokens from BA Redis session (stored by callback route after OAuth)
95
95
  let idpTokens = null;
96
96
  try {
97
97
  const { getRedis } = await Promise.resolve().then(() => __importStar(require('../lib/redis')));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payez/next-mvp",
3
- "version": "4.0.24",
3
+ "version": "4.0.26",
4
4
  "sideEffects": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -112,93 +112,6 @@ export function createBetterAuthInstance(idpConfig: IDPClientConfig) {
112
112
  },
113
113
  },
114
114
 
115
- // After social login, exchange Google identity for IDP tokens and store in Redis
116
- databaseHooks: {
117
- session: {
118
- create: {
119
- after: async (session: any) => {
120
- try {
121
- const userId = session.userId;
122
- const token = session.token;
123
- if (!userId || !token) return;
124
-
125
- // Look up user from Better Auth's memory/DB to get email
126
- // The user was just created/found by Better Auth during OAuth
127
- const baKey = `ba:${appSlug}:${token}`;
128
- const baRaw = await getRedis().get(baKey).catch(() => null);
129
- const baData = baRaw ? JSON.parse(baRaw) : null;
130
- const email = baData?.user?.email;
131
- const name = baData?.user?.name;
132
- const image = baData?.user?.image;
133
-
134
- if (!email) {
135
- console.warn('[BETTER_AUTH] Session created but no email found for IDP token exchange');
136
- return;
137
- }
138
-
139
- // Call IDP oauth-callback to get IDP tokens
140
- const idpUrl = process.env.IDP_URL || '';
141
- if (!idpUrl) {
142
- console.warn('[BETTER_AUTH] No IDP URL configured, skipping token exchange');
143
- return;
144
- }
145
-
146
- const oauthRes = await fetch(`${idpUrl}/api/ExternalAuth/oauth-callback`, {
147
- method: 'POST',
148
- headers: { 'Content-Type': 'application/json' },
149
- body: JSON.stringify({
150
- provider: 'google',
151
- provider_account_id: userId,
152
- email,
153
- name,
154
- image,
155
- client_id: idpConfig.clientSlug || String(idpConfig.clientId),
156
- }),
157
- });
158
-
159
- const oauthResText = await oauthRes.text();
160
- console.log('[BETTER_AUTH] IDP oauth-callback response:', oauthRes.status, oauthResText.substring(0, 500));
161
-
162
- if (!oauthRes.ok) {
163
- console.error('[BETTER_AUTH] IDP oauth-callback failed:', oauthRes.status);
164
- return;
165
- }
166
-
167
- let idpData: any;
168
- try { idpData = JSON.parse(oauthResText); } catch { return; }
169
- const result = idpData?.data?.result || idpData?.data || idpData;
170
-
171
- if (!result?.access_token) {
172
- console.warn('[BETTER_AUTH] IDP oauth-callback returned no access_token. Keys:', Object.keys(result || {}));
173
- return;
174
- }
175
-
176
- // Store IDP tokens in the BA Redis session
177
- if (baData) {
178
- const requiresTwoFactor = result.user?.requiresTwoFactor ?? result.requiresTwoFactor ?? false;
179
- baData.idpTokens = {
180
- idpAccessToken: result.access_token,
181
- idpRefreshToken: result.refresh_token,
182
- idpAccessTokenExpires: result.expires_in
183
- ? Date.now() + result.expires_in * 1000
184
- : Date.now() + 15 * 60 * 1000,
185
- userId: String(result.user?.user_id || result.user?.id || result.user_id || userId),
186
- email: result.user?.email || result.email || email,
187
- name: result.user?.full_name || result.user?.name || result.name || name,
188
- roles: result.user?.roles || result.roles || [],
189
- mfaVerified: !requiresTwoFactor,
190
- };
191
- await getRedis().setex(baKey, 7 * 24 * 60 * 60, JSON.stringify(baData));
192
- console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
193
- }
194
- } catch (err) {
195
- console.error('[BETTER_AUTH] Post-login IDP exchange failed:', err instanceof Error ? err.message : String(err));
196
- }
197
- },
198
- },
199
- },
200
- },
201
-
202
115
  // Cookie prefix must match slim-middleware expectations ({slug}.session-token)
203
116
  advanced: {
204
117
  cookiePrefix: appSlug,
@@ -272,3 +185,101 @@ export async function getBetterAuthHandler(): Promise<{ GET: (req: Request) => P
272
185
  const auth = await getBetterAuthInstance();
273
186
  return toNextJsHandler(auth);
274
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
+ }
@@ -66,7 +66,7 @@ async function tryBetterAuthSession(
66
66
  return null;
67
67
  }
68
68
 
69
- // Read IDP tokens from BA Redis session (stored by post-login hook)
69
+ // Read IDP tokens from BA Redis session (stored by callback route after OAuth)
70
70
  let idpTokens: any = null;
71
71
  try {
72
72
  const { getRedis } = await import('../lib/redis');