Skip to content

Conversation

@brafreider
Copy link

@brafreider brafreider commented Dec 9, 2025

This PR includes Altcha as an additional Captcha option. See https://altcha.org for details. Altcha uses PoW to check if the user is real. PoW is not a 100% proof, but it will need javascript to pass and waste spammer's ressources, depending on the complexity level you choose.

The main advantage, why we use Altcha in several projects is, that it can be used without external dependencies, which is perfect for GDPR compliance.

you can find a form, that uses this integration on https://visual4.de/kontakt/

I have taken the help of Kiro, so the PR is partly vibe coded, without, it would have taken much longer.

I have added:

  • Documentation
  • Tests
  • Translation
  • Tested in Mautic 5 with Preview and Wordpress integration
  • Tested in Mautic 6 (form preview)
  • Tested in Mautic 7
  • code to automatically add the HMAC-Token

- Implemented AltchaIntegration class for plugin configuration
- Added AltchaClient service for challenge generation and validation
- Created AltchaType form type with configurable options
- Implemented AltchaFormSubscriber for event handling
- Added Altcha widget template with invisible mode support
- Registered all services and event listeners
- Added English and German translations
- Implemented comprehensive test suite (19 tests, 23 assertions)
- All property-based tests passing (8 properties validated)
- GDPR-compliant: no external API calls, local validation only
- Run PHPUnit tests on PHP 8.1
- Create production ZIP package (excludes dev files)
- Upload package to GitLab Package Registry
- Trigger on main, tags, and develop branches
- Install bcmath, gd, imap, sockets, and zip extensions
- Configure gd with freetype and jpeg support
- Configure imap with kerberos and ssl support
- Resolves composer dependency installation errors
- Remove libc-client-dev and libkrb5-dev (not available in Debian 13)
- Skip IMAP extension as it's only needed by Mautic core, not our tests
- Use --ignore-platform-req=ext-imap for composer install
- Keep bcmath, gd, sockets, and zip extensions for actual test requirements
- Add 'docker' tag to test job
- Add 'docker' tag to package job
- Ensures jobs run on Docker executor instead of shell executor
- Remove LoggerInterface parameter from AltchaClient constructor to match other captcha clients
- Add challenge generation in AltchaFormSubscriber.onFormBuild()
- Update altcha.html.twig template to handle challenge data correctly
- Update AltchaClientTest to reflect constructor changes
- All tests passing (19/19)
…rdPress - Add event propagation stopping to prevent jQuery infinite loops - Add detailed logging for debugging validation issues - Improve base64 payload decoding - Add UTC timezone for challenge creation - Isolate widget in container to prevent event bubbling
- Add expires parameter to createChallenge() method to properly encode expiration time in salt
- Fix verify() method to handle both base64-encoded and JSON payloads
- Fix test to use base64-encoded payload as required by Altcha library
- All 20 tests now pass successfully
@brafreider brafreider marked this pull request as ready for review December 9, 2025 17:21
- Extract inline JavaScript to separate altcha-handler.js file
- Prevent 'Unexpected end of input' error when Mautic embeds forms
- Improve event handling and jQuery conflict prevention
- Add proper script loading management for embedded forms
- Maintain backward compatibility with existing functionality
@brafreider brafreider marked this pull request as draft December 11, 2025 14:07
- Add retry logic for widget initialization
- Improve script loading with proper timing
- Add comprehensive logging for debugging
- Ensure DOM readiness before widget initialization
- Fix widget visibility issues in embedded forms
- Remove overly complex altcha-handler.js
- Use simple script tag loading approach
- Let web developers handle multiple forms themselves
- Fix original JavaScript parsing error with minimal changes
- Use JavaScript to create script tag with type='module'
- Prevents Mautic from stripping the module type attribute
- Fixes 'unexpected export' error in browser
- Add AltchaExtension Twig extension for runtime challenge generation
- Add AltchaController with AJAX endpoint for dynamic challenge loading
- Update altcha.html.twig to use Twig function instead of cached challenge
- Remove challenge generation from AltchaFormSubscriber onFormBuild
- Register new services and routes in config.php

This fixes the issue where cached forms would fail after challenge expiration
by generating fresh challenges on every form render.
Use imported class name instead of fully qualified class name to avoid autoload issues.
- Remove AltchaExtension.php that caused cache:clear issues
- Update altcha.html.twig to use AJAX challenge loading
- Remove Twig extension registration from config.php
- Keep AltchaController for dynamic challenge generation

This approach avoids Mautic service container issues while still solving the caching problem.
Replace path() function with hardcoded URL to avoid routing issues.
The route is defined but Mautic may not recognize it immediately after deployment.
brafreider and others added 13 commits December 12, 2025 14:41
- Add AltchaApiController with secure /altcha/api/challenge endpoint
- Use ALTCHA native challengeurl attribute instead of custom JavaScript
- Fixed security issue: no user-controllable parameters in API
- Update templates to use challengeurl with refetchonexpire
- Add comprehensive documentation for API endpoint
- Update deployment configuration to include Controller directory
- Remove complex JavaScript in favor of native ALTCHA features
- Version bump to 1.1.1

Fixes caching issues where ALTCHA challenges were cached with forms,
causing expired challenges. Now each form load gets a fresh challenge
via the secure API endpoint.
- Fix controller route: use service reference instead of bundle syntax
- Fix challengeurl: make absolute URL to work with embedded forms
- Controller now properly registered as 'mautic.altcha.controller.api:generateChallengeAction'
- challengeurl now uses absolute_url() to work from external domains

Fixes 500 error when calling API endpoint and relative URL issues
when forms are embedded in external websites.
- Move controller from 'others' to 'controllers' service section
- Remove constructor injection, use container->get() instead
- Use direct class reference in route: AltchaApiController::class
- Follow Mautic controller patterns for proper DI container access

Fixes 'no container set' error when calling API endpoint.
- Change from CommonController to AbstractController to avoid complex dependencies
- Restore constructor injection with AltchaClient parameter
- Add arguments back to controller service registration
- Use Symfony's AbstractController for simpler API controller pattern

Fixes ArgumentCountError with CommonController constructor.
- Remove controller inheritance completely (no extends)
- Use plain PHP class with constructor injection
- Reference controller as service in route configuration
- Avoid Symfony/Mautic container conflicts with simple approach

This should resolve the 'no container set' service subscriber error.
- Remove @authors, @Package, @license tags that Doctrine tries to parse
- Keep only essential documentation without @ symbols
- Fix AnnotationException caused by non-standard @ tags in comments

Doctrine Annotations parser treats all @ symbols as potential annotations.
- Update endpoint path from /altcha/challenge to /altcha/api/challenge
- Add X-Altcha-Spam-Filter header to allowed CORS headers
- Remove unnecessary form/submit and general API locations
- Focus on essential ALTCHA-specific CORS configuration

Fixes CORS error: 'Request header field x-altcha-spam-filter is not allowed'
NGINX improvements:
- Add exact location match for /altcha/api/challenge
- Add broader regex location as fallback
- Include Cache-Control header for ALTCHA compatibility
- Add debug endpoint for testing
- Use 'always' directive to ensure headers are set

PHP Controller improvements:
- Handle OPTIONS preflight requests in controller
- Add CORS headers to all responses (success and error)
- Ensure CORS works even if nginx config is not applied
- Dual-layer CORS protection (nginx + PHP)

This should resolve persistent CORS issues with cross-origin requests.
@fmm-rwalraven fmm-rwalraven marked this pull request as ready for review January 19, 2026 14:27
@fmm-rwalraven
Copy link
Collaborator

Hey @brafreider,

Thank you for your PR! I noticed you also translated the strings of the original bundle to German in messages.ini. Thank you for the extra effort!

This has got to be one of the cleanest PRs I have ever seen. I'm very impressed. Even if it was with help of an LLM.

I took the liberty of cleaning up a little before merging.

@fmm-rwalraven fmm-rwalraven self-assigned this Jan 19, 2026
@fmm-rwalraven fmm-rwalraven added the enhancement New feature or request label Jan 19, 2026
@brafreider
Copy link
Author

@fmm-rwalraven wait a minute, there is a little issue with mautic form caching. I had to change some code after creating this PR.

@fmm-rwalraven fmm-rwalraven marked this pull request as draft January 19, 2026 14:31
@fmm-rwalraven
Copy link
Collaborator

@brafreider Marked as draft again. Let me know when you have it resolved! :)

@brafreider
Copy link
Author

the issue was that I did not find a way to prevent Mautic from caching the challenge token. After adding Altcha, to a form the tests succeeded, but after the challenge lifetime had passed, the form could not be submitted any more, until the cache was invalidated.

I solved this, by adding an API endpoint to retrieve the challenge. This works, but currently the configuration value for challenge expiration has no effect, as the endpoint does not know which form it is called from.

This means, that the value is hard coded at the moment, it could be changed in a global configuration value and of course the field level configuration should be removed.

- Removed maxNumber and expires fields from individual field configuration (AltchaType)
- Added maxNumber and expires as optional fields to global ALTCHA integration settings
- Updated AltchaClient with getConfiguration() method to retrieve global settings
- Modified API endpoint to use global configuration values with fallback defaults (50000/120)
- Added comprehensive tests for configuration retrieval and API endpoint behavior
- All 28 tests passing with 106 assertions
- Changed from getAvailableFields() to appendToForm() method
- Added NumberType and Range constraint imports
- Fields maxNumber and expires now properly display in plugin configuration
- Maintained validation constraints (1000-1000000 for maxNumber, 10-300 for expires)
- Default values: maxNumber=50000, expires=120
@brafreider
Copy link
Author

@fmm-rwalraven I have now removed the configuration of max-number and lifetime from field configuration and added both to the plugin configuration dialog. Additionally, I have added unit tests and validated functionality in our Mautic5 sandbox.

@brafreider brafreider marked this pull request as ready for review January 21, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants