@payez/next-mvp 4.0.10 → 4.0.12

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,6 +45,13 @@ 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
+ };
48
55
  advanced: {
49
56
  cookiePrefix: string;
50
57
  cookies: {
@@ -100,6 +100,88 @@ function createBetterAuthInstance(idpConfig) {
100
100
  refreshCache: false,
101
101
  },
102
102
  },
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.INTERNAL_IDP_URL || 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?.result || 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
+ baData.idpTokens = {
164
+ idpAccessToken: result.access_token,
165
+ idpRefreshToken: result.refresh_token,
166
+ idpAccessTokenExpires: result.expires_in
167
+ ? Date.now() + result.expires_in * 1000
168
+ : Date.now() + 15 * 60 * 1000,
169
+ userId: String(result.user?.id || result.id || userId),
170
+ email: result.user?.email || result.email || email,
171
+ name: result.user?.name || result.name || name,
172
+ roles: result.user?.roles || result.roles || [],
173
+ };
174
+ await (0, redis_1.getRedis)().setex(baKey, 7 * 24 * 60 * 60, JSON.stringify(baData));
175
+ console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
176
+ }
177
+ }
178
+ catch (err) {
179
+ console.error('[BETTER_AUTH] Post-login IDP exchange failed:', err instanceof Error ? err.message : String(err));
180
+ }
181
+ },
182
+ },
183
+ },
184
+ },
103
185
  // Cookie prefix must match slim-middleware expectations ({slug}.session-token)
104
186
  advanced: {
105
187
  cookiePrefix: appSlug,
@@ -231,15 +231,16 @@ async function ensureFreshToken(request) {
231
231
  const baRaw = await (0, redis_1.getRedis)().get(baKey);
232
232
  if (baRaw) {
233
233
  const baSession = JSON.parse(baRaw);
234
- // Map Better Auth session to SessionData
234
+ const idpTokens = baSession.idpTokens;
235
235
  sessionData = {
236
- userId: baSession.user?.id || betterAuthSession.user?.id || '',
237
- email: baSession.user?.email || betterAuthSession.user?.email || '',
238
- name: baSession.user?.name || betterAuthSession.user?.name,
239
- roles: [],
240
- idpAccessTokenExpires: baSession.session?.expiresAt
241
- ? new Date(baSession.session.expiresAt).getTime()
242
- : Date.now() + 24 * 60 * 60 * 1000,
236
+ userId: idpTokens?.userId || baSession.user?.id || betterAuthSession.user?.id || '',
237
+ email: idpTokens?.email || baSession.user?.email || betterAuthSession.user?.email || '',
238
+ name: idpTokens?.name || baSession.user?.name || betterAuthSession.user?.name,
239
+ roles: idpTokens?.roles || [],
240
+ idpAccessToken: idpTokens?.idpAccessToken,
241
+ idpRefreshToken: idpTokens?.idpRefreshToken,
242
+ idpAccessTokenExpires: idpTokens?.idpAccessTokenExpires
243
+ || (baSession.session?.expiresAt ? new Date(baSession.session.expiresAt).getTime() : Date.now() + 24 * 60 * 60 * 1000),
243
244
  mfaVerified: true,
244
245
  oauthProvider: 'google',
245
246
  };
@@ -247,19 +248,17 @@ async function ensureFreshToken(request) {
247
248
  }
248
249
  catch { /* Redis unavailable */ }
249
250
  }
250
- if (!sessionData) {
251
- // Last resort: build from Better Auth in-memory session
252
- if (betterAuthSession.user) {
253
- sessionData = {
254
- userId: betterAuthSession.user.id || '',
255
- email: betterAuthSession.user.email || '',
256
- name: betterAuthSession.user.name,
257
- roles: [],
258
- idpAccessTokenExpires: Date.now() + 24 * 60 * 60 * 1000,
259
- mfaVerified: true,
260
- oauthProvider: 'google',
261
- };
262
- }
251
+ if (!sessionData && betterAuthSession.user) {
252
+ // Last resort: build from Better Auth in-memory session (no IDP tokens)
253
+ sessionData = {
254
+ userId: betterAuthSession.user.id || '',
255
+ email: betterAuthSession.user.email || '',
256
+ name: betterAuthSession.user.name,
257
+ roles: [],
258
+ idpAccessTokenExpires: Date.now() + 24 * 60 * 60 * 1000,
259
+ mfaVerified: true,
260
+ oauthProvider: 'google',
261
+ };
263
262
  }
264
263
  if (!sessionData) {
265
264
  return {
@@ -28,6 +28,13 @@ 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
+ };
31
38
  advanced: {
32
39
  cookiePrefix: string;
33
40
  cookies: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payez/next-mvp",
3
- "version": "4.0.10",
3
+ "version": "4.0.12",
4
4
  "sideEffects": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -109,6 +109,91 @@ export function createBetterAuthInstance(idpConfig: IDPClientConfig) {
109
109
  },
110
110
  },
111
111
 
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.INTERNAL_IDP_URL || 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?.result || 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
+ baData.idpTokens = {
176
+ idpAccessToken: result.access_token,
177
+ idpRefreshToken: result.refresh_token,
178
+ idpAccessTokenExpires: result.expires_in
179
+ ? Date.now() + result.expires_in * 1000
180
+ : Date.now() + 15 * 60 * 1000,
181
+ userId: String(result.user?.id || result.id || userId),
182
+ email: result.user?.email || result.email || email,
183
+ name: result.user?.name || result.name || name,
184
+ roles: result.user?.roles || result.roles || [],
185
+ };
186
+ await getRedis().setex(baKey, 7 * 24 * 60 * 60, JSON.stringify(baData));
187
+ console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
188
+ }
189
+ } catch (err) {
190
+ console.error('[BETTER_AUTH] Post-login IDP exchange failed:', err instanceof Error ? err.message : String(err));
191
+ }
192
+ },
193
+ },
194
+ },
195
+ },
196
+
112
197
  // Cookie prefix must match slim-middleware expectations ({slug}.session-token)
113
198
  advanced: {
114
199
  cookiePrefix: appSlug,
@@ -294,15 +294,17 @@ export async function ensureFreshToken(
294
294
  const baRaw = await getRedis().get(baKey);
295
295
  if (baRaw) {
296
296
  const baSession = JSON.parse(baRaw);
297
- // Map Better Auth session to SessionData
297
+ const idpTokens = baSession.idpTokens;
298
+
298
299
  sessionData = {
299
- userId: baSession.user?.id || betterAuthSession.user?.id || '',
300
- email: baSession.user?.email || betterAuthSession.user?.email || '',
301
- name: baSession.user?.name || betterAuthSession.user?.name,
302
- roles: [],
303
- idpAccessTokenExpires: baSession.session?.expiresAt
304
- ? new Date(baSession.session.expiresAt).getTime()
305
- : Date.now() + 24 * 60 * 60 * 1000,
300
+ userId: idpTokens?.userId || baSession.user?.id || betterAuthSession.user?.id || '',
301
+ email: idpTokens?.email || baSession.user?.email || betterAuthSession.user?.email || '',
302
+ name: idpTokens?.name || baSession.user?.name || betterAuthSession.user?.name,
303
+ roles: idpTokens?.roles || [],
304
+ idpAccessToken: idpTokens?.idpAccessToken,
305
+ idpRefreshToken: idpTokens?.idpRefreshToken,
306
+ idpAccessTokenExpires: idpTokens?.idpAccessTokenExpires
307
+ || (baSession.session?.expiresAt ? new Date(baSession.session.expiresAt).getTime() : Date.now() + 24 * 60 * 60 * 1000),
306
308
  mfaVerified: true,
307
309
  oauthProvider: 'google',
308
310
  } as SessionData;
@@ -310,19 +312,17 @@ export async function ensureFreshToken(
310
312
  } catch { /* Redis unavailable */ }
311
313
  }
312
314
 
313
- if (!sessionData) {
314
- // Last resort: build from Better Auth in-memory session
315
- if (betterAuthSession.user) {
316
- sessionData = {
317
- userId: betterAuthSession.user.id || '',
318
- email: betterAuthSession.user.email || '',
319
- name: betterAuthSession.user.name,
320
- roles: [],
321
- idpAccessTokenExpires: Date.now() + 24 * 60 * 60 * 1000,
322
- mfaVerified: true,
323
- oauthProvider: 'google',
324
- } as SessionData;
325
- }
315
+ if (!sessionData && betterAuthSession.user) {
316
+ // Last resort: build from Better Auth in-memory session (no IDP tokens)
317
+ sessionData = {
318
+ userId: betterAuthSession.user.id || '',
319
+ email: betterAuthSession.user.email || '',
320
+ name: betterAuthSession.user.name,
321
+ roles: [],
322
+ idpAccessTokenExpires: Date.now() + 24 * 60 * 60 * 1000,
323
+ mfaVerified: true,
324
+ oauthProvider: 'google',
325
+ } as SessionData;
326
326
  }
327
327
 
328
328
  if (!sessionData) {