aboutsummaryrefslogtreecommitdiff
path: root/src/auth_flow.cpp
diff options
context:
space:
mode:
authorOsmium Sorcerer <os@sof.beauty>2026-03-22 18:55:26 +0000
committerOsmium Sorcerer <os@sof.beauty>2026-03-29 22:22:25 +0000
commita124f46861d549ddc13485536962e34d80de939a (patch)
tree3553849323aa70fef1e198f3476a2abcc7adfe39 /src/auth_flow.cpp
parentb1ad938c37f4e175e5509f727d1033b074b134d4 (diff)
Add authentication dialog
Introduce start_auth_flow, a function invoked by typing `/auth username` in OOC. It sends an public-key authentication request to the server, starting the entire flow. The flow invoves two dialogs: to select the key, and to enter the passphrase to unlock the key. For convenience, each successful unlock also remembers the key for that username on the server, storing this in `saved_auth.json` (I chose JSON because I wanted it to stay human-editable; INI would be better, but it suffers from bad platform quirks in Qt).
Diffstat (limited to 'src/auth_flow.cpp')
-rw-r--r--src/auth_flow.cpp159
1 files changed, 159 insertions, 0 deletions
diff --git a/src/auth_flow.cpp b/src/auth_flow.cpp
new file mode 100644
index 0000000..07544e8
--- /dev/null
+++ b/src/auth_flow.cpp
@@ -0,0 +1,159 @@
+#include <QHeaderView>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QTableView>
+#include <QVBoxLayout>
+
+#include "auth_flow.h"
+#include "keyring.h"
+
+#include "file_functions.h"
+
+// This function is supposed to open the authentication dialog with various
+// fields like method selection and fields to enter password or select a key,
+// but for now, it'll simply submit a public key auth request. Hostname
+// parameter is unused.
+void start_auth_flow(AOApplication *ao_app, QString username)
+{
+ ao_app->ex_auth_username = username;
+ AuthRequest req;
+ req.username = username;
+ req.method = AuthMethod::certificate;
+ ao_app->send_ex_message(serializeAuthRequest(req));
+}
+
+KeySelectDialog::KeySelectDialog(KeyringModel *model, QStringView username, QWidget *parent)
+ : QDialog(parent)
+ , m_model(model)
+{
+ this->setAttribute(Qt::WA_DeleteOnClose);
+ this->setWindowTitle(QString("Select key for %1").arg(username));
+ auto view = new QTableView(this);
+ view->setModel(m_model);
+ view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+ view->setSelectionBehavior(QAbstractItemView::SelectRows);
+ view->setSelectionMode(QAbstractItemView::SingleSelection);
+ QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
+ buttons->button(QDialogButtonBox::Ok)->setEnabled(false);
+ QVBoxLayout *layout = new QVBoxLayout(this);
+ layout->addWidget(view);
+ layout->addWidget(buttons);
+ connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
+ connect(buttons, &QDialogButtonBox::accepted, this, [this, view] {
+ auto rows = view->selectionModel()->selectedRows();
+ if (rows.isEmpty())
+ {
+ return;
+ }
+
+ QByteArray key_id = m_model->data(rows.first(), KeyringModel::KeyIDRole).toByteArray();
+ QString key_name = m_model->data(rows.first()).toString();
+
+ emit key_selected(key_id, key_name);
+ });
+ connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, [view, buttons](const QItemSelection &, const QItemSelection &) {
+ bool selected = !view->selectionModel()->selectedRows().isEmpty();
+ buttons->button(QDialogButtonBox::Ok)->setEnabled(selected);
+ });
+}
+
+KeyPassphraseDialog::KeyPassphraseDialog(QStringView key_name, QWidget *parent)
+ : QDialog(parent)
+{
+ this->setWindowTitle(QString("Enter passphrase for key %1").arg(key_name));
+ QVBoxLayout *pw_layout = new QVBoxLayout(this);
+ pw_layout->addWidget(new QLabel(QStringLiteral("Passphrase:"), this));
+ m_pw_line = new QLineEdit(this);
+ m_pw_line->setEchoMode(QLineEdit::Password);
+ pw_layout->addWidget(m_pw_line);
+ QDialogButtonBox *pw_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
+ pw_layout->addWidget(pw_buttons);
+ m_err_lbl = new QLabel(this);
+ m_err_lbl->setVisible(false);
+ pw_layout->addWidget(m_err_lbl);
+ connect(pw_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
+ connect(pw_buttons, &QDialogButtonBox::accepted, this, [this] {
+ emit passphrase_submitted(m_pw_line->text().toUtf8());
+ m_pw_line->clear();
+ // zero the buffer
+ });
+}
+
+void KeyPassphraseDialog::display_error(ResponseResult error) const
+{
+ QString error_text;
+ switch (error)
+ {
+ case ResponseResult::unsupported_version:
+ error_text = QStringLiteral("Unsupported key version.");
+ break;
+ case ResponseResult::decryption_failed:
+ error_text = QStringLiteral("Wrong passphrase (or corrupted key).");
+ break;
+ default:
+ error_text = QString("Error unlocking key (code %1). Catastrophic failure.").arg((int)error);
+ break;
+ }
+ m_err_lbl->setText(error_text);
+ m_err_lbl->setVisible(true);
+}
+
+AuthFlow::AuthFlow(AOApplication *ao_app, const AuthChallenge &challenge, QWidget *parent)
+ : QObject(parent)
+ , m_ao_app(ao_app)
+ , m_challenge(challenge)
+{
+ auto saved_key = ao_app->saved_auth.lookup(ao_app->m_serverdata.m_server_hostname.toUtf8(), ao_app->ex_auth_username.toUtf8());
+ if (!saved_key.isEmpty())
+ {
+ m_mode = FlowMode::Saved;
+ m_key_dlg = nullptr;
+ on_key_selected(saved_key, QString("%1@%2 (saved)").arg(ao_app->ex_auth_username, ao_app->m_serverdata.m_server_hostname));
+ }
+ else
+ {
+ m_mode = FlowMode::Default;
+ m_key_dlg = new KeySelectDialog(&ao_app->keyring_model, ao_app->ex_auth_username, parent);
+ connect(m_key_dlg, &KeySelectDialog::key_selected, this, &AuthFlow::on_key_selected);
+ m_key_dlg->open();
+ }
+}
+
+void AuthFlow::on_key_selected(QByteArrayView key_id, QStringView key_name)
+{
+ m_pwd_dlg = new KeyPassphraseDialog(key_name, m_key_dlg);
+ connect(m_pwd_dlg, &KeyPassphraseDialog::passphrase_submitted, this, [this, key_id](QByteArrayView passphrase) {
+ AuthResponse response;
+ ResponseResult result = unlock_and_auth(key_id, passphrase, m_challenge.challenge, m_ao_app->ex_auth_username.toUtf8(), response);
+ if (result == ResponseResult::success)
+ {
+ m_ao_app->send_ex_message(serializeAuthResponse(response));
+ if (m_mode == FlowMode::Default)
+ {
+ m_key_dlg->accept();
+ m_ao_app->saved_auth.insert(m_ao_app->m_serverdata.m_server_hostname.toUtf8(), m_ao_app->ex_auth_username.toUtf8(), key_id);
+ }
+ else
+ {
+ m_pwd_dlg->accept();
+ }
+ deleteLater();
+ }
+ else
+ {
+ m_pwd_dlg->display_error(result);
+ }
+ });
+ connect(m_pwd_dlg, &QDialog::rejected, this, [this] {
+ if (m_mode == FlowMode::Saved)
+ {
+ m_ao_app->saved_auth.remove(m_ao_app->m_serverdata.m_server_hostname.toUtf8(), m_ao_app->ex_auth_username.toUtf8());
+ m_mode = FlowMode::Default;
+
+ m_key_dlg = new KeySelectDialog(&m_ao_app->keyring_model, m_ao_app->ex_auth_username);
+ connect(m_key_dlg, &KeySelectDialog::key_selected, this, &AuthFlow::on_key_selected);
+ m_key_dlg->open();
+ }
+ });
+ m_pwd_dlg->open();
+}