fmodecontainer-rename.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. // FmodeAgent 容器重命名云函数(2026-11-05 建)
  2. //
  3. // 目的:让企业管理员能重命名容器(数字生命)。
  4. // 容器名是**跨用户可见**的 —— 同公司同事都会看到新名字,
  5. // 所以必须落库到服务端,不能只改客户端本地缓存。
  6. //
  7. // 调用:POST https://server.fmode.cn/api/functions
  8. // 头:X-Parse-Application-Id / X-Parse-Session-Token
  9. // body:{ id: "<本函数 objectId>", token, params: { op, agentId, displayName } }
  10. //
  11. // params.op:
  12. // rename { agentId, displayName } → { code:200, agent:{...} }
  13. //
  14. // 平台约定(与 fmodeagent-status / fmodeagent-chatthreads 一致,实测):
  15. // · 入口 async function handler(request, response);入参在**全局 params**;
  16. // · 身份:优先 request.user(平台按 sessionToken 注入),否则按 token 查 _Session;
  17. // · 🔴 输出必须 response.status(200).json(...) **显式写出**;
  18. // 直接 return 对象平台不回包,请求会挂起。
  19. //
  20. // ============ 安全约束(务必保持)============
  21. // 1. 权限在**服务端独立判定**,不信任客户端传的任何 flag:
  22. // 超管(masters / FMODE_AGENT_SUPERADMIN)→ 可改全部容器
  23. // 公司管理员(roles 含 `${自己companyId}_Admin`)→ 可改本公司容器
  24. // 其他 → 403
  25. // 前端隐藏按钮只是减少误导,**不是安全边界**。
  26. // 2. 跨租户保护:管理员只能改自己公司的容器,超管除外。
  27. // 3. 归属一律取服务端解析出的 user,忽略客户端传入的 user / company。
  28. // 4. 只改 name 一个字段,不碰其他业务字段。
  29. var SUPERADMIN_ROLES = ['masters', 'FMODE_AGENT_SUPERADMIN'];
  30. var NAME_MAX = 24;
  31. function _tokenOf() {
  32. var h = request && request.headers ? request.headers : {};
  33. if (h['x-parse-session-token']) return h['x-parse-session-token'];
  34. if (params && params.sessionToken) return params.sessionToken;
  35. if (request && request.body && request.body.token) return request.body.token;
  36. return null;
  37. }
  38. async function _getUser(t) {
  39. if (!t) return null;
  40. if (request.user && request.user.id && !(params && params.sessionToken)) return request.user;
  41. try {
  42. var q = new Parse.Query(Parse.Session);
  43. q.equalTo('sessionToken', t);
  44. var s = await q.first({ useMasterKey: true });
  45. return s ? s.get('user') : null;
  46. } catch (e) {
  47. return null;
  48. }
  49. }
  50. /** 当前用户的角色名列表(_Role.users relation 反查,与前端 fetchRoles 同口径) */
  51. async function _rolesOf(user) {
  52. try {
  53. var q = new Parse.Query(Parse.Role);
  54. q.equalTo('users', user);
  55. q.limit(50);
  56. var list = await q.find({ useMasterKey: true });
  57. return list.map(function (r) {
  58. return r.get('name') || '';
  59. });
  60. } catch (e) {
  61. return [];
  62. }
  63. }
  64. function _bad(res, code, msg) {
  65. res.status(200).json({ code: code, msg: msg });
  66. }
  67. async function _rename(user, res) {
  68. var agentId = params && params.agentId ? String(params.agentId) : '';
  69. var displayName = params && params.displayName ? String(params.displayName).trim() : '';
  70. if (!agentId) return _bad(res, 400, '缺少 agentId');
  71. if (!displayName) return _bad(res, 400, '名称不能为空');
  72. if (displayName.length > NAME_MAX) return _bad(res, 400, '名称不能超过 ' + NAME_MAX + ' 个字符');
  73. // ① 权限:服务端独立判定
  74. var roles = await _rolesOf(user);
  75. var isSuper = roles.some(function (r) {
  76. return SUPERADMIN_ROLES.indexOf(r) >= 0;
  77. });
  78. var companyId = user.get('company') ? user.get('company').id : null;
  79. var isCompanyAdmin = !!companyId && roles.indexOf(companyId + '_Admin') >= 0;
  80. if (!isSuper && !isCompanyAdmin) {
  81. return _bad(res, 403, '只有企业管理员可以重命名容器');
  82. }
  83. // ② 查目标容器
  84. var aq = new Parse.Query('FmodeAgent');
  85. aq.equalTo('agentId', agentId);
  86. var agent = await aq.first({ useMasterKey: true });
  87. if (!agent) return _bad(res, 404, '容器不存在');
  88. // ③ 跨租户保护:公司管理员只能改自己公司的容器
  89. if (!isSuper) {
  90. var owner = agent.get('user');
  91. var ownerCompany = null;
  92. if (owner) {
  93. try {
  94. var uq = new Parse.Query(Parse.User);
  95. uq.include('company');
  96. var ownerUser = await uq.get(owner.id, { useMasterKey: true });
  97. var c = ownerUser.get('company');
  98. ownerCompany = c ? c.id : null;
  99. } catch (e) {
  100. ownerCompany = null;
  101. }
  102. }
  103. // 容器无归属公司时,仅超管可改(避免误改他人私有容器)
  104. if (!ownerCompany || ownerCompany !== companyId) {
  105. return _bad(res, 403, '只能重命名本公司的容器');
  106. }
  107. }
  108. // ④ 写库(只改 name)
  109. agent.set('name', displayName);
  110. await agent.save(null, { useMasterKey: true });
  111. res.status(200).json({
  112. code: 200,
  113. agent: {
  114. objectId: agent.id,
  115. agentId: agentId,
  116. name: displayName,
  117. },
  118. });
  119. }
  120. async function handler(request, response) {
  121. try {
  122. var token = _tokenOf();
  123. var user = await _getUser(token);
  124. if (!user) return _bad(response, 403, '未登录或登录已过期');
  125. var op = params && params.op ? String(params.op) : 'rename';
  126. if (op !== 'rename') return _bad(response, 400, '不支持的操作:' + op);
  127. return await _rename(user, response);
  128. } catch (e) {
  129. return _bad(response, 500, '重命名失败:' + (e && e.message ? e.message : '未知错误'));
  130. }
  131. }