#include "memberpropsdialog.h" #include "../db/db_manager.h" #include #include #include #include #include #include #include "../../../lib/platform_compat.h" MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId, quint64 nodeId, const QString& memberName, QWidget* parent) : QDialog(parent) , m_db(db) , m_channelId(channelId) , m_nodeId(nodeId) { setWindowTitle("Member Properties"); setMinimumWidth(340); setModal(true); auto chs = m_db->getChannels(); for (const auto& ch : chs) { if (ch.channelId == channelId) { m_edPrivkey = ch.ed25519_privkey; break; } } bool haveKey = (m_edPrivkey.size() == 32); bool isOwn = (m_nodeId == m_db->myNodeId()); bool isAdmin = false, isSuper = false; QString curNick; if (m_db->m_db) { QString tbl = m_db->peersTableName(channelId); QByteArray sql = QString( "SELECT adm_tags, local_nick FROM \"%1\" WHERE node_id=?" ).arg(tbl).toUtf8(); sqlite3_stmt* st = nullptr; if (sqlite3_prepare_v2(m_db->m_db, sql.constData(), -1, &st, nullptr) == SQLITE_OK) { sqlite3_bind_int64(st, 1, (sqlite3_int64)nodeId); if (sqlite3_step(st) == SQLITE_ROW) { const char* tags = (const char*)sqlite3_column_text(st, 0); if (tags) { if (strstr(tags, "admin=yes")) isAdmin = true; if (strstr(tags, "supernode=yes")) isSuper = true; } curNick = DbManager::colText(st, 1); } sqlite3_finalize(st); } QByteArray alt = QString( "ALTER TABLE \"%1\" ADD COLUMN adm_tags TEXT" ).arg(tbl).toUtf8(); sqlite3_exec(m_db->m_db, alt.constData(), nullptr, nullptr, nullptr); alt = QString( "ALTER TABLE \"%1\" ADD COLUMN adm_tags_sig BLOB" ).arg(tbl).toUtf8(); sqlite3_exec(m_db->m_db, alt.constData(), nullptr, nullptr, nullptr); alt = QString( "ALTER TABLE \"%1\" ADD COLUMN local_nick TEXT" ).arg(tbl).toUtf8(); sqlite3_exec(m_db->m_db, alt.constData(), nullptr, nullptr, nullptr); } auto* layout = new QVBoxLayout(this); layout->setSpacing(12); layout->setContentsMargins(20, 16, 20, 16); auto* title = new QLabel( QString("Node: 0x%1").arg(nodeId, 16, 16, QChar('0')) + (memberName.isEmpty() ? "" : QString(" (%1)").arg(memberName)), this); title->setStyleSheet("font-weight: bold; font-size: 13px;"); layout->addWidget(title); m_nickEdit = new QLineEdit(this); m_nickEdit->setPlaceholderText("Local nickname..."); m_nickEdit->setText(curNick); m_nickEdit->setEnabled(!isOwn); layout->addWidget(m_nickEdit); m_adminCb = new QCheckBox("Admin", this); m_adminCb->setChecked(isAdmin); m_adminCb->setEnabled(haveKey); layout->addWidget(m_adminCb); m_superCb = new QCheckBox("Supernode", this); m_superCb->setChecked(isSuper); m_superCb->setEnabled(haveKey); layout->addWidget(m_superCb); if (!haveKey) { auto* hint = new QLabel("(Admin/Super require channel private key)", this); hint->setStyleSheet("color: gray; font-size: 11px;"); layout->addWidget(hint); } layout->addStretch(); auto* btnLayout = new QHBoxLayout(); btnLayout->addStretch(); auto* cancelBtn = new QPushButton("Cancel", this); cancelBtn->setFixedWidth(90); cancelBtn->setStyleSheet( "QPushButton { border: 1px solid palette(mid); border-radius: 4px;" " padding: 6px 16px; font-size: 13px; background: palette(button); }" "QPushButton:hover { background: palette(light); }"); connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject); btnLayout->addWidget(cancelBtn); auto* saveBtn = new QPushButton("Save", this); saveBtn->setFixedWidth(90); saveBtn->setDefault(true); saveBtn->setStyleSheet( "QPushButton { border: none; border-radius: 4px;" " padding: 6px 16px; font-size: 13px; font-weight: bold;" " background: #3390EC; color: white; }" "QPushButton:hover { background: #2B7ED4; }" "QPushButton:disabled { background: palette(mid); color: palette(midlight); }"); connect(saveBtn, &QPushButton::clicked, this, &MemberPropsDialog::onSave); btnLayout->addWidget(saveBtn); layout->addLayout(btnLayout); } void MemberPropsDialog::onSave() { if (!m_db->m_db) return; QString tbl = m_db->peersTableName(m_channelId); /* local_nick — always save (unsigned, local only) */ QString nick = m_nickEdit->text().trimmed(); QByteArray nickSql = QString( "UPDATE \"%1\" SET local_nick=? WHERE node_id=?" ).arg(tbl).toUtf8(); sqlite3_stmt* ns = nullptr; if (sqlite3_prepare_v2(m_db->m_db, nickSql.constData(), -1, &ns, nullptr) == SQLITE_OK) { sqlite3_bind_text(ns, 1, nick.isEmpty() ? nullptr : nick.toUtf8().constData(), -1, nick.isEmpty() ? SQLITE_STATIC : SQLITE_TRANSIENT); sqlite3_bind_int64(ns, 2, (sqlite3_int64)m_nodeId); sqlite3_step(ns); sqlite3_finalize(ns); } /* signed fields — only if key available */ if (m_edPrivkey.size() != 32) { accept(); return; } QString tags; if (m_adminCb->isChecked()) tags += "admin=yes"; if (m_superCb->isChecked()) { if (!tags.isEmpty()) tags += ","; tags += "supernode=yes"; } QByteArray msg = tags.toUtf8(); uint64_t le = htole64(m_nodeId); msg.append((const char*)&le, 8); uint8_t sig[64] = {}; EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, (const uint8_t*)m_edPrivkey.constData(), 32); if (pkey) { EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); if (mdctx) { size_t siglen = 64; if (EVP_DigestSignInit(mdctx, nullptr, nullptr, nullptr, pkey) == 1) EVP_DigestSign(mdctx, sig, &siglen, (const uint8_t*)msg.constData(), msg.size()); EVP_MD_CTX_free(mdctx); } EVP_PKEY_free(pkey); } QByteArray sql = QString( "UPDATE \"%1\" SET adm_tags=?, adm_tags_sig=?, node_type=? WHERE node_id=?" ).arg(tbl).toUtf8(); sqlite3_stmt* st = nullptr; if (sqlite3_prepare_v2(m_db->m_db, sql.constData(), -1, &st, nullptr) == SQLITE_OK) { sqlite3_bind_text(st, 1, tags.isEmpty() ? nullptr : tags.toUtf8().constData(), -1, tags.isEmpty() ? SQLITE_STATIC : SQLITE_TRANSIENT); sqlite3_bind_blob(st, 2, sig, 64, SQLITE_TRANSIENT); int nt = m_superCb->isChecked() ? 4 : 1; sqlite3_bind_int(st, 3, nt); sqlite3_bind_int64(st, 4, (sqlite3_int64)m_nodeId); sqlite3_step(st); sqlite3_finalize(st); } accept(); }