Description#
The json_captcha module feature adds CAPTCHA checks to runtime JSON API calls. reCAPTCHA v3 uses it to protect functions such as Runtime_Customer_Login, and any module can use it to add its own bot check.
The feature has 2 parts:
- A server-side function,
Module_JSON_CAPTCHA, that approves or blocks each runtime JSON request. - A client-side registry in
ajax.js. Each registered implementation can add parameters, such as a CAPTCHA token, to a runtime request before it’s sent.
AJAX_Call_JSON and AJAX_Call_Module_JSON run the registry for every runtime call, so existing JavaScript that uses them is covered without changes.
CAPTCHA tokens are usually tied to the endpoint being called and have to be generated when the shopper acts, so they can’t be fetched ahead of time. For that reason, runtime JSON requests are now always asynchronous.
Requirements#
Miva 26.03.00 or later.
Server side: Module_JSON_CAPTCHA#
Add json_captcha to your module’s features and implement Module_JSON_CAPTCHA.
<MvFUNCTION NAME = "Module_Description" PARAMETERS = "module var" STANDARDOUTPUTLEVEL = "">
<MvASSIGN NAME = "l.module:code" VALUE = "mycaptcha">
<MvASSIGN NAME = "l.module:name" VALUE = "My CAPTCHA">
<MvASSIGN NAME = "l.module:provider" VALUE = "Example">
<MvASSIGN NAME = "l.module:version" VALUE = "1.0000">
<MvASSIGN NAME = "l.module:api_ver" VALUE = "5.72">
<MvASSIGN NAME = "l.module:features" VALUE = "json_captcha">
</MvFUNCTION>
<MvFUNCTION NAME = "Module_JSON_CAPTCHA" PARAMETERS = "module var" STANDARDOUTPUTLEVEL = "">
<MvCOMMENT>Build the function reference for the request</MvCOMMENT>
<MvIF EXPR = "{ g.Function EQ 'Module' }">
<MvASSIGN NAME = "l.function" VALUE = "{ 'Module:' $ g.Module_Code $ ':' $ g.Module_Function }">
<MvELSE>
<MvASSIGN NAME = "l.function" VALUE = "{ g.Function }">
</MvIF>
<MvCOMMENT>Permit every function this module doesn't protect</MvCOMMENT>
<MvIF EXPR = "{ NOT MyCaptcha_Function_Is_Protected( l.function ) }">
<MvFUNCTIONRETURN VALUE = 1>
</MvIF>
<MvIF EXPR = "{ NOT MyCaptcha_Verify_Token( trim( g.MyCaptcha_Token ) ) }">
<MvFUNCTIONRETURN VALUE = "{ [ g.Module_JSON ].JSON_Response_Error( 'MYCAPTCHA-00001', 'CAPTCHA verification failed' ) }">
</MvIF>
<MvFUNCTIONRETURN VALUE = 1>
</MvFUNCTION>
MyCaptcha_Function_Is_Protected and MyCaptcha_Verify_Token in the example stand in for your module’s own logic. g.MyCaptcha_Token is the parameter the module’s client-side implementation adds to the request.
Miva calls Module_JSON_CAPTCHA in every installed json_captcha module, for every runtime JSON request, while the store is open.
| Return | Result |
|---|---|
1 |
The request is permitted. |
0 |
The request is blocked. Call JSON_Response_Error with the reason before returning 0.That error is sent back to the client, and the requested function doesn’t run. |
g.Function holds the requested function. For module functions, g.Function is Module, and g.Module_Code and g.Module_Function identify the module function. The Module:<module code>:<function> reference in the example is the same format API token function permissions use.
A module sees every runtime request, so it should return 1 for any function it doesn’t protect.
Client side: ajax.js#
AJAX_Runtime_CAPTCHA_Register( implementation )#
Registers a CAPTCHA implementation. Implementations run in the order they’re registered.
ajax.js calls each implementation as implementation( endpoint, parameters ):
| Argument | Description |
|---|---|
endpoint |
The JSON endpoint being called. See the table below. |
parameters |
The request parameters so far, including fields added by implementations that ran earlier. |
The endpoint object:
| Member | Description |
|---|---|
Function |
Always present, and always the first member. The JSON API function, for example Runtime_BasketItem_Update. For a module function, this is Module. |
Module_Code |
Present only when Function is Module. |
Module_Function |
Present only when Function is Module. |
The implementation returns one of these:
| Return value | Result |
|---|---|
undefined |
The parameters aren’t changed. Return undefined for endpoints the implementation doesn’t protect. |
| A plain object | Its fields are merged into the parameters. |
| A Promise | The fields it resolves to are merged into the parameters. |
Merged fields can overwrite existing parameters.
If an implementation throws or its Promise rejects, the request isn’t sent. The caller’s callback receives this response:
| Field | Value |
|---|---|
success |
0 |
error_code |
#Error# |
error_message |
Verification failed., followed by the endpoint members and the error text. |
Example#
This implementation adds a Google reCAPTCHA v3 token to Runtime_BasketItem_Update calls and leaves every other request unchanged.
AJAX_Runtime_CAPTCHA_Register( ( endpoint, params ) =>
{
if ( endpoint.Function !== 'Runtime_BasketItem_Update' )
{
return;
}
return new Promise( ( resolve, reject ) =>
{
grecaptcha.ready( () =>
{
try
{
grecaptcha.execute( site_key, { action: 'example_action' } ).then( resolve, reject );
}
catch ( e )
{
reject( e );
}
} );
} ).then( token => ( { reCAPTCHAv3_Response: token } ) );
} );
AJAX_Runtime_CAPTCHA_Prepare( endpoint, parameters )#
Runs every registered implementation in order. It returns a Promise that resolves to the input parameters plus every field the implementations added, merged in registration order. The input object isn’t modified.
The Promise rejects if any implementation throws or rejects.
AJAX_Call_JSON and AJAX_Call_Module_JSON call it for you. Call it directly only if you send runtime requests some other way.
AJAX_Runtime_CAPTCHA_Request#
For runtime calls, AJAX_Call_JSON and AJAX_Call_Module_JSON return an AJAX_Runtime_CAPTCHA_Request in place of an XMLHttpRequest. It adds the runtime session parameters, runs AJAX_Runtime_CAPTCHA_Prepare, and sends the request once the parameters are ready.
| Member | Behavior |
|---|---|
onload, onreadystatechange |
Passed to the real request once it exists. Handlers assigned before then are applied when the request is created. |
abort() |
Before the CAPTCHA implementations finish, the request is never sent and no callback runs, including a later verification failure. After the request is sent, it calls the real request’s abort(). |
No other XMLHttpRequest members are supported.
If the server returns an invalid response, the callback receives success 0, status, error_code #Error#, and an error_message with the status and response text.