Skip to content

Commit

Permalink
CFM-333_Email_Verifier_Enroller_Plugin-chaset-configuration (#319)
Browse files Browse the repository at this point in the history
* Verfication Code charset dropdown and free form

* Review comment fixes

* Remove placeholder

* First time save render the dropdown by default.
  • Loading branch information
Ioannis authored Jun 10, 2025
1 parent a6c77a2 commit 5486f26
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 60 deletions.
12 changes: 0 additions & 12 deletions app/plugins/CoreAssigner/resources/locales/en_US/core_assigner.po
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,6 @@ msgstr "Random"
msgid "enumeration.CollisionModeEnum.S"
msgstr "Sequential"

msgid "enumeration.PermittedCharactersEnum.AN"
msgstr "AlphaNumeric Only"

msgid "enumeration.PermittedCharactersEnum.AD"
msgstr "AlphaNumeric and Dot, Dash, Underscore"

msgid "enumeration.PermittedCharactersEnum.AQ"
msgstr "AlphaNumeric and Dot, Dash, Underscore, Apostrophe"

msgid "enumeration.PermittedCharactersEnum.AL"
msgstr "Any"

msgid "error.SqlAssigners.failed"
msgstr "Could not map key Identifier to target Identifier"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,10 @@

namespace CoreAssigner\Model\Table;

use Cake\Datasource\ConnectionManager;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use App\Lib\Enum\PermittedCharactersEnum;
use Cake\ORM\Table;
use Cake\Validation\Validator;

use CoreAssigner\Lib\Enum\CollisionModeEnum;
use CoreAssigner\Lib\Enum\PermittedCharactersEnum;

class FormatAssignersTable extends Table {
use \App\Lib\Traits\AutoViewVarsTrait;
Expand Down Expand Up @@ -83,7 +79,7 @@ public function initialize(array $config): void {
],
'permittedCharacters' => [
'type' => 'enum',
'class' => 'CoreAssigner.PermittedCharactersEnum'
'class' => 'PermittedCharactersEnum'
]
]);

Expand Down
47 changes: 45 additions & 2 deletions app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
namespace CoreEnroller\Model\Table;

use App\Lib\Enum\EnrollmentActorEnum;
use App\Lib\Enum\PermittedCharactersEnum;
use App\Lib\Enum\PetitionStatusEnum;
use App\Lib\Enum\SuspendableStatusEnum;
use App\Lib\Enum\TableTypeEnum;
Expand Down Expand Up @@ -122,6 +123,10 @@ public function initialize(array $config): void {
'types' => [
'type' => 'auxiliary',
'model' => 'Types'
],
'permittedCharacters' => [
'type' => 'enum',
'class' => 'PermittedCharactersEnum'
]
]);

Expand Down Expand Up @@ -440,18 +445,26 @@ public function sendVerificationRequest(
])
->first();

[$charset, $regex] = $this->calculateVerificationCodeCharsetMode(
$emailVerifier->verification_code_charset,
$emailVerifier->verification_code_regex
);

if (empty($pVerification)) {
// Request Verification and create an associated Petition Verification

$this->llog('debug', "Sending verification code to $mail for Petition " . $petition->id);

// I need to figure out the allowed characters for the code.

$verificationId = $Verifications->requestCodeForPetition(
petitionId: $petition->id,
mail: $mail,
messageTemplateId: $emailVerifier->message_template_id,
validity: $emailVerifier->request_validity,
codeLength: !empty($emailVerifier->verification_code_length) ? $emailVerifier->verification_code_length : VerificationDefaultsEnum::DefaultCodeLength,
codeCharset: !empty($emailVerifier->verification_code_charset) ? $emailVerifier->verification_code_charset : VerificationDefaultsEnum::DefaultCharset,
codeCharset: $charset,
codeRegex: $regex,
);

$pVerification = $PetitionVerifications->saveOrFail(
Expand All @@ -474,7 +487,8 @@ public function sendVerificationRequest(
messageTemplateId: $emailVerifier->message_template_id,
validity: $emailVerifier->request_validity,
codeLength: !empty($emailVerifier->verification_code_length) ? $emailVerifier->verification_code_length : VerificationDefaultsEnum::DefaultCodeLength,
codeCharset: !empty($emailVerifier->verification_code_charset) ? $emailVerifier->verification_code_charset : VerificationDefaultsEnum::DefaultCharset,
codeCharset: $charset,
codeRegex: $regex,
verificationId: $pVerification->verification_id,
);
// There's nothing to update in the Petition Verification
Expand All @@ -485,6 +499,30 @@ public function sendVerificationRequest(
return false;
}


/**
* Calculate verification code charset based on provided charset or permitted characters.
* Returns default charset if neither is provided.
*
* @param ?string $charset Custom charset for verification code
* @param ?string $permitted Permitted characters enum value
* @return array Resolved charset to use for verification code
* @since COmanage Registry v5.1.0
*/
protected function calculateVerificationCodeCharsetMode(
?string $charset,
?string $permitted
): array {
if (empty($charset) && empty($permitted)) {
return [VerificationDefaultsEnum::DefaultCharset, null];
}
if (!empty($permitted)) {
return [null, PermittedCharactersEnum::getPermittedCharacters(enum: $permitted)];
}

return [$charset, null];
}

/**
* Set validation rules.
*
Expand Down Expand Up @@ -531,6 +569,11 @@ public function validationDefault(Validator $validator): Validator {
]);
$validator->allowEmptyString('verification_code_charset');

$validator->add('verification_code_regex', [
'content' => ['rule' => ['inList', PermittedCharactersEnum::getConstValues()]]
]);
$validator->allowEmptyString('verification_code_regex');

$validator
->add('verification_code_length', 'content', [
'rule' => 'isInteger',
Expand Down
3 changes: 2 additions & 1 deletion app/plugins/CoreEnroller/src/config/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"request_validity": { "type": "integer" },
"enable_blockonfailure": { "type": "boolean" },
"verification_code_length": { "type": "integer" },
"verification_code_charset": { "type": "string", "size": 64 }
"verification_code_charset": { "type": "string", "size": 64 },
"verification_code_regex": { "type": "string", "size": 2 }
},
"indexes": {
"email_verifiers_i1": { "columns": [ "enrollment_flow_step_id" ] },
Expand Down
79 changes: 72 additions & 7 deletions app/plugins/CoreEnroller/templates/EmailVerifiers/fields.inc
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,80 @@ print $this->element('form/listItem', [
]]
]);

print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'verification_code_charset',
'fieldOptions' => [
'required' => false,
'placeholder' => $defaultValues['DefaultCharset']
]]
// Textbox for "Other"
$otherField = $this->Form->control('verification_code_charset', [
'type' => 'text',
'label' => false,
'id' => 'verification-code-charset',
'style' => !empty($vv_obj?->verification_code_charset) ? '' : 'display: none;',
'required' => false,
]);

$checked = !empty($vv_obj?->verification_code_charset) ? 'checked' : '';
$otherLabel = __d('field','other.value');
$beforeField = <<<OTHER
<div class="field-suppliment">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="other-toggle" $checked/>
<label class="form-check-label" for="other-toggle">$otherLabel</label>
</div>
</div>
$otherField
OTHER;

$afterField = <<<'JS'
<script>
document.addEventListener('DOMContentLoaded', function() {
var checkbox = document.getElementById('other-toggle');
var select = document.getElementById('verification-code-regex');
var textbox = document.getElementById('verification-code-charset');
// Variables to hold previous values
var previousSelectValue = select.value;
var previousTextboxValue = textbox.value;
checkbox.addEventListener('change', function() {
if (checkbox.checked) {
select.style.display = 'none';
select.value = '';
textbox.style.display = '';
// Restore previous textbox value if it exists
if (previousTextboxValue !== undefined && previousTextboxValue !== '') {
textbox.value = previousTextboxValue;
}
} else {
select.style.display = '';
textbox.style.display = 'none';
textbox.value = '';
// Restore previous select value if it exists
if (previousSelectValue !== undefined && previousSelectValue !== '') {
select.value = previousSelectValue;
}
}
});
});
</script>
JS;

$regexDisplayFirstTime = empty($vv_obj?->verification_code_charset) && empty($vv_obj?->verification_code_regex);
print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'verification_code_regex',
'fieldLabel' => __d('core_enroller','field.EmailVerifiers.verification_code_charset'),
'fieldOptions' => [
'id' => 'verification-code-regex',
'required' => false,
'empty' => true,
'style' => $regexDisplayFirstTime || !empty($vv_obj?->verification_code_regex) ? '' : 'display: none;',
],
'fieldType' => 'select',
'fieldSelectOptions' => $permittedCharacters,
],
'beforeField' => $beforeField,
'afterField' => $afterField,
]
);

print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'verification_code_length',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,26 +124,27 @@
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');
})
});
// XXX Keep for now. This is the old way to handle the enter key
// $('#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>
12 changes: 12 additions & 0 deletions app/resources/locales/en_US/enumeration.po
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,18 @@ msgstr "Error Landing"
msgid "PageContextEnum.G"
msgstr "General"

msgid "PermittedCharactersEnum.AL"
msgstr "Any"

msgid "PermittedCharactersEnum.AN"
msgstr "AlphaNumeric Only"

msgid "PermittedCharactersEnum.AD"
msgstr "AlphaNumeric and Dot, Dash, Underscore"

msgid "PermittedCharactersEnum.AQ"
msgstr "AlphaNumeric and Dot, Dash, Underscore, Apostrophe"

msgid "PermittedNameFieldsEnum.given,family"
msgstr "Given, Family"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@

declare(strict_types = 1);

namespace CoreAssigner\Lib\Enum;

use App\Lib\Enum\StandardEnum;
namespace App\Lib\Enum;

class PermittedCharactersEnum extends StandardEnum {
const AlphaNumeric = 'AN';
Expand Down
26 changes: 21 additions & 5 deletions app/src/Lib/Random/RandomString.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

namespace App\Lib\Random;

use App\Lib\Enum\PermittedCharactersEnum;
use Random\RandomException;

class RandomString {
Expand Down Expand Up @@ -102,17 +103,32 @@ public static function generateCode(): string {
* @since COmanage Registry v5.0.0
*/

public static function generateToken(int $length = 16, string $allowedCharset = null): string
{
public static function generateToken(
int $length = 16,
string $allowedCharset = null,
string $regex = null, // Permitted
): string {
// Unlike App Keys, tokens don't have restrictions on characters.

$chars = $allowedCharset ?? 'abcdefghijklmnopqrstuvwxyz01234567890';

$token = "";

$allowedCharsetLength = strlen($chars) - 1;
for($i = 0;$i < $length;$i++) {
$token .= $chars[random_int(0, $allowedCharsetLength)];
if ($regex !== null) {
// We allow the characters that match the regex.
while(strlen($token) < $length) {
$ascii = random_int(32, 126); // 32 is space, 126 is '~'
$randomChar = chr($ascii);
if (preg_match('/'. $regex . '/', $randomChar) === 1) {
$token .= $randomChar;

}
}
} else {
$allowedCharsetLength = strlen($chars) - 1;
for($i = 0;$i < $length;$i++) {
$token .= $chars[random_int(0, $allowedCharsetLength)];
}
}

return $token;
Expand Down
8 changes: 5 additions & 3 deletions app/src/Model/Table/VerificationsTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ protected function recordHistory(
* @param int $messageTemplateId Message Template ID
* @param int $validity Request validity, in minutes
* @param int $codeLength
* @param string $codeCharset
* @param string|null $codeCharset
* @param string|null $codeRegex
* @param int|null $verificationId If set, resend Verification for this request
* @return int Verification ID
* @throws RandomException
Expand All @@ -261,11 +262,12 @@ public function requestCodeForPetition(
int $messageTemplateId,
int $validity,
int $codeLength,
string $codeCharset,
?string $codeCharset,
?string $codeRegex,
int $verificationId = null
): int {
// First generate a new code
$code = RandomString::generateToken($codeLength, $codeCharset);
$code = RandomString::generateToken($codeLength, $codeCharset, $codeRegex);
$expiry = date('Y-m-d H:i:s', time() + ($validity * 60));

$verification = null;
Expand Down

0 comments on commit 5486f26

Please sign in to comment.