@payez/next-mvp 4.0.10 → 4.0.11

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,80 @@ 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
+ if (!oauthRes.ok) {
144
+ console.error('[BETTER_AUTH] IDP oauth-callback failed:', oauthRes.status, await oauthRes.text().catch(() => ''));
145
+ return;
146
+ }
147
+ const idpData = await oauthRes.json();
148
+ const result = idpData?.data?.result || idpData?.result || idpData;
149
+ if (!result?.access_token) {
150
+ console.warn('[BETTER_AUTH] IDP oauth-callback returned no access_token');
151
+ return;
152
+ }
153
+ // Store IDP tokens in the BA Redis session
154
+ if (baData) {
155
+ baData.idpTokens = {
156
+ idpAccessToken: result.access_token,
157
+ idpRefreshToken: result.refresh_token,
158
+ idpAccessTokenExpires: result.expires_in
159
+ ? Date.now() + result.expires_in * 1000
160
+ : Date.now() + 15 * 60 * 1000,
161
+ userId: String(result.user?.id || result.id || userId),
162
+ email: result.user?.email || result.email || email,
163
+ name: result.user?.name || result.name || name,
164
+ roles: result.user?.roles || result.roles || [],
165
+ };
166
+ await (0, redis_1.getRedis)().setex(baKey, 7 * 24 * 60 * 60, JSON.stringify(baData));
167
+ console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
168
+ }
169
+ }
170
+ catch (err) {
171
+ console.error('[BETTER_AUTH] Post-login IDP exchange failed:', err instanceof Error ? err.message : String(err));
172
+ }
173
+ },
174
+ },
175
+ },
176
+ },
103
177
  // Cookie prefix must match slim-middleware expectations ({slug}.session-token)
104
178
  advanced: {
105
179
  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.11",
4
4
  "sideEffects": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -109,6 +109,87 @@ 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
+ if (!oauthRes.ok) {
157
+ console.error('[BETTER_AUTH] IDP oauth-callback failed:', oauthRes.status, await oauthRes.text().catch(() => ''));
158
+ return;
159
+ }
160
+
161
+ const idpData = await oauthRes.json();
162
+ const result = idpData?.data?.result || idpData?.result || idpData;
163
+
164
+ if (!result?.access_token) {
165
+ console.warn('[BETTER_AUTH] IDP oauth-callback returned no access_token');
166
+ return;
167
+ }
168
+
169
+ // Store IDP tokens in the BA Redis session
170
+ if (baData) {
171
+ baData.idpTokens = {
172
+ idpAccessToken: result.access_token,
173
+ idpRefreshToken: result.refresh_token,
174
+ idpAccessTokenExpires: result.expires_in
175
+ ? Date.now() + result.expires_in * 1000
176
+ : Date.now() + 15 * 60 * 1000,
177
+ userId: String(result.user?.id || result.id || userId),
178
+ email: result.user?.email || result.email || email,
179
+ name: result.user?.name || result.name || name,
180
+ roles: result.user?.roles || result.roles || [],
181
+ };
182
+ await getRedis().setex(baKey, 7 * 24 * 60 * 60, JSON.stringify(baData));
183
+ console.log('[BETTER_AUTH] IDP tokens stored in session for', email);
184
+ }
185
+ } catch (err) {
186
+ console.error('[BETTER_AUTH] Post-login IDP exchange failed:', err instanceof Error ? err.message : String(err));
187
+ }
188
+ },
189
+ },
190
+ },
191
+ },
192
+
112
193
  // Cookie prefix must match slim-middleware expectations ({slug}.session-token)
113
194
  advanced: {
114
195
  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) {