WebService: Verify username and token (#2930)
* WebService: Verify username and token; Log errors in PostJson * Fixup: added docstrings to the functions * Webservice: Added Icons to the verification, imrpved error detection in cpr, fixup nits * fixup: fmt warning
This commit is contained in:
parent
255fd8768d
commit
28c726f205
Binary file not shown.
After Width: | Height: | Size: 451 B |
Binary file not shown.
After Width: | Height: | Size: 428 B |
|
@ -0,0 +1,6 @@
|
||||||
|
<RCC>
|
||||||
|
<qresource prefix="icons">
|
||||||
|
<file>checked.png</file>
|
||||||
|
<file>failed.png</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
|
@ -162,6 +162,8 @@ void Config::ReadValues() {
|
||||||
sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
|
sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
|
||||||
Settings::values.telemetry_endpoint_url = sdl2_config->Get(
|
Settings::values.telemetry_endpoint_url = sdl2_config->Get(
|
||||||
"WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");
|
"WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");
|
||||||
|
Settings::values.verify_endpoint_url = sdl2_config->Get(
|
||||||
|
"WebService", "verify_endpoint_url", "https://services.citra-emu.org/api/profile");
|
||||||
Settings::values.citra_username = sdl2_config->Get("WebService", "citra_username", "");
|
Settings::values.citra_username = sdl2_config->Get("WebService", "citra_username", "");
|
||||||
Settings::values.citra_token = sdl2_config->Get("WebService", "citra_token", "");
|
Settings::values.citra_token = sdl2_config->Get("WebService", "citra_token", "");
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,8 @@ gdbstub_port=24689
|
||||||
enable_telemetry =
|
enable_telemetry =
|
||||||
# Endpoint URL for submitting telemetry data
|
# Endpoint URL for submitting telemetry data
|
||||||
telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry
|
telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry
|
||||||
|
# Endpoint URL to verify the username and token
|
||||||
|
verify_endpoint_url = https://services.citra-emu.org/api/profile
|
||||||
# Username and token for Citra Web Service
|
# Username and token for Citra Web Service
|
||||||
# See https://services.citra-emu.org/ for more info
|
# See https://services.citra-emu.org/ for more info
|
||||||
citra_username =
|
citra_username =
|
||||||
|
|
|
@ -79,6 +79,7 @@ set(UIS
|
||||||
main.ui
|
main.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*)
|
||||||
file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
|
file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
|
||||||
|
|
||||||
create_directory_groups(${SRCS} ${HEADERS} ${UIS})
|
create_directory_groups(${SRCS} ${HEADERS} ${UIS})
|
||||||
|
@ -92,10 +93,10 @@ endif()
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
set(MACOSX_ICON "../../dist/citra.icns")
|
set(MACOSX_ICON "../../dist/citra.icns")
|
||||||
set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||||
add_executable(citra-qt MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS} ${THEMES} ${MACOSX_ICON})
|
add_executable(citra-qt MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS} ${ICONS} ${THEMES} ${MACOSX_ICON})
|
||||||
set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
|
set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
|
||||||
else()
|
else()
|
||||||
add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS} ${THEMES})
|
add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS} ${ICONS} ${THEMES})
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
|
target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
|
||||||
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::OpenGL Qt5::Widgets)
|
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::OpenGL Qt5::Widgets)
|
||||||
|
|
|
@ -146,6 +146,10 @@ void Config::ReadValues() {
|
||||||
qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
|
qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
|
||||||
.toString()
|
.toString()
|
||||||
.toStdString();
|
.toStdString();
|
||||||
|
Settings::values.verify_endpoint_url =
|
||||||
|
qt_config->value("verify_endpoint_url", "https://services.citra-emu.org/api/profile")
|
||||||
|
.toString()
|
||||||
|
.toStdString();
|
||||||
Settings::values.citra_username = qt_config->value("citra_username").toString().toStdString();
|
Settings::values.citra_username = qt_config->value("citra_username").toString().toStdString();
|
||||||
Settings::values.citra_token = qt_config->value("citra_token").toString().toStdString();
|
Settings::values.citra_token = qt_config->value("citra_token").toString().toStdString();
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
@ -293,6 +297,8 @@ void Config::SaveValues() {
|
||||||
qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
|
qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
|
||||||
qt_config->setValue("telemetry_endpoint_url",
|
qt_config->setValue("telemetry_endpoint_url",
|
||||||
QString::fromStdString(Settings::values.telemetry_endpoint_url));
|
QString::fromStdString(Settings::values.telemetry_endpoint_url));
|
||||||
|
qt_config->setValue("verify_endpoint_url",
|
||||||
|
QString::fromStdString(Settings::values.verify_endpoint_url));
|
||||||
qt_config->setValue("citra_username", QString::fromStdString(Settings::values.citra_username));
|
qt_config->setValue("citra_username", QString::fromStdString(Settings::values.citra_username));
|
||||||
qt_config->setValue("citra_token", QString::fromStdString(Settings::values.citra_token));
|
qt_config->setValue("citra_token", QString::fromStdString(Settings::values.citra_token));
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <QMessageBox>
|
||||||
#include "citra_qt/configuration/configure_web.h"
|
#include "citra_qt/configuration/configure_web.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "core/telemetry_session.h"
|
#include "core/telemetry_session.h"
|
||||||
|
@ -11,7 +12,9 @@ ConfigureWeb::ConfigureWeb(QWidget* parent)
|
||||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
|
: QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
|
connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
|
||||||
&ConfigureWeb::refreshTelemetryID);
|
&ConfigureWeb::RefreshTelemetryID);
|
||||||
|
connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
|
||||||
|
connect(this, &ConfigureWeb::LoginVerified, this, &ConfigureWeb::OnLoginVerified);
|
||||||
|
|
||||||
this->setConfiguration();
|
this->setConfiguration();
|
||||||
}
|
}
|
||||||
|
@ -34,19 +37,66 @@ void ConfigureWeb::setConfiguration() {
|
||||||
ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
|
ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
|
||||||
ui->edit_username->setText(QString::fromStdString(Settings::values.citra_username));
|
ui->edit_username->setText(QString::fromStdString(Settings::values.citra_username));
|
||||||
ui->edit_token->setText(QString::fromStdString(Settings::values.citra_token));
|
ui->edit_token->setText(QString::fromStdString(Settings::values.citra_token));
|
||||||
|
// Connect after setting the values, to avoid calling OnLoginChanged now
|
||||||
|
connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
|
||||||
|
connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
|
||||||
ui->label_telemetry_id->setText("Telemetry ID: 0x" +
|
ui->label_telemetry_id->setText("Telemetry ID: 0x" +
|
||||||
QString::number(Core::GetTelemetryId(), 16).toUpper());
|
QString::number(Core::GetTelemetryId(), 16).toUpper());
|
||||||
|
user_verified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureWeb::applyConfiguration() {
|
void ConfigureWeb::applyConfiguration() {
|
||||||
Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
|
Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
|
||||||
|
if (user_verified) {
|
||||||
Settings::values.citra_username = ui->edit_username->text().toStdString();
|
Settings::values.citra_username = ui->edit_username->text().toStdString();
|
||||||
Settings::values.citra_token = ui->edit_token->text().toStdString();
|
Settings::values.citra_token = ui->edit_token->text().toStdString();
|
||||||
|
} else {
|
||||||
|
QMessageBox::warning(this, tr("Username and token not verfied"),
|
||||||
|
tr("Username and token were not verified. The changes to your "
|
||||||
|
"username and/or token have not been saved."));
|
||||||
|
}
|
||||||
Settings::Apply();
|
Settings::Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureWeb::refreshTelemetryID() {
|
void ConfigureWeb::RefreshTelemetryID() {
|
||||||
const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
|
const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
|
||||||
ui->label_telemetry_id->setText("Telemetry ID: 0x" +
|
ui->label_telemetry_id->setText("Telemetry ID: 0x" +
|
||||||
QString::number(new_telemetry_id, 16).toUpper());
|
QString::number(new_telemetry_id, 16).toUpper());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigureWeb::OnLoginChanged() {
|
||||||
|
if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) {
|
||||||
|
user_verified = true;
|
||||||
|
ui->label_username_verified->setPixmap(QPixmap(":/icons/checked.png"));
|
||||||
|
ui->label_token_verified->setPixmap(QPixmap(":/icons/checked.png"));
|
||||||
|
} else {
|
||||||
|
user_verified = false;
|
||||||
|
ui->label_username_verified->setPixmap(QPixmap(":/icons/failed.png"));
|
||||||
|
ui->label_token_verified->setPixmap(QPixmap(":/icons/failed.png"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureWeb::VerifyLogin() {
|
||||||
|
verified =
|
||||||
|
Core::VerifyLogin(ui->edit_username->text().toStdString(),
|
||||||
|
ui->edit_token->text().toStdString(), [&]() { emit LoginVerified(); });
|
||||||
|
ui->button_verify_login->setDisabled(true);
|
||||||
|
ui->button_verify_login->setText(tr("Verifying"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureWeb::OnLoginVerified() {
|
||||||
|
ui->button_verify_login->setEnabled(true);
|
||||||
|
ui->button_verify_login->setText(tr("Verify"));
|
||||||
|
if (verified.get()) {
|
||||||
|
user_verified = true;
|
||||||
|
ui->label_username_verified->setPixmap(QPixmap(":/icons/checked.png"));
|
||||||
|
ui->label_token_verified->setPixmap(QPixmap(":/icons/checked.png"));
|
||||||
|
} else {
|
||||||
|
ui->label_username_verified->setPixmap(QPixmap(":/icons/failed.png"));
|
||||||
|
ui->label_token_verified->setPixmap(QPixmap(":/icons/failed.png"));
|
||||||
|
QMessageBox::critical(
|
||||||
|
this, tr("Verification failed"),
|
||||||
|
tr("Verification failed. Check that you have entered your username and token "
|
||||||
|
"correctly, and that your internet connection is working."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <future>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
@ -21,10 +22,19 @@ public:
|
||||||
void applyConfiguration();
|
void applyConfiguration();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void refreshTelemetryID();
|
void RefreshTelemetryID();
|
||||||
|
void OnLoginChanged();
|
||||||
|
void VerifyLogin();
|
||||||
|
void OnLoginVerified();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void LoginVerified();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setConfiguration();
|
void setConfiguration();
|
||||||
|
|
||||||
|
bool user_verified = true;
|
||||||
|
std::future<bool> verified;
|
||||||
|
|
||||||
std::unique_ptr<Ui::ConfigureWeb> ui;
|
std::unique_ptr<Ui::ConfigureWeb> ui;
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>400</width>
|
<width>926</width>
|
||||||
<height>300</height>
|
<height>561</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
|
@ -31,14 +31,30 @@
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QGridLayout" name="gridLayoutCitraUsername">
|
<layout class="QGridLayout" name="gridLayoutCitraUsername">
|
||||||
<item row="0" column="0">
|
<item row="2" column="3">
|
||||||
<widget class="QLabel" name="label_username">
|
<widget class="QPushButton" name="button_verify_login">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="layoutDirection">
|
||||||
|
<enum>Qt::RightToLeft</enum>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Username: </string>
|
<string>Verify</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="web_signup_link">
|
||||||
|
<property name="text">
|
||||||
|
<string>Sign up</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1" colspan="3">
|
||||||
<widget class="QLineEdit" name="edit_username">
|
<widget class="QLineEdit" name="edit_username">
|
||||||
<property name="maxLength">
|
<property name="maxLength">
|
||||||
<number>36</number>
|
<number>36</number>
|
||||||
|
@ -52,7 +68,22 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="4">
|
||||||
|
<widget class="QLabel" name="label_token_verified">
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_username">
|
||||||
|
<property name="text">
|
||||||
|
<string>Username: </string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="4">
|
||||||
|
<widget class="QLabel" name="label_username_verified">
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1" colspan="3">
|
||||||
<widget class="QLineEdit" name="edit_token">
|
<widget class="QLineEdit" name="edit_token">
|
||||||
<property name="maxLength">
|
<property name="maxLength">
|
||||||
<number>36</number>
|
<number>36</number>
|
||||||
|
@ -62,13 +93,6 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="web_signup_link">
|
|
||||||
<property name="text">
|
|
||||||
<string>Sign up</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QLabel" name="web_token_info_link">
|
<widget class="QLabel" name="web_token_info_link">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -76,6 +100,19 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="2" column="2">
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
@ -133,6 +133,7 @@ struct Values {
|
||||||
// WebService
|
// WebService
|
||||||
bool enable_telemetry;
|
bool enable_telemetry;
|
||||||
std::string telemetry_endpoint_url;
|
std::string telemetry_endpoint_url;
|
||||||
|
std::string verify_endpoint_url;
|
||||||
std::string citra_username;
|
std::string citra_username;
|
||||||
std::string citra_token;
|
std::string citra_token;
|
||||||
} extern values;
|
} extern values;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
#ifdef ENABLE_WEB_SERVICE
|
#ifdef ENABLE_WEB_SERVICE
|
||||||
#include "web_service/telemetry_json.h"
|
#include "web_service/telemetry_json.h"
|
||||||
|
#include "web_service/verify_login.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
@ -75,6 +76,17 @@ u64 RegenerateTelemetryId() {
|
||||||
return new_telemetry_id;
|
return new_telemetry_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func) {
|
||||||
|
#ifdef ENABLE_WEB_SERVICE
|
||||||
|
return WebService::VerifyLogin(username, token, Settings::values.verify_endpoint_url, func);
|
||||||
|
#else
|
||||||
|
return std::async(std::launch::async, [func{std::move(func)}]() {
|
||||||
|
func();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
TelemetrySession::TelemetrySession() {
|
TelemetrySession::TelemetrySession() {
|
||||||
#ifdef ENABLE_WEB_SERVICE
|
#ifdef ENABLE_WEB_SERVICE
|
||||||
if (Settings::values.enable_telemetry) {
|
if (Settings::values.enable_telemetry) {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <future>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "common/telemetry.h"
|
#include "common/telemetry.h"
|
||||||
|
|
||||||
|
@ -47,4 +48,13 @@ u64 GetTelemetryId();
|
||||||
*/
|
*/
|
||||||
u64 RegenerateTelemetryId();
|
u64 RegenerateTelemetryId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the username and token.
|
||||||
|
* @param username Citra username to use for authentication.
|
||||||
|
* @param token Citra token to use for authentication.
|
||||||
|
* @param func A function that gets exectued when the verification is finished
|
||||||
|
* @returns Future with bool indicating whether the verification succeeded
|
||||||
|
*/
|
||||||
|
std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func);
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
set(SRCS
|
set(SRCS
|
||||||
telemetry_json.cpp
|
telemetry_json.cpp
|
||||||
|
verify_login.cpp
|
||||||
web_backend.cpp
|
web_backend.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
telemetry_json.h
|
telemetry_json.h
|
||||||
|
verify_login.h
|
||||||
web_backend.h
|
web_backend.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <json.hpp>
|
||||||
|
#include "web_service/verify_login.h"
|
||||||
|
#include "web_service/web_backend.h"
|
||||||
|
|
||||||
|
namespace WebService {
|
||||||
|
|
||||||
|
std::future<bool> VerifyLogin(std::string& username, std::string& token,
|
||||||
|
const std::string& endpoint_url, std::function<void()> func) {
|
||||||
|
auto get_func = [func, username](const std::string& reply) -> bool {
|
||||||
|
func();
|
||||||
|
if (reply.empty())
|
||||||
|
return false;
|
||||||
|
nlohmann::json json = nlohmann::json::parse(reply);
|
||||||
|
std::string result;
|
||||||
|
try {
|
||||||
|
result = json["username"];
|
||||||
|
} catch (const nlohmann::detail::out_of_range&) {
|
||||||
|
}
|
||||||
|
return result == username;
|
||||||
|
};
|
||||||
|
return GetJson<bool>(get_func, endpoint_url, false, username, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace WebService
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace WebService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if username and token is valid
|
||||||
|
* @param username Citra username to use for authentication.
|
||||||
|
* @param token Citra token to use for authentication.
|
||||||
|
* @param endpoint_url URL of the services.citra-emu.org endpoint.
|
||||||
|
* @param func A function that gets exectued when the verification is finished
|
||||||
|
* @returns Future with bool indicating whether the verification succeeded
|
||||||
|
*/
|
||||||
|
std::future<bool> VerifyLogin(std::string& username, std::string& token,
|
||||||
|
const std::string& endpoint_url, std::function<void()> func);
|
||||||
|
|
||||||
|
} // namespace WebService
|
|
@ -18,6 +18,19 @@ static constexpr char API_VERSION[]{"1"};
|
||||||
|
|
||||||
static std::unique_ptr<cpr::Session> g_session;
|
static std::unique_ptr<cpr::Session> g_session;
|
||||||
|
|
||||||
|
void Win32WSAStartup() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
// On Windows, CPR/libcurl does not properly initialize Winsock. The below code is used to
|
||||||
|
// initialize Winsock globally, which fixes this problem. Without this, only the first CPR
|
||||||
|
// session will properly be created, and subsequent ones will fail.
|
||||||
|
WSADATA wsa_data;
|
||||||
|
const int wsa_result{WSAStartup(MAKEWORD(2, 2), &wsa_data)};
|
||||||
|
if (wsa_result) {
|
||||||
|
LOG_CRITICAL(WebService, "WSAStartup failed: %d", wsa_result);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
|
void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
|
||||||
const std::string& username, const std::string& token) {
|
const std::string& username, const std::string& token) {
|
||||||
if (url.empty()) {
|
if (url.empty()) {
|
||||||
|
@ -31,16 +44,7 @@ void PostJson(const std::string& url, const std::string& data, bool allow_anonym
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
Win32WSAStartup();
|
||||||
// On Windows, CPR/libcurl does not properly initialize Winsock. The below code is used to
|
|
||||||
// initialize Winsock globally, which fixes this problem. Without this, only the first CPR
|
|
||||||
// session will properly be created, and subsequent ones will fail.
|
|
||||||
WSADATA wsa_data;
|
|
||||||
const int wsa_result{WSAStartup(MAKEWORD(2, 2), &wsa_data)};
|
|
||||||
if (wsa_result) {
|
|
||||||
LOG_CRITICAL(WebService, "WSAStartup failed: %d", wsa_result);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Built request header
|
// Built request header
|
||||||
cpr::Header header;
|
cpr::Header header;
|
||||||
|
@ -56,8 +60,81 @@ void PostJson(const std::string& url, const std::string& data, bool allow_anonym
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post JSON asynchronously
|
// Post JSON asynchronously
|
||||||
static cpr::AsyncResponse future;
|
static std::future<void> future;
|
||||||
future = cpr::PostAsync(cpr::Url{url.c_str()}, cpr::Body{data.c_str()}, header);
|
future = cpr::PostCallback(
|
||||||
|
[](cpr::Response r) {
|
||||||
|
if (r.error) {
|
||||||
|
LOG_ERROR(WebService, "POST returned cpr error: %u:%s",
|
||||||
|
static_cast<u32>(r.error.code), r.error.message.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (r.status_code >= 400) {
|
||||||
|
LOG_ERROR(WebService, "POST returned error status code: %u", r.status_code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (r.header["content-type"].find("application/json") == std::string::npos) {
|
||||||
|
LOG_ERROR(WebService, "POST returned wrong content: %s",
|
||||||
|
r.header["content-type"].c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cpr::Url{url}, cpr::Body{data}, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
std::future<T> GetJson(std::function<T(const std::string&)> func, const std::string& url,
|
||||||
|
bool allow_anonymous, const std::string& username,
|
||||||
|
const std::string& token) {
|
||||||
|
if (url.empty()) {
|
||||||
|
LOG_ERROR(WebService, "URL is invalid");
|
||||||
|
return std::async(std::launch::async, [func{std::move(func)}]() { return func(""); });
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool are_credentials_provided{!token.empty() && !username.empty()};
|
||||||
|
if (!allow_anonymous && !are_credentials_provided) {
|
||||||
|
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
|
||||||
|
return std::async(std::launch::async, [func{std::move(func)}]() { return func(""); });
|
||||||
|
}
|
||||||
|
|
||||||
|
Win32WSAStartup();
|
||||||
|
|
||||||
|
// Built request header
|
||||||
|
cpr::Header header;
|
||||||
|
if (are_credentials_provided) {
|
||||||
|
// Authenticated request if credentials are provided
|
||||||
|
header = {{"Content-Type", "application/json"},
|
||||||
|
{"x-username", username.c_str()},
|
||||||
|
{"x-token", token.c_str()},
|
||||||
|
{"api-version", API_VERSION}};
|
||||||
|
} else {
|
||||||
|
// Otherwise, anonymous request
|
||||||
|
header = cpr::Header{{"Content-Type", "application/json"}, {"api-version", API_VERSION}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get JSON asynchronously
|
||||||
|
return cpr::GetCallback(
|
||||||
|
[func{std::move(func)}](cpr::Response r) {
|
||||||
|
if (r.error) {
|
||||||
|
LOG_ERROR(WebService, "GET returned cpr error: %u:%s",
|
||||||
|
static_cast<u32>(r.error.code), r.error.message.c_str());
|
||||||
|
return func("");
|
||||||
|
}
|
||||||
|
if (r.status_code >= 400) {
|
||||||
|
LOG_ERROR(WebService, "GET returned error code: %u", r.status_code);
|
||||||
|
return func("");
|
||||||
|
}
|
||||||
|
if (r.header["content-type"].find("application/json") == std::string::npos) {
|
||||||
|
LOG_ERROR(WebService, "GET returned wrong content: %s",
|
||||||
|
r.header["content-type"].c_str());
|
||||||
|
return func("");
|
||||||
|
}
|
||||||
|
return func(r.text);
|
||||||
|
},
|
||||||
|
cpr::Url{url}, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
template std::future<bool> GetJson(std::function<bool(const std::string&)> func,
|
||||||
|
const std::string& url, bool allow_anonymous,
|
||||||
|
const std::string& username, const std::string& token);
|
||||||
|
|
||||||
} // namespace WebService
|
} // namespace WebService
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
@ -20,4 +22,18 @@ namespace WebService {
|
||||||
void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
|
void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
|
||||||
const std::string& username = {}, const std::string& token = {});
|
const std::string& username = {}, const std::string& token = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets JSON from services.citra-emu.org.
|
||||||
|
* @param func A function that gets exectued when the json as a string is received
|
||||||
|
* @param url URL of the services.citra-emu.org endpoint to post data to.
|
||||||
|
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
|
||||||
|
* @param username Citra username to use for authentication.
|
||||||
|
* @param token Citra token to use for authentication.
|
||||||
|
* @return future that holds the return value T of the func
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
std::future<T> GetJson(std::function<T(const std::string&)> func, const std::string& url,
|
||||||
|
bool allow_anonymous, const std::string& username = {},
|
||||||
|
const std::string& token = {});
|
||||||
|
|
||||||
} // namespace WebService
|
} // namespace WebService
|
||||||
|
|
Loading…
Reference in New Issue