This commit is contained in:
payacom
2026-10-02 18:13:34 +03:30
parent d4ebc20aa8
commit fade8543e2
9 changed files with 180 additions and 63 deletions

View File

@@ -371,7 +371,7 @@ const sendRoomMessage = async (req, res, next) => {
}
const textContent = content != null ? String(content).trim() : ''
const uploaded = req.files?.file
const uploaded = req.file
const resolvedFileType = fileType || undefined
if (!textContent && !uploaded) {
@@ -409,22 +409,16 @@ const sendRoomMessage = async (req, res, next) => {
let fileUrl = null
if (uploaded) {
const uploadDir = path.join(__dirname, '../../../storage/chat-rooms/messages')
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true })
}
// اسمِ فایل هیچ‌وقت مستقیم از ورودیِ کاربر (uploaded.name) گرفته نمی‌شه — فقط پسوندش
const uniqueFileName = `${Date.now()}-${crypto.randomUUID()}${path.extname(uploaded.name || '')}`
const filePath = path.join(uploadDir, uniqueFileName)
await fs.move(uploaded.path, filePath)
if (resolvedFileType === 'image' || (uploaded.type || '').startsWith('image/')) {
await optimizeImageFile(filePath)
} else if (resolvedFileType === 'video' || (uploaded.type || '').startsWith('video/')) {
optimizeVideoFaststart(filePath).catch((err) => {
// multer (roomMessageUpload توی routes) از قبل فایل رو مستقیماً روی پوشه‌ی
// نهایی نوشته — دیگه نیازی به fs.move جداگانه نیست
if (resolvedFileType === 'image' || (uploaded.mimetype || '').startsWith('image/')) {
await optimizeImageFile(uploaded.path)
} else if (resolvedFileType === 'video' || (uploaded.mimetype || '').startsWith('video/')) {
optimizeVideoFaststart(uploaded.path).catch((err) => {
console.error('[chatRoomController] video optimize background error:', err.message)
})
}
fileUrl = `/chat-rooms/messages/${uniqueFileName}`
fileUrl = `/chat-rooms/messages/${uploaded.filename}`
}
room.lastActivityAt = new Date()

View File

@@ -14,7 +14,7 @@ const {
consumeFreeCreation,
freeCreationLimitResponse
} = require('../../../utils/freeCreationLimit')
const { canUseFreeBillboard, consumeFreeBillboard } = require('../../../utils/billboardFreeLimit')
const { tryConsumeFreeBillboard } = require('../../../utils/billboardFreeLimit')
const {
PROJECT_ACCEPT_RATE,
PROJECT_COMPLETION_COMMISSION_RATE,
@@ -64,6 +64,18 @@ const initiatePayment = async (req, res) => {
const itemName = req.body.item_name // Get item name from request body
const price = await getPriceFromDatabase(itemName) // Get price from database
const projectId = req.body.projectId // Get project ID from request body
// IDOR: قبلاً هیچ‌جا چک نمی‌شد که این پروژه واقعاً متعلق به همین کاربرِ
// احرازهویت‌شده‌ست — یه projectId دلخواه کافی بود تا کاربرِ دیگه‌ای پرداخت کنه
// ولی پروژه‌ی یه کاربرِ کاملاً متفاوت تایید/پرداخت‌شده بشه
const projectForOwnerCheck = await ProjectModel.findById(projectId).select('creator_id')
if (!projectForOwnerCheck) {
return res.status(404).json({ message: 'پروژه یافت نشد' })
}
if (String(projectForOwnerCheck.creator_id) !== String(userId)) {
return res.status(403).json({ message: 'دسترسی به این پروژه ندارید' })
}
// Save project ID for later use in callback URL
req.session.projectId = projectId
const item = await TypeAndPriceModel.findOne({ name: itemName })
@@ -641,15 +653,24 @@ const initiateAdvertisingPayment = async (req, res) => {
const advertisingId = req.body.advertisingId // Get project ID from request body
const showDiscount = req.body.showDiscount
// IDOR: قبلاً هیچ‌جا چک نمی‌شد که این بیلبورد واقعاً متعلق به همین کاربرِ
// احرازهویت‌شده‌ست — یه advertisingId دلخواه کافی بود تا کاربرِ دیگه‌ای پرداخت
// کنه ولی بیلبوردِ یه کاربرِ کاملاً متفاوت تایید/پرداخت‌شده بشه
const advertisingForOwnerCheck = await AdvertisingModel.findById(advertisingId).select('creator_id')
if (!advertisingForOwnerCheck) {
return res.status(404).json({ message: 'بیلبورد یافت نشد' })
}
if (String(advertisingForOwnerCheck.creator_id) !== String(userId)) {
return res.status(403).json({ message: 'دسترسی به این بیلبورد ندارید' })
}
// Save project ID for later use in callback URL
req.session.advertisingId = advertisingId
const item = await AdvertisingTypeModel.findOne({ name: itemName })
const user = await UserModel.findById(userId)
if (item.name === 'free' && item.price === 0) {
if (canUseFreeBillboard(user)) {
await consumeFreeBillboard(user)
await user.save()
const freeResult = await tryConsumeFreeBillboard(UserModel, userId)
if (freeResult.ok) {
const project = await AdvertisingModel.findById(advertisingId)
if (!project) {
res.status(404).send('Advertising not found')
@@ -660,7 +681,7 @@ const initiateAdvertisingPayment = async (req, res) => {
await project.save()
res.status(200).json({ message: 'بیلبورد رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'سقف ۲۰۰ بیلبورد رایگان شما به پایان رسیده است.' })
res.status(403).json({ message: freeResult.message })
}
} else {
const response = await axios.post(
@@ -796,12 +817,10 @@ const republishAdvertisingPayment = async (req, res) => {
// Save project ID for later use in callback URL
req.session.advertisingId = advertisingId
const item = await AdvertisingTypeModel.findOne({ name: itemName })
const user = await UserModel.findById(userId)
if (item.name === 'free' && item.price === 0) {
if (canUseFreeBillboard(user)) {
await consumeFreeBillboard(user)
await user.save()
const freeResult = await tryConsumeFreeBillboard(UserModel, userId)
if (freeResult.ok) {
const project = await AdvertisingModel.findById(advertisingId)
if (!project) {
res.status(404).send('Advertising not found')
@@ -812,7 +831,7 @@ const republishAdvertisingPayment = async (req, res) => {
await project.save()
res.status(200).json({ message: 'بیلبورد رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'سقف ۲۰۰ بیلبورد رایگان شما به پایان رسیده است.' })
res.status(403).json({ message: freeResult.message })
}
} else {
const response = await axios.post(

View File

@@ -9,7 +9,7 @@ const NotificationModel = require('../../../models/NotificationModel')
const UserModel = require('../../../models/UserModel')
const AdvertisingTypeModel = require('../../../models/AdvertisingTypeModel')
const AdvertisingModel = require('../../../models/AdvertisingModel')
const { canUseFreeBillboard, consumeFreeBillboard } = require('../../../utils/billboardFreeLimit')
const { tryConsumeFreeBillboard } = require('../../../utils/billboardFreeLimit')
const OfferTypeModel = require('../../../models/OfferTypeModel')
const OfferModel = require('../../../models/OfferModel')
const { default: axios } = require('axios')
@@ -92,6 +92,17 @@ const initiatePaymentWeb = async (req, res) => {
})
}
// IDOR: قبلاً هیچ‌جا چک نمی‌شد که این پروژه واقعاً متعلق به همین کاربرِ
// احرازهویت‌شده‌ست — یه projectId دلخواه (مثلاً از یه لینکِ ساختگی) کافی بود تا
// کاربرِ دیگه‌ای پرداخت کنه ولی پروژه‌ی یه کاربرِ کاملاً متفاوت تایید/پرداخت‌شده بشه
const projectForOwnerCheck = await ProjectModel.findById(projectId).select('creator_id')
if (!projectForOwnerCheck) {
return res.status(404).json({ error: true, message: 'پروژه یافت نشد' })
}
if (String(projectForOwnerCheck.creator_id) !== String(userId)) {
return res.status(403).json({ error: true, message: 'دسترسی به این پروژه ندارید' })
}
const price = Number(item.price) || 0
const user = await UserModel.findById(userId)
if (!user) {
@@ -566,16 +577,25 @@ const initiateAdvertisingPaymentWeb = async (req, res) => {
const advertisingId = req.body.advertisingId // Get project ID from request body
const showDiscount = req.body.showDiscount
// IDOR: قبلاً هیچ‌جا چک نمی‌شد که این بیلبورد واقعاً متعلق به همین کاربرِ
// احرازهویت‌شده‌ست — یه advertisingId دلخواه کافی بود تا کاربرِ دیگه‌ای پرداخت
// کنه ولی بیلبوردِ یه کاربرِ کاملاً متفاوت تایید/پرداخت‌شده بشه (رایگان بود، فقط
// با مصرفِ سهمیه‌ی رایگانِ همون کاربر هم ممکن بود)
const advertisingForOwnerCheck = await AdvertisingModel.findById(advertisingId).select('creator_id')
if (!advertisingForOwnerCheck) {
return res.status(404).json({ message: 'بیلبورد یافت نشد' })
}
if (String(advertisingForOwnerCheck.creator_id) !== String(userId)) {
return res.status(403).json({ message: 'دسترسی به این بیلبورد ندارید' })
}
// Save project ID for later use in callback URL
req.session.advertisingId = advertisingId
const item = await AdvertisingTypeModel.findOne({ name: itemName })
const user = await UserModel.findById(userId)
if (item.name === 'free' && item.price === 0) {
if (canUseFreeBillboard(user)) {
await consumeFreeBillboard(user)
// updateOne + $inc به‌جای user.save() — رجوع کنید به توضیح مشابه در initiateOfferPaymentWeb
await UserModel.updateOne({ _id: userId }, { $inc: { free_billboard_count: 1 } })
const freeResult = await tryConsumeFreeBillboard(UserModel, userId)
if (freeResult.ok) {
const project = await AdvertisingModel.findById(advertisingId)
if (!project) {
res.status(404).send('Advertising not found')
@@ -586,7 +606,7 @@ const initiateAdvertisingPaymentWeb = async (req, res) => {
await project.save()
res.status(200).json({ message: 'بیلبورد رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'سقف ۲۰۰ بیلبورد رایگان شما به پایان رسیده است.' })
res.status(403).json({ message: freeResult.message })
}
} else {
const response = await axios.post(
@@ -721,16 +741,25 @@ const republishAdvertisingPaymentWeb = async (req, res) => {
const price = await getAdvertisingPriceFromDatabase(itemName) // Get price from database
const advertisingId = req.body.advertisingId // Get project ID from request body
const showDiscount = req.body.showDiscount
// IDOR: قبلاً هیچ‌جا چک نمی‌شد که این بیلبورد واقعاً متعلق به همین کاربرِ
// احرازهویت‌شده‌ست — یه advertisingId دلخواه کافی بود تا کاربرِ دیگه‌ای پرداخت
// کنه ولی بیلبوردِ یه کاربرِ کاملاً متفاوت تایید/پرداخت‌شده بشه (رایگان بود، فقط
// با مصرفِ سهمیه‌ی رایگانِ همون کاربر هم ممکن بود)
const advertisingForOwnerCheck = await AdvertisingModel.findById(advertisingId).select('creator_id')
if (!advertisingForOwnerCheck) {
return res.status(404).json({ message: 'بیلبورد یافت نشد' })
}
if (String(advertisingForOwnerCheck.creator_id) !== String(userId)) {
return res.status(403).json({ message: 'دسترسی به این بیلبورد ندارید' })
}
// Save project ID for later use in callback URL
req.session.advertisingId = advertisingId
const item = await AdvertisingTypeModel.findOne({ name: itemName })
const user = await UserModel.findById(userId)
if (item.name === 'free' && item.price === 0) {
if (canUseFreeBillboard(user)) {
await consumeFreeBillboard(user)
// updateOne + $inc به‌جای user.save() — رجوع کنید به توضیح مشابه در initiateOfferPaymentWeb
await UserModel.updateOne({ _id: userId }, { $inc: { free_billboard_count: 1 } })
const freeResult = await tryConsumeFreeBillboard(UserModel, userId)
if (freeResult.ok) {
const project = await AdvertisingModel.findById(advertisingId)
if (!project) {
res.status(404).send('Advertising not found')
@@ -741,7 +770,7 @@ const republishAdvertisingPaymentWeb = async (req, res) => {
await project.save()
res.status(200).json({ message: 'بیلبورد رایگان با موفقیت ایجاد شد', advertisingId, type: 'free' })
} else {
res.status(403).json({ message: 'سقف ۲۰۰ بیلبورد رایگان شما به پایان رسیده است.' })
res.status(403).json({ message: freeResult.message })
}
} else {
const response = await axios.post(

View File

@@ -69,6 +69,7 @@ const completeSocialSignup = async (req, res, next) => {
)
return res.status(422).json({
error: true,
code: 'duplicate_username',
field: 'user_name',
message: 'نام کاربری تکراری است',
suggestions

View File

@@ -59,6 +59,7 @@ const setUserName = async (req, res, next) => {
)
return res.status(422).json({
error: true,
code: 'duplicate_username',
message: 'نام کاربری تکراری است',
suggestions
})
@@ -113,6 +114,7 @@ const updateUserName = async (req, res, next) => {
)
return res.status(422).json({
error: true,
code: 'duplicate_username',
message: 'نام کاربری تکراری است',
suggestions
})