Files
rentall-app/backend/utils/s3KeyValidator.js
2025-12-12 13:33:24 -05:00

103 lines
2.6 KiB
JavaScript

/**
* S3 Key Validation Utility
* Validates that user-supplied S3 keys match expected formats
* to prevent IDOR and arbitrary data injection attacks
*/
// UUID v4 regex pattern
const UUID_PATTERN =
"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
// Allowed image extensions
const ALLOWED_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "webp"];
// Valid S3 folders
const VALID_FOLDERS = [
"profiles",
"items",
"messages",
"forum",
"condition-checks",
];
/**
* Build regex pattern for a specific folder
* @param {string} folder - The S3 folder name
* @returns {RegExp}
*/
function buildKeyPattern(folder) {
const extPattern = ALLOWED_EXTENSIONS.join("|");
return new RegExp(`^${folder}/${UUID_PATTERN}\\.(${extPattern})$`, "i");
}
/**
* Validate a single S3 key
* @param {string} key - The S3 key to validate
* @param {string} folder - Expected folder (profiles, items, messages, forum, condition-checks)
* @returns {{ valid: boolean, error?: string }}
*/
function validateS3Key(key, folder) {
if (!key || typeof key !== "string") {
return { valid: false, error: "Key must be a non-empty string" };
}
if (!VALID_FOLDERS.includes(folder)) {
return { valid: false, error: `Invalid folder` };
}
const pattern = buildKeyPattern(folder);
if (!pattern.test(key)) {
return {
valid: false,
error: `Invalid key format`,
};
}
return { valid: true };
}
/**
* Validate an array of S3 keys
* @param {Array} keys - Array of S3 keys to validate
* @param {string} folder - Expected folder (profiles, items, messages, forum, condition-checks)
* @param {Object} options - Validation options
* @param {number} options.maxKeys - Maximum number of keys allowed
* @returns {{ valid: boolean, error?: string, invalidKeys?: Array }}
*/
function validateS3Keys(keys, folder, options = {}) {
const { maxKeys = 20 } = options;
if (!Array.isArray(keys)) {
return { valid: false, error: "Keys must be an array" };
}
if (keys.length > maxKeys) {
return { valid: false, error: `Maximum ${maxKeys} keys allowed` };
}
if (keys.length === 0) {
return { valid: true };
}
const uniqueKeys = new Set(keys);
if (uniqueKeys.size !== keys.length) {
return { valid: false, error: "Duplicate keys not allowed" };
}
const invalidKeys = [];
for (const key of keys) {
const result = validateS3Key(key, folder);
if (!result.valid) {
invalidKeys.push({ key, error: result.error });
}
}
if (invalidKeys.length > 0) {
return { valid: false, error: "Invalid S3 key format", invalidKeys };
}
return { valid: true };
}
module.exports = { validateS3Keys };