Feature: filter device visibility by allowed groups for web user
This commit is contained in:
@@ -997,10 +997,14 @@ inline std::string GetWebPageHTML() {
|
|||||||
<h4>Create New User</h4>
|
<h4>Create New User</h4>
|
||||||
<input type="text" id="new-username" placeholder="Username" autocomplete="off">
|
<input type="text" id="new-username" placeholder="Username" autocomplete="off">
|
||||||
<input type="password" id="new-password" placeholder="Password" autocomplete="new-password">
|
<input type="password" id="new-password" placeholder="Password" autocomplete="new-password">
|
||||||
<select id="new-role">
|
<select id="new-role" onchange="onRoleChange()">
|
||||||
<option value="viewer">Viewer (read-only)</option>
|
<option value="viewer">Viewer (read-only)</option>
|
||||||
<option value="admin">Admin (full access)</option>
|
<option value="admin">Admin (full access)</option>
|
||||||
</select>
|
</select>
|
||||||
|
<div class="groups-section" id="groups-section">
|
||||||
|
<label style="font-size:13px;color:#aaa;display:block;margin:8px 0 4px;">Allowed Groups:</label>
|
||||||
|
<div id="groups-checkboxes" style="max-height:120px;overflow-y:auto;background:rgba(0,0,0,0.2);border-radius:6px;padding:6px 8px;"></div>
|
||||||
|
</div>
|
||||||
<button onclick="createUser()">Create User</button>
|
<button onclick="createUser()">Create User</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-list">
|
<div class="user-list">
|
||||||
@@ -1286,6 +1290,11 @@ inline std::string GetWebPageHTML() {
|
|||||||
renderUsersList(msg.users);
|
renderUsersList(msg.users);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'groups':
|
||||||
|
if (msg.ok) {
|
||||||
|
renderGroupsCheckboxes(msg.groups);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)HTML";
|
)HTML";
|
||||||
@@ -1661,7 +1670,35 @@ inline std::string GetWebPageHTML() {
|
|||||||
function openUsersModal() {
|
function openUsersModal() {
|
||||||
document.getElementById('users-modal').classList.add('active');
|
document.getElementById('users-modal').classList.add('active');
|
||||||
document.getElementById('user-msg').innerHTML = '';
|
document.getElementById('user-msg').innerHTML = '';
|
||||||
|
document.getElementById('new-role').value = 'viewer'; // Reset to default
|
||||||
|
onRoleChange(); // Update groups section visibility
|
||||||
listUsers();
|
listUsers();
|
||||||
|
getGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroups() {
|
||||||
|
if (ws && ws.readyState === WebSocket.OPEN && token) {
|
||||||
|
ws.send(JSON.stringify({ cmd: 'get_groups', token }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGroupsCheckboxes(groups) {
|
||||||
|
const container = document.getElementById('groups-checkboxes');
|
||||||
|
if (!groups || groups.length === 0) {
|
||||||
|
container.innerHTML = '<span style="color:#666;font-size:12px;">No groups available</span>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
container.innerHTML = groups.map(g =>
|
||||||
|
'<label style="display:flex;align-items:center;padding:3px 0;cursor:pointer;white-space:nowrap;">' +
|
||||||
|
'<input type="checkbox" value="' + escapeHtml(g) + '" style="margin:0 6px 0 0;flex-shrink:0;width:14px;height:14px;">' +
|
||||||
|
escapeHtml(g) + '</label>'
|
||||||
|
).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRoleChange() {
|
||||||
|
const role = document.getElementById('new-role').value;
|
||||||
|
const groupsSection = document.getElementById('groups-section');
|
||||||
|
groupsSection.style.display = (role === 'admin') ? 'none' : 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeUsersModal() {
|
function closeUsersModal() {
|
||||||
@@ -1685,8 +1722,12 @@ inline std::string GetWebPageHTML() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect selected groups
|
||||||
|
const checkboxes = document.querySelectorAll('#groups-checkboxes input[type="checkbox"]:checked');
|
||||||
|
const allowed_groups = Array.from(checkboxes).map(cb => cb.value);
|
||||||
|
|
||||||
if (ws && ws.readyState === WebSocket.OPEN && token) {
|
if (ws && ws.readyState === WebSocket.OPEN && token) {
|
||||||
ws.send(JSON.stringify({ cmd: 'create_user', token, username, password, role }));
|
ws.send(JSON.stringify({ cmd: 'create_user', token, username, password, role, allowed_groups }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1712,10 +1753,14 @@ inline std::string GetWebPageHTML() {
|
|||||||
container.innerHTML = users.map(u => {
|
container.innerHTML = users.map(u => {
|
||||||
const isAdmin = u.role === 'admin';
|
const isAdmin = u.role === 'admin';
|
||||||
const canDelete = u.username !== 'admin'; // Cannot delete built-in admin
|
const canDelete = u.username !== 'admin'; // Cannot delete built-in admin
|
||||||
|
const groups = u.allowed_groups || [];
|
||||||
|
const groupsText = u.username === 'admin' ? '(all)' :
|
||||||
|
(groups.length > 0 ? groups.join(', ') : '(none)');
|
||||||
return '<div class="user-item">' +
|
return '<div class="user-item">' +
|
||||||
'<div class="user-info">' +
|
'<div class="user-info">' +
|
||||||
'<div class="username">' + escapeHtml(u.username) + '</div>' +
|
'<div class="username">' + escapeHtml(u.username) + '</div>' +
|
||||||
'<div class="role ' + (isAdmin ? 'admin' : '') + '">' + u.role + '</div>' +
|
'<div class="role ' + (isAdmin ? 'admin' : '') + '">' + u.role + '</div>' +
|
||||||
|
'<div class="groups" style="font-size:11px;color:#888;margin-top:2px;">Groups: ' + escapeHtml(groupsText) + '</div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
(canDelete ? '<button class="delete-btn" onclick="deleteUser(\'' + escapeHtml(u.username) + '\')">Delete</button>' : '') +
|
(canDelete ? '<button class="delete-btn" onclick="deleteUser(\'' + escapeHtml(u.username) + '\')">Delete</button>' : '') +
|
||||||
'</div>';
|
'</div>';
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <shlobj.h>
|
#include <shlobj.h>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
// Algorithm constants (same as ScreenSpyDlg.cpp)
|
// Algorithm constants (same as ScreenSpyDlg.cpp)
|
||||||
#define ALGORITHM_H264 2
|
#define ALGORITHM_H264 2
|
||||||
@@ -363,6 +364,8 @@ void CWebService::ServerThread(int port) {
|
|||||||
HandleDeleteUser(ws_ptr, msg);
|
HandleDeleteUser(ws_ptr, msg);
|
||||||
} else if (cmd == "list_users") {
|
} else if (cmd == "list_users") {
|
||||||
HandleListUsers(ws_ptr, token);
|
HandleListUsers(ws_ptr, token);
|
||||||
|
} else if (cmd == "get_groups") {
|
||||||
|
HandleGetGroups(ws_ptr, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -566,7 +569,7 @@ void CWebService::HandleGetDevices(void* ws_ptr, const std::string& token) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SendText(ws_ptr, BuildDeviceListJson());
|
SendText(ws_ptr, BuildDeviceListJson(username));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWebService::HandleConnect(void* ws_ptr, const std::string& token, uint64_t device_id) {
|
void CWebService::HandleConnect(void* ws_ptr, const std::string& token, uint64_t device_id) {
|
||||||
@@ -588,6 +591,32 @@ void CWebService::HandleConnect(void* ws_ptr, const std::string& token, uint64_t
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check group permission (admin can access all devices)
|
||||||
|
if (username != "admin") {
|
||||||
|
std::string deviceGroup = ctx->GetGroupName();
|
||||||
|
if (deviceGroup.empty()) deviceGroup = "default";
|
||||||
|
|
||||||
|
bool hasAccess = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_UsersMutex);
|
||||||
|
for (const auto& u : m_Users) {
|
||||||
|
if (u.username == username) {
|
||||||
|
for (const auto& g : u.allowed_groups) {
|
||||||
|
if (g == deviceGroup) {
|
||||||
|
hasAccess = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasAccess) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("connect_result", false, "Permission denied"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check max clients per device
|
// Check max clients per device
|
||||||
int current_count = GetWebClientCount(device_id);
|
int current_count = GetWebClientCount(device_id);
|
||||||
if (current_count >= m_nMaxClientsPerDevice) {
|
if (current_count >= m_nMaxClientsPerDevice) {
|
||||||
@@ -954,12 +983,23 @@ void CWebService::HandleCreateUser(void* ws_ptr, const std::string& msg) {
|
|||||||
std::string newPassword = root.get("password", "").asString();
|
std::string newPassword = root.get("password", "").asString();
|
||||||
std::string newRole = root.get("role", "viewer").asString();
|
std::string newRole = root.get("role", "viewer").asString();
|
||||||
|
|
||||||
|
// Parse allowed_groups array
|
||||||
|
std::vector<std::string> allowedGroups;
|
||||||
|
const Json::Value& groups = root["allowed_groups"];
|
||||||
|
if (groups.isArray()) {
|
||||||
|
for (const auto& g : groups) {
|
||||||
|
if (g.isString() && !g.asString().empty()) {
|
||||||
|
allowedGroups.push_back(g.asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (newUsername.empty() || newPassword.empty()) {
|
if (newUsername.empty() || newPassword.empty()) {
|
||||||
SendText(ws_ptr, BuildJsonResponse("create_user_result", false, "Username and password required"));
|
SendText(ws_ptr, BuildJsonResponse("create_user_result", false, "Username and password required"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CreateUser(newUsername, newPassword, newRole)) {
|
if (CreateUser(newUsername, newPassword, newRole, allowedGroups)) {
|
||||||
SendText(ws_ptr, BuildJsonResponse("create_user_result", true));
|
SendText(ws_ptr, BuildJsonResponse("create_user_result", true));
|
||||||
} else {
|
} else {
|
||||||
SendText(ws_ptr, BuildJsonResponse("create_user_result", false, "Failed to create user (may already exist)"));
|
SendText(ws_ptr, BuildJsonResponse("create_user_result", false, "Failed to create user (may already exist)"));
|
||||||
@@ -1009,19 +1049,28 @@ void CWebService::HandleListUsers(void* ws_ptr, const std::string& token) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto users = ListUsers();
|
|
||||||
|
|
||||||
Json::Value res;
|
Json::Value res;
|
||||||
res["cmd"] = "list_users_result";
|
res["cmd"] = "list_users_result";
|
||||||
res["ok"] = true;
|
res["ok"] = true;
|
||||||
|
|
||||||
Json::Value usersArray(Json::arrayValue);
|
Json::Value usersArray(Json::arrayValue);
|
||||||
for (const auto& u : users) {
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_UsersMutex);
|
||||||
|
for (const auto& u : m_Users) {
|
||||||
Json::Value user;
|
Json::Value user;
|
||||||
user["username"] = u.first;
|
user["username"] = u.username;
|
||||||
user["role"] = u.second;
|
user["role"] = u.role;
|
||||||
|
|
||||||
|
// Include allowed_groups
|
||||||
|
Json::Value groups(Json::arrayValue);
|
||||||
|
for (const auto& g : u.allowed_groups) {
|
||||||
|
groups.append(g);
|
||||||
|
}
|
||||||
|
user["allowed_groups"] = groups;
|
||||||
|
|
||||||
usersArray.append(user);
|
usersArray.append(user);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
res["users"] = usersArray;
|
res["users"] = usersArray;
|
||||||
|
|
||||||
Json::StreamWriterBuilder builder;
|
Json::StreamWriterBuilder builder;
|
||||||
@@ -1030,6 +1079,48 @@ void CWebService::HandleListUsers(void* ws_ptr, const std::string& token) {
|
|||||||
SendText(ws_ptr, json);
|
SendText(ws_ptr, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CWebService::HandleGetGroups(void* ws_ptr, const std::string& token) {
|
||||||
|
std::string username, role;
|
||||||
|
if (!ValidateToken(token, username, role)) {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("groups", false, "Invalid token"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only admin can get groups list (for user management)
|
||||||
|
if (role != "admin") {
|
||||||
|
SendText(ws_ptr, BuildJsonResponse("groups", false, "Permission denied"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all unique groups from online devices
|
||||||
|
std::set<std::string> groups;
|
||||||
|
groups.insert("default"); // Always include default group
|
||||||
|
|
||||||
|
if (m_pParentDlg) {
|
||||||
|
EnterCriticalSection(&m_pParentDlg->m_cs);
|
||||||
|
for (context* ctx : m_pParentDlg->m_HostList) {
|
||||||
|
if (!ctx || !ctx->IsLogin()) continue;
|
||||||
|
std::string g = ctx->GetGroupName();
|
||||||
|
groups.insert(g.empty() ? "default" : g);
|
||||||
|
}
|
||||||
|
LeaveCriticalSection(&m_pParentDlg->m_cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build response
|
||||||
|
Json::Value res;
|
||||||
|
res["cmd"] = "groups";
|
||||||
|
res["ok"] = true;
|
||||||
|
res["groups"] = Json::Value(Json::arrayValue);
|
||||||
|
for (const auto& g : groups) {
|
||||||
|
res["groups"].append(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::StreamWriterBuilder builder;
|
||||||
|
builder["indentation"] = "";
|
||||||
|
std::string json = Json::writeString(builder, res);
|
||||||
|
SendText(ws_ptr, json);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// Token Management (delegated to WebServiceAuth module)
|
// Token Management (delegated to WebServiceAuth module)
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
@@ -1170,6 +1261,14 @@ void CWebService::LoadUsers() {
|
|||||||
user.salt = u.get("salt", "").asString();
|
user.salt = u.get("salt", "").asString();
|
||||||
user.role = u.get("role", "viewer").asString();
|
user.role = u.get("role", "viewer").asString();
|
||||||
|
|
||||||
|
// Load allowed_groups
|
||||||
|
const Json::Value& groups = u["allowed_groups"];
|
||||||
|
if (groups.isArray()) {
|
||||||
|
for (const auto& g : groups) {
|
||||||
|
user.allowed_groups.push_back(g.asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!user.password_hash.empty()) {
|
if (!user.password_hash.empty()) {
|
||||||
m_Users.push_back(user);
|
m_Users.push_back(user);
|
||||||
loaded++;
|
loaded++;
|
||||||
@@ -1197,6 +1296,14 @@ void CWebService::SaveUsers() {
|
|||||||
user["password_hash"] = u.password_hash;
|
user["password_hash"] = u.password_hash;
|
||||||
user["salt"] = u.salt;
|
user["salt"] = u.salt;
|
||||||
user["role"] = u.role;
|
user["role"] = u.role;
|
||||||
|
|
||||||
|
// Save allowed_groups
|
||||||
|
Json::Value groups(Json::arrayValue);
|
||||||
|
for (const auto& g : u.allowed_groups) {
|
||||||
|
groups.append(g);
|
||||||
|
}
|
||||||
|
user["allowed_groups"] = groups;
|
||||||
|
|
||||||
users.append(user);
|
users.append(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1217,7 +1324,8 @@ void CWebService::SaveUsers() {
|
|||||||
Mprintf("[WebService] Saved %d users to users.json\n", (int)users.size());
|
Mprintf("[WebService] Saved %d users to users.json\n", (int)users.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWebService::CreateUser(const std::string& username, const std::string& password, const std::string& role) {
|
bool CWebService::CreateUser(const std::string& username, const std::string& password, const std::string& role,
|
||||||
|
const std::vector<std::string>& allowed_groups) {
|
||||||
if (username.empty() || password.empty()) return false;
|
if (username.empty() || password.empty()) return false;
|
||||||
if (username == "admin") return false; // Cannot create user named "admin"
|
if (username == "admin") return false; // Cannot create user named "admin"
|
||||||
if (role != "admin" && role != "viewer") return false;
|
if (role != "admin" && role != "viewer") return false;
|
||||||
@@ -1236,9 +1344,11 @@ bool CWebService::CreateUser(const std::string& username, const std::string& pas
|
|||||||
user.salt = GenerateSalt();
|
user.salt = GenerateSalt();
|
||||||
user.password_hash = WSAuth::ComputeSHA256(password + user.salt);
|
user.password_hash = WSAuth::ComputeSHA256(password + user.salt);
|
||||||
user.role = role;
|
user.role = role;
|
||||||
|
user.allowed_groups = allowed_groups;
|
||||||
|
|
||||||
m_Users.push_back(user);
|
m_Users.push_back(user);
|
||||||
Mprintf("[WebService] Created user: %s (role: %s)\n", username.c_str(), role.c_str());
|
Mprintf("[WebService] Created user: %s (role: %s, groups: %d)\n",
|
||||||
|
username.c_str(), role.c_str(), (int)allowed_groups.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to file (outside lock scope since SaveUsers acquires its own lock)
|
// Save to file (outside lock scope since SaveUsers acquires its own lock)
|
||||||
@@ -1295,17 +1405,47 @@ std::string CWebService::BuildJsonResponse(const std::string& cmd, bool ok, cons
|
|||||||
return Json::writeString(builder, res);
|
return Json::writeString(builder, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string CWebService::BuildDeviceListJson() {
|
std::string CWebService::BuildDeviceListJson(const std::string& username) {
|
||||||
Json::Value res;
|
Json::Value res;
|
||||||
res["cmd"] = "device_list";
|
res["cmd"] = "device_list";
|
||||||
res["devices"] = Json::Value(Json::arrayValue);
|
res["devices"] = Json::Value(Json::arrayValue);
|
||||||
|
|
||||||
|
// Get user's allowed groups for filtering (skip for admin or empty username)
|
||||||
|
std::vector<std::string> allowedGroups;
|
||||||
|
bool filterByGroup = false;
|
||||||
|
if (!username.empty() && username != "admin") {
|
||||||
|
std::lock_guard<std::mutex> lock(m_UsersMutex);
|
||||||
|
for (const auto& u : m_Users) {
|
||||||
|
if (u.username == username) {
|
||||||
|
allowedGroups = u.allowed_groups;
|
||||||
|
filterByGroup = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (m_pParentDlg) {
|
if (m_pParentDlg) {
|
||||||
// Access device list with lock
|
// Access device list with lock
|
||||||
EnterCriticalSection(&m_pParentDlg->m_cs);
|
EnterCriticalSection(&m_pParentDlg->m_cs);
|
||||||
for (context* ctx : m_pParentDlg->m_HostList) {
|
for (context* ctx : m_pParentDlg->m_HostList) {
|
||||||
if (!ctx || !ctx->IsLogin()) continue;
|
if (!ctx || !ctx->IsLogin()) continue;
|
||||||
|
|
||||||
|
// Get device group (empty = "default")
|
||||||
|
std::string deviceGroup = ctx->GetGroupName();
|
||||||
|
if (deviceGroup.empty()) deviceGroup = "default";
|
||||||
|
|
||||||
|
// Filter by allowed groups if user is not admin
|
||||||
|
if (filterByGroup) {
|
||||||
|
bool allowed = false;
|
||||||
|
for (const auto& g : allowedGroups) {
|
||||||
|
if (g == deviceGroup) {
|
||||||
|
allowed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!allowed) continue; // Skip device not in allowed groups
|
||||||
|
}
|
||||||
|
|
||||||
Json::Value device;
|
Json::Value device;
|
||||||
// Use string for ID to avoid JavaScript number precision loss
|
// Use string for ID to avoid JavaScript number precision loss
|
||||||
device["id"] = std::to_string(ctx->GetClientID());
|
device["id"] = std::to_string(ctx->GetClientID());
|
||||||
@@ -1332,6 +1472,9 @@ std::string CWebService::BuildDeviceListJson() {
|
|||||||
device["activeWindow"] = AnsiToUtf8(activeWindow);
|
device["activeWindow"] = AnsiToUtf8(activeWindow);
|
||||||
device["online"] = true;
|
device["online"] = true;
|
||||||
|
|
||||||
|
// Add device group to response
|
||||||
|
device["group"] = deviceGroup;
|
||||||
|
|
||||||
// Get screen info from client's reported resolution
|
// Get screen info from client's reported resolution
|
||||||
// Format: "n:MxN" where n=monitor count, M=width, N=height
|
// Format: "n:MxN" where n=monitor count, M=width, N=height
|
||||||
CString resolution = ctx->GetAdditionalData(RES_RESOLUTION);
|
CString resolution = ctx->GetAdditionalData(RES_RESOLUTION);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ struct WebUser {
|
|||||||
std::string password_hash; // SHA256(password + salt)
|
std::string password_hash; // SHA256(password + salt)
|
||||||
std::string salt;
|
std::string salt;
|
||||||
std::string role; // "admin" | "viewer"
|
std::string role; // "admin" | "viewer"
|
||||||
|
std::vector<std::string> allowed_groups; // Groups this user can view (empty = no access, admin = all)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Device info for web clients
|
// Device info for web clients
|
||||||
@@ -79,7 +80,8 @@ public:
|
|||||||
void SetAdminPassword(const std::string& password);
|
void SetAdminPassword(const std::string& password);
|
||||||
|
|
||||||
// User management
|
// User management
|
||||||
bool CreateUser(const std::string& username, const std::string& password, const std::string& role);
|
bool CreateUser(const std::string& username, const std::string& password, const std::string& role,
|
||||||
|
const std::vector<std::string>& allowed_groups = {});
|
||||||
bool DeleteUser(const std::string& username);
|
bool DeleteUser(const std::string& username);
|
||||||
std::vector<std::pair<std::string, std::string>> ListUsers(); // Returns [(username, role), ...]
|
std::vector<std::pair<std::string, std::string>> ListUsers(); // Returns [(username, role), ...]
|
||||||
|
|
||||||
@@ -144,7 +146,7 @@ private:
|
|||||||
|
|
||||||
// JSON helpers
|
// JSON helpers
|
||||||
std::string BuildJsonResponse(const std::string& cmd, bool ok, const std::string& msg = "");
|
std::string BuildJsonResponse(const std::string& cmd, bool ok, const std::string& msg = "");
|
||||||
std::string BuildDeviceListJson();
|
std::string BuildDeviceListJson(const std::string& username = "");
|
||||||
|
|
||||||
// Password verification
|
// Password verification
|
||||||
bool VerifyPassword(const std::string& input, const WebUser& user);
|
bool VerifyPassword(const std::string& input, const WebUser& user);
|
||||||
@@ -157,6 +159,7 @@ private:
|
|||||||
void HandleCreateUser(void* ws_ptr, const std::string& msg);
|
void HandleCreateUser(void* ws_ptr, const std::string& msg);
|
||||||
void HandleDeleteUser(void* ws_ptr, const std::string& msg);
|
void HandleDeleteUser(void* ws_ptr, const std::string& msg);
|
||||||
void HandleListUsers(void* ws_ptr, const std::string& token);
|
void HandleListUsers(void* ws_ptr, const std::string& token);
|
||||||
|
void HandleGetGroups(void* ws_ptr, const std::string& token);
|
||||||
|
|
||||||
// Send to WebSocket
|
// Send to WebSocket
|
||||||
void SendText(void* ws_ptr, const std::string& text);
|
void SendText(void* ws_ptr, const std::string& text);
|
||||||
|
|||||||
Reference in New Issue
Block a user