mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-31 15:21:58 +08:00
WebView and Connect integration: implemented login ceremony via /slicer/login request (and alternative disabled one with fetch function overriding); all stored JWT tokens merged into single secret store record to reduce number of login dialogs appearances when new app is run for first time on Mac.
This commit is contained in:
parent
5601a6e75f
commit
850ac19167
@ -342,7 +342,7 @@ template<class T>
|
||||
struct NilValueTempl<T, std::enable_if_t<std::is_enum_v<T>, void>>
|
||||
{
|
||||
using NilType = T;
|
||||
static constexpr auto value = static_cast<T>(std::numeric_limits<std::underlying_type_t<T>>::max());
|
||||
static constexpr auto value = std::numeric_limits<std::underlying_type_t<T>>::max();
|
||||
};
|
||||
|
||||
template<class T> struct NilValueTempl<T, std::enable_if_t<std::is_floating_point_v<T>, void>> {
|
||||
|
@ -653,7 +653,7 @@ void PhysicalPrinterDialog::update(bool printer_change)
|
||||
text_ctrl* printhost_win = printhost_field ? dynamic_cast<text_ctrl*>(printhost_field->getWindow()) : nullptr;
|
||||
if (!m_opened_as_connect && printhost_win && m_last_host_type != htPrusaConnect){
|
||||
m_stored_host = printhost_win->GetValue();
|
||||
printhost_win->SetValue(L"https://connect.prusa3d.com");
|
||||
printhost_win->SetValue(L"https://dev.connect.prusa3d.com");
|
||||
}
|
||||
} else {
|
||||
m_printhost_browse_btn->Show();
|
||||
@ -888,10 +888,10 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event)
|
||||
text_ctrl* printhost_win = printhost_field ? dynamic_cast<text_ctrl*>(printhost_field->getWindow()) : nullptr;
|
||||
const auto opt = m_config->option<ConfigOptionEnum<PrintHostType>>("host_type");
|
||||
if (opt->value == htPrusaConnect) {
|
||||
if (printhost_win && printhost_win->GetValue() != L"https://connect.prusa3d.com"){
|
||||
InfoDialog msg(this, _L("Warning"), _L("URL of PrusaConnect is different from https://connect.prusa3d.com. Do you want to continue?"), true, wxYES_NO);
|
||||
if (printhost_win && printhost_win->GetValue() != L"https://dev.connect.prusa3d.com"){
|
||||
InfoDialog msg(this, _L("Warning"), _L("URL of PrusaConnect is different from https://dev.connect.prusa3d.com. Do you want to continue?"), true, wxYES_NO);
|
||||
if(msg.ShowModal() != wxID_YES){
|
||||
printhost_win->SetValue(L"https://connect.prusa3d.com");
|
||||
printhost_win->SetValue(L"https://dev.connect.prusa3d.com");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -889,6 +889,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
});
|
||||
|
||||
this->q->Bind(EVT_UA_ID_USER_SUCCESS, [this](UserAccountSuccessEvent& evt) {
|
||||
// There are multiple handlers and we want to notify all
|
||||
evt.Skip();
|
||||
std::string username;
|
||||
if (user_account->on_user_id_success(evt.data, username)) {
|
||||
// login notification
|
||||
|
@ -143,12 +143,22 @@ UserAccountCommunication::UserAccountCommunication(wxEvtHandler* evt_handler, Ap
|
||||
|
||||
std::string access_token, refresh_token, shared_session_key, next_timeout;
|
||||
if (is_secret_store_ok()) {
|
||||
std::string key0, key1, key2;
|
||||
load_secret("access_token", key0, access_token);
|
||||
load_secret("refresh_token", key1, refresh_token);
|
||||
load_secret("access_token_timeout", key2, next_timeout);
|
||||
assert(key0 == key1);
|
||||
std::string key0, key1, key2, tokens;
|
||||
if (load_secret("tokens", key0, tokens)) {
|
||||
std::vector<std::string> token_list;
|
||||
boost::split(token_list, tokens, boost::is_any_of("|"), boost::token_compress_off);
|
||||
assert(token_list.empty() || token_list.size() == 3);
|
||||
access_token = token_list.size() > 0 ? token_list[0] : std::string();
|
||||
refresh_token = token_list.size() > 1 ? token_list[1] : std::string();
|
||||
next_timeout = token_list.size() > 2 ? token_list[2] : std::string();
|
||||
} else {
|
||||
load_secret("access_token", key0, access_token);
|
||||
load_secret("refresh_token", key1, refresh_token);
|
||||
load_secret("access_token_timeout", key2, next_timeout);
|
||||
assert(key0 == key1);
|
||||
}
|
||||
shared_session_key = key0;
|
||||
|
||||
} else {
|
||||
access_token = m_app_config->get("access_token");
|
||||
refresh_token = m_app_config->get("refresh_token");
|
||||
@ -173,7 +183,7 @@ UserAccountCommunication::UserAccountCommunication(wxEvtHandler* evt_handler, Ap
|
||||
UserAccountCommunication::~UserAccountCommunication()
|
||||
{
|
||||
if (m_thread.joinable()) {
|
||||
Stop the worker thread, if running.
|
||||
// Stop the worker thread, if running.
|
||||
{
|
||||
// Notify the worker thread to cancel wait on detection polling.
|
||||
std::lock_guard<std::mutex> lck(m_thread_stop_mutex);
|
||||
@ -191,9 +201,13 @@ void UserAccountCommunication::set_username(const std::string& username)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_session_mutex);
|
||||
if (is_secret_store_ok()) {
|
||||
save_secret("access_token", m_session->get_shared_session_key(), m_remember_session ? m_session->get_access_token() : std::string());
|
||||
save_secret("refresh_token", m_session->get_shared_session_key(), m_remember_session ? m_session->get_refresh_token() : std::string());
|
||||
save_secret("access_token_timeout", m_session->get_shared_session_key(), m_remember_session ? GUI::format("%1%",m_session->get_next_token_timeout()) : "0");
|
||||
std::string tokens;
|
||||
if (m_remember_session) {
|
||||
tokens = m_session->get_access_token() +
|
||||
"|" + m_session->get_refresh_token() +
|
||||
"|" + std::to_string(m_session->get_next_token_timeout());
|
||||
}
|
||||
save_secret("tokens", m_session->get_shared_session_key(), tokens);
|
||||
}
|
||||
else {
|
||||
m_app_config->set("access_token", m_remember_session ? m_session->get_access_token() : std::string());
|
||||
@ -245,7 +259,7 @@ void UserAccountCommunication::on_uuid_map_success()
|
||||
|
||||
void UserAccountCommunication::login_redirect()
|
||||
{
|
||||
const std::string AUTH_HOST = "https://account.prusa3d.com";
|
||||
const std::string AUTH_HOST = "https://test-account.prusa3d.com";
|
||||
const std::string CLIENT_ID = client_id();
|
||||
const std::string REDIRECT_URI = "prusaslicer://login";
|
||||
CodeChalengeGenerator ccg;
|
||||
|
@ -94,8 +94,7 @@ private:
|
||||
void wakeup_session_thread();
|
||||
void init_session_thread();
|
||||
void login_redirect();
|
||||
std::string client_id() const { return "oamhmhZez7opFosnwzElIgE2oGgI2iJORSkw587O"; }
|
||||
|
||||
std::string client_id() const { return "UfTRUm5QjWwaQEGpWQBHGHO3reAyuzgOdBaiqO52"; }
|
||||
|
||||
|
||||
};
|
||||
|
@ -111,15 +111,15 @@ public:
|
||||
|
||||
// do not forget to add delete to destructor
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_DUMMY] = std::make_unique<DummyUserAction>();
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN] = std::make_unique<UserActionPost>("EXCHANGE_TOKENS", "https://account.prusa3d.com/o/token/");
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CODE_FOR_TOKEN] = std::make_unique<UserActionPost>("EXCHANGE_TOKENS", "https://account.prusa3d.com/o/token/");
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID] = std::make_unique<UserActionGetWithEvent>("USER_ID", "https://account.prusa3d.com/api/v1/me/", EVT_UA_ID_USER_SUCCESS, EVT_UA_RESET);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN] = std::make_unique<UserActionGetWithEvent>("TEST_ACCESS_TOKEN", "https://account.prusa3d.com/api/v1/me/", EVT_UA_ID_USER_SUCCESS, EVT_UA_FAIL);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_TEST_CONNECTION] = std::make_unique<UserActionGetWithEvent>("TEST_CONNECTION", "https://account.prusa3d.com/api/v1/me/", wxEVT_NULL, EVT_UA_RESET);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_STATUS] = std::make_unique<UserActionGetWithEvent>("CONNECT_STATUS", "https://connect.prusa3d.com/slicer/status", EVT_UA_PRUSACONNECT_STATUS_SUCCESS, EVT_UA_FAIL);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_PRINTER_MODELS] = std::make_unique<UserActionGetWithEvent>("CONNECT_PRINTER_MODELS", "https://connect.prusa3d.com/slicer/printer_list", EVT_UA_PRUSACONNECT_PRINTER_MODELS_SUCCESS, EVT_UA_FAIL);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN] = std::make_unique<UserActionPost>("EXCHANGE_TOKENS", "https://test-account.prusa3d.com/o/token/");
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CODE_FOR_TOKEN] = std::make_unique<UserActionPost>("EXCHANGE_TOKENS", "https://test-account.prusa3d.com/o/token/");
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID] = std::make_unique<UserActionGetWithEvent>("USER_ID", "https://test-account.prusa3d.com/api/v1/me/", EVT_UA_ID_USER_SUCCESS, EVT_UA_RESET);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN] = std::make_unique<UserActionGetWithEvent>("TEST_ACCESS_TOKEN", "https://test-account.prusa3d.com/api/v1/me/", EVT_UA_ID_USER_SUCCESS, EVT_UA_FAIL);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_TEST_CONNECTION] = std::make_unique<UserActionGetWithEvent>("TEST_CONNECTION", "https://test-account.prusa3d.com/api/v1/me/", wxEVT_NULL, EVT_UA_RESET);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_STATUS] = std::make_unique<UserActionGetWithEvent>("CONNECT_STATUS", "https://dev.connect.prusa3d.com/slicer/status", EVT_UA_PRUSACONNECT_STATUS_SUCCESS, EVT_UA_FAIL);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_PRINTER_MODELS] = std::make_unique<UserActionGetWithEvent>("CONNECT_PRINTER_MODELS", "https://dev.connect.prusa3d.com/slicer/printer_list", EVT_UA_PRUSACONNECT_PRINTER_MODELS_SUCCESS, EVT_UA_FAIL);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_AVATAR] = std::make_unique<UserActionGetWithEvent>("AVATAR", "https://media.printables.com/media/", EVT_UA_AVATAR_SUCCESS, EVT_UA_FAIL);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_DATA_FROM_UUID] = std::make_unique<UserActionGetWithEvent>("USER_ACCOUNT_ACTION_CONNECT_DATA_FROM_UUID", "https://connect.prusa3d.com/app/printers/", EVT_UA_PRUSACONNECT_PRINTER_DATA_SUCCESS, EVT_UA_FAIL);
|
||||
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_DATA_FROM_UUID] = std::make_unique<UserActionGetWithEvent>("USER_ACCOUNT_ACTION_CONNECT_DATA_FROM_UUID", "https://dev.connect.prusa3d.com/app/printers/", EVT_UA_PRUSACONNECT_PRINTER_DATA_SUCCESS, EVT_UA_FAIL);
|
||||
}
|
||||
~UserAccountSession()
|
||||
{
|
||||
@ -162,7 +162,7 @@ private:
|
||||
void cancel_queue();
|
||||
void code_exchange_fail_callback(const std::string& body);
|
||||
void token_success_callback(const std::string& body);
|
||||
std::string client_id() const { return "oamhmhZez7opFosnwzElIgE2oGgI2iJORSkw587O"; }
|
||||
std::string client_id() const { return "UfTRUm5QjWwaQEGpWQBHGHO3reAyuzgOdBaiqO52"; }
|
||||
|
||||
// false prevents action queu to be processed - no communication is done
|
||||
// sets to true by init_with_code or enqueue_action call
|
||||
|
@ -15,6 +15,11 @@
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
// if set to 1 the fetch() JS function gets override to include JWT in authorization header
|
||||
// if set to 0, the /slicer/login is invoked from WebKit (passing JWT token only to this request)
|
||||
// to set authorization cookie for all WebKit requests to Connect
|
||||
#define AUTH_VIA_FETCH_OVERRIDE 0
|
||||
|
||||
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
@ -542,8 +547,83 @@ void ConnectRequestHandler::on_connect_action_request_config()
|
||||
}
|
||||
|
||||
ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent)
|
||||
: WebViewPanel(parent, L"https://connect.prusa3d.com/", { "_prusaSlicer" }, "connect_loading")
|
||||
: WebViewPanel(parent, L"https://dev.connect.prusa3d.com/", { "_prusaSlicer" }, "connect_loading")
|
||||
{
|
||||
//m_browser->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new WebViewHandler("https")));
|
||||
|
||||
Plater* plater = wxGetApp().plater();
|
||||
m_browser->AddUserScript(wxString::Format(
|
||||
|
||||
#if AUTH_VIA_FETCH_OVERRIDE
|
||||
/*
|
||||
* Notes:
|
||||
* - The fetch() function has two distinct prototypes (i.e. input args):
|
||||
* 1. fetch(url: string, options: object | undefined)
|
||||
* 2. fetch(req: Request, options: object | undefined)
|
||||
* - For some reason I can't explain the headers can be extended only via Request object
|
||||
* i.e. the fetch prototype (2). So we need to convert (1) call into (2) before
|
||||
*
|
||||
*/
|
||||
R"(
|
||||
if (window.__fetch === undefined) {
|
||||
window.__fetch = fetch;
|
||||
window.fetch = function(req, opts = {}) {
|
||||
if (typeof req === 'string') {
|
||||
req = new Request(req, opts);
|
||||
opts = {};
|
||||
}
|
||||
if (window.__access_token && (req.url[0] == '/' || req.url.indexOf('prusa3d.com') > 0)) {
|
||||
req.headers.set('Authorization', 'Bearer ' + window.__access_token);
|
||||
console.log('Header updated: ', req.headers.get('Authorization'));
|
||||
console.log('AT Version: ', __access_token_version);
|
||||
}
|
||||
//console.log('Injected fetch used', req, opts);
|
||||
return __fetch(req, opts);
|
||||
};
|
||||
}
|
||||
window.__access_token = '%s';
|
||||
window.__access_token_version = 0;
|
||||
)",
|
||||
#else
|
||||
R"(
|
||||
console.log('Preparing login');
|
||||
window.fetch('/slicer/login', {method: 'POST', headers: {Authorization: 'Bearer %s'}})
|
||||
.then((resp) => {
|
||||
console.log('Login resp', resp);
|
||||
resp.text().then((json) => console.log('Login resp body', json));
|
||||
});
|
||||
)",
|
||||
#endif
|
||||
plater->get_user_account()->get_access_token()
|
||||
));
|
||||
plater->Bind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this);
|
||||
}
|
||||
|
||||
ConnectWebViewPanel::~ConnectWebViewPanel()
|
||||
{
|
||||
m_browser->Unbind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this);
|
||||
}
|
||||
|
||||
void ConnectWebViewPanel::on_user_token(UserAccountSuccessEvent& e)
|
||||
{
|
||||
e.Skip();
|
||||
wxString javascript = wxString::Format(
|
||||
#if AUTH_VIA_FETCH_OVERRIDE
|
||||
"window.__access_token = '%s';window.__access_token_version = (window.__access_token_version || 0) + 1;console.log('Updated Auth token', window.__access_token);",
|
||||
#else
|
||||
R"(
|
||||
console.log('Preparing login');
|
||||
window.fetch('/slicer/login', {method: 'POST', headers: {Authorization: 'Bearer %s'}})
|
||||
.then((resp) => {
|
||||
console.log('Login resp', resp);
|
||||
resp.text().then((json) => console.log('Login resp body', json));
|
||||
});
|
||||
)",
|
||||
#endif
|
||||
wxGetApp().plater()->get_user_account()->get_access_token()
|
||||
);
|
||||
//m_browser->AddUserScript(javascript, wxWEBVIEW_INJECT_AT_DOCUMENT_END);
|
||||
m_browser->RunScript(javascript);
|
||||
}
|
||||
|
||||
void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt)
|
||||
@ -1040,7 +1120,7 @@ void WebViewDialog::EndModal(int retCode)
|
||||
|
||||
PrinterPickWebViewDialog::PrinterPickWebViewDialog(wxWindow* parent, std::string& ret_val)
|
||||
: WebViewDialog(parent
|
||||
, L"https://connect.prusa3d.com/slicer-select-printer"
|
||||
, L"https://dev.connect.prusa3d.com/slicer-select-printer"
|
||||
, _L("Choose a printer")
|
||||
, wxSize(std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 2, 50 * wxGetApp().em_unit()))
|
||||
,{"_prusaSlicer"}
|
||||
|
@ -1,18 +1,24 @@
|
||||
#ifndef slic3r_WebViewDialog_hpp_
|
||||
#define slic3r_WebViewDialog_hpp_
|
||||
|
||||
#define DEBUG_URL_PANEL
|
||||
|
||||
#include <map>
|
||||
#include <wx/wx.h>
|
||||
#include <wx/event.h>
|
||||
|
||||
#include "UserAccountSession.hpp"
|
||||
|
||||
#ifdef DEBUG_URL_PANEL
|
||||
#include <wx/infobar.h>
|
||||
#endif
|
||||
|
||||
class wxWebView;
|
||||
class wxWebViewEvent;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
//#define DEBUG_URL_PANEL
|
||||
|
||||
class WebViewPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
@ -183,6 +189,7 @@ class ConnectWebViewPanel : public WebViewPanel, public ConnectRequestHandler
|
||||
{
|
||||
public:
|
||||
ConnectWebViewPanel(wxWindow* parent);
|
||||
~ConnectWebViewPanel() override;
|
||||
void on_script_message(wxWebViewEvent& evt) override;
|
||||
void logout();
|
||||
void sys_color_changed() override;
|
||||
@ -191,6 +198,8 @@ protected:
|
||||
void on_connect_action_print() override;
|
||||
void on_connect_action_webapp_ready() override {}
|
||||
void run_script_bridge(const wxString& script) override {run_script(script); }
|
||||
private:
|
||||
void on_user_token(UserAccountSuccessEvent& e);
|
||||
};
|
||||
|
||||
class PrinterWebViewPanel : public WebViewPanel
|
||||
|
@ -60,7 +60,7 @@ bool PrusaConnectNew::test(wxString& curl_msg) const
|
||||
{
|
||||
// Test is not used by upload and gets list of files on a device.
|
||||
const std::string name = get_name();
|
||||
std::string url = GUI::format("https://connect.prusa3d.com/app/teams/%1%/files?printer_uuid=%2%", m_team_id, m_uuid);
|
||||
std::string url = GUI::format("https://dev.connect.prusa3d.com/app/teams/%1%/files?printer_uuid=%2%", m_team_id, m_uuid);
|
||||
const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token();
|
||||
BOOST_LOG_TRIVIAL(info) << GUI::format("%1%: Get files/raw at: %2%", name, url);
|
||||
bool res = true;
|
||||
|
@ -32,7 +32,7 @@ public:
|
||||
bool has_auto_discovery() const override { return true; }
|
||||
bool can_test() const override { return true; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::QueuePrint; }
|
||||
std::string get_host() const override { return "https://connect.prusa3d.com"; }
|
||||
std::string get_host() const override { return "https://dev.connect.prusa3d.com"; }
|
||||
bool get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const override;
|
||||
//const std::string& get_apikey() const { return m_apikey; }
|
||||
//const std::string& get_cafile() const { return m_cafile; }
|
||||
|
Loading…
x
Reference in New Issue
Block a user