Files
im_webapp/src/views/acc-security-view.jsx
T
Your NameandClaude Sonnet 5 a71aee9487 修复"安全"设置页退出登录无二次确认,单击立即登出
现象:从 account-settings-view.jsx"账户设置"、info-view.jsx,以及第2章系统消息列表"前往
安全设置"跳转按钮都能到达 acc-security-view.jsx 这个"安全"页(并非废弃页面,是与
personal-settings-view.jsx"设置"页并存的独立入口),点击"登出"没有任何二次确认,单击
图标就会立即退出登录,且没有任何撤销机会。

根因:该文件里"登出"按钮的 onClick 直接调用 this.props.onLogout(),而同文件里紧挨着的
"删除帐户"按钮走的是 onShowAlert 弹确认框(handleDeleteAccount)。此前 46a98782 那一轮
已经把 personal-settings-view.jsx"设置"页的登出从原生 window.confirm() 改成了自己的
ConfirmSheet/LogoutConfirm,但漏改了这个仍然可达的旧版"安全"页入口。

修复:新增 handleLogoutRequest,复用本文件已有的 onShowAlert 机制弹确认框,文案
("退出登录"/"确定要退出当前登录吗?")与 personal-settings-view.jsx 的 LogoutConfirm
保持一致,不新造一套措辞。

验证:本地登录后访问 #security,点击"登出"确认弹出对话框(标题/正文/按钮文案核对无误);
点击"取消"保持登录态且仍停留在安全页;点击"好"确认后正确清除 auth-token 并退回登录/租户
判断逻辑接管的空 hash。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 15:36:55 +08:00

210 lines
8.9 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Edit account parameters.
import React from 'react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import CheckBox from '../widgets/checkbox.jsx';
import InPlaceEdit from '../widgets/in-place-edit.jsx';
import PermissionsEditor from '../widgets/permissions-editor.jsx';
const messages = defineMessages({
delete_account: {
id: 'delete_account',
defaultMessage: 'Delete account',
description: 'Title for delete account warning'
},
delete_account_warning: {
id: 'delete_account_warning',
defaultMessage: 'Are you sure you want to delete your account? It cannot be undone.',
description: 'Warning message when deleting an account'
}
});
class AccSecurityView extends React.Component {
constructor(props) {
super(props);
const me = this.props.tinode.getMeTopic();
let blockedCount = 0;
me.contacts((c) => {
if (c.acs && !c.acs.isJoiner()) {
blockedCount ++;
}
});
const defacs = me.getDefaultAccess();
this.state = {
auth: defacs ? defacs.auth : null,
anon: defacs ? defacs.anon : null,
showPermissionEditorFor: undefined,
blockedCount: blockedCount,
};
this.handlePasswordUpdate = this.handlePasswordUpdate.bind(this);
this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);
this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);
this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);
this.handleDeleteAccount = this.handleDeleteAccount.bind(this);
this.handleLogoutRequest = this.handleLogoutRequest.bind(this);
this.handleCheckboxClick = this.handleCheckboxClick.bind(this);
}
handlePasswordUpdate(pwd) {
this.setState({password: pwd});
this.props.onUpdatePassword(pwd);
}
handleLaunchPermissionsEditor(which) {
this.setState({
showPermissionEditorFor: which,
editedPermissions: this.state[which]
});
}
handleHidePermissionsEditor() {
this.setState({showPermissionEditorFor: undefined});
}
handlePermissionsChanged(perm) {
let defacs = {};
defacs[this.state.showPermissionEditorFor] = perm;
this.props.onUpdateAccountDesc('me', undefined, undefined, defacs);
let newState = {showPermissionEditorFor: undefined};
newState[this.state.showPermissionEditorFor] = perm;
this.setState(newState);
}
handleCheckboxClick(what, checked) {
this.props.onToggleIncognitoMode(checked);
}
handleDeleteAccount(e) {
e.preventDefault();
const {formatMessage} = this.props.intl;
this.props.onShowAlert(
formatMessage(messages.delete_account), // title
formatMessage(messages.delete_account_warning), // content
(_ => this.props.onDeleteAccount()), // onConfirm
null, // use default text "OK"
true, // Show Reject button
null // use default text "Cancel"
);
}
// "退出登录"这个按钮原来是 onClick 里直接 `this.props.onLogout()`,没有任何二次确认——同一个文件里
// 紧挨着的"删除帐户"按钮反而是走 onShowAlert 弹确认框的(见上面 handleDeleteAccount)。本页
// ('security' 路由)并不是废弃页面:account-settings-view.jsx、info-view.jsx 的"安全"入口,以及
// 第2章系统消息列表里"前往安全设置"跳转按钮(message-center-view.jsx)都会真的落到这个页面,
// 跟 personal-settings-view.jsx 的"设置"页(那边的退出登录已经在别的改造里换成了自己的
// ConfirmSheet/LogoutConfirm)是两条并存的独立路径——用户从系统消息点"前往安全设置"进来,单击一下
// 图标就会被立即退出登录,且没有任何撤销机会。改成跟"删除帐户"一样走 onShowAlert,文案与
// personal-settings-view.jsx 的 LogoutConfirm 保持一致,不新造一套措辞。
handleLogoutRequest(e) {
e.preventDefault();
this.props.onShowAlert(
'退出登录',
'确定要退出当前登录吗?',
(_ => this.props.onLogout()),
null, // use default text "OK"
true, // Show Reject button
null // use default text "Cancel"
);
}
render() {
return (
<>{this.state.showPermissionEditorFor ?
<PermissionsEditor
mode={this.state.editedPermissions}
skip="O"
onSubmit={this.handlePermissionsChanged}
onCancel={this.handleHidePermissionsEditor} />
:
<div className="scrollable-panel">
<div className="panel-form-column">
<label className="small">
<FormattedMessage id="label_password" defaultMessage="Password"
description="Label for password editing" />
</label>
<div>
<FormattedMessage id="password_unchanged_prompt" defaultMessage="Unchanged"
description="Message in editor while password is unchanged">{
(password_unchanged) => <InPlaceEdit
placeholder={password_unchanged}
type="password"
onFinished={this.handlePasswordUpdate} />
}</FormattedMessage>
</div>
</div>
<div className="hr" />
<div className="panel-form-row">
<label htmlFor="incognito-mode">
<FormattedMessage id="label_incognito_mode" defaultMessage="Incognito mode:"
description="Label for incognito mode toggle" />
</label>
<CheckBox name="incognito" id="incognito-mode"
checked={this.props.incognitoMode} onChange={this.handleCheckboxClick} />
</div>
<div className="panel-form-row">
<span className="small gray">
<FormattedMessage id="incognito_mode_description" defaultMessage="Hide your online status and typing indicators."
description="Description for incognito mode toggle" />
</span>
</div>
<div className="hr" />
<div className="panel-form-column security-product-links">
<button type="button" className="flat-button" onClick={() => this.props.onNavigate('realname')}>
<i className="material-icons">verified_user</i><span>实名认证</span><i className="material-icons trailing">chevron_right</i>
</button>
<button type="button" className="flat-button" onClick={() => this.props.onNavigate('identity-check')}>
<i className="material-icons">phonelink_lock</i><span>身份验证</span><i className="material-icons trailing">chevron_right</i>
</button>
<button type="button" className="flat-button" onClick={() => this.props.onNavigate('independent-password')}>
<i className="material-icons">password</i><span>设置独立密码</span><i className="material-icons trailing">chevron_right</i>
</button>
</div>
<div className="hr" />
<div className="panel-form-column">
<a href="#" className="danger flat-button" onClick={this.handleLogoutRequest}>
<i className="material-icons">exit_to_app</i> &nbsp;<FormattedMessage id="button_logout"
defaultMessage="Logout" description="Button [Logout]" />
</a>
<a href="#" className="danger flat-button" onClick={(e) => {this.handleDeleteAccount(e);}}>
<i className="material-icons">delete</i> &nbsp;<FormattedMessage id="button_delete_account"
defaultMessage="Delete account" description="Button [Delete account]" />
</a>
</div>
<div className="hr" />
<div className="panel-form-column">
<div>
<label className="small">
<FormattedMessage id="label_default_access_mode" defaultMessage="Default access mode:"
description="Label for default access mode" />
</label>
</div>
<div className="quoted">
<div>Auth: <tt className="clickable"
onClick={this.handleLaunchPermissionsEditor.bind(this, 'auth')}>{this.state.auth}</tt></div>
<div>Anon: <tt className="clickable"
onClick={this.handleLaunchPermissionsEditor.bind(this, 'anon')}>{this.state.anon}</tt></div>
</div>
</div>
{this.state.blockedCount > 0 ?
<>
<div className="hr" />
<div className="panel-form-row">
<i className="material-icons">block</i>&nbsp;
<a href="#" className="gray" onClick={(e) => {e.preventDefault(); this.props.onShowBlocked();}}>
<FormattedMessage id="blocked_contacts_link" defaultMessage="Blocked contacts ({count})"
values={{ count: this.state.blockedCount }} description="Blocked contacts link" />
</a>
</div>
</>
: null }
</div>
}</>
);
}
};
export default injectIntl(AccSecurityView);