@payez/next-mvp 4.0.27 → 4.0.29
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.
|
@@ -108,3 +108,21 @@ export declare function getBetterAuthHandler(): Promise<{
|
|
|
108
108
|
* This replaces the old databaseHooks approach which doesn't fire in stateless mode.
|
|
109
109
|
*/
|
|
110
110
|
export declare function exchangeOAuthForIdpTokens(sessionToken: string, provider?: string): Promise<boolean>;
|
|
111
|
+
/**
|
|
112
|
+
* Create a production-ready GET handler for the auth catch-all route.
|
|
113
|
+
*
|
|
114
|
+
* Wraps better-auth's GET handler with:
|
|
115
|
+
* - OAuth state error recovery (redirects to login instead of error page)
|
|
116
|
+
* - IDP token exchange after successful OAuth callback
|
|
117
|
+
*
|
|
118
|
+
* Usage in host app:
|
|
119
|
+
* ```ts
|
|
120
|
+
* import { createAuthGetHandler, getBetterAuthHandler } from '@payez/next-mvp/auth/better-auth';
|
|
121
|
+
* export const GET = createAuthGetHandler('/account-auth/login');
|
|
122
|
+
* export async function POST(req: Request) {
|
|
123
|
+
* const ba = await getBetterAuthHandler();
|
|
124
|
+
* return ba!.POST(req);
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export declare function createAuthGetHandler(loginPath?: string): (request: Request) => Promise<Response>;
|
package/dist/auth/better-auth.js
CHANGED
|
@@ -17,6 +17,7 @@ exports.isBetterAuthEnabled = isBetterAuthEnabled;
|
|
|
17
17
|
exports.getBetterAuthInstance = getBetterAuthInstance;
|
|
18
18
|
exports.getBetterAuthHandler = getBetterAuthHandler;
|
|
19
19
|
exports.exchangeOAuthForIdpTokens = exchangeOAuthForIdpTokens;
|
|
20
|
+
exports.createAuthGetHandler = createAuthGetHandler;
|
|
20
21
|
require("server-only");
|
|
21
22
|
const better_auth_1 = require("better-auth");
|
|
22
23
|
const next_js_1 = require("better-auth/next-js");
|
|
@@ -257,3 +258,64 @@ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
|
|
|
257
258
|
return false;
|
|
258
259
|
}
|
|
259
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Create a production-ready GET handler for the auth catch-all route.
|
|
263
|
+
*
|
|
264
|
+
* Wraps better-auth's GET handler with:
|
|
265
|
+
* - OAuth state error recovery (redirects to login instead of error page)
|
|
266
|
+
* - IDP token exchange after successful OAuth callback
|
|
267
|
+
*
|
|
268
|
+
* Usage in host app:
|
|
269
|
+
* ```ts
|
|
270
|
+
* import { createAuthGetHandler, getBetterAuthHandler } from '@payez/next-mvp/auth/better-auth';
|
|
271
|
+
* export const GET = createAuthGetHandler('/account-auth/login');
|
|
272
|
+
* export async function POST(req: Request) {
|
|
273
|
+
* const ba = await getBetterAuthHandler();
|
|
274
|
+
* return ba!.POST(req);
|
|
275
|
+
* }
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
function createAuthGetHandler(loginPath = '/account-auth/login') {
|
|
279
|
+
return async function GET(request) {
|
|
280
|
+
const ba = await getBetterAuthHandler();
|
|
281
|
+
if (!ba) {
|
|
282
|
+
return new Response('Auth handler not configured', { status: 500 });
|
|
283
|
+
}
|
|
284
|
+
const response = await ba.GET(request);
|
|
285
|
+
// Intercept auth errors (state mismatch, expired cookies) — redirect to login cleanly
|
|
286
|
+
if (response.status === 302) {
|
|
287
|
+
const location = response.headers.get('location') || '';
|
|
288
|
+
if (location.includes('/api/auth/error') || location.includes('please_restart')) {
|
|
289
|
+
console.warn('[BETTER_AUTH] OAuth state error, redirecting to login');
|
|
290
|
+
return Response.redirect(new URL(loginPath, request.url), 302);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// After successful OAuth callback: exchange Google identity for IDP tokens
|
|
294
|
+
const url = new URL(request.url);
|
|
295
|
+
if (url.pathname.includes('/callback/') && response.status === 302) {
|
|
296
|
+
try {
|
|
297
|
+
const auth = await getBetterAuthInstance();
|
|
298
|
+
if (auth?.api?.getSession) {
|
|
299
|
+
const setCookies = response.headers.getSetCookie?.() || [];
|
|
300
|
+
const cookieHeader = setCookies
|
|
301
|
+
.map((c) => c.split(';')[0])
|
|
302
|
+
.join('; ');
|
|
303
|
+
const headers = new Headers();
|
|
304
|
+
headers.set('cookie', cookieHeader);
|
|
305
|
+
const session = await auth.api.getSession({ headers });
|
|
306
|
+
if (session?.session?.token) {
|
|
307
|
+
console.log('[BETTER_AUTH] Got session token from callback:', session.session.token.substring(0, 10), '| email:', session.user?.email);
|
|
308
|
+
await exchangeOAuthForIdpTokens(session.session.token);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
console.warn('[BETTER_AUTH] Could not get session after OAuth callback');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
console.error('[BETTER_AUTH] IDP token exchange failed:', err.message);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return response;
|
|
320
|
+
};
|
|
321
|
+
}
|
|
@@ -146,5 +146,5 @@ function EnhancedProfilePage() {
|
|
|
146
146
|
contact_info.preferred_contact_method.charAt(0).toUpperCase() +
|
|
147
147
|
contact_info.preferred_contact_method.slice(1) : undefined, isDarkMode: isDarkMode })] }) }), (0, jsx_runtime_1.jsx)(EditableSection, { title: "Address", isDarkMode: isDarkMode, children: address?.address_line_1 ? ((0, jsx_runtime_1.jsxs)("div", { className: textPrimary, children: [(0, jsx_runtime_1.jsx)("p", { children: address.address_line_1 }), address.address_line_2 && (0, jsx_runtime_1.jsx)("p", { children: address.address_line_2 }), (0, jsx_runtime_1.jsx)("p", { children: [address.city, address.state_name, address.postal_code]
|
|
148
148
|
.filter(Boolean)
|
|
149
|
-
.join(', ') }), (0, jsx_runtime_1.jsx)("p", { children: address.country_name || address.country_code })] })) : ((0, jsx_runtime_1.jsx)("p", { className: textMuted, children: "No address on file" })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-center gap-6 pt-4", children: [(0, jsx_runtime_1.jsx)("a", { href: "/account/security", className: `text-sm ${isDarkMode ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`, children: "Security Settings \u2192" }), (0, jsx_runtime_1.jsx)("a", { href: "/account/settings", className: `text-sm ${isDarkMode ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`, children: "Preferences \u2192" })] })] }) }));
|
|
149
|
+
.join(', ') }), (0, jsx_runtime_1.jsx)("p", { children: address.country_name || address.country_code })] })) : ((0, jsx_runtime_1.jsx)("p", { className: textMuted, children: "No address on file" })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap justify-center gap-6 pt-4", children: [(0, jsx_runtime_1.jsx)("a", { href: "/account/subscription", className: `text-sm ${isDarkMode ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`, children: "Subscription & Billing \u2192" }), (0, jsx_runtime_1.jsx)("a", { href: "/account/security", className: `text-sm ${isDarkMode ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`, children: "Security Settings \u2192" }), (0, jsx_runtime_1.jsx)("a", { href: "/account/settings", className: `text-sm ${isDarkMode ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`, children: "Preferences \u2192" })] })] }) }));
|
|
150
150
|
}
|
package/package.json
CHANGED
package/src/auth/better-auth.ts
CHANGED
|
@@ -283,3 +283,69 @@ export async function exchangeOAuthForIdpTokens(
|
|
|
283
283
|
return false;
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Create a production-ready GET handler for the auth catch-all route.
|
|
289
|
+
*
|
|
290
|
+
* Wraps better-auth's GET handler with:
|
|
291
|
+
* - OAuth state error recovery (redirects to login instead of error page)
|
|
292
|
+
* - IDP token exchange after successful OAuth callback
|
|
293
|
+
*
|
|
294
|
+
* Usage in host app:
|
|
295
|
+
* ```ts
|
|
296
|
+
* import { createAuthGetHandler, getBetterAuthHandler } from '@payez/next-mvp/auth/better-auth';
|
|
297
|
+
* export const GET = createAuthGetHandler('/account-auth/login');
|
|
298
|
+
* export async function POST(req: Request) {
|
|
299
|
+
* const ba = await getBetterAuthHandler();
|
|
300
|
+
* return ba!.POST(req);
|
|
301
|
+
* }
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
export function createAuthGetHandler(loginPath: string = '/account-auth/login') {
|
|
305
|
+
return async function GET(request: Request): Promise<Response> {
|
|
306
|
+
const ba = await getBetterAuthHandler();
|
|
307
|
+
if (!ba) {
|
|
308
|
+
return new Response('Auth handler not configured', { status: 500 });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const response = await ba.GET(request);
|
|
312
|
+
|
|
313
|
+
// Intercept auth errors (state mismatch, expired cookies) — redirect to login cleanly
|
|
314
|
+
if (response.status === 302) {
|
|
315
|
+
const location = response.headers.get('location') || '';
|
|
316
|
+
if (location.includes('/api/auth/error') || location.includes('please_restart')) {
|
|
317
|
+
console.warn('[BETTER_AUTH] OAuth state error, redirecting to login');
|
|
318
|
+
return Response.redirect(new URL(loginPath, request.url), 302);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// After successful OAuth callback: exchange Google identity for IDP tokens
|
|
323
|
+
const url = new URL(request.url);
|
|
324
|
+
if (url.pathname.includes('/callback/') && response.status === 302) {
|
|
325
|
+
try {
|
|
326
|
+
const auth = await getBetterAuthInstance();
|
|
327
|
+
if (auth?.api?.getSession) {
|
|
328
|
+
const setCookies = response.headers.getSetCookie?.() || [];
|
|
329
|
+
const cookieHeader = setCookies
|
|
330
|
+
.map((c: string) => c.split(';')[0])
|
|
331
|
+
.join('; ');
|
|
332
|
+
|
|
333
|
+
const headers = new Headers();
|
|
334
|
+
headers.set('cookie', cookieHeader);
|
|
335
|
+
|
|
336
|
+
const session = await auth.api.getSession({ headers });
|
|
337
|
+
if (session?.session?.token) {
|
|
338
|
+
console.log('[BETTER_AUTH] Got session token from callback:', session.session.token.substring(0, 10), '| email:', session.user?.email);
|
|
339
|
+
await exchangeOAuthForIdpTokens(session.session.token);
|
|
340
|
+
} else {
|
|
341
|
+
console.warn('[BETTER_AUTH] Could not get session after OAuth callback');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
} catch (err: any) {
|
|
345
|
+
console.error('[BETTER_AUTH] IDP token exchange failed:', err.message);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return response;
|
|
350
|
+
};
|
|
351
|
+
}
|
|
@@ -459,7 +459,13 @@ export default function EnhancedProfilePage() {
|
|
|
459
459
|
</EditableSection>
|
|
460
460
|
|
|
461
461
|
{/* Quick Links */}
|
|
462
|
-
<div className="flex justify-center gap-6 pt-4">
|
|
462
|
+
<div className="flex flex-wrap justify-center gap-6 pt-4">
|
|
463
|
+
<a
|
|
464
|
+
href="/account/subscription"
|
|
465
|
+
className={`text-sm ${isDarkMode ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`}
|
|
466
|
+
>
|
|
467
|
+
Subscription & Billing →
|
|
468
|
+
</a>
|
|
463
469
|
<a
|
|
464
470
|
href="/account/security"
|
|
465
471
|
className={`text-sm ${isDarkMode ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-700'}`}
|