From 175fa5d2442325ade9f986785c7da9ad5ab0e843 Mon Sep 17 00:00:00 2001
From: qing <1776177662@qq.com>
Date: Thu, 13 Aug 2026 08:08:01 +0800
Subject: [PATCH] =?UTF-8?q?refactor(fsys):=20=E7=A7=BB=E9=99=A4=E9=81=97?=
=?UTF-8?q?=E7=95=99=E6=AD=BB=E6=8E=A5=E5=8F=A3=E4=B8=8E=E5=BF=AB=E6=8D=B7?=
=?UTF-8?q?=E7=99=BB=E5=BD=95=E5=85=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
删除无调用方的 /api/check_user(Users 无 email 列,生产恒 500)与
/api/hash(前端早改直传密码给 ca,无鉴权 SHA-256 端点徒增哈希预言机
风险),连带清理 _rpc/_call_db/FsysExp/sha256_hex/限速设施等失去唯一
使用方的死代码;login.html 移除 yhdet 快捷登录按钮及"或"分隔线
(fsys 无 /yhdet_callback,入口本就失效)。同步两处 README。
---
README.md | 2 +-
fsys/README.md | 4 +-
fsys/public/login.html | 3 -
fsys/src/fsys.cpp | 231 -----------------------------------------
4 files changed, 2 insertions(+), 238 deletions(-)
diff --git a/README.md b/README.md
index 1f01871..45d9541 100755
--- a/README.md
+++ b/README.md
@@ -122,7 +122,7 @@
- 支持文件浏览、视频/音频在线播放(HTTP Range 分段请求,返回 206/416)、一键下载。
- 路径遍历防护(realpath 前缀校验),仅允许访问 `--root` 之内。
- **dnfs2 代理**:`/get_notes`、`/add_note`、`/delete_note`、`/update_note` 透传 Authorization 转发到 `dnfs2:8006`(解决浏览器跨域)。
-- **账号旁路**:`/api/check_user`、`/api/reset_password` 等接口直接读写 `gateway/sqlite3.db` 的 Users 表;`/yhdet_callback`、`/api/yhdet_*` 对接 yhdet.top OAuth 登录。
+- **账号旁路已收口**:原 static 时代的 `/api/check_user`、`/api/reset_password`、yhdet OAuth 等旁路接口已移除/迁至 ca(忘记密码走 ca `/reset/send_code` + `/reset_password`),fsys 不再直接读写用户表。
- 统一品牌"树状菜单",支持深色模式(`css/theme.css`)与样式节点注入的自定义主题/样式(见 [docs/dnfs_custom_themes.md](docs/dnfs_custom_themes.md))。
- 配置全部经 CLI 传递(CLI > 环境变量 > 默认值,原 `confs/configs/static.ini` 迁入 `core/services.yaml` 的 fsys 条目 args),不依赖 confs。
diff --git a/fsys/README.md b/fsys/README.md
index ca38fdd..1c16305 100644
--- a/fsys/README.md
+++ b/fsys/README.md
@@ -20,7 +20,7 @@ fsys :9200 (libhttp 异步多线程 + CORS)
│
├── public/ 静态文件根目录
├── dnfs2 :8006 笔记代理(反向代理)
- ├── db2 :8005 用户查询 + 日志写入
+ ├── db2 :8005 日志写入
└── (配置纯 CLI 传递,不依赖 confs)
```
@@ -40,8 +40,6 @@ fsys :9200 (libhttp 异步多线程 + CORS)
| 路径 | 说明 |
|------|------|
-| `POST /api/hash` | SHA-256 密码哈希(限速 20/min/IP) |
-| `POST /api/check_user` | 检查用户是否已注册(限速 10/min/IP) |
| `GET /api/favicon?url=` | favicon SSRF 防护代理(512KB 上限,仅浏览器侧 Cache-Control 缓存 1 天,无服务端缓存) |
### dnfs2 代理路由
diff --git a/fsys/public/login.html b/fsys/public/login.html
index 2c96e8e..52197ea 100644
--- a/fsys/public/login.html
+++ b/fsys/public/login.html
@@ -37,9 +37,6 @@
忘记密码?
-
- 开源社区 快捷登录
-
diff --git a/fsys/src/fsys.cpp b/fsys/src/fsys.cpp
index 36c004a..ff00323 100644
--- a/fsys/src/fsys.cpp
+++ b/fsys/src/fsys.cpp
@@ -5,8 +5,6 @@
* GET /
静态文件 / 目录浏览
* GET /api/filelist?path= 文件列表 JSON
* GET /video/... 视频/音频流媒体(HTTP Range 206)
- * POST /api/hash SHA-256 密码哈希(限速 20/min/IP)
- * POST /api/check_user 用户存在性检查(限速 10/min/IP)
* GET /api/favicon?url= favicon SSRF 防护代理
* POST /get_notes 等 4 个 dnfs2 反向代理
* GET /health 健康检查
@@ -32,7 +30,6 @@
#include
#include
#include
-#include
#include
#include
@@ -94,12 +91,6 @@ static constexpr size_t kHttpMaxResponse = 10 * 1024 * 1024;
// 单次文件响应大小上限(string_body 全量读入内存,防大文件 DoS;审计 F-4)
static constexpr uintmax_t kMaxServeFileSize = 50 * 1024 * 1024;
-// 限速
-static constexpr int kHashRateLimit = 20;
-static constexpr int kCheckUserRateLimit = 10;
-static constexpr int kRateWindowSeconds = 60;
-static constexpr size_t kRateMaxKeys = 65536; // 限速键数上限,防伪造 XFF 旋转导致内存增长
-
// 视图文件名
static const std::string kViewFile = "__view.html";
@@ -134,30 +125,6 @@ static int safe_stoi(const std::string& s, const char* field_name) {
}
}
-static std::string sha256_hex(const std::string& input) {
- EVP_MD* md = EVP_MD_fetch(nullptr, "SHA2-256", nullptr);
- const bool fetched = (md != nullptr);
- if (!md) {
- // OpenSSL < 3.0 回退
- md = const_cast(EVP_sha256());
- }
- unsigned char hash[EVP_MAX_MD_SIZE];
- unsigned int hash_len = 0;
- EVP_Digest(input.data(), input.size(), hash, &hash_len, md, nullptr);
- // EVP_MD_fetch 返回的无论 digest 成败都需释放,EVP_sha256 返回的不需要
- if (fetched) {
- EVP_MD_free(md);
- }
- static const char hex_chars[] = "0123456789abcdef";
- std::string result;
- result.reserve(hash_len * 2);
- for (unsigned int i = 0; i < hash_len; ++i) {
- result += hex_chars[hash[i] >> 4];
- result += hex_chars[hash[i] & 0x0f];
- }
- return result;
-}
-
static std::string format_file_size(uintmax_t size) {
double s = static_cast(size);
const char* units[] = {"B", "KB", "MB", "GB", "TB"};
@@ -341,19 +308,6 @@ static Config load_config(int argc, char* argv[]) {
return cfg;
}
-// ============================================================================
-// 异常类
-// ============================================================================
-
-class FsysExp : public std::runtime_error {
-public:
- FsysExp(const std::string& msg, int code)
- : std::runtime_error(msg), status_(code) {}
- int status() const noexcept { return status_; }
-private:
- int status_;
-};
-
// ============================================================================
// MIME 类型映射
// ============================================================================
@@ -582,8 +536,6 @@ class FsysServer {
public:
explicit FsysServer(Config cfg)
: _cfg(std::move(cfg)),
- _rpc(_cfg.db_host, _cfg.db_port, "fsys", 5.0,
- /*retry_on_refused=*/true),
_log_rpc(_cfg.db_host, _cfg.db_port, "fsys-log", kLogRpcTimeout,
/*retry_on_refused=*/false)
{
@@ -618,7 +570,6 @@ public:
private:
Config _cfg;
- RpcClient _rpc; // db RPC(retry_on_refused=true)
RpcClient _log_rpc; // 日志写入
std::unique_ptr> _log_queue;
@@ -627,10 +578,6 @@ private:
HttpServer* _server = nullptr;
static std::atomic _should_stop;
- // 限速存储
- std::mutex _rate_mutex;
- std::unordered_map> _rate_store;
-
static void _signal_handler(int) { _should_stop.store(true); }
// ---- 日志 --------------------------------------------------------------
@@ -671,68 +618,6 @@ private:
}
}
- // ---- db 调用 -----------------------------------------------------------
-
- struct DbResult {
- bool success = false;
- json::value data;
- std::string error;
- };
-
- DbResult _call_db(const json::object& payload) {
- try {
- json::value data = _rpc.call("execute", payload);
- return {true, data, ""};
- } catch (const RpcError& e) {
- return {false, nullptr, e.what()};
- } catch (const std::exception& e) {
- _log("ERROR", "fsys", std::string("db 调用失败: ") + e.what());
- throw FsysExp("数据库服务暂时不可用", 502);
- }
- }
-
- // ---- 客户端 IP ---------------------------------------------------------
-
- static std::string client_ip(const http::request& req) {
- auto xff = req.find("X-Forwarded-For");
- if (xff != req.end()) {
- std::string val(xff->value());
- auto comma = val.find(',');
- std::string ip = (comma == std::string::npos) ? val : val.substr(0, comma);
- auto b = ip.find_first_not_of(" ");
- auto e = ip.find_last_not_of(" ");
- if (b != std::string::npos) return ip.substr(b, e - b + 1);
- }
- return "unknown";
- }
-
- // ---- 限速 --------------------------------------------------------------
-
- bool _check_rate_limit(const std::string& key, int max_requests) {
- auto now = std::chrono::steady_clock::now();
- auto cutoff = now - std::chrono::seconds(kRateWindowSeconds);
- std::lock_guard lock(_rate_mutex);
- // 键数超限:全局驱逐过期窗口(防伪造 XFF 旋转导致无限增长)
- if (_rate_store.size() > kRateMaxKeys) {
- for (auto it = _rate_store.begin(); it != _rate_store.end();) {
- auto& ts = it->second;
- ts.erase(std::remove_if(ts.begin(), ts.end(),
- [&](const auto& t) { return t < cutoff; }),
- ts.end());
- if (ts.empty()) it = _rate_store.erase(it);
- else ++it;
- }
- }
- auto& timestamps = _rate_store[key];
- timestamps.erase(
- std::remove_if(timestamps.begin(), timestamps.end(),
- [&](const auto& t) { return t < cutoff; }),
- timestamps.end());
- if (static_cast(timestamps.size()) >= max_requests) return false;
- timestamps.push_back(now);
- return true;
- }
-
// ---- CORS --------------------------------------------------------------
bool _is_allowed_origin(const std::string& origin) const {
@@ -778,10 +663,6 @@ private:
const http::request& req);
http::response _handle_video(
const http::request& req);
- http::response _handle_hash(
- const http::request& req);
- http::response _handle_check_user(
- const http::request& req);
http::response _handle_favicon(
const http::request& req);
http::response _handle_dnfs_proxy(
@@ -822,9 +703,6 @@ void FsysServer::run() {
server.get("/api/favicon", [this](const auto& req, const auto&) { return _handle_favicon(req); });
server.get("/health", [this](const auto& req, const auto&) { return _handle_health(req); });
- server.post("/api/hash", [this](const auto& req, const auto&) { return _handle_hash(req); });
- server.post("/api/check_user", [this](const auto& req, const auto&) { return _handle_check_user(req); });
-
// dnfs2 代理路由
auto dnfs_handler = [this](const std::string& route) {
return [this, route](const auto& req, const auto&) {
@@ -1072,8 +950,6 @@ http::response FsysServer::_handle_filelist(
result["items"] = items;
return make_json_obj(http::status::ok, result, req);
- } catch (const FsysExp& e) {
- return make_error(static_cast(e.status()), e.what(), req);
} catch (const std::exception& e) {
_log("ERROR", "fsys", std::string("\u6587\u4ef6\u5217\u8868\u9519\u8bef: ") + e.what());
return make_error(http::status::internal_server_error, "\u5185\u90e8\u9519\u8bef", req);
@@ -1177,113 +1053,6 @@ http::response FsysServer::_handle_video(
}
}
-// ============================================================================
-// POST /api/hash — SHA-256 密码哈希
-// ============================================================================
-
-http::response FsysServer::_handle_hash(
- const http::request& req)
-{
- try {
- std::string ip = client_ip(req);
- if (!_check_rate_limit("hash:" + ip, kHashRateLimit))
- return make_error(http::status::too_many_requests, "rate limit exceeded", req);
-
- json::value val;
- try { val = json::parse(req.body()); }
- catch (...) { return make_error(http::status::bad_request, "invalid input", req); }
-
- if (!val.is_object())
- return make_error(http::status::bad_request, "invalid input", req);
-
- const auto& obj = val.as_object();
- auto it = obj.find("password");
- if (it == obj.end() || !it->value().is_string())
- return make_error(http::status::bad_request, "invalid input", req);
-
- std::string password(it->value().as_string());
- std::string hash = sha256_hex(password);
-
- json::object result;
- result["hash"] = hash;
- return make_json_obj(http::status::ok, result, req);
-
- } catch (const std::exception& e) {
- _log("ERROR", "fsys", std::string("hash 错误: ") + e.what());
- return make_error(http::status::internal_server_error, "内部错误", req);
- }
-}
-
-// ============================================================================
-// POST /api/check_user — 检查用户是否存在
-// ============================================================================
-
-http::response FsysServer::_handle_check_user(
- const http::request& req)
-{
- try {
- std::string ip = client_ip(req);
- if (!_check_rate_limit("check_user:" + ip, kCheckUserRateLimit))
- return make_error(http::status::too_many_requests, "rate limit exceeded", req);
-
- json::value val;
- try { val = json::parse(req.body()); }
- catch (...) {
- json::object r; r["success"] = false; r["error"] = "missing email";
- return make_json_obj(http::status::bad_request, r, req);
- }
-
- if (!val.is_object()) {
- json::object r; r["success"] = false; r["error"] = "missing email";
- return make_json_obj(http::status::bad_request, r, req);
- }
-
- const auto& obj = val.as_object();
- auto it = obj.find("email");
- if (it == obj.end() || !it->value().is_string()) {
- json::object r; r["success"] = false; r["error"] = "missing email";
- return make_json_obj(http::status::bad_request, r, req);
- }
-
- std::string email(it->value().as_string());
- // trim
- auto b = email.find_first_not_of(" \t");
- auto e = email.find_last_not_of(" \t");
- if (b == std::string::npos) {
- json::object r; r["success"] = false; r["error"] = "missing email";
- return make_json_obj(http::status::bad_request, r, req);
- }
- email = email.substr(b, e - b + 1);
-
- json::object payload;
- payload["action"] = "select";
- payload["table"] = "Users";
- payload["where"] = json::object{{"email", email}};
- payload["columns"] = json::array{"email"};
-
- DbResult r = _call_db(payload);
- if (!r.success) {
- _log("WARNING", "fsys", "check_user 查询失败: " + r.error);
- json::object result; result["success"] = false; result["error"] = "query failed";
- return make_json_obj(http::status::internal_server_error, result, req);
- }
-
- bool found = r.data.is_array() && !r.data.as_array().empty();
- json::object result;
- result["success"] = true;
- result["found"] = found;
- return make_json_obj(http::status::ok, result, req);
-
- } catch (const FsysExp& exp) {
- json::object r; r["success"] = false; r["error"] = exp.what();
- return make_json_obj(static_cast(exp.status()), r, req);
- } catch (const std::exception& e) {
- _log("ERROR", "fsys", std::string("check_user 错误: ") + e.what());
- json::object r; r["success"] = false; r["error"] = "internal error";
- return make_json_obj(http::status::internal_server_error, r, req);
- }
-}
-
// ============================================================================
// GET /api/favicon?url= — favicon SSRF 防护代理
// ============================================================================