From 15ba75e4cf1d1b8ceaa9d8ce33dcfdd7dbe80741 Mon Sep 17 00:00:00 2001 From: sorcerykid Date: Sat, 23 May 2020 06:24:06 -0500 Subject: [PATCH] Add on_authplayer callback and 'last_login' to on_joinplayer (#9574) Replace on_auth_fail callback with more versatile on_authplayer Better clarify account login process in Lua API documentation Change initial timestamp for newly registered accounts to -1 --- builtin/game/auth.lua | 3 +- builtin/game/chat.lua | 2 +- builtin/game/deprecated.lua | 16 +++++++++++ builtin/game/register.lua | 2 +- doc/lua_api.txt | 20 +++++++------ src/network/serverpackethandler.cpp | 44 +++++++++++++++-------------- src/script/cpp_api/s_player.cpp | 19 +++++++++---- src/script/cpp_api/s_player.h | 4 +-- src/script/cpp_api/s_server.cpp | 14 ++++++--- src/script/cpp_api/s_server.h | 3 +- 10 files changed, 81 insertions(+), 46 deletions(-) diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua index 7aedfc82e..fc061666c 100644 --- a/builtin/game/auth.lua +++ b/builtin/game/auth.lua @@ -41,7 +41,6 @@ core.builtin_auth_handler = { return { password = auth_entry.password, privileges = privileges, - -- Is set to nil if unknown last_login = auth_entry.last_login, } end, @@ -53,7 +52,7 @@ core.builtin_auth_handler = { name = name, password = password, privileges = core.string_to_privs(core.settings:get("default_privs")), - last_login = os.time(), + last_login = -1, -- Defer login time calculation until record_login (called by on_joinplayer) }) end, delete_auth = function(name) diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index b9f84e522..aae811794 100644 --- a/builtin/game/chat.lua +++ b/builtin/game/chat.lua @@ -1068,7 +1068,7 @@ core.register_chatcommand("last-login", { param = name end local pauth = core.get_auth_handler().get_auth(param) - if pauth and pauth.last_login then + if pauth and pauth.last_login and pauth.last_login ~= -1 then -- Time in UTC, ISO 8601 format return true, "Last login time was " .. os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login) diff --git a/builtin/game/deprecated.lua b/builtin/game/deprecated.lua index 73e105eb8..20f0482eb 100644 --- a/builtin/game/deprecated.lua +++ b/builtin/game/deprecated.lua @@ -70,3 +70,19 @@ core.setting_get = setting_proxy("get") core.setting_setbool = setting_proxy("set_bool") core.setting_getbool = setting_proxy("get_bool") core.setting_save = setting_proxy("write") + +-- +-- core.register_on_auth_fail +-- + +function core.register_on_auth_fail(func) + core.log("deprecated", "core.register_on_auth_fail " .. + "is obsolete and should be replaced by " .. + "core.register_on_authplayer instead.") + + core.register_on_authplayer(function (player_name, ip, is_success) + if not is_success then + func(player_name, ip) + end + end) +end diff --git a/builtin/game/register.lua b/builtin/game/register.lua index eb6c2897c..1034d4f2b 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -607,9 +607,9 @@ core.registered_on_item_eats, core.register_on_item_eat = make_registration() core.registered_on_punchplayers, core.register_on_punchplayer = make_registration() core.registered_on_priv_grant, core.register_on_priv_grant = make_registration() core.registered_on_priv_revoke, core.register_on_priv_revoke = make_registration() +core.registered_on_authplayers, core.register_on_authplayer = make_registration() core.registered_can_bypass_userlimit, core.register_can_bypass_userlimit = make_registration() core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration() -core.registered_on_auth_fail, core.register_on_auth_fail = make_registration() core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration() core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration() diff --git a/doc/lua_api.txt b/doc/lua_api.txt index bd0cb8acb..26061eccb 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4374,7 +4374,7 @@ Call these functions only at load time! * Called after generating a piece of world. Modifying nodes inside the area is a bit faster than usually. * `minetest.register_on_newplayer(function(ObjectRef))` - * Called after a new player has been created + * Called when a new player enters the world for the first time * `minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage))` * Called when a player is punched * Note: This callback is invoked even if the punched player is dead. @@ -4415,19 +4415,23 @@ Call these functions only at load time! * Called _before_ repositioning of player occurs * return true in func to disable regular player placement * `minetest.register_on_prejoinplayer(function(name, ip))` - * Called before a player joins the game - * If it returns a string, the player is disconnected with that string as + * Called when a client connects to the server, prior to authentication + * If it returns a string, the client is disconnected with that string as reason. -* `minetest.register_on_joinplayer(function(ObjectRef))` +* `minetest.register_on_joinplayer(function(ObjectRef, last_login))` * Called when a player joins the game + * `last_login`: The timestamp of the previous login, or nil if player is new * `minetest.register_on_leaveplayer(function(ObjectRef, timed_out))` * Called when a player leaves the game * `timed_out`: True for timeout, false for other reasons. +* `minetest.register_on_authplayer(function(name, ip, is_success))` + * Called when a client attempts to log into an account. + * `name`: The name of the account being authenticated. + * `ip`: The IP address of the client + * `is_success`: Whether the client was successfully authenticated + * For newly registered accounts, `is_success` will always be true * `minetest.register_on_auth_fail(function(name, ip))` - * Called when a client attempts to log into an account but supplies the - wrong password. - * `ip`: The IP address of the client. - * `name`: The account the client attempted to log into. + * Deprecated: use `minetest.register_on_authplayer(name, ip, is_success)` instead. * `minetest.register_on_cheat(function(ObjectRef, cheat))` * Called when a player cheats * `cheat`: `{type=}`, where `` is one of: diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 2fa9d4196..fed3b6f85 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -409,9 +409,12 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt) // (u16) 1 + std::string represents a pseudo vector serialization representation notice_pkt << (u8) PLAYER_LIST_ADD << (u16) 1 << std::string(playersao->getPlayer()->getName()); m_clients.sendToAll(¬ice_pkt); - m_clients.event(peer_id, CSE_SetClientReady); - m_script->on_joinplayer(playersao); + + s64 last_login; + m_script->getAuth(playersao->getPlayer()->getName(), nullptr, nullptr, &last_login); + m_script->on_joinplayer(playersao, last_login); + // Send shutdown timer if shutdown has been scheduled if (m_shutdown_state.isTimerRunning()) { SendChatMessage(peer_id, m_shutdown_state.getShutdownTimerMessage()); @@ -1512,6 +1515,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) initial_ver_key = encode_srp_verifier(verification_key, salt); m_script->createAuth(playername, initial_ver_key); + m_script->on_authplayer(playername, addr_s, true); acceptAuth(peer_id, false); } else { @@ -1648,24 +1652,25 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); + std::string addr_s = getPeerAddress(pkt->getPeerId()).serializeString(); + std::string playername = client->getName(); bool wantSudo = (cstate == CS_Active); verbosestream << "Server: Received TOCLIENT_SRP_BYTES_M." << std::endl; if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { - actionstream << "Server: got SRP _M packet in wrong state " << cstate << - " from " << getPeerAddress(peer_id).serializeString() << - ". Ignoring." << std::endl; + actionstream << "Server: got SRP _M packet in wrong state " + << cstate << " from " << addr_s + << ". Ignoring." << std::endl; return; } if (client->chosen_mech != AUTH_MECHANISM_SRP && client->chosen_mech != AUTH_MECHANISM_LEGACY_PASSWORD) { - actionstream << "Server: got SRP _M packet, while auth is going on " - "with mech " << client->chosen_mech << " from " << - getPeerAddress(peer_id).serializeString() << - " (wantSudo=" << wantSudo << "). Denying." << std::endl; + actionstream << "Server: got SRP _M packet, while auth" + << "is going on with mech " << client->chosen_mech << " from " + << addr_s << " (wantSudo=" << wantSudo << "). Denying." << std::endl; if (wantSudo) { DenySudoAccess(peer_id); return; @@ -1680,9 +1685,8 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) if (srp_verifier_get_session_key_length((SRPVerifier *) client->auth_data) != bytes_M.size()) { - actionstream << "Server: User " << client->getName() << " at " << - getPeerAddress(peer_id).serializeString() << - " sent bytes_M with invalid length " << bytes_M.size() << std::endl; + actionstream << "Server: User " << playername << " at " << addr_s + << " sent bytes_M with invalid length " << bytes_M.size() << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA); return; } @@ -1694,24 +1698,21 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) if (!bytes_HAMK) { if (wantSudo) { - actionstream << "Server: User " << client->getName() << " at " << - getPeerAddress(peer_id).serializeString() << - " tried to change their password, but supplied wrong (SRP) " - "password for authentication." << std::endl; + actionstream << "Server: User " << playername << " at " << addr_s + << " tried to change their password, but supplied wrong" + << " (SRP) password for authentication." << std::endl; DenySudoAccess(peer_id); return; } - std::string ip = getPeerAddress(peer_id).serializeString(); - actionstream << "Server: User " << client->getName() << " at " << ip << - " supplied wrong password (auth mechanism: SRP)." << std::endl; - m_script->on_auth_failure(client->getName(), ip); + actionstream << "Server: User " << playername << " at " << addr_s + << " supplied wrong password (auth mechanism: SRP)." << std::endl; + m_script->on_authplayer(playername, addr_s, false); DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_PASSWORD); return; } if (client->create_player_on_auth_success) { - std::string playername = client->getName(); m_script->createAuth(playername, client->enc_pwd); std::string checkpwd; // not used, but needed for passing something @@ -1725,6 +1726,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) client->create_player_on_auth_success = false; } + m_script->on_authplayer(playername, addr_s, true); acceptAuth(peer_id, wantSudo); } diff --git a/src/script/cpp_api/s_player.cpp b/src/script/cpp_api/s_player.cpp index df67ea00c..712120c61 100644 --- a/src/script/cpp_api/s_player.cpp +++ b/src/script/cpp_api/s_player.cpp @@ -147,7 +147,7 @@ bool ScriptApiPlayer::can_bypass_userlimit(const std::string &name, const std::s return lua_toboolean(L, -1); } -void ScriptApiPlayer::on_joinplayer(ServerActiveObject *player) +void ScriptApiPlayer::on_joinplayer(ServerActiveObject *player, s64 last_login) { SCRIPTAPI_PRECHECKHEADER @@ -156,7 +156,11 @@ void ScriptApiPlayer::on_joinplayer(ServerActiveObject *player) lua_getfield(L, -1, "registered_on_joinplayers"); // Call callbacks objectrefGetOrCreate(L, player); - runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); + if (last_login != -1) + lua_pushinteger(L, last_login); + else + lua_pushnil(L); + runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiPlayer::on_leaveplayer(ServerActiveObject *player, @@ -216,16 +220,19 @@ void ScriptApiPlayer::on_playerReceiveFields(ServerActiveObject *player, runCallbacks(3, RUN_CALLBACKS_MODE_OR_SC); } -void ScriptApiPlayer::on_auth_failure(const std::string &name, const std::string &ip) +void ScriptApiPlayer::on_authplayer(const std::string &name, const std::string &ip, bool is_success) { SCRIPTAPI_PRECHECKHEADER - // Get core.registered_on_auth_failure + // Get core.registered_on_authplayers lua_getglobal(L, "core"); - lua_getfield(L, -1, "registered_on_auth_fail"); + lua_getfield(L, -1, "registered_on_authplayers"); + + // Call callbacks lua_pushstring(L, name.c_str()); lua_pushstring(L, ip.c_str()); - runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); + lua_pushboolean(L, is_success); + runCallbacks(3, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiPlayer::pushMoveArguments( diff --git a/src/script/cpp_api/s_player.h b/src/script/cpp_api/s_player.h index 7ca3d8f30..a337f975b 100644 --- a/src/script/cpp_api/s_player.h +++ b/src/script/cpp_api/s_player.h @@ -41,7 +41,7 @@ public: bool on_prejoinplayer(const std::string &name, const std::string &ip, std::string *reason); bool can_bypass_userlimit(const std::string &name, const std::string &ip); - void on_joinplayer(ServerActiveObject *player); + void on_joinplayer(ServerActiveObject *player, s64 last_login); void on_leaveplayer(ServerActiveObject *player, bool timeout); void on_cheat(ServerActiveObject *player, const std::string &cheat_type); bool on_punchplayer(ServerActiveObject *player, ServerActiveObject *hitter, @@ -51,7 +51,7 @@ public: const PlayerHPChangeReason &reason); void on_playerReceiveFields(ServerActiveObject *player, const std::string &formname, const StringMap &fields); - void on_auth_failure(const std::string &name, const std::string &ip); + void on_authplayer(const std::string &name, const std::string &ip, bool is_success); // Player inventory callbacks // Return number of accepted items to be moved diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp index 1ce2f9d45..96cb28b28 100644 --- a/src/script/cpp_api/s_server.cpp +++ b/src/script/cpp_api/s_server.cpp @@ -23,7 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc., bool ScriptApiServer::getAuth(const std::string &playername, std::string *dst_password, - std::set *dst_privs) + std::set *dst_privs, + s64 *dst_last_login) { SCRIPTAPI_PRECHECKHEADER @@ -43,8 +44,7 @@ bool ScriptApiServer::getAuth(const std::string &playername, luaL_checktype(L, -1, LUA_TTABLE); std::string password; - bool found = getstringfield(L, -1, "password", password); - if (!found) + if (!getstringfield(L, -1, "password", password)) throw LuaError("Authentication handler didn't return password"); if (dst_password) *dst_password = password; @@ -54,7 +54,13 @@ bool ScriptApiServer::getAuth(const std::string &playername, throw LuaError("Authentication handler didn't return privilege table"); if (dst_privs) readPrivileges(-1, *dst_privs); - lua_pop(L, 1); + lua_pop(L, 1); // Remove key from privs table + + s64 last_login; + if(!getintfield(L, -1, "last_login", last_login)) + throw LuaError("Authentication handler didn't return last_login"); + if (dst_last_login) + *dst_last_login = (s64)last_login; return true; } diff --git a/src/script/cpp_api/s_server.h b/src/script/cpp_api/s_server.h index a4cede84d..d8639cba7 100644 --- a/src/script/cpp_api/s_server.h +++ b/src/script/cpp_api/s_server.h @@ -43,7 +43,8 @@ public: /* auth */ bool getAuth(const std::string &playername, std::string *dst_password, - std::set *dst_privs); + std::set *dst_privs, + s64 *dst_last_login = nullptr); void createAuth(const std::string &playername, const std::string &password); bool setPassword(const std::string &playername,