e35ac6a41c
Security Enhancements: - Fix SQL injection vulnerabilities by replacing whereRaw queries with parameterized queries - Add LIKE pattern escaping to prevent SQL injection in search functionality - Implement account lockout protection (5 failed attempts = 30 min lockout) - Add comprehensive login attempt tracking and audit trail - Enhance JWT tokens with issuer validation, IP tracking, and password change detection - Add logout endpoint and session management - Prevent user enumeration with generic error messages Database Changes: - Add login_attempts table for authentication tracking - Add security columns to admin_users (password_changed_at, last_login_ip, two_factor_enabled) New Security Features: - Brute force protection with configurable lockout duration - Automatic cleanup of old login attempts - Enhanced authentication middleware with stricter validation - Monitoring scripts for security health checks All fixes are backward compatible and production-ready with rollback plans included. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
117 lines
3.2 KiB
JavaScript
117 lines
3.2 KiB
JavaScript
/**
|
|
* SQL Security Utilities
|
|
* Provides safe methods for handling user input in SQL queries
|
|
*/
|
|
|
|
/**
|
|
* Validate and sanitize days parameter for date range queries
|
|
* @param {any} days - The days parameter from user input
|
|
* @returns {number} Safe integer between 1 and 365
|
|
*/
|
|
function sanitizeDays(days) {
|
|
const parsed = parseInt(days);
|
|
|
|
// Check if it's a valid number
|
|
if (isNaN(parsed)) {
|
|
return 7; // Default to 7 days
|
|
}
|
|
|
|
// Ensure it's within reasonable bounds
|
|
if (parsed < 1) {
|
|
return 1;
|
|
}
|
|
|
|
if (parsed > 365) {
|
|
return 365; // Maximum 1 year
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
/**
|
|
* Escape special characters in LIKE queries
|
|
* @param {string} input - The search string from user input
|
|
* @returns {string} Escaped string safe for LIKE queries
|
|
*/
|
|
function escapeLikePattern(input) {
|
|
if (!input || typeof input !== 'string') {
|
|
return '';
|
|
}
|
|
|
|
// Escape special LIKE pattern characters
|
|
// In SQL LIKE patterns:
|
|
// % matches any sequence of characters
|
|
// _ matches any single character
|
|
// \ is the escape character
|
|
return input
|
|
.replace(/\\/g, '\\\\') // Escape backslashes first
|
|
.replace(/%/g, '\\%') // Escape percent signs
|
|
.replace(/_/g, '\\_') // Escape underscores
|
|
.replace(/'/g, "''"); // Escape single quotes for safety
|
|
}
|
|
|
|
/**
|
|
* Create a safe date range condition using Knex
|
|
* @param {object} query - Knex query builder instance
|
|
* @param {string} column - The timestamp column name
|
|
* @param {number} days - Number of days to go back
|
|
* @returns {object} Modified query with safe date range condition
|
|
*/
|
|
function addDateRangeCondition(query, column, days) {
|
|
const safeDays = sanitizeDays(days);
|
|
const startDate = new Date();
|
|
startDate.setDate(startDate.getDate() - safeDays);
|
|
|
|
// Use Knex's built-in date comparison which handles parameterization
|
|
return query.where(column, '>=', startDate.toISOString());
|
|
}
|
|
|
|
/**
|
|
* Create a safe LIKE condition using Knex
|
|
* @param {object} query - Knex query builder instance
|
|
* @param {string} column - The column to search
|
|
* @param {string} pattern - The search pattern
|
|
* @returns {object} Modified query with safe LIKE condition
|
|
*/
|
|
function addLikeCondition(query, column, pattern) {
|
|
if (!pattern || typeof pattern !== 'string') {
|
|
return query;
|
|
}
|
|
|
|
const escapedPattern = escapeLikePattern(pattern);
|
|
// Knex handles parameterization of the LIKE value
|
|
return query.where(column, 'like', `%${escapedPattern}%`);
|
|
}
|
|
|
|
/**
|
|
* Validate sort column against whitelist
|
|
* @param {string} column - The column name to sort by
|
|
* @param {string[]} allowedColumns - Array of allowed column names
|
|
* @param {string} defaultColumn - Default column if invalid
|
|
* @returns {string} Safe column name
|
|
*/
|
|
function validateSortColumn(column, allowedColumns, defaultColumn) {
|
|
if (!column || !allowedColumns.includes(column)) {
|
|
return defaultColumn;
|
|
}
|
|
return column;
|
|
}
|
|
|
|
/**
|
|
* Validate sort order
|
|
* @param {string} order - The sort order (asc/desc)
|
|
* @returns {string} Safe sort order
|
|
*/
|
|
function validateSortOrder(order) {
|
|
const lowerOrder = (order || '').toLowerCase();
|
|
return lowerOrder === 'asc' ? 'asc' : 'desc';
|
|
}
|
|
|
|
module.exports = {
|
|
sanitizeDays,
|
|
escapeLikePattern,
|
|
addDateRangeCondition,
|
|
addLikeCondition,
|
|
validateSortColumn,
|
|
validateSortOrder
|
|
}; |