From 7815dd3de9eda0e882d23fd424deaab03f3a4c06 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Fri, 25 Apr 2025 19:11:57 +0300 Subject: [PATCH] Add block on failure configuration --- app/config/routes.php | 7 +- .../resources/locales/en_US/core_enroller.po | 7 ++ .../Controller/EmailVerifiersController.php | 6 ++ .../src/Model/Table/EmailVerifiersTable.php | 5 ++ .../CoreEnroller/src/config/plugin.json | 1 + .../templates/EmailVerifiers/fields.inc | 8 ++ .../element/emailVerifiers/verify.php | 84 +++++++++++++------ app/src/Lib/Traits/ApplicationStatesTrait.php | 10 +++ app/templates/element/notify/blockUser.php | 2 - 9 files changed, 102 insertions(+), 28 deletions(-) diff --git a/app/config/routes.php b/app/config/routes.php index 2bf695e7b..bd049807f 100644 --- a/app/config/routes.php +++ b/app/config/routes.php @@ -96,7 +96,12 @@ ['_namePrefix' => 'apiAjaxV2:'], function (RouteBuilder $builder) { // Register scoped middleware for in scopes. - $builder->registerMiddleware('csrf', new CsrfProtectionMiddleware(['httponly' => true])); + $builder->registerMiddleware('csrf', new CsrfProtectionMiddleware([ + 'httponly' => true, + 'secure' => true, + 'cookieName' => 'csrfToken', + 'accessibleHeaders' => ['X-CSRF-Token'], + ])); // BodyParserMiddleware will automatically parse JSON bodies, but we only // want that for API transactions, so we only apply it to the /api scope. $builder->registerMiddleware('bodyparser', new BodyParserMiddleware()); diff --git a/app/plugins/CoreEnroller/resources/locales/en_US/core_enroller.po b/app/plugins/CoreEnroller/resources/locales/en_US/core_enroller.po index aaf25b65f..d53ad5dfb 100644 --- a/app/plugins/CoreEnroller/resources/locales/en_US/core_enroller.po +++ b/app/plugins/CoreEnroller/resources/locales/en_US/core_enroller.po @@ -196,6 +196,13 @@ msgstr "Verification Code Length" msgid "field.EmailVerifiers.verification_code_length.desc" msgstr "Set the verification code length. Default length is 8." +msgid "field.EmailVerifiers.enable_blockonfailure" +msgstr "Enable Attempt Blocker" + +msgid "field.EmailVerifiers.enable_blockonfailure.desc" +msgstr "Enables blocking after consecutive failed attempts. Blocking duration increases exponentially based on the number of failed attempts." + + msgid "field.EnrollmentAttributes.address_required_fields" msgstr "Required Address Fields" diff --git a/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php b/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php index fb7df71f0..5e8e1e820 100644 --- a/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php +++ b/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php @@ -33,6 +33,7 @@ use App\Lib\Enum\PetitionStatusEnum; use App\Lib\Util\StringUtilities; use Cake\Http\Exception\BadRequestException; +use Cake\I18n\FrozenTime; use Cake\ORM\TableRegistry; use CoreEnroller\Lib\Enum\VerificationModeEnum; use \App\Lib\Enum\HttpStatusCodesEnum; @@ -185,6 +186,11 @@ public function dispatch(string $id) { } else { $PetitionVerifications = TableRegistry::getTableLocator()->get('CoreEnroller.PetitionVerifications'); $pVerification = $PetitionVerifications->getPetitionVerification($petition->id, $mail, false); + // Reset the counter if nothing happened for the last 30 minutes + if (!empty($pVerification->modified) && !$pVerification->modified->wasWithinLast('30 minute')) { + $pVerification->attempts_count = 0; + $PetitionVerifications->save($pVerification); + } // Tell dispatch.inc to render a verification form $this->set('vv_verify_address', $mail); diff --git a/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php b/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php index 538edf552..0f3a08b38 100644 --- a/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php +++ b/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php @@ -555,6 +555,11 @@ public function validationDefault(Validator $validator): Validator { ]); $validator->allowEmptyString('verification_code_length'); + $validator->add('enable_blockonfailure', [ + 'content' => ['rule' => ['boolean']] + ]); + $validator->allowEmptyString('enable_blockonfailure'); + return $validator; } } diff --git a/app/plugins/CoreEnroller/src/config/plugin.json b/app/plugins/CoreEnroller/src/config/plugin.json index a60a8864d..ed2552daf 100644 --- a/app/plugins/CoreEnroller/src/config/plugin.json +++ b/app/plugins/CoreEnroller/src/config/plugin.json @@ -44,6 +44,7 @@ "mode": { "type": "string", "size": 2 }, "message_template_id": {}, "request_validity": { "type": "integer" }, + "enable_blockonfailure": { "type": "boolean" }, "verification_code_length": { "type": "integer" }, "verification_code_charset": { "type": "string", "size": 64 } }, diff --git a/app/plugins/CoreEnroller/templates/EmailVerifiers/fields.inc b/app/plugins/CoreEnroller/templates/EmailVerifiers/fields.inc index f89a18429..1bd7a2c6c 100644 --- a/app/plugins/CoreEnroller/templates/EmailVerifiers/fields.inc +++ b/app/plugins/CoreEnroller/templates/EmailVerifiers/fields.inc @@ -68,3 +68,11 @@ print $this->element('form/listItem', [ 'placeholder' => $defaultValues['DefaultCodeLength'] ]] ]); + +print $this->element('form/listItem', [ + 'arguments' => [ + 'fieldName' => 'enable_blockonfailure', + 'fieldOptions' => [ + 'default' => true, + ]] +]); diff --git a/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php b/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php index ce130c5dd..ae443a6bc 100644 --- a/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php +++ b/app/plugins/CoreEnroller/templates/element/emailVerifiers/verify.php @@ -31,31 +31,33 @@ use App\Lib\Util\StringUtilities; use Cake\Routing\Router; -$stateAttr = ApplicationStateEnum::VerifyEmailBlocked; -$appStateValue = $this->ApplicationState->getValue($stateAttr, 'lock'); -$appStateId = $this->ApplicationState->getId($stateAttr); - -if ($vv_attempts_count > 0 && $appStateValue !== 'unlock') { - $currentUrl = Router::url(null, true); - print $this->element('notify/blockUser', compact( - 'vv_attempts_count', - 'currentUrl', - 'stateAttr', - 'appStateId', - )); - return; -} - -// Reset the locker -if ($appStateValue !== 'lock') { - $data = [ - 'tag' => $stateAttr, - 'value' => 'lock', - 'username' => $vv_user['username'] ?? '', - 'co_id' => $vv_cur_co->id, - 'person_id' => $vv_person_id ?? $vv_user_roles["person_id"] ?? null - ]; - $this->ApplicationState->updateState($data, (int)$appStateId); +if (filter_var($vv_config->enable_blockonfailure, FILTER_VALIDATE_BOOLEAN)) { + $stateAttr = ApplicationStateEnum::VerifyEmailBlocked; + $appStateValue = $this->ApplicationState->getValue($stateAttr, 'lock'); + $appStateId = $this->ApplicationState->getId($stateAttr); + + if ($vv_attempts_count > 0 && $appStateValue !== 'unlock') { + $currentUrl = Router::url(null, true); + print $this->element('notify/blockUser', compact( + 'vv_attempts_count', + 'currentUrl', + 'stateAttr', + 'appStateId', + )); + return; + } + + // Reset the locker + if ($appStateValue !== 'lock') { + $data = [ + 'tag' => $stateAttr, + 'value' => 'lock', + 'username' => $vv_user['username'] ?? '', + 'co_id' => $vv_cur_co->id, + 'person_id' => $vv_person_id ?? $vv_user_roles["person_id"] ?? null + ]; + $this->ApplicationState->updateState($data, (int)$appStateId); + } } $this->Field->enableFormEditMode(); @@ -118,3 +120,35 @@ 'emailAddress' => $m, 'vv_config' => $vv_config, ]) ?> + + diff --git a/app/src/Lib/Traits/ApplicationStatesTrait.php b/app/src/Lib/Traits/ApplicationStatesTrait.php index 1da3b5089..ec9e468d7 100644 --- a/app/src/Lib/Traits/ApplicationStatesTrait.php +++ b/app/src/Lib/Traits/ApplicationStatesTrait.php @@ -107,6 +107,16 @@ public function getId(string $stateTag): string } + /** + * Update or create a state record in the ApplicationStates table. + * + * @param array $data The data to be saved. + * @param int|null $id The ID of the record to update, or null to create a new record. + * + * @return void + * @throws \Cake\ORM\Exception\PersistenceFailedException When the save operation fails. + * @since COmanage Registry v5.2.0 + */ public function updateState(array $data, ?int $id): void { $appstate = TableRegistry::getTableLocator()->get('ApplicationStates'); diff --git a/app/templates/element/notify/blockUser.php b/app/templates/element/notify/blockUser.php index c3001c32c..71fc8024e 100644 --- a/app/templates/element/notify/blockUser.php +++ b/app/templates/element/notify/blockUser.php @@ -6,8 +6,6 @@ return; } -// todo: remove hardcoded debug value -$vv_attempts_count = 3; $retrySeconds = pow($vv_attempts_count, 2); ?>