mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Settings dialog (fixes #4) and WIP authentication (Buggy and needs testing)
This commit is contained in:
parent
5f7061c586
commit
bf317987c3
@ -4,11 +4,16 @@ project(obs-websocket)
|
||||
set(CMAKE_PREFIX_PATH "${QTDIR}")
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
include(external/FindLibObs.cmake)
|
||||
find_package(LibObs REQUIRED)
|
||||
find_package(Qt5Core REQUIRED)
|
||||
find_package(Qt5WebSockets REQUIRED)
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
|
||||
add_subdirectory(deps/mbedtls EXCLUDE_FROM_ALL)
|
||||
set(ENABLED_PROGRAMS false)
|
||||
|
||||
set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library")
|
||||
if(OBS_FRONTEND_LIB EQUAL "OBS_FRONTEND_LIB-NOTFOUND")
|
||||
@ -21,7 +26,8 @@ set(obs-websocket_SOURCES
|
||||
WSRequestHandler.cpp
|
||||
WSEvents.cpp
|
||||
Config.cpp
|
||||
Utils.cpp)
|
||||
Utils.cpp
|
||||
forms/settings-dialog.cpp)
|
||||
|
||||
set(obs-websocket_HEADERS
|
||||
obs-websocket.h
|
||||
@ -29,19 +35,24 @@ set(obs-websocket_HEADERS
|
||||
WSRequestHandler.h
|
||||
WSEvents.h
|
||||
Config.h
|
||||
Utils.h)
|
||||
Utils.h
|
||||
forms/settings-dialog.h)
|
||||
|
||||
add_library(obs-websocket MODULE
|
||||
${obs-websocket_SOURCES}
|
||||
${obs-websocket_HEADERS})
|
||||
qt5_use_modules(obs-websocket
|
||||
Core WebSockets)
|
||||
add_dependencies(obs-websocket mbedcrypto)
|
||||
include_directories(
|
||||
"${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api"
|
||||
${Qt5Core_INCLUDES}
|
||||
${Qt5WebSockets_INCLUDES})
|
||||
${Qt5WebSockets_INCLUDES}
|
||||
${Qt5Widgets_INCLUDES}
|
||||
${mbedcrypto_INCLUDES}
|
||||
"${CMAKE_SOURCE_DIR}/deps/mbedtls/include")
|
||||
target_link_libraries(obs-websocket
|
||||
libobs
|
||||
${OBS_FRONTEND_LIB}
|
||||
Qt5::Core
|
||||
Qt5::WebSockets)
|
||||
Qt5::WebSockets
|
||||
Qt5::Widgets
|
||||
mbedcrypto)
|
||||
|
113
Config.cpp
113
Config.cpp
@ -1,16 +1,102 @@
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include "Config.h"
|
||||
|
||||
#define CONFIG_SECTION_NAME "obs-websocket"
|
||||
#define CONFIG_PARAM_CHALLENGE "auth_hash"
|
||||
#define CONFIG_PARAM_SALT "auth_salt"
|
||||
#define CONFIG_PARAM_AUTHREQUIRED "auth_required"
|
||||
|
||||
Config *Config::_instance = new Config();
|
||||
|
||||
Config::Config() {
|
||||
// Default settings
|
||||
AuthRequired = false;
|
||||
Challenge = "";
|
||||
Salt = "";
|
||||
SettingsLoaded = false;
|
||||
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&rng);
|
||||
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
|
||||
//mbedtls_ctr_drbg_set_prediction_resistance(&rng, MBEDTLS_CTR_DRBG_PR_ON);
|
||||
}
|
||||
|
||||
Config::~Config() {
|
||||
mbedtls_ctr_drbg_free(&rng);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
}
|
||||
|
||||
const char* Config::GenerateSalt(mbedtls_ctr_drbg_context *rng) {
|
||||
// Generate 32 random chars
|
||||
unsigned char *random_chars = (unsigned char *)bzalloc(32);
|
||||
mbedtls_ctr_drbg_random(rng, random_chars, 32);
|
||||
|
||||
// Convert the 32 random chars to a base64 string
|
||||
unsigned char *salt = (unsigned char*)bzalloc(64);
|
||||
size_t salt_bytes;
|
||||
mbedtls_base64_encode(salt, 64, &salt_bytes, random_chars, 32);
|
||||
salt[salt_bytes] = 0; // Null-terminate the string
|
||||
|
||||
bfree(random_chars);
|
||||
return (char *)salt;
|
||||
}
|
||||
|
||||
const char* Config::GenerateChallenge(const char *password, const char *salt) {
|
||||
size_t passwordLength = strlen(password);
|
||||
size_t saltLength = strlen(salt);
|
||||
|
||||
// Concatenate the password and the salt
|
||||
unsigned char *passAndSalt = (unsigned char*)bzalloc(passwordLength + saltLength);
|
||||
memcpy(passAndSalt, password, passwordLength);
|
||||
memcpy(passAndSalt + passwordLength, salt, saltLength);
|
||||
passAndSalt[passwordLength + saltLength] = 0; // Null-terminate the string
|
||||
|
||||
// Generate a SHA256 hash of the password
|
||||
unsigned char *challengeHash = (unsigned char *)bzalloc(32);
|
||||
mbedtls_sha256(passAndSalt, passwordLength + saltLength, challengeHash, 0);
|
||||
|
||||
// Encode SHA256 hash to Base64
|
||||
unsigned char *challenge = (unsigned char*)bzalloc(64);
|
||||
size_t challenge_bytes = 0;
|
||||
mbedtls_base64_encode(challenge, 64, &challenge_bytes, challengeHash, 32);
|
||||
challenge[64] = 0; // Null-terminate the string
|
||||
|
||||
bfree(passAndSalt);
|
||||
bfree(challengeHash);
|
||||
return (char*)challenge;
|
||||
}
|
||||
|
||||
void Config::SetPassword(const char *password) {
|
||||
const char *new_salt = GenerateSalt(&rng);
|
||||
const char *new_challenge = GenerateChallenge(password, new_salt);
|
||||
|
||||
this->Salt = new_salt;
|
||||
this->Challenge = new_challenge;
|
||||
}
|
||||
|
||||
bool Config::CheckAuth(const char *response) {
|
||||
size_t challengeLength = strlen(this->Challenge);
|
||||
size_t responseLength = strlen(response);
|
||||
|
||||
// Concatenate challenge and auth response
|
||||
char *challengeAndResponse = (char*)bzalloc(challengeLength + responseLength);
|
||||
memcpy(challengeAndResponse, this->Challenge, challengeLength);
|
||||
memcpy(challengeAndResponse + challengeLength, response, responseLength);
|
||||
challengeAndResponse[challengeLength + responseLength] = 0; // Null-terminate the string
|
||||
|
||||
// Generate a SHA256 hash of challengeAndResponse
|
||||
unsigned char *hash = (unsigned char*)bzalloc(32);
|
||||
mbedtls_sha256((unsigned char*)challengeAndResponse, challengeLength + responseLength, hash, 0);
|
||||
|
||||
// Encode the SHA256 hash to Base64
|
||||
unsigned char *expected_response = (unsigned char*)bzalloc(64);
|
||||
size_t base64_size = 0;
|
||||
mbedtls_base64_encode(expected_response, 64, &base64_size, hash, 32);
|
||||
expected_response[64] = 0; // Null-terminate the string
|
||||
|
||||
return (strcmp((char*)expected_response, response) == 0);
|
||||
}
|
||||
|
||||
void Config::OBSSaveCallback(obs_data_t *save_data, bool saving, void *private_data) {
|
||||
@ -18,28 +104,23 @@ void Config::OBSSaveCallback(obs_data_t *save_data, bool saving, void *private_d
|
||||
|
||||
if (saving) {
|
||||
obs_data_t *settings = obs_data_create();
|
||||
obs_data_set_bool(settings, "auth_required", conf->AuthRequired);
|
||||
obs_data_set_string(settings, "auth_hash", conf->Challenge);
|
||||
obs_data_set_string(settings, "auth_salt", conf->Salt);
|
||||
obs_data_set_bool(settings, CONFIG_PARAM_AUTHREQUIRED, conf->AuthRequired);
|
||||
obs_data_set_string(settings, CONFIG_PARAM_CHALLENGE, conf->Challenge);
|
||||
obs_data_set_string(settings, CONFIG_PARAM_SALT, conf->Salt);
|
||||
|
||||
obs_data_set_obj(save_data, "obs-websocket", settings);
|
||||
obs_data_set_obj(save_data, CONFIG_SECTION_NAME, settings);
|
||||
|
||||
obs_data_release(settings);
|
||||
}
|
||||
else {
|
||||
obs_data_t *settings = obs_data_get_obj(save_data, "obs-websocket");
|
||||
if (!settings) {
|
||||
settings = obs_data_create();
|
||||
obs_data_set_bool(settings, "auth_required", conf->AuthRequired);
|
||||
obs_data_set_string(settings, "auth_hash", conf->Challenge);
|
||||
obs_data_set_string(settings, "auth_salt", conf->Salt);
|
||||
obs_data_t *settings = obs_data_get_obj(save_data, CONFIG_SECTION_NAME);
|
||||
if (settings) {
|
||||
conf->AuthRequired = obs_data_get_bool(settings, CONFIG_PARAM_AUTHREQUIRED);
|
||||
conf->Challenge = obs_data_get_string(settings, CONFIG_PARAM_CHALLENGE);
|
||||
conf->Salt = obs_data_get_string(settings, CONFIG_PARAM_SALT);
|
||||
|
||||
conf->SettingsLoaded = true;
|
||||
}
|
||||
|
||||
conf->AuthRequired = obs_data_get_bool(settings, "auth_required");
|
||||
conf->Challenge = obs_data_get_string(settings, "auth_hash");
|
||||
conf->Salt = obs_data_get_string(settings, "auth_salt");
|
||||
conf->SettingsLoaded = true;
|
||||
|
||||
obs_data_release(settings);
|
||||
}
|
||||
}
|
||||
|
13
Config.h
13
Config.h
@ -2,21 +2,30 @@
|
||||
#define CONFIG_H
|
||||
|
||||
#include <obs-module.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
|
||||
class Config {
|
||||
public:
|
||||
Config();
|
||||
~Config();
|
||||
void SetPassword(const char *password);
|
||||
bool CheckAuth(const char *userChallenge);
|
||||
static const char* GenerateSalt(mbedtls_ctr_drbg_context *rng);
|
||||
static const char* GenerateChallenge(const char *password, const char *salt);
|
||||
static void OBSSaveCallback(obs_data_t *save_data, bool saving, void *);
|
||||
|
||||
bool AuthRequired;
|
||||
const char *Challenge;
|
||||
const char *Salt;
|
||||
bool SettingsLoaded;
|
||||
static void OBSSaveCallback(obs_data_t *save_data, bool saving, void *);
|
||||
|
||||
|
||||
static Config* Current();
|
||||
|
||||
private:
|
||||
static Config *_instance;
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context rng;
|
||||
};
|
||||
|
||||
#endif // CONFIG_H
|
@ -77,6 +77,7 @@ void WSRequestHandler::processTextMessage(QString textMessage) {
|
||||
void WSRequestHandler::socketDisconnected() {
|
||||
blog(LOG_INFO, "[obs-websockets] client %s:%d disconnected", _client->peerAddress().toString().toStdString(), _client->peerPort());
|
||||
|
||||
_authenticated = false;
|
||||
_client->deleteLater();
|
||||
emit disconnected();
|
||||
}
|
||||
@ -147,10 +148,13 @@ void WSRequestHandler::HandleAuthenticate(WSRequestHandler *owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO : Implement auth here
|
||||
|
||||
owner->_authenticated = true;
|
||||
owner->SendOKResponse();
|
||||
if (!(owner->_authenticated) && Config::Current()->CheckAuth(auth)) {
|
||||
owner->_authenticated = true;
|
||||
owner->SendOKResponse();
|
||||
}
|
||||
else {
|
||||
owner->SendErrorResponse("Authentication Failed.");
|
||||
}
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleSetCurrentScene(WSRequestHandler *owner) {
|
||||
|
4
data/locale/en-US.ini
Normal file
4
data/locale/en-US.ini
Normal file
@ -0,0 +1,4 @@
|
||||
Menu.SettingsItem="Websocket server settings"
|
||||
Settings.DialogTitle="obs-websocket"
|
||||
Settings.AuthRequired="Enable authentication"
|
||||
Settings.Password="Password"
|
4
data/locale/fr-FR.ini
Normal file
4
data/locale/fr-FR.ini
Normal file
@ -0,0 +1,4 @@
|
||||
Menu.SettingsItem="Paramètres du serveur Websocket"
|
||||
Settings.DialogTitle="obs-websocket"
|
||||
Settings.AuthRequired="Activer l'authentification"
|
||||
Settings.Password="Mot de passe"
|
70
forms/settings-dialog.cpp
Normal file
70
forms/settings-dialog.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#include <obs-frontend-api.h>
|
||||
#include "settings-dialog.h"
|
||||
#include "ui_settings-dialog.h"
|
||||
#include "Config.h"
|
||||
|
||||
#define CHANGE_ME "changeme"
|
||||
|
||||
SettingsDialog::SettingsDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::SettingsDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->authRequired, &QCheckBox::stateChanged, this, &SettingsDialog::AuthCheckboxChanged);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::FormAccepted);
|
||||
|
||||
AuthCheckboxChanged();
|
||||
}
|
||||
|
||||
void SettingsDialog::showEvent(QShowEvent *event) {
|
||||
ui->authRequired->setChecked(Config::Current()->AuthRequired);
|
||||
ui->password->setText(CHANGE_ME);
|
||||
}
|
||||
|
||||
void SettingsDialog::ToggleShowHide() {
|
||||
if (!isVisible()) {
|
||||
setVisible(true);
|
||||
}
|
||||
else {
|
||||
setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::AuthCheckboxChanged() {
|
||||
if (ui->authRequired->isChecked()) {
|
||||
ui->password->setEnabled(true);
|
||||
}
|
||||
else {
|
||||
ui->password->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::FormAccepted() {
|
||||
if (ui->authRequired->isChecked()) {
|
||||
if (ui->password->text() != CHANGE_ME) {
|
||||
QByteArray pwd = ui->password->text().toLocal8Bit();
|
||||
const char *new_password = pwd;
|
||||
|
||||
blog(LOG_INFO, "new password : %s", new_password);
|
||||
Config::Current()->SetPassword(new_password);
|
||||
}
|
||||
|
||||
if (strcmp(Config::Current()->Challenge, "") != 0) {
|
||||
Config::Current()->AuthRequired = true;
|
||||
}
|
||||
else {
|
||||
Config::Current()->AuthRequired = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Config::Current()->AuthRequired = false;
|
||||
}
|
||||
|
||||
obs_frontend_save();
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
28
forms/settings-dialog.h
Normal file
28
forms/settings-dialog.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef SETTINGSDIALOG_H
|
||||
#define SETTINGSDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class SettingsDialog;
|
||||
}
|
||||
|
||||
class SettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SettingsDialog(QWidget *parent = 0);
|
||||
~SettingsDialog();
|
||||
void showEvent(QShowEvent *event);
|
||||
void ToggleShowHide();
|
||||
|
||||
private Q_SLOTS:
|
||||
void AuthCheckboxChanged();
|
||||
void FormAccepted();
|
||||
|
||||
private:
|
||||
Ui::SettingsDialog *ui;
|
||||
};
|
||||
|
||||
#endif // SETTINGSDIALOG_H
|
101
forms/settings-dialog.ui
Normal file
101
forms/settings-dialog.ui
Normal file
@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SettingsDialog</class>
|
||||
<widget class="QDialog" name="SettingsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>354</width>
|
||||
<height>110</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Settings.DialogTitle</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="lbl_password">
|
||||
<property name="text">
|
||||
<string>Settings.Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="authRequired">
|
||||
<property name="text">
|
||||
<string>Settings.AuthRequired</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SettingsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SettingsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -1,26 +1,40 @@
|
||||
#include <obs-module.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QAction>
|
||||
|
||||
#include "obs-websocket.h"
|
||||
#include "WSEvents.h"
|
||||
#include "WSServer.h"
|
||||
#include "Config.h"
|
||||
#include "forms/settings-dialog.h"
|
||||
|
||||
OBS_DECLARE_MODULE()
|
||||
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
||||
|
||||
WSEvents *eventHandler;
|
||||
WSServer *server;
|
||||
SettingsDialog *settings_dialog;
|
||||
|
||||
bool obs_module_load(void)
|
||||
{
|
||||
blog(LOG_INFO, "[obs-websockets] you can haz websockets (version %.2f)", OBS_WEBSOCKET_VERSION);
|
||||
blog(LOG_INFO, "[obs-websockets] you can haz websockets (version %.1f)", OBS_WEBSOCKET_VERSION);
|
||||
|
||||
server = new WSServer(4444);
|
||||
eventHandler = new WSEvents(server);
|
||||
|
||||
obs_frontend_add_save_callback(Config::OBSSaveCallback, Config::Current());
|
||||
|
||||
QAction *menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(obs_module_text("Menu.SettingsItem"));
|
||||
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
settings_dialog = new SettingsDialog();
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
auto menu_cb = [] {
|
||||
settings_dialog->ToggleShowHide();
|
||||
};
|
||||
menu_action->connect(menu_action, &QAction::triggered, menu_cb);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user