fix: cross-audit fixes — file validation, type safety, dedup

- Avatar upload: whitelist image MIME types and extensions (jpg/png/gif/webp)
- Normalize device_id to String for strict comparison in treatment sync
- Add ORDER BY expire_time DESC to purchase/adminCreate subscription queries
- Deduplicate readBearer: middleware imports from lib/auth.js
- Parameterize createTrial INTERVAL instead of string concatenation
- Add rate limiting (20/15min) to avatar upload and phone auth endpoints
这个提交包含在:
Guoguo
2026-05-11 06:22:24 -07:00
父节点 583fcb7e3d
当前提交 0c65ef95f8
修改 5 个文件,包含 29 行新增16 行删除
+12 -3
查看文件
@@ -9,7 +9,14 @@ const userDao = require('../dao/user.dao')
const deviceDao = require('../dao/device.dao')
const logDao = require('../dao/log.dao')
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 2 * 1024 * 1024 } })
const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 2 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
cb(null, ALLOWED_IMAGE_TYPES.includes(file.mimetype))
}
})
const wrap = fn => (req, res, next) => fn(req, res, next).catch(next)
router.get('/user/profile', requireUser, wrap(async (req, res) => {
@@ -37,8 +44,10 @@ router.put('/user/profile', requireUser, wrap(async (req, res) => {
}))
router.post('/user/avatar', requireUser, upload.single('file'), wrap(async (req, res) => {
if (!req.file) return res.json(fail(2001, 'file required'))
const ext = (req.file.originalname || '').split('.').pop() || 'jpg'
if (!req.file) return res.json(fail(2001, 'file required, only jpg/png/gif/webp allowed'))
const ALLOWED_EXTS = ['jpg', 'jpeg', 'png', 'gif', 'webp']
const ext = (req.file.originalname || '').split('.').pop().toLowerCase().replace(/[^a-z]/g, '') || 'jpg'
if (!ALLOWED_EXTS.includes(ext)) return res.json(fail(2001, 'unsupported image format'))
const key = 'avatars/' + req.user.user_id + '_' + Date.now() + '.' + ext
const cos = new COS({ SecretId: config.cos.secretId, SecretKey: config.cos.secretKey })
await new Promise((resolve, reject) => {