diff --git a/gateway/inc/DbC.hpp b/gateway/inc/DbC.hpp new file mode 100644 index 0000000..9261625 --- /dev/null +++ b/gateway/inc/DbC.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include +namespace qing { + namespace json = boost::json; + class DbC { + public: + static json::value call_http(json::value& params) { + + auto client = pmc::net::HttpClient(); + client.set_header("Content-Type: application/json"); + std::string payload = json::serialize(params); + std::string res = ""; + client.post_stream_sync(_DBURL, payload, [&res](const auto& ret) { + res += ret; + }); + return json::parse(res); + } + private: + static constexpr const char* _DBURL = "http://127.0.0.1:8005/execute"; + }; +} diff --git a/gateway/src/main.cpp b/gateway/src/main.cpp index ac2d552..ece8bfd 100644 --- a/gateway/src/main.cpp +++ b/gateway/src/main.cpp @@ -19,6 +19,7 @@ #include "ExpBase.hpp" #include "LocalTime.hpp" #include "Conf.hpp" +#include "DbC.hpp" using namespace pmc::net; using namespace qing; @@ -398,7 +399,7 @@ int main(int argc, char **argv) { { try { - ConfIni cf(vm["input"].as()); + ConfIni cf(vm["input"].as()); _ADDR = cf.get("Server.address", ""); _PORT = cf.get ("Server.port", -1); _TLS = cf.get ("Server.tls", false); @@ -481,26 +482,22 @@ void db_init() { } - +/* 注册http路由 */ void registerRoutes(HttpServer &server) { server.post("/captcha", [](const auto& req, const auto& params) { return handleCaptcha(req, params); }); - server.post("/login", []( - const http::request& req, - const std::unordered_map& params - ) -> http::response { - return wrapperHttp(req, params, []( - const auto& req, const auto& params - ) { - return handleLogin(req, params); - }); + /* const http::request& req, const std::unordered_map& params */ + server.post("/login", [](const auto& req, const auto& params) -> http::response { + return wrapperHttp(req, params, + [](const auto& req, const auto& params) {return handleLogin(req, params);}); }); - server.post("/register", [](const auto& req, const auto& params) { - return handleRegister(req, params); + server.post("/register", [](const auto& req, const auto& params) -> http::response { + return wrapperHttp(req, params, + [](const auto& req, const auto& params) { return handleRegister(req, params); }); }); server.get("*", [](const auto& req, const auto& params) { @@ -539,16 +536,36 @@ void registerRoutes(HttpServer &server) { }); } +/* Http视图包装器 + * + * 该函数收集视图函数抛出的异常 将信息发送给浏览器。这样视图函数有问题就可以直接抛出异常。 */ http::response wrapperHttp( const http::request& req, const std::unordered_map& params, HttpRequestHandler _main) -try { +try { return _main(req, params); } - return _main(req, params); + +/* 主动抛出的异常 */ +catch (const HttpException& e) { + http::response res; + res.version(req.version()); + // 详细错误信息(e.what() 含源码路径/行号/C++ 模板类型)暂时以注释方式保留(同 handleLogin) + std::cerr << "[handleRegister HttpException] " << e.what() << std::endl; + json::value jv_r = { + {"success", false}, + {"message", e.what()} // 泄露源码信息,已禁用 + /* {"message", "Registration failed"} */ + }; + res.set(http::field::content_type, "application/json"); + res.result(e.status()); + res.body() = json::serialize(jv_r); + res.prepare_payload(); + return res; } +/* 预料之外的异常 */ catch (const std::exception& e) { http::response res; @@ -572,6 +589,7 @@ catch (const std::exception& e) { } // CAPTCHA endpoint: generates a random text, stores it, returns a simple token +// by shuiyu http::response handleCaptcha( const http::request& req, const std::unordered_map& params) @@ -608,6 +626,9 @@ http::response handleCaptcha( return res; } +/* 处理登陆 + * + * /login */ http::response handleLogin( const http::request& req, const std::unordered_map& params) @@ -618,11 +639,10 @@ http::response handleLogin( // IP rate limit check std::string client_ip = ""; auto it_host = req.base().find("X-Forwarded-For"); - if (it_host != req.base().end()) { + if (it_host != req.base().end()) client_ip = std::string(it_host->value()); - } else { + else client_ip = "direct"; - } if (!_LOGIN_RATE.check_and_record(client_ip, 5, 300)) { json::value jv_r = { @@ -636,118 +656,125 @@ http::response handleLogin( return res; } - try { - json::value jv; - try { - jv = json::parse(req.body()); - } - catch (const boost::wrapexcept& e) { - THROW_EX_CUSTOM(HttpException, 2001, "Invalid_json_Argument", - 400, "/login"); - } + json::value jv; + try { jv = json::parse(req.body()); } + catch (const boost::wrapexcept& e) { + THROW_EX_CUSTOM(HttpException, 2001, "Invalid_json_Argument", + 400, "/login"); + } - if (!jv.is_object()) { - THROW_EX_CUSTOM(HttpException, 2002, "Invalid_json_object_Argument", - 400, "/login"); - } - auto& obj = jv.as_object(); - auto username = std::string(obj.at("username").as_string()); - auto password = std::string(obj.at("password").as_string()); + if (!jv.is_object()) THROW_EX_CUSTOM(HttpException, 2002, "Invalid_json_object_Argument", 400, "/login"); + auto& obj = jv.as_object(); + auto username = std::string(obj.at("username").as_string()); + auto password = std::string(obj.at("password").as_string()); - // CAPTCHA verification - auto captcha_it = obj.find("captcha_token"); - auto captcha_ans_it = obj.find("captcha_answer"); - if (captcha_it != obj.end() && captcha_ans_it != obj.end()) { - std::string ct = std::string(captcha_it->value().as_string()); - std::string ca = std::string(captcha_ans_it->value().as_string()); - if (!verify_captcha(ct, ca)) { - THROW_EX_CUSTOM(HttpException, 2201, "Invalid CAPTCHA", - 400, "/login"); - } - } + // CAPTCHA verification + auto captcha_it = obj.find("captcha_token"); + auto captcha_ans_it = obj.find("captcha_answer"); + if (captcha_it != obj.end() && captcha_ans_it != obj.end()) { + std::string ct = std::string(captcha_it->value().as_string()); + std::string ca = std::string(captcha_ans_it->value().as_string()); + if (!verify_captcha(ct, ca)) THROW_EX_CUSTOM(HttpException, 2201, "Invalid CAPTCHA", 400, "/login"); + } + std::string stored_hash = ""; + { // Fetch stored hash - support both PBKDF2 and legacy SHA256 - std::string sql = "SELECT password_hash FROM Users WHERE email=?;"; - auto stmt = _SQLDB->stmt(sql); - stmt.bind_text(1, username); - std::string stored_hash = ""; - if (stmt.step()) { - stored_hash = std::string(reinterpret_cast(stmt.read_text(0))); - } + //std::string sql = "SELECT password_hash FROM Users WHERE email=?;"; + //auto stmt = _SQLDB->stmt(sql); + //stmt.bind_text(1, username); + //if (stmt.step()) { + // stored_hash = std::string(reinterpret_cast(stmt.read_text(0))); + //} - bool auth_ok = false; - if (!stored_hash.empty()) { - if (stored_hash.substr(0, 7) == "pbkdf2:") { - // New PBKDF2 hash - auth_ok = PBKDF2Hasher::verify_password(password, stored_hash); - } else { - // Legacy SHA256 hash - verify then auto-migrate - auto legacy_hash = SHA256::sha256(password); - if (legacy_hash == stored_hash) { - auth_ok = true; - // Auto-migrate to PBKDF2 - auto new_hash = PBKDF2Hasher::hash_password(password); - std::string sql_migrate = "UPDATE Users SET password_hash=? WHERE email=?;"; - auto stmt_mig = _SQLDB->stmt(sql_migrate); - stmt_mig.bind_text(1, new_hash); - stmt_mig.bind_text(2, username); - stmt_mig.step(); - std::cout << "Migrated password hash for " << username << " to PBKDF2" << std::endl; - } - } - } - - if (!auth_ok) { - THROW_EX_CUSTOM(HttpException, 2101, "Invalid Username or Password", - 401, "/login"); - } - - // Update last_login - std::string sql_update = "UPDATE Users SET last_login=datetime('now','localtime') WHERE email=?;"; - auto stmt_upd = _SQLDB->stmt(sql_update); - stmt_upd.bind_text(1, username); - stmt_upd.step(); - - auto _PRIV = MyRSA::Private_Key::from_pem(_KEYGEN->get_private_key_pem()); - - json::value jv_t = { - {"user", username}, - {"created_at", LocalTime::getCurrent()}, - {"keep_time", _TOKEN_KEEP_TIME} + // 新版调用db模块 + json::value jv_select = { + {"action", "select"}, + {"table", "Users"}, + {"where", json::object{ + {"username", username}, + }} }; - std::string data = json::serialize(jv_t); - std::string token = Base64::base64_encode(data); - std::string sign = _PRIV.Sign(data); - - json::value jv_rr = { - {"success", true}, - {"data", json::object{{"token", token + "_" + sign}}} - }; - res.set("Connection", "close"); - res.result(http::status::ok); - res.body() = json::serialize(jv_rr); + auto jv_db_r = DbC::call_http(jv_select); + auto obj_r = jv_db_r.as_object(); + auto success = obj_r["success"].as_bool(); + if (!success) THROW_EX_CUSTOM(HttpException, 2102, "Login (Update Step) Failed", 500, "/login"); + auto obj_rd = obj_r.at("data").as_array(); + auto obj_ru = obj_rd.at(0).as_object(); + stored_hash = obj_ru.at("password_hash").as_string(); } - catch (const HttpException& e) { - // 详细错误信息(e.what() 含源码路径/行号/C++ 模板类型)暂时以注释方式保留 - // 原因:黑盒检测发现透传到客户端会泄露内部实现,便于攻击者定位代码(P0-3) - // 排障:完整信息仍输出到服务端 stderr - std::cerr << "[handleLogin HttpException] " << e.what() << std::endl; - json::value jv_r = { - {"success", false}, - /* {"message", e.what()}, */ // 泄露源码信息,已禁用 - {"message", "Invalid Username or Password"} - }; - res.set(http::field::content_type, "application/json"); - res.result(e.status()); - res.body() = json::serialize(jv_r); + + bool auth_ok = false; + if (!stored_hash.empty()) { + if (stored_hash.substr(0, 7) == "pbkdf2:") + // New PBKDF2 hash + auth_ok = PBKDF2Hasher::verify_password(password, stored_hash); + else { + // Legacy SHA256 hash - verify then auto-migrate + //auto legacy_hash = SHA256::sha256(password); + //if (legacy_hash == stored_hash) { + // auth_ok = true; + // Auto-migrate to PBKDF2 + // auto new_hash = PBKDF2Hasher::hash_password(password); + // std::string sql_migrate = "UPDATE Users SET password_hash=? WHERE email=?;"; + // auto stmt_mig = _SQLDB->stmt(sql_migrate); + // stmt_mig.bind_text(1, new_hash); + // stmt_mig.bind_text(2, username); + // stmt_mig.step(); + // std::cout << "Migrated password hash for " << username << " to PBKDF2" << std::endl; + } } + if (!auth_ok) THROW_EX_CUSTOM(HttpException, 2101, "Invalid Username or Password", 401, "/login"); + + // Update last_login + //std::string sql_update = "UPDATE Users SET last_login=datetime('now','localtime') WHERE email=?;"; + //auto stmt_upd = _SQLDB->stmt(sql_update); + //stmt_upd.bind_text(1, username); + //stmt_upd.step(); + // + // 新版调用db模块 无法通过DB上传时间戳目前 + //json::value jv_upd = { + // {"action", "update"}, + // {"table", "Users"}, + // {"values", json::object{ + // {"last_login", LocalTime::getCurrent()}, + // }}, + // {"where", json::object{ + // {"username", username}, + // }} + //}; + //auto jv_db_r = DbC::call_http(jv_upd); + //auto obj_r = jv_db_r.as_object(); + //auto success = obj_r["success"].as_bool(); + //if (!success) THROW_EX_CUSTOM(HttpException, 2102, json::serialize(jv_db_r)+"Login (Update Step) Failed", 500, "/login"); + + + auto _PRIV = MyRSA::Private_Key::from_pem(_KEYGEN->get_private_key_pem()); + + json::value jv_t = { + {"user", username}, + {"created_at", LocalTime::getCurrent()}, + {"keep_time", _TOKEN_KEEP_TIME} + }; + std::string data = json::serialize(jv_t); + std::string token = Base64::base64_encode(data); + std::string sign = _PRIV.Sign(data); + + json::value jv_rr = { + {"success", true}, + {"data", json::object{{"token", token + "_" + sign}}} + }; + res.set("Connection", "close"); + res.result(http::status::ok); + res.body() = json::serialize(jv_rr); res.prepare_payload(); return res; } +/* 处理注册 /register */ http::response handleRegister( const http::request& req, const std::unordered_map& params) @@ -758,114 +785,64 @@ http::response handleRegister( // IP rate limit check std::string client_ip = "direct"; auto it_host = req.base().find("X-Forwarded-For"); - if (it_host != req.base().end()) { + if (it_host != req.base().end()) client_ip = std::string(it_host->value()); - } - if (!_REGISTER_RATE.check_and_record(client_ip, 3, 600)) { - json::value jv_r = { - {"success", false}, - {"message", "Too many registration attempts. Please try again later."} - }; - res.set(http::field::content_type, "application/json"); - res.result(http::status::too_many_requests); - res.body() = json::serialize(jv_r); - res.prepare_payload(); - return res; + if (!_REGISTER_RATE.check_and_record(client_ip, 3, 600)) + THROW_EX_CUSTOM(HttpException, 3001, "Too many registration attempts. Please try again later.", + 403, "/register"); + + auto jv = json::parse(req.body()); + if (!jv.is_object()) + THROW_EX_CUSTOM(HttpException, 3002, "Invalid Argument", 401, "/register"); + + auto& obj = jv.as_object(); + auto username = std::string(obj.at("username").as_string()); + auto password = std::string(obj.at("password").as_string()); + + // Strong password check + auto pw_result = PasswordValidator::validate(password); + if (!pw_result.valid) + THROW_EX_CUSTOM(HttpException, 3003, pw_result.message, 401, "/register"); + + // Email format check + std::regex email_regex(R"(^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$)"); + if (!std::regex_match(username, email_regex)) + THROW_EX_CUSTOM(HttpException, 3004, "Invalid email format", 400, "/register"); + + // CAPTCHA verification + auto captcha_it = obj.find("captcha_token"); + auto captcha_ans_it = obj.find("captcha_answer"); + if (captcha_it != obj.end() && captcha_ans_it != obj.end()) { + std::string ct = std::string(captcha_it->value().as_string()); + std::string ca = std::string(captcha_ans_it->value().as_string()); + if (!verify_captcha(ct, ca)) + THROW_EX_CUSTOM(HttpException, 2201, "Invalid CAPTCHA", 400, "/register"); } - try { - - auto jv = json::parse(req.body()); - if (!jv.is_object()) { - throw std::runtime_error("400"); - } - auto& obj = jv.as_object(); - auto username = std::string(obj.at("username").as_string()); - auto password = std::string(obj.at("password").as_string()); - - // Strong password check - auto pw_result = PasswordValidator::validate(password); - if (!pw_result.valid) { - json::value jv_r = { - {"success", false}, - {"message", pw_result.message} - }; - res.set(http::field::content_type, "application/json"); - res.result(http::status::bad_request); - res.body() = json::serialize(jv_r); - res.prepare_payload(); - return res; - } - - // Email format check - std::regex email_regex(R"(^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$)"); - if (!std::regex_match(username, email_regex)) { - json::value jv_r = { - {"success", false}, - {"message", "Invalid email format"} - }; - res.set(http::field::content_type, "application/json"); - res.result(http::status::bad_request); - res.body() = json::serialize(jv_r); - res.prepare_payload(); - return res; - } - - // CAPTCHA verification - auto captcha_it = obj.find("captcha_token"); - auto captcha_ans_it = obj.find("captcha_answer"); - if (captcha_it != obj.end() && captcha_ans_it != obj.end()) { - std::string ct = std::string(captcha_it->value().as_string()); - std::string ca = std::string(captcha_ans_it->value().as_string()); - if (!verify_captcha(ct, ca)) { - THROW_EX_CUSTOM(HttpException, 2201, "Invalid CAPTCHA", - 400, "/register"); - } - } - - auto password_hash = PBKDF2Hasher::hash_password(password); - - std::string sql = "INSERT INTO Users (email, password_hash, last_login) VALUES (?, ?, datetime('now', 'localtime'));"; - auto stmt = _SQLDB->stmt(sql); - stmt.bind_text(1, username); - stmt.bind_text(2, password_hash); - stmt.step(); - - json::value jv_r = {{"success", true}}; - res.result(http::status::ok); - res.body() = json::serialize(jv_r); - } - - catch (const HttpException& e) { - // 详细错误信息(e.what() 含源码路径/行号/C++ 模板类型)暂时以注释方式保留(同 handleLogin) - std::cerr << "[handleRegister HttpException] " << e.what() << std::endl; - json::value jv_r = { - {"success", false}, - /* {"message", e.what()}, */ // 泄露源码信息,已禁用 - {"message", "Registration failed"} - }; - res.set(http::field::content_type, "application/json"); - res.result(e.status()); - res.body() = json::serialize(jv_r); - } - - catch (const std::exception& e) { - const auto& ti = typeid(e); - // 详细错误信息(ti.name() 含 mangled 类型名、e.what() 含内部细节)暂时以注释方式保留 - // 原因:mangled 类型名会确认技术栈与 STL 容器,e.what() 可能泄露内部路径(P0-3) - // 排障:完整信息仍输出到服务端 stderr - std::cerr << "[handleRegister std::exception] <" << ti.name() << "> " << e.what() << std::endl; - res.set(http::field::content_type, "application/json"); - res.result(http::status::internal_server_error); - json::value jv_r = { - {"success", false}, - /* {"err_name", ti.name()}, // 泄露 C++ 类型信息,已禁用 */ - /* {"err_message", e.what()} // 泄露内部细节,已禁用 */ - {"message", "Internal Server Error"} - }; - res.body() = json::serialize(jv_r); - } + auto password_hash = PBKDF2Hasher::hash_password(password); + //std::string sql = "INSERT INTO Users (email, password_hash, last_login) VALUES (?, ?, datetime('now', 'localtime'));"; + //auto stmt = _SQLDB->stmt(sql); + //stmt.bind_text(1, username); + //stmt.bind_text(2, password_hash); + //stmt.step(); + // + // 新版调用db模块 + json::value jv_insert = { + {"action", "insert"}, + {"table", "Users"}, + {"values", json::object{ + {"username", username}, + {"password_hash", password_hash} + }} + }; + auto jv_result = DbC::call_http(jv_insert); + auto obj_r = jv_result.as_object(); + auto success = obj_r["success"].as_bool(); + if (!success) THROW_EX_CUSTOM(HttpException, 2202, "Registe Failed", 400, "/register"); + auto jv_r = json::value{{"success", success}}; + res.result(http::status::ok); + res.body() = json::serialize(jv_r); res.prepare_payload(); return res; }