diff --git a/frontend/src/entrypoints/captcha.tsx b/frontend/src/entrypoints/captcha.tsx new file mode 100644 index 000000000..4f4cbbd70 --- /dev/null +++ b/frontend/src/entrypoints/captcha.tsx @@ -0,0 +1,46 @@ +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. + +// Entrypoint which mounts the React `Captcha` component as an inline +// "island" on server-rendered pages. The server templates emit a +// placeholder element carrying the CAPTCHA configuration: +// +//
+// +// and include this entrypoint, which mounts the widget onto every such +// element. This is the pattern we intend to use for progressively +// enhancing server-rendered pages with React components. + +import { StrictMode, Suspense } from "react"; +import { createRoot } from "react-dom/client"; +import * as v from "valibot"; +import { Captcha, CaptchaPlaceholder } from "../components/Captcha"; + +const schema = v.object({ + captchaService: v.picklist([ + "recaptcha_v2", + "cloudflare_turnstile", + "hcaptcha", + ]), + captchaSiteKey: v.string(), +}); + +for (const node of document.querySelectorAll( + "[data-captcha-service]", +)) { + const config = v.parse(schema, node.dataset); + createRoot(node).render( + + }> + + + , + ); +} diff --git a/templates/base.html b/templates/base.html index 8f4bddcea..2113a34fd 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,4 +1,5 @@ {# +Copyright 2025, 2026 Element Creations Ltd. Copyright 2024, 2025 New Vector Ltd. Copyright 2021-2024 The Matrix.org Foundation C.I.C. @@ -15,7 +16,6 @@ Please see LICENSE files in the repository root for full details. {% import "components/errors.html" as errors %} {% import "components/icon.html" as icon %} {% import "components/scope.html" as scope %} -{% import "components/captcha.html" as captcha %} @@ -27,7 +27,6 @@ Please see LICENSE files in the repository root for full details. {{ include_asset('src/entrypoints/shared.css') | indent(4) | safe }} {{ include_asset('src/entrypoints/templates.css') | indent(4) | safe }} {{ include_asset('src/entrypoints/templates.ts') | indent(4) | safe }} - {{ captcha.head() }} - {%- if captcha_config.service == "recaptcha_v2" -%} -
- {%- elif captcha_config.service == "cloudflare_turnstile" -%} -
- {%- elif captcha_config.service == "hcaptcha" -%} -
- {%- else -%} - {{ throw(message="Invalid captcha service setup") }} - {%- endif %} + {{ include_asset('src/entrypoints/captcha.tsx') }} +
{%- endif -%} {% endmacro %} - -{% macro head() -%} - {%- if captcha_config|default(False) -%} - {%- if captcha_config.service == "recaptcha_v2" -%} - - {%- elif captcha_config.service == "cloudflare_turnstile" -%} - - {%- elif captcha_config.service == "hcaptcha" -%} - - {%- else -%} - {{ throw(message="Invalid captcha service setup") }} - {%- endif %} - {%- endif -%} -{%- endmacro %} diff --git a/templates/pages/register/password.html b/templates/pages/register/password.html index f1a77efad..08e71fe1e 100644 --- a/templates/pages/register/password.html +++ b/templates/pages/register/password.html @@ -1,4 +1,5 @@ {# +Copyright 2025, 2026 Element Creations Ltd. Copyright 2024, 2025 New Vector Ltd. Copyright 2021-2024 The Matrix.org Foundation C.I.C. @@ -8,6 +9,8 @@ Please see LICENSE files in the repository root for full details. {% extends "base.html" %} +{% import "components/captcha.html" as captcha %} + {% block content %}
diff --git a/translations/en.json b/translations/en.json index 320fed5e0..c0b1ecad8 100644 --- a/translations/en.json +++ b/translations/en.json @@ -10,7 +10,7 @@ }, "continue": "Continue", "@continue": { - "context": "form_post.html:25:28-48, pages/consent.html:71:28-48, pages/device_link.html:42:28-48, pages/login.html:68:30-50, pages/reauth.html:32:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:77:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/registration_token.html:41:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:52:28-48" + "context": "form_post.html:25:28-48, pages/consent.html:71:28-48, pages/device_link.html:42:28-48, pages/login.html:68:30-50, pages/reauth.html:32:28-48, pages/recovery/start.html:38:26-46, pages/register/password.html:80:26-46, pages/register/steps/display_name.html:43:28-48, pages/register/steps/registration_token.html:41:28-48, pages/register/steps/verify_email.html:51:26-46, pages/sso.html:52:28-48" }, "create_account": "Create Account", "@create_account": { @@ -79,7 +79,7 @@ }, "email_address": "Email address", "@email_address": { - "context": "pages/recovery/start.html:34:33-58, pages/register/password.html:40:35-60, pages/upstream_oauth2/do_register.html:115:37-62" + "context": "pages/recovery/start.html:34:33-58, pages/register/password.html:43:35-60, pages/upstream_oauth2/do_register.html:115:37-62" }, "loading": "Loading…", "@loading": { @@ -91,15 +91,15 @@ }, "password": "Password", "@password": { - "context": "pages/login.html:56:37-57, pages/reauth.html:28:35-55, pages/register/password.html:45:33-53" + "context": "pages/login.html:56:37-57, pages/reauth.html:28:35-55, pages/register/password.html:48:33-53" }, "password_confirm": "Confirm password", "@password_confirm": { - "context": "pages/register/password.html:49:33-61" + "context": "pages/register/password.html:52:33-61" }, "username": "Username", "@username": { - "context": "pages/login.html:51:39-59, pages/register/index.html:30:35-55, pages/register/password.html:34:33-53, pages/upstream_oauth2/do_register.html:101:35-55, pages/upstream_oauth2/do_register.html:107:39-59" + "context": "pages/login.html:51:39-59, pages/register/index.html:30:35-55, pages/register/password.html:37:33-53, pages/upstream_oauth2/do_register.html:101:35-55, pages/upstream_oauth2/do_register.html:107:39-59" } }, "error": { @@ -149,7 +149,7 @@ "captcha": { "noscript": "This form is protected by a CAPTCHA and requires JavaScript to be enabled to submit it. Please enable JavaScript in your browser and reload this page.", "@noscript": { - "context": "components/captcha.html:13:11-36" + "context": "components/captcha.html:14:11-36" } }, "change_password": { @@ -660,7 +660,7 @@ "register": { "call_to_login": "Already have an account?", "@call_to_login": { - "context": "pages/register/index.html:63:35-66, pages/register/password.html:80:33-64", + "context": "pages/register/index.html:63:35-66, pages/register/password.html:83:33-64", "description": "Displayed on the registration page to suggest to log in instead" }, "continue_with_email": "Continue with email address", @@ -678,12 +678,12 @@ }, "heading": "Create an account", "@heading": { - "context": "pages/register/index.html:21:29-69, pages/register/password.html:18:27-67" + "context": "pages/register/index.html:21:29-69, pages/register/password.html:21:27-67" } }, "terms_of_service": "I agree to the Terms and Conditions", "@terms_of_service": { - "context": "pages/register/password.html:54:35-95, pages/upstream_oauth2/do_register.html:180:35-95" + "context": "pages/register/password.html:57:35-95, pages/upstream_oauth2/do_register.html:180:35-95" } }, "registration_token": {