@payez/next-mvp 4.0.15 → 4.0.16
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.
|
@@ -53,6 +53,7 @@ function JwtInspectPage() {
|
|
|
53
53
|
const [isDarkMode, setIsDarkMode] = (0, react_1.useState)(false);
|
|
54
54
|
const [jwtHeader, setJwtHeader] = (0, react_1.useState)(null);
|
|
55
55
|
const [jwtPayload, setJwtPayload] = (0, react_1.useState)(null);
|
|
56
|
+
const [serverSession, setServerSession] = (0, react_1.useState)(null);
|
|
56
57
|
// Detect dark mode
|
|
57
58
|
(0, react_1.useEffect)(() => {
|
|
58
59
|
const checkDarkMode = () => {
|
|
@@ -65,14 +66,26 @@ function JwtInspectPage() {
|
|
|
65
66
|
mediaQuery.addEventListener('change', checkDarkMode);
|
|
66
67
|
return () => mediaQuery.removeEventListener('change', checkDarkMode);
|
|
67
68
|
}, []);
|
|
68
|
-
//
|
|
69
|
+
// Fetch enriched session from server (has IDP tokens, roles, userId)
|
|
69
70
|
(0, react_1.useEffect)(() => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
if (!session)
|
|
72
|
+
return;
|
|
73
|
+
fetch('/api/test-env/session-data')
|
|
74
|
+
.then(r => r.ok ? r.json() : null)
|
|
75
|
+
.then(d => {
|
|
76
|
+
if (d)
|
|
77
|
+
setServerSession(d);
|
|
78
|
+
})
|
|
79
|
+
.catch(() => { });
|
|
75
80
|
}, [session]);
|
|
81
|
+
// Decode JWT header and payload from server session's IDP access token
|
|
82
|
+
(0, react_1.useEffect)(() => {
|
|
83
|
+
const token = serverSession?.idpAccessToken || session?.accessToken;
|
|
84
|
+
if (token) {
|
|
85
|
+
setJwtHeader(decodeJwtHeader(token));
|
|
86
|
+
setJwtPayload(decodeJwtPayload(token));
|
|
87
|
+
}
|
|
88
|
+
}, [serverSession, session]);
|
|
76
89
|
const copyToClipboard = (text, label) => {
|
|
77
90
|
navigator.clipboard.writeText(text);
|
|
78
91
|
setCopied(label);
|
|
@@ -84,16 +97,40 @@ function JwtInspectPage() {
|
|
|
84
97
|
if (status === 'unauthenticated') {
|
|
85
98
|
return ((0, jsx_runtime_1.jsx)("div", { className: `min-h-screen p-8 ${isDarkMode ? 'bg-slate-950 text-white' : 'bg-gray-50 text-gray-900'}`, children: (0, jsx_runtime_1.jsxs)("div", { className: `p-4 rounded-lg ${isDarkMode ? 'bg-red-900/30 border border-red-800' : 'bg-red-50 border border-red-200'}`, children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-lg font-semibold mb-2", children: "Not Authenticated" }), (0, jsx_runtime_1.jsx)("p", { children: "Please log in to inspect session data." })] }) }));
|
|
86
99
|
}
|
|
87
|
-
//
|
|
100
|
+
// Merge server-side enriched session with client session
|
|
101
|
+
const ss = serverSession || {};
|
|
88
102
|
const ext = session;
|
|
89
|
-
const user =
|
|
103
|
+
const user = {
|
|
104
|
+
id: ss.userId || ext?.user?.id,
|
|
105
|
+
email: ss.email || ext?.user?.email,
|
|
106
|
+
name: ss.name || ext?.user?.name,
|
|
107
|
+
roles: ss.roles || [],
|
|
108
|
+
oauthProvider: ss.oauthProvider,
|
|
109
|
+
idpClientId: ss.idpClientId,
|
|
110
|
+
merchantId: ss.merchantId,
|
|
111
|
+
mfaVerified: ss.mfaVerified,
|
|
112
|
+
requiresTwoFactor: false,
|
|
113
|
+
authenticationMethods: ss.authenticationMethods,
|
|
114
|
+
authenticationLevel: ss.authenticationLevel,
|
|
115
|
+
mfaCompletedAt: ss.mfaCompletedAt,
|
|
116
|
+
mfaExpiresAt: ss.mfaExpiresAt,
|
|
117
|
+
twoFactorSessionVerified: ss.mfaVerified,
|
|
118
|
+
};
|
|
119
|
+
// Token fields for display
|
|
120
|
+
const displayExt = {
|
|
121
|
+
...ext,
|
|
122
|
+
sessionToken: ss.sessionToken || ext?.sessionToken,
|
|
123
|
+
accessToken: ss.idpAccessToken || ext?.accessToken,
|
|
124
|
+
refreshToken: ss.idpRefreshToken || ext?.refreshToken,
|
|
125
|
+
accessTokenExpires: ss.idpAccessTokenExpires || ext?.accessTokenExpires,
|
|
126
|
+
};
|
|
90
127
|
// Card styling helpers
|
|
91
128
|
const cardClass = isDarkMode ? 'bg-slate-900 border border-slate-700' : 'bg-white border border-gray-200';
|
|
92
129
|
const labelClass = isDarkMode ? 'text-slate-400' : 'text-gray-500';
|
|
93
130
|
const valueClass = isDarkMode ? 'text-white' : 'text-gray-900';
|
|
94
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: `min-h-screen p-8 ${isDarkMode ? 'bg-slate-950 text-white' : 'bg-gray-50 text-gray-900'}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "max-w-4xl mx-auto space-y-6", children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-2xl font-bold", children: "Session Inspector" }), (0, jsx_runtime_1.jsx)("p", { className: `text-sm ${labelClass}`, children: "
|
|
131
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: `min-h-screen p-8 ${isDarkMode ? 'bg-slate-950 text-white' : 'bg-gray-50 text-gray-900'}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "max-w-4xl mx-auto space-y-6", children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-2xl font-bold", children: "Session Inspector" }), (0, jsx_runtime_1.jsx)("p", { className: `text-sm ${labelClass}`, children: "Better Auth session + IDP tokens from Redis" }), (0, jsx_runtime_1.jsxs)("div", { className: `p-4 rounded-lg ${cardClass}`, children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-lg font-semibold mb-4", children: "User Identity" }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3 text-sm", children: [(0, jsx_runtime_1.jsx)(InfoRow, { label: "User ID", value: user.id, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Email", value: user.email, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Name", value: user.name, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "OAuth Provider", value: user.oauthProvider, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "IDP Client ID", value: user.idpClientId, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Merchant ID", value: user.merchantId, labelClass: labelClass, valueClass: valueClass })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: `p-4 rounded-lg ${isDarkMode ? 'bg-purple-900/30 border border-purple-700' : 'bg-purple-50 border border-purple-200'}`, children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-lg font-semibold mb-4", children: "Roles" }), user.roles && user.roles.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "flex flex-wrap gap-2", children: user.roles.map((role) => ((0, jsx_runtime_1.jsx)("span", { className: `px-3 py-1 rounded-full text-sm font-medium ${isDarkMode ? 'bg-purple-800 text-purple-100' : 'bg-purple-200 text-purple-800'}`, children: role }, role))) })) : ((0, jsx_runtime_1.jsx)("p", { className: labelClass, children: "No roles assigned" }))] }), (0, jsx_runtime_1.jsxs)("div", { className: `p-4 rounded-lg ${isDarkMode ? 'bg-yellow-900/30 border border-yellow-700' : 'bg-yellow-50 border border-yellow-200'}`, children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-lg font-semibold mb-4", children: "2FA Status" }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3 text-sm", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { className: labelClass, children: "2FA Verified:" }), ' ', (0, jsx_runtime_1.jsx)(StatusBadge, { value: user.twoFactorSessionVerified, trueText: "Yes", falseText: "No", isDarkMode: isDarkMode })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { className: labelClass, children: "Requires 2FA:" }), ' ', (0, jsx_runtime_1.jsx)(StatusBadge, { value: user.requiresTwoFactor, trueText: "Yes", falseText: "No", isDarkMode: isDarkMode, invertColors: true })] }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Auth Methods (AMR)", value: user.authenticationMethods?.join(', '), labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Auth Level (ACR)", value: user.authenticationLevel, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "MFA Completed At", value: user.mfaCompletedAt ? new Date(user.mfaCompletedAt).toISOString() : undefined, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "MFA Expires At", value: user.mfaExpiresAt ? new Date(user.mfaExpiresAt).toISOString() : undefined, labelClass: labelClass, valueClass: valueClass })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: `p-4 rounded-lg ${isDarkMode ? 'bg-blue-900/30 border border-blue-700' : 'bg-blue-50 border border-blue-200'}`, children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-lg font-semibold mb-4", children: "Tokens" }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [(0, jsx_runtime_1.jsx)("span", { className: labelClass, children: "Session Token (Redis Key):" }), displayExt.sessionToken && ((0, jsx_runtime_1.jsx)(CopyButton, { onClick: () => copyToClipboard(displayExt.sessionToken, 'session'), copied: copied === 'session', isDarkMode: isDarkMode }))] }), (0, jsx_runtime_1.jsx)("code", { className: `block p-2 rounded text-xs break-all ${isDarkMode ? 'bg-slate-800' : 'bg-gray-100'}`, children: displayExt.sessionToken || 'N/A' })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [(0, jsx_runtime_1.jsx)("span", { className: labelClass, children: "Access Token (IDP):" }), displayExt.accessToken && ((0, jsx_runtime_1.jsx)(CopyButton, { onClick: () => copyToClipboard(displayExt.accessToken, 'access'), copied: copied === 'access', isDarkMode: isDarkMode }))] }), (0, jsx_runtime_1.jsx)("code", { className: `block p-2 rounded text-xs break-all max-h-24 overflow-auto ${isDarkMode ? 'bg-slate-800' : 'bg-gray-100'}`, children: displayExt.accessToken || 'N/A' })] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3 text-sm", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { className: labelClass, children: "Has Refresh Token:" }), ' ', (0, jsx_runtime_1.jsx)(StatusBadge, { value: !!displayExt.refreshToken, trueText: "Yes", falseText: "No", isDarkMode: isDarkMode })] }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Access Token Expires", value: displayExt.accessTokenExpires ? new Date(displayExt.accessTokenExpires).toISOString() : undefined, labelClass: labelClass, valueClass: valueClass })] })] })] }), jwtHeader && ((0, jsx_runtime_1.jsxs)("div", { className: `p-4 rounded-lg ${isDarkMode ? 'bg-orange-900/30 border border-orange-700' : 'bg-orange-50 border border-orange-200'}`, children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-lg font-semibold mb-4", children: "JWT Header" }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3 text-sm", children: [(0, jsx_runtime_1.jsx)(InfoRow, { label: "Algorithm (alg)", value: jwtHeader.alg, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Type (typ)", value: jwtHeader.typ, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { className: labelClass, children: "Key ID (kid):" }), ' ', (0, jsx_runtime_1.jsx)("span", { className: `font-mono ${jwtHeader.kid ? (isDarkMode ? 'text-green-400' : 'text-green-600') : (isDarkMode ? 'text-red-400' : 'text-red-600')}`, children: jwtHeader.kid || 'NOT PRESENT' })] }), Object.entries(jwtHeader)
|
|
95
132
|
.filter(([key]) => !['alg', 'typ', 'kid'].includes(key))
|
|
96
|
-
.map(([key, value]) => ((0, jsx_runtime_1.jsx)(InfoRow, { label: key, value: typeof value === 'object' ? JSON.stringify(value) : String(value), labelClass: labelClass, valueClass: valueClass }, key)))] })] })), jwtPayload && ((0, jsx_runtime_1.jsxs)("div", { className: `p-4 rounded-lg ${isDarkMode ? 'bg-green-900/30 border border-green-700' : 'bg-green-50 border border-green-200'}`, children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-lg font-semibold mb-4", children: "JWT Payload Claims" }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3 text-sm", children: [(0, jsx_runtime_1.jsx)(InfoRow, { label: "Subject (sub)", value: jwtPayload.sub, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Issuer (iss)", value: jwtPayload.iss, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Audience (aud)", value: Array.isArray(jwtPayload.aud) ? jwtPayload.aud.join(', ') : jwtPayload.aud, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Client ID", value: jwtPayload.client_id, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Expires (exp)", value: jwtPayload.exp ? new Date(jwtPayload.exp * 1000).toISOString() : undefined, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Issued At (iat)", value: jwtPayload.iat ? new Date(jwtPayload.iat * 1000).toISOString() : undefined, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "AMR (Auth Methods)", value: Array.isArray(jwtPayload.amr) ? jwtPayload.amr.join(', ') : jwtPayload.amr, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "ACR (Auth Context)", value: jwtPayload.acr, labelClass: labelClass, valueClass: valueClass })] })] })), (0, jsx_runtime_1.jsxs)("div", { className: `p-4 rounded-lg ${cardClass}`, children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-lg font-semibold mb-4", children: "Session Metadata" }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3 text-sm", children: [(0, jsx_runtime_1.jsx)(InfoRow, { label: "Session Expires", value: session?.expires ? new Date(session.expires).toISOString() : undefined, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Error", value: ext.error, labelClass: labelClass, valueClass: ext.error ? 'text-red-500' : valueClass })] })] }), (0, jsx_runtime_1.jsxs)("details", { className: `p-4 rounded-lg ${cardClass}`, children: [(0, jsx_runtime_1.jsx)("summary", { className: "font-semibold cursor-pointer", children: "Raw Session Data (Click to expand)" }), (0, jsx_runtime_1.jsx)("pre", { className: `mt-4 text-xs overflow-auto p-3 rounded ${isDarkMode ? 'bg-slate-800' : 'bg-gray-100'}`, children: JSON.stringify(session, null, 2) })] })] }) }));
|
|
133
|
+
.map(([key, value]) => ((0, jsx_runtime_1.jsx)(InfoRow, { label: key, value: typeof value === 'object' ? JSON.stringify(value) : String(value), labelClass: labelClass, valueClass: valueClass }, key)))] })] })), jwtPayload && ((0, jsx_runtime_1.jsxs)("div", { className: `p-4 rounded-lg ${isDarkMode ? 'bg-green-900/30 border border-green-700' : 'bg-green-50 border border-green-200'}`, children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-lg font-semibold mb-4", children: "JWT Payload Claims" }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3 text-sm", children: [(0, jsx_runtime_1.jsx)(InfoRow, { label: "Subject (sub)", value: jwtPayload.sub, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Issuer (iss)", value: jwtPayload.iss, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Audience (aud)", value: Array.isArray(jwtPayload.aud) ? jwtPayload.aud.join(', ') : jwtPayload.aud, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Client ID", value: jwtPayload.client_id, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Expires (exp)", value: jwtPayload.exp ? new Date(jwtPayload.exp * 1000).toISOString() : undefined, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Issued At (iat)", value: jwtPayload.iat ? new Date(jwtPayload.iat * 1000).toISOString() : undefined, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "AMR (Auth Methods)", value: Array.isArray(jwtPayload.amr) ? jwtPayload.amr.join(', ') : jwtPayload.amr, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "ACR (Auth Context)", value: jwtPayload.acr, labelClass: labelClass, valueClass: valueClass })] })] })), (0, jsx_runtime_1.jsxs)("div", { className: `p-4 rounded-lg ${cardClass}`, children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-lg font-semibold mb-4", children: "Session Metadata" }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3 text-sm", children: [(0, jsx_runtime_1.jsx)(InfoRow, { label: "Session Expires", value: session?.expires ? new Date(session.expires).toISOString() : undefined, labelClass: labelClass, valueClass: valueClass }), (0, jsx_runtime_1.jsx)(InfoRow, { label: "Error", value: ext.error, labelClass: labelClass, valueClass: ext.error ? 'text-red-500' : valueClass })] })] }), (0, jsx_runtime_1.jsxs)("details", { className: `p-4 rounded-lg ${cardClass}`, children: [(0, jsx_runtime_1.jsx)("summary", { className: "font-semibold cursor-pointer", children: "Raw Session Data (Click to expand)" }), (0, jsx_runtime_1.jsx)("pre", { className: `mt-4 text-xs overflow-auto p-3 rounded ${isDarkMode ? 'bg-slate-800' : 'bg-gray-100'}`, children: JSON.stringify({ clientSession: session, serverSession: serverSession }, null, 2) })] })] }) }));
|
|
97
134
|
}
|
|
98
135
|
// Helper Components
|
|
99
136
|
function InfoRow({ label, value, labelClass, valueClass, }) {
|
package/package.json
CHANGED
|
@@ -49,6 +49,7 @@ export function JwtInspectPage() {
|
|
|
49
49
|
const [isDarkMode, setIsDarkMode] = useState(false);
|
|
50
50
|
const [jwtHeader, setJwtHeader] = useState<any>(null);
|
|
51
51
|
const [jwtPayload, setJwtPayload] = useState<any>(null);
|
|
52
|
+
const [serverSession, setServerSession] = useState<any>(null);
|
|
52
53
|
|
|
53
54
|
// Detect dark mode
|
|
54
55
|
useEffect(() => {
|
|
@@ -63,15 +64,26 @@ export function JwtInspectPage() {
|
|
|
63
64
|
return () => mediaQuery.removeEventListener('change', checkDarkMode);
|
|
64
65
|
}, []);
|
|
65
66
|
|
|
66
|
-
//
|
|
67
|
+
// Fetch enriched session from server (has IDP tokens, roles, userId)
|
|
67
68
|
useEffect(() => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
if (!session) return;
|
|
70
|
+
fetch('/api/test-env/session-data')
|
|
71
|
+
.then(r => r.ok ? r.json() : null)
|
|
72
|
+
.then(d => {
|
|
73
|
+
if (d) setServerSession(d);
|
|
74
|
+
})
|
|
75
|
+
.catch(() => {});
|
|
73
76
|
}, [session]);
|
|
74
77
|
|
|
78
|
+
// Decode JWT header and payload from server session's IDP access token
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
const token = serverSession?.idpAccessToken || (session as any)?.accessToken;
|
|
81
|
+
if (token) {
|
|
82
|
+
setJwtHeader(decodeJwtHeader(token));
|
|
83
|
+
setJwtPayload(decodeJwtPayload(token));
|
|
84
|
+
}
|
|
85
|
+
}, [serverSession, session]);
|
|
86
|
+
|
|
75
87
|
const copyToClipboard = (text: string, label: string) => {
|
|
76
88
|
navigator.clipboard.writeText(text);
|
|
77
89
|
setCopied(label);
|
|
@@ -97,9 +109,33 @@ export function JwtInspectPage() {
|
|
|
97
109
|
);
|
|
98
110
|
}
|
|
99
111
|
|
|
100
|
-
//
|
|
112
|
+
// Merge server-side enriched session with client session
|
|
113
|
+
const ss = serverSession || {};
|
|
101
114
|
const ext = session as any;
|
|
102
|
-
const user =
|
|
115
|
+
const user = {
|
|
116
|
+
id: ss.userId || ext?.user?.id,
|
|
117
|
+
email: ss.email || ext?.user?.email,
|
|
118
|
+
name: ss.name || ext?.user?.name,
|
|
119
|
+
roles: ss.roles || [],
|
|
120
|
+
oauthProvider: ss.oauthProvider,
|
|
121
|
+
idpClientId: ss.idpClientId,
|
|
122
|
+
merchantId: ss.merchantId,
|
|
123
|
+
mfaVerified: ss.mfaVerified,
|
|
124
|
+
requiresTwoFactor: false,
|
|
125
|
+
authenticationMethods: ss.authenticationMethods,
|
|
126
|
+
authenticationLevel: ss.authenticationLevel,
|
|
127
|
+
mfaCompletedAt: ss.mfaCompletedAt,
|
|
128
|
+
mfaExpiresAt: ss.mfaExpiresAt,
|
|
129
|
+
twoFactorSessionVerified: ss.mfaVerified,
|
|
130
|
+
};
|
|
131
|
+
// Token fields for display
|
|
132
|
+
const displayExt = {
|
|
133
|
+
...ext,
|
|
134
|
+
sessionToken: ss.sessionToken || ext?.sessionToken,
|
|
135
|
+
accessToken: ss.idpAccessToken || ext?.accessToken,
|
|
136
|
+
refreshToken: ss.idpRefreshToken || ext?.refreshToken,
|
|
137
|
+
accessTokenExpires: ss.idpAccessTokenExpires || ext?.accessTokenExpires,
|
|
138
|
+
};
|
|
103
139
|
|
|
104
140
|
// Card styling helpers
|
|
105
141
|
const cardClass = isDarkMode ? 'bg-slate-900 border border-slate-700' : 'bg-white border border-gray-200';
|
|
@@ -111,7 +147,7 @@ export function JwtInspectPage() {
|
|
|
111
147
|
<div className="max-w-4xl mx-auto space-y-6">
|
|
112
148
|
<h1 className="text-2xl font-bold">Session Inspector</h1>
|
|
113
149
|
<p className={`text-sm ${labelClass}`}>
|
|
114
|
-
|
|
150
|
+
Better Auth session + IDP tokens from Redis
|
|
115
151
|
</p>
|
|
116
152
|
|
|
117
153
|
{/* User Identity */}
|
|
@@ -205,32 +241,32 @@ export function JwtInspectPage() {
|
|
|
205
241
|
<div>
|
|
206
242
|
<div className="flex items-center justify-between mb-2">
|
|
207
243
|
<span className={labelClass}>Session Token (Redis Key):</span>
|
|
208
|
-
{
|
|
244
|
+
{displayExt.sessionToken && (
|
|
209
245
|
<CopyButton
|
|
210
|
-
onClick={() => copyToClipboard(
|
|
246
|
+
onClick={() => copyToClipboard(displayExt.sessionToken, 'session')}
|
|
211
247
|
copied={copied === 'session'}
|
|
212
248
|
isDarkMode={isDarkMode}
|
|
213
249
|
/>
|
|
214
250
|
)}
|
|
215
251
|
</div>
|
|
216
252
|
<code className={`block p-2 rounded text-xs break-all ${isDarkMode ? 'bg-slate-800' : 'bg-gray-100'}`}>
|
|
217
|
-
{
|
|
253
|
+
{displayExt.sessionToken || 'N/A'}
|
|
218
254
|
</code>
|
|
219
255
|
</div>
|
|
220
256
|
|
|
221
257
|
<div>
|
|
222
258
|
<div className="flex items-center justify-between mb-2">
|
|
223
259
|
<span className={labelClass}>Access Token (IDP):</span>
|
|
224
|
-
{
|
|
260
|
+
{displayExt.accessToken && (
|
|
225
261
|
<CopyButton
|
|
226
|
-
onClick={() => copyToClipboard(
|
|
262
|
+
onClick={() => copyToClipboard(displayExt.accessToken, 'access')}
|
|
227
263
|
copied={copied === 'access'}
|
|
228
264
|
isDarkMode={isDarkMode}
|
|
229
265
|
/>
|
|
230
266
|
)}
|
|
231
267
|
</div>
|
|
232
268
|
<code className={`block p-2 rounded text-xs break-all max-h-24 overflow-auto ${isDarkMode ? 'bg-slate-800' : 'bg-gray-100'}`}>
|
|
233
|
-
{
|
|
269
|
+
{displayExt.accessToken || 'N/A'}
|
|
234
270
|
</code>
|
|
235
271
|
</div>
|
|
236
272
|
|
|
@@ -238,7 +274,7 @@ export function JwtInspectPage() {
|
|
|
238
274
|
<div>
|
|
239
275
|
<span className={labelClass}>Has Refresh Token:</span>{' '}
|
|
240
276
|
<StatusBadge
|
|
241
|
-
value={!!
|
|
277
|
+
value={!!displayExt.refreshToken}
|
|
242
278
|
trueText="Yes"
|
|
243
279
|
falseText="No"
|
|
244
280
|
isDarkMode={isDarkMode}
|
|
@@ -246,7 +282,7 @@ export function JwtInspectPage() {
|
|
|
246
282
|
</div>
|
|
247
283
|
<InfoRow
|
|
248
284
|
label="Access Token Expires"
|
|
249
|
-
value={
|
|
285
|
+
value={displayExt.accessTokenExpires ? new Date(displayExt.accessTokenExpires).toISOString() : undefined}
|
|
250
286
|
labelClass={labelClass}
|
|
251
287
|
valueClass={valueClass}
|
|
252
288
|
/>
|
|
@@ -338,7 +374,7 @@ export function JwtInspectPage() {
|
|
|
338
374
|
<details className={`p-4 rounded-lg ${cardClass}`}>
|
|
339
375
|
<summary className="font-semibold cursor-pointer">Raw Session Data (Click to expand)</summary>
|
|
340
376
|
<pre className={`mt-4 text-xs overflow-auto p-3 rounded ${isDarkMode ? 'bg-slate-800' : 'bg-gray-100'}`}>
|
|
341
|
-
{JSON.stringify(session, null, 2)}
|
|
377
|
+
{JSON.stringify({ clientSession: session, serverSession: serverSession }, null, 2)}
|
|
342
378
|
</pre>
|
|
343
379
|
</details>
|
|
344
380
|
</div>
|