Skip to content

Commit

Permalink
Add block on failure configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
Ioannis committed Apr 28, 2025
1 parent 07932a3 commit 7f7afa0
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 28 deletions.
7 changes: 6 additions & 1 deletion app/config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
1 change: 1 addition & 0 deletions app/plugins/CoreEnroller/src/config/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
},
Expand Down
8 changes: 8 additions & 0 deletions app/plugins/CoreEnroller/templates/EmailVerifiers/fields.inc
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,11 @@ print $this->element('form/listItem', [
'placeholder' => $defaultValues['DefaultCodeLength']
]]
]);

print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'enable_blockonfailure',
'fieldOptions' => [
'default' => true,
]]
]);
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -118,3 +120,35 @@
'emailAddress' => $m,
'vv_config' => $vv_config,
]) ?>

<script type="text/javascript">
$(document).ready(function() {
// See https://stackoverflow.com/a/25665232 , 'Update' version
history.pushState(null, null, document.URL);
window.addEventListener('popstate', function () {
history.pushState(null, null, document.URL);
});

$('#code').bind('keypress', function (event) {
if (event.charCode === 13) {
$("#verification-code-form").submit();
} else {
// Allow for regular characters and include these special few:
// comma, period, explanation point, new line
var regex = new RegExp("^[a-zA-Z0-9\-]+$");
var key = String.fromCharCode(!event.charCode ? event.which : event.charCode);
if (!regex.test(key)) {
event.preventDefault();
return false;
}
}
});

$('#code').on('keyup', function() {
$(this).val (function () {
return this.value.toUpperCase();
}).trigger('change');
})
});

</script>
10 changes: 10 additions & 0 deletions app/src/Lib/Traits/ApplicationStatesTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
2 changes: 0 additions & 2 deletions app/templates/element/notify/blockUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
return;
}

// todo: remove hardcoded debug value
$vv_attempts_count = 3;
$retrySeconds = pow($vv_attempts_count, 2);

?>
Expand Down

0 comments on commit 7f7afa0

Please sign in to comment.