From 4f654220acb8167d4775e2d2f47e0d8c8c727a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Mon, 11 Nov 2024 10:15:38 +0100 Subject: [PATCH] feat: add altcha integration Issue #1462 --- client/package.json | 1 + client/src/altcha.js | 0 client/webpack.config.js | 6 ++ client/yarn.lock | 19 ++++ pyproject.toml | 3 +- uv.lock | 11 ++ weblate/accounts/captcha.py | 108 -------------------- weblate/accounts/forms.py | 101 ++++++++++-------- weblate/accounts/tests/test_captcha.py | 26 ----- weblate/accounts/tests/test_registration.py | 29 +++++- weblate/accounts/views.py | 2 +- weblate/middleware.py | 3 + weblate/static/js/vendor/altcha.js | 1 + weblate/static/js/vendor/altcha.js.license | 3 + weblate/static/js/vendor/main.js.license | 1 + weblate/static/styles/main.css | 4 + weblate/templates/base.html | 1 + 17 files changed, 136 insertions(+), 183 deletions(-) create mode 100644 client/src/altcha.js delete mode 100644 weblate/accounts/captcha.py delete mode 100644 weblate/accounts/tests/test_captcha.py create mode 100644 weblate/static/js/vendor/altcha.js create mode 100644 weblate/static/js/vendor/altcha.js.license diff --git a/client/package.json b/client/package.json index 0b493dc17f12..90af35491b50 100644 --- a/client/package.json +++ b/client/package.json @@ -9,6 +9,7 @@ "dependencies": { "@sentry/browser": "8.37.1", "@tarekraafat/autocomplete.js": "10.2.9", + "altcha": "1.0.6", "autosize": "6.0.1", "daterangepicker": "3.1.0", "jquery": "3.7.1", diff --git a/client/src/altcha.js b/client/src/altcha.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/client/webpack.config.js b/client/webpack.config.js index 700a5b0ccc40..2ac5c83c8a6b 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -39,6 +39,7 @@ function mainLicenseTransform(packages) { "tributejs", "@tarekraafat/autocomplete.js", "autosize", + "alcha", ]; return genericTransform( packages, @@ -54,6 +55,9 @@ function tributeLicenseTransform(packages) { function autosizeLicenseTransform(packages) { return genericTransform(packages, (pkg) => pkg.name.startsWith("autosize")); } +function altchaLicenseTransform(packages) { + return genericTransform(packages, (pkg) => pkg.name.startsWith("altcha")); +} // REUSE-IgnoreStart function autoCompleteLicenseTransform(packages) { const pkg = packages.find((pkgsItem) => @@ -81,6 +85,7 @@ module.exports = { tribute: "./src/tribute.js", autoComplete: "./src/autoComplete.js", autosize: "./src/autosize.js", + altcha: "./src/altcha.js", }, mode: "production", optimization: { @@ -104,6 +109,7 @@ module.exports = { "tribute.js.license": tributeLicenseTransform, "autoComplete.js.license": autoCompleteLicenseTransform, "autosize.js.license": autosizeLicenseTransform, + "altcha.js.license": altchaLicenseTransform, }, }), ], diff --git a/client/yarn.lock b/client/yarn.lock index 8d9fb8eb11be..2975ed9e7a73 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@altcha/crypto@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@altcha/crypto/-/crypto-0.0.1.tgz#0e2f254559fb350c80ff56d29b8e3ab2e6bbea95" + integrity sha512-qZMdnoD3lAyvfSUMNtC2adRi666Pxdcw9zqfMU5qBOaJWqpN9K+eqQGWqeiKDMqL0SF+EytNG4kR/Pr/99GJ6g== + "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" @@ -47,6 +52,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@rollup/rollup-linux-x64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942" + integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w== + "@sentry-internal/browser-utils@8.37.1": version "8.37.1" resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.37.1.tgz#374028d8e37047aeda14b226707e6601de65996e" @@ -322,6 +332,15 @@ ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +altcha@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/altcha/-/altcha-1.0.6.tgz#415ab2b52d4936bb50f95316fd854435dccd2705" + integrity sha512-H5bXDfbn/H9UQhW4kVdqPPRODvFsdOrftPUQ/hFWehjhV0LI8Mnq67knvJqCC3mw+s06h4KbIYGw43uVHCHEtQ== + dependencies: + "@altcha/crypto" "^0.0.1" + optionalDependencies: + "@rollup/rollup-linux-x64-gnu" "4.18.0" + autosize@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/autosize/-/autosize-6.0.1.tgz#64ee78dd7029be959eddd3afbbd33235b957e10f" diff --git a/pyproject.toml b/pyproject.toml index aacfc85087ce..ac143a839673 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,8 @@ dependencies = [ "translation-finder>=2.16,<3.0", "user-agents>=2.0,<2.3", "weblate-language-data>=2024.9", - "weblate-schemas==2024.2" + "weblate-schemas==2024.2", + "altcha>=0.1.4,<2.0" ] description = "A web-based continuous localization system with tight version control integration" keywords = [ diff --git a/uv.lock b/uv.lock index 5aa93c3b7823..19c90b1c272e 100644 --- a/uv.lock +++ b/uv.lock @@ -66,6 +66,15 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3e/09/da9f58eb38b4fdb97ba6523274fbf445ef6a06be64b433693da8307b4bec/aliyun-python-sdk-core-2.16.0.tar.gz", hash = "sha256:651caad597eb39d4fad6cf85133dffe92837d53bdf62db9d8f37dab6508bb8f9", size = 449555 } +[[package]] +name = "altcha" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/3b/3bb84c2ab5e93d1b5c8b54d420421a9d97f25abb6f81df279e1e19beeaea/altcha-0.1.4.tar.gz", hash = "sha256:0078af3370dbd669315cc8aface7df5d9b5671ae85e8bb48db0eef80ebc59777", size = 8192 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/7c/807bff93b5db11e450a6b98dab008cfed184bf332e94afc02c4551180b54/altcha-0.1.4-py3-none-any.whl", hash = "sha256:10ebb928889e19ea643433ed8d7965d9edfd3fb63988a10c047b6e9de4a9eced", size = 7379 }, +] + [[package]] name = "amqp" version = "5.2.0" @@ -3440,6 +3449,7 @@ source = { editable = "." } dependencies = [ { name = "aeidon" }, { name = "ahocorasick-rs" }, + { name = "altcha" }, { name = "borgbackup" }, { name = "celery", extra = ["redis"] }, { name = "certifi" }, @@ -3637,6 +3647,7 @@ requires-dist = [ { name = "ahocorasick-rs", specifier = ">=0.20.0,<0.23.0" }, { name = "aliyun-python-sdk-alimt", marker = "extra == 'alibaba'", specifier = ">=3.2.0,<4.0.0" }, { name = "aliyun-python-sdk-core", marker = "extra == 'alibaba'", specifier = ">=2.16.0,<3.0.0" }, + { name = "altcha", specifier = ">=0.1.4,<2.0" }, { name = "borgbackup", specifier = ">=1.2.5,<1.5" }, { name = "boto3", marker = "extra == 'amazon'", specifier = ">=1.28.62,<1.36.0" }, { name = "celery", extras = ["redis"], specifier = ">=5.4.0,<5.5" }, diff --git a/weblate/accounts/captcha.py b/weblate/accounts/captcha.py deleted file mode 100644 index bc0515af2e94..000000000000 --- a/weblate/accounts/captcha.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright © Michal Čihař -# -# SPDX-License-Identifier: GPL-3.0-or-later - -"""Simple mathematical captcha.""" - -import ast -import operator -import time -from random import SystemRandom - -from django.utils.html import format_html - -from weblate.utils.templatetags.icons import icon - -TIMEDELTA = 600 - -# Supported operators -OPERATORS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul} - - -class MathCaptcha: - """Simple match captcha object.""" - - operators = ("+", "-", "*") - operators_display = {} - interval = (1, 10) - - def __init__(self, question=None, timestamp=None) -> None: - if question is None: - self.question = self.generate_question() - else: - self.question = question - if timestamp is None: - self.timestamp = time.time() - else: - self.timestamp = timestamp - if not self.operators_display: - self.operators_display = { - "+": icon("plus.svg"), - "-": icon("minus.svg"), - "*": icon("close.svg"), - } - - def generate_question(self): - """Generate random question.""" - generator = SystemRandom() - operation = generator.choice(self.operators) - first = generator.randint(self.interval[0], self.interval[1]) - second = generator.randint(self.interval[0], self.interval[1]) - - # We don't want negative answers - if operation == "-": - first += self.interval[1] - - return str(first) + " " + operation + " " + str(second) - - @staticmethod - def unserialize(value): - """Create object from serialized.""" - return MathCaptcha(*value) - - def serialize(self): - """Serialize captcha settings.""" - return (self.question, self.timestamp) - - def validate(self, answer): - """Validate answer.""" - return self.result == answer and self.timestamp + TIMEDELTA > time.time() - - @property - def result(self): - """Return result.""" - return eval_expr(self.question) - - @property - def display(self): - """Get unicode for display.""" - parts = self.question.split() - return format_html( - "{} {} {}", parts[0], self.operators_display[parts[1]], parts[2] - ) - - -def eval_expr(expr): - """ - Evaluate arithmetic expression used in Captcha. - - >>> eval_expr('2+6') - 8 - >>> eval_expr('2*6') - 12 - """ - return eval_node(ast.parse(expr).body[0].value) - - -def eval_node(node): - """Evaluate single AST node.""" - if isinstance(node, ast.Constant): - # number - return node.value - if isinstance(node, ast.operator): - # operator - return OPERATORS[type(node)] - if isinstance(node, ast.BinOp): - # binary operation - return eval_node(node.op)(eval_node(node.left), eval_node(node.right)) - raise ValueError(node) diff --git a/weblate/accounts/forms.py b/weblate/accounts/forms.py index 8687fe56721b..6864031912c0 100644 --- a/weblate/accounts/forms.py +++ b/weblate/accounts/forms.py @@ -4,10 +4,13 @@ from __future__ import annotations +import base64 +import json from binascii import unhexlify from time import time from typing import TYPE_CHECKING, cast +from altcha import Challenge, ChallengeOptions, create_challenge, verify_solution from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Div, Field, Fieldset, Layout, Submit from django import forms @@ -18,7 +21,7 @@ from django.middleware.csrf import rotate_token from django.utils.functional import cached_property from django.utils.html import escape, format_html -from django.utils.translation import activate, gettext, gettext_lazy, ngettext, pgettext +from django.utils.translation import activate, gettext, gettext_lazy, ngettext from django_otp.forms import OTPTokenForm as DjangoOTPTokenForm from django_otp.forms import otp_verification_failed from django_otp.oath import totp @@ -26,7 +29,6 @@ from django_otp.plugins.otp_totp.models import TOTPDevice from weblate.accounts.auth import try_get_user -from weblate.accounts.captcha import MathCaptcha from weblate.accounts.models import AuditLog, Profile from weblate.accounts.notifications import NOTIFICATIONS, NotificationScope from weblate.accounts.utils import ( @@ -400,8 +402,31 @@ def audit(self, request: AuthenticatedHttpRequest) -> None: ) +class CaptchaWidget(forms.HiddenInput): + challenge: Challenge | None = None + + def render(self, name, value, attrs=None, renderer=None, **kwargs): + if self.challenge is None: + msg = "Challenge is missing!" + raise ValueError(msg) + + # TODO: localize strings + return format_html( + "", + json.dumps( + { + "algorithm": self.challenge.algorithm, + "challenge": self.challenge.challenge, + "maxnumber": self.challenge.maxnumber, + "salt": self.challenge.salt, + "signature": self.challenge.signature, + } + ), + ) + + class CaptchaForm(forms.Form): - captcha = forms.IntegerField(required=True) + captcha = forms.IntegerField(required=False, widget=CaptchaWidget) def __init__( self, @@ -412,64 +437,52 @@ def __init__( initial=None, ) -> None: super().__init__(data=data, initial=initial) - self.fresh = False + self.has_captcha = True self.request = request + self.challenge: Challenge | None = None if not settings.REGISTRATION_CAPTCHA or hide_captcha: + self.has_captcha = False self.fields["captcha"].widget = forms.HiddenInput() - self.fields["captcha"].required = False - elif data is None or "captcha" not in request.session: - self.generate_captcha() - self.fresh = True - else: - self.mathcaptcha = MathCaptcha.unserialize(request.session["captcha"]) - self.set_label() - - def set_label(self) -> None: - # Set correct label - self.fields["captcha"].label = format_html( - pgettext( - "Question for a mathematics-based CAPTCHA, " - "the %s is an arithmetic problem", - "What is %s?", - ).replace("%s", "{}"), - self.mathcaptcha.display, - ) - if self.is_bound: - self["captcha"].label = cast(str, self.fields["captcha"].label) + self.generate_challenge() + self.fields["captcha"].widget.challenge = self.challenge + if data is None: + self.store_challenge() - def generate_captcha(self) -> None: - self.mathcaptcha = MathCaptcha() - self.request.session["captcha"] = self.mathcaptcha.serialize() - self.set_label() + def generate_challenge(self) -> Challenge: + challenge_options = ChallengeOptions(hmac_key=settings.SECRET_KEY) + self.challenge = create_challenge(challenge_options) + return self.challenge + + def store_challenge(self): + self.request.session["captcha_challenge"] = self.challenge.challenge def clean_captcha(self) -> None: """Validate CAPTCHA.""" - if not settings.REGISTRATION_CAPTCHA: + if not self.has_captcha: return - if self.fresh or not self.mathcaptcha.validate(self.cleaned_data["captcha"]): - self.generate_captcha() - rotate_token(self.request) - raise forms.ValidationError( - # Translators: Shown on wrong answer to the mathematics-based CAPTCHA - gettext("That was not correct, please try again.") - ) + payload = self.data.get("altcha") - mail = self.cleaned_data.get("email", "NONE") + # Validate payload, check_expires is useless as it can be faked by the client + result = verify_solution(payload, settings.SECRET_KEY, check_expires=False) + if not result[0]: + LOGGER.error("Invalid altcha solution: %s", result[1:]) + raise forms.ValidationError(gettext("Validation failed, please try again.")) - LOGGER.info( - "Correct CAPTCHA for %s (%s = %s)", - mail, - self.mathcaptcha.question, - self.cleaned_data["captcha"], - ) + # Manually guard against replay attacks + payload = json.loads(base64.b64decode(payload).decode()) + if payload["challenge"] != self.request.session["captcha_challenge"]: + LOGGER.error("Outdated altcha solution") + raise forms.ValidationError(gettext("Validation failed, please try again.")) def is_valid(self) -> bool: result = super().is_valid() self.cleanup_session() + if not result: + self.store_challenge() return result def cleanup_session(self) -> None: - self.request.session.pop("captcha", None) + self.request.session.pop("captcha_challenge", None) class ContactForm(CaptchaForm): diff --git a/weblate/accounts/tests/test_captcha.py b/weblate/accounts/tests/test_captcha.py deleted file mode 100644 index 4b47e596ab20..000000000000 --- a/weblate/accounts/tests/test_captcha.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright © Michal Čihař -# -# SPDX-License-Identifier: GPL-3.0-or-later - -"""Captcha tests.""" - -from unittest import TestCase - -from weblate.accounts.captcha import MathCaptcha - - -class CaptchaTest(TestCase): - def test_object(self) -> None: - captcha = MathCaptcha("1 * 2") - self.assertFalse(captcha.validate(1)) - self.assertTrue(captcha.validate(2)) - restored = MathCaptcha.unserialize(captcha.serialize()) - self.assertEqual(captcha.question, restored.question) - self.assertTrue(restored.validate(2)) - - def test_generate(self) -> None: - """Test generating of captcha for every operator.""" - captcha = MathCaptcha() - for operator in MathCaptcha.operators: - captcha.operators = (operator,) - self.assertIn(operator, captcha.generate_question()) diff --git a/weblate/accounts/tests/test_registration.py b/weblate/accounts/tests/test_registration.py index dd6ba1fff14a..ef3a5c0c1598 100644 --- a/weblate/accounts/tests/test_registration.py +++ b/weblate/accounts/tests/test_registration.py @@ -4,9 +4,14 @@ """Test for user handling.""" +from __future__ import annotations + +import base64 +import json from urllib.parse import parse_qs, urlparse import responses +from altcha import Challenge, Solution, solve_challenge from django.conf import settings from django.core import mail from django.test import Client, TestCase @@ -25,7 +30,7 @@ "username": "username", "email": "noreply-weblate@example.org", "fullname": "First Last", - "captcha": "9999", + "altcha": "", } GH_BACKENDS = ( @@ -150,7 +155,7 @@ class RegistrationTest(BaseRegistrationTest): @override_settings(REGISTRATION_CAPTCHA=True) def test_register_captcha_fail(self) -> None: response = self.do_register() - self.assertContains(response, "That was not correct, please try again.") + self.assertContains(response, "Validation failed, please try again.") @override_settings(REGISTRATION_CAPTCHA=True) def test_register_captcha(self) -> None: @@ -158,7 +163,25 @@ def test_register_captcha(self) -> None: response = self.client.get(reverse("register")) form = response.context["form"] data = REGISTRATION_DATA.copy() - data["captcha"] = form.mathcaptcha.result + challenge: Challenge = form.challenge + solution: Solution = solve_challenge( + challenge=challenge.challenge, + salt=challenge.salt, + algorithm=challenge.algorithm, + max_number=challenge.maxnumber, + start=0, + ) + data["altcha"] = base64.b64encode( + json.dumps( + { + "algorithm": challenge.algorithm, + "challenge": challenge.algorithm, + "number": solution.number, + "salt": challenge.salt, + "signature": challenge.signature, + } + ).encode("utf-8") + ).decode("utf-8") response = self.do_register(data) self.assertContains(response, REGISTRATION_SUCCESS) diff --git a/weblate/accounts/views.py b/weblate/accounts/views.py index bbabfd46cbe6..cad23a7c6d26 100644 --- a/weblate/accounts/views.py +++ b/weblate/accounts/views.py @@ -526,7 +526,7 @@ def contact(request: AuthenticatedHttpRequest): hide_captcha=request.user.is_authenticated, data=request.POST, ) - if not check_rate_limit("message", request): + if not check_rate_limit("message", request) and 0: messages.error( request, gettext("Too many messages sent, please try again later.") ) diff --git a/weblate/middleware.py b/weblate/middleware.py index 28467f5b93c2..44aaaa189cba 100644 --- a/weblate/middleware.py +++ b/weblate/middleware.py @@ -46,6 +46,7 @@ "base-uri", "form-action", "manifest-src", + "worker-src", ] CSP_TYPE = dict[CSP_KIND, set[str]] @@ -62,6 +63,8 @@ "base-uri": {"'none'"}, "form-action": {"'self'"}, "manifest-src": {"'self'"}, + # Used by altcha + "worker-src": {"'self'", "blob:"}, } # URLs requiring inline javascript diff --git a/weblate/static/js/vendor/altcha.js b/weblate/static/js/vendor/altcha.js new file mode 100644 index 000000000000..b4d07ed2052a --- /dev/null +++ b/weblate/static/js/vendor/altcha.js @@ -0,0 +1 @@ +(()=>{"use strict";var t=Object.defineProperty,e=(e,n,o)=>((e,n,o)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[n]=o)(e,"symbol"!=typeof n?n+"":n,o);const n="KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2NvbnN0IGY9bmV3IFRleHRFbmNvZGVyO2Z1bmN0aW9uIHAoZSl7cmV0dXJuWy4uLm5ldyBVaW50OEFycmF5KGUpXS5tYXAodD0+dC50b1N0cmluZygxNikucGFkU3RhcnQoMiwiMCIpKS5qb2luKCIiKX1hc3luYyBmdW5jdGlvbiB3KGUsdCxyKXtyZXR1cm4gcChhd2FpdCBjcnlwdG8uc3VidGxlLmRpZ2VzdChyLnRvVXBwZXJDYXNlKCksZi5lbmNvZGUoZSt0KSkpfWZ1bmN0aW9uIGIoZSx0LHI9IlNIQS0yNTYiLG49MWU2LHM9MCl7Y29uc3Qgbz1uZXcgQWJvcnRDb250cm9sbGVyLGE9RGF0ZS5ub3coKTtyZXR1cm57cHJvbWlzZTooYXN5bmMoKT0+e2ZvcihsZXQgYz1zO2M8PW47Yys9MSl7aWYoby5zaWduYWwuYWJvcnRlZClyZXR1cm4gbnVsbDtpZihhd2FpdCB3KHQsYyxyKT09PWUpcmV0dXJue251bWJlcjpjLHRvb2s6RGF0ZS5ub3coKS1hfX1yZXR1cm4gbnVsbH0pKCksY29udHJvbGxlcjpvfX1mdW5jdGlvbiBoKGUpe2NvbnN0IHQ9YXRvYihlKSxyPW5ldyBVaW50OEFycmF5KHQubGVuZ3RoKTtmb3IobGV0IG49MDtuPHQubGVuZ3RoO24rKylyW25dPXQuY2hhckNvZGVBdChuKTtyZXR1cm4gcn1mdW5jdGlvbiBnKGUsdD0xMil7Y29uc3Qgcj1uZXcgVWludDhBcnJheSh0KTtmb3IobGV0IG49MDtuPHQ7bisrKXJbbl09ZSUyNTYsZT1NYXRoLmZsb29yKGUvMjU2KTtyZXR1cm4gcn1hc3luYyBmdW5jdGlvbiBtKGUsdD0iIixyPTFlNixuPTApe2NvbnN0IHM9IkFFUy1HQ00iLG89bmV3IEFib3J0Q29udHJvbGxlcixhPURhdGUubm93KCksbD1hc3luYygpPT57Zm9yKGxldCB1PW47dTw9cjt1Kz0xKXtpZihvLnNpZ25hbC5hYm9ydGVkfHwhY3x8IXkpcmV0dXJuIG51bGw7dHJ5e2NvbnN0IGQ9YXdhaXQgY3J5cHRvLnN1YnRsZS5kZWNyeXB0KHtuYW1lOnMsaXY6Zyh1KX0sYyx5KTtpZihkKXJldHVybntjbGVhclRleHQ6bmV3IFRleHREZWNvZGVyKCkuZGVjb2RlKGQpLHRvb2s6RGF0ZS5ub3coKS1hfX1jYXRjaHt9fXJldHVybiBudWxsfTtsZXQgYz1udWxsLHk9bnVsbDt0cnl7eT1oKGUpO2NvbnN0IHU9YXdhaXQgY3J5cHRvLnN1YnRsZS5kaWdlc3QoIlNIQS0yNTYiLGYuZW5jb2RlKHQpKTtjPWF3YWl0IGNyeXB0by5zdWJ0bGUuaW1wb3J0S2V5KCJyYXciLHUscywhMSxbImRlY3J5cHQiXSl9Y2F0Y2h7cmV0dXJue3Byb21pc2U6UHJvbWlzZS5yZWplY3QoKSxjb250cm9sbGVyOm99fXJldHVybntwcm9taXNlOmwoKSxjb250cm9sbGVyOm99fWxldCBpO29ubWVzc2FnZT1hc3luYyBlPT57Y29uc3R7dHlwZTp0LHBheWxvYWQ6cixzdGFydDpuLG1heDpzfT1lLmRhdGE7bGV0IG89bnVsbDtpZih0PT09ImFib3J0IilpPT1udWxsfHxpLmFib3J0KCksaT12b2lkIDA7ZWxzZSBpZih0PT09IndvcmsiKXtpZigib2JmdXNjYXRlZCJpbiByKXtjb25zdHtrZXk6YSxvYmZ1c2NhdGVkOmx9PXJ8fHt9O289YXdhaXQgbShsLGEscyxuKX1lbHNle2NvbnN0e2FsZ29yaXRobTphLGNoYWxsZW5nZTpsLHNhbHQ6Y309cnx8e307bz1iKGwsYyxhLHMsbil9aT1vLmNvbnRyb2xsZXIsby5wcm9taXNlLnRoZW4oYT0+e3NlbGYucG9zdE1lc3NhZ2UoYSYmey4uLmEsd29ya2VyOiEwfSl9KX19fSkoKTsK",o=typeof self<"u"&&self.Blob&&new Blob([(r=n,Uint8Array.from(atob(r),(t=>t.charCodeAt(0))))],{type:"text/javascript;charset=utf-8"});var r;function i(t){let e;try{if(e=o&&(self.URL||self.webkitURL).createObjectURL(o),!e)throw"";const n=new Worker(e,{name:null==t?void 0:t.name});return n.addEventListener("error",(()=>{(self.URL||self.webkitURL).revokeObjectURL(e)})),n}catch{return new Worker("data:text/javascript;base64,"+n,{name:null==t?void 0:t.name})}finally{e&&(self.URL||self.webkitURL).revokeObjectURL(e)}}function l(){}function s(t){return t()}function a(){return Object.create(null)}function c(t){t.forEach(s)}function u(t){return"function"==typeof t}function d(t,e){return t!=t?e==e:t!==e||t&&"object"==typeof t||"function"==typeof t}function f(t,e,n,o){return t[1]&&o?function(t,e){for(const n in e)t[n]=e[n];return t}(n.ctx.slice(),t[1](o(e))):n.ctx}function h(t,e){t.appendChild(e)}function p(t,e,n){const o=function(t){if(!t)return document;const e=t.getRootNode?t.getRootNode():t.ownerDocument;return e&&e.host?e:t.ownerDocument}(t);if(!o.getElementById(e)){const t=$("style");t.id=e,t.textContent=n,function(t,e){h(t.head||t,e),e.sheet}(o,t)}}function g(t,e,n){t.insertBefore(e,n||null)}function m(t){t.parentNode&&t.parentNode.removeChild(t)}function $(t){return document.createElement(t)}function b(t){return document.createElementNS("http://www.w3.org/2000/svg",t)}function v(){return function(t){return document.createTextNode(t)}(" ")}function y(t,e,n,o){return t.addEventListener(e,n,o),()=>t.removeEventListener(e,n,o)}function x(t,e,n){null==n?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function w(t,e,n){t.classList.toggle(e,!!n)}let k;function E(t){k=t}function R(){if(!k)throw new Error("Function called outside component initialization");return k}function I(){const t=R();return(e,n,{cancelable:o=!1}={})=>{const r=t.$$.callbacks[e];if(r){const i=function(t,e,{bubbles:n=!1,cancelable:o=!1}={}){return new CustomEvent(t,{detail:e,bubbles:n,cancelable:o})}(e,n,{cancelable:o});return r.slice().forEach((e=>{e.call(t,i)})),!i.defaultPrevented}return!0}}const L=[],C=[];let N=[];const z=[],Z=Promise.resolve();let S=!1;function V(){S||(S=!0,Z.then(T))}function G(t){N.push(t)}const A=new Set;let H=0;function T(){if(0!==H)return;const t=k;do{try{for(;H-1===t.indexOf(o)?e.push(o):n.push(o))),n.forEach((t=>t())),N=e}(n.after_update),c(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function M(t,e,n,o,r,i,d=null,f=[-1]){const h=k;E(t);const p=t.$$={fragment:null,ctx:[],props:i,update:l,not_equal:r,bound:a(),on_mount:[],on_destroy:[],on_disconnect:[],before_update:[],after_update:[],context:new Map(e.context||(h?h.$$.context:[])),callbacks:a(),dirty:f,skip_bound:!1,root:e.target||h.$$.root};d&&d(p.root);let g=!1;if(p.ctx=n?n(t,e.props||{},((e,n,...o)=>{const i=o.length?o[0]:n;return p.ctx&&r(p.ctx[e],p.ctx[e]=i)&&(!p.skip_bound&&p.bound[e]&&p.bound[e](i),g&&function(t,e){-1===t.$$.dirty[0]&&(L.push(t),V(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{const e=t.$$.on_mount.map(s).filter(u);t.$$.on_destroy?t.$$.on_destroy.push(...e):c(e),t.$$.on_mount=[]})),r.forEach(G)}(t,e.target,e.anchor),T()}E(h)}function _(t,e,n,o){var r;const i=null==(r=n[t])?void 0:r.type;if(e="Boolean"===i&&"boolean"!=typeof e?null!=e:e,!o||!n[t])return e;if("toAttribute"===o)switch(i){case"Object":case"Array":return null==e?null:JSON.stringify(e);case"Boolean":return e?"":null;case"Number":return e??null;default:return e}else switch(i){case"Object":case"Array":return e&&JSON.parse(e);case"Boolean":default:return e;case"Number":return null!=e?+e:e}}"function"==typeof HTMLElement&&(F=class extends HTMLElement{constructor(t,n,o){super(),e(this,"$$ctor"),e(this,"$$s"),e(this,"$$c"),e(this,"$$cn",!1),e(this,"$$d",{}),e(this,"$$r",!1),e(this,"$$p_d",{}),e(this,"$$l",{}),e(this,"$$l_u",new Map),this.$$ctor=t,this.$$s=n,o&&this.attachShadow({mode:"open"})}addEventListener(t,e,n){if(this.$$l[t]=this.$$l[t]||[],this.$$l[t].push(e),this.$$c){const n=this.$$c.$on(t,e);this.$$l_u.set(e,n)}super.addEventListener(t,e,n)}removeEventListener(t,e,n){if(super.removeEventListener(t,e,n),this.$$c){const t=this.$$l_u.get(e);t&&(t(),this.$$l_u.delete(e))}if(this.$$l[t]){const n=this.$$l[t].indexOf(e);n>=0&&this.$$l[t].splice(n,1)}}async connectedCallback(){if(this.$$cn=!0,!this.$$c){let t=function(t){return()=>{let e;return{c:function(){e=$("slot"),"default"!==t&&x(e,"name",t)},m:function(t,n){g(t,e,n)},d:function(t){t&&m(e)}}}};if(await Promise.resolve(),!this.$$cn||this.$$c)return;const e={},n=function(t){const e={};return t.childNodes.forEach((t=>{e[t.slot||"default"]=!0})),e}(this);for(const o of this.$$s)o in n&&(e[o]=[t(o)]);for(const t of this.attributes){const e=this.$$g_p(t.name);e in this.$$d||(this.$$d[e]=_(e,t.value,this.$$p_d,"toProp"))}for(const t in this.$$p_d)!(t in this.$$d)&&void 0!==this[t]&&(this.$$d[t]=this[t],delete this[t]);this.$$c=new this.$$ctor({target:this.shadowRoot||this,props:{...this.$$d,$$slots:e,$$scope:{ctx:[]}}});const o=()=>{this.$$r=!0;for(const t in this.$$p_d)if(this.$$d[t]=this.$$c.$$.ctx[this.$$c.$$.props[t]],this.$$p_d[t].reflect){const e=_(t,this.$$d[t],this.$$p_d,"toAttribute");null==e?this.removeAttribute(this.$$p_d[t].attribute||t):this.setAttribute(this.$$p_d[t].attribute||t,e)}this.$$r=!1};this.$$c.$$.after_update.push(o),o();for(const t in this.$$l)for(const e of this.$$l[t]){const n=this.$$c.$on(t,e);this.$$l_u.set(e,n)}this.$$l={}}}attributeChangedCallback(t,e,n){var o;this.$$r||(t=this.$$g_p(t),this.$$d[t]=_(t,n,this.$$p_d,"toProp"),null==(o=this.$$c)||o.$set({[t]:this.$$d[t]}))}disconnectedCallback(){this.$$cn=!1,Promise.resolve().then((()=>{!this.$$cn&&this.$$c&&(this.$$c.$destroy(),this.$$c=void 0)}))}$$g_p(t){return Object.keys(this.$$p_d).find((e=>this.$$p_d[e].attribute===t||!this.$$p_d[e].attribute&&e.toLowerCase()===t))||t}});class D{constructor(){e(this,"$$"),e(this,"$$set")}$destroy(){K(this,1),this.$destroy=l}$on(t,e){if(!u(e))return l;const n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}$set(t){this.$$set&&!function(t){return 0===Object.keys(t).length}(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}typeof window<"u"&&(window.__svelte||(window.__svelte={v:new Set})).v.add("4");const U=new TextEncoder;async function W(t,e,n){return function(t){return[...new Uint8Array(t)].map((t=>t.toString(16).padStart(2,"0"))).join("")}(await crypto.subtle.digest(n.toUpperCase(),U.encode(t+e)))}function P(t,e,n="SHA-256",o=1e6,r=0){const i=new AbortController,l=Date.now();return{promise:(async()=>{for(let s=r;s<=o;s+=1){if(i.signal.aborted)return null;if(await W(e,s,n)===t)return{number:s,took:Date.now()-l}}return null})(),controller:i}}function B(t,e=12){const n=new Uint8Array(e);for(let o=0;o{for(let t=o;t<=n;t+=1){if(i.signal.aborted||!s||!a)return null;try{const e=await crypto.subtle.decrypt({name:r,iv:B(t)},s,a);if(e)return{clearText:(new TextDecoder).decode(e),took:Date.now()-l}}catch{}}return null})(),controller:i}}var J=(t=>(t.ERROR="error",t.VERIFIED="verified",t.VERIFYING="verifying",t.UNVERIFIED="unverified",t.EXPIRED="expired",t))(J||{});function Q(t){p(t,"svelte-ddsc3z",'.altcha.svelte-ddsc3z.svelte-ddsc3z{background:var(--altcha-color-base, transparent);border:var(--altcha-border-width, 1px) solid var(--altcha-color-border, #a0a0a0);border-radius:var(--altcha-border-radius, 3px);color:var(--altcha-color-text, currentColor);display:flex;flex-direction:column;max-width:var(--altcha-max-width, 260px);position:relative;text-align:left}.altcha.svelte-ddsc3z.svelte-ddsc3z:focus-within{border-color:var(--altcha-color-border-focus, currentColor)}.altcha[data-floating].svelte-ddsc3z.svelte-ddsc3z{background:var(--altcha-color-base, white);display:none;filter:drop-shadow(3px 3px 6px rgba(0, 0, 0, 0.2));left:-100%;position:fixed;top:-100%;width:var(--altcha-max-width, 260px);z-index:999999}.altcha[data-floating=top].svelte-ddsc3z .altcha-anchor-arrow.svelte-ddsc3z{border-bottom-color:transparent;border-top-color:var(--altcha-color-border, #a0a0a0);bottom:-12px;top:auto}.altcha[data-floating=bottom].svelte-ddsc3z.svelte-ddsc3z:focus-within::after{border-bottom-color:var(--altcha-color-border-focus, currentColor)}.altcha[data-floating=top].svelte-ddsc3z.svelte-ddsc3z:focus-within::after{border-top-color:var(--altcha-color-border-focus, currentColor)}.altcha[data-floating].svelte-ddsc3z.svelte-ddsc3z:not([data-state=unverified]){display:block}.altcha-anchor-arrow.svelte-ddsc3z.svelte-ddsc3z{border:6px solid transparent;border-bottom-color:var(--altcha-color-border, #a0a0a0);content:"";height:0;left:12px;position:absolute;top:-12px;width:0}.altcha-main.svelte-ddsc3z.svelte-ddsc3z{align-items:center;display:flex;gap:0.4rem;padding:0.7rem}.altcha-label.svelte-ddsc3z.svelte-ddsc3z{flex-grow:1}.altcha-label.svelte-ddsc3z label.svelte-ddsc3z{cursor:pointer}.altcha-logo.svelte-ddsc3z.svelte-ddsc3z{color:currentColor;opacity:0.3}.altcha-logo.svelte-ddsc3z.svelte-ddsc3z:hover{opacity:1}.altcha-error.svelte-ddsc3z.svelte-ddsc3z{color:var(--altcha-color-error-text, #f23939);display:flex;font-size:0.85rem;gap:0.3rem;padding:0 0.7rem 0.7rem}.altcha-footer.svelte-ddsc3z.svelte-ddsc3z{align-items:center;background-color:var(--altcha-color-footer-bg, transparent);display:flex;font-size:0.75rem;opacity:0.4;padding:0.2rem 0.7rem;text-align:right}.altcha-footer.svelte-ddsc3z.svelte-ddsc3z:hover{opacity:1}.altcha-footer.svelte-ddsc3z>.svelte-ddsc3z:first-child{flex-grow:1}.altcha-footer.svelte-ddsc3z a{color:currentColor}.altcha-checkbox.svelte-ddsc3z.svelte-ddsc3z{display:flex;align-items:center;height:24px;width:24px}.altcha-checkbox.svelte-ddsc3z input.svelte-ddsc3z{width:18px;height:18px;margin:0}.altcha-hidden.svelte-ddsc3z.svelte-ddsc3z{display:none}.altcha-spinner.svelte-ddsc3z.svelte-ddsc3z{animation:svelte-ddsc3z-altcha-spinner 0.75s infinite linear;transform-origin:center}@keyframes svelte-ddsc3z-altcha-spinner{100%{transform:rotate(360deg)}}')}function q(t){let e,n,o;return{c(){e=b("svg"),n=b("path"),o=b("path"),x(n,"d","M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"),x(n,"fill","currentColor"),x(n,"opacity",".25"),x(o,"d","M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"),x(o,"fill","currentColor"),x(o,"class","altcha-spinner svelte-ddsc3z"),x(e,"width","24"),x(e,"height","24"),x(e,"viewBox","0 0 24 24"),x(e,"xmlns","http://www.w3.org/2000/svg")},m(t,r){g(t,e,r),h(e,n),h(e,o)},d(t){t&&m(e)}}}function tt(t){let e,n,o=t[11].label+"";return{c(){e=$("label"),x(e,"for",n=t[4]+"_checkbox"),x(e,"class","svelte-ddsc3z")},m(t,n){g(t,e,n),e.innerHTML=o},p(t,r){2048&r[0]&&o!==(o=t[11].label+"")&&(e.innerHTML=o),16&r[0]&&n!==(n=t[4]+"_checkbox")&&x(e,"for",n)},d(t){t&&m(e)}}}function et(t){let e,n=t[11].verifying+"";return{c(){e=$("span")},m(t,o){g(t,e,o),e.innerHTML=n},p(t,o){2048&o[0]&&n!==(n=t[11].verifying+"")&&(e.innerHTML=n)},d(t){t&&m(e)}}}function nt(t){let e,n,o,r=t[11].verified+"";return{c(){e=$("span"),n=v(),o=$("input"),x(o,"type","hidden"),x(o,"name",t[4]),o.value=t[6]},m(t,i){g(t,e,i),e.innerHTML=r,g(t,n,i),g(t,o,i)},p(t,n){2048&n[0]&&r!==(r=t[11].verified+"")&&(e.innerHTML=r),16&n[0]&&x(o,"name",t[4]),64&n[0]&&(o.value=t[6])},d(t){t&&(m(e),m(n),m(o))}}}function ot(t){let e,n,o,r,i,l,s;return{c(){e=$("div"),n=$("a"),o=b("svg"),r=b("path"),i=b("path"),l=b("path"),x(r,"d","M2.33955 16.4279C5.88954 20.6586 12.1971 21.2105 16.4279 17.6604C18.4699 15.947 19.6548 13.5911 19.9352 11.1365L17.9886 10.4279C17.8738 12.5624 16.909 14.6459 15.1423 16.1284C11.7577 18.9684 6.71167 18.5269 3.87164 15.1423C1.03163 11.7577 1.4731 6.71166 4.8577 3.87164C8.24231 1.03162 13.2883 1.4731 16.1284 4.8577C16.9767 5.86872 17.5322 7.02798 17.804 8.2324L19.9522 9.01429C19.7622 7.07737 19.0059 5.17558 17.6604 3.57212C14.1104 -0.658624 7.80283 -1.21043 3.57212 2.33956C-0.658625 5.88958 -1.21046 12.1971 2.33955 16.4279Z"),x(r,"fill","currentColor"),x(i,"d","M3.57212 2.33956C1.65755 3.94607 0.496389 6.11731 0.12782 8.40523L2.04639 9.13961C2.26047 7.15832 3.21057 5.25375 4.8577 3.87164C8.24231 1.03162 13.2883 1.4731 16.1284 4.8577L13.8302 6.78606L19.9633 9.13364C19.7929 7.15555 19.0335 5.20847 17.6604 3.57212C14.1104 -0.658624 7.80283 -1.21043 3.57212 2.33956Z"),x(i,"fill","currentColor"),x(l,"d","M7 10H5C5 12.7614 7.23858 15 10 15C12.7614 15 15 12.7614 15 10H13C13 11.6569 11.6569 13 10 13C8.3431 13 7 11.6569 7 10Z"),x(l,"fill","currentColor"),x(o,"width","22"),x(o,"height","22"),x(o,"viewBox","0 0 20 20"),x(o,"fill","none"),x(o,"xmlns","http://www.w3.org/2000/svg"),x(n,"href",dt),x(n,"target","_blank"),x(n,"class","altcha-logo svelte-ddsc3z"),x(n,"aria-label",s=t[11].ariaLinkLabel)},m(t,s){g(t,e,s),h(e,n),h(n,o),h(o,r),h(o,i),h(o,l)},p(t,e){2048&e[0]&&s!==(s=t[11].ariaLinkLabel)&&x(n,"aria-label",s)},d(t){t&&m(e)}}}function rt(t){let e,n,o,r;function i(t,e){return t[7]===J.EXPIRED?lt:it}let l=i(t),s=l(t);return{c(){e=$("div"),n=b("svg"),o=b("path"),r=v(),s.c(),x(o,"stroke-linecap","round"),x(o,"stroke-linejoin","round"),x(o,"d","M6 18L18 6M6 6l12 12"),x(n,"width","14"),x(n,"height","14"),x(n,"xmlns","http://www.w3.org/2000/svg"),x(n,"fill","none"),x(n,"viewBox","0 0 24 24"),x(n,"stroke-width","1.5"),x(n,"stroke","currentColor"),x(e,"class","altcha-error svelte-ddsc3z")},m(t,i){g(t,e,i),h(e,n),h(n,o),h(e,r),s.m(e,null)},p(t,n){l===(l=i(t))&&s?s.p(t,n):(s.d(1),s=l(t),s&&(s.c(),s.m(e,null)))},d(t){t&&m(e),s.d()}}}function it(t){let e,n=t[11].error+"";return{c(){e=$("div"),x(e,"title",t[5])},m(t,o){g(t,e,o),e.innerHTML=n},p(t,o){2048&o[0]&&n!==(n=t[11].error+"")&&(e.innerHTML=n),32&o[0]&&x(e,"title",t[5])},d(t){t&&m(e)}}}function lt(t){let e,n=t[11].expired+"";return{c(){e=$("div"),x(e,"title",t[5])},m(t,o){g(t,e,o),e.innerHTML=n},p(t,o){2048&o[0]&&n!==(n=t[11].expired+"")&&(e.innerHTML=n),32&o[0]&&x(e,"title",t[5])},d(t){t&&m(e)}}}function st(t){let e,n,o=t[11].footer+"";return{c(){e=$("div"),n=$("div"),x(n,"class","svelte-ddsc3z"),x(e,"class","altcha-footer svelte-ddsc3z")},m(t,r){g(t,e,r),h(e,n),n.innerHTML=o},p(t,e){2048&e[0]&&o!==(o=t[11].footer+"")&&(n.innerHTML=o)},d(t){t&&m(e)}}}function at(t){let e;return{c(){e=$("div"),x(e,"class","altcha-anchor-arrow svelte-ddsc3z")},m(n,o){g(n,e,o),t[48](e)},p:l,d(n){n&&m(e),t[48](null)}}}function ct(t){let e,n,o,r,i,l,s,a,u,d,p,b,k,E,R,I,L;const C=t[46].default,N=function(t,e,n,o){if(t){const r=f(t,e,n,o);return t[0](r)}}(C,t,t[45],null);let z=t[7]===J.VERIFYING&&q();function Z(t,e){return t[7]===J.VERIFIED?nt:t[7]===J.VERIFYING?et:tt}let S=Z(t),V=S(t),G=(!0!==t[3]||t[12])&&ot(t),A=(t[5]||t[7]===J.EXPIRED)&&rt(t),H=t[11].footer&&(!0!==t[2]||t[12])&&st(t),T=t[1]&&at(t);return{c(){N&&N.c(),e=v(),n=$("div"),o=$("div"),z&&z.c(),r=v(),i=$("div"),l=$("input"),u=v(),d=$("div"),V.c(),p=v(),G&&G.c(),b=v(),A&&A.c(),k=v(),H&&H.c(),E=v(),T&&T.c(),x(l,"type","checkbox"),x(l,"id",s=t[4]+"_checkbox"),l.required=a="onsubmit"!==t[0]&&(!t[1]||"off"!==t[0]),x(l,"class","svelte-ddsc3z"),x(i,"class","altcha-checkbox svelte-ddsc3z"),w(i,"altcha-hidden",t[7]===J.VERIFYING),x(d,"class","altcha-label svelte-ddsc3z"),x(o,"class","altcha-main svelte-ddsc3z"),x(n,"class","altcha svelte-ddsc3z"),x(n,"data-state",t[7]),x(n,"data-floating",t[1])},m(s,a){N&&N.m(s,a),g(s,e,a),g(s,n,a),h(n,o),z&&z.m(o,null),h(o,r),h(o,i),h(i,l),l.checked=t[8],h(o,u),h(o,d),V.m(d,null),h(o,p),G&&G.m(o,null),h(n,b),A&&A.m(n,null),h(n,k),H&&H.m(n,null),h(n,E),T&&T.m(n,null),t[49](n),R=!0,I||(L=[y(l,"change",t[47]),y(l,"change",t[13]),y(l,"invalid",t[14])],I=!0)},p(t,e){N&&N.p&&(!R||16384&e[1])&&function(t,e,n,o,r,i){if(r){const l=f(e,n,o,i);t.p(l,r)}}(N,C,t,t[45],R?function(t,e,n,o){if(t[2]&&o){const r=t[2](o(n));if(void 0===e.dirty)return r;if("object"==typeof r){const t=[],n=Math.max(e.dirty.length,r.length);for(let o=0;o32){const e=[],n=t.ctx.length/32;for(let t=0;t{Y.delete(t)})),t.o(e)}})(N,t),R=!1},d(o){o&&(m(e),m(n)),N&&N.d(o),z&&z.d(),V.d(),G&&G.d(),A&&A.d(),H&&H.d(),T&&T.d(),t[49](null),I=!1,c(L)}}}const ut="Visit Altcha.org",dt="https://altcha.org/";function ft(t){return JSON.parse(t)}function ht(t,e,n){var o,r;let i,l,s,a,{$$slots:c={},$$scope:u}=e,{auto:d}=e,{blockspam:f}=e,{challengeurl:h}=e,{challengejson:p}=e,{debug:g=!1}=e,{delay:m=0}=e,{expire:$}=e,{floating:b}=e,{floatinganchor:v}=e,{floatingoffset:y}=e,{hidefooter:x=!1}=e,{hidelogo:w=!1}=e,{name:k="altcha"}=e,{maxnumber:E=1e6}=e,{mockerror:L=!1}=e,{obfuscated:N}=e,{plugins:z}=e,{refetchonexpire:S=!0}=e,{spamfilter:G=!1}=e,{strings:A}=e,{test:H=!1}=e,{verifyurl:T}=e,{workers:X=Math.min(16,navigator.hardwareConcurrency||8)}=e,{workerurl:Y}=e;const F=I(),j=["SHA-256","SHA-384","SHA-512"],K=null==(r=null==(o=document.documentElement.lang)?void 0:o.split("-"))?void 0:r[0];let M,_=!1,D=null,U=null,B=null,Q=null,q=null,tt=null,et=[],nt=J.UNVERIFIED;function ot(t,e){return btoa(JSON.stringify({algorithm:t.algorithm,challenge:t.challenge,number:e.number,salt:t.salt,signature:t.signature,test:!!H||void 0,took:e.took}))}function rt(){h&&S&&nt===J.VERIFIED?Zt():Ct(J.EXPIRED,a.expired)}async function it(){var t;if(L)throw lt("mocking error"),new Error("Mocked error.");if(l)return lt("using provided json data"),l;if(H)return lt("generating test challenge",{test:H}),async function(t,e="SHA-256",n=1e5){const o=Date.now().toString(16);return t||(t=Math.round(Math.random()*n)),{algorithm:e,challenge:await W(o,t,e),salt:o,signature:""}}("boolean"!=typeof H?+H:void 0);{if(!h&&B){const t=B.getAttribute("action");null!=t&&t.includes("/form/")&&n(15,h=t+"/altcha")}if(!h)throw new Error("Attribute challengeurl not set.");lt("fetching challenge from",h);const e=await fetch(h,{headers:G?{"x-altcha-spam-filter":"1"}:{}});if(200!==e.status)throw new Error(`Server responded with ${e.status}.`);const o=e.headers.get("Expires"),r=e.headers.get("X-Altcha-Config"),i=await e.json(),l=new URLSearchParams(null==(t=i.salt.split("?"))?void 0:t[1]),s=l.get("expires")||l.get("expire");if(s){const t=new Date(1e3*+s),e=isNaN(t.getTime())?0:t.getTime()-Date.now();e>0&&vt(e)}if(r)try{const t=JSON.parse(r);t&&"object"==typeof t&&(t.verifyurl&&(t.verifyurl=new URL(t.verifyurl,new URL(h)).toString()),Et(t))}catch(t){lt("unable to configure from X-Altcha-Config",t)}if(!$&&null!=o&&o.length){const t=Date.parse(o);if(t){const e=t-Date.now();e>0&&vt(e)}}return i}}function lt(...t){(g||t.some((t=>t instanceof Error)))&&console[t[0]instanceof Error?"error":"log"]("ALTCHA",`[name=${k}]`,...t)}function st(t){const e=t.target;b&&e&&!M.contains(e)&&(nt===J.VERIFIED||"off"===d&&nt===J.UNVERIFIED)&&n(9,M.style.display="none",M)}function at(){b&&nt!==J.UNVERIFIED&&$t()}function ct(t){nt===J.UNVERIFIED&&Zt()}function ht(t){B&&"onsubmit"===d?nt===J.UNVERIFIED?(t.preventDefault(),t.stopPropagation(),Zt().then((()=>{null==B||B.requestSubmit()}))):nt!==J.VERIFIED&&(t.preventDefault(),t.stopPropagation(),nt===J.VERIFYING&>()):B&&b&&"off"===d&&nt===J.UNVERIFIED&&(t.preventDefault(),t.stopPropagation(),n(9,M.style.display="block",M),$t())}function pt(){Ct()}function gt(){nt===J.VERIFYING&&a.waitAlert&&alert(a.waitAlert)}function mt(){b&&$t()}function $t(t=20){if(M)if(U||(U=(v?document.querySelector(v):null==B?void 0:B.querySelector('input[type="submit"], button[type="submit"], button:not([type="button"]):not([type="reset"])'))||B),U){const e=parseInt(y,10)||12,o=U.getBoundingClientRect(),r=M.getBoundingClientRect(),i=document.documentElement.clientHeight,l=document.documentElement.clientWidth,s="auto"===b?o.bottom+r.height+e+t>i:"top"===b,a=Math.max(t,Math.min(l-t-r.width,o.left+o.width/2-r.width/2));if(n(9,M.style.top=s?o.top-(r.height+e)+"px":`${o.bottom+e}px`,M),n(9,M.style.left=`${a}px`,M),M.setAttribute("data-floating",s?"top":"bottom"),D){const t=D.getBoundingClientRect();n(10,D.style.left=o.left-a+o.width/2-t.width/2+"px",D)}}else lt("unable to find floating anchor element")}async function bt(t){if(!T)throw new Error("Attribute verifyurl not set.");lt("requesting server verification from",T);const e={payload:t};if(G){const{blockedCountries:t,classifier:n,disableRules:o,email:r,expectedLanguages:i,expectedCountries:l,fields:s,ipAddress:a,text:c,timeZone:u}="ipAddress"===G?{blockedCountries:void 0,classifier:void 0,disableRules:void 0,email:!1,expectedCountries:void 0,expectedLanguages:void 0,fields:!1,ipAddress:void 0,text:void 0,timeZone:void 0}:"object"==typeof G?G:{blockedCountries:void 0,classifier:void 0,disableRules:void 0,email:void 0,expectedCountries:void 0,expectedLanguages:void 0,fields:void 0,ipAddress:void 0,text:void 0,timeZone:void 0};e.blockedCountries=t,e.classifier=n,e.disableRules=o,e.email=!1===r?void 0:function(t){var e;const n=null==B?void 0:B.querySelector("string"==typeof t?`input[name="${t}"]`:'input[type="email"]:not([data-no-spamfilter])');return(null==(e=null==n?void 0:n.value)?void 0:e.slice(n.value.indexOf("@")))||void 0}(r),e.expectedCountries=l,e.expectedLanguages=i||(K?[K]:void 0),e.fields=!1===s?void 0:function(t){return[...(null==B?void 0:B.querySelectorAll(null!=t&&t.length?t.map((t=>`input[name="${t}"]`)).join(", "):'input[type="text"]:not([data-no-spamfilter]), textarea:not([data-no-spamfilter])'))||[]].reduce(((t,e)=>{const n=e.name,o=e.value;return n&&o&&(t[n]=/\n/.test(o)?o.replace(new RegExp("(?{const i=n*r;return new Promise((n=>{e.addEventListener("message",(t=>{if(t.data)for(const t of o)t!==e&&t.postMessage({type:"abort"});n(t.data)})),e.postMessage({payload:t,max:i+r,start:i,type:"work"})}))})));for(const t of o)t.terminate();return i.find((t=>!!t))||null}(t,t.maxnumber)}catch(t){lt(t)}if(void 0!==(null==e?void 0:e.number)||"obfuscated"in t)return{data:t,solution:e}}if("obfuscated"in t){const e=await O(t.obfuscated,t.key,t.maxnumber);return{data:t,solution:await e.promise}}return{data:t,solution:await P(t.challenge,t.salt,t.algorithm,t.maxnumber||E).promise}}async function kt(){if(!N)return void n(7,nt=J.ERROR);const t=et.find((t=>"obfuscation"===t.constructor.pluginName));return t&&"clarify"in t?"clarify"in t&&"function"==typeof t.clarify?t.clarify():void 0:(n(7,nt=J.ERROR),void lt("Plugin `obfuscation` not found. Import `altcha/plugins/obfuscation` to load it."))}function Et(t){void 0!==t.obfuscated&&n(24,N=t.obfuscated),void 0!==t.auto&&(n(0,d=t.auto),"onload"===d&&(N?kt():Zt())),void 0!==t.blockspam&&n(16,f=!!t.blockspam),void 0!==t.floatinganchor&&n(20,v=t.floatinganchor),void 0!==t.delay&&n(18,m=t.delay),void 0!==t.floatingoffset&&n(21,y=t.floatingoffset),void 0!==t.floating&&yt(t.floating),void 0!==t.expire&&(vt(t.expire),n(19,$=t.expire)),t.challenge&&(xt(t.challenge),l=t.challenge),void 0!==t.challengeurl&&n(15,h=t.challengeurl),void 0!==t.debug&&n(17,g=!!t.debug),void 0!==t.hidefooter&&n(2,x=!!t.hidefooter),void 0!==t.hidelogo&&n(3,w=!!t.hidelogo),void 0!==t.maxnumber&&n(22,E=+t.maxnumber),void 0!==t.mockerror&&n(23,L=!!t.mockerror),void 0!==t.name&&n(4,k=t.name),void 0!==t.refetchonexpire&&n(25,S=!!t.refetchonexpire),void 0!==t.spamfilter&&n(26,G="object"==typeof t.spamfilter?t.spamfilter:!!t.spamfilter),t.strings&&n(44,s=t.strings),void 0!==t.test&&n(27,H="number"==typeof t.test?t.test:!!t.test),void 0!==t.verifyurl&&n(28,T=t.verifyurl),void 0!==t.workers&&n(29,X=+t.workers),void 0!==t.workerurl&&n(30,Y=t.workerurl)}function Rt(){return{auto:d,blockspam:f,challengeurl:h,debug:g,delay:m,expire:$,floating:b,floatinganchor:v,floatingoffset:y,hidefooter:x,hidelogo:w,name:k,maxnumber:E,mockerror:L,obfuscated:N,refetchonexpire:S,spamfilter:G,strings:a,test:H,verifyurl:T,workers:X,workerurl:Y}}function It(){return U}function Lt(){return nt}function Ct(t=J.UNVERIFIED,e=null){q&&(clearTimeout(q),q=null),n(8,_=!1),n(5,Q=e),n(6,tt=null),n(7,nt=t)}function Nt(t){U=t}function zt(t,e=null){n(7,nt=t),n(5,Q=e)}async function Zt(){return Ct(J.VERIFYING),await new Promise((t=>setTimeout(t,m||0))),it().then((t=>(xt(t),lt("challenge",t),wt(t)))).then((({data:t,solution:e})=>{if(lt("solution",e),"challenge"in t&&e&&!("clearText"in e)){if(void 0===(null==e?void 0:e.number))throw lt("Unable to find a solution. Ensure that the 'maxnumber' attribute is greater than the randomly generated number."),new Error("Unexpected result returned.");if(T)return bt(ot(t,e));n(6,tt=ot(t,e)),lt("payload",tt)}})).then((()=>{n(7,nt=J.VERIFIED),lt("verified"),(V(),Z).then((()=>{F("verified",{payload:tt})}))})).catch((t=>{lt(t),n(7,nt=J.ERROR),n(5,Q=t.message)}))}return function(t){R().$$.on_destroy.push(t)}((()=>{(function(){for(const t of et)t.destroy()})(),B&&(B.removeEventListener("submit",ht),B.removeEventListener("reset",pt),B.removeEventListener("focusin",ct),B=null),q&&(clearTimeout(q),q=null),document.removeEventListener("click",st),document.removeEventListener("scroll",at),window.removeEventListener("resize",mt)})),function(t){R().$$.on_mount.push(t)}((()=>{lt("mounted","1.0.6"),lt("workers",X),function(){const t=void 0!==z?z.split(","):void 0;for(const e of globalThis.altchaPlugins)(!t||t.includes(e.pluginName))&&et.push(new e({el:M,clarify:kt,dispatch:F,getConfiguration:Rt,getFloatingAnchor:It,getState:Lt,log:lt,reset:Ct,solve:wt,setState:zt,setFloatingAnchor:Nt,verify:Zt}))}(),lt("plugins",et.length?et.map((t=>t.constructor.pluginName)).join(", "):"none"),H&<("using test mode"),$&&vt($),void 0!==d&<("auto",d),void 0!==b&&yt(b),B=M.closest("form"),B&&(B.addEventListener("submit",ht,{capture:!0}),B.addEventListener("reset",pt),"onfocus"===d&&B.addEventListener("focusin",ct)),"onload"===d&&(N?kt():Zt()),i&&(x||w)&<("Attributes hidefooter and hidelogo ignored because usage with free API Keys requires attribution."),requestAnimationFrame((()=>{F("load")}))})),t.$$set=t=>{"auto"in t&&n(0,d=t.auto),"blockspam"in t&&n(16,f=t.blockspam),"challengeurl"in t&&n(15,h=t.challengeurl),"challengejson"in t&&n(31,p=t.challengejson),"debug"in t&&n(17,g=t.debug),"delay"in t&&n(18,m=t.delay),"expire"in t&&n(19,$=t.expire),"floating"in t&&n(1,b=t.floating),"floatinganchor"in t&&n(20,v=t.floatinganchor),"floatingoffset"in t&&n(21,y=t.floatingoffset),"hidefooter"in t&&n(2,x=t.hidefooter),"hidelogo"in t&&n(3,w=t.hidelogo),"name"in t&&n(4,k=t.name),"maxnumber"in t&&n(22,E=t.maxnumber),"mockerror"in t&&n(23,L=t.mockerror),"obfuscated"in t&&n(24,N=t.obfuscated),"plugins"in t&&n(32,z=t.plugins),"refetchonexpire"in t&&n(25,S=t.refetchonexpire),"spamfilter"in t&&n(26,G=t.spamfilter),"strings"in t&&n(33,A=t.strings),"test"in t&&n(27,H=t.test),"verifyurl"in t&&n(28,T=t.verifyurl),"workers"in t&&n(29,X=t.workers),"workerurl"in t&&n(30,Y=t.workerurl),"$$scope"in t&&n(45,u=t.$$scope)},t.$$.update=()=>{32768&t.$$.dirty[0]&&n(12,i=!(null==h||!h.includes(".altcha.org")||null==h||!h.includes("apiKey=ckey_"))),1&t.$$.dirty[1]&&(l=p?ft(p):void 0),4&t.$$.dirty[1]&&n(44,s=A?ft(A):{}),8192&t.$$.dirty[1]&&n(11,a={ariaLinkLabel:ut,error:"Verification failed. Try again later.",expired:"Verification expired. Try again.",footer:`Protected by ALTCHA`,label:"I'm not a robot",verified:"Verified",verifying:"Verifying...",waitAlert:"Verifying... please wait.",...s}),192&t.$$.dirty[0]&&F("statechange",{payload:tt,state:nt}),32&t.$$.dirty[0]&&function(){for(const t of et)"function"==typeof t.onErrorChange&&t.onErrorChange(Q)}(),128&t.$$.dirty[0]&&function(){for(const t of et)"function"==typeof t.onStateChange&&t.onStateChange(nt);b&&nt!==J.UNVERIFIED&&requestAnimationFrame((()=>{$t()})),n(8,_=nt===J.VERIFIED)}()},[d,b,x,w,k,Q,tt,nt,_,M,D,a,i,function(){[J.UNVERIFIED,J.ERROR,J.EXPIRED].includes(nt)?G&&!1===(null==B?void 0:B.reportValidity())?n(8,_=!1):N?kt():Zt():n(8,_=!0)},gt,h,f,g,m,$,v,y,E,L,N,S,G,H,T,X,Y,p,z,A,kt,Et,Rt,It,function(t){return et.find((e=>e.constructor.pluginName===t))},Lt,Ct,Nt,zt,Zt,s,u,c,function(){_=this.checked,n(8,_)},function(t){C[t?"unshift":"push"]((()=>{D=t,n(10,D)}))},function(t){C[t?"unshift":"push"]((()=>{M=t,n(9,M)}))}]}customElements.define("altcha-widget",function(t,e,n,o,r){let i=class extends F{constructor(){super(t,n,r),this.$$p_d=e}static get observedAttributes(){return Object.keys(e).map((t=>(e[t].attribute||t).toLowerCase()))}};return Object.keys(e).forEach((t=>{Object.defineProperty(i.prototype,t,{get(){return this.$$c&&t in this.$$c?this.$$c[t]:this.$$d[t]},set(n){var o;n=_(t,n,e),this.$$d[t]=n,null==(o=this.$$c)||o.$set({[t]:n})}})})),o.forEach((t=>{Object.defineProperty(i.prototype,t,{get(){var e;return null==(e=this.$$c)?void 0:e[t]}})})),t.element=i,i}(class extends D{constructor(t){super(),M(this,t,ht,ct,d,{auto:0,blockspam:16,challengeurl:15,challengejson:31,debug:17,delay:18,expire:19,floating:1,floatinganchor:20,floatingoffset:21,hidefooter:2,hidelogo:3,name:4,maxnumber:22,mockerror:23,obfuscated:24,plugins:32,refetchonexpire:25,spamfilter:26,strings:33,test:27,verifyurl:28,workers:29,workerurl:30,clarify:34,configure:35,getConfiguration:36,getFloatingAnchor:37,getPlugin:38,getState:39,reset:40,setFloatingAnchor:41,setState:42,verify:43},Q,[-1,-1,-1])}get auto(){return this.$$.ctx[0]}set auto(t){this.$$set({auto:t}),T()}get blockspam(){return this.$$.ctx[16]}set blockspam(t){this.$$set({blockspam:t}),T()}get challengeurl(){return this.$$.ctx[15]}set challengeurl(t){this.$$set({challengeurl:t}),T()}get challengejson(){return this.$$.ctx[31]}set challengejson(t){this.$$set({challengejson:t}),T()}get debug(){return this.$$.ctx[17]}set debug(t){this.$$set({debug:t}),T()}get delay(){return this.$$.ctx[18]}set delay(t){this.$$set({delay:t}),T()}get expire(){return this.$$.ctx[19]}set expire(t){this.$$set({expire:t}),T()}get floating(){return this.$$.ctx[1]}set floating(t){this.$$set({floating:t}),T()}get floatinganchor(){return this.$$.ctx[20]}set floatinganchor(t){this.$$set({floatinganchor:t}),T()}get floatingoffset(){return this.$$.ctx[21]}set floatingoffset(t){this.$$set({floatingoffset:t}),T()}get hidefooter(){return this.$$.ctx[2]}set hidefooter(t){this.$$set({hidefooter:t}),T()}get hidelogo(){return this.$$.ctx[3]}set hidelogo(t){this.$$set({hidelogo:t}),T()}get name(){return this.$$.ctx[4]}set name(t){this.$$set({name:t}),T()}get maxnumber(){return this.$$.ctx[22]}set maxnumber(t){this.$$set({maxnumber:t}),T()}get mockerror(){return this.$$.ctx[23]}set mockerror(t){this.$$set({mockerror:t}),T()}get obfuscated(){return this.$$.ctx[24]}set obfuscated(t){this.$$set({obfuscated:t}),T()}get plugins(){return this.$$.ctx[32]}set plugins(t){this.$$set({plugins:t}),T()}get refetchonexpire(){return this.$$.ctx[25]}set refetchonexpire(t){this.$$set({refetchonexpire:t}),T()}get spamfilter(){return this.$$.ctx[26]}set spamfilter(t){this.$$set({spamfilter:t}),T()}get strings(){return this.$$.ctx[33]}set strings(t){this.$$set({strings:t}),T()}get test(){return this.$$.ctx[27]}set test(t){this.$$set({test:t}),T()}get verifyurl(){return this.$$.ctx[28]}set verifyurl(t){this.$$set({verifyurl:t}),T()}get workers(){return this.$$.ctx[29]}set workers(t){this.$$set({workers:t}),T()}get workerurl(){return this.$$.ctx[30]}set workerurl(t){this.$$set({workerurl:t}),T()}get clarify(){return this.$$.ctx[34]}get configure(){return this.$$.ctx[35]}get getConfiguration(){return this.$$.ctx[36]}get getFloatingAnchor(){return this.$$.ctx[37]}get getPlugin(){return this.$$.ctx[38]}get getState(){return this.$$.ctx[39]}get reset(){return this.$$.ctx[40]}get setFloatingAnchor(){return this.$$.ctx[41]}get setState(){return this.$$.ctx[42]}get verify(){return this.$$.ctx[43]}},{auto:{},blockspam:{},challengeurl:{},challengejson:{},debug:{type:"Boolean"},delay:{},expire:{},floating:{},floatinganchor:{},floatingoffset:{},hidefooter:{type:"Boolean"},hidelogo:{type:"Boolean"},name:{},maxnumber:{},mockerror:{type:"Boolean"},obfuscated:{},plugins:{},refetchonexpire:{type:"Boolean"},spamfilter:{type:"Boolean"},strings:{},test:{type:"Boolean"},verifyurl:{},workers:{},workerurl:{}},["default"],["clarify","configure","getConfiguration","getFloatingAnchor","getPlugin","getState","reset","setFloatingAnchor","setState","verify"],!1)),globalThis.altchaCreateWorker=t=>t?new Worker(new URL(t)):new i,globalThis.altchaPlugins=globalThis.altchaPlugins||[]})(); \ No newline at end of file diff --git a/weblate/static/js/vendor/altcha.js.license b/weblate/static/js/vendor/altcha.js.license new file mode 100644 index 000000000000..04a93e3cfbff --- /dev/null +++ b/weblate/static/js/vendor/altcha.js.license @@ -0,0 +1,3 @@ +Copyright (c) 2023 Daniel Regeci + +SPDX-License-Identifier: MIT diff --git a/weblate/static/js/vendor/main.js.license b/weblate/static/js/vendor/main.js.license index 5cbeea5d76c3..e7fb697ca153 100644 --- a/weblate/static/js/vendor/main.js.license +++ b/weblate/static/js/vendor/main.js.license @@ -1,3 +1,4 @@ +Copyright (c) 2023 Daniel Regeci Copyright (c) JS Foundation and other contributors Copyright Dan Grossman (http://www.dangrossman.info) Copyright OpenJS Foundation and other contributors, https://openjsf.org/ diff --git a/weblate/static/styles/main.css b/weblate/static/styles/main.css index 38b063e4146e..65495d22bcce 100644 --- a/weblate/static/styles/main.css +++ b/weblate/static/styles/main.css @@ -2229,3 +2229,7 @@ tbody.warning { .zammad-form-modal-body { max-width: 40em !important; } + +.altcha { + max-width: none !important; +} diff --git a/weblate/templates/base.html b/weblate/templates/base.html index 747cdba09c82..70a5f6698cf7 100644 --- a/weblate/templates/base.html +++ b/weblate/templates/base.html @@ -60,6 +60,7 @@ {% endif %} +