From 5e12e1ea93ba828cb7cc10ae36bccc93f044f2b1 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Fri, 16 May 2025 18:10:48 +0200 Subject: [PATCH 01/10] [ADD] fastapi_captcha --- fastapi_captcha/README.rst | 98 ++++ fastapi_captcha/__init__.py | 1 + fastapi_captcha/__manifest__.py | 20 + fastapi_captcha/captcha_middleware.py | 36 ++ fastapi_captcha/models/__init__.py | 1 + fastapi_captcha/models/fastapi_endpoint.py | 213 +++++++++ fastapi_captcha/readme/CONTRIBUTORS.md | 1 + fastapi_captcha/readme/DESCRIPTION.md | 8 + fastapi_captcha/readme/USAGE.md | 2 + fastapi_captcha/static/description/index.html | 439 ++++++++++++++++++ fastapi_captcha/tests/__init__.py | 1 + fastapi_captcha/tests/test_fastapi_captcha.py | 189 ++++++++ .../views/fastapi_endpoint_views.xml | 37 ++ 13 files changed, 1046 insertions(+) create mode 100644 fastapi_captcha/README.rst create mode 100644 fastapi_captcha/__init__.py create mode 100644 fastapi_captcha/__manifest__.py create mode 100644 fastapi_captcha/captcha_middleware.py create mode 100644 fastapi_captcha/models/__init__.py create mode 100644 fastapi_captcha/models/fastapi_endpoint.py create mode 100644 fastapi_captcha/readme/CONTRIBUTORS.md create mode 100644 fastapi_captcha/readme/DESCRIPTION.md create mode 100644 fastapi_captcha/readme/USAGE.md create mode 100644 fastapi_captcha/static/description/index.html create mode 100644 fastapi_captcha/tests/__init__.py create mode 100644 fastapi_captcha/tests/test_fastapi_captcha.py create mode 100644 fastapi_captcha/views/fastapi_endpoint_views.xml diff --git a/fastapi_captcha/README.rst b/fastapi_captcha/README.rst new file mode 100644 index 000000000..90272cdf1 --- /dev/null +++ b/fastapi_captcha/README.rst @@ -0,0 +1,98 @@ +=============== +Fastapi Captcha +=============== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:a507efff5b29eb67557d6283c396db18daddc1e48115ede431daff7f686594b6 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github + :target: https://github.com/OCA/rest-framework/tree/16.0/fastapi_captcha + :alt: OCA/rest-framework +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/rest-framework-16-0/rest-framework-16-0-fastapi_captcha + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module provides a simple way to protect several fastapi endpoints +routes with a captcha. + +It curreently supports the following captcha providers: + +- `Google reCAPTCHA `__ +- `hCaptcha `__ +- `Altcha `__ + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Check the ``Use Captcha`` checkbox in your FastAPI endpoint to enable +captcha validation, then enter your captcha provider, secret key and an +array of route url regex. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Akretion + +Contributors +------------ + +- Florian Mounier florian.mounier@akretion.com + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-paradoxxxzero| image:: https://github.com/paradoxxxzero.png?size=40px + :target: https://github.com/paradoxxxzero + :alt: paradoxxxzero + +Current `maintainer `__: + +|maintainer-paradoxxxzero| + +This module is part of the `OCA/rest-framework `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/fastapi_captcha/__init__.py b/fastapi_captcha/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/fastapi_captcha/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/fastapi_captcha/__manifest__.py b/fastapi_captcha/__manifest__.py new file mode 100644 index 000000000..2169ca0d2 --- /dev/null +++ b/fastapi_captcha/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2025 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Fastapi Captcha", + "version": "16.0.1.0.0", + "author": "Akretion, Odoo Community Association (OCA)", + "summary": "Add a captcha to your FastAPI routes", + "category": "Tools", + "depends": ["fastapi"], + "website": "https://github.com/OCA/rest-framework", + "data": [ + "views/fastapi_endpoint_views.xml", + ], + "maintainers": ["paradoxxxzero"], + "demo": [], + "installable": True, + "license": "AGPL-3", +} diff --git a/fastapi_captcha/captcha_middleware.py b/fastapi_captcha/captcha_middleware.py new file mode 100644 index 000000000..035c9038b --- /dev/null +++ b/fastapi_captcha/captcha_middleware.py @@ -0,0 +1,36 @@ +# Copyright 2025 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from starlette.middleware.base import BaseHTTPMiddleware + +from odoo import _ +from odoo.exceptions import AccessError + +from odoo.addons.fastapi.context import odoo_env_ctx + + +class CaptchaMiddleware(BaseHTTPMiddleware): + def __init__(self, app, endpoint_id, root_path, routes_regex=None): + super().__init__(app) + self.endpoint_id = endpoint_id + self.root_path = root_path + self.routes_regex = routes_regex + + async def dispatch(self, request, call_next): + url = request.url.path.replace(self.root_path, "", 1) + if self.routes_regex and not any( + rex.fullmatch(url) for rex in self.routes_regex + ): + return await call_next(request) + + env = odoo_env_ctx.get() + endpoint = env["fastapi.endpoint"].sudo().browse(self.endpoint_id) + token = request.headers.get("X-Captcha-Token") + if not token: + raise AccessError( + _("Captcha token not found in headers"), + ) + endpoint.validate_captcha(token) + response = await call_next(request) + return response diff --git a/fastapi_captcha/models/__init__.py b/fastapi_captcha/models/__init__.py new file mode 100644 index 000000000..b825fab92 --- /dev/null +++ b/fastapi_captcha/models/__init__.py @@ -0,0 +1 @@ +from . import fastapi_endpoint diff --git a/fastapi_captcha/models/fastapi_endpoint.py b/fastapi_captcha/models/fastapi_endpoint.py new file mode 100644 index 000000000..4f0348d91 --- /dev/null +++ b/fastapi_captcha/models/fastapi_endpoint.py @@ -0,0 +1,213 @@ +# Copyright 2025 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import re +from typing import Annotated + +import requests +from starlette.middleware import Middleware + +from odoo import _, api, fields, models +from odoo.exceptions import AccessError, UserError, ValidationError + +from fastapi import Depends, Header + +from ..captcha_middleware import CaptchaMiddleware + + +class FastapiEndpoint(models.Model): + _inherit = "fastapi.endpoint" + + use_captcha = fields.Boolean( + help="If checked, this endpoint will be protected by a captcha", + ) + + captcha_type = fields.Selection( + [ + ("recaptcha", "Recaptcha"), + ("hcaptcha", "Hcaptcha"), + ("altcha", "Altcha"), + ], + help="Type of captcha to use for this endpoint", + ) + + captcha_secret_key = fields.Char( + help="Secret key to use for the captcha validation", + groups="base.group_system", + ) + + captcha_routes_regex = fields.Char( + help="Regexes to match against routes url that should be protected " + "by this captcha, comma separated. If empty, all routes will be protected", + ) + + captcha_minimum_score = fields.Float( + default=0.5, + help="Minimum score to accept the captcha if a score is provided by the " + "captcha service.", + ) + + @property + def _server_env_fields(self): + fields = getattr(super(), "_server_env_fields", None) or {} + fields["captcha_secret_key"] = {} + return fields + + @api.constrains("captcha_routes_regex") + def _check_captcha_routes_regex(self): + """Check that the captcha routes regex is valid""" + for record in self: + if record.captcha_routes_regex: + for rex in record.captcha_routes_regex.split(","): + rex = rex.strip() + if not rex: + continue + # Check that the regex is valid + try: + re.compile(rex) + except re.error as e: + raise ValidationError( + _( + "Invalid regex for captcha routes: %(regex)s (error: %(error)s)" + ) + % { + "regex": rex, + "error": str(e), + } + ) from e + + def _get_fastapi_app_middlewares(self): + # Add the captcha middleware to the list of middlewares if enabled + middlewares = super()._get_fastapi_app_middlewares() + if self.use_captcha: + middlewares.append( + Middleware( + CaptchaMiddleware, + endpoint_id=self.id, + root_path=self.root_path, + routes_regex=[ + re.compile(rex) for rex in self.captcha_routes_regex.split(",") + ] + if self.captcha_routes_regex + else None, + ) + ) + return middlewares + + def _get_fastapi_app_dependencies(self): + # Add the captcha header to the list of dependencies + dependencies = super()._get_fastapi_app_dependencies() + if self.use_captcha: + dependencies.append(Depends(captcha_token)) + + return dependencies + + def validate_captcha(self, captcha_response): + """Validate the captcha response.""" + secret_key = self.captcha_secret_key + if not secret_key: + raise UserError(_("No secret key found for this endpoint")) + + if self.captcha_type == "recaptcha": + return self._validate_recaptcha(captcha_response, secret_key) + elif self.captcha_type == "hcaptcha": + return self._validate_hcaptcha(captcha_response, secret_key) + elif self.captcha_type == "altcha": + return self._validate_altcha(captcha_response, secret_key) + + def _validate_recaptcha(self, captcha_response, secret_key): + """Validate the recaptcha response""" + data = { + "secret": secret_key, + "response": captcha_response, + } + response = requests.post( + "https://www.google.com/recaptcha/api/siteverify", + data=data, + timeout=10, + ) + result = response.json() + success = result.get("success", False) + if not success: + error_codes = result.get("error-codes", ["?"]) + raise AccessError( + _("Recaptcha validation failed: %s") % ", ".join(error_codes) + ) + score = result.get("score", 1) + if score < self.captcha_minimum_score: + raise AccessError( + _("Recaptcha validation failed: score %(score)s < %(min_score)s") + % { + "score": score, + "min_score": self.captcha_minimum_score, + } + ) + + def _validate_hcaptcha(self, captcha_response, secret_key): + """Validate the hcaptcha response""" + + data = { + "secret": secret_key, + "response": captcha_response, + } + response = requests.post( + "https://api.hcaptcha.com/siteverify", data=data, timeout=10 + ) + result = response.json() + success = result.get("success", False) + if not success: + error_codes = result.get("error-codes", ["?"]) + raise AccessError( + _("Hcaptcha validation failed: %s") % ", ".join(error_codes) + ) + score = result.get("score", 1) + if score < self.captcha_minimum_score: + raise AccessError( + _( + "Hcaptcha validation failed: score %(score)s < %(min_score)s (%(reason)s)" + ) + % { + "score": score, + "min_score": self.captcha_minimum_score, + "reason": result.get("score_reason", ""), + } + ) + + def _validate_altcha(self, captcha_response, secret_key): + """Validate the altcha response""" + data = { + "apiKey": secret_key, + "payload": captcha_response, + } + response = requests.post( + "https://eu.altcha.org/api/v1/challenge/verify", + data=data, + timeout=10, + ) + result = response.json() + success = result.get("verified", False) + if not success: + error = result.get("error", "?") + raise AccessError(_("Altcha validation failed: %s") % error) + + @api.model + def _fastapi_app_fields(self): + # We need to reload fastapi app when we change these captcha fields + fields = super()._fastapi_app_fields() + return [ + "use_captcha", + "captcha_routes_regex", + ] + fields + + +def captcha_token( + captcha_token: Annotated[ + str | None, + Header( + alias="X-Captcha-Token", + description="The X-Captcha-Token header is used to specify the captcha ", + ), + ] = None, +) -> str: + return captcha_token diff --git a/fastapi_captcha/readme/CONTRIBUTORS.md b/fastapi_captcha/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..328a37da8 --- /dev/null +++ b/fastapi_captcha/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Florian Mounier diff --git a/fastapi_captcha/readme/DESCRIPTION.md b/fastapi_captcha/readme/DESCRIPTION.md new file mode 100644 index 000000000..80049a67c --- /dev/null +++ b/fastapi_captcha/readme/DESCRIPTION.md @@ -0,0 +1,8 @@ +This module provides a simple way to protect several fastapi endpoints routes with a +captcha. + +It curreently supports the following captcha providers: + +- [Google reCAPTCHA](https://www.google.com/recaptcha) +- [hCaptcha](https://www.hcaptcha.com/) +- [Altcha](https://altcha.org/) diff --git a/fastapi_captcha/readme/USAGE.md b/fastapi_captcha/readme/USAGE.md new file mode 100644 index 000000000..805f8a6b1 --- /dev/null +++ b/fastapi_captcha/readme/USAGE.md @@ -0,0 +1,2 @@ +Check the `Use Captcha` checkbox in your FastAPI endpoint to enable captcha validation, +then enter your captcha provider, secret key and an array of route url regex. diff --git a/fastapi_captcha/static/description/index.html b/fastapi_captcha/static/description/index.html new file mode 100644 index 000000000..1a7f65550 --- /dev/null +++ b/fastapi_captcha/static/description/index.html @@ -0,0 +1,439 @@ + + + + + +Fastapi Captcha + + + +
+

Fastapi Captcha

+ + +

Beta License: AGPL-3 OCA/rest-framework Translate me on Weblate Try me on Runboat

+

This module provides a simple way to protect several fastapi endpoints +routes with a captcha.

+

It curreently supports the following captcha providers:

+ +

Table of contents

+ +
+

Usage

+

Check the Use Captcha checkbox in your FastAPI endpoint to enable +captcha validation, then enter your captcha provider, secret key and an +array of route url regex.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

paradoxxxzero

+

This module is part of the OCA/rest-framework project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/fastapi_captcha/tests/__init__.py b/fastapi_captcha/tests/__init__.py new file mode 100644 index 000000000..4bd3138a6 --- /dev/null +++ b/fastapi_captcha/tests/__init__.py @@ -0,0 +1 @@ +from . import test_fastapi_captcha diff --git a/fastapi_captcha/tests/test_fastapi_captcha.py b/fastapi_captcha/tests/test_fastapi_captcha.py new file mode 100644 index 000000000..23f9436d5 --- /dev/null +++ b/fastapi_captcha/tests/test_fastapi_captcha.py @@ -0,0 +1,189 @@ +# Copyright 2025 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from unittest.mock import patch + +import requests + +from odoo.exceptions import AccessError + +from odoo.addons.fastapi.tests.common import FastAPITransactionCase + +from fastapi import status + + +class FastAPICaptcha(FastAPITransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.default_fastapi_running_user = cls.env.ref("fastapi.my_demo_app_user") + cls.default_fastapi_authenticated_partner = cls.env["res.partner"].create( + {"name": "FastAPI Demo"} + ) + cls.endpoint = cls.env.ref("fastapi.fastapi_endpoint_demo") + cls.endpoint.use_captcha = True + cls.endpoint.captcha_type = "recaptcha" + cls.endpoint.captcha_secret_key = "test_secret" + cls.default_fastapi_app = cls.endpoint._get_app() + + def test_missing_header(self): + with self._create_test_client() as test_client: + with self.assertRaisesRegex( + AccessError, + "Captcha token not found in headers", + ): + test_client.get("/demo/") + with self.assertRaisesRegex( + AccessError, + "Captcha token not found in headers", + ): + test_client.get("/demo/who_ami") + + def test_invalid_header_recaptcha(self): + with patch( + "odoo.addons.fastapi_captcha.models.fastapi_endpoint.requests.post", + return_value=requests.Response(), + ) as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json = lambda: { + "success": False, + "error-codes": ["invalid-input-response"], + } + with self._create_test_client() as test_client: + with self.assertRaisesRegex( + AccessError, + "Recaptcha validation failed: invalid-input-response", + ): + test_client.get("/demo/", headers={"X-Captcha-Token": "invalid"}) + with self.assertRaisesRegex( + AccessError, + "Recaptcha validation failed: invalid-input-response", + ): + test_client.get( + "/demo/who_ami", headers={"X-Captcha-Token": "invalid"} + ) + + def test_valid_header_recaptcha(self): + with patch( + "odoo.addons.fastapi_captcha.models.fastapi_endpoint.requests.post", + return_value=requests.Response(), + ) as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json = lambda: { + "success": True, + "score": 0.9, + } + with self._create_test_client() as test_client: + response = test_client.get( + "/demo/", headers={"X-Captcha-Token": "valid"} + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), {"Hello": "World"}) + + response = test_client.get( + "/demo/who_ami", headers={"X-Captcha-Token": "valid"} + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + partner = self.default_fastapi_authenticated_partner + self.assertDictEqual( + response.json(), + { + "name": partner.name, + "display_name": partner.display_name, + }, + ) + + def test_invalid_header_hcaptcha(self): + self.endpoint.captcha_type = "hcaptcha" + with patch( + "odoo.addons.fastapi_captcha.models.fastapi_endpoint.requests.post", + return_value=requests.Response(), + ) as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json = lambda: { + "success": False, + "error-codes": ["invalid-input-response"], + } + with self._create_test_client() as test_client: + with self.assertRaisesRegex( + AccessError, + "Hcaptcha validation failed: invalid-input-response", + ): + test_client.get("/demo/", headers={"X-Captcha-Token": "invalid"}) + with self.assertRaisesRegex( + AccessError, + "Hcaptcha validation failed: invalid-input-response", + ): + test_client.get( + "/demo/who_ami", headers={"X-Captcha-Token": "invalid"} + ) + + def test_valid_header_hcaptcha(self): + self.endpoint.captcha_type = "hcaptcha" + with patch( + "odoo.addons.fastapi_captcha.models.fastapi_endpoint.requests.post", + return_value=requests.Response(), + ) as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json = lambda: { + "success": True, + "score": 0.9, + } + with self._create_test_client() as test_client: + response = test_client.get( + "/demo/", headers={"X-Captcha-Token": "valid"} + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), {"Hello": "World"}) + + response = test_client.get( + "/demo/who_ami", headers={"X-Captcha-Token": "valid"} + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + partner = self.default_fastapi_authenticated_partner + self.assertDictEqual( + response.json(), + { + "name": partner.name, + "display_name": partner.display_name, + }, + ) + + def test_routes_matching_1(self): + self.endpoint.captcha_routes_regex = "/demo/wh.*,/demo/ca.?" + # Refresh app + self.default_fastapi_app = self.endpoint._get_app() + + with self._create_test_client() as test_client: + response = test_client.get("/demo") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), {"Hello": "World"}) + + with self.assertRaisesRegex( + AccessError, + "Captcha token not found in headers", + ): + test_client.get("/demo/who_ami") + + def test_routes_matching_2(self): + self.endpoint.captcha_routes_regex = "/demo" + # Refresh app + self.default_fastapi_app = self.endpoint._get_app() + + with self._create_test_client() as test_client: + with self.assertRaisesRegex( + AccessError, + "Captcha token not found in headers", + ): + test_client.get("/demo") + + response = test_client.get("/demo/who_ami") + self.assertEqual(response.status_code, status.HTTP_200_OK) + partner = self.default_fastapi_authenticated_partner + self.assertDictEqual( + response.json(), + { + "name": partner.name, + "display_name": partner.display_name, + }, + ) diff --git a/fastapi_captcha/views/fastapi_endpoint_views.xml b/fastapi_captcha/views/fastapi_endpoint_views.xml new file mode 100644 index 000000000..7dde2cb63 --- /dev/null +++ b/fastapi_captcha/views/fastapi_endpoint_views.xml @@ -0,0 +1,37 @@ + + + + + + + fastapi.endpoint + + + + + + + + + + + + + + + + + From 7726dcc793cd58764f32b5cbe71bfc0004eeff32 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Mon, 1 Sep 2025 16:42:38 +0200 Subject: [PATCH 02/10] [IMP] fastapi_captcha: Handle custom altcha url --- fastapi_captcha/captcha_middleware.py | 9 +- fastapi_captcha/models/fastapi_endpoint.py | 17 +- fastapi_captcha/tests/test_fastapi_captcha.py | 146 +++++++++++++++++- .../views/fastapi_endpoint_views.xml | 4 + 4 files changed, 169 insertions(+), 7 deletions(-) diff --git a/fastapi_captcha/captcha_middleware.py b/fastapi_captcha/captcha_middleware.py index 035c9038b..2120b1644 100644 --- a/fastapi_captcha/captcha_middleware.py +++ b/fastapi_captcha/captcha_middleware.py @@ -31,6 +31,13 @@ async def dispatch(self, request, call_next): raise AccessError( _("Captcha token not found in headers"), ) - endpoint.validate_captcha(token) + try: + endpoint.validate_captcha(token) + except AccessError as e: + raise e + except IOError as e: + raise AccessError( + _("Captcha validation failed: %s") % str(e), + ) from e response = await call_next(request) return response diff --git a/fastapi_captcha/models/fastapi_endpoint.py b/fastapi_captcha/models/fastapi_endpoint.py index 4f0348d91..7797fca05 100644 --- a/fastapi_captcha/models/fastapi_endpoint.py +++ b/fastapi_captcha/models/fastapi_endpoint.py @@ -48,6 +48,10 @@ class FastapiEndpoint(models.Model): "captcha service.", ) + captcha_custom_verify_url = fields.Char( + help="Custom URL to use for the captcha verification", + ) + @property def _server_env_fields(self): fields = getattr(super(), "_server_env_fields", None) or {} @@ -180,16 +184,19 @@ def _validate_altcha(self, captcha_response, secret_key): "apiKey": secret_key, "payload": captcha_response, } - response = requests.post( - "https://eu.altcha.org/api/v1/challenge/verify", - data=data, - timeout=10, + url = ( + self.captcha_custom_verify_url + or "https://eu.altcha.org/api/v1/challenge/verify" ) + response = requests.post(url, data=data, timeout=10) result = response.json() success = result.get("verified", False) if not success: error = result.get("error", "?") - raise AccessError(_("Altcha validation failed: %s") % error) + raise AccessError( + _("Altcha (%(url)s) validation failed: %(error)s") + % {"url": url, "error": error} + ) @api.model def _fastapi_app_fields(self): diff --git a/fastapi_captcha/tests/test_fastapi_captcha.py b/fastapi_captcha/tests/test_fastapi_captcha.py index 23f9436d5..b5479a1fb 100644 --- a/fastapi_captcha/tests/test_fastapi_captcha.py +++ b/fastapi_captcha/tests/test_fastapi_captcha.py @@ -5,7 +5,7 @@ import requests -from odoo.exceptions import AccessError +from odoo.exceptions import AccessError, UserError, ValidationError from odoo.addons.fastapi.tests.common import FastAPITransactionCase @@ -26,6 +26,22 @@ def setUpClass(cls): cls.endpoint.captcha_secret_key = "test_secret" cls.default_fastapi_app = cls.endpoint._get_app() + def test_no_secret_key(self): + self.endpoint.captcha_secret_key = False + with self._create_test_client() as test_client: + with self.assertRaisesRegex( + UserError, + "No secret key found for this endpoint", + ): + test_client.get("/demo/", headers={"X-Captcha-Token": "valid"}) + + def test_invalid_regex(self): + with self.assertRaisesRegex( + ValidationError, + r"Invalid regex for captcha routes: /route/\( ", + ): + self.endpoint.captcha_routes_regex = r"/route/(" + def test_missing_header(self): with self._create_test_client() as test_client: with self.assertRaisesRegex( @@ -95,6 +111,7 @@ def test_valid_header_recaptcha(self): def test_invalid_header_hcaptcha(self): self.endpoint.captcha_type = "hcaptcha" + self.endpoint.captcha_minimum_score = 0.8 with patch( "odoo.addons.fastapi_captcha.models.fastapi_endpoint.requests.post", return_value=requests.Response(), @@ -120,6 +137,7 @@ def test_invalid_header_hcaptcha(self): def test_valid_header_hcaptcha(self): self.endpoint.captcha_type = "hcaptcha" + self.endpoint.captcha_minimum_score = 0.8 with patch( "odoo.addons.fastapi_captcha.models.fastapi_endpoint.requests.post", return_value=requests.Response(), @@ -149,6 +167,132 @@ def test_valid_header_hcaptcha(self): }, ) + def test_valid_header_low_score_hcaptcha(self): + self.endpoint.captcha_type = "hcaptcha" + self.endpoint.captcha_minimum_score = 0.8 + with patch( + "odoo.addons.fastapi_captcha.models.fastapi_endpoint.requests.post", + return_value=requests.Response(), + ) as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json = lambda: { + "success": True, + "score": 0.6, + "score_reason": "low-confidence", + } + with self._create_test_client() as test_client: + with self.assertRaisesRegex( + AccessError, + r"Hcaptcha validation failed: score 0.6 < 0.8 \(low-confidence\)", + ): + test_client.get("/demo/", headers={"X-Captcha-Token": "valid"}) + + def test_invalid_header_altcha(self): + self.endpoint.captcha_type = "altcha" + with patch( + "odoo.addons.fastapi_captcha.models.fastapi_endpoint.requests.post", + return_value=requests.Response(), + ) as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json = lambda: { + "verified": False, + "error": "invalid-input-response", + } + with self._create_test_client() as test_client: + with self.assertRaisesRegex( + AccessError, + r"Altcha \(https://eu.altcha.org/api/v1/challenge/verify\) " + "validation failed: invalid-input-response", + ): + test_client.get("/demo/", headers={"X-Captcha-Token": "invalid"}) + + self.assertGreaterEqual(mock_post.call_count, 1) + self.assertEqual( + mock_post.call_args.args[0], + "https://eu.altcha.org/api/v1/challenge/verify", + ) + + with self.assertRaisesRegex( + AccessError, + r"Altcha \(https://eu.altcha.org/api/v1/challenge/verify\) " + "validation failed: invalid-input-response", + ): + test_client.get( + "/demo/who_ami", headers={"X-Captcha-Token": "invalid"} + ) + + def test_valid_header_altcha(self): + self.endpoint.captcha_type = "altcha" + with patch( + "odoo.addons.fastapi_captcha.models.fastapi_endpoint.requests.post", + return_value=requests.Response(), + ) as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json = lambda: { + "verified": True, + } + with self._create_test_client() as test_client: + response = test_client.get( + "/demo/", headers={"X-Captcha-Token": "valid"} + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), {"Hello": "World"}) + + self.assertGreaterEqual(mock_post.call_count, 1) + self.assertEqual( + mock_post.call_args.args[0], + "https://eu.altcha.org/api/v1/challenge/verify", + ) + response = test_client.get( + "/demo/who_ami", headers={"X-Captcha-Token": "valid"} + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + partner = self.default_fastapi_authenticated_partner + self.assertDictEqual( + response.json(), + { + "name": partner.name, + "display_name": partner.display_name, + }, + ) + + def test_valid_header_custom_url_altcha(self): + self.endpoint.captcha_type = "altcha" + self.endpoint.captcha_custom_verify_url = "https://custom.exemple.org/verify" + + with patch( + "odoo.addons.fastapi_captcha.models.fastapi_endpoint.requests.post", + return_value=requests.Response(), + ) as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json = lambda: { + "verified": True, + } + with self._create_test_client() as test_client: + response = test_client.get( + "/demo/", headers={"X-Captcha-Token": "valid"} + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), {"Hello": "World"}) + + self.assertGreaterEqual(mock_post.call_count, 1) + self.assertEqual( + mock_post.call_args.args[0], + "https://custom.exemple.org/verify", + ) + response = test_client.get( + "/demo/who_ami", headers={"X-Captcha-Token": "valid"} + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + partner = self.default_fastapi_authenticated_partner + self.assertDictEqual( + response.json(), + { + "name": partner.name, + "display_name": partner.display_name, + }, + ) + def test_routes_matching_1(self): self.endpoint.captcha_routes_regex = "/demo/wh.*,/demo/ca.?" # Refresh app diff --git a/fastapi_captcha/views/fastapi_endpoint_views.xml b/fastapi_captcha/views/fastapi_endpoint_views.xml index 7dde2cb63..21b6bcde1 100644 --- a/fastapi_captcha/views/fastapi_endpoint_views.xml +++ b/fastapi_captcha/views/fastapi_endpoint_views.xml @@ -28,6 +28,10 @@ attrs="{'required': [('use_captcha', '=', True)]}" /> + From e4dc5cebb91dec9789cc1c9a440bc0310089f162 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Tue, 19 Aug 2025 16:18:59 +0200 Subject: [PATCH 03/10] [IMP] fastapi_captcha: Handle custom altcha url --- fastapi_captcha/README.rst | 5 ++++- fastapi_captcha/readme/DESCRIPTION.md | 2 +- fastapi_captcha/readme/USAGE.md | 3 +++ fastapi_captcha/static/description/index.html | 4 +++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/fastapi_captcha/README.rst b/fastapi_captcha/README.rst index 90272cdf1..e2ebfe7b3 100644 --- a/fastapi_captcha/README.rst +++ b/fastapi_captcha/README.rst @@ -31,7 +31,7 @@ Fastapi Captcha This module provides a simple way to protect several fastapi endpoints routes with a captcha. -It curreently supports the following captcha providers: +It currently supports the following captcha providers: - `Google reCAPTCHA `__ - `hCaptcha `__ @@ -49,6 +49,9 @@ Check the ``Use Captcha`` checkbox in your FastAPI endpoint to enable captcha validation, then enter your captcha provider, secret key and an array of route url regex. +Every matching route will now require a valid captcha token in the +X-Captcha-Token header. + Bug Tracker =========== diff --git a/fastapi_captcha/readme/DESCRIPTION.md b/fastapi_captcha/readme/DESCRIPTION.md index 80049a67c..743b4e136 100644 --- a/fastapi_captcha/readme/DESCRIPTION.md +++ b/fastapi_captcha/readme/DESCRIPTION.md @@ -1,7 +1,7 @@ This module provides a simple way to protect several fastapi endpoints routes with a captcha. -It curreently supports the following captcha providers: +It currently supports the following captcha providers: - [Google reCAPTCHA](https://www.google.com/recaptcha) - [hCaptcha](https://www.hcaptcha.com/) diff --git a/fastapi_captcha/readme/USAGE.md b/fastapi_captcha/readme/USAGE.md index 805f8a6b1..0967b20d8 100644 --- a/fastapi_captcha/readme/USAGE.md +++ b/fastapi_captcha/readme/USAGE.md @@ -1,2 +1,5 @@ Check the `Use Captcha` checkbox in your FastAPI endpoint to enable captcha validation, then enter your captcha provider, secret key and an array of route url regex. + +Every matching route will now require a valid captcha token in the X-Captcha-Token +header. diff --git a/fastapi_captcha/static/description/index.html b/fastapi_captcha/static/description/index.html index 1a7f65550..36ac1639d 100644 --- a/fastapi_captcha/static/description/index.html +++ b/fastapi_captcha/static/description/index.html @@ -372,7 +372,7 @@

Fastapi Captcha

Beta License: AGPL-3 OCA/rest-framework Translate me on Weblate Try me on Runboat

This module provides a simple way to protect several fastapi endpoints routes with a captcha.

-

It curreently supports the following captcha providers:

+

It currently supports the following captcha providers:

  • Google reCAPTCHA
  • hCaptcha
  • @@ -396,6 +396,8 @@

    Usage

    Check the Use Captcha checkbox in your FastAPI endpoint to enable captcha validation, then enter your captcha provider, secret key and an array of route url regex.

    +

    Every matching route will now require a valid captcha token in the +X-Captcha-Token header.

    Bug Tracker

    From 82691ff0478f60d3fc8a27e8c363263b57c6c590 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Mon, 16 Mar 2026 10:29:53 +0100 Subject: [PATCH 04/10] [MIG] fastapi_captcha --- fastapi_captcha/README.rst | 10 ++++---- fastapi_captcha/__manifest__.py | 3 +-- fastapi_captcha/captcha_middleware.py | 7 +++--- fastapi_captcha/models/fastapi_endpoint.py | 24 +++++++++++-------- fastapi_captcha/pyproject.toml | 3 +++ fastapi_captcha/static/description/index.html | 6 ++--- .../views/fastapi_endpoint_views.xml | 20 ++++------------ 7 files changed, 33 insertions(+), 40 deletions(-) create mode 100644 fastapi_captcha/pyproject.toml diff --git a/fastapi_captcha/README.rst b/fastapi_captcha/README.rst index e2ebfe7b3..bfd5ac850 100644 --- a/fastapi_captcha/README.rst +++ b/fastapi_captcha/README.rst @@ -17,13 +17,13 @@ Fastapi Captcha :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github - :target: https://github.com/OCA/rest-framework/tree/16.0/fastapi_captcha + :target: https://github.com/OCA/rest-framework/tree/18.0/fastapi_captcha :alt: OCA/rest-framework .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/rest-framework-16-0/rest-framework-16-0-fastapi_captcha + :target: https://translation.odoo-community.org/projects/rest-framework-18-0/rest-framework-18-0-fastapi_captcha :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&target_branch=16.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&target_branch=18.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -58,7 +58,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -96,6 +96,6 @@ Current `maintainer `__: |maintainer-paradoxxxzero| -This module is part of the `OCA/rest-framework `_ project on GitHub. +This module is part of the `OCA/rest-framework `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/fastapi_captcha/__manifest__.py b/fastapi_captcha/__manifest__.py index 2169ca0d2..25068baa5 100644 --- a/fastapi_captcha/__manifest__.py +++ b/fastapi_captcha/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Fastapi Captcha", - "version": "16.0.1.0.0", + "version": "18.0.1.0.0", "author": "Akretion, Odoo Community Association (OCA)", "summary": "Add a captcha to your FastAPI routes", "category": "Tools", @@ -14,7 +14,6 @@ "views/fastapi_endpoint_views.xml", ], "maintainers": ["paradoxxxzero"], - "demo": [], "installable": True, "license": "AGPL-3", } diff --git a/fastapi_captcha/captcha_middleware.py b/fastapi_captcha/captcha_middleware.py index 2120b1644..b6e276cf2 100644 --- a/fastapi_captcha/captcha_middleware.py +++ b/fastapi_captcha/captcha_middleware.py @@ -4,7 +4,6 @@ from starlette.middleware.base import BaseHTTPMiddleware -from odoo import _ from odoo.exceptions import AccessError from odoo.addons.fastapi.context import odoo_env_ctx @@ -29,15 +28,15 @@ async def dispatch(self, request, call_next): token = request.headers.get("X-Captcha-Token") if not token: raise AccessError( - _("Captcha token not found in headers"), + env._("Captcha token not found in headers"), ) try: endpoint.validate_captcha(token) except AccessError as e: raise e - except IOError as e: + except OSError as e: raise AccessError( - _("Captcha validation failed: %s") % str(e), + env._("Captcha validation failed: %s") % str(e), ) from e response = await call_next(request) return response diff --git a/fastapi_captcha/models/fastapi_endpoint.py b/fastapi_captcha/models/fastapi_endpoint.py index 7797fca05..00d4f7e38 100644 --- a/fastapi_captcha/models/fastapi_endpoint.py +++ b/fastapi_captcha/models/fastapi_endpoint.py @@ -8,7 +8,7 @@ import requests from starlette.middleware import Middleware -from odoo import _, api, fields, models +from odoo import api, fields, models from odoo.exceptions import AccessError, UserError, ValidationError from fastapi import Depends, Header @@ -72,8 +72,9 @@ def _check_captcha_routes_regex(self): re.compile(rex) except re.error as e: raise ValidationError( - _( - "Invalid regex for captcha routes: %(regex)s (error: %(error)s)" + self.env._( + "Invalid regex for captcha routes: %(regex)s " + "(error: %(error)s)" ) % { "regex": rex, @@ -111,7 +112,7 @@ def validate_captcha(self, captcha_response): """Validate the captcha response.""" secret_key = self.captcha_secret_key if not secret_key: - raise UserError(_("No secret key found for this endpoint")) + raise UserError(self.env._("No secret key found for this endpoint")) if self.captcha_type == "recaptcha": return self._validate_recaptcha(captcha_response, secret_key) @@ -136,12 +137,14 @@ def _validate_recaptcha(self, captcha_response, secret_key): if not success: error_codes = result.get("error-codes", ["?"]) raise AccessError( - _("Recaptcha validation failed: %s") % ", ".join(error_codes) + self.env._("Recaptcha validation failed: %s") % ", ".join(error_codes) ) score = result.get("score", 1) if score < self.captcha_minimum_score: raise AccessError( - _("Recaptcha validation failed: score %(score)s < %(min_score)s") + self.env._( + "Recaptcha validation failed: score %(score)s < %(min_score)s" + ) % { "score": score, "min_score": self.captcha_minimum_score, @@ -163,13 +166,14 @@ def _validate_hcaptcha(self, captcha_response, secret_key): if not success: error_codes = result.get("error-codes", ["?"]) raise AccessError( - _("Hcaptcha validation failed: %s") % ", ".join(error_codes) + self.env._("Hcaptcha validation failed: %s") % ", ".join(error_codes) ) score = result.get("score", 1) if score < self.captcha_minimum_score: raise AccessError( - _( - "Hcaptcha validation failed: score %(score)s < %(min_score)s (%(reason)s)" + self.env._( + "Hcaptcha validation failed: score %(score)s < %(min_score)s " + "(%(reason)s)" ) % { "score": score, @@ -194,7 +198,7 @@ def _validate_altcha(self, captcha_response, secret_key): if not success: error = result.get("error", "?") raise AccessError( - _("Altcha (%(url)s) validation failed: %(error)s") + self.env._("Altcha (%(url)s) validation failed: %(error)s") % {"url": url, "error": error} ) diff --git a/fastapi_captcha/pyproject.toml b/fastapi_captcha/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/fastapi_captcha/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/fastapi_captcha/static/description/index.html b/fastapi_captcha/static/description/index.html index 36ac1639d..1a9316e26 100644 --- a/fastapi_captcha/static/description/index.html +++ b/fastapi_captcha/static/description/index.html @@ -369,7 +369,7 @@

    Fastapi Captcha

    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:a507efff5b29eb67557d6283c396db18daddc1e48115ede431daff7f686594b6 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

    Beta License: AGPL-3 OCA/rest-framework Translate me on Weblate Try me on Runboat

    +

    Beta License: AGPL-3 OCA/rest-framework Translate me on Weblate Try me on Runboat

    This module provides a simple way to protect several fastapi endpoints routes with a captcha.

    It currently supports the following captcha providers:

    @@ -404,7 +404,7 @@

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

    +feedback.

    Do not contact contributors directly about support or help with technical issues.

    @@ -432,7 +432,7 @@

    Maintainers

    promote its widespread use.

    Current maintainer:

    paradoxxxzero

    -

    This module is part of the OCA/rest-framework project on GitHub.

    +

    This module is part of the OCA/rest-framework project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    diff --git a/fastapi_captcha/views/fastapi_endpoint_views.xml b/fastapi_captcha/views/fastapi_endpoint_views.xml index 21b6bcde1..424eb15b2 100644 --- a/fastapi_captcha/views/fastapi_endpoint_views.xml +++ b/fastapi_captcha/views/fastapi_endpoint_views.xml @@ -5,8 +5,6 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> - - fastapi.endpoint @@ -15,27 +13,17 @@ - - - + + + - From f720499ecfde0be0bfe2a18da6f8eab17a49e59c Mon Sep 17 00:00:00 2001 From: oca-ci Date: Sat, 6 Jun 2026 16:50:30 +0000 Subject: [PATCH 05/10] [UPD] Update fastapi_captcha.pot --- fastapi_captcha/i18n/fastapi_captcha.pot | 153 +++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 fastapi_captcha/i18n/fastapi_captcha.pot diff --git a/fastapi_captcha/i18n/fastapi_captcha.pot b/fastapi_captcha/i18n/fastapi_captcha.pot new file mode 100644 index 000000000..0cb09fb7d --- /dev/null +++ b/fastapi_captcha/i18n/fastapi_captcha.pot @@ -0,0 +1,153 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fastapi_captcha +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: fastapi_captcha +#: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__altcha +msgid "Altcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Altcha (%(url)s) validation failed: %(error)s" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_custom_verify_url +msgid "Captcha Custom Verify Url" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_minimum_score +msgid "Captcha Minimum Score" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_routes_regex +msgid "Captcha Routes Regex" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_secret_key +msgid "Captcha Secret Key" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_type +msgid "Captcha Type" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/captcha_middleware.py:0 +msgid "Captcha token not found in headers" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/captcha_middleware.py:0 +msgid "Captcha validation failed: %s" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_custom_verify_url +msgid "Custom URL to use for the captcha verification" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model,name:fastapi_captcha.model_fastapi_endpoint +msgid "FastAPI Endpoint" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__hcaptcha +msgid "Hcaptcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Hcaptcha validation failed: %s" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "" +"Hcaptcha validation failed: score %(score)s < %(min_score)s (%(reason)s)" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__use_captcha +msgid "If checked, this endpoint will be protected by a captcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Invalid regex for captcha routes: %(regex)s (error: %(error)s)" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_minimum_score +msgid "" +"Minimum score to accept the captcha if a score is provided by the captcha " +"service." +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "No secret key found for this endpoint" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__recaptcha +msgid "Recaptcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Recaptcha validation failed: %s" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Recaptcha validation failed: score %(score)s < %(min_score)s" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_routes_regex +msgid "" +"Regexes to match against routes url that should be protected by this " +"captcha, comma separated. If empty, all routes will be protected" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_secret_key +msgid "Secret key to use for the captcha validation" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_type +msgid "Type of captcha to use for this endpoint" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__use_captcha +msgid "Use Captcha" +msgstr "" From 994e468106c3e52300037f42257c916f03a0fedf Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sat, 6 Jun 2026 16:54:08 +0000 Subject: [PATCH 06/10] [BOT] post-merge updates --- fastapi_captcha/README.rst | 8 +++-- fastapi_captcha/static/description/icon.png | Bin 0 -> 10254 bytes fastapi_captcha/static/description/index.html | 28 +++++++++++------- 3 files changed, 23 insertions(+), 13 deletions(-) create mode 100644 fastapi_captcha/static/description/icon.png diff --git a/fastapi_captcha/README.rst b/fastapi_captcha/README.rst index bfd5ac850..8556df7f6 100644 --- a/fastapi_captcha/README.rst +++ b/fastapi_captcha/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + =============== Fastapi Captcha =============== @@ -7,13 +11,13 @@ Fastapi Captcha !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:a507efff5b29eb67557d6283c396db18daddc1e48115ede431daff7f686594b6 + !! source digest: sha256:9f521219b54944652afeaf18f64a122f01e1b6646171bc5212e48cb40d0906a0 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github diff --git a/fastapi_captcha/static/description/icon.png b/fastapi_captcha/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1dcc49c24f364e9adf0afbc6fc0bac6dbecdeb11 GIT binary patch literal 10254 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 -Fastapi Captcha +README.rst -
    -

    Fastapi Captcha

    +
    + + +Odoo Community Association + +
    +

    Fastapi Captcha

    -

    Beta License: AGPL-3 OCA/rest-framework Translate me on Weblate Try me on Runboat

    +

    Beta License: AGPL-3 OCA/rest-framework Translate me on Weblate Try me on Runboat

    This module provides a simple way to protect several fastapi endpoints routes with a captcha.

    It currently supports the following captcha providers:

    @@ -392,7 +397,7 @@

    Fastapi Captcha

-

Usage

+

Usage

Check the Use Captcha checkbox in your FastAPI endpoint to enable captcha validation, then enter your captcha provider, secret key and an array of route url regex.

@@ -400,7 +405,7 @@

Usage

X-Captcha-Token header.

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -408,21 +413,21 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

+ From e10ba8f007c4a263ea69e8fdc0770daac8e0ed55 Mon Sep 17 00:00:00 2001 From: mymage Date: Mon, 8 Jun 2026 10:16:21 +0000 Subject: [PATCH 07/10] Added translation using Weblate (Italian) --- fastapi_captcha/i18n/it.po | 154 +++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 fastapi_captcha/i18n/it.po diff --git a/fastapi_captcha/i18n/it.po b/fastapi_captcha/i18n/it.po new file mode 100644 index 000000000..0d0505de0 --- /dev/null +++ b/fastapi_captcha/i18n/it.po @@ -0,0 +1,154 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fastapi_captcha +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: fastapi_captcha +#: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__altcha +msgid "Altcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Altcha (%(url)s) validation failed: %(error)s" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_custom_verify_url +msgid "Captcha Custom Verify Url" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_minimum_score +msgid "Captcha Minimum Score" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_routes_regex +msgid "Captcha Routes Regex" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_secret_key +msgid "Captcha Secret Key" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_type +msgid "Captcha Type" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/captcha_middleware.py:0 +msgid "Captcha token not found in headers" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/captcha_middleware.py:0 +msgid "Captcha validation failed: %s" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_custom_verify_url +msgid "Custom URL to use for the captcha verification" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model,name:fastapi_captcha.model_fastapi_endpoint +msgid "FastAPI Endpoint" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__hcaptcha +msgid "Hcaptcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Hcaptcha validation failed: %s" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "" +"Hcaptcha validation failed: score %(score)s < %(min_score)s (%(reason)s)" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__use_captcha +msgid "If checked, this endpoint will be protected by a captcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Invalid regex for captcha routes: %(regex)s (error: %(error)s)" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_minimum_score +msgid "" +"Minimum score to accept the captcha if a score is provided by the captcha " +"service." +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "No secret key found for this endpoint" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__recaptcha +msgid "Recaptcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Recaptcha validation failed: %s" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Recaptcha validation failed: score %(score)s < %(min_score)s" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_routes_regex +msgid "" +"Regexes to match against routes url that should be protected by this " +"captcha, comma separated. If empty, all routes will be protected" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_secret_key +msgid "Secret key to use for the captcha validation" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_type +msgid "Type of captcha to use for this endpoint" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__use_captcha +msgid "Use Captcha" +msgstr "" From 7872bea4e9c4fa7fd4b925c56b28c4b7e8f19597 Mon Sep 17 00:00:00 2001 From: Thibault REY Date: Thu, 16 Jul 2026 10:54:55 +0000 Subject: [PATCH 08/10] Added translation using Weblate (French) --- fastapi_captcha/i18n/fr.po | 154 +++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 fastapi_captcha/i18n/fr.po diff --git a/fastapi_captcha/i18n/fr.po b/fastapi_captcha/i18n/fr.po new file mode 100644 index 000000000..aec9c525d --- /dev/null +++ b/fastapi_captcha/i18n/fr.po @@ -0,0 +1,154 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fastapi_captcha +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +#. module: fastapi_captcha +#: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__altcha +msgid "Altcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Altcha (%(url)s) validation failed: %(error)s" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_custom_verify_url +msgid "Captcha Custom Verify Url" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_minimum_score +msgid "Captcha Minimum Score" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_routes_regex +msgid "Captcha Routes Regex" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_secret_key +msgid "Captcha Secret Key" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_type +msgid "Captcha Type" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/captcha_middleware.py:0 +msgid "Captcha token not found in headers" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/captcha_middleware.py:0 +msgid "Captcha validation failed: %s" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_custom_verify_url +msgid "Custom URL to use for the captcha verification" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model,name:fastapi_captcha.model_fastapi_endpoint +msgid "FastAPI Endpoint" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__hcaptcha +msgid "Hcaptcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Hcaptcha validation failed: %s" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "" +"Hcaptcha validation failed: score %(score)s < %(min_score)s (%(reason)s)" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__use_captcha +msgid "If checked, this endpoint will be protected by a captcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Invalid regex for captcha routes: %(regex)s (error: %(error)s)" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_minimum_score +msgid "" +"Minimum score to accept the captcha if a score is provided by the captcha " +"service." +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "No secret key found for this endpoint" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__recaptcha +msgid "Recaptcha" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Recaptcha validation failed: %s" +msgstr "" + +#. module: fastapi_captcha +#. odoo-python +#: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 +msgid "Recaptcha validation failed: score %(score)s < %(min_score)s" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_routes_regex +msgid "" +"Regexes to match against routes url that should be protected by this " +"captcha, comma separated. If empty, all routes will be protected" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_secret_key +msgid "Secret key to use for the captcha validation" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_type +msgid "Type of captcha to use for this endpoint" +msgstr "" + +#. module: fastapi_captcha +#: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__use_captcha +msgid "Use Captcha" +msgstr "" From b9a080252a43755355e387bc1d63dbf4011704db Mon Sep 17 00:00:00 2001 From: Thibault REY Date: Thu, 16 Jul 2026 11:07:04 +0000 Subject: [PATCH 09/10] Translated using Weblate (French) Currently translated at 100.0% (25 of 25 strings) Translation: rest-framework-18.0/rest-framework-18.0-fastapi_captcha Translate-URL: https://translation.odoo-community.org/projects/rest-framework-18-0/rest-framework-18-0-fastapi_captcha/fr/ --- fastapi_captcha/i18n/fr.po | 55 +++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/fastapi_captcha/i18n/fr.po b/fastapi_captcha/i18n/fr.po index aec9c525d..dea1d68ae 100644 --- a/fastapi_captcha/i18n/fr.po +++ b/fastapi_captcha/i18n/fr.po @@ -6,82 +6,84 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 18.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2026-07-16 13:42+0000\n" +"Last-Translator: Thibault REY \n" "Language-Team: none\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 5.15.2\n" #. module: fastapi_captcha #: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__altcha msgid "Altcha" -msgstr "" +msgstr "Altcha" #. module: fastapi_captcha #. odoo-python #: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 msgid "Altcha (%(url)s) validation failed: %(error)s" -msgstr "" +msgstr "??chec de la validation d'Altcha (%(url)s) : %(error)s" #. module: fastapi_captcha #: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_custom_verify_url msgid "Captcha Custom Verify Url" -msgstr "" +msgstr "URL de v??rification personnalis??e du Captcha" #. module: fastapi_captcha #: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_minimum_score msgid "Captcha Minimum Score" -msgstr "" +msgstr "Score minimum du Captcha" #. module: fastapi_captcha #: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_routes_regex msgid "Captcha Routes Regex" -msgstr "" +msgstr "Expressions r??guli??res (regex) des routes prot??g??es par le Captcha" #. module: fastapi_captcha #: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_secret_key msgid "Captcha Secret Key" -msgstr "" +msgstr "Cl?? secr??te du Captcha" #. module: fastapi_captcha #: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__captcha_type msgid "Captcha Type" -msgstr "" +msgstr "Type de Captcha" #. module: fastapi_captcha #. odoo-python #: code:addons/fastapi_captcha/captcha_middleware.py:0 msgid "Captcha token not found in headers" -msgstr "" +msgstr "Le jeton Captcha n'a pas ??t?? trouv?? dans les en-t??tes" #. module: fastapi_captcha #. odoo-python #: code:addons/fastapi_captcha/captcha_middleware.py:0 msgid "Captcha validation failed: %s" -msgstr "" +msgstr "??chec de la validation du Captcha : %s" #. module: fastapi_captcha #: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_custom_verify_url msgid "Custom URL to use for the captcha verification" -msgstr "" +msgstr "URL personnalis??e ?? utiliser pour la v??rification par le Captcha" #. module: fastapi_captcha #: model:ir.model,name:fastapi_captcha.model_fastapi_endpoint msgid "FastAPI Endpoint" -msgstr "" +msgstr "Endpoint FastAPI" #. module: fastapi_captcha #: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__hcaptcha msgid "Hcaptcha" -msgstr "" +msgstr "Hcaptcha" #. module: fastapi_captcha #. odoo-python #: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 msgid "Hcaptcha validation failed: %s" -msgstr "" +msgstr "??chec de la validation Hcaptcha : %s" #. module: fastapi_captcha #. odoo-python @@ -89,17 +91,21 @@ msgstr "" msgid "" "Hcaptcha validation failed: score %(score)s < %(min_score)s (%(reason)s)" msgstr "" +"??chec de la validation Hcaptcha : score %(score)s < %(min_score)s (%(reason)" +"s)" #. module: fastapi_captcha #: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__use_captcha msgid "If checked, this endpoint will be protected by a captcha" -msgstr "" +msgstr "Si cette case est coch??e, ce Endpoint sera prot??g?? par un captcha" #. module: fastapi_captcha #. odoo-python #: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 msgid "Invalid regex for captcha routes: %(regex)s (error: %(error)s)" msgstr "" +"Expression r??guli??re (regex) non valide pour les routes captcha : %(regex)s " +"(erreur : %(error)s)" #. module: fastapi_captcha #: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_minimum_score @@ -107,29 +113,31 @@ msgid "" "Minimum score to accept the captcha if a score is provided by the captcha " "service." msgstr "" +"Score minimum requis pour valider le captcha si un score est fourni par le " +"service de captcha." #. module: fastapi_captcha #. odoo-python #: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 msgid "No secret key found for this endpoint" -msgstr "" +msgstr "Aucune cl?? secr??te n'a ??t?? trouv??e pour ce Endpoint" #. module: fastapi_captcha #: model:ir.model.fields.selection,name:fastapi_captcha.selection__fastapi_endpoint__captcha_type__recaptcha msgid "Recaptcha" -msgstr "" +msgstr "Recaptcha" #. module: fastapi_captcha #. odoo-python #: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 msgid "Recaptcha validation failed: %s" -msgstr "" +msgstr "??chec de la validation Recaptcha : %s" #. module: fastapi_captcha #. odoo-python #: code:addons/fastapi_captcha/models/fastapi_endpoint.py:0 msgid "Recaptcha validation failed: score %(score)s < %(min_score)s" -msgstr "" +msgstr "??chec de la validation Recaptcha : score %(score)s < %(min_score)s" #. module: fastapi_captcha #: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_routes_regex @@ -137,18 +145,21 @@ msgid "" "Regexes to match against routes url that should be protected by this " "captcha, comma separated. If empty, all routes will be protected" msgstr "" +"Expressions r??guli??res (regex) permettant de faire correspondre les URL des " +"routes qui doivent ??tre prot??g??es par ce captcha, s??par??es par des virgules. " +"Si ce champ est vide, toutes les routes seront prot??g??es." #. module: fastapi_captcha #: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_secret_key msgid "Secret key to use for the captcha validation" -msgstr "" +msgstr "Cl?? secr??te ?? utiliser pour la validation du captcha" #. module: fastapi_captcha #: model:ir.model.fields,help:fastapi_captcha.field_fastapi_endpoint__captcha_type msgid "Type of captcha to use for this endpoint" -msgstr "" +msgstr "Type de CAPTCHA ?? utiliser pour ce point de terminaison" #. module: fastapi_captcha #: model:ir.model.fields,field_description:fastapi_captcha.field_fastapi_endpoint__use_captcha msgid "Use Captcha" -msgstr "" +msgstr "Utiliser le Captcha" From 56e1b5d03ce1572addfaabc90543bf01fadc003c Mon Sep 17 00:00:00 2001 From: Churnika-Chaudhari Date: Fri, 23 Oct 2026 15:46:40 +0530 Subject: [PATCH 10/10] [MIG] fastapi_captcha --- fastapi_captcha/README.rst | 10 ++--- fastapi_captcha/__manifest__.py | 2 +- fastapi_captcha/captcha_middleware.py | 2 +- fastapi_captcha/models/fastapi_endpoint.py | 44 ++++++++++--------- fastapi_captcha/tests/test_fastapi_captcha.py | 1 + 5 files changed, 31 insertions(+), 28 deletions(-) diff --git a/fastapi_captcha/README.rst b/fastapi_captcha/README.rst index 8556df7f6..be3278219 100644 --- a/fastapi_captcha/README.rst +++ b/fastapi_captcha/README.rst @@ -21,13 +21,13 @@ Fastapi Captcha :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Frest--framework-lightgray.png?logo=github - :target: https://github.com/OCA/rest-framework/tree/18.0/fastapi_captcha + :target: https://github.com/OCA/rest-framework/tree/19.0/fastapi_captcha :alt: OCA/rest-framework .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/rest-framework-18-0/rest-framework-18-0-fastapi_captcha + :target: https://translation.odoo-community.org/projects/rest-framework-19-0/rest-framework-19-0-fastapi_captcha :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/rest-framework&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -62,7 +62,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -100,6 +100,6 @@ Current `maintainer `__: |maintainer-paradoxxxzero| -This module is part of the `OCA/rest-framework `_ project on GitHub. +This module is part of the `OCA/rest-framework `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/fastapi_captcha/__manifest__.py b/fastapi_captcha/__manifest__.py index 25068baa5..550bf6137 100644 --- a/fastapi_captcha/__manifest__.py +++ b/fastapi_captcha/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Fastapi Captcha", - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "author": "Akretion, Odoo Community Association (OCA)", "summary": "Add a captcha to your FastAPI routes", "category": "Tools", diff --git a/fastapi_captcha/captcha_middleware.py b/fastapi_captcha/captcha_middleware.py index b6e276cf2..ed6c4ccbc 100644 --- a/fastapi_captcha/captcha_middleware.py +++ b/fastapi_captcha/captcha_middleware.py @@ -36,7 +36,7 @@ async def dispatch(self, request, call_next): raise e except OSError as e: raise AccessError( - env._("Captcha validation failed: %s") % str(e), + env._("Captcha validation failed: %s", str(e)), ) from e response = await call_next(request) return response diff --git a/fastapi_captcha/models/fastapi_endpoint.py b/fastapi_captcha/models/fastapi_endpoint.py index 00d4f7e38..3895d7063 100644 --- a/fastapi_captcha/models/fastapi_endpoint.py +++ b/fastapi_captcha/models/fastapi_endpoint.py @@ -74,12 +74,10 @@ def _check_captcha_routes_regex(self): raise ValidationError( self.env._( "Invalid regex for captcha routes: %(regex)s " - "(error: %(error)s)" + "(error: %(error)s)", + regex=rex, + error=str(e), ) - % { - "regex": rex, - "error": str(e), - } ) from e def _get_fastapi_app_middlewares(self): @@ -137,18 +135,19 @@ def _validate_recaptcha(self, captcha_response, secret_key): if not success: error_codes = result.get("error-codes", ["?"]) raise AccessError( - self.env._("Recaptcha validation failed: %s") % ", ".join(error_codes) + self.env._( + "Recaptcha validation failed: %s", + ", ".join(error_codes), + ) ) score = result.get("score", 1) if score < self.captcha_minimum_score: raise AccessError( self.env._( - "Recaptcha validation failed: score %(score)s < %(min_score)s" + "Recaptcha validation failed: score %(score)s < %(min_score)s", + score=score, + min_score=self.captcha_minimum_score, ) - % { - "score": score, - "min_score": self.captcha_minimum_score, - } ) def _validate_hcaptcha(self, captcha_response, secret_key): @@ -166,20 +165,20 @@ def _validate_hcaptcha(self, captcha_response, secret_key): if not success: error_codes = result.get("error-codes", ["?"]) raise AccessError( - self.env._("Hcaptcha validation failed: %s") % ", ".join(error_codes) + self.env._( + "Hcaptcha validation failed: %s", + ", ".join(error_codes), + ) ) score = result.get("score", 1) if score < self.captcha_minimum_score: raise AccessError( self.env._( - "Hcaptcha validation failed: score %(score)s < %(min_score)s " - "(%(reason)s)" + "Hcaptcha validation failed: score %(score)s < %(min_score)s (%(reason)s)", + score=score, + min_score=self.captcha_minimum_score, + reason=result.get("score_reason", ""), ) - % { - "score": score, - "min_score": self.captcha_minimum_score, - "reason": result.get("score_reason", ""), - } ) def _validate_altcha(self, captcha_response, secret_key): @@ -198,8 +197,11 @@ def _validate_altcha(self, captcha_response, secret_key): if not success: error = result.get("error", "?") raise AccessError( - self.env._("Altcha (%(url)s) validation failed: %(error)s") - % {"url": url, "error": error} + self.env._( + "Altcha (%(url)s) validation failed: %(error)s", + url=url, + error=error, + ) ) @api.model diff --git a/fastapi_captcha/tests/test_fastapi_captcha.py b/fastapi_captcha/tests/test_fastapi_captcha.py index b5479a1fb..379e2d175 100644 --- a/fastapi_captcha/tests/test_fastapi_captcha.py +++ b/fastapi_captcha/tests/test_fastapi_captcha.py @@ -16,6 +16,7 @@ class FastAPICaptcha(FastAPITransactionCase): @classmethod def setUpClass(cls): super().setUpClass() + cls.env["fastapi.endpoint"]._load_demo_data() cls.default_fastapi_running_user = cls.env.ref("fastapi.my_demo_app_user") cls.default_fastapi_authenticated_partner = cls.env["res.partner"].create( {"name": "FastAPI Demo"}