@payez/next-mvp 4.1.1 → 4.1.2
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 +3 -0
- package/dist/auth/better-auth.js +22 -1
- package/dist/lib/ensure-fresh-access-token.d.ts +30 -0
- package/dist/lib/ensure-fresh-access-token.js +269 -0
- package/dist/lib/session-store.js +24 -21
- package/dist/lib/token-lifecycle.js +2 -0
- package/dist/models/SessionModel.d.ts +3 -0
- package/dist/models/SessionModel.js +3 -0
- package/dist/routes/auth/session.js +1 -1
- package/dist/server/auth.d.ts +59 -0
- package/dist/server/auth.js +156 -16
- package/dist/server/decode-session.js +2 -0
- package/package.json +6 -1
- package/src/auth/better-auth.ts +434 -408
- package/src/lib/ensure-fresh-access-token.ts +320 -0
- package/src/lib/session-store.ts +692 -689
- package/src/lib/token-lifecycle.ts +470 -468
- package/src/models/SessionModel.ts +264 -258
- package/src/routes/auth/session.ts +166 -166
- package/src/server/auth.ts +272 -78
- package/src/server/decode-session.ts +202 -200
|
@@ -1,167 +1,167 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ready-to-Use Session Management Route
|
|
3
|
-
*
|
|
4
|
-
* Provides pre-configured session handlers for checking and updating session state.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```typescript
|
|
8
|
-
* // app/api/auth/session/route.ts
|
|
9
|
-
* export { GET, POST } from '@payez/next-mvp/routes/auth/session';
|
|
10
|
-
* ```
|
|
11
|
-
*
|
|
12
|
-
* @version 2.0.0
|
|
13
|
-
* @since auth-ready-v2
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
17
|
-
import { getSession as getBetterAuthSession } from '../../server/auth';
|
|
18
|
-
import { getSession as getRedisSession, updateSession } from '../../lib/session-store';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* GET /api/auth/session - Check current session status
|
|
22
|
-
*
|
|
23
|
-
* Returns the current session information including:
|
|
24
|
-
* - User details
|
|
25
|
-
* - Token expiry status
|
|
26
|
-
* - Session validity
|
|
27
|
-
*/
|
|
28
|
-
export async function GET(req: NextRequest) {
|
|
29
|
-
try {
|
|
30
|
-
const authSession = await getBetterAuthSession(req);
|
|
31
|
-
|
|
32
|
-
if (!authSession) {
|
|
33
|
-
console.warn('[SESSION_ROUTE] Better Auth session not found');
|
|
34
|
-
// MUST return empty {} — useSession() treats any non-empty
|
|
35
|
-
// response object as "authenticated", causing redirect loops on login page.
|
|
36
|
-
return NextResponse.json({});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const redisSessionId = authSession.session?.token;
|
|
40
|
-
|
|
41
|
-
console.log('[SESSION_ROUTE] Session found:', {
|
|
42
|
-
userId: authSession.user?.id,
|
|
43
|
-
email: authSession.user?.email,
|
|
44
|
-
name: authSession.user?.name,
|
|
45
|
-
redisSessionId: redisSessionId ? redisSessionId.substring(0, 8) + '...' : 'MISSING',
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Fetch full session data from Redis
|
|
49
|
-
const session = redisSessionId ? await getRedisSession(redisSessionId) : null;
|
|
50
|
-
|
|
51
|
-
console.log('[SESSION_ROUTE] Redis session:', {
|
|
52
|
-
found: !!session,
|
|
53
|
-
userId: session?.userId,
|
|
54
|
-
roles: session?.roles,
|
|
55
|
-
hasAccessToken: !!session?.idpAccessToken,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Return session format with Redis data
|
|
59
|
-
// useSession() expects: { user: {...}, expires: "..." }
|
|
60
|
-
// We enrich with all session data from Redis
|
|
61
|
-
return NextResponse.json({
|
|
62
|
-
user: {
|
|
63
|
-
id: session?.userId || authSession.user?.id,
|
|
64
|
-
email: session?.email || authSession.user?.email,
|
|
65
|
-
name: session?.name || authSession.user?.name,
|
|
66
|
-
image: authSession.user?.image || null,
|
|
67
|
-
// Redis session data
|
|
68
|
-
roles: session?.roles || [],
|
|
69
|
-
twoFactorSessionVerified: session?.mfaVerified || false,
|
|
70
|
-
requiresTwoFactor: !session?.mfaVerified,
|
|
71
|
-
authenticationMethods: session?.authenticationMethods,
|
|
72
|
-
authenticationLevel: session?.authenticationLevel,
|
|
73
|
-
mfaCompletedAt: session?.mfaCompletedAt,
|
|
74
|
-
mfaExpiresAt: session?.mfaExpiresAt,
|
|
75
|
-
mfaValidityHours: session?.mfaValidityHours,
|
|
76
|
-
oauthProvider: session?.oauthProvider,
|
|
77
|
-
idpClientId: session?.idpClientId,
|
|
78
|
-
merchantId: session?.merchantId,
|
|
79
|
-
},
|
|
80
|
-
// Session tokens
|
|
81
|
-
sessionToken: redisSessionId,
|
|
82
|
-
accessToken: session?.idpAccessToken,
|
|
83
|
-
refreshToken: session?.idpRefreshToken,
|
|
84
|
-
accessTokenExpires: session?.idpAccessTokenExpires,
|
|
85
|
-
expires: authSession.session?.expiresAt
|
|
86
|
-
? new Date(authSession.session.expiresAt).toISOString()
|
|
87
|
-
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
88
|
-
});
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error('[SESSION_ROUTE] Error checking session:', error);
|
|
91
|
-
return NextResponse.json({
|
|
92
|
-
error: 'Failed to check session',
|
|
93
|
-
details: error instanceof Error ? error.message : 'Unknown error'
|
|
94
|
-
}, { status: 500 });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* POST /api/auth/session - Update session data
|
|
100
|
-
*
|
|
101
|
-
* Allows updating session metadata (not tokens).
|
|
102
|
-
* Token refresh should use the /api/auth/refresh endpoint.
|
|
103
|
-
*
|
|
104
|
-
* Body:
|
|
105
|
-
* - metadata: object - Custom metadata to store in session
|
|
106
|
-
*/
|
|
107
|
-
export async function POST(req: NextRequest) {
|
|
108
|
-
try {
|
|
109
|
-
const authSession = await getBetterAuthSession(req);
|
|
110
|
-
|
|
111
|
-
if (!authSession) {
|
|
112
|
-
return NextResponse.json({
|
|
113
|
-
error: 'No session found',
|
|
114
|
-
code: 'UNAUTHORIZED'
|
|
115
|
-
}, { status: 401 });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const sessionToken = authSession.session?.token;
|
|
119
|
-
if (!sessionToken) {
|
|
120
|
-
return NextResponse.json({
|
|
121
|
-
error: 'Invalid session',
|
|
122
|
-
code: 'INVALID_SESSION'
|
|
123
|
-
}, { status: 400 });
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const body = await req.json();
|
|
127
|
-
const { metadata, access_token, refresh_token, twoFactorComplete, twoFactorMethod } = body;
|
|
128
|
-
|
|
129
|
-
// Get current session from Redis
|
|
130
|
-
const session = await getRedisSession(sessionToken);
|
|
131
|
-
if (!session) {
|
|
132
|
-
return NextResponse.json({
|
|
133
|
-
error: 'Session not found',
|
|
134
|
-
code: 'SESSION_NOT_FOUND'
|
|
135
|
-
}, { status: 404 });
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Update session with new data
|
|
139
|
-
const updatedSession = {
|
|
140
|
-
...session,
|
|
141
|
-
...(access_token ? { accessToken: access_token } : {}),
|
|
142
|
-
...(refresh_token ? { refreshToken: refresh_token } : {}),
|
|
143
|
-
...(typeof twoFactorComplete === 'boolean' ? { twoFactorComplete } : {}),
|
|
144
|
-
...(twoFactorMethod ? { twoFactorMethod } : {}),
|
|
145
|
-
...(metadata ? {
|
|
146
|
-
metadata: {
|
|
147
|
-
...(session.metadata || {}),
|
|
148
|
-
...metadata,
|
|
149
|
-
updatedAt: new Date().toISOString()
|
|
150
|
-
}
|
|
151
|
-
} : {})
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
await updateSession(sessionToken, updatedSession);
|
|
155
|
-
|
|
156
|
-
return NextResponse.json({
|
|
157
|
-
success: true,
|
|
158
|
-
message: 'Session updated successfully'
|
|
159
|
-
});
|
|
160
|
-
} catch (error) {
|
|
161
|
-
console.error('[SESSION_ROUTE] Error updating session:', error);
|
|
162
|
-
return NextResponse.json({
|
|
163
|
-
error: 'Failed to update session',
|
|
164
|
-
details: error instanceof Error ? error.message : 'Unknown error'
|
|
165
|
-
}, { status: 500 });
|
|
166
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Ready-to-Use Session Management Route
|
|
3
|
+
*
|
|
4
|
+
* Provides pre-configured session handlers for checking and updating session state.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // app/api/auth/session/route.ts
|
|
9
|
+
* export { GET, POST } from '@payez/next-mvp/routes/auth/session';
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* @version 2.0.0
|
|
13
|
+
* @since auth-ready-v2
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
17
|
+
import { getSession as getBetterAuthSession } from '../../server/auth';
|
|
18
|
+
import { getSession as getRedisSession, updateSession } from '../../lib/session-store';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* GET /api/auth/session - Check current session status
|
|
22
|
+
*
|
|
23
|
+
* Returns the current session information including:
|
|
24
|
+
* - User details
|
|
25
|
+
* - Token expiry status
|
|
26
|
+
* - Session validity
|
|
27
|
+
*/
|
|
28
|
+
export async function GET(req: NextRequest) {
|
|
29
|
+
try {
|
|
30
|
+
const authSession = await getBetterAuthSession(req);
|
|
31
|
+
|
|
32
|
+
if (!authSession) {
|
|
33
|
+
console.warn('[SESSION_ROUTE] Better Auth session not found');
|
|
34
|
+
// MUST return empty {} — useSession() treats any non-empty
|
|
35
|
+
// response object as "authenticated", causing redirect loops on login page.
|
|
36
|
+
return NextResponse.json({});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const redisSessionId = authSession.session?.token;
|
|
40
|
+
|
|
41
|
+
console.log('[SESSION_ROUTE] Session found:', {
|
|
42
|
+
userId: authSession.user?.id,
|
|
43
|
+
email: authSession.user?.email,
|
|
44
|
+
name: authSession.user?.name,
|
|
45
|
+
redisSessionId: redisSessionId ? redisSessionId.substring(0, 8) + '...' : 'MISSING',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Fetch full session data from Redis
|
|
49
|
+
const session = redisSessionId ? await getRedisSession(redisSessionId) : null;
|
|
50
|
+
|
|
51
|
+
console.log('[SESSION_ROUTE] Redis session:', {
|
|
52
|
+
found: !!session,
|
|
53
|
+
userId: session?.userId,
|
|
54
|
+
roles: session?.roles,
|
|
55
|
+
hasAccessToken: !!session?.idpAccessToken,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Return session format with Redis data
|
|
59
|
+
// useSession() expects: { user: {...}, expires: "..." }
|
|
60
|
+
// We enrich with all session data from Redis
|
|
61
|
+
return NextResponse.json({
|
|
62
|
+
user: {
|
|
63
|
+
id: session?.userId || authSession.user?.id,
|
|
64
|
+
email: session?.email || authSession.user?.email,
|
|
65
|
+
name: session?.name || authSession.user?.name,
|
|
66
|
+
image: authSession.user?.image || session?.image || null,
|
|
67
|
+
// Redis session data
|
|
68
|
+
roles: session?.roles || [],
|
|
69
|
+
twoFactorSessionVerified: session?.mfaVerified || false,
|
|
70
|
+
requiresTwoFactor: !session?.mfaVerified,
|
|
71
|
+
authenticationMethods: session?.authenticationMethods,
|
|
72
|
+
authenticationLevel: session?.authenticationLevel,
|
|
73
|
+
mfaCompletedAt: session?.mfaCompletedAt,
|
|
74
|
+
mfaExpiresAt: session?.mfaExpiresAt,
|
|
75
|
+
mfaValidityHours: session?.mfaValidityHours,
|
|
76
|
+
oauthProvider: session?.oauthProvider,
|
|
77
|
+
idpClientId: session?.idpClientId,
|
|
78
|
+
merchantId: session?.merchantId,
|
|
79
|
+
},
|
|
80
|
+
// Session tokens
|
|
81
|
+
sessionToken: redisSessionId,
|
|
82
|
+
accessToken: session?.idpAccessToken,
|
|
83
|
+
refreshToken: session?.idpRefreshToken,
|
|
84
|
+
accessTokenExpires: session?.idpAccessTokenExpires,
|
|
85
|
+
expires: authSession.session?.expiresAt
|
|
86
|
+
? new Date(authSession.session.expiresAt).toISOString()
|
|
87
|
+
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('[SESSION_ROUTE] Error checking session:', error);
|
|
91
|
+
return NextResponse.json({
|
|
92
|
+
error: 'Failed to check session',
|
|
93
|
+
details: error instanceof Error ? error.message : 'Unknown error'
|
|
94
|
+
}, { status: 500 });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* POST /api/auth/session - Update session data
|
|
100
|
+
*
|
|
101
|
+
* Allows updating session metadata (not tokens).
|
|
102
|
+
* Token refresh should use the /api/auth/refresh endpoint.
|
|
103
|
+
*
|
|
104
|
+
* Body:
|
|
105
|
+
* - metadata: object - Custom metadata to store in session
|
|
106
|
+
*/
|
|
107
|
+
export async function POST(req: NextRequest) {
|
|
108
|
+
try {
|
|
109
|
+
const authSession = await getBetterAuthSession(req);
|
|
110
|
+
|
|
111
|
+
if (!authSession) {
|
|
112
|
+
return NextResponse.json({
|
|
113
|
+
error: 'No session found',
|
|
114
|
+
code: 'UNAUTHORIZED'
|
|
115
|
+
}, { status: 401 });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const sessionToken = authSession.session?.token;
|
|
119
|
+
if (!sessionToken) {
|
|
120
|
+
return NextResponse.json({
|
|
121
|
+
error: 'Invalid session',
|
|
122
|
+
code: 'INVALID_SESSION'
|
|
123
|
+
}, { status: 400 });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const body = await req.json();
|
|
127
|
+
const { metadata, access_token, refresh_token, twoFactorComplete, twoFactorMethod } = body;
|
|
128
|
+
|
|
129
|
+
// Get current session from Redis
|
|
130
|
+
const session = await getRedisSession(sessionToken);
|
|
131
|
+
if (!session) {
|
|
132
|
+
return NextResponse.json({
|
|
133
|
+
error: 'Session not found',
|
|
134
|
+
code: 'SESSION_NOT_FOUND'
|
|
135
|
+
}, { status: 404 });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Update session with new data
|
|
139
|
+
const updatedSession = {
|
|
140
|
+
...session,
|
|
141
|
+
...(access_token ? { accessToken: access_token } : {}),
|
|
142
|
+
...(refresh_token ? { refreshToken: refresh_token } : {}),
|
|
143
|
+
...(typeof twoFactorComplete === 'boolean' ? { twoFactorComplete } : {}),
|
|
144
|
+
...(twoFactorMethod ? { twoFactorMethod } : {}),
|
|
145
|
+
...(metadata ? {
|
|
146
|
+
metadata: {
|
|
147
|
+
...(session.metadata || {}),
|
|
148
|
+
...metadata,
|
|
149
|
+
updatedAt: new Date().toISOString()
|
|
150
|
+
}
|
|
151
|
+
} : {})
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
await updateSession(sessionToken, updatedSession);
|
|
155
|
+
|
|
156
|
+
return NextResponse.json({
|
|
157
|
+
success: true,
|
|
158
|
+
message: 'Session updated successfully'
|
|
159
|
+
});
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('[SESSION_ROUTE] Error updating session:', error);
|
|
162
|
+
return NextResponse.json({
|
|
163
|
+
error: 'Failed to update session',
|
|
164
|
+
details: error instanceof Error ? error.message : 'Unknown error'
|
|
165
|
+
}, { status: 500 });
|
|
166
|
+
}
|
|
167
167
|
}
|