103 lines
2.6 KiB
JavaScript
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 };
|