diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index e09515f08c..78369233d1 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -332,6 +332,8 @@ set(SLIC3R_GUI_SOURCES Utils/Http.hpp Utils/FixModelByWin10.cpp Utils/FixModelByWin10.hpp + Utils/Jwt.cpp + Utils/Jwt.hpp Utils/Moonraker.cpp Utils/Moonraker.hpp Utils/OctoPrint.cpp diff --git a/src/slic3r/GUI/UserAccountCommunication.cpp b/src/slic3r/GUI/UserAccountCommunication.cpp index c5605bbf0a..c46e7d5953 100644 --- a/src/slic3r/GUI/UserAccountCommunication.cpp +++ b/src/slic3r/GUI/UserAccountCommunication.cpp @@ -475,8 +475,7 @@ void UserAccountCommunication::enqueue_refresh() BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in."; return; } - if (m_session->is_enqueued(UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN)) - { + if (m_session->is_enqueued(UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN)) { BOOST_LOG_TRIVIAL(debug) << "User Account: Token refresh already enqueued, skipping..."; return; } diff --git a/src/slic3r/GUI/UserAccountSession.cpp b/src/slic3r/GUI/UserAccountSession.cpp index 19e92d95e4..06ece32168 100644 --- a/src/slic3r/GUI/UserAccountSession.cpp +++ b/src/slic3r/GUI/UserAccountSession.cpp @@ -2,6 +2,7 @@ #include "GUI_App.hpp" #include "format.hpp" #include "../Utils/Http.hpp" +#include "../Utils/Jwt.hpp" #include "I18N.hpp" #include @@ -53,8 +54,17 @@ void UserActionGetWithEvent::perform(wxEvtHandler* evt_handler, const std::strin { std::string url = m_url + input; auto http = Http::get(std::move(url)); - if (!access_token.empty()) + if (!access_token.empty()) { http.header("Authorization", "Bearer " + access_token); +#ifndef _NDEBUG + // In debug mode, also verify the token expiration + // This is here to help with "dev" accounts with shorten (sort of faked) expiration time + // The /api/v1/me will accept these tokens even if these are fake-marked as expired + if (!Utils::verify_exp(access_token)) { + fail_callback("Token Expired"); + } +#endif + } http.on_error([evt_handler, fail_callback, action_name = &m_action_name, fail_evt_type = m_fail_evt_type](std::string body, std::string error, unsigned status) { if (fail_callback) fail_callback(body); diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index 3b9096ac27..f1d4825857 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -540,7 +540,7 @@ void ConnectRequestHandler::handle_message(const std::string& message) void ConnectRequestHandler::on_connect_action_error(const std::string &message_data) { - BOOST_LOG_TRIVIAL(error) << "WebKit runtime error: " << message_data; + BOOST_LOG_TRIVIAL(error) << "WebView runtime error: " << message_data; } void ConnectRequestHandler::resend_config() @@ -550,7 +550,7 @@ void ConnectRequestHandler::resend_config() void ConnectRequestHandler::on_connect_action_log(const std::string& message_data) { - BOOST_LOG_TRIVIAL(info) << "WebKit log: " << message_data; + BOOST_LOG_TRIVIAL(info) << "WebView log: " << message_data; } void ConnectRequestHandler::on_connect_action_request_config(const std::string& message_data) @@ -647,8 +647,9 @@ wxString ConnectWebViewPanel::get_login_script(bool refresh) R"( if (window._prusaSlicer_initLogin !== undefined) { console.log('Refreshing login'); - _prusaSlicer.postMessage({action: 'LOG', message: 'Refreshing login'}); - _prusaSlicer_initLogin('%s'); + if (window._prusaSlicer !== undefined) + _prusaSlicer.postMessage({action: 'LOG', message: 'Refreshing login'}); + _prusaSlicer_initLogin('%s', 'refresh'); } else { console.log('Refreshing login skipped as no _prusaSlicer_initLogin defined (yet?)'); if (window._prusaSlicer === undefined) { @@ -660,7 +661,11 @@ wxString ConnectWebViewPanel::get_login_script(bool refresh) )" : R"( - function _prusaSlicer_log(msg) { console.log(msg); _prusaSlicer.postMessage({action: 'LOG', message: msg}); } + function _prusaSlicer_log(msg) { + console.log(msg); + if (window._prusaSlicer !== undefined) + _prusaSlicer.postMessage({action: 'LOG', message: msg}); + } function _prusaSlicer_errorHandler(err) { const msg = { action: 'ERROR', @@ -677,7 +682,7 @@ wxString ConnectWebViewPanel::get_login_script(bool refresh) }); } - async function _prusaSlicer_initLogin(token) { + async function _prusaSlicer_initLogin(token, reason) { const parts = token.split('.'); const claims = JSON.parse(atob(parts[1])); const now = new Date().getTime() / 1000; @@ -694,10 +699,10 @@ wxString ConnectWebViewPanel::get_login_script(bool refresh) let error = false; try { - _prusaSlicer_log('Slicer Login request'); + _prusaSlicer_log('Slicer Login request (' + reason + ') ' + token.substring(token.length - 8)); let resp = await fetch('/slicer/login', {method: 'POST', headers: {Authorization: 'Bearer ' + token}}); let body = await resp.text(); - _prusaSlicer_log('Slicer Login resp ' + resp.status + ' body: ' + body); + _prusaSlicer_log('Slicer Login resp ' + resp.status + ' (' +reason + ' ' + token.substring(token.length - 8) + ') body: ' + body); if (resp.status >= 500 || resp.status == 408) { retry = true; } else { @@ -721,7 +726,7 @@ wxString ConnectWebViewPanel::get_login_script(bool refresh) } if (window._prusaSlicer_initialLoad === undefined) { console.log('Initial login'); - _prusaSlicer_initLogin('%s'); + _prusaSlicer_initLogin('%s', 'init-load'); window._prusaSlicer_initialLoad = true; } )", @@ -743,7 +748,12 @@ void ConnectWebViewPanel::on_user_token(UserAccountSuccessEvent& e) e.Skip(); auto access_token = wxGetApp().plater()->get_user_account()->get_access_token(); assert(!access_token.empty()); - wxString javascript = get_login_script(true); + wxString javascript = get_login_script(false); + + m_browser->RemoveAllUserScripts(); + m_browser->AddUserScript(javascript); + + javascript = get_login_script(true); //m_browser->AddUserScript(javascript, wxWEBVIEW_INJECT_AT_DOCUMENT_END); BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; m_browser->RunScriptAsync(javascript); diff --git a/src/slic3r/Utils/Jwt.cpp b/src/slic3r/Utils/Jwt.cpp new file mode 100644 index 0000000000..4584471e04 --- /dev/null +++ b/src/slic3r/Utils/Jwt.cpp @@ -0,0 +1,58 @@ +#include "Jwt.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace Slic3r::Utils { + +bool verify_exp(const std::string& token) +{ + size_t payload_start = token.find('.'); + if (payload_start == std::string::npos) + return false; + payload_start += 1; // payload starts after dot + + const size_t payload_end = token.find('.', payload_start); + if (payload_end == std::string::npos) + return false; + + size_t encoded_length = payload_end - payload_start; + size_t decoded_length = boost::beast::detail::base64::decoded_size(encoded_length); + + auto json_b64 = token.substr(payload_start, encoded_length); + std::replace(json_b64.begin(), json_b64.end(), '-', '+'); + std::replace(json_b64.begin(), json_b64.end(), '_', '/'); + + size_t padding = encoded_length % 4; + encoded_length += padding; + while (padding--) json_b64 += '='; + + + std::string json; + json.resize(decoded_length + 2); + size_t read_bytes, written_bytes; + std::tie(written_bytes, read_bytes) = boost::beast::detail::base64::decode(json.data(), json_b64.data(), json_b64.length()); + json.resize(written_bytes); + if (written_bytes == 0) + return false; + + namespace pt = boost::property_tree; + + pt::ptree payload; + std::istringstream iss(json); + pt::json_parser::read_json(iss, payload); + + auto exp_opt = payload.get_optional("exp"); + if (!exp_opt) + return false; + + auto now = time(nullptr); + return exp_opt.get() > now; +} + +} diff --git a/src/slic3r/Utils/Jwt.hpp b/src/slic3r/Utils/Jwt.hpp new file mode 100644 index 0000000000..b957e8df8d --- /dev/null +++ b/src/slic3r/Utils/Jwt.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace Slic3r::Utils { + +bool verify_exp(const std::string& token); + + +}