sitepaige-mcp-server 1.0.3 → 1.2.0
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/components/IntegrationComponent.tsx +1 -0
- package/components/admin.tsx +30 -27
- package/components/auth.tsx +9 -9
- package/components/cta.tsx +3 -10
- package/components/headerlogin.tsx +19 -37
- package/components/login.tsx +7 -4
- package/components/logincallback.tsx +1 -0
- package/components/menu.tsx +0 -6
- package/components/profile.tsx +304 -47
- package/defaultapp/api/Auth/resend-verification/route.ts +1 -1
- package/defaultapp/api/Auth/route.ts +39 -39
- package/defaultapp/api/Auth/signup/route.ts +1 -1
- package/defaultapp/api/Auth/verify-email/route.ts +6 -6
- package/defaultapp/api/admin/users/route.ts +5 -3
- package/defaultapp/api/profile/route.ts +154 -0
- package/defaultapp/auth/auth.ts +9 -9
- package/defaultapp/db-mysql.ts +1 -1
- package/defaultapp/db-postgres.ts +1 -1
- package/defaultapp/db-sqlite.ts +1 -1
- package/defaultapp/db-users.ts +64 -64
- package/defaultapp/profile/page.tsx +6 -0
- package/dist/blueprintWriter.js.map +1 -1
- package/dist/components/IntegrationComponent.tsx +1 -0
- package/dist/components/admin.tsx +30 -27
- package/dist/components/auth.tsx +9 -9
- package/dist/components/cta.tsx +3 -10
- package/dist/components/headerlogin.tsx +19 -37
- package/dist/components/login.tsx +7 -4
- package/dist/components/logincallback.tsx +1 -0
- package/dist/components/menu.tsx +0 -6
- package/dist/components/profile.tsx +304 -47
- package/dist/defaultapp/api/Auth/resend-verification/route.ts +1 -1
- package/dist/defaultapp/api/Auth/route.ts +39 -39
- package/dist/defaultapp/api/Auth/signup/route.ts +1 -1
- package/dist/defaultapp/api/Auth/verify-email/route.ts +6 -6
- package/dist/defaultapp/api/admin/users/route.ts +5 -3
- package/dist/defaultapp/api/profile/route.ts +154 -0
- package/dist/defaultapp/auth/auth.ts +9 -9
- package/dist/defaultapp/db-mysql.ts +1 -1
- package/dist/defaultapp/db-postgres.ts +1 -1
- package/dist/defaultapp/db-sqlite.ts +1 -1
- package/dist/defaultapp/db-users.ts +64 -64
- package/dist/defaultapp/profile/page.tsx +6 -0
- package/dist/generators/env-example-template.txt +4 -3
- package/dist/generators/sql.js +11 -3
- package/dist/generators/sql.js.map +1 -1
- package/dist/generators/views.js +2 -2
- package/dist/sitepaige.js +2 -1
- package/dist/sitepaige.js.map +1 -1
- package/package.json +1 -1
- package/defaultapp/admin/page.tsx +0 -6
|
@@ -1,85 +1,342 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Sitepaige Components v1.0.0
|
|
3
|
-
Sitepaige components are automatically added to your project the first time it is built, and are only added again if the "Build Components" button is
|
|
4
|
-
checked in the system build settings. It is safe to modify this file without it being overwritten unless that setting is selected.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
'use client';
|
|
8
2
|
|
|
9
3
|
import { useState, useEffect } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
// import { useCsrfToken } from './csrf-provider'; // Disabled for now
|
|
10
6
|
|
|
11
|
-
interface
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
interface UserData {
|
|
8
|
+
userid: string;
|
|
9
|
+
username: string;
|
|
10
|
+
avatarurl?: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
userlevel: number;
|
|
13
|
+
source: string;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export default function Profile() {
|
|
18
|
-
const
|
|
17
|
+
const router = useRouter();
|
|
18
|
+
// const csrfToken = useCsrfToken(); // Disabled for now
|
|
19
|
+
const [userData, setUserData] = useState<UserData | null>(null);
|
|
19
20
|
const [loading, setLoading] = useState(true);
|
|
20
21
|
const [error, setError] = useState<string | null>(null);
|
|
22
|
+
const [success, setSuccess] = useState<string | null>(null);
|
|
23
|
+
|
|
24
|
+
// Password change state
|
|
25
|
+
const [currentPassword, setCurrentPassword] = useState('');
|
|
26
|
+
const [newPassword, setNewPassword] = useState('');
|
|
27
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
28
|
+
const [isChangingPassword, setIsChangingPassword] = useState(false);
|
|
29
|
+
|
|
30
|
+
// Account deletion state
|
|
31
|
+
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
32
|
+
const [deleteConfirmText, setDeleteConfirmText] = useState('');
|
|
33
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
21
34
|
|
|
22
35
|
useEffect(() => {
|
|
23
|
-
|
|
36
|
+
fetchUserData();
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const fetchUserData = async () => {
|
|
24
40
|
try {
|
|
41
|
+
setLoading(true);
|
|
25
42
|
const response = await fetch('/api/Auth', {
|
|
26
|
-
|
|
43
|
+
credentials: 'include'
|
|
27
44
|
});
|
|
28
45
|
|
|
29
46
|
if (!response.ok) {
|
|
30
|
-
|
|
47
|
+
throw new Error('Failed to fetch user data');
|
|
31
48
|
}
|
|
32
49
|
|
|
33
50
|
const data = await response.json();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const userData = data.user;
|
|
41
|
-
setProfile({
|
|
42
|
-
ID: userData.userid,
|
|
43
|
-
UserName: userData.UserName,
|
|
44
|
-
AvatarURL: userData.AvatarURL
|
|
45
|
-
});
|
|
51
|
+
if (data.user) {
|
|
52
|
+
setUserData(data.user);
|
|
53
|
+
} else {
|
|
54
|
+
// Not authenticated, redirect to login
|
|
55
|
+
router.push('/login');
|
|
56
|
+
}
|
|
46
57
|
} catch (err) {
|
|
47
|
-
|
|
58
|
+
setError(err instanceof Error ? err.message : 'Failed to load profile');
|
|
48
59
|
} finally {
|
|
49
60
|
setLoading(false);
|
|
50
61
|
}
|
|
51
62
|
};
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
const handlePasswordChange = async (e: React.FormEvent) => {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
setError(null);
|
|
67
|
+
setSuccess(null);
|
|
68
|
+
|
|
69
|
+
// Validate passwords
|
|
70
|
+
if (newPassword !== confirmPassword) {
|
|
71
|
+
setError('Passwords do not match');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (newPassword.length < 8) {
|
|
76
|
+
setError('Password must be at least 8 characters long');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setIsChangingPassword(true);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const response = await fetch('/api/profile', {
|
|
84
|
+
method: 'PUT',
|
|
85
|
+
headers: {
|
|
86
|
+
'Content-Type': 'application/json'
|
|
87
|
+
// 'X-CSRF-Token': csrfToken || '' // Disabled for now
|
|
88
|
+
},
|
|
89
|
+
credentials: 'include',
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
currentPassword,
|
|
92
|
+
newPassword
|
|
93
|
+
})
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const data = await response.json();
|
|
97
|
+
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new Error(data.error || 'Failed to update password');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setSuccess('Password updated successfully');
|
|
103
|
+
setCurrentPassword('');
|
|
104
|
+
setNewPassword('');
|
|
105
|
+
setConfirmPassword('');
|
|
106
|
+
} catch (err) {
|
|
107
|
+
setError(err instanceof Error ? err.message : 'Failed to update password');
|
|
108
|
+
} finally {
|
|
109
|
+
setIsChangingPassword(false);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
55
112
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
113
|
+
const handleDeleteAccount = async () => {
|
|
114
|
+
if (deleteConfirmText !== 'DELETE') {
|
|
115
|
+
setError('Please type DELETE to confirm account deletion');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
setIsDeleting(true);
|
|
120
|
+
setError(null);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const response = await fetch('/api/profile', {
|
|
124
|
+
method: 'DELETE',
|
|
125
|
+
headers: {
|
|
126
|
+
'Content-Type': 'application/json'
|
|
127
|
+
// 'X-CSRF-Token': csrfToken || '' // Disabled for now
|
|
128
|
+
},
|
|
129
|
+
credentials: 'include',
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
confirmDelete: true
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const data = await response.json();
|
|
136
|
+
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
throw new Error(data.error || 'Failed to delete account');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Redirect to home page after successful deletion
|
|
142
|
+
window.location.href = '/';
|
|
143
|
+
} catch (err) {
|
|
144
|
+
setError(err instanceof Error ? err.message : 'Failed to delete account');
|
|
145
|
+
setIsDeleting(false);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const getUserLevelName = (level: number) => {
|
|
150
|
+
switch (level) {
|
|
151
|
+
case 0: return 'Guest';
|
|
152
|
+
case 1: return 'Registered User';
|
|
153
|
+
case 2: return 'Admin';
|
|
154
|
+
default: return 'Unknown';
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const getAuthMethodName = (source: string) => {
|
|
159
|
+
switch (source) {
|
|
160
|
+
case 'userpass': return 'Email/Password';
|
|
161
|
+
case 'google': return 'Google';
|
|
162
|
+
case 'facebook': return 'Facebook';
|
|
163
|
+
case 'apple': return 'Apple';
|
|
164
|
+
case 'github': return 'GitHub';
|
|
165
|
+
default: return source;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
59
168
|
|
|
60
|
-
if (
|
|
61
|
-
return
|
|
169
|
+
if (loading) {
|
|
170
|
+
return (
|
|
171
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
172
|
+
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-600"></div>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
62
175
|
}
|
|
63
176
|
|
|
64
|
-
if (!
|
|
65
|
-
return
|
|
177
|
+
if (!userData) {
|
|
178
|
+
return null; // Will redirect to login
|
|
66
179
|
}
|
|
67
180
|
|
|
68
181
|
return (
|
|
69
|
-
<div className="
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
182
|
+
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
|
183
|
+
<h1 className="text-3xl font-bold mb-8">Profile Settings</h1>
|
|
184
|
+
|
|
185
|
+
{/* User Information */}
|
|
186
|
+
<div className="bg-white rounded-lg shadow p-6 mb-8">
|
|
187
|
+
<h2 className="text-xl font-semibold mb-4">User Information</h2>
|
|
188
|
+
<div className="grid md:grid-cols-2 gap-4">
|
|
189
|
+
<div className="flex items-center space-x-4">
|
|
190
|
+
{userData.avatarurl ? (
|
|
191
|
+
<img
|
|
192
|
+
src={userData.avatarurl}
|
|
193
|
+
alt={userData.username}
|
|
194
|
+
className="w-16 h-16 rounded-full"
|
|
76
195
|
referrerPolicy="no-referrer"
|
|
77
196
|
crossOrigin="anonymous"
|
|
78
197
|
/>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
198
|
+
) : (
|
|
199
|
+
<div className="w-16 h-16 rounded-full bg-gray-300 flex items-center justify-center">
|
|
200
|
+
<span className="text-2xl text-gray-600">
|
|
201
|
+
{userData.username.charAt(0).toUpperCase()}
|
|
202
|
+
</span>
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
<div>
|
|
206
|
+
<p className="font-semibold">{userData.username}</p>
|
|
207
|
+
{userData.email && (
|
|
208
|
+
<p className="text-sm text-gray-600">{userData.email}</p>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
<div className="space-y-2">
|
|
213
|
+
<p><span className="font-semibold">User Level:</span> {getUserLevelName(userData.userlevel)}</p>
|
|
214
|
+
<p><span className="font-semibold">Auth Method:</span> {getAuthMethodName(userData.source)}</p>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
{/* Error and Success Messages */}
|
|
220
|
+
{error && (
|
|
221
|
+
<div className="mb-4 p-4 bg-red-50 border border-red-200 text-red-700 rounded">
|
|
222
|
+
{error}
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
{success && (
|
|
226
|
+
<div className="mb-4 p-4 bg-green-50 border border-green-200 text-green-700 rounded">
|
|
227
|
+
{success}
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
|
|
231
|
+
{/* Password Change Section - Only for email/password users */}
|
|
232
|
+
{userData.source === 'userpass' && (
|
|
233
|
+
<div className="bg-white rounded-lg shadow p-6 mb-8">
|
|
234
|
+
<h2 className="text-xl font-semibold mb-4">Change Password</h2>
|
|
235
|
+
<form onSubmit={handlePasswordChange} className="space-y-4">
|
|
236
|
+
<div>
|
|
237
|
+
<label htmlFor="currentPassword" className="block text-sm font-medium text-gray-700 mb-1">
|
|
238
|
+
Current Password
|
|
239
|
+
</label>
|
|
240
|
+
<input
|
|
241
|
+
type="password"
|
|
242
|
+
id="currentPassword"
|
|
243
|
+
value={currentPassword}
|
|
244
|
+
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
245
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
246
|
+
required
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
<div>
|
|
250
|
+
<label htmlFor="newPassword" className="block text-sm font-medium text-gray-700 mb-1">
|
|
251
|
+
New Password
|
|
252
|
+
</label>
|
|
253
|
+
<input
|
|
254
|
+
type="password"
|
|
255
|
+
id="newPassword"
|
|
256
|
+
value={newPassword}
|
|
257
|
+
onChange={(e) => setNewPassword(e.target.value)}
|
|
258
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
259
|
+
required
|
|
260
|
+
minLength={8}
|
|
261
|
+
/>
|
|
262
|
+
<p className="text-xs text-gray-500 mt-1">Must be at least 8 characters long</p>
|
|
263
|
+
</div>
|
|
264
|
+
<div>
|
|
265
|
+
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
|
|
266
|
+
Confirm New Password
|
|
267
|
+
</label>
|
|
268
|
+
<input
|
|
269
|
+
type="password"
|
|
270
|
+
id="confirmPassword"
|
|
271
|
+
value={confirmPassword}
|
|
272
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
273
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
274
|
+
required
|
|
275
|
+
/>
|
|
276
|
+
</div>
|
|
277
|
+
<button
|
|
278
|
+
type="submit"
|
|
279
|
+
disabled={isChangingPassword}
|
|
280
|
+
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
281
|
+
>
|
|
282
|
+
{isChangingPassword ? 'Updating...' : 'Update Password'}
|
|
283
|
+
</button>
|
|
284
|
+
</form>
|
|
285
|
+
</div>
|
|
286
|
+
)}
|
|
287
|
+
|
|
288
|
+
{/* Delete Account Section */}
|
|
289
|
+
<div className="bg-white rounded-lg shadow p-6">
|
|
290
|
+
<h2 className="text-xl font-semibold mb-4 text-red-600">Danger Zone</h2>
|
|
291
|
+
<div className="border border-red-200 rounded-md p-4">
|
|
292
|
+
<h3 className="font-semibold mb-2">Delete Account</h3>
|
|
293
|
+
<p className="text-sm text-gray-600 mb-4">
|
|
294
|
+
Once you delete your account, there is no going back. Please be certain.
|
|
295
|
+
</p>
|
|
296
|
+
|
|
297
|
+
{!showDeleteConfirm ? (
|
|
298
|
+
<button
|
|
299
|
+
onClick={() => setShowDeleteConfirm(true)}
|
|
300
|
+
className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700"
|
|
301
|
+
>
|
|
302
|
+
Delete Account
|
|
303
|
+
</button>
|
|
304
|
+
) : (
|
|
305
|
+
<div className="space-y-4">
|
|
306
|
+
<p className="text-sm font-semibold">
|
|
307
|
+
To confirm deletion, please type <span className="font-mono bg-gray-100 px-1">DELETE</span> below:
|
|
308
|
+
</p>
|
|
309
|
+
<input
|
|
310
|
+
type="text"
|
|
311
|
+
value={deleteConfirmText}
|
|
312
|
+
onChange={(e) => setDeleteConfirmText(e.target.value)}
|
|
313
|
+
placeholder="Type DELETE to confirm"
|
|
314
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-500"
|
|
315
|
+
/>
|
|
316
|
+
<div className="flex space-x-4">
|
|
317
|
+
<button
|
|
318
|
+
onClick={handleDeleteAccount}
|
|
319
|
+
disabled={isDeleting || deleteConfirmText !== 'DELETE'}
|
|
320
|
+
className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
321
|
+
>
|
|
322
|
+
{isDeleting ? 'Deleting...' : 'Confirm Delete'}
|
|
323
|
+
</button>
|
|
324
|
+
<button
|
|
325
|
+
onClick={() => {
|
|
326
|
+
setShowDeleteConfirm(false);
|
|
327
|
+
setDeleteConfirmText('');
|
|
328
|
+
setError(null);
|
|
329
|
+
}}
|
|
330
|
+
disabled={isDeleting}
|
|
331
|
+
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400"
|
|
332
|
+
>
|
|
333
|
+
Cancel
|
|
334
|
+
</button>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
)}
|
|
338
|
+
</div>
|
|
82
339
|
</div>
|
|
83
340
|
</div>
|
|
84
341
|
);
|
|
85
|
-
}
|
|
342
|
+
}
|
|
@@ -36,7 +36,7 @@ export async function POST(request: Request) {
|
|
|
36
36
|
const { verificationToken } = result;
|
|
37
37
|
|
|
38
38
|
// Get site domain from environment or default
|
|
39
|
-
const siteDomain = process.env.
|
|
39
|
+
const siteDomain = process.env.NODE_ENV === 'production' ? process.env.DOMAIN : process.env.LOCAL_DOMAIN;
|
|
40
40
|
const verificationUrl = `${siteDomain}/api/Auth/verify-email?token=${verificationToken}`;
|
|
41
41
|
|
|
42
42
|
// Send verification email
|
|
@@ -41,7 +41,7 @@ export async function POST(request: Request) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// Handle username/password authentication
|
|
44
|
-
if (provider === '
|
|
44
|
+
if (provider === 'userpass') {
|
|
45
45
|
if (!email || !password) {
|
|
46
46
|
return NextResponse.json(
|
|
47
47
|
{ error: 'Email and password are required' },
|
|
@@ -69,7 +69,7 @@ export async function POST(request: Request) {
|
|
|
69
69
|
// Create or update user in the main Users table
|
|
70
70
|
const user = await upsertUser(
|
|
71
71
|
`password_${authRecord.id}`, // Unique OAuth ID for password users
|
|
72
|
-
'
|
|
72
|
+
'userpass' as any, // Source type
|
|
73
73
|
email.split('@')[0], // Username from email
|
|
74
74
|
email,
|
|
75
75
|
undefined // No avatar for password auth
|
|
@@ -77,14 +77,14 @@ export async function POST(request: Request) {
|
|
|
77
77
|
|
|
78
78
|
// Delete existing sessions for this user
|
|
79
79
|
const existingSessions = await db_query(db,
|
|
80
|
-
"SELECT
|
|
80
|
+
"SELECT id FROM usersession WHERE userid = ?",
|
|
81
81
|
[user.userid]
|
|
82
82
|
);
|
|
83
83
|
|
|
84
84
|
if (existingSessions && existingSessions.length > 0) {
|
|
85
|
-
const sessionIds = existingSessions.map(session => session.
|
|
85
|
+
const sessionIds = existingSessions.map(session => session.id);
|
|
86
86
|
const placeholders = sessionIds.map(() => '?').join(',');
|
|
87
|
-
await db_query(db, `DELETE FROM usersession WHERE
|
|
87
|
+
await db_query(db, `DELETE FROM usersession WHERE id IN (${placeholders})`, sessionIds);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// Generate secure session token and ID
|
|
@@ -93,7 +93,7 @@ export async function POST(request: Request) {
|
|
|
93
93
|
|
|
94
94
|
// Create new session with secure token
|
|
95
95
|
await db_query(db,
|
|
96
|
-
"INSERT INTO usersession (
|
|
96
|
+
"INSERT INTO usersession (id, sessiontoken, userid, expirationdate) VALUES (?, ?, ?, ?)",
|
|
97
97
|
[sessionId, sessionToken, user.userid, new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()]
|
|
98
98
|
);
|
|
99
99
|
|
|
@@ -112,10 +112,10 @@ export async function POST(request: Request) {
|
|
|
112
112
|
// Create a completely clean object to avoid any database result object issues
|
|
113
113
|
const cleanUserData = {
|
|
114
114
|
userid: String(user.userid),
|
|
115
|
-
userName: String(user.
|
|
116
|
-
avatarURL: String(user.
|
|
117
|
-
userLevel: Number(user.
|
|
118
|
-
isAdmin: Number(user.
|
|
115
|
+
userName: String(user.username),
|
|
116
|
+
avatarURL: String(user.avatarurl || ''),
|
|
117
|
+
userLevel: Number(user.userlevel),
|
|
118
|
+
isAdmin: Number(user.userlevel) === 2
|
|
119
119
|
};
|
|
120
120
|
|
|
121
121
|
return NextResponse.json({
|
|
@@ -152,7 +152,7 @@ export async function POST(request: Request) {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
let userData = {
|
|
155
|
-
|
|
155
|
+
id: '',
|
|
156
156
|
name: '',
|
|
157
157
|
email: '',
|
|
158
158
|
avatar_url: '',
|
|
@@ -203,28 +203,28 @@ export async function POST(request: Request) {
|
|
|
203
203
|
switch (validProvider) {
|
|
204
204
|
|
|
205
205
|
case 'google':
|
|
206
|
-
userData.
|
|
206
|
+
userData.id = fetchedUserData.id;
|
|
207
207
|
userData.name = fetchedUserData.name;
|
|
208
208
|
userData.email = fetchedUserData.email;
|
|
209
209
|
userData.avatar_url = fetchedUserData.picture;
|
|
210
210
|
break;
|
|
211
211
|
|
|
212
212
|
case 'facebook':
|
|
213
|
-
userData.
|
|
213
|
+
userData.id = fetchedUserData.id;
|
|
214
214
|
userData.name = fetchedUserData.name;
|
|
215
215
|
userData.email = fetchedUserData.email;
|
|
216
216
|
userData.avatar_url = fetchedUserData.picture?.data?.url;
|
|
217
217
|
break;
|
|
218
218
|
|
|
219
219
|
case 'apple':
|
|
220
|
-
userData.
|
|
220
|
+
userData.id = fetchedUserData.sub;
|
|
221
221
|
userData.name = `${fetchedUserData.given_name || ''} ${fetchedUserData.family_name || ''}`.trim();
|
|
222
222
|
userData.email = fetchedUserData.email;
|
|
223
223
|
// Apple doesn't provide avatar URL
|
|
224
224
|
break;
|
|
225
225
|
|
|
226
226
|
case 'github':
|
|
227
|
-
userData.
|
|
227
|
+
userData.id = fetchedUserData.id?.toString();
|
|
228
228
|
userData.name = fetchedUserData.name || fetchedUserData.login;
|
|
229
229
|
userData.email = fetchedUserData.email;
|
|
230
230
|
userData.avatar_url = fetchedUserData.avatar_url;
|
|
@@ -239,7 +239,7 @@ export async function POST(request: Request) {
|
|
|
239
239
|
|
|
240
240
|
// Create or update user using the new user management system
|
|
241
241
|
const user = await upsertUser(
|
|
242
|
-
userData.
|
|
242
|
+
userData.id,
|
|
243
243
|
validProvider,
|
|
244
244
|
userData.name,
|
|
245
245
|
userData.email,
|
|
@@ -257,14 +257,14 @@ export async function POST(request: Request) {
|
|
|
257
257
|
|
|
258
258
|
// Delete existing sessions for this user
|
|
259
259
|
const existingSessions = await db_query(db,
|
|
260
|
-
"SELECT
|
|
260
|
+
"SELECT id FROM usersession WHERE userid = ?",
|
|
261
261
|
[user.userid]
|
|
262
262
|
);
|
|
263
263
|
|
|
264
264
|
if (existingSessions && existingSessions.length > 0) {
|
|
265
|
-
const sessionIds = existingSessions.map(session => session.
|
|
265
|
+
const sessionIds = existingSessions.map(session => session.id);
|
|
266
266
|
const placeholders = sessionIds.map(() => '?').join(',');
|
|
267
|
-
await db_query(db, `DELETE FROM usersession WHERE
|
|
267
|
+
await db_query(db, `DELETE FROM usersession WHERE id IN (${placeholders})`, sessionIds);
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
// Generate secure session token and ID
|
|
@@ -273,7 +273,7 @@ export async function POST(request: Request) {
|
|
|
273
273
|
|
|
274
274
|
// Create new session with secure token
|
|
275
275
|
await db_query(db,
|
|
276
|
-
"INSERT INTO usersession (
|
|
276
|
+
"INSERT INTO usersession (id, sessiontoken, userid, expirationdate) VALUES (?, ?, ?, ?)",
|
|
277
277
|
[sessionId, sessionToken, user.userid, new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()]
|
|
278
278
|
);
|
|
279
279
|
|
|
@@ -292,10 +292,10 @@ export async function POST(request: Request) {
|
|
|
292
292
|
// Create a completely clean object to avoid any database result object issues
|
|
293
293
|
const cleanUserData = {
|
|
294
294
|
userid: String(user.userid),
|
|
295
|
-
userName: String(user.
|
|
296
|
-
avatarURL: String(user.
|
|
297
|
-
userLevel: Number(user.
|
|
298
|
-
isAdmin: Number(user.
|
|
295
|
+
userName: String(user.username),
|
|
296
|
+
avatarURL: String(user.avatarurl || ''),
|
|
297
|
+
userLevel: Number(user.userlevel),
|
|
298
|
+
isAdmin: Number(user.userlevel) === 2
|
|
299
299
|
};
|
|
300
300
|
|
|
301
301
|
return NextResponse.json({
|
|
@@ -346,13 +346,13 @@ export async function GET() {
|
|
|
346
346
|
const response = NextResponse.json({
|
|
347
347
|
user: {
|
|
348
348
|
userid: sessionData.user.userid,
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
349
|
+
username: sessionData.user.username,
|
|
350
|
+
avatarurl: sessionData.user.avatarurl,
|
|
351
|
+
email: sessionData.user.email,
|
|
352
|
+
userlevel: sessionData.user.userlevel,
|
|
353
|
+
isadmin: sessionData.user.userlevel === 2,
|
|
354
|
+
source: sessionData.user.source,
|
|
355
|
+
lastlogindate: sessionData.user.lastlogindate
|
|
356
356
|
}
|
|
357
357
|
});
|
|
358
358
|
|
|
@@ -375,13 +375,13 @@ export async function GET() {
|
|
|
375
375
|
return NextResponse.json({
|
|
376
376
|
user: {
|
|
377
377
|
userid: sessionData.user.userid,
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
378
|
+
username: sessionData.user.username,
|
|
379
|
+
avatarurl: sessionData.user.avatarurl,
|
|
380
|
+
email: sessionData.user.email,
|
|
381
|
+
userlevel: sessionData.user.userlevel,
|
|
382
|
+
isadmin: sessionData.user.userlevel === 2,
|
|
383
|
+
source: sessionData.user.source,
|
|
384
|
+
lastlogindate: sessionData.user.lastlogindate
|
|
385
385
|
}
|
|
386
386
|
});
|
|
387
387
|
|
|
@@ -410,7 +410,7 @@ export async function DELETE(request: Request) {
|
|
|
410
410
|
|
|
411
411
|
// Delete session from database using the actual session token
|
|
412
412
|
await db_query(db,
|
|
413
|
-
"DELETE FROM usersession WHERE
|
|
413
|
+
"DELETE FROM usersession WHERE sessiontoken = ?",
|
|
414
414
|
[sessionToken]
|
|
415
415
|
);
|
|
416
416
|
|
|
@@ -37,7 +37,7 @@ export async function POST(request: Request) {
|
|
|
37
37
|
const { passwordAuth, verificationToken } = await createPasswordAuth(email, password);
|
|
38
38
|
|
|
39
39
|
// Get site domain from environment or default
|
|
40
|
-
const siteDomain = process.env.
|
|
40
|
+
const siteDomain = process.env.NODE_ENV === 'production' ? process.env.DOMAIN : process.env.LOCAL_DOMAIN;
|
|
41
41
|
const verificationUrl = `${siteDomain}/api/Auth/verify-email?token=${verificationToken}`;
|
|
42
42
|
|
|
43
43
|
// Send verification email
|
|
@@ -53,14 +53,14 @@ export async function GET(request: Request) {
|
|
|
53
53
|
|
|
54
54
|
// Delete existing sessions for this user
|
|
55
55
|
const existingSessions = await db_query(db,
|
|
56
|
-
"SELECT
|
|
56
|
+
"SELECT id FROM usersession WHERE userid = ?",
|
|
57
57
|
[user.userid]
|
|
58
58
|
);
|
|
59
59
|
|
|
60
60
|
if (existingSessions && existingSessions.length > 0) {
|
|
61
|
-
const sessionIds = existingSessions.map(session => session.
|
|
61
|
+
const sessionIds = existingSessions.map(session => session.id);
|
|
62
62
|
const placeholders = sessionIds.map(() => '?').join(',');
|
|
63
|
-
await db_query(db, `DELETE FROM usersession WHERE
|
|
63
|
+
await db_query(db, `DELETE FROM usersession WHERE id IN (${placeholders})`, sessionIds);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// Generate secure session token and ID
|
|
@@ -69,7 +69,7 @@ export async function GET(request: Request) {
|
|
|
69
69
|
|
|
70
70
|
// Create new session with secure token
|
|
71
71
|
await db_query(db,
|
|
72
|
-
"INSERT INTO usersession (
|
|
72
|
+
"INSERT INTO usersession (id, sessiontoken, userid, expirationdate) VALUES (?, ?, ?, ?)",
|
|
73
73
|
[sessionId, sessionToken, user.userid, new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()]
|
|
74
74
|
);
|
|
75
75
|
|
|
@@ -86,7 +86,7 @@ export async function GET(request: Request) {
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
// Redirect to home page or dashboard with success message
|
|
89
|
-
const siteDomain = process.env.
|
|
89
|
+
const siteDomain = process.env.NODE_ENV === 'production' ? process.env.DOMAIN : process.env.LOCAL_DOMAIN;
|
|
90
90
|
const redirectUrl = new URL('/', siteDomain);
|
|
91
91
|
redirectUrl.searchParams.set('verified', 'true');
|
|
92
92
|
|
|
@@ -96,7 +96,7 @@ export async function GET(request: Request) {
|
|
|
96
96
|
console.error('Email verification error:', error);
|
|
97
97
|
|
|
98
98
|
// Redirect to home with error
|
|
99
|
-
const siteDomain = process.env.
|
|
99
|
+
const siteDomain = process.env.NODE_ENV === 'production' ? process.env.DOMAIN : process.env.LOCAL_DOMAIN;
|
|
100
100
|
const redirectUrl = new URL('/', siteDomain);
|
|
101
101
|
redirectUrl.searchParams.set('error', 'verification_failed');
|
|
102
102
|
|