// FmodeAgent 容器重命名云函数(2026-11-05 建) // // 目的:让企业管理员能重命名容器(数字生命)。 // 容器名是**跨用户可见**的 —— 同公司同事都会看到新名字, // 所以必须落库到服务端,不能只改客户端本地缓存。 // // 调用:POST https://server.fmode.cn/api/functions // 头:X-Parse-Application-Id / X-Parse-Session-Token // body:{ id: "<本函数 objectId>", token, params: { op, agentId, displayName } } // // params.op: // rename { agentId, displayName } → { code:200, agent:{...} } // // 平台约定(与 fmodeagent-status / fmodeagent-chatthreads 一致,实测): // · 入口 async function handler(request, response);入参在**全局 params**; // · 身份:优先 request.user(平台按 sessionToken 注入),否则按 token 查 _Session; // · 🔴 输出必须 response.status(200).json(...) **显式写出**; // 直接 return 对象平台不回包,请求会挂起。 // // ============ 安全约束(务必保持)============ // 1. 权限在**服务端独立判定**,不信任客户端传的任何 flag: // 超管(masters / FMODE_AGENT_SUPERADMIN)→ 可改全部容器 // 公司管理员(roles 含 `${自己companyId}_Admin`)→ 可改本公司容器 // 其他 → 403 // 前端隐藏按钮只是减少误导,**不是安全边界**。 // 2. 跨租户保护:管理员只能改自己公司的容器,超管除外。 // 3. 归属一律取服务端解析出的 user,忽略客户端传入的 user / company。 // 4. 只改 name 一个字段,不碰其他业务字段。 var SUPERADMIN_ROLES = ['masters', 'FMODE_AGENT_SUPERADMIN']; var NAME_MAX = 24; function _tokenOf() { var h = request && request.headers ? request.headers : {}; if (h['x-parse-session-token']) return h['x-parse-session-token']; if (params && params.sessionToken) return params.sessionToken; if (request && request.body && request.body.token) return request.body.token; return null; } async function _getUser(t) { if (!t) return null; if (request.user && request.user.id && !(params && params.sessionToken)) return request.user; try { var q = new Parse.Query(Parse.Session); q.equalTo('sessionToken', t); var s = await q.first({ useMasterKey: true }); return s ? s.get('user') : null; } catch (e) { return null; } } /** 当前用户的角色名列表(_Role.users relation 反查,与前端 fetchRoles 同口径) */ async function _rolesOf(user) { try { var q = new Parse.Query(Parse.Role); q.equalTo('users', user); q.limit(50); var list = await q.find({ useMasterKey: true }); return list.map(function (r) { return r.get('name') || ''; }); } catch (e) { return []; } } function _bad(res, code, msg) { res.status(200).json({ code: code, msg: msg }); } async function _rename(user, res) { var agentId = params && params.agentId ? String(params.agentId) : ''; var displayName = params && params.displayName ? String(params.displayName).trim() : ''; if (!agentId) return _bad(res, 400, '缺少 agentId'); if (!displayName) return _bad(res, 400, '名称不能为空'); if (displayName.length > NAME_MAX) return _bad(res, 400, '名称不能超过 ' + NAME_MAX + ' 个字符'); // ① 权限:服务端独立判定 var roles = await _rolesOf(user); var isSuper = roles.some(function (r) { return SUPERADMIN_ROLES.indexOf(r) >= 0; }); var companyId = user.get('company') ? user.get('company').id : null; var isCompanyAdmin = !!companyId && roles.indexOf(companyId + '_Admin') >= 0; if (!isSuper && !isCompanyAdmin) { return _bad(res, 403, '只有企业管理员可以重命名容器'); } // ② 查目标容器 var aq = new Parse.Query('FmodeAgent'); aq.equalTo('agentId', agentId); var agent = await aq.first({ useMasterKey: true }); if (!agent) return _bad(res, 404, '容器不存在'); // ③ 跨租户保护:公司管理员只能改自己公司的容器 if (!isSuper) { var owner = agent.get('user'); var ownerCompany = null; if (owner) { try { var uq = new Parse.Query(Parse.User); uq.include('company'); var ownerUser = await uq.get(owner.id, { useMasterKey: true }); var c = ownerUser.get('company'); ownerCompany = c ? c.id : null; } catch (e) { ownerCompany = null; } } // 容器无归属公司时,仅超管可改(避免误改他人私有容器) if (!ownerCompany || ownerCompany !== companyId) { return _bad(res, 403, '只能重命名本公司的容器'); } } // ④ 写库(只改 name) agent.set('name', displayName); await agent.save(null, { useMasterKey: true }); res.status(200).json({ code: 200, agent: { objectId: agent.id, agentId: agentId, name: displayName, }, }); } async function handler(request, response) { try { var token = _tokenOf(); var user = await _getUser(token); if (!user) return _bad(response, 403, '未登录或登录已过期'); var op = params && params.op ? String(params.op) : 'rename'; if (op !== 'rename') return _bad(response, 400, '不支持的操作:' + op); return await _rename(user, response); } catch (e) { return _bad(response, 500, '重命名失败:' + (e && e.message ? e.message : '未知错误')); } }