From acc2995d8621579dcd096d6541386f67671de053 Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Tue, 19 Oct 2021 00:42:33 +0200 Subject: [PATCH 01/10] invalidate used token --- db/patch-2fa-invalidate-used-token.sql | 7 +++++++ server/database.js | 1 + server/server.js | 14 ++++++++++---- 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 db/patch-2fa-invalidate-used-token.sql diff --git a/db/patch-2fa-invalidate-used-token.sql b/db/patch-2fa-invalidate-used-token.sql new file mode 100644 index 00000000..2f0b42ca --- /dev/null +++ b/db/patch-2fa-invalidate-used-token.sql @@ -0,0 +1,7 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE user + ADD twofa_last_token VARCHAR(6); + +COMMIT; diff --git a/server/database.js b/server/database.js index 1030ffdd..e97dea99 100644 --- a/server/database.js +++ b/server/database.js @@ -50,6 +50,7 @@ class Database { "patch-group-table.sql": true, "patch-monitor-push_token.sql": true, "patch-http-monitor-method-body-and-headers.sql": true, + "patch-2fa-invalidate-used-token.sql": true, } /** diff --git a/server/server.js b/server/server.js index c4d18869..a6e26aab 100644 --- a/server/server.js +++ b/server/server.js @@ -265,7 +265,7 @@ exports.entryPage = "dashboard"; if (user) { afterLogin(socket, user); - if (user.twofaStatus == 0) { + if (user.twofa_status == 0) { callback({ ok: true, token: jwt.sign({ @@ -274,7 +274,7 @@ exports.entryPage = "dashboard"; }); } - if (user.twofaStatus == 1 && !data.token) { + if (user.twofa_status == 1 && !data.token) { callback({ tokenRequired: true, }); @@ -283,7 +283,13 @@ exports.entryPage = "dashboard"; if (data.token) { let verify = notp.totp.verify(data.token, user.twofa_secret, twofa_verification_opts); - if (verify && verify.delta == 0) { + if (user.twofa_last_token !== data.token && verify) { + + await R.exec("UPDATE `user` SET twofa_last_token = ? WHERE id = ? ", [ + data.token, + socket.userID, + ]); + callback({ ok: true, token: jwt.sign({ @@ -401,7 +407,7 @@ exports.entryPage = "dashboard"; let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts); - if (verify && verify.delta == 0) { + if (user.twofa_last_token !== token && verify) { callback({ ok: true, valid: true, From b77b33e790620b9b94f1f09c188ecc6e4f71d70e Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 23 Oct 2021 16:35:13 +0800 Subject: [PATCH 02/10] add login rate limiter --- server/auth.js | 32 +++++++++++++++++++++----------- server/rate-limiter.js | 39 +++++++++++++++++++++++++++++++++++++++ server/server.js | 6 ++++++ 3 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 server/rate-limiter.js diff --git a/server/auth.js b/server/auth.js index 35d2a080..c476ea1e 100644 --- a/server/auth.js +++ b/server/auth.js @@ -1,8 +1,9 @@ -const basicAuth = require("express-basic-auth") +const basicAuth = require("express-basic-auth"); const passwordHash = require("./password-hash"); const { R } = require("redbean-node"); const { setting } = require("./util-server"); const { debug } = require("../src/util"); +const { loginRateLimiter } = require("./rate-limiter"); /** * @@ -13,7 +14,7 @@ const { debug } = require("../src/util"); exports.login = async function (username, password) { let user = await R.findOne("user", " username = ? AND active = 1 ", [ username, - ]) + ]); if (user && passwordHash.verify(password, user.password)) { // Upgrade the hash to bcrypt @@ -27,21 +28,30 @@ exports.login = async function (username, password) { } return null; -} +}; function myAuthorizer(username, password, callback) { - setting("disableAuth").then((result) => { - if (result) { - callback(null, true) + callback(null, true); } else { - exports.login(username, password).then((user) => { - callback(null, user != null) - }) - } - }) + // Login Rate Limit + loginRateLimiter.pass(null, 0).then((pass) => { + if (pass) { + exports.login(username, password).then((user) => { + callback(null, user != null); + + if (user == null) { + loginRateLimiter.removeTokens(1); + } + }); + } else { + callback(null, false); + } + }); + } + }); } exports.basicAuth = basicAuth({ diff --git a/server/rate-limiter.js b/server/rate-limiter.js new file mode 100644 index 00000000..0bacc14c --- /dev/null +++ b/server/rate-limiter.js @@ -0,0 +1,39 @@ +const { RateLimiter } = require("limiter"); +const { debug } = require("../src/util"); + +class KumaRateLimiter { + constructor(config) { + this.errorMessage = config.errorMessage; + this.rateLimiter = new RateLimiter(config); + } + + async pass(callback, num = 1) { + const remainingRequests = await this.removeTokens(num); + debug("Rate Limit (remainingRequests):" + remainingRequests); + if (remainingRequests < 0) { + if (callback) { + callback({ + ok: false, + msg: this.errorMessage, + }); + } + return false; + } + return true; + } + + async removeTokens(num = 1) { + return await this.rateLimiter.removeTokens(num); + } +} + +const loginRateLimiter = new KumaRateLimiter({ + tokensPerInterval: 20, + interval: "minute", + fireImmediately: true, + errorMessage: "Too frequently, try again later." +}); + +module.exports = { + loginRateLimiter +}; diff --git a/server/server.js b/server/server.js index 11f03061..b88c2f64 100644 --- a/server/server.js +++ b/server/server.js @@ -52,6 +52,7 @@ const Database = require("./database"); debug("Importing Background Jobs"); const { initBackgroundJobs } = require("./jobs"); +const { loginRateLimiter } = require("./rate-limiter"); const { basicAuth } = require("./auth"); const { login } = require("./auth"); @@ -281,6 +282,11 @@ exports.entryPage = "dashboard"; socket.on("login", async (data, callback) => { console.log("Login"); + // Login Rate Limit + if (! await loginRateLimiter.pass(callback)) { + return; + } + let user = await login(data.username, data.password); if (user) { From b5eb17ed9335167019ca6073386440c961772418 Mon Sep 17 00:00:00 2001 From: kry008 Date: Sat, 23 Oct 2021 15:07:24 +0200 Subject: [PATCH 03/10] Update pl.js --- src/languages/pl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/pl.js b/src/languages/pl.js index 2861ed0f..d2dbc36e 100644 --- a/src/languages/pl.js +++ b/src/languages/pl.js @@ -271,8 +271,8 @@ export default { "Messaging API": "API Wiadomości", wayToGetLineChannelToken: "Najpierw uzyskaj dostęp do {0}, utwórz dostawcę i kanał (Messaging API), a następnie możesz uzyskać token dostępu do kanału i identyfikator użytkownika z wyżej wymienionych pozycji menu.", "Icon URL": "Adres Ikony", - aboutIconURL: "You can provide a link to a picture in \"Icon URL\" to override the default profile picture. Will not be used if Icon Emoji is set.", - aboutMattermostChannelName: "You can override the default channel that webhook posts to by entering the channel name into \"Channel Name\" field. This needs to be enabled in Mattermost webhook settings. Ex: #other-channel", + aboutIconURL: "Możesz podać link do zdjęcia w \"Adres URL ikony\", aby zastąpić domyślne zdjęcie profilowe. Nie będzie używany, jeśli ustawiona jest ikona Emoji.", + aboutMattermostChannelName: "Możesz zastąpić domyślny kanał, na którym publikowane są posty webhooka, wpisując nazwę kanału w polu \"Nazwa Kanału\". Należy to włączyć w ustawieniach webhooka Mattermost. Np.: #inny-kanał", "matrix": "Matrix", promosmsTypeEco: "SMS ECO - Tanie, lecz wolne. Dostępne tylko w Polsce", promosmsTypeFlash: "SMS FLASH - Wiadomość automatycznie wyświetli się na urządzeniu. Dostępne tylko w Polsce.", From c9549c0de2b90dfc824963ad0620d6ffd3ef528d Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sat, 23 Oct 2021 22:14:05 +0800 Subject: [PATCH 04/10] change body and header placeholders, less misleading. --- src/pages/EditMonitor.vue | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index a763a424..65c3dad6 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -340,11 +340,17 @@ export default { }, bodyPlaceholder() { - return "{\n\t\"id\": 124357,\n\t\"username\": \"admin\",\n\t\"password\": \"myAdminPassword\"\n}"; + return `Example: +{ + "key": "value" +}`; }, headersPlaceholder() { - return "{\n\t\"Authorization\": \"Bearer abc123\",\n\t\"Content-Type\": \"application/json\"\n}"; + return `Example: +{ + "HeaderName": "HeaderValue" +}`; } }, From 89c64f4ea2464fd0fd669adf1b852ed66ecd32a5 Mon Sep 17 00:00:00 2001 From: Adam Stachowicz Date: Sun, 24 Oct 2021 11:16:29 +0200 Subject: [PATCH 05/10] Create PULL_REQUEST_TEMPLATE.md From https://github.com/Harsha200105/DesktopAssistant/pull/51 --- .github/PULL_REQUEST_TEMPLATE.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..58bd3030 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +# Description + +Fixes #(issue) + +## Type of change + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] User Interface +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + + +# Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] My changes generate no new warnings + +# Screenshots (if any) From ab5ddae2eeb96f85cd852a22604153740019cddc Mon Sep 17 00:00:00 2001 From: Adam Stachowicz Date: Sun, 24 Oct 2021 11:18:07 +0200 Subject: [PATCH 06/10] Delete double new line --- .github/PULL_REQUEST_TEMPLATE.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 58bd3030..5c326682 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,7 +10,6 @@ Fixes #(issue) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update - # Checklist: - [ ] My code follows the style guidelines of this project From 8fe5e4e605873d83656374fd8f0a5b669aab6f53 Mon Sep 17 00:00:00 2001 From: Adam Stachowicz Date: Sun, 24 Oct 2021 11:19:42 +0200 Subject: [PATCH 07/10] Fix lint --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5c326682..d9c2cec5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,11 +10,11 @@ Fixes #(issue) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update -# Checklist: +## Checklist - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] My changes generate no new warnings -# Screenshots (if any) +## Screenshots (if any) From 65158cb06bfade0459720e73e6c1d486da376e1b Mon Sep 17 00:00:00 2001 From: Adam Stachowicz Date: Sun, 24 Oct 2021 12:21:59 +0200 Subject: [PATCH 08/10] Update PULL_REQUEST_TEMPLATE.md Changes after Code Review --- .github/PULL_REQUEST_TEMPLATE.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d9c2cec5..be2caa09 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,13 +8,16 @@ Fixes #(issue) - [ ] User Interface - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Translation update - [ ] This change requires a documentation update ## Checklist - [ ] My code follows the style guidelines of this project -- [ ] I have performed a self-review of my own code +- [ ] I ran ESLint and other linters for modified files +- [ ] I have performed a self-review of my own code and test it - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] My changes generate no new warnings +- [ ] My code needed automated testing. I have added them ## Screenshots (if any) From 7c63cbfd84852a63086ac3bd7489d5fa9ff05cdc Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Sun, 24 Oct 2021 22:50:33 +0800 Subject: [PATCH 09/10] add node.js 17 to auto test --- .github/workflows/auto-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml index c59a61b9..e01c02ce 100644 --- a/.github/workflows/auto-test.yml +++ b/.github/workflows/auto-test.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] - node-version: [14.x, 16.x] + node-version: [14.x, 16.x, 17.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: From d82f305f6efd551f844d68ab8fb30a403705c580 Mon Sep 17 00:00:00 2001 From: Ponkhy Date: Sun, 24 Oct 2021 17:56:05 +0200 Subject: [PATCH 10/10] Updated german language file --- src/languages/de-DE.js | 107 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js index 5e69899d..740c8852 100644 --- a/src/languages/de-DE.js +++ b/src/languages/de-DE.js @@ -197,4 +197,111 @@ export default { pushbullet: "Pushbullet", line: "Line Messenger", mattermost: "Mattermost", + "Primary Base URL": "Primär URL", + "Push URL": "Push URL", + needPushEvery: "Du solltest diese URL alle {0} Sekunden aufrufen.", + pushOptionalParams: "Optionale Parameter: {0}", + defaultNotificationName: "Meine {notification} Alarm ({number})", + here: "hier", + Required: "Erforderlich", + "Bot Token": "Bot Token", + wayToGetTelegramToken: "Hier kannst du einen Token erhalten {0}.", + "Chat ID": "Chat ID", + supportTelegramChatID: "Unterstützt Direkt Chat / Gruppe / Kanal Chat-ID's", + wayToGetTelegramChatID: "Du kannst die Chat-ID erhalten, indem du eine Nachricht an den Bot sendest und zu dieser URL gehst, um die chat_id: zu sehen.", + "YOUR BOT TOKEN HERE": "HIER DEIN BOT TOKEN", + chatIDNotFound: "Chat-ID wurde nicht gefunden: bitte sende zuerst eine Nachricht an diesen Bot", + "Post URL": "Post URL", + "Content Type": "Content Type", + webhookJsonDesc: "{0} ist gut für alle modernen HTTP-Server sowie Express.js", + webhookFormDataDesc: "{multipart} ist gut für PHP. Die JSON muss mit {decodeFunction} geparst werden", + secureOptionNone: "Keine / STARTTLS (25, 587)", + secureOptionTLS: "TLS (465)", + "Ignore TLS Error": "TLS-Fehler ignorieren", + "From Email": "Von Email", + emailCustomSubject: "Benutzerdefinierter Betreff", + "To Email": "Zu Email", + smtpCC: "CC", + smtpBCC: "BCC", + "Discord Webhook URL": "Discord Webhook URL", + wayToGetDiscordURL: "Du kannst diesen erhalten, indem du zu den Servereinstellungen gehst -> Integrationen -> Neuer Webhook", + "Bot Display Name": "Bot-Anzeigename", + "Prefix Custom Message": "Benutzerdefinierter Nachrichten Präfix", + "Hello @everyone is...": "Hallo {'@'}everyone ist...", + "Webhook URL": "Webhook URL", + wayToGetTeamsURL: "Hier erfährst du, wie eine Webhook-URL erstellt werden kann {0}.", + Number: "Nummer", + Recipients: "Empfänger", + needSignalAPI: "Es wird ein Signal Client mit REST-API benötigt.", + wayToCheckSignalURL: "Du kannst diese URL aufrufen, um zu sehen, wie du eine einrichtest:", + signalImportant: "WICHTIG: Gruppen und Nummern können in Empfängern nicht gemischt werden!", + "Application Token": "Anwendungs Token", + "Server URL": "Server URL", + Priority: "Priorität", + "Icon Emoji": "Icon Emoji", + "Channel Name": "Kanalname", + "Uptime Kuma URL": "Uptime Kuma URL", + aboutWebhooks: "Weitere Informationen zu Webhooks auf: {0}", + aboutChannelName: "Gebe den Kanalnamen ein auf {0} Feld Kanalname, wenn du den Webhook-Kanal umgehen möchtest. Ex: #other-channel", + aboutKumaURL: "Wenn das Feld für die Uptime Kuma URL leer gelassen wird, wird es standardmäßig die GitHub Projekt Seite verwenden.", + emojiCheatSheet: "Emoji Cheat Sheet: {0}", + "User Key": "Benutzerschlüssel", + Device: "Gerät", + "Message Title": "Nachrichtentitel", + "Notification Sound": "Benachrichtigungston", + "More info on:": "Mehr Infos auf: {0}", + pushoverDesc1: "Notfallpriorität (2) hat Standardmäßig 30 Sekunden Auszeit, zwischen den Versuchen und läuft nach 1 Stunde ab.", + pushoverDesc2: "Fülle das Geräte Feld aus, wenn du Benachrichtigungen an verschiedene Geräte senden möchtest.", + "SMS Type": "SMS Typ", + octopushTypePremium: "Premium (Schnell - zur Benachrichtigung empfohlen)", + octopushTypeLowCost: "Kostengünstig (Langsam - manchmal vom Betreiber gesperrt)", + checkPrice: "Prüfe {0} Preise:", + octopushLegacyHint: "Verwendest du die Legacy-Version von Octopush (2011-2020) oder die neue Version?", + "Check octopush prices": "Überprüfe die Oktopush Preise {0}.", + octopushPhoneNumber: "Telefonnummer (Internationales Format, z.B : +49612345678) ", + octopushSMSSender: "Name des SMS-Absenders : 3-11 alphanumerische Zeichen und Leerzeichen (a-zA-Z0-9)", + "LunaSea Device ID": "LunaSea Geräte ID", + "Apprise URL": "Apprise URL", + "Example:": "Beispiel: {0}", + "Read more:": "Weiterlesen: {0}", + "Status:": "Status: {0}", + "Read more": "Weiterlesen", + appriseInstalled: "Apprise ist installiert.", + appriseNotInstalled: "Apprise ist nicht installiert. {0}", + "Access Token": "Access Token", + "Channel access token": "Channel access token", + "Line Developers Console": "Line Developers Console", + lineDevConsoleTo: "Line Developers Console - {0}", + "Basic Settings": "Basic Settings", + "User ID": "User ID", + "Messaging API": "Messaging API", + wayToGetLineChannelToken: "Rufe zuerst {0} auf, erstelle dann einen Provider und Channel (Messaging API). Als nächstes kannst du den Channel access token und die User ID aus den oben genannten Menüpunkten abrufen.", + "Icon URL": "Icon URL", + aboutIconURL: "Du kannst einen Link zu einem Bild in 'Icon URL' übergeben um das Standardprofilbild zu überschreiben. Wird nicht verwendet, wenn ein Icon Emoji gesetzt ist.", + aboutMattermostChannelName: "Du kannst den Standardkanal, auf dem der Webhook postet überschreiben, indem der Kanalnamen in das Feld 'Channel Name' eingeben wird. Dies muss in den Mattermost Webhook-Einstellungen aktiviert werden. Ex: #other-channel", + matrix: "Matrix", + promosmsTypeEco: "SMS ECO - billig, aber langsam und oft überladen. Nur auf polnische Empfänger beschränkt.", + promosmsTypeFlash: "SMS FLASH - Die Nachricht wird automatisch auf dem Empfängergerät angezeigt. Nur auf polnische Empfänger beschränkt.", + promosmsTypeFull: "SMS FULL - Premium Stufe von SMS, es kann der Absendernamen verwendet werden (Der Name musst zuerst registriert werden). Zuverlässig für Warnungen.", + promosmsTypeSpeed: "SMS SPEED - Höchste Priorität im System. Sehr schnell und zuverlässig, aber teuer (Ungefähr das doppelte von SMS FULL).", + promosmsPhoneNumber: "Phone number (Für polnische Empfänger können die Vorwahlen übersprungen werden)", + promosmsSMSSender: "Name des SMS-Absenders : vorregistrierter Name oder einer der Standardwerte: InfoSMS, SMS Info, MaxSMS, INFO, SMS", + "Feishu WebHookUrl": "Feishu Webhook URL", + matrixHomeserverURL: "Heimserver URL (mit http(s):// und optionalen Ports)", + "Internal Room Id": "Interne Raum-ID", + matrixDesc1: "Die interne Raum-ID findest du im erweiterten Bereich der Raumeinstellungen im Matrix-Client. Es sollte es aussehen wie z.B. !QMdRCpUIfLwsfjxye6:home.server.", + matrixDesc2: "Es wird dringend empfohlen, dass ein neuen Benutzer erstellt wird und nicht den Zugriffstoken deines eigenen Matrix-Benutzers verwendest. Anderfalls ermöglicht es vollen Zugriff auf dein Konto und alle Räume, denen du beigetreten bist. Erstelle stattdessen einen neuen Benutzer und lade ihn nur in den Raum ein, in dem du die Benachrichtigung erhalten möchtest. Du kannst den Zugriffstoken erhalten, indem du folgendes ausführst {0}", + Method: "Method", + Body: "Body", + Headers: "Headers", + PushUrl: "Push URL", + HeadersInvalidFormat: "Die Header ist kein gültiges JSON: ", + BodyInvalidFormat: "Der Body ist kein gültiges JSON: ", + "Monitor History": "Monitor Verlauf:", + clearDataOlderThan: "Bewahre die Monitor-Verlaufsdaten für {0} Tage auf.", + PasswordsDoNotMatch: "Passwörter stimmen nicht überein.", + records: "Einträge", + "One record": "Ein Eintrag", + "Showing {from} to {to} of {count} records": "Zeige {from} zu {to} von {count} Einträge", + steamApiKeyDescription: "Um einen Steam Game Server zu überwachen, wird ein Steam Web-API-Schlüssel benötigt. Dieser kann hier registriert werden: ", };