webupdatev1
This commit is contained in:
@@ -1,21 +1,48 @@
|
||||
const { query } = require("../config/database");
|
||||
|
||||
// Whitelist of allowed table names to prevent SQL injection
|
||||
const ALLOWED_TABLES = [
|
||||
"products",
|
||||
"product_images",
|
||||
"portfolioprojects",
|
||||
"blogposts",
|
||||
"pages",
|
||||
"adminusers",
|
||||
"roles",
|
||||
"uploads",
|
||||
"media_folders",
|
||||
"team_members",
|
||||
"site_settings",
|
||||
"session",
|
||||
];
|
||||
|
||||
// Validate table name against whitelist
|
||||
const validateTableName = (table) => {
|
||||
if (!ALLOWED_TABLES.includes(table)) {
|
||||
throw new Error(`Invalid table name: ${table}`);
|
||||
}
|
||||
return table;
|
||||
};
|
||||
|
||||
const buildSelectQuery = (
|
||||
table,
|
||||
conditions = [],
|
||||
orderBy = "createdat DESC"
|
||||
) => {
|
||||
validateTableName(table);
|
||||
const whereClause =
|
||||
conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
||||
return `SELECT * FROM ${table} ${whereClause} ORDER BY ${orderBy}`;
|
||||
};
|
||||
|
||||
const getById = async (table, id) => {
|
||||
validateTableName(table);
|
||||
const result = await query(`SELECT * FROM ${table} WHERE id = $1`, [id]);
|
||||
return result.rows[0] || null;
|
||||
};
|
||||
|
||||
const getAllActive = async (table, orderBy = "createdat DESC") => {
|
||||
validateTableName(table);
|
||||
const result = await query(
|
||||
`SELECT * FROM ${table} WHERE isactive = true ORDER BY ${orderBy}`
|
||||
);
|
||||
@@ -23,6 +50,7 @@ const getAllActive = async (table, orderBy = "createdat DESC") => {
|
||||
};
|
||||
|
||||
const deleteById = async (table, id) => {
|
||||
validateTableName(table);
|
||||
const result = await query(
|
||||
`DELETE FROM ${table} WHERE id = $1 RETURNING id`,
|
||||
[id]
|
||||
@@ -31,6 +59,7 @@ const deleteById = async (table, id) => {
|
||||
};
|
||||
|
||||
const countRecords = async (table, condition = "") => {
|
||||
validateTableName(table);
|
||||
const whereClause = condition ? `WHERE ${condition}` : "";
|
||||
const result = await query(`SELECT COUNT(*) FROM ${table} ${whereClause}`);
|
||||
return parseInt(result.rows[0].count);
|
||||
@@ -42,4 +71,5 @@ module.exports = {
|
||||
getAllActive,
|
||||
deleteById,
|
||||
countRecords,
|
||||
validateTableName,
|
||||
};
|
||||
|
||||
111
backend/utils/sanitization.js
Normal file
111
backend/utils/sanitization.js
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Sanitization utilities for user input
|
||||
* Prevents XSS attacks by escaping HTML special characters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Escape HTML special characters to prevent XSS
|
||||
* @param {string} str - String to escape
|
||||
* @returns {string} Escaped string
|
||||
*/
|
||||
const escapeHtml = (str) => {
|
||||
if (typeof str !== "string") {
|
||||
return str;
|
||||
}
|
||||
|
||||
const htmlEscapeMap = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"/": "/",
|
||||
};
|
||||
|
||||
return str.replace(/[&<>"'/]/g, (char) => htmlEscapeMap[char]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitize object by escaping all string values
|
||||
* @param {Object} obj - Object to sanitize
|
||||
* @returns {Object} Sanitized object
|
||||
*/
|
||||
const sanitizeObject = (obj) => {
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const sanitized = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (typeof value === "string") {
|
||||
sanitized[key] = escapeHtml(value);
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
sanitized[key] = sanitizeObject(value);
|
||||
} else {
|
||||
sanitized[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
};
|
||||
|
||||
/**
|
||||
* Strip all HTML tags from a string
|
||||
* @param {string} str - String to strip
|
||||
* @returns {string} String without HTML tags
|
||||
*/
|
||||
const stripHtml = (str) => {
|
||||
if (typeof str !== "string") {
|
||||
return str;
|
||||
}
|
||||
|
||||
return str.replace(/<[^>]*>/g, "");
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate and sanitize URL
|
||||
* @param {string} url - URL to validate
|
||||
* @returns {string|null} Sanitized URL or null if invalid
|
||||
*/
|
||||
const sanitizeUrl = (url) => {
|
||||
if (typeof url !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
// Only allow http and https protocols
|
||||
if (!["http:", "https:"].includes(parsed.protocol)) {
|
||||
return null;
|
||||
}
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitize filename for safe storage
|
||||
* @param {string} filename - Filename to sanitize
|
||||
* @returns {string} Sanitized filename
|
||||
*/
|
||||
const sanitizeFilename = (filename) => {
|
||||
if (typeof filename !== "string") {
|
||||
return "file";
|
||||
}
|
||||
|
||||
// Remove path separators and null bytes
|
||||
return filename
|
||||
.replace(/[\/\\]/g, "")
|
||||
.replace(/\0/g, "")
|
||||
.replace(/[^a-zA-Z0-9._-]/g, "-")
|
||||
.substring(0, 255);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
escapeHtml,
|
||||
sanitizeObject,
|
||||
stripHtml,
|
||||
sanitizeUrl,
|
||||
sanitizeFilename,
|
||||
};
|
||||
Reference in New Issue
Block a user