Add user management
This commit is contained in:
parent
7b408be5fa
commit
367562ea96
@ -52,6 +52,7 @@ public:
|
||||
EvDashErrorNoError = 0,
|
||||
EvDashErrorBackendError,
|
||||
EvDashErrorDuplicateUser,
|
||||
EvDashErrorUserNotFound,
|
||||
EvDashErrorBadPassword
|
||||
};
|
||||
Q_ENUM(EvDashError)
|
||||
|
||||
@ -30,18 +30,22 @@
|
||||
|
||||
#include "evdashjsonhandler.h"
|
||||
#include "evdashengine.h"
|
||||
#include "evdashwebserverresource.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcEvDashExperience)
|
||||
|
||||
EvDashJsonHandler::EvDashJsonHandler(EvDashEngine *engine, QObject *parent):
|
||||
EvDashJsonHandler::EvDashJsonHandler(EvDashEngine *engine, EvDashWebServerResource *resource, QObject *parent):
|
||||
JsonHandler{parent},
|
||||
m_engine{engine}
|
||||
m_engine{engine},
|
||||
m_resource{resource}
|
||||
{
|
||||
registerEnum<EvDashEngine::EvDashError>();
|
||||
|
||||
QVariantMap params, returns;
|
||||
QString description;
|
||||
QVariantMap params, returns;
|
||||
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get the enabled status of EV Dash service.";
|
||||
@ -54,16 +58,52 @@ EvDashJsonHandler::EvDashJsonHandler(EvDashEngine *engine, QObject *parent):
|
||||
returns.insert("evDashError", enumRef<EvDashEngine::EvDashError>());
|
||||
registerMethod("SetEnabled", description, params, returns);
|
||||
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Get the list of available users names.";
|
||||
returns.insert("usernames", enumValueName(StringList));
|
||||
registerMethod("GetUsers", description, params, returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Add a new user with the given username and password.";
|
||||
params.insert("username", enumValueName(String));
|
||||
params.insert("password", enumValueName(String));
|
||||
returns.insert("evDashError", enumRef<EvDashEngine::EvDashError>());
|
||||
registerMethod("AddUser", description, params, returns);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Remove the user with the given username.";
|
||||
params.insert("username", enumValueName(String));
|
||||
returns.insert("evDashError", enumRef<EvDashEngine::EvDashError>());
|
||||
registerMethod("RemoveUser", description, params, returns);
|
||||
|
||||
// Notifications
|
||||
params.clear();
|
||||
description = "Emitted whenever the EV Dash service has been enabled or disabled.";
|
||||
params.insert("enabled", enumValueName(Bool));
|
||||
registerNotification("EnabledChanged", description, params);
|
||||
|
||||
connect(m_engine, &EvDashEngine::enabledChanged, this, [=](bool enabled){
|
||||
connect(m_engine, &EvDashEngine::enabledChanged, this, [this](bool enabled){
|
||||
emit EnabledChanged({{"enabled", enabled}});
|
||||
});
|
||||
|
||||
params.clear();
|
||||
description = "Emitted whenever a new username has been added for the dashboard.";
|
||||
params.insert("username", enumValueName(String));
|
||||
registerNotification("UserAdded", description, params);
|
||||
|
||||
connect(m_resource, &EvDashWebServerResource::userAdded, this, [this](const QString &username){
|
||||
emit UserAdded({{"username", username}});
|
||||
});
|
||||
|
||||
params.clear();
|
||||
description = "Emitted whenever a username has been removed from the dashboard.";
|
||||
params.insert("username", enumValueName(String));
|
||||
registerNotification("UserRemoved", description, params);
|
||||
|
||||
connect(m_resource, &EvDashWebServerResource::userRemoved, this, [this](const QString &username){
|
||||
emit UserRemoved({{"username", username}});
|
||||
});
|
||||
}
|
||||
|
||||
QString EvDashJsonHandler::name() const
|
||||
@ -93,3 +133,36 @@ JsonReply *EvDashJsonHandler::SetEnabled(const QVariantMap ¶ms)
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *EvDashJsonHandler::GetUsers(const QVariantMap ¶ms)
|
||||
{
|
||||
Q_UNUSED(params)
|
||||
|
||||
QVariantMap returns;
|
||||
returns.insert("usernames", m_resource->usernames());
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *EvDashJsonHandler::AddUser(const QVariantMap ¶ms)
|
||||
{
|
||||
QString username = params.value("username").toString();
|
||||
QString password = params.value("password").toString();
|
||||
|
||||
EvDashEngine::EvDashError error = m_resource->addUser(username, password);
|
||||
|
||||
QVariantMap returns;
|
||||
returns.insert("evDashError", enumValueName(error));
|
||||
return createReply(returns);
|
||||
}
|
||||
|
||||
JsonReply *EvDashJsonHandler::RemoveUser(const QVariantMap ¶ms)
|
||||
{
|
||||
QString username = params.value("username").toString();
|
||||
|
||||
EvDashEngine::EvDashError error = m_resource->removeUser(username);
|
||||
|
||||
QVariantMap returns;
|
||||
returns.insert("evDashError", enumValueName(error));
|
||||
return createReply(returns);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -36,23 +36,32 @@
|
||||
#include <jsonrpc/jsonhandler.h>
|
||||
|
||||
class EvDashEngine;
|
||||
class EvDashWebServerResource;
|
||||
|
||||
class EvDashJsonHandler : public JsonHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EvDashJsonHandler(EvDashEngine *engine, QObject *parent = nullptr);
|
||||
explicit EvDashJsonHandler(EvDashEngine *engine, EvDashWebServerResource *resource, QObject *parent = nullptr);
|
||||
|
||||
QString name() const override;
|
||||
|
||||
Q_INVOKABLE JsonReply *GetEnabled(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *SetEnabled(const QVariantMap ¶ms);
|
||||
|
||||
Q_INVOKABLE JsonReply *GetUsers(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *AddUser(const QVariantMap ¶ms);
|
||||
Q_INVOKABLE JsonReply *RemoveUser(const QVariantMap ¶ms);
|
||||
|
||||
signals:
|
||||
void EnabledChanged(const QVariantMap ¶ms);
|
||||
|
||||
void UserAdded(const QVariantMap ¶ms);
|
||||
void UserRemoved(const QVariantMap ¶ms);
|
||||
|
||||
private:
|
||||
EvDashEngine *m_engine = nullptr;
|
||||
EvDashWebServerResource *m_resource = nullptr;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -1,10 +1,42 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2025, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "evdashwebserverresource.h"
|
||||
#include "evdashsettings.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QUuid>
|
||||
#include <QCryptographicHash>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcEvDashExperience)
|
||||
@ -12,7 +44,25 @@ Q_DECLARE_LOGGING_CATEGORY(dcEvDashExperience)
|
||||
EvDashWebServerResource::EvDashWebServerResource(QObject *parent)
|
||||
: WebServerResource{"/evdash", parent}
|
||||
{
|
||||
// Load users
|
||||
EvDashSettings settings;
|
||||
settings.beginGroup("Users");
|
||||
|
||||
foreach (const QString &username, settings.childGroups()) {
|
||||
UserInfo info;
|
||||
|
||||
settings.beginGroup(username);
|
||||
info.username = username;
|
||||
info.passwordHash = settings.value("hash").toString().toUtf8();
|
||||
info.passwordSalt = settings.value("salt").toString().toUtf8();
|
||||
settings.endGroup(); // username
|
||||
|
||||
m_users.insert(username, info);
|
||||
}
|
||||
|
||||
settings.endGroup(); // Users
|
||||
|
||||
qCInfo(dcEvDashExperience()) << "Loaded" << m_users.count() << "users for the dashboard.";
|
||||
}
|
||||
|
||||
HttpReply *EvDashWebServerResource::processRequest(const HttpRequest &request)
|
||||
@ -52,6 +102,72 @@ HttpReply *EvDashWebServerResource::processRequest(const HttpRequest &request)
|
||||
return redirectToIndex();
|
||||
}
|
||||
|
||||
QStringList EvDashWebServerResource::usernames() const
|
||||
{
|
||||
return m_users.keys();
|
||||
}
|
||||
|
||||
EvDashEngine::EvDashError EvDashWebServerResource::addUser(const QString &username, const QString &password)
|
||||
{
|
||||
if (m_users.keys().contains(username)) {
|
||||
qCWarning(dcEvDashExperience()) << "Cannot add new user. There is already a user with the username" << username;
|
||||
return EvDashEngine::EvDashErrorDuplicateUser;
|
||||
}
|
||||
|
||||
if (password.size() < s_minimalPasswordLength) {
|
||||
qCWarning(dcEvDashExperience()) << "Cannot add new user. The given password is to short. The minimum size is" << s_minimalPasswordLength;
|
||||
return EvDashEngine::EvDashErrorBadPassword;
|
||||
}
|
||||
|
||||
UserInfo info;
|
||||
info.username = username;
|
||||
info.passwordSalt = QUuid::createUuid().toString().remove(QRegularExpression("[{}]")).toUtf8();
|
||||
info.passwordHash = QCryptographicHash::hash(QString(password + info.passwordSalt).toUtf8(), QCryptographicHash::Sha3_512).toBase64();
|
||||
|
||||
EvDashSettings settings;
|
||||
settings.beginGroup("Users");
|
||||
settings.beginGroup(username);
|
||||
settings.setValue("hash", QString::fromUtf8(info.passwordHash));
|
||||
settings.setValue("salt", QString::fromUtf8(info.passwordSalt));
|
||||
settings.endGroup(); // username
|
||||
settings.endGroup(); // Users
|
||||
|
||||
qCDebug(dcEvDashExperience()) << "Added successfully new user with username" << username;
|
||||
|
||||
m_users.insert(username, info);
|
||||
emit userAdded(username);
|
||||
|
||||
return EvDashEngine::EvDashErrorNoError;
|
||||
}
|
||||
|
||||
EvDashEngine::EvDashError EvDashWebServerResource::removeUser(const QString &username)
|
||||
{
|
||||
if (!m_users.contains(username)) {
|
||||
qCWarning(dcEvDashExperience()) << "Cannot remove user with username" << username << "because there is no such user.";
|
||||
return EvDashEngine::EvDashErrorUserNotFound;
|
||||
}
|
||||
|
||||
m_users.remove(username);
|
||||
|
||||
foreach (const QString &token, m_activeTokens.keys()) {
|
||||
if (m_activeTokens.value(token).username == username) {
|
||||
qCDebug(dcEvDashExperience()) << "Revoke active token" << token << "for user" << username;
|
||||
m_activeTokens.remove(token);
|
||||
}
|
||||
}
|
||||
|
||||
EvDashSettings settings;
|
||||
settings.beginGroup("Users");
|
||||
settings.remove(username);
|
||||
settings.endGroup(); // Users
|
||||
|
||||
qCDebug(dcEvDashExperience()) << "User with username" << username << "removed successfully";
|
||||
|
||||
emit userRemoved(username);
|
||||
|
||||
return EvDashEngine::EvDashErrorNoError;
|
||||
}
|
||||
|
||||
HttpReply *EvDashWebServerResource::redirectToIndex()
|
||||
{
|
||||
HttpReply *reply = HttpReply::createErrorReply(HttpReply::PermanentRedirect);
|
||||
@ -156,8 +272,12 @@ HttpReply *EvDashWebServerResource::handleRefreshRequest(const HttpRequest &requ
|
||||
|
||||
bool EvDashWebServerResource::verifyCredentials(const QString &username, const QString &password) const
|
||||
{
|
||||
Q_UNUSED(username)
|
||||
Q_UNUSED(password)
|
||||
const UserInfo info = m_users.value(username);
|
||||
if (info.passwordHash != QCryptographicHash::hash(QString(password + info.passwordSalt).toUtf8(), QCryptographicHash::Sha3_512).toBase64()) {
|
||||
qCWarning(dcEvDashExperience()) << "Authentication error for user:" << username;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,33 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2025, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef EVDASHWEBSERVERRESOURCE_H
|
||||
#define EVDASHWEBSERVERRESOURCE_H
|
||||
|
||||
@ -8,6 +38,8 @@
|
||||
|
||||
#include <webserver/webserverresource.h>
|
||||
|
||||
#include "evdashengine.h"
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
class EvDashWebServerResource : public WebServerResource
|
||||
@ -18,26 +50,48 @@ public:
|
||||
|
||||
HttpReply *processRequest(const HttpRequest &request) override;
|
||||
|
||||
// User management
|
||||
QStringList usernames() const;
|
||||
|
||||
EvDashEngine::EvDashError addUser(const QString &username, const QString &password);
|
||||
EvDashEngine::EvDashError removeUser(const QString &username);
|
||||
|
||||
bool validateToken(const QString &token);
|
||||
|
||||
signals:
|
||||
void userAdded(const QString &username);
|
||||
void userRemoved(const QString &username);
|
||||
|
||||
private:
|
||||
struct TokenInfo {
|
||||
QString username;
|
||||
QDateTime expiresAt;
|
||||
};
|
||||
|
||||
struct UserInfo {
|
||||
QString username;
|
||||
QByteArray passwordHash;
|
||||
QByteArray passwordSalt;
|
||||
};
|
||||
|
||||
static constexpr int s_tokenLifetimeSeconds = 3600;
|
||||
static constexpr int s_minimalPasswordLength = 4;
|
||||
|
||||
QHash<QString, UserInfo> m_users;
|
||||
QHash<QString, TokenInfo> m_activeTokens;
|
||||
|
||||
HttpReply *handleLoginRequest(const HttpRequest &request);
|
||||
HttpReply *handleRefreshRequest(const HttpRequest &request);
|
||||
HttpReply *redirectToIndex();
|
||||
|
||||
bool verifyStaticFile(const QString &fileName);
|
||||
bool verifyCredentials(const QString &username, const QString &password) const;
|
||||
QString issueToken(const QString &username);
|
||||
void purgeExpiredTokens();
|
||||
|
||||
static constexpr int s_tokenLifetimeSeconds = 3600;
|
||||
bool verifyStaticFile(const QString &fileName);
|
||||
|
||||
bool verifyCredentials(const QString &username, const QString &password) const;
|
||||
|
||||
|
||||
QHash<QString, TokenInfo> m_activeTokens;
|
||||
};
|
||||
|
||||
#endif // EVDASHWEBSERVERRESOURCE_H
|
||||
|
||||
@ -49,13 +49,13 @@ void ExperiencePluginEvDash::init()
|
||||
{
|
||||
qCDebug(dcEvDashExperience()) << "Initializing experience...";
|
||||
|
||||
m_webServerResource = new EvDashWebServerResource(this);
|
||||
m_engine = new EvDashEngine(thingManager(), m_webServerResource, this);
|
||||
m_resource = new EvDashWebServerResource(this);
|
||||
m_engine = new EvDashEngine(thingManager(), m_resource, this);
|
||||
|
||||
jsonRpcServer()->registerExperienceHandler(new EvDashJsonHandler(m_engine, this), 1, 0);
|
||||
jsonRpcServer()->registerExperienceHandler(new EvDashJsonHandler(m_engine, m_resource, this), 1, 0);
|
||||
}
|
||||
|
||||
WebServerResource *ExperiencePluginEvDash::webServerResource() const
|
||||
{
|
||||
return m_webServerResource;
|
||||
return m_resource;
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ public:
|
||||
|
||||
private:
|
||||
EvDashEngine *m_engine = nullptr;
|
||||
EvDashWebServerResource *m_webServerResource = nullptr;
|
||||
EvDashWebServerResource *m_resource = nullptr;
|
||||
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user