prepare('SELECT password_hash FROM users WHERE username = ? LIMIT 1'); $st->execute([$username]); $row = $st->fetch(); if (!$row) { // 用户不存在也执行一次等价开销,削弱用户名枚举的时序差异 password_verify($plain, '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'); return false; } $stored = (string) $row['password_hash']; // 旧库:32 位十六进制 md5 → 校验通过后透明升级 if (preg_match('/^[0-9a-f]{32}$/i', $stored)) { if (!hash_equals($stored, hp_password_hash_legacy($plain))) { return false; } $upd = db()->prepare('UPDATE users SET password_hash = ? WHERE username = ?'); $upd->execute([hp_password_hash($plain), $username]); return true; } return password_verify($plain, $stored); } /** 指定用户(默认当前登录用户)是否必须修改密码(初始/重置密码未改时为 true) */ function hp_password_needs_change(?string $username = null): bool { $username = ($username !== null) ? $username : (string) ($_SESSION['admin'] ?? ''); if ($username === '') { return false; } try { $st = db()->prepare('SELECT must_change_pwd FROM users WHERE username = ? LIMIT 1'); $st->execute([$username]); return ((int) $st->fetchColumn()) === 1; } catch (Throwable $e) { return false; } } /** 执行登录(成功返回 true;成功/失败均写入登录日志与设备信息) */ function hp_login(string $username, string $plain, string $deviceClient = ''): bool { $uaRaw = (string) ($_SERVER['HTTP_USER_AGENT'] ?? ''); if (hp_verify_user($username, $plain)) { session_regenerate_id(true); $_SESSION['admin'] = $username; // 记录本次登录信息(供后台“登录信息”卡片展示) $_SESSION['login_at'] = date('Y-m-d H:i:s'); $_SESSION['login_ip'] = client_ip(); $_SESSION['login_ua'] = login_ua_brief($uaRaw); $_SESSION['login_device'] = login_device_brief($deviceClient, $uaRaw); login_log_write($username, 'ok', $deviceClient); return true; } login_log_write($username, 'fail', $deviceClient); return false; } /** 修改当前用户密码 */ function hp_change_password(string $oldPlain, string $newPlain): array { if (!is_logged_in()) { return [false, '未登录']; } if (!hp_verify_user((string) $_SESSION['admin'], $oldPlain)) { return [false, '原密码不正确']; } if (strlen($newPlain) < 6) { return [false, '新密码至少 6 位']; } $st = db()->prepare('UPDATE users SET password_hash = ?, must_change_pwd = 0 WHERE username = ?'); $st->execute([hp_password_hash($newPlain), (string) $_SESSION['admin']]); return [true, '密码修改成功']; } /** 退出登录 */ function hp_logout(): void { $_SESSION = []; if (ini_get('session.use_cookies')) { $p = session_get_cookie_params(); setcookie(session_name(), '', time() - 42000, $p['path'], $p['domain'], $p['secure'], $p['httponly']); } session_destroy(); } /** CSRF token */ function csrf_token(): string { if (empty($_SESSION['csrf'])) { $_SESSION['csrf'] = bin2hex(random_bytes(16)); } return (string) $_SESSION['csrf']; } function csrf_field(): string { return ''; } function csrf_verify(): bool { return isset($_POST['csrf']) && is_string($_POST['csrf']) && hash_equals(csrf_token(), $_POST['csrf']); }