sitepaige-mcp-server 1.1.0 → 1.2.1
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/headerlogin.tsx +10 -28
- package/components/profile.tsx +302 -46
- package/components/slideshow.tsx +1 -9
- package/defaultapp/api/Auth/resend-verification/route.ts +3 -3
- package/defaultapp/api/Auth/route.ts +2 -2
- package/defaultapp/api/Auth/signup/route.ts +3 -3
- package/defaultapp/api/Auth/verify-email/route.ts +5 -5
- package/defaultapp/api/admin/users/route.ts +3 -3
- package/defaultapp/{db-users.ts → api/db-users.ts} +1 -1
- package/defaultapp/api/profile/route.ts +154 -0
- package/defaultapp/profile/page.tsx +6 -0
- package/dist/blueprintWriter.js.map +1 -1
- package/dist/components/headerlogin.tsx +10 -28
- package/dist/components/profile.tsx +302 -46
- package/dist/components/slideshow.tsx +1 -9
- package/dist/defaultapp/api/Auth/resend-verification/route.ts +3 -3
- package/dist/defaultapp/api/Auth/route.ts +2 -2
- package/dist/defaultapp/api/Auth/signup/route.ts +3 -3
- package/dist/defaultapp/api/Auth/verify-email/route.ts +5 -5
- package/dist/defaultapp/api/admin/users/route.ts +3 -3
- package/dist/defaultapp/api/csrf.ts +111 -0
- package/dist/defaultapp/api/db-mysql.ts +183 -0
- package/dist/defaultapp/api/db-password-auth.ts +362 -0
- package/dist/defaultapp/api/db-postgres.ts +189 -0
- package/dist/defaultapp/api/db-sqlite.ts +335 -0
- package/dist/defaultapp/api/db-users.ts +520 -0
- package/dist/defaultapp/api/db.ts +149 -0
- package/dist/defaultapp/api/profile/route.ts +154 -0
- package/dist/defaultapp/api/storage/email.ts +162 -0
- package/dist/defaultapp/api/storage/files.ts +160 -0
- package/dist/defaultapp/db-users.ts +1 -1
- package/dist/defaultapp/profile/page.tsx +6 -0
- package/dist/generators/env-example-template.txt +4 -3
- package/dist/generators/skeleton.js +3 -5
- package/dist/generators/skeleton.js.map +1 -1
- package/dist/generators/sql.js +60 -0
- package/dist/generators/sql.js.map +1 -1
- 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
- package/defaultapp/api/example-secure/route.ts +0 -100
- package/defaultapp/migrate.ts +0 -142
- /package/defaultapp/{csrf.ts → api/csrf.ts} +0 -0
- /package/defaultapp/{db-mysql.ts → api/db-mysql.ts} +0 -0
- /package/defaultapp/{db-password-auth.ts → api/db-password-auth.ts} +0 -0
- /package/defaultapp/{db-postgres.ts → api/db-postgres.ts} +0 -0
- /package/defaultapp/{db-sqlite.ts → api/db-sqlite.ts} +0 -0
- /package/defaultapp/{db.ts → api/db.ts} +0 -0
- /package/defaultapp/{storage → api/storage}/email.ts +0 -0
- /package/defaultapp/{storage → api/storage}/files.ts +0 -0
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { NextResponse } from 'next/server';
|
|
7
|
-
import { regenerateVerificationToken } from '
|
|
8
|
-
import { send_email } from '
|
|
7
|
+
import { regenerateVerificationToken } from '../../db-password-auth';
|
|
8
|
+
import { send_email } from '../../storage/email';
|
|
9
9
|
|
|
10
10
|
// Email validation regex
|
|
11
11
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -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
|
|
@@ -8,8 +8,8 @@ import { cookies } from 'next/headers';
|
|
|
8
8
|
import { NextResponse } from 'next/server';
|
|
9
9
|
import * as crypto from 'node:crypto';
|
|
10
10
|
|
|
11
|
-
import { db_init, db_query } from '
|
|
12
|
-
import { upsertUser, storeOAuthToken, validateSession, rotateSession } from '
|
|
11
|
+
import { db_init, db_query } from '../db';
|
|
12
|
+
import { upsertUser, storeOAuthToken, validateSession, rotateSession } from '../db-users';
|
|
13
13
|
|
|
14
14
|
type OAuthProvider = 'google' | 'facebook' | 'apple' | 'github';
|
|
15
15
|
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { NextResponse } from 'next/server';
|
|
7
|
-
import { createPasswordAuth } from '
|
|
8
|
-
import { send_email } from '
|
|
7
|
+
import { createPasswordAuth } from '../../db-password-auth';
|
|
8
|
+
import { send_email } from '../../storage/email';
|
|
9
9
|
|
|
10
10
|
// Email validation regex
|
|
11
11
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -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
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { NextResponse } from 'next/server';
|
|
7
|
-
import { verifyEmailWithToken } from '
|
|
8
|
-
import { upsertUser } from '
|
|
9
|
-
import { db_init, db_query } from '
|
|
7
|
+
import { verifyEmailWithToken } from '../../db-password-auth';
|
|
8
|
+
import { upsertUser } from '../../db-users';
|
|
9
|
+
import { db_init, db_query } from '../../db';
|
|
10
10
|
import { cookies } from 'next/headers';
|
|
11
11
|
import * as crypto from 'node:crypto';
|
|
12
12
|
|
|
@@ -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
|
|
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
|
|
6
6
|
import { NextRequest, NextResponse } from 'next/server';
|
|
7
7
|
import { cookies } from 'next/headers';
|
|
8
|
-
import { db_init, db_query } from '
|
|
8
|
+
import { db_init, db_query } from '../../db';
|
|
9
9
|
import {
|
|
10
10
|
getAllUsers,
|
|
11
11
|
updateUserPermission,
|
|
12
12
|
deleteUser,
|
|
13
13
|
getUserStats,
|
|
14
14
|
getUserByID
|
|
15
|
-
} from '
|
|
16
|
-
import { validateCsrfToken } from '
|
|
15
|
+
} from '../../db-users';
|
|
16
|
+
import { validateCsrfToken } from '../../csrf';
|
|
17
17
|
|
|
18
18
|
// Helper function to check if the current user is an admin
|
|
19
19
|
async function checkAdminAuth(): Promise<{ isAdmin: boolean; userId?: string }> {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Sitepaige v1.0.0
|
|
3
|
+
CSRF Protection Utility
|
|
4
|
+
WARNING: This file is automatically generated and should not be modified.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { cookies, headers } from 'next/headers';
|
|
8
|
+
import { NextRequest } from 'next/server';
|
|
9
|
+
|
|
10
|
+
export class CsrfError extends Error {
|
|
11
|
+
constructor(message: string) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'CsrfError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generates a new CSRF token
|
|
19
|
+
*/
|
|
20
|
+
export function generateCsrfToken(): string {
|
|
21
|
+
return crypto.randomUUID();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Gets the CSRF token from cookies
|
|
26
|
+
*/
|
|
27
|
+
export async function getCsrfToken(): Promise<string | undefined> {
|
|
28
|
+
const cookieStore = await cookies();
|
|
29
|
+
return cookieStore.get('csrf-token')?.value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validates CSRF token from request
|
|
34
|
+
* Checks both header and form data for the token
|
|
35
|
+
*/
|
|
36
|
+
export async function validateCsrfToken(request: NextRequest | Request): Promise<boolean> {
|
|
37
|
+
// Skip CSRF validation for GET and HEAD requests
|
|
38
|
+
if (request.method === 'GET' || request.method === 'HEAD') {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const cookieStore = await cookies();
|
|
43
|
+
const headerStore = await headers();
|
|
44
|
+
const cookieToken = cookieStore.get('csrf-token')?.value;
|
|
45
|
+
|
|
46
|
+
if (!cookieToken) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check X-CSRF-Token header first
|
|
51
|
+
const headerToken = headerStore.get('x-csrf-token');
|
|
52
|
+
if (headerToken === cookieToken) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// For form submissions, check the request body
|
|
57
|
+
if (request.headers.get('content-type')?.includes('application/x-www-form-urlencoded')) {
|
|
58
|
+
try {
|
|
59
|
+
const formData = await request.formData();
|
|
60
|
+
const formToken = formData.get('csrf_token');
|
|
61
|
+
if (formToken === cookieToken) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// If form parsing fails, continue to check JSON body
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// For JSON requests, check the body
|
|
70
|
+
if (request.headers.get('content-type')?.includes('application/json')) {
|
|
71
|
+
try {
|
|
72
|
+
const body = await request.json();
|
|
73
|
+
if (body.csrf_token === cookieToken) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// If JSON parsing fails, token is invalid
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Middleware helper to validate CSRF token
|
|
86
|
+
* Throws CsrfError if token is invalid
|
|
87
|
+
*/
|
|
88
|
+
export async function requireCsrfToken(request: NextRequest | Request): Promise<void> {
|
|
89
|
+
const isValid = await validateCsrfToken(request);
|
|
90
|
+
if (!isValid) {
|
|
91
|
+
throw new CsrfError('Invalid or missing CSRF token');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* React hook helper to get CSRF token for client-side forms
|
|
97
|
+
*/
|
|
98
|
+
export function useCsrfToken(): { token: string | undefined; header: Record<string, string> } {
|
|
99
|
+
// This will be used client-side
|
|
100
|
+
const token = typeof document !== 'undefined'
|
|
101
|
+
? document.cookie
|
|
102
|
+
.split('; ')
|
|
103
|
+
.find(row => row.startsWith('csrf-token='))
|
|
104
|
+
?.split('=')[1]
|
|
105
|
+
: undefined;
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
token,
|
|
109
|
+
header: token ? { 'X-CSRF-Token': token } : {}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Sitepaige v1.0.0
|
|
3
|
+
MySQL database implementation
|
|
4
|
+
WARNING: This file is automatically generated and should not be modified.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import mysql from 'mysql2/promise';
|
|
8
|
+
import type { Model, ModelField } from './db';
|
|
9
|
+
|
|
10
|
+
// Connection pool for better performance
|
|
11
|
+
let pool: mysql.Pool | null = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initialize database connection
|
|
15
|
+
* @returns Database client (Pool)
|
|
16
|
+
*/
|
|
17
|
+
export async function db_init(): Promise<mysql.Pool> {
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
// Return existing pool if already initialized
|
|
21
|
+
if (pool) {
|
|
22
|
+
return pool;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Create connection configuration
|
|
27
|
+
const poolConfig: mysql.PoolOptions = {
|
|
28
|
+
host: process.env.DB_HOST || 'localhost',
|
|
29
|
+
port: parseInt(process.env.DB_PORT || '3306'),
|
|
30
|
+
user: process.env.DB_USER || 'root',
|
|
31
|
+
password: process.env.DB_PASSWORD,
|
|
32
|
+
database: process.env.DB_NAME || 'app',
|
|
33
|
+
waitForConnections: true,
|
|
34
|
+
connectionLimit: 20,
|
|
35
|
+
queueLimit: 0,
|
|
36
|
+
enableKeepAlive: true,
|
|
37
|
+
keepAliveInitialDelay: 0
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Create the pool
|
|
41
|
+
pool = mysql.createPool(poolConfig);
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
return pool;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Execute a SQL query using the provided MySQL client
|
|
52
|
+
* @param client Database client from db_init()
|
|
53
|
+
* @param query SQL query string (MySQL syntax with ? parameter placeholders)
|
|
54
|
+
* @param params Optional array of parameters for the query
|
|
55
|
+
* @returns Array of selected rows or execution results
|
|
56
|
+
*/
|
|
57
|
+
export async function db_query(
|
|
58
|
+
client: mysql.Pool,
|
|
59
|
+
query: string,
|
|
60
|
+
params?: any[]
|
|
61
|
+
): Promise<any[]> {
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// MySQL uses ? placeholders like SQLite, so no conversion needed
|
|
66
|
+
const [results] = await client.execute(query, params);
|
|
67
|
+
|
|
68
|
+
// Handle different result types
|
|
69
|
+
if (Array.isArray(results)) {
|
|
70
|
+
// SELECT query results
|
|
71
|
+
return results;
|
|
72
|
+
} else {
|
|
73
|
+
// INSERT, UPDATE, DELETE results
|
|
74
|
+
const resultInfo = results as mysql.ResultSetHeader;
|
|
75
|
+
return [{
|
|
76
|
+
changes: resultInfo.affectedRows,
|
|
77
|
+
insertId: resultInfo.insertId
|
|
78
|
+
}];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
} catch (error) {
|
|
82
|
+
// If table doesn't exist for SELECT, return empty array
|
|
83
|
+
if (error instanceof Error && error.message.includes("doesn't exist") && query.trim().toUpperCase().startsWith('SELECT')) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generates a CREATE TABLE SQL string for the specified table and fields
|
|
92
|
+
* @param model The model definition
|
|
93
|
+
* @param dbType Database type
|
|
94
|
+
* @returns SQL string for creating the table
|
|
95
|
+
*/
|
|
96
|
+
export function db_migrate(model: Model, dbType: string): string {
|
|
97
|
+
|
|
98
|
+
const sanitizedTableName = model.name.toLowerCase().replace(/\s+/g, '_');
|
|
99
|
+
|
|
100
|
+
// Start with the model's fields
|
|
101
|
+
let fields = [...model.fields];
|
|
102
|
+
|
|
103
|
+
// Add userid field if data is user specific
|
|
104
|
+
if (model.data_is_user_specific === "true") {
|
|
105
|
+
fields.push({
|
|
106
|
+
name: 'userid',
|
|
107
|
+
datatype: 'VARCHAR',
|
|
108
|
+
datatypesize: '36', // UUID as string
|
|
109
|
+
key: 'foreign',
|
|
110
|
+
required: 'true',
|
|
111
|
+
default: undefined
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Map SQL data types for MySQL
|
|
116
|
+
const sqlTypeMap: { [key: string]: string } = {
|
|
117
|
+
'UUID': 'VARCHAR(36)',
|
|
118
|
+
'TINYINT': 'TINYINT',
|
|
119
|
+
'SMALLINT': 'SMALLINT',
|
|
120
|
+
'BIGINT': 'BIGINT',
|
|
121
|
+
'INT128': 'DECIMAL(39,0)',
|
|
122
|
+
'VARCHAR': 'VARCHAR',
|
|
123
|
+
'TEXT': 'TEXT',
|
|
124
|
+
'BINARY': 'BLOB',
|
|
125
|
+
'DATE': 'DATE',
|
|
126
|
+
'TIME': 'TIME',
|
|
127
|
+
'DATETIME': 'DATETIME',
|
|
128
|
+
'DOUBLE': 'DOUBLE',
|
|
129
|
+
'FLOAT': 'FLOAT',
|
|
130
|
+
'BOOLEAN': 'BOOLEAN'
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Build column definitions
|
|
134
|
+
const columnDefs = fields.sort((a, b) => (b.key === 'primary' ? 1 : 0) - (a.key === 'primary' ? 1 : 0)).map(field => {
|
|
135
|
+
const dataType = field.datatype.toUpperCase();
|
|
136
|
+
let sqlType = sqlTypeMap[dataType] || dataType;
|
|
137
|
+
|
|
138
|
+
// Add size for VARCHAR
|
|
139
|
+
if (dataType === 'VARCHAR' && field.datatypesize) {
|
|
140
|
+
sqlType = `VARCHAR(${field.datatypesize})`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let def = `\`${field.name}\` ${sqlType}`;
|
|
144
|
+
|
|
145
|
+
// Add NOT NULL if required
|
|
146
|
+
if (field.required === "true") {
|
|
147
|
+
def += ' NOT NULL';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Add default if specified
|
|
151
|
+
if (field.default !== undefined) {
|
|
152
|
+
if (field.datatype === 'VARCHAR' || field.datatype === 'TEXT') {
|
|
153
|
+
def += ` DEFAULT '${field.default}'`;
|
|
154
|
+
} else if (field.datatype === 'BOOLEAN') {
|
|
155
|
+
def += ` DEFAULT ${field.default === 'true' || field.default === true ? 1 : 0}`;
|
|
156
|
+
} else {
|
|
157
|
+
def += ` DEFAULT ${field.default}`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Add primary key constraint
|
|
162
|
+
if (field.key === 'primary') {
|
|
163
|
+
def += ' PRIMARY KEY';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return def;
|
|
167
|
+
}).join(',\n ');
|
|
168
|
+
|
|
169
|
+
// Add foreign key constraints at the end
|
|
170
|
+
const foreignKeys: string[] = [];
|
|
171
|
+
if (model.data_is_user_specific === "true") {
|
|
172
|
+
foreignKeys.push(`FOREIGN KEY (\`userid\`) REFERENCES \`users\` (\`userid\`)`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const allConstraints = foreignKeys.length > 0
|
|
176
|
+
? columnDefs + ',\n ' + foreignKeys.join(',\n ')
|
|
177
|
+
: columnDefs;
|
|
178
|
+
|
|
179
|
+
// Build the CREATE TABLE statement
|
|
180
|
+
const sql = `CREATE TABLE IF NOT EXISTS \`${sanitizedTableName}\` (\n ${allConstraints}\n);`;
|
|
181
|
+
|
|
182
|
+
return sql;
|
|
183
|
+
}
|