diff --git a/meta.lua b/meta.lua index 4b06609940..ec418f48a7 100644 --- a/meta.lua +++ b/meta.lua @@ -3553,9 +3553,10 @@ function LoginHttp.create() end ---@param port integer ---@param email string ---@param password string +---@param token string ---@param requestId integer ---@param httpLogin boolean -function LoginHttp:httpLogin(host, path, port, email, password, requestId, httpLogin) end +function LoginHttp:httpLogin(host, path, port, email, password, token, requestId, httpLogin) end -------------------------------- ------------ g_http ------------ diff --git a/modules/client_entergame/entergame.lua b/modules/client_entergame/entergame.lua index cc6221a22e..172ec805e2 100644 --- a/modules/client_entergame/entergame.lua +++ b/modules/client_entergame/entergame.lua @@ -26,6 +26,39 @@ local function onError(protocol, message, errorCode) }) end +local function onTokenRequired(protocol) + if loadBox then + loadBox:destroy() + loadBox = nil + end + + local function promptForToken() + displayInputBox( + tr('Authenticator Required'), + tr('Please enter your authenticator token:'), + function(text) + local token = text and text:trim() or '' + G.authenticatorToken = token + + local tokenEdit = enterGame and enterGame:getChildById('authenticatorTokenTextEdit') + if tokenEdit then + tokenEdit:setText(token) + end + + EnterGame.doLogin() + end, + function() + G.authenticatorToken = '' + EnterGame.show() + end, + G.authenticatorToken or '', + 8 + ) + end + + promptForToken() +end + local function onMotd(protocol, motd) G.motdNumber = tonumber(motd:sub(0, motd:find('\n'))) G.motdMessage = motd:sub(motd:find('\n') + 1, #motd) @@ -462,6 +495,12 @@ function EnterGame.show() return end + local tokenEdit = enterGame and enterGame:getChildById('authenticatorTokenTextEdit') + if tokenEdit then + tokenEdit:setText('') + end + G.authenticatorToken = '' + enterGame:show() enterGame:raise() enterGame:focus() @@ -633,7 +672,7 @@ function EnterGame.tryHttpLogin(clientVersion, httpLogin) G.requestId = math.random(1) local http = LoginHttp.create() - http:httpLogin(host, path, G.port, G.account, G.password, G.requestId, httpLogin) + http:httpLogin(host, path, G.port, G.account, G.password, G.authenticatorToken or '', G.requestId, httpLogin) connect(loadBox, { onCancel = function(msgbox) loadBox = nil @@ -716,17 +755,34 @@ function EnterGame.loginFailed(requestId, msg, result) if G.requestId ~= requestId then return end + + local shouldRequestToken = false + + if msg then + local lowerMsg = msg:lower() + if lowerMsg:find('two%-factor') then + shouldRequestToken = true + end + end + + if shouldRequestToken then + G.authenticatorToken = '' + onTokenRequired(nil) + else onError(nil, msg, result) + end end function EnterGame.doLogin() G.account = enterGame:getChildById('accountNameTextEdit'):getText() G.password = enterGame:getChildById('accountPasswordTextEdit'):getText() - G.authenticatorToken = enterGame:getChildById('authenticatorTokenTextEdit'):getText() + local initialToken = enterGame:getChildById('authenticatorTokenTextEdit'):getText() + G.authenticatorToken = initialToken and initialToken:trim() or '' G.stayLogged = enterGame:getChildById('stayLoggedBox'):isChecked() G.host = enterGame:getChildById('serverHostTextEdit'):getText() G.port = tonumber(enterGame:getChildById('serverPortTextEdit'):getText()) local clientVersion = tonumber(clientBox:getText()) + G.clientVersion = clientVersion local httpLogin = enterGame:getChildById('httpLoginBox'):isChecked() EnterGame.hide() @@ -751,6 +807,7 @@ function EnterGame.doLogin() protocolLogin.onSessionKey = onSessionKey protocolLogin.onCharacterList = onCharacterList protocolLogin.onUpdateNeeded = onUpdateNeeded + protocolLogin.onTokenRequired = onTokenRequired loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to login server...')) connect(loadBox, { diff --git a/modules/corelib/ui/uiinputbox.lua b/modules/corelib/ui/uiinputbox.lua index 4495204182..6c7e278431 100644 --- a/modules/corelib/ui/uiinputbox.lua +++ b/modules/corelib/ui/uiinputbox.lua @@ -140,3 +140,33 @@ function displayNumberInputBox(title, label, okCallback, cancelCallback, min, ma inputBox:addSpinBox(label, min, max, value, step) inputBox:display() end + +function displayInputBox(title, message, okCallback, cancelCallback, defaultText, maxLength) + local function handleOk(...) + if okCallback then + okCallback(...) + end + end + + local function handleCancel() + if cancelCallback then + cancelCallback() + end + end + + local inputBox = UIInputBox.create(title, handleOk, handleCancel) + + if message and message ~= '' then + inputBox:addLabel(message) + end + + local lineEdit = inputBox:addLineEdit(nil, defaultText, maxLength) + + inputBox:display() + + if lineEdit and lineEdit.focus then + lineEdit:focus() + end + + return inputBox +end diff --git a/modules/gamelib/protocollogin.lua b/modules/gamelib/protocollogin.lua index e18a2f1d0c..254058bdfd 100644 --- a/modules/gamelib/protocollogin.lua +++ b/modules/gamelib/protocollogin.lua @@ -167,9 +167,8 @@ function ProtocolLogin:onRecv(msg) elseif opcode == LoginServerTokenSuccess then local unknown = msg:getU8() elseif opcode == LoginServerTokenError then - -- TODO: prompt for token here local unknown = msg:getU8() - signalcall(self.onLoginError, self, tr('Invalid authentification token.')) + signalcall(self.onTokenRequired, self) elseif opcode == LoginServerCharacterList then self:parseCharacterList(msg) elseif opcode == LoginServerExtendedCharacterList then diff --git a/src/client/protocolgamesend.cpp b/src/client/protocolgamesend.cpp index fd9207fce2..d82c27ffa7 100644 --- a/src/client/protocolgamesend.cpp +++ b/src/client/protocolgamesend.cpp @@ -81,6 +81,7 @@ void ProtocolGame::sendLoginPacket(const uint32_t challengeTimestamp, const uint if (g_game.getFeature(Otc::GameSessionKey)) { msg->addString(m_sessionKey); msg->addString(m_characterName); + } else { if (g_game.getFeature(Otc::GameAccountNames)) msg->addString(m_accountName); diff --git a/src/framework/net/httplogin.cpp b/src/framework/net/httplogin.cpp index d259b8f0ca..3d15bd169a 100644 --- a/src/framework/net/httplogin.cpp +++ b/src/framework/net/httplogin.cpp @@ -104,17 +104,17 @@ std::string LoginHttp::getSession() { return this->session; } void LoginHttp::httpLogin(const std::string& host, const std::string& path, uint16_t port, const std::string& email, - const std::string& password, int request_id, - bool httpLogin) { + const std::string& password, const std::string& token, + int request_id, bool httpLogin) { #ifndef __EMSCRIPTEN__ g_asyncDispatcher.detach_task( - [this, host, path, port, email, password, request_id, httpLogin] { + [this, host, path, port, email, password, token, request_id, httpLogin] { if (cancelled.load()) return; httplib::Result result = - this->loginHttpsJson(host, path, port, email, password); + this->loginHttpsJson(host, path, port, email, password, token); if (httpLogin && (!result || result->status != Success)) { if (cancelled.load()) return; - result = loginHttpJson(host, path, port, email, password); + result = loginHttpJson(host, path, port, email, password, token); } if (cancelled.load()) return; @@ -154,7 +154,7 @@ void LoginHttp::httpLogin(const std::string& host, const std::string& path, }); #else g_asyncDispatcher.detach_task( - [this, host, path, port, email, password, request_id, httpLogin] { + [this, host, path, port, email, password, token, request_id, httpLogin] { if (cancelled.load()) return; emscripten_fetch_attr_t attr; emscripten_fetch_attr_init(&attr); @@ -166,6 +166,10 @@ void LoginHttp::httpLogin(const std::string& host, const std::string& path, attr.requestHeaders = headers; attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_SYNCHRONOUS; json body = json{ {"email", email}, {"password", password}, {"stayloggedin", true}, {"type", "login"} }; + if (!token.empty()) { + body["token"] = token; + body["authenticatorToken"] = token; + } std::string bodyStr = body.dump(1); attr.requestData = bodyStr.data(); attr.requestDataSize = bodyStr.length(); @@ -224,7 +228,8 @@ httplib::Result LoginHttp::loginHttpsJson(const std::string& host, const std::string& path, const uint16_t port, const std::string& email, - const std::string& password) { + const std::string& password, + const std::string& token) { httplib::SSLClient client(host, port); client.set_logger( @@ -234,7 +239,11 @@ httplib::Result LoginHttp::loginHttpsJson(const std::string& host, client.enable_server_certificate_verification(false); client.enable_server_hostname_verification(false); - const json body = { {"email", email}, {"password", password}, {"stayloggedin", true}, {"type", "login"} }; + json body = { {"email", email}, {"password", password}, {"stayloggedin", true}, {"type", "login"} }; + if (!token.empty()) { + body["token"] = token; + body["authenticatorToken"] = token; + } const httplib::Headers headers = { {"User-Agent", "Mozilla/5.0"} }; httplib::Result response = @@ -261,13 +270,18 @@ httplib::Result LoginHttp::loginHttpJson(const std::string& host, const std::string& path, const uint16_t port, const std::string& email, - const std::string& password) { + const std::string& password, + const std::string& token) { httplib::Client client(host, port); client.set_logger( [this](const auto& req, const auto& res) { LoginHttp::Logger(req, res); }); const httplib::Headers headers = { {"User-Agent", "Mozilla/5.0"} }; - const json body = { {"email", email}, {"password", password}, {"stayloggedin", true}, {"type", "login"} }; + json body = { {"email", email}, {"password", password}, {"stayloggedin", true}, {"type", "login"} }; + if (!token.empty()) { + body["token"] = token; + body["authenticatorToken"] = token; + } httplib::Result response = client.Post(path, headers, body.dump(), "application/json"); diff --git a/src/framework/net/httplogin.h b/src/framework/net/httplogin.h index 2fb93116d1..d7acb93c01 100644 --- a/src/framework/net/httplogin.h +++ b/src/framework/net/httplogin.h @@ -47,17 +47,20 @@ class LoginHttp final : public LuaObject void httpLogin(const std::string& host, const std::string& path, uint16_t port, const std::string& email, - const std::string& password, int request_id, bool httpLogin); + const std::string& password, const std::string& token, + int request_id, bool httpLogin); httplib::Result loginHttpsJson(const std::string& host, const std::string& path, uint16_t port, const std::string& email, - const std::string& password); + const std::string& password, + const std::string& token); httplib::Result loginHttpJson(const std::string& host, const std::string& path, uint16_t port, const std::string& email, - const std::string& password); + const std::string& password, + const std::string& token); void cancel();