From 61c3876fa5da17edb2222cbb980c38cb596c866d Mon Sep 17 00:00:00 2001
From: peterbolha <xbolha@fi.muni.cz>
Date: Tue, 28 Nov 2023 16:33:08 +0100
Subject: [PATCH] fix: failcounter handling

---
 dictionaries/privacyidea.definition.json  |  3 +++
 dictionaries/privacyidea.translation.json |  3 +++
 lib/Auth/Utils.php                        | 18 ++++++++++++++++--
 locales/cs/LC_MESSAGES/privacyidea.po     |  3 +++
 locales/en/LC_MESSAGES/privacyidea.po     |  3 +++
 www/FormBuilder.php                       |  7 +++++++
 6 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/dictionaries/privacyidea.definition.json b/dictionaries/privacyidea.definition.json
index 9cf5d28..27694ba 100644
--- a/dictionaries/privacyidea.definition.json
+++ b/dictionaries/privacyidea.definition.json
@@ -92,6 +92,9 @@
   "error_message": {
     "en": "Verification was not successful. Please try again."
   },
+  "failcounter_error_message": {
+    "en": "Verification code you have entered is either incorrect or this method of authentication has been deactivated for your account due to numerous failed login attempts. If your login attempts using verification codes keep failing, please use a recovery code or a security key."
+  },
   "error": {
     "en": "Error"
   }
diff --git a/dictionaries/privacyidea.translation.json b/dictionaries/privacyidea.translation.json
index ccfceef..2b1368c 100644
--- a/dictionaries/privacyidea.translation.json
+++ b/dictionaries/privacyidea.translation.json
@@ -138,6 +138,9 @@
   "error_message": {
     "cs": "Ověření nebylo úspěšné. Zkuste to znovu nebo použijte jinou metodu."
   },
+  "failcounter_error_message": {
+    "cs": "Zadaný ověřovací kód je nesprávný, nebo byly ověřovací kódy ve Vašem účtu zablokovány (např. kvůli velkému počtu neúspěšných pokusů). Pokud se Vám opakovaně nedaří použít ověřovací kód, prosím použijte bezpečnostní klíč nebo záložní kód."
+  },
   "error": {
     "cs": "Chyba"
   }
diff --git a/lib/Auth/Utils.php b/lib/Auth/Utils.php
index 5e7d456..35a24c0 100644
--- a/lib/Auth/Utils.php
+++ b/lib/Auth/Utils.php
@@ -105,7 +105,21 @@ class Utils
             }
         } else {
             try {
-                $response = $pi->validateCheck($username, $formParams['otp'], $transactionID);
+                // limit otp validation to totp tokens to prevent incrementing of webauthn failcounter
+                $params["type"] = "totp";
+                $params["user"] = $username;
+                $params["pass"] = $formParams['otp'];
+                $headers = [];
+
+                $rawResponse = $pi->sendRequest($params, $headers, 'POST', '/validate/check');
+                $response = PIResponse::fromJSON($rawResponse, $pi);
+
+                $isAuthUnuccessful = $response->value === false;
+                if ($isAuthUnuccessful) {
+                    // prepare custom error message placeholder - failcounter might have been exceeded
+                    Logger::debug("Original TOTP validation response error message: " . $response->errorMessage);
+                    $response->errorMessage = "possible failcounter exceeded";
+                }
             } catch (\Exception $e) {
                 self::handlePrivacyIDEAException($e, $state);
             }
@@ -318,7 +332,7 @@ class Utils
         } else {
             // Unexpected response
             Logger::error('privacyIDEA: ' . $response->message);
-            $state['privacyidea:privacyidea']['errorMessage'] = $response->message;
+            $state['privacyidea:privacyidea']['errorMessage'] = $response->errorMessage;
         }
 
         return State::saveState($state, 'privacyidea:privacyidea');
diff --git a/locales/cs/LC_MESSAGES/privacyidea.po b/locales/cs/LC_MESSAGES/privacyidea.po
index 52bd280..65a541e 100644
--- a/locales/cs/LC_MESSAGES/privacyidea.po
+++ b/locales/cs/LC_MESSAGES/privacyidea.po
@@ -119,6 +119,9 @@ msgstr "Zkusit znovu"
 msgid "{privacyidea:privacyidea:error_message}"
 msgstr "Ověření nebylo úspěšné. Zkuste to znovu nebo použijte jinou metodu."
 
+msgid "{privacyidea:privacyidea:failcounter_error_message}"
+msgstr "Zadaný kód ověřovací kód je nesprávný nebo byly ověřovací kódy ve Vašem účtu zablokovány (např. kvůli velkému počtu neúspěšných pokusů). Pokud se Vám opakovaně nedaří použít ověřovací kód, prosím použijte bezpečnostní klíč nebo záložní kód."
+
 msgid "{privacyidea:privacyidea:error}"
 msgstr "Chyba"
 
diff --git a/locales/en/LC_MESSAGES/privacyidea.po b/locales/en/LC_MESSAGES/privacyidea.po
index 8be873c..e10919b 100644
--- a/locales/en/LC_MESSAGES/privacyidea.po
+++ b/locales/en/LC_MESSAGES/privacyidea.po
@@ -117,6 +117,9 @@ msgstr "Try Again"
 msgid "{privacyidea:privacyidea:error_message}"
 msgstr "Verification was not successful. Please try again."
 
+msgid "{privacyidea:privacyidea:failcounter_error_message}"
+msgstr "Verification code you have entered is either incorrect or this method of authentication has been deactivated for your account due to numerous failed login attempts. If your login attempts using verification codes keep failing, please use a recovery code or a security key."
+
 msgid "{privacyidea:privacyidea:error}"
 msgstr "Error"
 
diff --git a/www/FormBuilder.php b/www/FormBuilder.php
index 4345d63..de5efde 100644
--- a/www/FormBuilder.php
+++ b/www/FormBuilder.php
@@ -45,6 +45,13 @@ if (
     $tpl->data['errorCode'] = ($state['privacyidea:privacyidea']['errorCode'] ?? null) ?: '';
     $state['privacyidea:privacyidea']['errorCode'] = '';
     $tpl->data['errorMessage'] = $tpl->t('{privacyidea:privacyidea:error_message}');
+
+    // replace custom error message placeholder
+    $errorMessage = $state['privacyidea:privacyidea']['errorMessage'];
+    if (stripos($errorMessage, "possible failcounter exceeded") !== false) {
+        $tpl->data['errorMessage'] = $tpl->t('{privacyidea:privacyidea:failcounter_error_message}');
+    }
+
     $state['privacyidea:privacyidea']['errorMessage'] = '';
     $stateId = State::saveState($state, 'privacyidea:privacyidea');
 }
-- 
GitLab