diff --git a/api/account.php b/api/account.php index 4d0f1ba..1062a95 100644 --- a/api/account.php +++ b/api/account.php @@ -891,6 +891,17 @@ function acctBindEmailStart() { if ($email === '') error('请输入邮箱地址'); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) error('邮箱格式不正确'); + // ★ 第56批:邮箱后缀校验(与Java端ConfigManager一致) + if (!checkEmailSuffix($email)) { + $mode = '默认'; + $cfgDb = getDB(); + $cfgRow = $cfgDb->query("SELECT mode FROM email_suffix_config WHERE id = 1")->fetchArray(SQLITE3_ASSOC); + if ($cfgRow) $mode = (string)$cfgRow['mode']; + if ($mode === '白名单') error('该邮箱后缀不在允许列表中'); + else if ($mode === '黑名单') error('该邮箱后缀被禁止使用'); + else error('该邮箱后缀不可用'); + } + $cur = acctEmailOf($player); if ($cur !== '') error('账号已绑定邮箱,无需重复绑定'); diff --git a/api/register.php b/api/register.php index c2491f6..4d2ca9b 100644 --- a/api/register.php +++ b/api/register.php @@ -70,6 +70,17 @@ function registerAccount($token) { if (!$passwordHash) error('缺少password_hash'); if (!$salt) error('缺少salt'); + // ★ 第56批:邮箱后缀校验(与Java端一致) + if ($email !== '' && !checkEmailSuffix($email)) { + $mode = '默认'; + $cfgDb = getDB(); + $cfgRow = $cfgDb->query("SELECT mode FROM email_suffix_config WHERE id = 1")->fetchArray(SQLITE3_ASSOC); + if ($cfgRow) $mode = (string)$cfgRow['mode']; + if ($mode === '白名单') error('该邮箱后缀不在允许列表中'); + else if ($mode === '黑名单') error('该邮箱后缀被禁止使用'); + else error('该邮箱后缀不可用'); + } + // 验证token(注册需要token) $isPreview = true; if ($token) { @@ -126,6 +137,17 @@ function webRegister() { if (!$password) error('缺少password'); if (strlen($password) < 6) error('密码长度至少6位'); + // ★ 第56批:邮箱后缀校验(与Java端一致) + if ($email !== '' && !checkEmailSuffix($email)) { + $mode = '默认'; + $cfgDb = getDB(); + $cfgRow = $cfgDb->query("SELECT mode FROM email_suffix_config WHERE id = 1")->fetchArray(SQLITE3_ASSOC); + if ($cfgRow) $mode = (string)$cfgRow['mode']; + if ($mode === '白名单') error('该邮箱后缀不在允许列表中'); + else if ($mode === '黑名单') error('该邮箱后缀被禁止使用'); + else error('该邮箱后缀不可用'); + } + // 验证web_token $db = getDB(); $stmt = $db->prepare("SELECT * FROM weblogin_tokens WHERE web_token = :token"); diff --git a/api/sync.php b/api/sync.php index 6f9e31a..8d0c885 100644 --- a/api/sync.php +++ b/api/sync.php @@ -2930,6 +2930,19 @@ function pushPlayerLoginStatus() { $loginVerified = $val ? (int)$val : 0; } + // ★ 第56批:读取邮箱后缀配置(Java端配置为唯一权威) + $emailSuffixMode = (string)getParam('email_suffix_mode', '默认'); + $emailSuffixList = (string)getParam('email_suffix_list', ''); + + // ★ 存储后缀配置(供PHP注册/绑邮箱时校验) + $cfgDb = getDB(); + $cfgDb->exec("CREATE TABLE IF NOT EXISTS email_suffix_config (id INTEGER PRIMARY KEY DEFAULT 1, mode TEXT DEFAULT '默认', suffix_list TEXT DEFAULT '', updated_at INTEGER DEFAULT 0)"); + $cfgUpd = $cfgDb->prepare("INSERT OR REPLACE INTO email_suffix_config (id, mode, suffix_list, updated_at) VALUES (1, :mode, :list, :time)"); + $cfgUpd->bindValue(':mode', $emailSuffixMode, SQLITE3_TEXT); + $cfgUpd->bindValue(':list', $emailSuffixList, SQLITE3_TEXT); + $cfgUpd->bindValue(':time', time(), SQLITE3_INTEGER); + $cfgUpd->execute(); + if (!$player || !$webToken) { @error_log("[pushPlayerLoginStatus] FAIL: missing player or web_token. player=" . var_export($player, true) . ", web_token=" . var_export($webToken, true)); error('缺少player或web_token参数'); @@ -5096,6 +5109,32 @@ function checkPendingWebEmailBindRequests() { success(['requests' => $requests, 'count' => count($requests)]); } +// ===== 第56批:邮箱后缀校验(与Java端ConfigManager.isEmailSuffixAllowed逻辑一致)===== +function checkEmailSuffix($email) { + $db = getDB(); + $cfgRow = $db->query("SELECT mode, suffix_list FROM email_suffix_config WHERE id = 1")->fetchArray(SQLITE3_ASSOC); + if (!$cfgRow) return true; // 无配置=默认模式=不校验 + $mode = (string)$cfgRow['mode']; + $suffixList = (string)$cfgRow['suffix_list']; + if ($mode === '默认' || $suffixList === '') return true; + + $suffix = ''; + $atIdx = strpos($email, '@'); + if ($atIdx !== false && $atIdx < strlen($email) - 1) { + $suffix = strtolower(trim(substr($email, $atIdx + 1))); + } + + $suffixes = preg_split('/[,;\\n]+/', $suffixList); + $found = false; + foreach ($suffixes as $s) { + $t = strtolower(trim($s)); + if ($t !== '' && $t === $suffix) { $found = true; break; } + } + if ($mode === '白名单') return $found; + if ($mode === '黑名单') return !$found; + return true; +} + // ===== 第53批:插件回传游戏内玩家对邮箱绑定请求的处理结果(SECRET_KEY认证)===== function webEmailBindResult() { $secret = getParam('secret'); diff --git a/player.php b/player.php index 77d42f7..dc9374a 100644 --- a/player.php +++ b/player.php @@ -248,6 +248,31 @@ if ($currentVersion !== $BUILD_VERSION) { from { opacity: 0; transform: translateY(30px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } } + /* ★ 第56批:抽屉式弹窗动画(绑定邮箱专用)*/ + @keyframes drawerSlideDown { + from { transform: translateY(-100%); opacity: 0; } + to { transform: translateY(0); opacity: 1; } + } + @keyframes drawerSlideUp { + from { transform: translateY(0); opacity: 1; } + to { transform: translateY(100%); opacity: 0; } + } + .drawer-overlay { + position: fixed; top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0,0,0,0.6); z-index: 999; + display: none; justify-content: center; align-items: flex-start; + padding-top: 15vh; + backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); + } + .drawer-overlay.show { display: flex; } + .drawer-card { + background: var(--card); border: 1px solid var(--border); + border-radius: 12px 12px 0 0; padding: 24px; + width: 420px; max-width: 90%; + box-shadow: 0 -8px 32px rgba(0,0,0,0.3); + } + .drawer-card.animate-in { animation: drawerSlideDown 0.3s ease; } + .drawer-card.animate-out { animation: drawerSlideUp 0.25s ease forwards; } /* ★ 毛玻璃Alert弹窗 */ .glass-alert-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; @@ -368,6 +393,18 @@ if ($currentVersion !== $BUILD_VERSION) { + +
+
+

📧 网页绑定邮箱

+
+
+ + +
+
+
+