群主转让加一步密码二次确认,跟账号注销/改密码等其它高风险操作保持一致(第6章复测发现)

serveIMGroupOwnerTransfer 核实过:只认 {target_user_id, version},没有任何
action_token/二次验证机制——不是文档滞后,是这个操作本身就没有服务端强制的二次
确认。但转让群主同样不可逆、后果同样重大("确定后你将立即失去群主身份"),本项目
对同一类场景已有明确先例:personal-settings-view.jsx 的 DeleteAccountConfirm 就是
纯前端加的密码输入门槛,不指望服务端校验它,只是让"手滑点确定"变成需要多打一遍
密码才会发生的事。

OwnerSelect 原来点目标人选后只有一层"确定/取消"的确认表,点确定直接调用真实转让
接口。现在拆成两步:第一步维持原有确认文案不变,第二步新增密码输入,留空点确定
会被拦下(不发请求),填了任意内容才真正调用 transferGroupOwner。

真实验证(真实群+真实候选人):空密码点确定确认没有发出请求;填入密码后
transferGroupOwner 真实调用成功,正确跳转回群组管理页。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-04 17:04:26 +08:00
co-authored by Claude Opus 5
parent 542aca3dc2
commit 7728afe79f
4 changed files with 73 additions and 8 deletions
+41 -4
View File
@@ -12097,6 +12097,8 @@ function OwnerSelect({
const [members, setMembers] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(null);
const [query, setQuery] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
const [target, setTarget] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(null);
const [passwordStep, setPasswordStep] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
const [password, setPassword] = react__WEBPACK_IMPORTED_MODULE_0___default().useState('');
const [busy, setBusy] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
const [loadError, setLoadError] = react__WEBPACK_IMPORTED_MODULE_0___default().useState(false);
react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => {
@@ -12111,7 +12113,16 @@ function OwnerSelect({
const visible = (members || []).filter(m => (m.group_nickname || m.display_name || '').includes(query));
const admins = visible.filter(m => m.role === 'admin');
const others = visible.filter(m => m.role !== 'admin');
const reset = () => {
setTarget(null);
setPasswordStep(false);
setPassword('');
};
const confirm = () => {
if (!password) {
onToast('请输入密码以确认');
return;
}
if (busy || !target) return;
setBusy(true);
(0,_lib_im_api_js__WEBPACK_IMPORTED_MODULE_4__.transferGroupOwner)(topic, target.user_id).then(() => {
@@ -12159,7 +12170,7 @@ function OwnerSelect({
}, void 0, false), others.map(row)]
}, void 0, true) : null]
}, void 0, true)
}, void 0, false), target ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", {
}, void 0, false), target && !passwordStep ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", {
className: "group-sheet-backdrop",
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("section", {
className: "group-confirm-sheet",
@@ -12170,12 +12181,38 @@ function OwnerSelect({
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
type: "button",
className: "danger",
disabled: busy,
onClick: confirm,
onClick: () => setPasswordStep(true),
children: "确定"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
type: "button",
onClick: () => setTarget(null),
onClick: reset,
children: "取消"
}, void 0, false)]
}, void 0, true)
}, void 0, false) : null, target && passwordStep ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", {
className: "group-sheet-backdrop",
children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("section", {
className: "group-confirm-sheet",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("h2", {
children: "输入密码确认转让"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("p", {
children: ["请输入登录密码以确认将群主转让给", target.group_nickname || target.display_name, "(该操作立即生效,不可撤销)。"]
}, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("input", {
type: "password",
autoFocus: true,
className: "group-password-input",
value: password,
onChange: e => setPassword(e.target.value),
placeholder: "登录密码"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
type: "button",
className: "danger",
disabled: busy,
onClick: confirm,
children: busy ? '处理中…' : '确定'
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", {
type: "button",
onClick: reset,
children: "取消"
}, void 0, false)]
}, void 0, true)
+1 -1
View File
File diff suppressed because one or more lines are too long