| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- // 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 : '未知错误'));
- }
- }
|