fix(customer): 联系人新增与跟进编辑补上归属校验

两个端点都只有 requirePermission('customer.edit'),而那只说明「这个人有权
编辑客户」,没说明「有权编辑**这一条**客户」。实测一个 sales_rep 账号:

  GET   /api/customers/4            → 404(读是隔离的)
  POST  /api/customers/4/contacts   → 201(写进去了)
  PATCH /api/customers/4/follow-ups/12 → 200
        把「客户确认 3000 套订单」改成了「客户取消了订单」

也就是说一个销售能往看不见的客户里插联系人(还能把自己设成主联系人),
还能改写另一个销售客户的跟进历史。

两处都有对照组:紧挨着的 PATCH /contacts 和 POST /follow-ups 一直有这道
检查,所以是漏了而不是设计如此。补上后重放两个利用都是 403,归属人自己
仍然 201/200。

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Drsakura
2026-10-22 15:22:15 +08:00
co-authored by Claude Opus 5
parent 691f315341
commit 3e9fd2dc97
+14
View File
@@ -484,6 +484,13 @@ app.post('/api/customers/:id/contacts', auth.requirePermission('customer.edit'),
try { try {
const customer = getCustomer(req.params.id); const customer = getCustomer(req.params.id);
if (!customer) return res.status(404).json({ error: '客户不存在' }); if (!customer) return res.status(404).json({ error: '客户不存在' });
/* customer.edit 只说明「这个人有权编辑客户」,没说明「有权编辑**这一条**客户」。
少了这道归属校验,任何销售业务员猜个 id 就能往别人名下、甚至别组织的客户里
插联系人(GET 同一条客户是 404,写却成功),还能把 is_primary 设成 1
把自己变成主联系人。隔壁的 PATCH 一直有这道检查,这里是漏了。 */
if (!customerAccess.canEdit(req.user, customer)) {
return res.status(403).json({ error: '这条客户不在你名下,请先到客户公海领取' });
}
const name = String(req.body?.name || '').trim(); const name = String(req.body?.name || '').trim();
if (!name) return res.status(400).json({ error: '联系人姓名不能为空' }); if (!name) return res.status(400).json({ error: '联系人姓名不能为空' });
const fields = ['customer_id', 'name', 'name_en', 'position', 'department', 'email', 'phone', 'whatsapp', 'wechat', 'is_primary', 'notes', 'created_at']; const fields = ['customer_id', 'name', 'name_en', 'position', 'department', 'email', 'phone', 'whatsapp', 'wechat', 'is_primary', 'notes', 'created_at'];
@@ -564,6 +571,13 @@ app.patch('/api/customers/:id/follow-ups/:followUpId', auth.requirePermission('c
try { try {
const customer = getCustomer(req.params.id); const customer = getCustomer(req.params.id);
if (!customer) return res.status(404).json({ error: '客户不存在' }); if (!customer) return res.status(404).json({ error: '客户不存在' });
/* 同 POST 联系人:校验了「记录属于这条客户」不等于校验了「这条客户属于我」。
漏了这道,别人能把我客户的跟进记录改写掉 —— 比如把
「客户确认 3000 套订单」改成「客户取消了订单」,CRM 历史就被篡改了。
POST /follow-ups 一直有这道检查,这里是漏了。 */
if (!customerAccess.canEdit(req.user, customer)) {
return res.status(403).json({ error: '这条客户不在你名下,请先到客户公海领取' });
}
const record = db.prepare('SELECT * FROM follow_up_records WHERE id = ? AND customer_id = ?').get(Number(req.params.followUpId), customer.id); const record = db.prepare('SELECT * FROM follow_up_records WHERE id = ? AND customer_id = ?').get(Number(req.params.followUpId), customer.id);
if (!record) return res.status(404).json({ error: '跟进记录不存在' }); if (!record) return res.status(404).json({ error: '跟进记录不存在' });
const fields = []; const fields = [];