diff --git a/README.md b/README.md
index 60b2874..ba198a6 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,6 @@ requiring additional configuration.
> You can generate a new secured HMAC key using:
> ``python -c "import secrets; print(secrets.token_hex(64))"``
-
## Usage
### Adding the CAPTCHA Field to Your Form
@@ -124,12 +123,77 @@ class MyForm(forms.Form):
> registering the view.
> For example: ``AltchaChallengeView.as_view(max_number=2000000)``
+### Replay Attack Protection
+
+Django Altcha **automatically protects against replay attacks** by ensuring each
+challenge can only be used once.
+When a challenge is successfully validated, it is stored in a
+cache and any subsequent attempt to reuse the same challenge will be rejected.
+
+This protection is enabled by default and requires no additional configuration for
+single-process deployments.
+
+> [!IMPORTANT]
+> The default in-memory cache is **not shared across workers**. If you run multiple
+> workers (e.g., with gunicorn or uwsgi), you must configure a shared cache backend
+> using the `ALTCHA_CACHE_ALIAS` setting.
+
## Settings
### ALTCHA_HMAC_KEY
**Required.** This key is used to HMAC-sign ALTCHA challenges and **must be kept secret**.
+### ALTCHA_CACHE_ALIAS
+
+Cache alias used for replay attack protection.
+
+By default, challenges are stored in a local in-memory cache to prevent reuse.
+This works well for single-process deployments, but **does not protect against
+replay attacks in multi-worker setups** (e.g., gunicorn or uwsgi with multiple workers).
+
+For production deployments with multiple workers, configure a shared cache backend:
+
+**Using Redis or Memcached:**
+
+```python
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
+ 'LOCATION': 'redis://127.0.0.1:6379',
+ }
+}
+ALTCHA_CACHE_ALIAS = 'default'
+```
+
+**Using Database Caching:**
+
+If you prefer not to set up Redis or Memcached,
+[Django's built-in database cache](https://docs.djangoproject.com/en/dev/topics/cache/#database-caching)
+is a simple alternative:
+
+```python
+CACHES = {
+ 'altcha': {
+ 'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
+ 'LOCATION': 'altcha_cache',
+ }
+}
+ALTCHA_CACHE_ALIAS = 'altcha'
+```
+
+Then create the cache table:
+
+```bash
+python manage.py createcachetable
+```
+
+### ALTCHA_CHALLENGE_EXPIRE
+
+Challenge expiration duration in milliseconds.
+Defaults to 20 minutes as per
+[Altcha security recommendations](https://altcha.org/docs/v2/security-recommendations/).
+
### ALTCHA_JS_URL
URL of the Altcha JavaScript file.
@@ -150,12 +214,7 @@ Only loaded when `ALTCHA_INCLUDE_TRANSLATIONS` is `True`.
### ALTCHA_VERIFICATION_ENABLED
Set to `False` to skip Altcha validation altogether.
-
-### ALTCHA_CHALLENGE_EXPIRE
-
-Challenge expiration duration in milliseconds.
-Default to 20 minutes as per Altcha security recommendations.
-See https://altcha.org/docs/v2/security-recommendations/
+Defaults to `True`.
## Contributing
@@ -165,4 +224,5 @@ Feel free to submit issues or pull requests!
## License
This project is licensed under the **MIT License**.
-See the [LICENSE](./LICENSE) file for details.
+See the [LICENSE](https://github.com/aboutcode-org/django-altcha/blob/main/LICENSE)
+file for details.
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 360287f..f603de2 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -34,6 +34,7 @@
"sphinx_rtd_dark_mode",
"sphinx.ext.extlinks",
"sphinx_copybutton",
+ "myst_parser",
]
@@ -75,7 +76,7 @@
html_context = {
"display_github": True,
- "github_user": "nexB",
+ "github_user": "aboutcode-org",
"github_repo": "django-altcha",
"github_version": "develop", # branch
"conf_py_path": "/docs/source/", # path in the checkout to the docs root
diff --git a/docs/source/index.rst b/docs/source/index.md
similarity index 59%
rename from docs/source/index.rst
rename to docs/source/index.md
index 3ea86c2..b1bf228 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.md
@@ -1,5 +1,4 @@
-Welcome to django-altcha's documentation!
-=========================================
+# Welcome to django-altcha's documentation!
Django Altcha is a Django library that provides easy integration of Altcha CAPTCHA
into your Django forms, enhancing user verification with configurable options.
@@ -8,15 +7,11 @@ By default, CAPTCHA validation operates in a **fully self-hosted mode**,
**eliminating the need for external services** while ensuring privacy and control over
the verification process.
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
+```{include} ../../README.md
+```
- installation
+## Indices and tables
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+- {ref}`genindex`
+- {ref}`modindex`
+- {ref}`search`
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
deleted file mode 100644
index ecefa3e..0000000
--- a/docs/source/installation.rst
+++ /dev/null
@@ -1,111 +0,0 @@
-Installation
-============
-
-1. **Install the package:**
-
-.. code-block:: bash
-
- pip install django-altcha
-
-2. **Add to INSTALLED_APPS:**
-
-Update your Django project's ``settings.py``:
-
-.. code-block:: python
-
- INSTALLED_APPS = [
- # Other installed apps
- "django_altcha",
- ]
-
-3. **Set your secret HMAC key:**
-
-This key is used to HMAC-sign ALTCHA challenges and **must be kept secret**.
-
-**Treat it like a password**: use a secure, 64-character hex string.
-
-Update your Django project's ``settings.py``:
-
-.. code-block:: python
-
- ALTCHA_HMAC_KEY="your_secret_hmac_key"
-
-.. note::
- You can generate a new secured HMAC key using:
- ``python -c "import secrets; print(secrets.token_hex(64))"``
-
-Usage
-=====
-
-Adding the CAPTCHA Field to Your Form
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To add a Altcha CAPTCHA field to a Django form, import ``AltchaField`` and add it to
-your form definition:
-
-.. code-block:: python
-
- from django import forms
- from django_altcha import AltchaField
-
- class MyForm(forms.Form):
- captcha = AltchaField()
-
-Configuration Options
----------------------
-
-You can pass configuration options to ``AltchaField`` that are supported by Altcha.
-These options are documented at
-`Altcha's website integration guide `_.
-
-Example with additional options:
-
-.. code-block:: python
-
- class MyForm(forms.Form):
- captcha = AltchaField(
- floating=True, # Enables floating behavior
- debug=True, # Enables debug mode (for development)
- # Additional options supported by Altcha
- )
-
-Settings
-========
-
-ALTCHA_HMAC_KEY
-~~~~~~~~~~~~~~~
-
-**Required.**
-This key is used to HMAC-sign ALTCHA challenges and **must be kept secret**.
-
-ALTCHA_JS_URL
-~~~~~~~~~~~~~
-
-URL of the Altcha JavaScript file.
-Defaults to the bundled django-altcha file.
-
-ALTCHA_INCLUDE_TRANSLATIONS
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Whether to include `Altcha translations `_.
-Defaults to ``False``.
-
-ALTCHA_JS_TRANSLATIONS_URL
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-URL of the Altcha translations JavaScript file.
-Defaults to the bundled django-altcha file.
-
-Only loaded when ``ALTCHA_INCLUDE_TRANSLATIONS`` is ``True``.
-
-ALTCHA_VERIFICATION_ENABLED
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Set to ``False`` to skip Altcha validation altogether.
-
-ALTCHA_CHALLENGE_EXPIRE
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Challenge expiration duration in milliseconds.
-Default to 20 minutes as per Altcha security recommendations.
-See https://altcha.org/docs/v2/security-recommendations/
diff --git a/pyproject.toml b/pyproject.toml
index 0ce2725..faaae20 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -48,6 +48,7 @@ docs = [
"sphinx-autobuild",
"sphinx-rtd-dark-mode>=1.3.0",
"sphinx-copybutton",
+ "myst-parser",
]
[project.urls]