From 792752997c5ae2aaa4f54d0a2e2af2a96d7d1e9f Mon Sep 17 00:00:00 2001 From: Muhammad Rifqi Priyo Susanto Date: Sat, 13 Jan 2018 18:07:16 +0700 Subject: [PATCH] Add confirmation on new player registration (#6849) * Attempt to add registration confirmation Using SRP auth mechanism, if server sent AUTH_MECHANISM_FIRST_SRP that means the player isn't exist. Also tell player about the server and chosen username. Local game has localhost as IP address of the server. Add RenderingEngine::draw_menu_scene() to draw GUI and clouds background. aborted -> connection_aborted * Rewrite information message text Client::promptConfirmRegister() -> Client::promptConfirmRegistration() --- src/client.cpp | 16 ++ src/client.h | 4 + src/client/renderingengine.cpp | 20 +++ src/client/renderingengine.h | 9 ++ src/game.cpp | 42 +++-- src/gui/CMakeLists.txt | 1 + src/gui/guiConfirmRegistration.cpp | 231 ++++++++++++++++++++++++++++ src/gui/guiConfirmRegistration.h | 61 ++++++++ src/network/clientpackethandler.cpp | 7 +- 9 files changed, 378 insertions(+), 13 deletions(-) create mode 100644 src/gui/guiConfirmRegistration.cpp create mode 100644 src/gui/guiConfirmRegistration.h diff --git a/src/client.cpp b/src/client.cpp index e6af5c324..90f1c4e01 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -318,6 +318,10 @@ void Client::step(float dtime) initial_step = false; } else if(m_state == LC_Created) { + if (m_is_registration_confirmation_state) { + // Waiting confirmation + return; + } float &counter = m_connection_reinit_timer; counter -= dtime; if(counter <= 0.0) { @@ -974,6 +978,18 @@ void Client::sendInit(const std::string &playerName) Send(&pkt); } +void Client::promptConfirmRegistration(AuthMechanism chosen_auth_mechanism) +{ + m_chosen_auth_mech = chosen_auth_mechanism; + m_is_registration_confirmation_state = true; +} + +void Client::confirmRegistration() +{ + m_is_registration_confirmation_state = false; + startAuth(m_chosen_auth_mech); +} + void Client::startAuth(AuthMechanism chosen_auth_mechanism) { m_chosen_auth_mech = chosen_auth_mechanism; diff --git a/src/client.h b/src/client.h index f5929e055..11f27e9bd 100644 --- a/src/client.h +++ b/src/client.h @@ -345,6 +345,9 @@ public: { return m_proto_ver; } bool connectedToServer(); + void confirmRegistration(); + bool m_is_registration_confirmation_state = false; + bool m_simple_singleplayer_mode; float mediaReceiveProgress(); @@ -448,6 +451,7 @@ private: static AuthMechanism choseAuthMech(const u32 mechs); void sendInit(const std::string &playerName); + void promptConfirmRegistration(AuthMechanism chosen_auth_mechanism); void startAuth(AuthMechanism chosen_auth_mechanism); void sendDeletedBlocks(std::vector &blocks); void sendGotBlocks(v3s16 block); diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index f9da178b9..3c48c28a8 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -410,6 +410,26 @@ void RenderingEngine::_draw_load_screen(const std::wstring &text, guitext->remove(); } +/* + Draws the menu scene including (optional) cloud background. +*/ +void RenderingEngine::_draw_menu_scene(gui::IGUIEnvironment *guienv, + float dtime, bool clouds) +{ + bool cloud_menu_background = clouds && g_settings->getBool("menu_clouds"); + if (cloud_menu_background) { + g_menuclouds->step(dtime * 3); + g_menuclouds->render(); + get_video_driver()->beginScene( + true, true, video::SColor(255, 140, 186, 250)); + g_menucloudsmgr->drawAll(); + } else + get_video_driver()->beginScene(true, true, video::SColor(255, 0, 0, 0)); + + guienv->drawAll(); + get_video_driver()->endScene(); +} + std::vector> RenderingEngine::getSupportedVideoModes() { IrrlichtDevice *nulldevice = createDevice(video::EDT_NULL); diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index ac6b6926c..8728363d2 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -110,6 +110,12 @@ public: text, guienv, tsrc, dtime, percent, clouds); } + inline static void draw_menu_scene( + gui::IGUIEnvironment *guienv, float dtime, bool clouds) + { + s_singleton->_draw_menu_scene(guienv, dtime, clouds); + } + inline static void draw_scene(video::SColor skycolor, bool show_hud, bool show_minimap, bool draw_wield_tool, bool draw_crosshair) { @@ -138,6 +144,9 @@ private: ITextureSource *tsrc, float dtime = 0, int percent = 0, bool clouds = true); + void _draw_menu_scene(gui::IGUIEnvironment *guienv, float dtime = 0, + bool clouds = true); + void _draw_scene(video::SColor skycolor, bool show_hud, bool show_minimap, bool draw_wield_tool, bool draw_crosshair); diff --git a/src/game.cpp b/src/game.cpp index 1ab360d3c..32ec6b73e 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -41,6 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "gettext.h" #include "gui/guiChatConsole.h" +#include "gui/guiConfirmRegistration.h" #include "gui/guiFormSpecMenu.h" #include "gui/guiKeyChangeMenu.h" #include "gui/guiPasswordChange.h" @@ -1302,6 +1303,7 @@ private: EventManager *eventmgr = nullptr; QuicktuneShortcutter *quicktune = nullptr; + bool registration_confirmation_shown = false; std::unique_ptr m_game_ui; GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop() @@ -1900,10 +1902,10 @@ bool Game::initGui() bool Game::connectToServer(const std::string &playername, const std::string &password, std::string *address, u16 port, - bool *connect_ok, bool *aborted) + bool *connect_ok, bool *connection_aborted) { *connect_ok = false; // Let's not be overly optimistic - *aborted = false; + *connection_aborted = false; bool local_server_mode = false; showOverlayMessage("Resolving address...", 0, 15); @@ -1946,6 +1948,8 @@ bool Game::connectToServer(const std::string &playername, if (!client) return false; + client->m_simple_singleplayer_mode = simple_singleplayer_mode; + infostream << "Connecting to server at "; connect_address.print(&infostream); infostream << std::endl; @@ -1985,6 +1989,9 @@ bool Game::connectToServer(const std::string &playername, } // Break conditions + if (*connection_aborted) + break; + if (client->accessDenied()) { *error_message = "Access denied. Reason: " + client->accessDeniedReason(); @@ -1994,21 +2001,32 @@ bool Game::connectToServer(const std::string &playername, } if (wasKeyDown(KeyType::ESC) || input->wasKeyDown(CancelKey)) { - *aborted = true; + *connection_aborted = true; infostream << "Connect aborted [Escape]" << std::endl; break; } - wait_time += dtime; - // Only time out if we aren't waiting for the server we started - if (!address->empty() && wait_time > 10) { - *error_message = "Connection timed out."; - errorstream << *error_message << std::endl; - break; - } + if (client->m_is_registration_confirmation_state) { + if (registration_confirmation_shown) { + // Keep drawing the GUI + RenderingEngine::draw_menu_scene(guienv, dtime, true); + } else { + registration_confirmation_shown = true; + (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1, + &g_menumgr, client, playername, password, *address, connection_aborted))->drop(); + } + } else { + wait_time += dtime; + // Only time out if we aren't waiting for the server we started + if (!address->empty() && wait_time > 10) { + *error_message = "Connection timed out."; + errorstream << *error_message << std::endl; + break; + } - // Update status - showOverlayMessage("Connecting to server...", dtime, 20); + // Update status + showOverlayMessage("Connecting to server...", dtime, 20); + } } } catch (con::PeerNotFoundException &e) { // TODO: Should something be done here? At least an info/error diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 067ba09a8..fa8ab0e6d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,5 +1,6 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiConfirmRegistration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiEditBoxWithScrollbar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiEngine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiFormSpecMenu.cpp diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp new file mode 100644 index 000000000..f45340f33 --- /dev/null +++ b/src/gui/guiConfirmRegistration.cpp @@ -0,0 +1,231 @@ +/* +Minetest +Copyright (C) 2018 srifqi, Muhammad Rifqi Priyo Susanto + + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "guiConfirmRegistration.h" +#include "client.h" +#include +#include +#include +#include +#include + +#include "gettext.h" + +// Continuing from guiPasswordChange.cpp +const int ID_confirmPassword = 262; +const int ID_confirm = 263; +const int ID_message = 264; +const int ID_cancel = 265; + +GUIConfirmRegistration::GUIConfirmRegistration(gui::IGUIEnvironment *env, + gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, Client *client, + const std::string &playername, const std::string &password, + const std::string &address, bool *aborted) : + GUIModalMenu(env, parent, id, menumgr), + m_client(client), m_playername(playername), m_password(password), + m_address(address), m_aborted(aborted) +{ +} + +GUIConfirmRegistration::~GUIConfirmRegistration() +{ + removeChildren(); +} + +void GUIConfirmRegistration::removeChildren() +{ + const core::list &children = getChildren(); + core::list children_copy; + for (gui::IGUIElement *i : children) + children_copy.push_back(i); + for (gui::IGUIElement *i : children_copy) + i->remove(); +} +void GUIConfirmRegistration::regenerateGui(v2u32 screensize) +{ + acceptInput(); + removeChildren(); + + /* + Calculate new sizes and positions + */ + core::rect rect(screensize.X / 2 - 600 / 2, screensize.Y / 2 - 300 / 2, + screensize.X / 2 + 600 / 2, screensize.Y / 2 + 300 / 2); + + DesiredRect = rect; + recalculateAbsolutePosition(false); + + v2s32 size = rect.getSize(); + v2s32 topleft_client(0, 0); + + const wchar_t *text; + + /* + Add stuff + */ + s32 ypos = 30; + { + std::string address = m_address; + if (address.empty()) + address = "localhost"; + core::rect rect(0, 0, 540, 90); + rect += topleft_client + v2s32(30, ypos); + static const std::string info_text_template = strgettext( + "You are about to join this server (%1$s) with the " + "name \"%2$s\" the first time. If you proceed, a " + "new account using your credentials will be created " + "on this server.\n" + "Please type your password once again to confirm " + "account creation or cancel to abort."); + char info_text_buf[1024]; + snprintf(info_text_buf, sizeof(info_text_buf), info_text_template.c_str(), + address.c_str(), m_playername.c_str()); + Environment->addStaticText(narrow_to_wide_c(info_text_buf), rect, false, + true, this, -1); + } + + ypos += 120; + { + core::rect rect(0, 0, 540, 30); + rect += topleft_client + v2s32(30, ypos); + gui::IGUIEditBox *e = Environment->addEditBox(m_pass_confirm.c_str(), + rect, true, this, ID_confirmPassword); + e->setPasswordBox(true); + } + + ypos += 90; + { + core::rect rect(0, 0, 230, 35); + rect = rect + v2s32(size.X / 2 - 220, ypos); + text = wgettext("Register and Join"); + Environment->addButton(rect, this, ID_confirm, text); + delete[] text; + } + { + core::rect rect(0, 0, 120, 35); + rect = rect + v2s32(size.X / 2 + 70, ypos); + text = wgettext("Cancel"); + Environment->addButton(rect, this, ID_cancel, text); + delete[] text; + } + { + core::rect rect(0, 0, 200, 20); + rect += topleft_client + v2s32(30, ypos - 40); + text = wgettext("Passwords do not match!"); + IGUIElement *e = Environment->addStaticText( + text, rect, false, true, this, ID_message); + e->setVisible(false); + delete[] text; + } +} + +void GUIConfirmRegistration::drawMenu() +{ + gui::IGUISkin *skin = Environment->getSkin(); + if (!skin) + return; + video::IVideoDriver *driver = Environment->getVideoDriver(); + + video::SColor bgcolor(140, 0, 0, 0); + driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); + + gui::IGUIElement::draw(); +} + +void GUIConfirmRegistration::closeMenu(bool goNext) +{ + quitMenu(); + if (goNext) { + m_client->confirmRegistration(); + } else { + *m_aborted = true; + infostream << "Connect aborted [Escape]" << std::endl; + } +} + +void GUIConfirmRegistration::acceptInput() +{ + gui::IGUIElement *e; + e = getElementFromId(ID_confirmPassword); + if (e) + m_pass_confirm = e->getText(); +} + +bool GUIConfirmRegistration::processInput() +{ + std::wstring m_password_ws = narrow_to_wide(m_password); + if (m_password_ws != m_pass_confirm) { + gui::IGUIElement *e = getElementFromId(ID_message); + if (e) + e->setVisible(true); + return false; + } + return true; +} + +bool GUIConfirmRegistration::OnEvent(const SEvent &event) +{ + if (event.EventType == EET_KEY_INPUT_EVENT) { + if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) { + closeMenu(false); + return true; + } + if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) { + acceptInput(); + if (processInput()) + closeMenu(true); + return true; + } + } + + if (event.EventType != EET_GUI_EVENT) + return Parent ? Parent->OnEvent(event) : false; + + if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) { + if (!canTakeFocus(event.GUIEvent.Element)) { + dstream << "GUIConfirmRegistration: Not allowing focus " + "change." + << std::endl; + // Returning true disables focus change + return true; + } + } else if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) { + switch (event.GUIEvent.Caller->getID()) { + case ID_confirm: + acceptInput(); + if (processInput()) + closeMenu(true); + return true; + case ID_cancel: + closeMenu(false); + return true; + } + } else if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) { + switch (event.GUIEvent.Caller->getID()) { + case ID_confirmPassword: + acceptInput(); + if (processInput()) + closeMenu(true); + return true; + } + } + + return false; +} diff --git a/src/gui/guiConfirmRegistration.h b/src/gui/guiConfirmRegistration.h new file mode 100644 index 000000000..e14066e0e --- /dev/null +++ b/src/gui/guiConfirmRegistration.h @@ -0,0 +1,61 @@ +/* +Minetest +Copyright (C) 2018 srifqi, Muhammad Rifqi Priyo Susanto + + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "irrlichttypes_extrabloated.h" +#include "modalMenu.h" +#include + +class Client; + +class GUIConfirmRegistration : public GUIModalMenu +{ +public: + GUIConfirmRegistration(gui::IGUIEnvironment *env, gui::IGUIElement *parent, + s32 id, IMenuManager *menumgr, Client *client, + const std::string &playername, const std::string &password, + const std::string &address, bool *aborted); + ~GUIConfirmRegistration(); + + void removeChildren(); + /* + Remove and re-add (or reposition) stuff + */ + void regenerateGui(v2u32 screensize); + + void drawMenu(); + + void closeMenu(bool goNext); + + void acceptInput(); + + bool processInput(); + + bool OnEvent(const SEvent &event); + +private: + Client *m_client = nullptr; + const std::string &m_playername; + const std::string &m_password; + const std::string &m_address; + bool *m_aborted = nullptr; + std::wstring m_pass_confirm = L""; +}; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 0ec46049e..14aa7b6e6 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -96,7 +96,12 @@ void Client::handleCommand_Hello(NetworkPacket* pkt) // Authenticate using that method, or abort if there wasn't any method found if (chosen_auth_mechanism != AUTH_MECHANISM_NONE) { - startAuth(chosen_auth_mechanism); + if (chosen_auth_mechanism == AUTH_MECHANISM_FIRST_SRP + && !m_simple_singleplayer_mode) { + promptConfirmRegistration(chosen_auth_mechanism); + } else { + startAuth(chosen_auth_mechanism); + } } else { m_chosen_auth_mech = AUTH_MECHANISM_NONE; m_access_denied = true; -- 2.25.1