@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.
- package/dist/auth/better-auth.d.ts +7 -0
- package/dist/auth/better-auth.js +82 -0
- package/dist/lib/token-lifecycle.js +20 -21
- package/dist/server/auth.d.ts +7 -0
- package/package.json +1 -1
- package/src/auth/better-auth.ts +85 -0
- package/src/lib/token-lifecycle.ts +21 -21
|
@@ -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: {
|
package/dist/auth/better-auth.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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 {
|
package/dist/server/auth.d.ts
CHANGED
|
@@ -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
package/src/auth/better-auth.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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) {
|