@payez/next-mvp 4.0.6 → 4.0.7
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.
|
@@ -66,6 +66,13 @@ export declare function createBetterAuthInstance(idpConfig: IDPClientConfig): im
|
|
|
66
66
|
* Better Auth is always enabled (NextAuth removed in 4.0).
|
|
67
67
|
*/
|
|
68
68
|
export declare function isBetterAuthEnabled(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Get Better Auth Next.js route handlers (GET, POST).
|
|
71
|
+
* Initializes Better Auth from IDP config on first call, caches the instance.
|
|
72
|
+
*/
|
|
73
|
+
declare let cachedInstance: any;
|
|
74
|
+
export { cachedInstance as __betterAuthInstance };
|
|
75
|
+
export declare function getBetterAuthInstance(): Promise<any>;
|
|
69
76
|
/**
|
|
70
77
|
* Get flag-gated auth handler for Next.js route.
|
|
71
78
|
*
|
package/dist/auth/better-auth.js
CHANGED
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
* @see BETTER-AUTH-MIGRATION-SPEC.md
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.__betterAuthInstance = void 0;
|
|
13
14
|
exports.buildBetterAuthProviders = buildBetterAuthProviders;
|
|
14
15
|
exports.createBetterAuthInstance = createBetterAuthInstance;
|
|
15
16
|
exports.isBetterAuthEnabled = isBetterAuthEnabled;
|
|
17
|
+
exports.getBetterAuthInstance = getBetterAuthInstance;
|
|
16
18
|
exports.getBetterAuthHandler = getBetterAuthHandler;
|
|
17
19
|
require("server-only");
|
|
18
20
|
const better_auth_1 = require("better-auth");
|
|
@@ -96,6 +98,7 @@ function isBetterAuthEnabled() {
|
|
|
96
98
|
*/
|
|
97
99
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
100
|
let cachedInstance = null;
|
|
101
|
+
exports.__betterAuthInstance = cachedInstance;
|
|
99
102
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
103
|
let initPromise = null;
|
|
101
104
|
async function getBetterAuthInstance() {
|
|
@@ -104,7 +107,7 @@ async function getBetterAuthInstance() {
|
|
|
104
107
|
if (!initPromise) {
|
|
105
108
|
initPromise = (0, idp_client_config_1.getIDPClientConfig)().then(config => {
|
|
106
109
|
const instance = createBetterAuthInstance(config);
|
|
107
|
-
cachedInstance = instance;
|
|
110
|
+
exports.__betterAuthInstance = cachedInstance = instance;
|
|
108
111
|
console.log('[BETTER_AUTH] Instance created for', config.clientSlug || config.clientId);
|
|
109
112
|
return instance;
|
|
110
113
|
});
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Server-Side Session Decoder
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Zero HTTP self-fetches. Direct Redis reads only.
|
|
4
|
+
* Uses Better Auth's server-side session API to get the current session.
|
|
5
|
+
* Falls back to legacy JWT + Redis path if Better Auth session not found.
|
|
8
6
|
*/
|
|
9
7
|
import 'server-only';
|
|
10
8
|
import { type JWTPayload } from 'jose';
|
|
@@ -17,8 +15,8 @@ export interface DecodedSession {
|
|
|
17
15
|
};
|
|
18
16
|
}
|
|
19
17
|
/**
|
|
20
|
-
* Decode the session from cookies
|
|
21
|
-
*
|
|
18
|
+
* Decode the session from cookies.
|
|
19
|
+
* Tries Better Auth first, falls back to legacy JWT + Redis.
|
|
22
20
|
*
|
|
23
21
|
* @param requestCookies Optional cookie getter for API route context (NextRequest.cookies).
|
|
24
22
|
* If omitted, uses next/headers cookies() for server components.
|
|
@@ -2,11 +2,42 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Server-Side Session Decoder
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Zero HTTP self-fetches. Direct Redis reads only.
|
|
5
|
+
* Uses Better Auth's server-side session API to get the current session.
|
|
6
|
+
* Falls back to legacy JWT + Redis path if Better Auth session not found.
|
|
9
7
|
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
10
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
42
|
exports.decodeSession = decodeSession;
|
|
12
43
|
require("server-only");
|
|
@@ -17,17 +48,100 @@ const idp_client_config_1 = require("../lib/idp-client-config");
|
|
|
17
48
|
const app_slug_1 = require("../lib/app-slug");
|
|
18
49
|
const startup_init_1 = require("../lib/startup-init");
|
|
19
50
|
/**
|
|
20
|
-
*
|
|
21
|
-
* Returns
|
|
51
|
+
* Try Better Auth's server-side session API.
|
|
52
|
+
* Returns a DecodedSession if Better Auth has an active session, null otherwise.
|
|
53
|
+
*/
|
|
54
|
+
async function tryBetterAuthSession(requestCookies) {
|
|
55
|
+
try {
|
|
56
|
+
const { getBetterAuthHandler } = await Promise.resolve().then(() => __importStar(require('../auth/better-auth')));
|
|
57
|
+
// getBetterAuthHandler initializes the instance; we need the raw instance
|
|
58
|
+
const { default: getBetterAuthInstanceFn } = await Promise.resolve().then(() => __importStar(require('../auth/better-auth'))).then(m => ({ default: m.getBetterAuthInstance || null }))
|
|
59
|
+
.catch(() => ({ default: null }));
|
|
60
|
+
// Access the cached instance via the module's internal getter
|
|
61
|
+
let auth = null;
|
|
62
|
+
try {
|
|
63
|
+
// Force handler init which caches the instance, then use the API
|
|
64
|
+
await getBetterAuthHandler();
|
|
65
|
+
// The instance is cached in the module — re-import to access it
|
|
66
|
+
const mod = await Promise.resolve().then(() => __importStar(require('../auth/better-auth')));
|
|
67
|
+
auth = mod.__betterAuthInstance;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (!auth?.api?.getSession) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
// Build headers from cookies for Better Auth to read
|
|
76
|
+
const cookieStore = requestCookies || (await (0, headers_1.cookies)());
|
|
77
|
+
const headerObj = new Headers();
|
|
78
|
+
// Collect all cookies into a Cookie header
|
|
79
|
+
if ('getAll' in cookieStore && typeof cookieStore.getAll === 'function') {
|
|
80
|
+
const allCookies = cookieStore.getAll();
|
|
81
|
+
const cookieStr = allCookies.map((c) => `${c.name}=${c.value}`).join('; ');
|
|
82
|
+
headerObj.set('cookie', cookieStr);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Fallback: read known cookie names
|
|
86
|
+
const sessionCookieName = (0, app_slug_1.getSessionCookieName)();
|
|
87
|
+
const secureCookieName = (0, app_slug_1.getSecureSessionCookieName)();
|
|
88
|
+
const parts = [];
|
|
89
|
+
const sc = cookieStore.get(secureCookieName);
|
|
90
|
+
if (sc?.value)
|
|
91
|
+
parts.push(`${secureCookieName}=${sc.value}`);
|
|
92
|
+
const nc = cookieStore.get(sessionCookieName);
|
|
93
|
+
if (nc?.value)
|
|
94
|
+
parts.push(`${sessionCookieName}=${nc.value}`);
|
|
95
|
+
if (parts.length > 0)
|
|
96
|
+
headerObj.set('cookie', parts.join('; '));
|
|
97
|
+
}
|
|
98
|
+
const result = await auth.api.getSession({ headers: headerObj });
|
|
99
|
+
if (!result?.session || !result?.user) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
// Map Better Auth session to SessionData
|
|
103
|
+
const sessionData = {
|
|
104
|
+
userId: result.user.id || '',
|
|
105
|
+
email: result.user.email || '',
|
|
106
|
+
name: result.user.name || undefined,
|
|
107
|
+
roles: [],
|
|
108
|
+
idpAccessTokenExpires: result.session.expiresAt
|
|
109
|
+
? new Date(result.session.expiresAt).getTime()
|
|
110
|
+
: Date.now() + 24 * 60 * 60 * 1000,
|
|
111
|
+
mfaVerified: true, // Social login doesn't require MFA
|
|
112
|
+
oauthProvider: 'google',
|
|
113
|
+
};
|
|
114
|
+
const jwtPayload = {
|
|
115
|
+
sub: result.user.id,
|
|
116
|
+
email: result.user.email,
|
|
117
|
+
name: result.user.name,
|
|
118
|
+
iat: Math.floor(Date.now() / 1000),
|
|
119
|
+
exp: sessionData.idpAccessTokenExpires / 1000,
|
|
120
|
+
sessionToken: result.session.token,
|
|
121
|
+
};
|
|
122
|
+
return { sessionData, jwtPayload };
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.warn('[DECODE-SESSION] Better Auth session check failed:', error instanceof Error ? error.message : String(error));
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Decode the session from cookies.
|
|
131
|
+
* Tries Better Auth first, falls back to legacy JWT + Redis.
|
|
22
132
|
*
|
|
23
133
|
* @param requestCookies Optional cookie getter for API route context (NextRequest.cookies).
|
|
24
134
|
* If omitted, uses next/headers cookies() for server components.
|
|
25
135
|
*/
|
|
26
136
|
async function decodeSession(requestCookies) {
|
|
27
137
|
try {
|
|
28
|
-
// Ensure startup initialization is complete (Redis, IDP config, etc.)
|
|
29
138
|
await (0, startup_init_1.ensureInitialized)();
|
|
30
|
-
//
|
|
139
|
+
// Try Better Auth session first
|
|
140
|
+
const betterAuthSession = await tryBetterAuthSession(requestCookies);
|
|
141
|
+
if (betterAuthSession) {
|
|
142
|
+
return betterAuthSession;
|
|
143
|
+
}
|
|
144
|
+
// Fall back to legacy JWT + Redis path
|
|
31
145
|
const cookieStore = requestCookies || (await (0, headers_1.cookies)());
|
|
32
146
|
const sessionCookieName = (0, app_slug_1.getSessionCookieName)();
|
|
33
147
|
const secureCookieName = (0, app_slug_1.getSecureSessionCookieName)();
|
|
@@ -36,14 +150,12 @@ async function decodeSession(requestCookies) {
|
|
|
36
150
|
if (!cookieValue) {
|
|
37
151
|
return null;
|
|
38
152
|
}
|
|
39
|
-
// Get the NextAuth secret from IDP config
|
|
40
153
|
const config = await (0, idp_client_config_1.getIDPClientConfig)();
|
|
41
154
|
const secret = config.nextAuthSecret;
|
|
42
155
|
if (!secret) {
|
|
43
156
|
console.error('[DECODE-SESSION] No nextAuthSecret available from IDP config');
|
|
44
157
|
return null;
|
|
45
158
|
}
|
|
46
|
-
// Decode the JWT (same pattern as test-aware-get-token.ts)
|
|
47
159
|
const secretKey = new TextEncoder().encode(secret);
|
|
48
160
|
let payload;
|
|
49
161
|
try {
|
|
@@ -51,17 +163,14 @@ async function decodeSession(requestCookies) {
|
|
|
51
163
|
payload = result.payload;
|
|
52
164
|
}
|
|
53
165
|
catch (jwtError) {
|
|
54
|
-
// JWT decode failed - cookie may be corrupted or secret rotated
|
|
55
166
|
console.warn('[DECODE-SESSION] JWT verification failed:', jwtError instanceof Error ? jwtError.message : String(jwtError));
|
|
56
167
|
return null;
|
|
57
168
|
}
|
|
58
|
-
// Extract the Redis session ID from JWT payload
|
|
59
169
|
const sessionToken = payload.sessionToken || payload.redisSessionId;
|
|
60
170
|
if (!sessionToken) {
|
|
61
171
|
console.warn('[DECODE-SESSION] JWT payload missing sessionToken/redisSessionId');
|
|
62
172
|
return null;
|
|
63
173
|
}
|
|
64
|
-
// Fetch session from Redis (direct, no HTTP)
|
|
65
174
|
const sessionData = await (0, session_store_1.getSession)(sessionToken);
|
|
66
175
|
if (!sessionData) {
|
|
67
176
|
return null;
|
package/package.json
CHANGED
package/src/auth/better-auth.ts
CHANGED
|
@@ -118,7 +118,11 @@ let cachedInstance: any = null;
|
|
|
118
118
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
119
|
let initPromise: Promise<any> | null = null;
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
// Expose for server-side session access (decode-session.ts)
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
|
+
export { cachedInstance as __betterAuthInstance };
|
|
124
|
+
|
|
125
|
+
export async function getBetterAuthInstance() {
|
|
122
126
|
if (cachedInstance) return cachedInstance;
|
|
123
127
|
|
|
124
128
|
if (!initPromise) {
|
|
@@ -1,91 +1,175 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server-Side Session Decoder
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import '
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Server-Side Session Decoder
|
|
3
|
+
*
|
|
4
|
+
* Uses Better Auth's server-side session API to get the current session.
|
|
5
|
+
* Falls back to legacy JWT + Redis path if Better Auth session not found.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import 'server-only';
|
|
9
|
+
import { cookies } from 'next/headers';
|
|
10
|
+
import { jwtVerify, type JWTPayload } from 'jose';
|
|
11
|
+
import { getSession, type SessionData } from '../lib/session-store';
|
|
12
|
+
import { getIDPClientConfig } from '../lib/idp-client-config';
|
|
13
|
+
import { getSessionCookieName, getSecureSessionCookieName } from '../lib/app-slug';
|
|
14
|
+
import { ensureInitialized } from '../lib/startup-init';
|
|
15
|
+
|
|
16
|
+
export interface DecodedSession {
|
|
17
|
+
sessionData: SessionData;
|
|
18
|
+
jwtPayload: JWTPayload & { sessionToken?: string; redisSessionId?: string };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Try Better Auth's server-side session API.
|
|
23
|
+
* Returns a DecodedSession if Better Auth has an active session, null otherwise.
|
|
24
|
+
*/
|
|
25
|
+
async function tryBetterAuthSession(
|
|
26
|
+
requestCookies?: { get: (name: string) => { value: string } | undefined }
|
|
27
|
+
): Promise<DecodedSession | null> {
|
|
28
|
+
try {
|
|
29
|
+
const { getBetterAuthHandler } = await import('../auth/better-auth');
|
|
30
|
+
// getBetterAuthHandler initializes the instance; we need the raw instance
|
|
31
|
+
const { default: getBetterAuthInstanceFn } = await import('../auth/better-auth')
|
|
32
|
+
.then(m => ({ default: (m as any).getBetterAuthInstance || null }))
|
|
33
|
+
.catch(() => ({ default: null }));
|
|
34
|
+
|
|
35
|
+
// Access the cached instance via the module's internal getter
|
|
36
|
+
let auth: any = null;
|
|
37
|
+
try {
|
|
38
|
+
// Force handler init which caches the instance, then use the API
|
|
39
|
+
await getBetterAuthHandler();
|
|
40
|
+
// The instance is cached in the module — re-import to access it
|
|
41
|
+
const mod = await import('../auth/better-auth');
|
|
42
|
+
auth = (mod as any).__betterAuthInstance;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!auth?.api?.getSession) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Build headers from cookies for Better Auth to read
|
|
52
|
+
const cookieStore = requestCookies || (await cookies());
|
|
53
|
+
const headerObj = new Headers();
|
|
54
|
+
|
|
55
|
+
// Collect all cookies into a Cookie header
|
|
56
|
+
if ('getAll' in cookieStore && typeof cookieStore.getAll === 'function') {
|
|
57
|
+
const allCookies = (cookieStore as any).getAll();
|
|
58
|
+
const cookieStr = allCookies.map((c: any) => `${c.name}=${c.value}`).join('; ');
|
|
59
|
+
headerObj.set('cookie', cookieStr);
|
|
60
|
+
} else {
|
|
61
|
+
// Fallback: read known cookie names
|
|
62
|
+
const sessionCookieName = getSessionCookieName();
|
|
63
|
+
const secureCookieName = getSecureSessionCookieName();
|
|
64
|
+
const parts: string[] = [];
|
|
65
|
+
const sc = cookieStore.get(secureCookieName);
|
|
66
|
+
if (sc?.value) parts.push(`${secureCookieName}=${sc.value}`);
|
|
67
|
+
const nc = cookieStore.get(sessionCookieName);
|
|
68
|
+
if (nc?.value) parts.push(`${sessionCookieName}=${nc.value}`);
|
|
69
|
+
if (parts.length > 0) headerObj.set('cookie', parts.join('; '));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = await auth.api.getSession({ headers: headerObj });
|
|
73
|
+
|
|
74
|
+
if (!result?.session || !result?.user) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Map Better Auth session to SessionData
|
|
79
|
+
const sessionData: SessionData = {
|
|
80
|
+
userId: result.user.id || '',
|
|
81
|
+
email: result.user.email || '',
|
|
82
|
+
name: result.user.name || undefined,
|
|
83
|
+
roles: [],
|
|
84
|
+
idpAccessTokenExpires: result.session.expiresAt
|
|
85
|
+
? new Date(result.session.expiresAt).getTime()
|
|
86
|
+
: Date.now() + 24 * 60 * 60 * 1000,
|
|
87
|
+
mfaVerified: true, // Social login doesn't require MFA
|
|
88
|
+
oauthProvider: 'google',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const jwtPayload: DecodedSession['jwtPayload'] = {
|
|
92
|
+
sub: result.user.id,
|
|
93
|
+
email: result.user.email,
|
|
94
|
+
name: result.user.name,
|
|
95
|
+
iat: Math.floor(Date.now() / 1000),
|
|
96
|
+
exp: sessionData.idpAccessTokenExpires / 1000,
|
|
97
|
+
sessionToken: result.session.token,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return { sessionData, jwtPayload };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.warn('[DECODE-SESSION] Better Auth session check failed:', error instanceof Error ? error.message : String(error));
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Decode the session from cookies.
|
|
109
|
+
* Tries Better Auth first, falls back to legacy JWT + Redis.
|
|
110
|
+
*
|
|
111
|
+
* @param requestCookies Optional cookie getter for API route context (NextRequest.cookies).
|
|
112
|
+
* If omitted, uses next/headers cookies() for server components.
|
|
113
|
+
*/
|
|
114
|
+
export async function decodeSession(
|
|
115
|
+
requestCookies?: { get: (name: string) => { value: string } | undefined }
|
|
116
|
+
): Promise<DecodedSession | null> {
|
|
117
|
+
try {
|
|
118
|
+
await ensureInitialized();
|
|
119
|
+
|
|
120
|
+
// Try Better Auth session first
|
|
121
|
+
const betterAuthSession = await tryBetterAuthSession(requestCookies);
|
|
122
|
+
if (betterAuthSession) {
|
|
123
|
+
return betterAuthSession;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Fall back to legacy JWT + Redis path
|
|
127
|
+
const cookieStore = requestCookies || (await cookies());
|
|
128
|
+
const sessionCookieName = getSessionCookieName();
|
|
129
|
+
const secureCookieName = getSecureSessionCookieName();
|
|
130
|
+
|
|
131
|
+
const cookieValue =
|
|
132
|
+
cookieStore.get(secureCookieName)?.value ||
|
|
133
|
+
cookieStore.get(sessionCookieName)?.value;
|
|
134
|
+
|
|
135
|
+
if (!cookieValue) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const config = await getIDPClientConfig();
|
|
140
|
+
const secret = config.nextAuthSecret;
|
|
141
|
+
if (!secret) {
|
|
142
|
+
console.error('[DECODE-SESSION] No nextAuthSecret available from IDP config');
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const secretKey = new TextEncoder().encode(secret);
|
|
147
|
+
let payload: JWTPayload;
|
|
148
|
+
try {
|
|
149
|
+
const result = await jwtVerify(cookieValue, secretKey);
|
|
150
|
+
payload = result.payload;
|
|
151
|
+
} catch (jwtError) {
|
|
152
|
+
console.warn('[DECODE-SESSION] JWT verification failed:', jwtError instanceof Error ? jwtError.message : String(jwtError));
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const sessionToken = (payload as any).sessionToken || (payload as any).redisSessionId;
|
|
157
|
+
if (!sessionToken) {
|
|
158
|
+
console.warn('[DECODE-SESSION] JWT payload missing sessionToken/redisSessionId');
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const sessionData = await getSession(sessionToken);
|
|
163
|
+
if (!sessionData) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
sessionData,
|
|
169
|
+
jwtPayload: payload as DecodedSession['jwtPayload'],
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error('[DECODE-SESSION] Error:', error instanceof Error ? error.message : String(error));
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|