Discussion
Pegasystems Inc.
US
Last activity: 17 Jan 2025 19:11 EST
How to implement a CAPTCHA Challenge inside a form in Constellation
Websites present users with a CAPTCHA challenge, usually involving distorted text, images, or simple tasks. These challenges are typically easy for humans to solve but difficult for computers to decipher due to the distortions or the nature of the task.
In Pega traditional UI, it is possible to add a Captcha in a section by dropping the property pyCaptchaResponse - The custom control would call an activity that would generate the image and store the captcha temporary on the requestor page. The property uses an Edit validate rule called 'pzValidateCaptcha' that will check if the value entered by the user is the same as the one stored on the requestor page. The Captcha image is generated using the library https://github.com/rookiefly/simplecaptcha which creates a binary image.
In Constellation UI, it is not required to implement a Constellation DX component to achieve the core functionality of a Captcha - You can use 2 properties: one of type URL and one of type text to achieve the needed outcome. For improved styling or providing a refresh button, a Constellation component might be required. Also for a more robust solution, some 3rd party providers offers more sophisticated and bullet proof solutions instead of the one provided out of the box by the Pega Platform and can be plugged in into your application using a Constellation DX Component.
One of the challenge is to not send the captcha value to the browser and keep it on the server. Since the Constellation DX API is stateless, it is not possible to rely on the requestor page to store the temporary value of the captcha displayed on the screen. Instead we will persist the value using a data instance in System-User-Activity-Settings. The instance is already used in Constellation to store some of the ui behavior settings and is specific to a user and application.
Here are the steps the achieve the desired functionality:
1/ Create a property of type 'URL' to store the image
resave the property pyCaptchaResponse to your case type class - call it 'CaptchaImage' - make sure that the UI Control is set to 'pxURL' (it should be the case already) and that in the Advanced tab, the 'Do not save property data' is checked. Remove the use validate rule defined on the property. After saving the property, make it relevant record in the Actions menu
2/ Create a property of type 'Text' to store the user input
resave the property pyCaptchaResponse to your case type class - call it 'CaptchaUserResponse' - make sure that the UI Control is empty (instead of pxURL) and that in the Advanced tab, the 'Do not save property data' is checked. Add a new use validate Rule called 'ValidateCaptcha'.
The edit validate rule has the following content:
ClipboardPage reqPage = tools.findPage("pxRequestor"); String operatorID = reqPage.getString("pxUserIdentifier"); ClipboardPage appPage = tools.findPage("Application"); String appName = appPage.getString("pyProductName"); com.google.gson.JsonObject responseData = pega.getUIEngine().getConstellationUserSettingsUtils().getUserSettings(operatorID, appName); String captchaValue = responseData.get("captcha").getAsString(); String value = theProperty.getParentPage().getString("CaptchaUserResponse"); if(!captchaValue.equals(value)) { theProperty.addMessage("Please enter the correct image characters"); return false; } return true;
After saving the property, make it relevant record in the Actions menu
3/ In your case type, create a new partial view called 'Captcha'
create a partial view in the UX tab (not a form view) - partial view are read-only and will render the URL as an image. Select the CaptchaImage property - make it read only and render as an image - update the label to provide instructional text
4/ Add the 2 new captcha fields to one of your assignment
add the new partial Captcha view (add view) and the CaptchaUserResponse field in your form view
5/ Add a pre-processing activity in your flow action to load the image
create a new activity LoadCaptcha and add it as a pre-processing on your flow action
the activity is a single java step with the following code:
try { String skipProcessing = tools.getParamValue("skipPreRoboticAutomationInPostProcessing"); if(!"true".equals(skipProcessing)){ char[] DEFAULT_CHARS = new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'k', 'm', 'n', 'p', 'r', 'w', 'x', 'y', '2', '3', '4', '5', '6', '7', '8', }; nl.captcha.text.producer.DefaultTextProducer textProd = new nl.captcha.text.producer.DefaultTextProducer(6, DEFAULT_CHARS ); List DEFAULT_COLORS = new ArrayList(); List DEFAULT_FONTS = new ArrayList(); DEFAULT_COLORS.add(new java.awt.Color(0,51,102)); DEFAULT_FONTS.add(new java.awt.Font("Arial", java.awt.Font.PLAIN, 40)); nl.captcha.text.renderer.DefaultWordRenderer wordRenderer = new nl.captcha.text.renderer.DefaultWordRenderer(DEFAULT_COLORS, DEFAULT_FONTS); nl.captcha.Captcha captcha = new nl.captcha.Captcha.Builder(175, 50).addText(textProd , wordRenderer) .gimp(new nl.captcha.gimpy.RippleGimpyRenderer()) .build(); // create a new captcha java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); javax.imageio.ImageIO.write(captcha.getImage(), "png", baos); byte[] bytesOut = baos.toByteArray(); HashStringMap params = new HashStringMap(); params.put("contentType", "image/png"); tools.sendFile(bytesOut,"captchaImage.png",false,params,false); // send the captcha image to browser String base64ImageString = java.util.Base64.getEncoder().encodeToString(bytesOut); myStepPage.putString("CaptchaImage", "data:image/png;base64," + base64ImageString); myStepPage.putString("CaptchaUserResponse", ""); ClipboardPage reqPage = tools.findPage("pxRequestor"); String operatorID = reqPage.getString("pxUserIdentifier"); ClipboardPage appPage = tools.findPage("Application"); String appName = appPage.getString("pyProductName"); com.google.gson.JsonObject responseData = new com.google.gson.JsonObject(); String payload = "{\"captcha\": \"" + captcha.getAnswer() + "\"}"; com.google.gson.JsonObject inputData = new com.google.gson.Gson().fromJson(payload, com.google.gson.JsonObject.class); //saving data in database. com.google.gson.JsonObject userSettingStream = pega.getUIEngine().getConstellationUserSettingsUtils().updateUserSettings(operatorID, appName, inputData); } } catch(java.io.IOException e) { oLog.error(e.getMessage()); e.printStackTrace(); }
At runtime, the 2 properties will not be stored inside the case data since they are set as do not persist. The edit validate set on CaptchaUserResponse will fire on submit or save and will check against the captcha stored as a user setting - if it is different, the edit validate will throw an error message and return false.
Here is a screenshot at runtime