Skip to content

Commit a8e9e80

Browse files
authored
Support deleting API key
PR #23388.
1 parent 9ce5463 commit a8e9e80

File tree

10 files changed

+158
-37
lines changed

10 files changed

+158
-37
lines changed

WebAPI_Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## 2.14.1
44
* [#23212](https://github.com/qbittorrent/qBittorrent/pull/23212)
55
* Add `app/rotateAPIKey` endpoint for generating, and rotating, the WebAPI API key
6+
* [#23388](https://github.com/qbittorrent/qBittorrent/pull/23388)
7+
* Add `app/deleteAPIKey` endpoint for deleting the existing WebAPI API key
68

79
## 2.14.0
810
* [#23202](https://github.com/qbittorrent/qBittorrent/pull/23202)

src/gui/optionsdialog.cpp

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,21 +1356,10 @@ void OptionsDialog::loadWebUITabOptions()
13561356

13571357
// API Key
13581358
if (const QString apiKey = pref->getWebUIApiKey(); Utils::APIKey::isValid(apiKey))
1359-
{
13601359
m_currentAPIKey = apiKey;
1361-
m_ui->textWebUIAPIKey->setText(maskAPIKey(m_currentAPIKey));
1362-
m_ui->textWebUIAPIKey->setEnabled(true);
1363-
m_ui->btnWebUIAPIKeyCopy->setEnabled(true);
1364-
m_ui->btnWebUIAPIKeyRotate->setToolTip(tr("Rotate API key"));
1365-
}
13661360
else
1367-
{
13681361
m_currentAPIKey.clear();
1369-
m_ui->textWebUIAPIKey->clear();
1370-
m_ui->textWebUIAPIKey->setEnabled(false);
1371-
m_ui->btnWebUIAPIKeyCopy->setEnabled(false);
1372-
m_ui->btnWebUIAPIKeyRotate->setToolTip(tr("Generate API key"));
1373-
}
1362+
setupWebUIAPIKey();
13741363

13751364
m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUILocalAuthEnabled());
13761365
m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUIAuthSubnetWhitelistEnabled());
@@ -1412,8 +1401,9 @@ void OptionsDialog::loadWebUITabOptions()
14121401

14131402
connect(m_ui->textWebUIUsername, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
14141403
connect(m_ui->textWebUIPassword, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
1415-
connect(m_ui->btnWebUIAPIKeyCopy, &QPushButton::clicked, this, &ThisType::onBtnWebUIAPIKeyCopy);
1416-
connect(m_ui->btnWebUIAPIKeyRotate, &QPushButton::clicked, this, &ThisType::onBtnWebUIAPIKeyRotate);
1404+
connect(m_ui->btnWebUIAPIKeyCopy, &QPushButton::clicked, this, &ThisType::onBtnWebUIAPIKeyCopyClicked);
1405+
connect(m_ui->btnWebUIAPIKeyRotate, &QPushButton::clicked, this, &ThisType::onBtnWebUIAPIKeyRotateClicked);
1406+
connect(m_ui->btnWebUIAPIKeyDelete, &QPushButton::clicked, this, &ThisType::onBtnWebUIAPIKeyDeleteClicked);
14171407

14181408
connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
14191409
connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
@@ -1490,13 +1480,13 @@ void OptionsDialog::saveWebUITabOptions() const
14901480
pref->setDynDNSPassword(m_ui->DNSPasswordTxt->text());
14911481
}
14921482

1493-
void OptionsDialog::onBtnWebUIAPIKeyCopy()
1483+
void OptionsDialog::onBtnWebUIAPIKeyCopyClicked()
14941484
{
14951485
if (!m_currentAPIKey.isEmpty())
14961486
QApplication::clipboard()->setText(m_currentAPIKey);
14971487
}
14981488

1499-
void OptionsDialog::onBtnWebUIAPIKeyRotate()
1489+
void OptionsDialog::onBtnWebUIAPIKeyRotateClicked()
15001490
{
15011491
const QString title = m_currentAPIKey.isEmpty()
15021492
? tr("Generate API key")
@@ -1511,16 +1501,51 @@ void OptionsDialog::onBtnWebUIAPIKeyRotate()
15111501
if (button == QMessageBox::Yes)
15121502
{
15131503
m_currentAPIKey = Utils::APIKey::generate();
1514-
m_ui->textWebUIAPIKey->setText(maskAPIKey(m_currentAPIKey));
1515-
m_ui->textWebUIAPIKey->setEnabled(true);
1516-
m_ui->btnWebUIAPIKeyCopy->setEnabled(true);
1517-
m_ui->btnWebUIAPIKeyRotate->setToolTip(tr("Rotate API key"));
1504+
setupWebUIAPIKey();
1505+
1506+
auto *preferences = Preferences::instance();
1507+
preferences->setWebUIApiKey(m_currentAPIKey);
1508+
preferences->apply();
1509+
}
1510+
}
1511+
1512+
void OptionsDialog::onBtnWebUIAPIKeyDeleteClicked()
1513+
{
1514+
const QString title = tr("Delete API key");
1515+
const QString message = tr("Delete this API key? The current key will immediately stop working.");
1516+
const QMessageBox::StandardButton button = QMessageBox::question(
1517+
this, title, message, (QMessageBox::Yes | QMessageBox::No), QMessageBox::No);
1518+
1519+
if (button == QMessageBox::Yes)
1520+
{
1521+
m_currentAPIKey.clear();
1522+
setupWebUIAPIKey();
15181523

15191524
auto *preferences = Preferences::instance();
15201525
preferences->setWebUIApiKey(m_currentAPIKey);
15211526
preferences->apply();
15221527
}
15231528
}
1529+
1530+
void OptionsDialog::setupWebUIAPIKey()
1531+
{
1532+
if (Utils::APIKey::isValid(m_currentAPIKey))
1533+
{
1534+
m_ui->textWebUIAPIKey->setText(maskAPIKey(m_currentAPIKey));
1535+
m_ui->textWebUIAPIKey->setEnabled(true);
1536+
m_ui->btnWebUIAPIKeyCopy->setEnabled(true);
1537+
m_ui->btnWebUIAPIKeyRotate->setToolTip(tr("Rotate API key"));
1538+
m_ui->btnWebUIAPIKeyDelete->setEnabled(true);
1539+
}
1540+
else
1541+
{
1542+
m_ui->textWebUIAPIKey->clear();
1543+
m_ui->textWebUIAPIKey->setEnabled(false);
1544+
m_ui->btnWebUIAPIKeyCopy->setEnabled(false);
1545+
m_ui->btnWebUIAPIKeyRotate->setToolTip(tr("Generate API key"));
1546+
m_ui->btnWebUIAPIKeyDelete->setEnabled(false);
1547+
}
1548+
}
15241549
#endif // DISABLE_WEBUI
15251550

15261551
void OptionsDialog::initializeLanguageCombo()

src/gui/optionsdialog.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,10 @@ private slots:
110110
void webUIHttpsCertChanged(const Path &path);
111111
void webUIHttpsKeyChanged(const Path &path);
112112
void on_registerDNSBtn_clicked();
113-
void onBtnWebUIAPIKeyCopy();
114-
void onBtnWebUIAPIKeyRotate();
113+
void onBtnWebUIAPIKeyCopyClicked();
114+
void onBtnWebUIAPIKeyRotateClicked();
115+
void onBtnWebUIAPIKeyDeleteClicked();
116+
void setupWebUIAPIKey();
115117
#endif
116118

117119
private:

src/gui/optionsdialog.ui

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3861,6 +3861,35 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
38613861
</property>
38623862
</widget>
38633863
</item>
3864+
<item>
3865+
<widget class="QPushButton" name="btnWebUIAPIKeyDelete">
3866+
<property name="sizePolicy">
3867+
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
3868+
<horstretch>0</horstretch>
3869+
<verstretch>0</verstretch>
3870+
</sizepolicy>
3871+
</property>
3872+
<property name="maximumSize">
3873+
<size>
3874+
<width>32</width>
3875+
<height>32</height>
3876+
</size>
3877+
</property>
3878+
<property name="toolTip">
3879+
<string>Delete API key</string>
3880+
</property>
3881+
<property name="text">
3882+
<string/>
3883+
</property>
3884+
<property name="icon">
3885+
<iconset resource="../icons.qrc">
3886+
<normaloff>:/icons/list-remove.svg</normaloff>:/icons/list-remove.svg</iconset>
3887+
</property>
3888+
<property name="flat">
3889+
<bool>false</bool>
3890+
</property>
3891+
</widget>
3892+
</item>
38643893
</layout>
38653894
</item>
38663895
</layout>

src/webui/api/appcontroller.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,13 @@ void AppController::rotateAPIKeyAction()
13261326
setResult(QJsonObject {{u"apiKey"_s, key}});
13271327
}
13281328

1329+
void AppController::deleteAPIKeyAction()
1330+
{
1331+
auto *preferences = Preferences::instance();
1332+
preferences->setWebUIApiKey({});
1333+
preferences->apply();
1334+
}
1335+
13291336
void AppController::networkInterfaceListAction()
13301337
{
13311338
QJsonArray ifaceList;

src/webui/api/appcontroller.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ private slots:
5353
void cookiesAction();
5454
void setCookiesAction();
5555
void rotateAPIKeyAction();
56+
void deleteAPIKeyAction();
5657

5758
void networkInterfaceListAction();
5859
void networkInterfaceAddressListAction();

src/webui/webapplication.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
#include "base/utils/version.h"
5454
#include "api/isessionmanager.h"
5555

56-
inline const Utils::Version<3, 2> API_VERSION {2, 14, 0};
56+
inline const Utils::Version<3, 2> API_VERSION {2, 14, 1};
5757

5858
class APIController;
5959
class AuthController;
@@ -150,6 +150,7 @@ class WebApplication final : public ApplicationComponent<QObject>
150150
const QHash<std::pair<QString, QString>, QString> m_allowedMethod =
151151
{
152152
// <<controller name, action name>, HTTP method>
153+
{{u"app"_s, u"deleteAPIKey"_s}, Http::METHOD_POST},
153154
{{u"app"_s, u"rotateAPIKey"_s}, Http::METHOD_POST},
154155
{{u"app"_s, u"sendTestEmail"_s}, Http::METHOD_POST},
155156
{{u"app"_s, u"setCookies"_s}, Http::METHOD_POST},

src/webui/www/private/views/confirmRotateAPIKey.html renamed to src/webui/www/private/views/confirmAPIKey.html

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,39 @@
1-
<div id="confirmRotateAPIKeyDialog">
1+
<div id="confirmAPIKeyDialog">
22
<div class="genericConfirmGrid">
33
<span class="confirmGridItem confirmWarning"></span>
44
<span class="confirmGridItem dialogMessage"></span>
55
</div>
66
</div>
77
<div>
8-
<input type="button" value="QBT_TR(Yes)QBT_TR[CONTEXT=MainWindow]" id="confirmRotateButton">
9-
<input type="button" value="QBT_TR(No)QBT_TR[CONTEXT=MainWindow]" id="cancelRotateButton">
8+
<input type="button" value="QBT_TR(Yes)QBT_TR[CONTEXT=MainWindow]" id="confirmAPIKeyButton">
9+
<input type="button" value="QBT_TR(No)QBT_TR[CONTEXT=MainWindow]" id="cancelAPIKeyButton">
1010
</div>
1111

1212
<script>
1313
"use strict";
1414

1515
(() => {
16-
const { windowEl, options } = window.MUI.Windows.instances["confirmRotateAPIKeyDialog"];
17-
const { message } = options.data;
16+
const { windowEl, options } = window.MUI.Windows.instances["confirmAPIKeyDialog"];
17+
const { message, action } = options.data;
1818

19-
const confirmButton = document.getElementById("confirmRotateButton");
20-
const cancelButton = document.getElementById("cancelRotateButton");
21-
const dialog = document.getElementById("confirmRotateAPIKeyDialog");
19+
const confirmButton = document.getElementById("confirmAPIKeyButton");
20+
const cancelButton = document.getElementById("cancelAPIKeyButton");
21+
const dialog = document.getElementById("confirmAPIKeyDialog");
2222

2323
dialog.querySelector("span.dialogMessage").textContent = message;
2424

2525
cancelButton.addEventListener("click", (e) => {
2626
window.qBittorrent.Client.closeWindow(dialog);
2727
});
2828
confirmButton.addEventListener("click", (e) => {
29-
window.qBittorrent.Preferences.rotateAPIKey();
29+
switch (action) {
30+
case "rotate":
31+
window.qBittorrent.Preferences.rotateAPIKey();
32+
break;
33+
case "delete":
34+
window.qBittorrent.Preferences.deleteAPIKey();
35+
break;
36+
}
3037
window.qBittorrent.Client.closeWindow(dialog);
3138
});
3239

src/webui/www/private/views/preferences.html

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,11 @@
10121012
<img src="images/force-recheck.svg" alt="QBT_TR(Generate API key)QBT_TR[CONTEXT=OptionsDialog]" title="QBT_TR(Generate API key)QBT_TR[CONTEXT=OptionsDialog]" width="16" height="16" style="margin: 4px; top: 2px; position: relative;">
10131013
</button>
10141014
</td>
1015+
<td>
1016+
<button type="button" disabled id="webUIAPIKeyDeleteButton" style="padding: 0;" data-has-key="false" aria-label="QBT_TR(Delete API key)QBT_TR[CONTEXT=OptionsDialog]">
1017+
<img src="images/list-remove.svg" alt="QBT_TR(Delete API key)QBT_TR[CONTEXT=OptionsDialog]" title="QBT_TR(Delete API key)QBT_TR[CONTEXT=OptionsDialog]" width="16" height="16" style="margin: 4px; top: 2px; position: relative;">
1018+
</button>
1019+
</td>
10151020
</tr>
10161021
</tbody>
10171022
</table>
@@ -1823,6 +1828,7 @@
18231828
updateWebuiLocaleSelect: updateWebuiLocaleSelect,
18241829
registerDynDns: registerDynDns,
18251830
rotateAPIKey: rotateAPIKey,
1831+
deleteAPIKey: deleteAPIKey,
18261832
applyPreferences: applyPreferences
18271833
};
18281834
};
@@ -2586,6 +2592,7 @@
25862592
document.getElementById("webUIAPIKeyCopyButton").disabled = false;
25872593
document.getElementById("webUIAPIKeyRotateButton").dataset.hasKey = "true";
25882594
document.querySelector("#webUIAPIKeyRotateButton img").title = "QBT_TR(Rotate API key)QBT_TR[CONTEXT=OptionsDialog]";
2595+
document.getElementById("webUIAPIKeyDeleteButton").disabled = false;
25892596
}
25902597

25912598
// Use alternative WebUI
@@ -3287,12 +3294,37 @@
32873294
document.getElementById("webUIAPIKeyCopyButton").disabled = false;
32883295
document.getElementById("webUIAPIKeyRotateButton").dataset.hasKey = "true";
32893296
document.querySelector("#webUIAPIKeyRotateButton img").title = "QBT_TR(Rotate API key)QBT_TR[CONTEXT=OptionsDialog]";
3297+
document.getElementById("webUIAPIKeyDeleteButton").disabled = false;
32903298
})
32913299
.catch((error) => {
32923300
alert(`QBT_TR(Unable to rotate API key.)QBT_TR[CONTEXT=HttpServer] ${error.toString()}`);
32933301
});
32943302
};
32953303

3304+
const deleteAPIKey = () => {
3305+
fetch("api/v2/app/deleteAPIKey", {
3306+
method: "POST",
3307+
})
3308+
.then(async (response) => {
3309+
if (!response.ok) {
3310+
alert(await response.text());
3311+
return;
3312+
}
3313+
3314+
const apiKeyTextElem = document.getElementById("WebUIAPIKeyText");
3315+
apiKeyTextElem.value = "";
3316+
apiKeyTextElem.dataset.apiKey = "";
3317+
3318+
document.getElementById("webUIAPIKeyCopyButton").disabled = true;
3319+
document.getElementById("webUIAPIKeyRotateButton").dataset.hasKey = "false";
3320+
document.querySelector("#webUIAPIKeyRotateButton img").title = "QBT_TR(Generate API key)QBT_TR[CONTEXT=OptionsDialog]";
3321+
document.getElementById("webUIAPIKeyDeleteButton").disabled = true;
3322+
})
3323+
.catch((error) => {
3324+
alert(`QBT_TR(Unable to delete API key.)QBT_TR[CONTEXT=HttpServer] ${error.toString()}`);
3325+
});
3326+
};
3327+
32963328
document.getElementById("webUIAPIKeyCopyButton").addEventListener("click", async (e) => {
32973329
const apiKey = document.getElementById("WebUIAPIKeyText").dataset.apiKey;
32983330
await clipboardCopy(apiKey);
@@ -3310,7 +3342,7 @@
33103342
});
33113343

33123344
document.getElementById("webUIAPIKeyRotateButton").addEventListener("click", (e) => {
3313-
const hasKey = e.target.parentElement.dataset.hasKey;
3345+
const hasKey = e.target.parentElement.dataset.hasKey === "true";
33143346
const title = hasKey
33153347
? "QBT_TR(Rotate API key)QBT_TR[CONTEXT=OptionsDialog]"
33163348
: "QBT_TR(Generate API key)QBT_TR[CONTEXT=OptionsDialog]";
@@ -3320,11 +3352,26 @@
33203352

33213353
new MochaUI.Modal({
33223354
...window.qBittorrent.Dialog.baseModalOptions,
3323-
id: "confirmRotateAPIKeyDialog",
33243355
title: title,
3325-
contentURL: "views/confirmRotateAPIKey.html?v=${CACHEID}",
3356+
id: "confirmAPIKeyDialog",
3357+
contentURL: "views/confirmAPIKey.html?v=${CACHEID}",
3358+
data: {
3359+
action: "rotate",
3360+
message: message,
3361+
},
3362+
});
3363+
});
3364+
3365+
document.getElementById("webUIAPIKeyDeleteButton").addEventListener("click", (e) => {
3366+
const title = "QBT_TR(Delete API key)QBT_TR[CONTEXT=OptionsDialog]";
3367+
const message = "QBT_TR(Delete this API key? The current key will immediately stop working.)QBT_TR[CONTEXT=confirmRotateAPIKeyDialog]";
3368+
new MochaUI.Modal({
3369+
...window.qBittorrent.Dialog.baseModalOptions,
3370+
title: title,
3371+
id: "confirmAPIKeyDialog",
3372+
contentURL: "views/confirmAPIKey.html?v=${CACHEID}",
33263373
data: {
3327-
hasKey: hasKey,
3374+
action: "delete",
33283375
message: message,
33293376
},
33303377
});

src/webui/www/webui.qrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@
424424
<file>private/views/confirmAutoTMM.html</file>
425425
<file>private/views/confirmdeletion.html</file>
426426
<file>private/views/confirmRecheck.html</file>
427-
<file>private/views/confirmRotateAPIKey.html</file>
427+
<file>private/views/confirmAPIKey.html</file>
428428
<file>private/views/cookies.html</file>
429429
<file>private/views/createtorrent.html</file>
430430
<file>private/views/filters.html</file>

0 commit comments

Comments
 (0)