diff --git a/components/settings/settingService.js b/components/settings/settingService.js index 516394d..d745620 100644 --- a/components/settings/settingService.js +++ b/components/settings/settingService.js @@ -103,9 +103,9 @@ const saveSettings = async (body = {}) => { ? Object.fromEntries(incoming.map((item) => [item.key, item])) : incoming; - const existing = await Setting.findOne({ key: SETTINGS_KEY }); + const existing = await Setting.findOne({ key: SETTINGS_KEY }).lean(); const storedMap = existing ? readStoredMap(existing) : emptyTemplateMap(); - const nextMap = emptyTemplateMap(); + const nextMap = {}; for (const def of SMS_TEMPLATE_DEFS) { const storedEntry = normalizeStoredEntry(storedMap[def.key], def); @@ -119,7 +119,7 @@ const saveSettings = async (body = {}) => { if (incomingEntry.templateId !== undefined) { const sanitized = sanitizeTemplateId(incomingEntry.templateId); if (sanitized === null) { - throw new AppError('VALIDATION_FAILED', { field: def.key }, `Invalid SMS template ID for ${def.key}`); + throw new AppError('VALIDATION_FAILED', { field: def.key }, `شناسه قالب پیامک برای ${def.label} نامعتبر است`); } templateId = sanitized; } @@ -138,16 +138,19 @@ const saveSettings = async (body = {}) => { const slot = String(item.slot || '').trim(); if (!slot) continue; const rawName = String(item.name || '').trim(); - if (!rawName) continue; const sanitized = sanitizeVariableName(rawName); if (sanitized === null) { throw new AppError( 'VALIDATION_FAILED', { field: `${def.key}.${slot}` }, - `Invalid SMS variable name "${rawName}" for ${def.key}` + `نام متغیر «${rawName}» برای ${def.label} نامعتبر است` ); } - validVars.push({ slot, name: sanitized }); + const slotDef = (def?.slots || []).find((s) => s.key === slot); + validVars.push({ + slot, + name: sanitized || slotDef?.defaultName || slot + }); } variables = validVars; } @@ -165,7 +168,7 @@ const saveSettings = async (body = {}) => { { key: SETTINGS_KEY }, { $set: { smsTemplates: nextMap } }, { upsert: true, new: true, setDefaultsOnInsert: true } - ); + ).lean(); return { smsTemplates: toPublicTemplates(readStoredMap(doc)) diff --git a/components/settings/smsTemplates.js b/components/settings/smsTemplates.js index 4243ced..a42f4f1 100644 --- a/components/settings/smsTemplates.js +++ b/components/settings/smsTemplates.js @@ -60,15 +60,17 @@ const sanitizeTemplateId = (value) => { if (value == null) return ''; const digits = String(value).trim(); if (!digits) return ''; - if (!/^\d{1,20}$/.test(digits)) return null; + if (!/^\d{1,30}$/.test(digits)) return null; return digits; }; const sanitizeVariableName = (value) => { if (value == null) return ''; - const name = String(value).trim().replace(/^#+|#+$/g, ''); + const name = String(value).trim().replace(/^#+|#+$/g, '').trim(); if (!name) return ''; - if (!/^[A-Za-z][A-Za-z0-9_]{0,49}$/.test(name)) return null; + // Check for dangerous injection or control chars + if (/[\r\n\t\0<>"'`]/.test(name)) return null; + if (name.length > 100) return null; return name; }; @@ -144,7 +146,9 @@ const resolveVariablesList = (def, storedVariables) => { const slot = String(item.slot || '').trim(); if (!slot) return null; const slotDef = (def?.slots || []).find((s) => s.key === slot); - const rawName = item.name !== undefined ? item.name : slotDef?.defaultName || ''; + const rawName = item.name !== undefined && String(item.name).trim() !== '' + ? String(item.name).trim() + : slotDef?.defaultName || ''; const sanitized = sanitizeVariableName(rawName); return { slot,