2FA解绑报警改发玩家本人邮箱;未绑定2FA时不再提示验证码错误
This commit is contained in:
@@ -184,7 +184,7 @@ public class ConfigManager {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// 检查管理员报警邮箱配置项(2FA 解绑等安全事件的报警收件人)
|
||||
// 检查管理员报警邮箱配置项(备用收件人;2FA 解绑报警现发玩家本人,见 TwoFactorManager)
|
||||
if (!smtpSettings.containsKey("管理员邮箱")) {
|
||||
logger.info("[Sdf1_login] 邮件配置缺失: 管理员邮箱,自动补全");
|
||||
smtpSettings.put("管理员邮箱", "");
|
||||
@@ -204,7 +204,7 @@ public class ConfigManager {
|
||||
return smtpSettings.getOrDefault(key, "");
|
||||
}
|
||||
|
||||
/** 管理员报警邮箱(2FA 解绑等安全事件的收件人),未配置返回空串 */
|
||||
/** 管理员报警邮箱(备用收件人,当前 2FA 解绑报警不使用),未配置返回空串 */
|
||||
public String getAdminEmail() {
|
||||
return getSmtp("管理员邮箱").trim();
|
||||
}
|
||||
@@ -355,7 +355,7 @@ public class ConfigManager {
|
||||
|
||||
// 保存管理员报警邮箱(2FA 解绑等安全事件收件人)
|
||||
L.add("");
|
||||
L.add("# --- 管理员报警邮箱(2FA解绑等安全事件的收件人,留空则只记日志) ---");
|
||||
L.add("# --- 管理员报警邮箱(备用收件人,2FA解绑报警现发玩家本人,留空则只记日志) ---");
|
||||
L.add("管理员邮箱=" + smtpSettings.getOrDefault("管理员邮箱", ""));
|
||||
writtenKeys.add("管理员邮箱");
|
||||
|
||||
@@ -394,7 +394,7 @@ public class ConfigManager {
|
||||
L.add("# 示例: qq.com");
|
||||
L.add("# 示例: gmail.com");
|
||||
L.add("邮箱后缀列表=");
|
||||
L.add("# --- 管理员报警邮箱(2FA解绑等安全事件的收件人,留空则只记日志) ---");
|
||||
L.add("# --- 管理员报警邮箱(备用收件人,2FA解绑报警现发玩家本人,留空则只记日志) ---");
|
||||
L.add("管理员邮箱=");
|
||||
writeLines(f, L);
|
||||
}
|
||||
|
||||
@@ -31,10 +31,12 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* [控制台] /2fa remove -c 取消待执行的强制解绑(冷静期内可撤回)
|
||||
*
|
||||
* 安全约定:
|
||||
* 1) 玩家侧 remove 只接受认证器 App 的 6 位动态码,其它任何输入(含玩家名、--force、-c)
|
||||
* 1) 玩家侧 remove 先判绑定:未绑定直说「未绑定」,不出现任何验证码字样;
|
||||
* 已绑定则只接受认证器 App 的 6 位动态码,其它任何输入(含玩家名、--force、-c)
|
||||
* 一律回「验证码无效」,绝不提示权限问题(最小化信息透露)。
|
||||
* 2) 强制解绑权限级别 = 控制台,玩家侧代码路径完全不解析 --force。
|
||||
* 3) 解绑三场景(恢复代码 / 玩家主动 / 管理员强制)都会向「管理员邮箱」推报警邮件。
|
||||
* 3) 解绑三场景(恢复代码 / 玩家主动 / 管理员强制)都向「当事玩家本人绑定的邮箱」
|
||||
* 推报警邮件(不发管理员;玩家未绑定邮箱则只记日志)。
|
||||
*
|
||||
* 前置条件:必须先绑定邮箱(/email)。
|
||||
*/
|
||||
@@ -96,6 +98,11 @@ public class TwoFactorManager {
|
||||
break;
|
||||
case "remove":
|
||||
case "解绑":
|
||||
// ★ 未绑定 → 直说未绑定,不进入任何验证码话术
|
||||
if (!isEnabled(p)) {
|
||||
p.sendMessage("§c您尚未绑定二次验证,无需解绑");
|
||||
break;
|
||||
}
|
||||
if (args.length < 2) {
|
||||
p.sendMessage("§c用法: /2fa remove <6位动态码>");
|
||||
break;
|
||||
@@ -576,16 +583,17 @@ public class TwoFactorManager {
|
||||
|
||||
private void handleRemove(Player p, String code) {
|
||||
code = code.trim();
|
||||
// ★ 最小化信息透露:玩家名、--force、-c 等任何非动态码输入
|
||||
// ★ 未绑定 → 直说未绑定,绝不出「验证码」字样(用户要求)
|
||||
if (!isEnabled(p)) {
|
||||
p.sendMessage("§c您尚未绑定二次验证,无需解绑");
|
||||
return;
|
||||
}
|
||||
// ★ 最小化信息透露:已绑定玩家输入玩家名、--force、-c 等任何非动态码
|
||||
// 一律按「验证码无效」处理,不提示权限/用法差异
|
||||
if (!code.matches("\\d{6}")) {
|
||||
p.sendMessage("§c验证码无效,请核对认证器 App 上的 6 位数字");
|
||||
return;
|
||||
}
|
||||
if (!isEnabled(p)) {
|
||||
p.sendMessage("§c您尚未绑定二次验证");
|
||||
return;
|
||||
}
|
||||
String secret = str(plugin.getDb().getField(p.getName(), "twofa_secret"));
|
||||
if (secret.isEmpty() || !verifyCode(secret, code)) {
|
||||
p.sendMessage("§c动态码错误,请核对认证器 App 上的 6 位数字");
|
||||
@@ -825,11 +833,11 @@ public class TwoFactorManager {
|
||||
}
|
||||
|
||||
if (!sendUnbindAlarm(target, "管理员强制解绑", "控制台(管理员)", ip)) {
|
||||
out.sendMessage("§e[2FA] 报警邮件未发送:SMTP设置.txt 未配置"
|
||||
+ "「管理员邮箱」(详情见后台日志)");
|
||||
out.sendMessage("§e[2FA] 报警邮件未发送:玩家 " + target
|
||||
+ " 未绑定邮箱(详情见后台日志)");
|
||||
} else {
|
||||
out.sendMessage("§7[2FA] 解绑报警邮件已投递至 "
|
||||
+ maskEmail(safeAdminEmail()) + "(结果见后台日志)");
|
||||
out.sendMessage("§7[2FA] 解绑报警邮件已投递至玩家 "
|
||||
+ target + " 的绑定邮箱(结果见后台日志)");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
out.sendMessage("§c[2FA] 删除异常:" + e);
|
||||
@@ -839,49 +847,39 @@ public class TwoFactorManager {
|
||||
|
||||
// ==================== 解绑报警邮件 ====================
|
||||
|
||||
/** SMTP设置.txt 的「管理员邮箱」(安全事件报警收件人),未配置返回空串 */
|
||||
private String safeAdminEmail() {
|
||||
try {
|
||||
String v = plugin.getConfig2().getSmtp("管理员邮箱");
|
||||
return v == null ? "" : v.trim();
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 2FA 解绑报警邮件(恢复代码解绑 / 玩家主动解绑 / 管理员强制解绑 三场景统一调用)。
|
||||
* 收件人 = SMTP设置.txt 的「管理员邮箱」;未配置则只记日志。
|
||||
* 收件人 = 当事玩家本人在 DB 绑定的邮箱(email 字段),不发管理员;
|
||||
* 玩家未绑定邮箱则只记日志。
|
||||
* 发信在异步线程做(SMTP 阻塞最长 15 秒,绝不能卡主线程)。
|
||||
*
|
||||
* @return true = 已投递发送(成败见后台日志);false = 未配置管理员邮箱
|
||||
* @return true = 已投递发送(成败见后台日志);false = 玩家未绑定邮箱
|
||||
*/
|
||||
private boolean sendUnbindAlarm(String playerName, String mode,
|
||||
String operator, String ip) {
|
||||
String admin = safeAdminEmail();
|
||||
if (admin.isEmpty()) {
|
||||
plugin.getLogger().warning("[2FA] 解绑报警邮件未发送:"
|
||||
+ "SMTP设置.txt 未配置「管理员邮箱」(玩家=" + playerName
|
||||
+ " 方式=" + mode + ")");
|
||||
// 收件人 = 当事玩家本人绑定的邮箱
|
||||
String to = str(plugin.getDb().getField(playerName, "email"));
|
||||
if (to.isEmpty()) {
|
||||
plugin.getLogger().warning("[2FA] 解绑报警邮件未发送:玩家 "
|
||||
+ playerName + " 未绑定邮箱(方式=" + mode + ")");
|
||||
return false;
|
||||
}
|
||||
String time = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
||||
.format(new java.util.Date());
|
||||
String bound = str(plugin.getDb().getField(playerName, "email"));
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("【安全报警】玩家 ").append(playerName)
|
||||
b.append("【安全报警】您的账号 ").append(playerName)
|
||||
.append(" 的二次验证(2FA)已解除\n\n");
|
||||
b.append("触发方式:").append(mode).append('\n');
|
||||
b.append("执行者:").append(operator).append('\n');
|
||||
b.append("时间:").append(time).append('\n');
|
||||
b.append("来源IP:").append(ip == null || ip.isEmpty() ? "未知" : ip).append('\n');
|
||||
b.append("玩家绑定邮箱:").append(bound.isEmpty() ? "无" : maskEmail(bound)).append('\n');
|
||||
b.append("收件邮箱:").append(maskEmail(to)).append('\n');
|
||||
b.append("服务器:草原探险\n\n");
|
||||
b.append("若该操作非本人发起,账号当前仅剩密码保护,");
|
||||
b.append("请立即让玩家修改密码并重新绑定二次验证。");
|
||||
b.append("若该操作非本人发起,您的账号当前仅剩密码保护,");
|
||||
b.append("请立即修改密码并重新绑定二次验证;");
|
||||
b.append("如非本人操作,请尽快检查登录记录并联系管理员。");
|
||||
|
||||
final String to = admin;
|
||||
final String subject = "[Sdf1_login] 2FA解绑报警 - " + playerName + "(" + mode + ")";
|
||||
final String mailBody = b.toString();
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
|
||||
Reference in New Issue
Block a user