diff --git a/dictionaries/privacyidea.definition.json b/dictionaries/privacyidea.definition.json index 9cf5d288f5ddbef6f09d19f1c0692674fc2561be..27694ba7b359ea4bda84f93a9bd7feec39cab357 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 ccfceef395861ca9a1b391dd57eb0f3ca0e91b52..2b1368c841d8dd5033ad13a80bbc9484e9c5656f 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 5e7d456de5e2da87de1888616fc371bb57b7a156..adff02f9bd55ec7d4e331e53c667733cddbb0ed6 100644 --- a/lib/Auth/Utils.php +++ b/lib/Auth/Utils.php @@ -103,7 +103,28 @@ class Utils self::handlePrivacyIDEAException($e, $state); } } + } elseif ($formParams['mode'] === 'totp') { + try { + // 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); + } } else { + // Backup code validation try { $response = $pi->validateCheck($username, $formParams['otp'], $transactionID); } catch (\Exception $e) { @@ -318,7 +339,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 52bd280daba1072b6fee10684d3e4474fdb71129..65a541ef1ff2b96689668979a42c34bcc2afa30e 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 8be873cc0a986587c1e3ff593d9cbb7fe064b297..e10919bf92b32a6252a1e60acdf82abc7c7f25be 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 4345d639f8963f6c57d8ec8c7be52f334257721d..de5efde8ecf52555aebb27688332e700b0550462 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'); } diff --git a/www/js/pi-webauthn.js b/www/js/pi-webauthn.js index c47db7611dd3b265cb0dd90b3703bfef08b28e15..b92844cdda3180a8ac0c55ba10788f67ae6dca6e 100644 --- a/www/js/pi-webauthn.js +++ b/www/js/pi-webauthn.js @@ -53,14 +53,14 @@ var pi_webauthn = navigator.credentials ? window.pi_webauthn || {} : null; return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 - ? nChr - 71 - : nChr > 47 && nChr < 58 - ? nChr + 4 - : nChr === 43 - ? 62 - : nChr === 47 - ? 63 - : 0; + ? nChr - 71 + : nChr > 47 && nChr < 58 + ? nChr + 4 + : nChr === 43 + ? 62 + : nChr === 47 + ? 63 + : 0; }; /** @@ -80,14 +80,14 @@ var pi_webauthn = navigator.credentials ? window.pi_webauthn || {} : null; return nUint6 < 26 ? nUint6 + 65 : nUint6 < 52 - ? nUint6 + 71 - : nUint6 < 62 - ? nUint6 - 4 - : nUint6 === 62 - ? 43 - : nUint6 === 63 - ? 47 - : 65; + ? nUint6 + 71 + : nUint6 < 62 + ? nUint6 - 4 + : nUint6 === 62 + ? 43 + : nUint6 === 63 + ? 47 + : 65; }; /** @@ -245,26 +245,26 @@ var pi_webauthn = navigator.credentials ? window.pi_webauthn || {} : null; aBytes[++nIdx] - 128 : nPart > 247 && nPart < 252 && nIdx + 4 < nLen - ? ((nPart - 248) << 24) + - ((aBytes[++nIdx] - 128) << 18) + - ((aBytes[++nIdx] - 128) << 12) + - ((aBytes[++nIdx] - 128) << 6) + - aBytes[++nIdx] - - 128 - : nPart > 239 && nPart < 248 && nIdx + 3 < nLen - ? ((nPart - 240) << 18) + - ((aBytes[++nIdx] - 128) << 12) + - ((aBytes[++nIdx] - 128) << 6) + - aBytes[++nIdx] - - 128 - : nPart > 223 && nPart < 240 && nIdx + 2 < nLen - ? ((nPart - 224) << 12) + - ((aBytes[++nIdx] - 128) << 6) + - aBytes[++nIdx] - - 128 - : nPart > 191 && nPart < 224 && nIdx + 1 < nLen - ? ((nPart - 192) << 6) + aBytes[++nIdx] - 128 - : nPart, + ? ((nPart - 248) << 24) + + ((aBytes[++nIdx] - 128) << 18) + + ((aBytes[++nIdx] - 128) << 12) + + ((aBytes[++nIdx] - 128) << 6) + + aBytes[++nIdx] - + 128 + : nPart > 239 && nPart < 248 && nIdx + 3 < nLen + ? ((nPart - 240) << 18) + + ((aBytes[++nIdx] - 128) << 12) + + ((aBytes[++nIdx] - 128) << 6) + + aBytes[++nIdx] - + 128 + : nPart > 223 && nPart < 240 && nIdx + 2 < nLen + ? ((nPart - 224) << 12) + + ((aBytes[++nIdx] - 128) << 6) + + aBytes[++nIdx] - + 128 + : nPart > 191 && nPart < 224 && nIdx + 1 < nLen + ? ((nPart - 192) << 6) + aBytes[++nIdx] - 128 + : nPart, ); } @@ -297,14 +297,14 @@ var pi_webauthn = navigator.credentials ? window.pi_webauthn || {} : null; nChr < 0x80 ? 1 : nChr < 0x800 - ? 2 - : nChr < 0x10000 - ? 3 - : nChr < 0x200000 - ? 4 - : nChr < 0x4000000 - ? 5 - : 6; + ? 2 + : nChr < 0x10000 + ? 3 + : nChr < 0x200000 + ? 4 + : nChr < 0x4000000 + ? 5 + : 6; } aBytes = new Uint8Array(nArrLen);