diff --git a/README.md b/README.md index 60dd29a8a..d2a5f5b4d 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,15 @@ This project supports AI-based features using OpenAI. ```bash ddev terminus secret:site:set gizra-drupal-starter openai_api_key your-key-here --type=runtime --scope=web,user ``` +## reCAPTCHA Configuration + +1. Create a site in [Google reCAPTCHA](https://www.google.com/recaptcha/admin/create). +2. Add the required domains, then retrieve the site key and secret key from the reCAPTCHA settings. +3. Add these keys to the reCAPTCHA configuration at: +`/admin/config/people/captcha/recaptcha` +4. Save the configuration. + +Note: reCAPTCHA will not work without these keys. ## PHPCS (Code Sniffer) diff --git a/composer.json b/composer.json index d7f310a10..e8b2373e3 100644 --- a/composer.json +++ b/composer.json @@ -70,6 +70,7 @@ "drupal/pluggable_entity_view_builder": "^1.1", "drupal/quickedit": "^1.0", "drupal/real_aes": "^2.5", + "drupal/recaptcha": "^3.4", "drupal/redirect": "^1.6", "drupal/redis": "^1.7", "drupal/rollbar": "^2.1", diff --git a/composer.lock b/composer.lock index ca7d62939..b91bcad82 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9b3cb6d0fc0f78a87dab4d7a6748fdd9", + "content-hash": "7f717f44895ef455a2307bce9a418a53", "packages": [ { "name": "asm89/stack-cors", @@ -2232,6 +2232,86 @@ "source": "https://git.drupalcode.org/project/block_plugin_view_builder" } }, + { + "name": "drupal/captcha", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/captcha.git", + "reference": "2.0.8" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/captcha-2.0.8.zip", + "reference": "2.0.8", + "shasum": "7000566e8ee60d2aa4d0b038ba8d59ccf9663891" + }, + "require": { + "drupal/core": "^9.5 || ^10 || ^11" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "2.0.8", + "datestamp": "1753284367", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + }, + "branch-alias": { + "dev-8.x-1.x": "1.x-dev" + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "anybody", + "homepage": "https://www.drupal.org/user/291091" + }, + { + "name": "elachlan", + "homepage": "https://www.drupal.org/user/1021502" + }, + { + "name": "grevil", + "homepage": "https://www.drupal.org/user/3668491" + }, + { + "name": "japerry", + "homepage": "https://www.drupal.org/user/45640" + }, + { + "name": "naveenvalecha", + "homepage": "https://www.drupal.org/user/2665733" + }, + { + "name": "podarok", + "homepage": "https://www.drupal.org/user/116002" + }, + { + "name": "robloach", + "homepage": "https://www.drupal.org/user/61114" + }, + { + "name": "thomas.frobieter", + "homepage": "https://www.drupal.org/user/409335" + }, + { + "name": "wundo", + "homepage": "https://www.drupal.org/user/25523" + } + ], + "description": "The CAPTCHA module provides this feature to virtually any user facing web form on a Drupal site.", + "homepage": "https://www.drupal.org/project/captcha", + "support": { + "source": "https://git.drupalcode.org/project/captcha", + "issues": "https://www.drupal.org/project/issues/captcha" + } + }, { "name": "drupal/classy", "version": "1.0.2", @@ -5101,6 +5181,93 @@ "source": "https://git.drupalcode.org/project/real_aes" } }, + { + "name": "drupal/recaptcha", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/recaptcha.git", + "reference": "8.x-3.4" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/recaptcha-8.x-3.4.zip", + "reference": "8.x-3.4", + "shasum": "95fa7ac5dd064ea6a1c14fc4881778bf68200598" + }, + "require": { + "drupal/captcha": "^1.15 || ^2.0", + "drupal/core": "^10 || ^11", + "google/recaptcha": "^1.3" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "8.x-3.4", + "datestamp": "1723563033", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "hass", + "homepage": "https://www.drupal.org/u/hass" + }, + { + "name": "See other contributors", + "homepage": "https://www.drupal.org/node/147903/committers" + }, + { + "name": "diolan", + "homepage": "https://www.drupal.org/user/2336786" + }, + { + "name": "hass", + "homepage": "https://www.drupal.org/user/85918" + }, + { + "name": "id.medion", + "homepage": "https://www.drupal.org/user/2542592" + }, + { + "name": "kim.pepper", + "homepage": "https://www.drupal.org/user/370574" + }, + { + "name": "larowlan", + "homepage": "https://www.drupal.org/user/395439" + }, + { + "name": "liam morland", + "homepage": "https://www.drupal.org/user/493050" + }, + { + "name": "robloach", + "homepage": "https://www.drupal.org/user/61114" + }, + { + "name": "wundo", + "homepage": "https://www.drupal.org/user/25523" + }, + { + "name": "yseki", + "homepage": "https://www.drupal.org/user/1523064" + } + ], + "description": "Protect your website from spam and abuse while letting real people pass through with ease.", + "homepage": "https://www.drupal.org/project/recaptcha", + "support": { + "source": "https://git.drupalcode.org/project/recaptcha.git", + "issues": "https://www.drupal.org/project/issues/recaptcha" + } + }, { "name": "drupal/redirect", "version": "1.11.0", @@ -6814,6 +6981,58 @@ }, "time": "2022-08-05T20:32:58+00:00" }, + { + "name": "google/recaptcha", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/google/recaptcha.git", + "reference": "56522c261d2e8c58ba416c90f81a4cd9f2ed89b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/google/recaptcha/zipball/56522c261d2e8c58ba416c90f81a4cd9f2ed89b9", + "reference": "56522c261d2e8c58ba416c90f81a4cd9f2ed89b9", + "shasum": "" + }, + "require": { + "php": ">=8" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.14", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "ReCaptcha\\": "src/ReCaptcha" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Client library for reCAPTCHA, a free service that protects websites from spam and abuse.", + "homepage": "https://www.google.com/recaptcha/", + "keywords": [ + "Abuse", + "captcha", + "recaptcha", + "spam" + ], + "support": { + "forum": "https://groups.google.com/forum/#!forum/recaptcha", + "issues": "https://github.com/google/recaptcha/issues", + "source": "https://github.com/google/recaptcha" + }, + "time": "2025-06-26T22:21:57+00:00" + }, { "name": "grasmash/expander", "version": "3.0.1", @@ -17277,6 +17496,8 @@ "ext-json": "*", "ext-mbstring": "*" }, - "platform-dev": {}, + "platform-dev": { + "ext-posix": "*" + }, "plugin-api-version": "2.6.0" } diff --git a/config/sync/captcha.captcha_point.contact_message_personal_form.yml b/config/sync/captcha.captcha_point.contact_message_personal_form.yml new file mode 100644 index 000000000..2e56c6953 --- /dev/null +++ b/config/sync/captcha.captcha_point.contact_message_personal_form.yml @@ -0,0 +1,9 @@ +uuid: 99f9c715-53d9-4827-946d-736516318f9a +langcode: en +status: false +dependencies: { } +_core: + default_config_hash: 7zUmOK1ti1l0bc78ieysHa3_57MOin7IgHpHhwbSugs +formId: contact_message_personal_form +captchaType: default +label: contact_message_personal_form diff --git a/config/sync/captcha.captcha_point.node_landing_page_form.yml b/config/sync/captcha.captcha_point.node_landing_page_form.yml new file mode 100644 index 000000000..6846576d9 --- /dev/null +++ b/config/sync/captcha.captcha_point.node_landing_page_form.yml @@ -0,0 +1,7 @@ +uuid: c64bd524-122c-40e1-98cc-43de63f68573 +langcode: en +status: false +dependencies: { } +formId: node_landing_page_form +captchaType: default +label: node_landing_page_form diff --git a/config/sync/captcha.captcha_point.node_news_form.yml b/config/sync/captcha.captcha_point.node_news_form.yml new file mode 100644 index 000000000..2b4a05b4f --- /dev/null +++ b/config/sync/captcha.captcha_point.node_news_form.yml @@ -0,0 +1,7 @@ +uuid: 6016d008-b564-453b-92e3-ebd16b05a040 +langcode: en +status: false +dependencies: { } +formId: node_news_form +captchaType: default +label: node_news_form diff --git a/config/sync/captcha.captcha_point.user_login_form.yml b/config/sync/captcha.captcha_point.user_login_form.yml new file mode 100644 index 000000000..c592e3fa1 --- /dev/null +++ b/config/sync/captcha.captcha_point.user_login_form.yml @@ -0,0 +1,9 @@ +uuid: b2d8eaea-c507-4f86-9b86-74078dbb024e +langcode: en +status: false +dependencies: { } +_core: + default_config_hash: XbughDuwgOtc_8ztmYxz84gaXBeR760xH3bGSKD9v1Q +formId: user_login_form +captchaType: default +label: user_login_form diff --git a/config/sync/captcha.captcha_point.user_pass.yml b/config/sync/captcha.captcha_point.user_pass.yml new file mode 100644 index 000000000..49191de51 --- /dev/null +++ b/config/sync/captcha.captcha_point.user_pass.yml @@ -0,0 +1,9 @@ +uuid: 94c9d6b9-b605-454e-972f-247394f5d810 +langcode: en +status: false +dependencies: { } +_core: + default_config_hash: qZeHI5fZ9WQRKsfl8FMBwzk3puSsWCRtWKEZh04JJUo +formId: user_pass +captchaType: default +label: user_pass diff --git a/config/sync/captcha.captcha_point.user_register_form.yml b/config/sync/captcha.captcha_point.user_register_form.yml new file mode 100644 index 000000000..57db622a2 --- /dev/null +++ b/config/sync/captcha.captcha_point.user_register_form.yml @@ -0,0 +1,9 @@ +uuid: 330e1fc9-9804-4d04-acb5-dbd56818c8e7 +langcode: en +status: false +dependencies: { } +_core: + default_config_hash: 2iz3cMg7T1eSKXtNIB9WyaRptfg7wosDDPBSBhXFXl0 +formId: user_register_form +captchaType: default +label: user_register_form diff --git a/config/sync/captcha.settings.yml b/config/sync/captcha.settings.yml new file mode 100644 index 000000000..9ea4d7f39 --- /dev/null +++ b/config/sync/captcha.settings.yml @@ -0,0 +1,16 @@ +_core: + default_config_hash: hfqTr6E3eXAcVQU25_tYHa4BzsSDaYhwkoF34xq5_k4 +langcode: en +enable_globally: 0 +enable_globally_on_admin_routes: false +default_challenge: recaptcha/reCAPTCHA +description: 'This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.' +title: '' +administration_mode: false +administration_mode_on_admin_routes: false +whitelist_ips: '' +wrong_captcha_response_message: 'The answer you entered for the CAPTCHA was not correct.' +default_validation: 1 +persistence: 1 +enable_stats: false +log_wrong_responses: false diff --git a/config/sync/core.extension.yml b/config/sync/core.extension.yml index c1968f6f3..4bb6f72ed 100644 --- a/config/sync/core.extension.yml +++ b/config/sync/core.extension.yml @@ -16,6 +16,7 @@ module: block_content: 0 block_plugin_view_builder: 0 breakpoint: 0 + captcha: 0 ckeditor5: 0 config: 0 config_filter: 0 @@ -78,6 +79,7 @@ module: pluggable_entity_view_builder: 0 quickedit: 0 real_aes: 0 + recaptcha: 0 redirect: 0 redis: 0 responsive_image: 0 diff --git a/config/sync/recaptcha.settings.yml b/config/sync/recaptcha.settings.yml new file mode 100644 index 000000000..f16a7f271 --- /dev/null +++ b/config/sync/recaptcha.settings.yml @@ -0,0 +1,11 @@ +_core: + default_config_hash: ByOVf1cU9r5NkZ4ieBJ7k9sUid6jb03ojMN1gjJ0-OU +site_key: '' +secret_key: '' +verify_hostname: false +use_globally: false +widget: + theme: light + type: image + size: '' + noscript: false diff --git a/config/sync/ultimate_cron.job.captcha_cron.yml b/config/sync/ultimate_cron.job.captcha_cron.yml new file mode 100644 index 000000000..f081d46c5 --- /dev/null +++ b/config/sync/ultimate_cron.job.captcha_cron.yml @@ -0,0 +1,17 @@ +uuid: b5e42d53-9486-4032-8ddf-90010cc9d20f +langcode: en +status: true +dependencies: + module: + - captcha +title: 'Remove old sessions' +id: captcha_cron +weight: 0 +module: captcha +callback: 'captcha#cron' +scheduler: + id: simple +launcher: + id: serial +logger: + id: database diff --git a/config/sync/webform.webform.contact.yml b/config/sync/webform.webform.contact.yml index b90956799..6e6b0ec05 100644 --- a/config/sync/webform.webform.contact.yml +++ b/config/sync/webform.webform.contact.yml @@ -38,6 +38,9 @@ elements: |- '#type': textarea '#required': true '#test': 'Please ignore this email.' + captcha: + '#type': captcha + '#captcha_type': recaptcha/reCAPTCHA actions: '#type': webform_actions '#title': 'Submit button(s)' diff --git a/web/themes/custom/server_theme/templates/server-theme-header.html.twig b/web/themes/custom/server_theme/templates/server-theme-header.html.twig index c504e3bc9..2736234ec 100644 --- a/web/themes/custom/server_theme/templates/server-theme-header.html.twig +++ b/web/themes/custom/server_theme/templates/server-theme-header.html.twig @@ -41,3 +41,5 @@ + +{{ messages }} diff --git a/web/themes/custom/server_theme/templates/status-messages.html.twig b/web/themes/custom/server_theme/templates/status-messages.html.twig index c4bce2213..781ccad90 100644 --- a/web/themes/custom/server_theme/templates/status-messages.html.twig +++ b/web/themes/custom/server_theme/templates/status-messages.html.twig @@ -21,7 +21,7 @@ * @ingroup themeable */ #} -
+
{% for type, messages in message_list %} {% if type == 'error' %} {% set message_class = 'bg-red-400' %}