sitepaige-mcp-server 1.0.2 → 1.1.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.
Files changed (47) hide show
  1. package/components/IntegrationComponent.tsx +1 -0
  2. package/components/admin.tsx +30 -27
  3. package/components/auth.tsx +9 -9
  4. package/components/cta.tsx +3 -10
  5. package/components/headerlogin.tsx +9 -9
  6. package/components/login.tsx +90 -11
  7. package/components/logincallback.tsx +1 -0
  8. package/components/menu.tsx +0 -6
  9. package/components/profile.tsx +12 -11
  10. package/defaultapp/api/Auth/resend-verification/route.ts +130 -0
  11. package/defaultapp/api/Auth/route.ts +39 -49
  12. package/defaultapp/api/Auth/signup/route.ts +5 -15
  13. package/defaultapp/api/Auth/verify-email/route.ts +12 -5
  14. package/defaultapp/api/admin/users/route.ts +5 -3
  15. package/defaultapp/auth/auth.ts +9 -9
  16. package/defaultapp/db-mysql.ts +1 -1
  17. package/defaultapp/db-password-auth.ts +37 -0
  18. package/defaultapp/db-postgres.ts +1 -1
  19. package/defaultapp/db-sqlite.ts +1 -1
  20. package/defaultapp/db-users.ts +73 -73
  21. package/defaultapp/middleware.ts +15 -17
  22. package/dist/components/IntegrationComponent.tsx +1 -0
  23. package/dist/components/admin.tsx +30 -27
  24. package/dist/components/auth.tsx +9 -9
  25. package/dist/components/cta.tsx +3 -10
  26. package/dist/components/headerlogin.tsx +9 -9
  27. package/dist/components/login.tsx +90 -11
  28. package/dist/components/logincallback.tsx +1 -0
  29. package/dist/components/menu.tsx +0 -6
  30. package/dist/components/profile.tsx +12 -11
  31. package/dist/defaultapp/api/Auth/resend-verification/route.ts +130 -0
  32. package/dist/defaultapp/api/Auth/route.ts +39 -49
  33. package/dist/defaultapp/api/Auth/signup/route.ts +5 -15
  34. package/dist/defaultapp/api/Auth/verify-email/route.ts +12 -5
  35. package/dist/defaultapp/api/admin/users/route.ts +5 -3
  36. package/dist/defaultapp/auth/auth.ts +9 -9
  37. package/dist/defaultapp/db-mysql.ts +1 -1
  38. package/dist/defaultapp/db-password-auth.ts +37 -0
  39. package/dist/defaultapp/db-postgres.ts +1 -1
  40. package/dist/defaultapp/db-sqlite.ts +1 -1
  41. package/dist/defaultapp/db-users.ts +73 -73
  42. package/dist/defaultapp/middleware.ts +15 -17
  43. package/dist/generators/sql.js +11 -3
  44. package/dist/generators/sql.js.map +1 -1
  45. package/dist/generators/views.js +14 -14
  46. package/dist/generators/views.js.map +1 -1
  47. package/package.json +1 -1
@@ -10,7 +10,6 @@ import * as crypto from 'node:crypto';
10
10
 
11
11
  import { db_init, db_query } from '../../db';
12
12
  import { upsertUser, storeOAuthToken, validateSession, rotateSession } from '../../db-users';
13
- import { validateCsrfToken } from '../../csrf';
14
13
 
15
14
  type OAuthProvider = 'google' | 'facebook' | 'apple' | 'github';
16
15
 
@@ -42,7 +41,7 @@ export async function POST(request: Request) {
42
41
  }
43
42
 
44
43
  // Handle username/password authentication
45
- if (provider === 'username') {
44
+ if (provider === 'userpass') {
46
45
  if (!email || !password) {
47
46
  return NextResponse.json(
48
47
  { error: 'Email and password are required' },
@@ -70,7 +69,7 @@ export async function POST(request: Request) {
70
69
  // Create or update user in the main Users table
71
70
  const user = await upsertUser(
72
71
  `password_${authRecord.id}`, // Unique OAuth ID for password users
73
- 'username' as any, // Source type
72
+ 'userpass' as any, // Source type
74
73
  email.split('@')[0], // Username from email
75
74
  email,
76
75
  undefined // No avatar for password auth
@@ -78,14 +77,14 @@ export async function POST(request: Request) {
78
77
 
79
78
  // Delete existing sessions for this user
80
79
  const existingSessions = await db_query(db,
81
- "SELECT ID FROM usersession WHERE userid = ?",
80
+ "SELECT id FROM usersession WHERE userid = ?",
82
81
  [user.userid]
83
82
  );
84
83
 
85
84
  if (existingSessions && existingSessions.length > 0) {
86
- const sessionIds = existingSessions.map(session => session.ID);
85
+ const sessionIds = existingSessions.map(session => session.id);
87
86
  const placeholders = sessionIds.map(() => '?').join(',');
88
- await db_query(db, `DELETE FROM usersession WHERE ID IN (${placeholders})`, sessionIds);
87
+ await db_query(db, `DELETE FROM usersession WHERE id IN (${placeholders})`, sessionIds);
89
88
  }
90
89
 
91
90
  // Generate secure session token and ID
@@ -94,7 +93,7 @@ export async function POST(request: Request) {
94
93
 
95
94
  // Create new session with secure token
96
95
  await db_query(db,
97
- "INSERT INTO usersession (ID, SessionToken, userid, ExpirationDate) VALUES (?, ?, ?, ?)",
96
+ "INSERT INTO usersession (id, sessiontoken, userid, expirationdate) VALUES (?, ?, ?, ?)",
98
97
  [sessionId, sessionToken, user.userid, new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()]
99
98
  );
100
99
 
@@ -113,10 +112,10 @@ export async function POST(request: Request) {
113
112
  // Create a completely clean object to avoid any database result object issues
114
113
  const cleanUserData = {
115
114
  userid: String(user.userid),
116
- userName: String(user.UserName),
117
- avatarURL: String(user.AvatarURL || ''),
118
- userLevel: Number(user.UserLevel),
119
- isAdmin: Number(user.UserLevel) === 2
115
+ userName: String(user.username),
116
+ avatarURL: String(user.avatarurl || ''),
117
+ userLevel: Number(user.userlevel),
118
+ isAdmin: Number(user.userlevel) === 2
120
119
  };
121
120
 
122
121
  return NextResponse.json({
@@ -153,7 +152,7 @@ export async function POST(request: Request) {
153
152
  }
154
153
 
155
154
  let userData = {
156
- ID: '',
155
+ id: '',
157
156
  name: '',
158
157
  email: '',
159
158
  avatar_url: '',
@@ -204,28 +203,28 @@ export async function POST(request: Request) {
204
203
  switch (validProvider) {
205
204
 
206
205
  case 'google':
207
- userData.ID = fetchedUserData.id;
206
+ userData.id = fetchedUserData.id;
208
207
  userData.name = fetchedUserData.name;
209
208
  userData.email = fetchedUserData.email;
210
209
  userData.avatar_url = fetchedUserData.picture;
211
210
  break;
212
211
 
213
212
  case 'facebook':
214
- userData.ID = fetchedUserData.id;
213
+ userData.id = fetchedUserData.id;
215
214
  userData.name = fetchedUserData.name;
216
215
  userData.email = fetchedUserData.email;
217
216
  userData.avatar_url = fetchedUserData.picture?.data?.url;
218
217
  break;
219
218
 
220
219
  case 'apple':
221
- userData.ID = fetchedUserData.sub;
220
+ userData.id = fetchedUserData.sub;
222
221
  userData.name = `${fetchedUserData.given_name || ''} ${fetchedUserData.family_name || ''}`.trim();
223
222
  userData.email = fetchedUserData.email;
224
223
  // Apple doesn't provide avatar URL
225
224
  break;
226
225
 
227
226
  case 'github':
228
- userData.ID = fetchedUserData.id?.toString();
227
+ userData.id = fetchedUserData.id?.toString();
229
228
  userData.name = fetchedUserData.name || fetchedUserData.login;
230
229
  userData.email = fetchedUserData.email;
231
230
  userData.avatar_url = fetchedUserData.avatar_url;
@@ -240,7 +239,7 @@ export async function POST(request: Request) {
240
239
 
241
240
  // Create or update user using the new user management system
242
241
  const user = await upsertUser(
243
- userData.ID,
242
+ userData.id,
244
243
  validProvider,
245
244
  userData.name,
246
245
  userData.email,
@@ -258,14 +257,14 @@ export async function POST(request: Request) {
258
257
 
259
258
  // Delete existing sessions for this user
260
259
  const existingSessions = await db_query(db,
261
- "SELECT ID FROM usersession WHERE userid = ?",
260
+ "SELECT id FROM usersession WHERE userid = ?",
262
261
  [user.userid]
263
262
  );
264
263
 
265
264
  if (existingSessions && existingSessions.length > 0) {
266
- const sessionIds = existingSessions.map(session => session.ID);
265
+ const sessionIds = existingSessions.map(session => session.id);
267
266
  const placeholders = sessionIds.map(() => '?').join(',');
268
- await db_query(db, `DELETE FROM usersession WHERE ID IN (${placeholders})`, sessionIds);
267
+ await db_query(db, `DELETE FROM usersession WHERE id IN (${placeholders})`, sessionIds);
269
268
  }
270
269
 
271
270
  // Generate secure session token and ID
@@ -274,7 +273,7 @@ export async function POST(request: Request) {
274
273
 
275
274
  // Create new session with secure token
276
275
  await db_query(db,
277
- "INSERT INTO usersession (ID, SessionToken, userid, ExpirationDate) VALUES (?, ?, ?, ?)",
276
+ "INSERT INTO usersession (id, sessiontoken, userid, expirationdate) VALUES (?, ?, ?, ?)",
278
277
  [sessionId, sessionToken, user.userid, new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()]
279
278
  );
280
279
 
@@ -293,10 +292,10 @@ export async function POST(request: Request) {
293
292
  // Create a completely clean object to avoid any database result object issues
294
293
  const cleanUserData = {
295
294
  userid: String(user.userid),
296
- userName: String(user.UserName),
297
- avatarURL: String(user.AvatarURL || ''),
298
- userLevel: Number(user.UserLevel),
299
- isAdmin: Number(user.UserLevel) === 2
295
+ userName: String(user.username),
296
+ avatarURL: String(user.avatarurl || ''),
297
+ userLevel: Number(user.userlevel),
298
+ isAdmin: Number(user.userlevel) === 2
300
299
  };
301
300
 
302
301
  return NextResponse.json({
@@ -347,13 +346,13 @@ export async function GET() {
347
346
  const response = NextResponse.json({
348
347
  user: {
349
348
  userid: sessionData.user.userid,
350
- UserName: sessionData.user.UserName,
351
- AvatarURL: sessionData.user.AvatarURL,
352
- Email: sessionData.user.Email,
353
- UserLevel: sessionData.user.UserLevel,
354
- IsAdmin: sessionData.user.UserLevel === 2,
355
- Source: sessionData.user.Source,
356
- LastLoginDate: sessionData.user.LastLoginDate
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
357
356
  }
358
357
  });
359
358
 
@@ -376,13 +375,13 @@ export async function GET() {
376
375
  return NextResponse.json({
377
376
  user: {
378
377
  userid: sessionData.user.userid,
379
- UserName: sessionData.user.UserName,
380
- AvatarURL: sessionData.user.AvatarURL,
381
- Email: sessionData.user.Email,
382
- UserLevel: sessionData.user.UserLevel,
383
- IsAdmin: sessionData.user.UserLevel === 2,
384
- Source: sessionData.user.Source,
385
- LastLoginDate: sessionData.user.LastLoginDate
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
386
385
  }
387
386
  });
388
387
 
@@ -395,15 +394,6 @@ export async function GET() {
395
394
  }
396
395
 
397
396
  export async function DELETE(request: Request) {
398
- // Validate CSRF token for logout
399
- const isValidCsrf = await validateCsrfToken(request);
400
- if (!isValidCsrf) {
401
- return NextResponse.json(
402
- { error: 'Invalid CSRF token' },
403
- { status: 403 }
404
- );
405
- }
406
-
407
397
  const db = await db_init();
408
398
 
409
399
  try {
@@ -420,7 +410,7 @@ export async function DELETE(request: Request) {
420
410
 
421
411
  // Delete session from database using the actual session token
422
412
  await db_query(db,
423
- "DELETE FROM usersession WHERE SessionToken = ?",
413
+ "DELETE FROM usersession WHERE sessiontoken = ?",
424
414
  [sessionToken]
425
415
  );
426
416
 
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  import { NextResponse } from 'next/server';
7
- import { validateCsrfToken } from '../../../csrf';
8
7
  import { createPasswordAuth } from '../../../db-password-auth';
9
8
  import { send_email } from '../../../storage/email';
10
9
 
@@ -15,15 +14,6 @@ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
15
14
  const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
16
15
 
17
16
  export async function POST(request: Request) {
18
- // Validate CSRF token
19
- const isValidCsrf = await validateCsrfToken(request);
20
- if (!isValidCsrf) {
21
- return NextResponse.json(
22
- { error: 'Invalid CSRF token' },
23
- { status: 403 }
24
- );
25
- }
26
-
27
17
  try {
28
18
  const { email, password } = await request.json();
29
19
 
@@ -62,11 +52,11 @@ export async function POST(request: Request) {
62
52
  .button {
63
53
  display: inline-block;
64
54
  padding: 12px 24px;
65
- background-color: #4F46E5;
66
- color: white;
55
+ background-color: #f0f0f0;
56
+ color: #000000;
67
57
  text-decoration: none;
68
- border-radius: 6px;
69
- font-weight: bold;
58
+ border: 1px solid #cccccc;
59
+ border-radius: 4px;
70
60
  }
71
61
  .footer { margin-top: 30px; font-size: 12px; color: #666; }
72
62
  </style>
@@ -79,7 +69,7 @@ export async function POST(request: Request) {
79
69
  <a href="${verificationUrl}" class="button">Verify Email Address</a>
80
70
  </p>
81
71
  <p>Or copy and paste this link into your browser:</p>
82
- <p style="word-break: break-all; color: #4F46E5;">${verificationUrl}</p>
72
+ <p style="word-break: break-all; color: #0066cc;">${verificationUrl}</p>
83
73
  <p>This link will expire in 24 hours.</p>
84
74
  <div class="footer">
85
75
  <p>If you didn't create an account, you can safely ignore this email.</p>
@@ -35,25 +35,32 @@ export async function GET(request: Request) {
35
35
  // Create or update user in the main Users table
36
36
  const user = await upsertUser(
37
37
  `password_${authRecord.id}`, // Unique OAuth ID for password users
38
- 'username' as any, // Source type
38
+ 'userpass' as any, // Source type
39
39
  authRecord.email.split('@')[0], // Username from email
40
40
  authRecord.email,
41
41
  undefined // No avatar for password auth
42
42
  );
43
43
 
44
+ if (!user) {
45
+ return NextResponse.json(
46
+ { error: 'Failed to create user account' },
47
+ { status: 500 }
48
+ );
49
+ }
50
+
44
51
  // Auto-login the user after verification
45
52
  const db = await db_init();
46
53
 
47
54
  // Delete existing sessions for this user
48
55
  const existingSessions = await db_query(db,
49
- "SELECT ID FROM usersession WHERE userid = ?",
56
+ "SELECT id FROM usersession WHERE userid = ?",
50
57
  [user.userid]
51
58
  );
52
59
 
53
60
  if (existingSessions && existingSessions.length > 0) {
54
- const sessionIds = existingSessions.map(session => session.ID);
61
+ const sessionIds = existingSessions.map(session => session.id);
55
62
  const placeholders = sessionIds.map(() => '?').join(',');
56
- await db_query(db, `DELETE FROM usersession WHERE ID IN (${placeholders})`, sessionIds);
63
+ await db_query(db, `DELETE FROM usersession WHERE id IN (${placeholders})`, sessionIds);
57
64
  }
58
65
 
59
66
  // Generate secure session token and ID
@@ -62,7 +69,7 @@ export async function GET(request: Request) {
62
69
 
63
70
  // Create new session with secure token
64
71
  await db_query(db,
65
- "INSERT INTO usersession (ID, SessionToken, userid, ExpirationDate) VALUES (?, ?, ?, ?)",
72
+ "INSERT INTO usersession (id, sessiontoken, userid, expirationdate) VALUES (?, ?, ?, ?)",
66
73
  [sessionId, sessionToken, user.userid, new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()]
67
74
  );
68
75
 
@@ -28,9 +28,11 @@ async function checkAdminAuth(): Promise<{ isAdmin: boolean; userId?: string }>
28
28
 
29
29
  // Get session
30
30
  const sessions = await db_query(db,
31
- "SELECT userid FROM UserSession WHERE SessionToken = ?",
31
+ "SELECT userid FROM usersession WHERE sessiontoken = ?",
32
32
  [sessionId]
33
33
  );
34
+
35
+ console.log(sessions);
34
36
 
35
37
  if (sessions.length === 0) {
36
38
  return { isAdmin: false };
@@ -38,8 +40,8 @@ async function checkAdminAuth(): Promise<{ isAdmin: boolean; userId?: string }>
38
40
 
39
41
  // Check if user is admin
40
42
  const user = await getUserByID(sessions[0].userid);
41
-
42
- if (!user || user.UserLevel !== 2) {
43
+
44
+ if (!user || user.userlevel !== 2) {
43
45
  return { isAdmin: false };
44
46
  }
45
47
 
@@ -1,14 +1,14 @@
1
1
  import { cookies } from 'next/headers';
2
2
 
3
- export async function check_auth(db: any, db_query: any): Promise<{ userid: string, UserLevel: number, UserTier: number, IsAdmin: boolean }> {
3
+ export async function check_auth(db: any, db_query: any): Promise<{ userid: string, userlevel: number, usertier: number, isadmin: boolean }> {
4
4
  const cookieStore = await cookies();
5
5
  const sessionId = cookieStore.get('session_id')?.value;
6
6
 
7
7
  const sessionInfo = {
8
8
  userid: '',
9
- UserLevel: 0,
10
- UserTier: 0,
11
- IsAdmin: false
9
+ userlevel: 0,
10
+ usertier: 0,
11
+ isadmin: false
12
12
  };
13
13
 
14
14
  if (!sessionId) {
@@ -16,16 +16,16 @@ export async function check_auth(db: any, db_query: any): Promise<{ userid: stri
16
16
  }
17
17
 
18
18
  // SQLite is case-insensitive for identifiers, but use standard column names (no quotes, correct names)
19
- const session = await db_query(db, 'SELECT userid FROM UserSession WHERE SessionToken = ?', [sessionId]);
19
+ const session = await db_query(db, 'SELECT userid FROM usersession WHERE sessiontoken = ?', [sessionId]);
20
20
  if (session.length > 0) {
21
21
  sessionInfo.userid = session[0].userid;
22
22
 
23
23
  // In your Users table, the primary key is userid, and there is no IsAdmin column, but UserLevel (2 = admin)
24
- const user = await db_query(db, 'SELECT UserLevel, UserTier FROM Users WHERE userid = ?', [session[0].userid]);
24
+ const user = await db_query(db, 'SELECT userlevel, usertier FROM users WHERE userid = ?', [session[0].userid]);
25
25
  if (user.length > 0) {
26
- sessionInfo.UserLevel = user[0].UserLevel;
27
- sessionInfo.IsAdmin = user[0].UserLevel === 2;
28
- sessionInfo.UserTier = user[0].UserTier;
26
+ sessionInfo.userlevel = user[0].userlevel;
27
+ sessionInfo.isadmin = user[0].userlevel === 2;
28
+ sessionInfo.usertier = user[0].usertier;
29
29
  }
30
30
  }
31
31
  return sessionInfo;
@@ -95,7 +95,7 @@ export async function db_query(
95
95
  */
96
96
  export function db_migrate(model: Model, dbType: string): string {
97
97
 
98
- const sanitizedTableName = model.name;
98
+ const sanitizedTableName = model.name.toLowerCase().replace(/\s+/g, '_');
99
99
 
100
100
  // Start with the model's fields
101
101
  let fields = [...model.fields];
@@ -295,6 +295,43 @@ export async function updatePassword(email: string, currentPassword: string, new
295
295
  return true;
296
296
  }
297
297
 
298
+ /**
299
+ * Regenerate email verification token for unverified accounts
300
+ */
301
+ export async function regenerateVerificationToken(email: string): Promise<{ passwordAuth: PasswordAuth; verificationToken: string } | null> {
302
+ const client = await db_init();
303
+
304
+ const authRecord = await getPasswordAuthByEmail(email);
305
+ if (!authRecord) {
306
+ return null;
307
+ }
308
+
309
+ // Only regenerate for unverified accounts
310
+ if (authRecord.emailverified) {
311
+ throw new Error('Email is already verified');
312
+ }
313
+
314
+ // Generate new verification token
315
+ const verificationToken = randomBytes(32).toString('base64url');
316
+ const verificationTokenExpires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
317
+
318
+ await db_query(client,
319
+ `UPDATE passwordauth
320
+ SET verificationtoken = ?,
321
+ verificationtokenexpires = ?,
322
+ updatedat = CURRENT_TIMESTAMP
323
+ WHERE id = ?`,
324
+ [verificationToken, verificationTokenExpires.toISOString(), authRecord.id]
325
+ );
326
+
327
+ const updatedAuth = await getPasswordAuthByEmail(email);
328
+ if (!updatedAuth) {
329
+ throw new Error('Failed to update verification token');
330
+ }
331
+
332
+ return { passwordAuth: updatedAuth, verificationToken };
333
+ }
334
+
298
335
  /**
299
336
  * Check if an email is already registered
300
337
  */
@@ -101,7 +101,7 @@ export async function db_query(
101
101
  */
102
102
  export function db_migrate(model: Model, dbType: string): string {
103
103
 
104
- const sanitizedTableName = model.name;
104
+ const sanitizedTableName = model.name.toLowerCase().replace(/\s+/g, '_');
105
105
 
106
106
  // Start with the model's fields
107
107
  let fields = [...model.fields];
@@ -175,7 +175,7 @@ export async function db_query(
175
175
  export function db_migrate(model: Model, dbType: string): string {
176
176
  // Special handling for auth tables - create them first
177
177
 
178
- const sanitizedTableName = model.name;
178
+ const sanitizedTableName = model.name.toLowerCase().replace(/\s+/g, '_');
179
179
 
180
180
  // Start with the model's fields
181
181
  let fields = [...model.fields];