Django app for 2FA recovery codes
Project description
🔐 Django 2FA Recovery Codes
The premises of this resuable application, is that it takes any Django application and extends that application so that it can now use the 2FA recovery codes as a backup login should you lose access.
django_auth_recovery_codes is a Django app that provides a robust system for generating, storing, and managing 2FA recovery codes. Unlike a full two-factor authentication apps, this package focuses solely on recovery codes, although this is a lightweight application it is a very powerful tool, offering fine-grained control and asynchronous management for better UX and performance.
Table of Contents
- Requirements & Key Technologies
- Key Features
- Quickstart video walkthrough
- How it Differs From A Full Two-Factor Authentication Apps
- 2FA Recovery Code Generator
- Installation
- Quick Example
- How to Use 2FA Recovery Codes
- Setting up the Cache or using the default cache
- How does the cache work?
- Using Pagination and Caching
- What cache should I use?
- Using Django Cache Without Configuring a Backend
- Django-Q vs Celery and why Django Auth Recovery codes use Django-q
- Why this application uses Django-Q
- Using Django-Q with
django_auth_recovery_codes - Benefits of using Django-Q
- Setting up Django-q
- Django Auth Recovery Settings
- Using and setting up Django-q
- Django Auth Recovery flag settings
- Recovery Code Display Settings
- Cooldown Settings Flags
- Rate Limiting & Caching
- Audit & Logging Setting Flags
- Email & Admin Settings Flags
- Code Management & Limits
- Site Settings Flags
- Example Flag Usage
- Best Practices for Managing Environment Variables
- Default Values & Required Variables
- Run checks to verify that flags are valid
- Sending Emails and using Logging
- Using async vs synchronous
- Configuration settings
- Hang on a minute, why can I email myself the code only once, and only if I haven’t logged out after generating it?
- What does this mean for your codes?
- What happens if I refresh the page, can I still email myself the code?
- But if I’m still logged in, why can I only email myself a single copy?
- Can I email myself a copy if I generate a new batch?
- Using Logging with the Application
- Downloading Recovery Codes
- Quickstart and Walkthrough read
- Setup
- Installation (with Virtual Environment)
- 3. Upgrade pip (optional but recommended)
- 4. Install Django (latest version)
- 5. Install the recovery codes package
- 6. Verify installation
- 8. Run initial migrations
- 9. Create a Django superuser
- 10. Start a new app called
home - 12. Run the development server
- Configure URLs
- Configure your Settings.py file
- 16. Set up the file-based email backend (for testing)
- 17. Run the system checks
- 17a. Generate a recovery code
- Run Services
- Create a Home View
- Access the Admin
- Access the Recovery Codes page dashboard
- Code Generation
- Verifying Generated Codes
- Downloaded and Emailed Code
- Invalidating or Deleting a Code
- Viewing the Code Batch History
- Logout of the application
- Failed Attempts and Rate Limiting
- Successful Login
- Existing Project Setup
- Scheduling a Code Removal Using Django-Q
- Running Tests
- Warning ⚠
- Known Issues
- License
- Support
Requirements & Key Technologies
-
Python 3.10+
This library uses Structural Pattern Matching, introduced in Python 3.10, via thematch-casesyntax. -
Django 5.2+
The project is built and tested using Django 5.2, which has Long-Term Support (LTS).
Using an earlier version, such as Django 4.1.x, may work in some cases but is not guaranteed and could potentially break the application, since it has only been tested with Django 5.2.
Why Python 3.10?
The project relies on the match-case syntax, which provides a more readable and expressive way to handle complex conditional logic.
Key Technologies and Libraries Used
- EmailSender: a lightweight but powerful library for sending chainable rich emails with templates. Find out more
- EmailSenderLogger: a lightweight but powerful library for logging, emails sent by EmailSender. Find out more
- Django-q: an asynchronous task manager used for processing background tasks, including using EmailSender to send emails.
- JavaScript (JS): for interactivity and fetch requests.
- HTML: for structuring content.
- CSS: for styling the user interface.
Key Features
- Generate recovery codes in configurable batches.
- Track recovery codes individually:
- Mark codes as used, inactive, or scheduled for deletion.
- User the 2FA code to login which becomes invalid after a single use
- Batch management:
- Track issued and removed codes per batch.
- Statuses for active, invalidated, or deleted batches.
- Login
- Login in using your 2FA Recovery Backup code
- Asynchronous cleanup using Django-Q:
- Delete expired or invalid codes without locking the database.
- Scheduler allows admins to set cleanup intervals (e.g., every 2 days) without touching code.
- Optional options to email the report to the admin
- Optional option to store user emails (Whenever the user send themselves a code) in the database
- Optional scheduler to delete Recovery code Audit model (tracks the users, the number of code issued, time issued, etc)
- Secure storage:
- Codes are hashed before saving; no plaintext storage.
- Extensible utilities for generating and verifying codes.
How It Differs From A Full Two-Factor Authentication Apps?
django_auth_recovery_codes is designed solely for recovery codes, offering fine-grained control, asynchronous management, and admin-friendly batch handling.
-
User UI interface
- Dedicated login interface page to enter your email and 2FA recovery code
- Dashboard that allows the user to:
* Generate a batch of 2FA recovery codes (default=10 generated, configurable via settings flags) with expiry date or doesn't expiry
-
Regenerate code (Uses brute force rate limiter with a penalty that increases the wait time if codes is regenerated within that time window)
-
Email, Delete or Download entire codes via the buttons
-
One-time verification code setup form
- A one-time setup that allows the user to enter a 2FA code after generation (for the first time) to verify that the backend has configured it correctly without marking the code as used. The tests indicate whether the code has been set up correctly.
-
Invalidate or delete a code via interactive form
-
view batch histories
Example a single recovery code batch View
Field Value Batch ID 8C2655A1-8F14-4B56-AEC8-7DDA72F887A4 Expiry info Active User Egbie Date issued 23 Sept. 2025, Date modified 23 Sept. 2025, Number of codes issued 10 Number of codes used 0 Number of deactivated 0 Number of removed 0 Has generated code batch True Has viewed code batch True Has downloaded code batch False Has emailed code batch False -
Pagination to split the batch recovery codes history on different pages instead of one long page
-
-
Focuses exclusively on recovery codes, rather than full 2FA flows.
-
Built-in logger configuration which can be imported into settings or merged with an existing logger.
-
Asynchronous Usage
- Built with asynchronous usage using Django-Q:
- Automatically deletes expired or invalid codes when uses with scheduler.
- On a successful delete scheduler generates an audit report of the number of deleted codes and sends it to admin via email.
- Email sending can be configured to run asynchronous or synchronous depending on your environment:
DEBUG = True: uses synchronous sending (easy for development or testing).DEBUG = False: uses asynchronous sending (recommended for production; doesn’t block the application while sending in the background).
-
Admin interface
- Admin-friendly view interface code management, including the ability to scheduler deletion for expired or invalid codes e.g (every 2 days, etc) or even the audit history.
-
Code tracking
- Individual code tracking with granular control over each code.
-
Flag configuration
- Optional configuration to turn logger on or off to track the actions of users generating recovery codes, email sent, various aspect of the models, etc.
- Optional storage of user email in the model for auditing purposes.
-
Caching
- Utilises caching (Redis, Memcached, default cache, etc) for
- Pagination and page reads
- Brute-force rate limiting
- Other database-heavy operations
- Reduces database hits until cache expires or updates are made.
- Utilises caching (Redis, Memcached, default cache, etc) for
-
Email and logging capabilities
- Email sending capabilities via
EmailSenderlibrary. - Email logging via
EmailSenderLoggerlibrary.
- Email sending capabilities via
-
Rate limiter
- Maximum login attempt control with a brute-force rate limiter:
- Configurable penalty wait times that increase if a user retries during the wait window.
- Brute-force rate limiter for code generation:
- Prevents spam and imposes a penalty if the user attempts regeneration too soon.
- Maximum login attempt control with a brute-force rate limiter:
-
Code generation attribrutes
- Generate codes that expire or have no expiry.
- Allow users to download or email codes (one per batch).
- Invalidate, delete a single code or an entire batch easily.
- Users can view batch details, e.g., number of codes generated, emailed, or downloaded.
-
Configurable flags for developer
Configuration flags settings for the Django Auth Recovery code app
```python DJANGO_AUTH_RECOVERY_CODES_ADMIN_SENDER_EMAIL = DJANGO_AUTH_RECOVERY_CODE_ADMIN_EMAIL_HOST_USER = DJANGO_AUTH_RECOVERY_CODE_ADMIN_USERNAME = DJANGO_AUTH_RECOVERY_CODE_STORE_EMAIL_LOG = DJANGO_AUTH_RECOVERY_KEY = DJANGO_AUTH_RECOVERY_CODE_AUDIT_ENABLE_AUTO_CLEANUP = DJANGO_AUTH_RECOVERY_CODE_AUDIT_RETENTION_DAYS = DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_RETENTION_DAYS = DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_SCHEDULER_USE_LOGGER = DJANGO_AUTH_RECOVERY_CODES_AUTH_RATE_LIMITER_USE_CACHE = DJANGO_AUTH_RECOVERY_CODES_BASE_COOLDOWN = DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_CUTOFF_POINT = DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_MULTIPLIER = DJANGO_AUTH_RECOVERY_CODES_MAX_LOGIN_ATTEMPTS = DJANGO_AUTH_RECOVERY_CODES_CACHE_MAX = DJANGO_AUTH_RECOVERY_CODES_CACHE_MIN = DJANGO_AUTH_RECOVERY_CODES_CACHE_TTL = DJANGO_AUTH_RECOVERY_CODE_MAX_VISIBLE = DJANGO_AUTH_RECOVERY_CODE_PER_PAGE = DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE = DJANGO_AUTH_RECOVERY_CODES_MAX_DELETIONS_PER_RUN = DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FILE_NAME = DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FORMAT = DJANGO_AUTH_RECOVERY_CODES_SITE_NAME = DJANGO_AUTH_RECOVERY_CODE_REDIRECT_VIEW_AFTER_LOGOUT = DJANGO_AUTH_RECOVERY_CODE_EMAIL_SUCCESS_MSG = ```
Django 2FA Recovery Code Generator
Security Overview
These 2FA recovery codes generated are designed to be extremely secure and practically impossible to guess. Protects against Brute force, Rainbow attacks and timed attacks
Code Format
XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX
- 6 groups of 6 characters each (36 characters)
- Alphabet: 60 characters (
A–Z,a–z,2–9), the app avoiding confusing characters like0vsOand1vsl - Cryptographically secure randomness ensures codes are unpredictable
Entropy
Entropy measures how unpredictable a code is, the higher the entropy, the harder it is to guess.
- Entropy per character:
$$ \log_2(60) \approx 5.91 \text{ bits} $$
- Entropy per group (6 characters):
$$ 6 \times 5.91 \approx 35.5 \text{ bits} $$
- Total entropy for the full 36-character code:
$$ 36 \times 5.91 \approx 213 \text{ bits} $$
For comparison, AES-128 encryption has 128 bits of entropy. These recovery codes are much stronger in terms of guessing resistance.
Total Combinations
With 36 characters chosen from 60 possibilities each:
$$ 60^{36} \approx 2 \times 10^{63} \text{ unique codes} $$
This astronomical number of possibilities ensures that guessing a valid code is virtually impossible.
Brute-Force Resistance
Even with a supercomputer that tests codes extremely quickly, brute-forcing a valid recovery code is completely impractical:
| Attack Speed | Seconds | Years |
|---|---|---|
| 1 billion/sec (10^9) | 2 × 10^54 | 6 × 10^46 |
| 1 trillion/sec (10^12) | 2 × 10^51 | 6 × 10^43 |
| 1 quintillion/sec (10^18) | 2 × 10^45 | 6 × 10^37 |
For comparison: the age of the universe is only ~1.4 × 10^10 years. Even a computer testing a quintillion codes per second would need far longer than the universe has existed to find a valid code.
Prespective?
What this means?
- Each character is chosen randomly from 60 possibilities.
- With 36 characters, the number of possible codes is more than 2 followed by 63 zeros.
- Each recovery code has ≈213 bits of entropy, making it extremely resistant to guessing or brute-force attacks.
- That’s so many possibilities that even the fastest computers would take longer than the age of the universe to try them all.
- The vast number of possible codes ensures that every code is unique and unpredictable.
- This makes guessing a valid code virtually impossible and this is without brute rate limiter, with a rate limiter (which this app uses it is virtually impossible).
In short: it’s far stronger than standard encryption like AES-128.
You can trust these recovery codes to keep your account safe even against attackers with enormous computational power.
Developer Appendix
import math
def brute_force_time(alphabet_size=52, chars_per_group=6, groups=6, guesses_per_second=10**9):
total_combinations = alphabet_size ** (chars_per_group * groups)
seconds = total_combinations / guesses_per_second
years = seconds / (60 * 60 * 24 * 365)
return total_combinations, seconds, years
combos, seconds, years = brute_force_time()
print(f"Total combinations: {combos:e}")
print(f"Seconds to crack: {seconds:e}")
print(f"Years to crack: {years:e}")
Example output:
Total combinations: 3.292e+61
Seconds to crack: 3.292e+52
Years to crack: 1.043e+45
Summary
- 212.8 bits recovery codes → astronomically secure
- ≈3.3 × 10^61 combinations → impossible to brute-force
- Even with a supercomputer, cracking a single code would take trillions of times longer than the age of the universe
- With rate limiting, brute-force becomes completely infeasible
Use Cases
- Integrate with any existing 2FA system to provide a secure set of recovery codes.
- Large-scale systems where thousands of users might need recovery codes, ensuring database performance is not impacted.
- Admin-friendly management of recovery codes, including scheduling cleanups without developer intervention.
- Systems requiring secure, hashed storage of recovery codes while retaining full control over their lifecycle.
Installation
pip install django_auth_recovery_codes
# settings.py
INSTALLED_APPS = [
...
'django_2fa_recovery_codes',
'django_q',
]
Quick Example
from django_2fa_recovery_codes.models import RecoveryCodeBatch
# Create a batch of 10 recovery codes for a user
plain_codes, batch_instance = RecoveryCodeBatch.create_recovery_batch(user, days_to_expire=30)
How to Use 2FA Recovery Codes
Setting up the Cache or using the default cache
To use this application, you can either set up a permanent cache system in the backend or allow it to use the default cache.
Why is a cache necessary for this app?
This application is designed to be scalable, meaning it can support anything from a few users to thousands without compromising performance or putting unnecessary load on the database. It relies heavily on caching:
- Everything from page reads
- Pagination
- Brute-force rate limiting, waiting time for failed login attempts to the cooling period for regenerating new codes is computed and cached.
- Database-heavy operations
The database is only accessed when the cache expires or an update is made e.g the user uses, deletes or invalidates a code.
Cache Expiry and TTL Settings
Cache entries have a configurable Time-To-Live (TTL), which determines how long the data is stored before being refreshed. The following settings are used by default:
DJANGO_AUTH_RECOVERY_CODES_CACHE_TTL = 300 # Default 5 minutes
DJANGO_AUTH_RECOVERY_CODES_CACHE_MIN = 60 # Minimum 1 minute
DJANGO_AUTH_RECOVERY_CODES_CACHE_MAX = 3600 # Maximum 1 hour
These settings can be adjusted by the developer in the Django settings to balance performance with data freshness. This ensures cache expiry times remain within safe and predictable bounds.
How does the cache work?
The cache helps prevent issues such as race conditions and stale data.
What is a race condition?
A race condition occurs when two or more processes try to modify the same data at the same time, leading to unpredictable results.
Example:
Imagine two requests to generate a new 2FA recovery code arrive simultaneously for the same user. If both try to write to the cache at the same time, one could overwrite the other, resulting in lost data. To prevent this, the application ensures that only one process can write to a specific cache key at a time.
This mechanism guarantees that cache data remains consistent, preventing conflicts and ensuring that recovery codes are always valid and reliable.
Using Pagination and Caching
The application uses a TTL cache for paginated data. Without caching, every page refresh or navigation triggers a database query to fetch the relevant objects, which can be inefficient.
With the TTL cache (how long an data is cached is determined by the setting flags, default five minutes):
- Paginated query results (e.g., a history of recovery codes) are stored in memory for a set duration.
- While the cache is valid, page refreshes or navigation read from the cache instead of hitting the database.
- When underlying data changes, for example, a new batch of codes is generated, the database and the cache is updated, and the newly updated data from the cache is used. This ensures that subsequent requests are up-to-date.
What cache should I use?
That depends entirely on you. The application is designed to use caching, but it’s backend-agnostic. It will work with any cache supported by Django (e.g. Redis, Memcached, or in-memory cache). It assumes no cache over the other and leaves it to the developer to decide which one to use under the hood.
This flexibility is possible because the application only interacts with Django’s cache framework abstraction. Under the hood, all cache operations (cache.set, cache.get, etc.) are handled by Django. The actual backend Redis, Memcached, or in-memory is just a plug-in configured in settings.py.
- Redis : A common choice for production, especially in distributed systems. It supports persistence, clustering, and advanced features like pub/sub.
- Memcached : Lightweight and very fast, best for simple key/value caching when persistence is not required.
- In-memory cache : Used by default if no backend is configured. Easiest to set up, but limited to a single process and wipes entirely when the application restarts, so best for development or small-scale setups.
Example configurations (Django)
# settings.py
# Redis
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
}
}
# Memcached
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
"LOCATION": "127.0.0.1:11211",
}
}
# In-memory (local memory cache, default if none configured)
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "unique-snowflake",
}
}
Example usage
from django.core.cache import cache
# Store a value for 5 minutes
cache.set("greeting", "Hello, world!", timeout=300)
# Retrieve the value
message = cache.get("greeting")
print(message) # "Hello, world!"
Using Django Cache Without Configuring a Backend
Even if you don’t explicitly define a cache backend in settings.py, Django provides a default in-memory cache (LocMemCache) which the application uses by using the handlers cache.get() and cache.set() via a specifically designed cache functions:
Key points:
- Django uses
LocMemCacheinternally ifCACHESis not defined. - If a in-memory cache is used (nothing added in the settings) when Django is restarted, the cache is automatically cleared by Django
- Each worker process has its own separate cache.
In short: the app is built to use caching by default, but if no backend is configured it automatically falls back to an in-memory cache. However, because it is an in-memory when the Django sever restarts it resets the cache. For production, a persistent backend like Redis is recommended.
Using and setting up Django-q
What is Django-Q?
Django-Q is a task queue and asynchronous job manager for Django. It allows your application to run tasks outside the normal request/response cycle, this is useful for background processing, scheduling, or parallel execution.
Key features include:
- Asynchronous Task Execution
Allows tasks to run in the background so users don’t have to wait for them to complete, for example:
- Sending emails
- Generating reports
- Processing files
- Performing API requests
- Deleting tasks
Scheduled Tasks
Supports scheduling tasks similar to cron jobs:
- One-off tasks at a specific time
- Recurring tasks (daily, weekly, etc.)
Multiple Brokers
Tasks can be stored in different backends (brokers):
- Django ORM (default): stored in the database
- Redis: faster and suitable for high-performance needs
- Other databases (PostgreSQL, MySQL)
Cluster Mode
Runs multiple worker processes in parallel for better performance and scalability.
Result Storage
Stores task results so you can check completion status and retrieve outputs.
To run the worker cluster, use:
python manage.py qcluster
Django-Q vs Celery and why Django Auth Recovery codes use Django-q
Both Django-Q and Celery are task queues, but they differ in complexity and use cases:
| Feature | Django-Q | Celery |
|---|---|---|
| Async tasks | ✅ | ✅ |
| Scheduled tasks | ✅ | ✅ |
| Periodic/recurring tasks | ✅ | ✅ |
| Multiple brokers | ✅ | ✅ |
| Result backend | ✅ | ✅ |
| Retry/failure handling | Basic | Advanced |
| Task chaining & workflows | Limited | ✅ |
Key differences:
- Django-Q is simpler, uses Django’s ORM as a broker by default, and is ideal for small to medium projects.
- Celery is more complex, requires an external broker like Redis or RabbitMQ, and is better suited for large-scale, high-load projects with advanced workflows.
Why does this application use Django-Q?
django_auth_recovery_codes uses Django-Q to handle background tasks such as:
- When the user email themselves a copy of their plaintext code
- When the admin runs or sets up scheduler (once, daily, weekly, etc) to delete invalid or expired codes, a report is also generated and sent to the admin via email
Without using Django-q whenever a user deletes their code or sends a copy of their plaintext code it will block normal request/response, and if multiple users are deleting their codes at the same time it can causes problems in the database by. With this it ensures that these tasks do not block normal request/response cycles and can run efficiently in the background without impacting the user experience.
⚠️ Note on Batch Deletion
Even though expired codes are deleted asynchronously, deleting millions of codes at once can still cause performance issues such as long transactions or database locks.
To avoid this, django_auth_recovery_codes supports batch deletion via the configurable setting:
# settings.py
Django Auth Recovery Codes_BATCH_DELETE_SIZE = 1000
- If set, expired codes will be deleted in chunks of this size (e.g. 1000 at a time).
- If not set, all expired codes are deleted in a single query.
Using Django-Q with django_auth_recovery_codes
Django Auth Recovery Codes provides a utility task to clean up expired recovery codes, but since this is a reusable app, the scheduling of this task is left up to you, depending on your project’s needs and dataset size.
####. Scheduling the Task with Django-Q via the admin interface Recovery code batch scheduler
You can schedule this cleanup task to run at whatever time that that suits via the admin. For example every date at a given time.
See the Django-Q scheduling docs for more options.
How does Django-Q delete codes if the user deletes them from the frontend?
django_auth_recovery_codes does not immediately delete a code when the user deletes it from the frontend. Instead, it performs a soft delete, the code is marked as invalid and can no longer be used. From the user’s perspective, the code is “gone,” but the actual row still exists in the database until the cleanup task runs.
When the Django-Q scheduler task runs (either automatically or triggered by the admin), any codes marked for deletion are permanently removed in the background (in batches).
Why not delete the code immediately?
Since this is a reusable app that can be plugged into any Django projects of any size (small apps or large-scale environments), immediate deletion is avoided for two key reasons:
-
Database contention In environments with thousands of users, potential many codes could be deleted at the same time. Deleting them synchronously could lock rows or put heavy strain on the database.
-
User experience Immediate deletion happens in the request/response cycle. If many users delete codes at once, their requests would take longer, and the frontend might “freeze” while deletions are processed leading to a poor UX.
Benefits of using Django-Q
By offloading deletion to Django-Q:
- Deletion is handled as a background task, so it doesn’t block the frontend.
- The database can process deletions more efficiently, especially when using batch deletion.
- Users get a smoother experience ,the code disappears instantly from their view, while the actual cleanup happens safely in the background.
Deletion flow
Batch deletion configuration
For projects with very large datasets, batch deletion can be enabled via the DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE setting flag:
# settings.py
DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE = 1000
- If set, expired or soft-deleted codes will be removed in chunks of this size.
- If not set, all deletions happen in a single query.
This approach provides flexibility, small apps can use one-shot deletes, while larger systems can safely handle deletions in manageable batches.
Setting up Django-Q
The django_auth_recovery_codes library uses Django-Q internally. You don’t need to install it separately, but you must configure it in your Django project to ensure background tasks run properly.
1. Add Django-Q to Installed Apps
In your settings.py:
INSTALLED_APPS = [
...
'django_q',
]
2. Configure the Q_CLUSTER
Example configuration:
Q_CLUSTER = {
'name': 'recovery_codes',
'workers': 2,
'timeout': 300, # Maximum time (seconds) a task can run
'retry': 600, # Retry after 10 minutes if a task fails (retry must be greater than timeout)
'recycle': 500, # Recycle workers after this many tasks
'compress': True, # Compress data for storage
'cpu_affinity': 1, # Assign workers to CPU cores
'save_limit': 250, # Maximum number of task results to store
'queue_limit': 500,# Maximum number of tasks in the queue
'orm': 'default', # Use the default database for task storage
}
For more configuration options, see the official Django-Q documentation
3. Running the Cluster
Don’t forget to start the Django-Q worker cluster so scheduled tasks actually run:
python manage.py qcluster
Django Auth Recovery flag settings
These environment variables configure the Django Auth Recovery system, controlling email notifications, audit logs, recovery code display, rate limiting, cooldowns, and code management.
📌 Cheat Sheet: Variable Categories
| Icon | Category | Jump to Section |
|---|---|---|
| 📧 | Email & Admin Settings | Email & Admin Settings Flags |
| 📝 | Audit & Logging | Audit & Logging Setting Flags |
| 📄 | Code Display & Pagination | Recovery Code Display Settings |
| ⚡ | Rate Limiting & Caching | Rate Limiting & Caching |
| ⏱ | Cooldown Settings | Cooldown Settings Flags |
| 🗂 | Code Management & Limits | Code Management & Limits |
Quick visual roadmap to jump to any section in the README.
Email & Admin Settings Flags
These settings control which email and admin accounts are used for recovery code notifications and scheduled operations.
| Variable | Description |
|---|---|
DJANGO_AUTH_RECOVERY_CODES_ADMIN_SENDER_EMAIL |
Admin email address used to receive generated reports after scheduled code deletions. |
DJANGO_AUTH_RECOVERY_CODE_ADMIN_EMAIL_HOST_USER |
The email address used by the application to send emails, typically the same as EMAIL_HOST_USER in your Django settings. |
DJANGO_AUTH_RECOVERY_CODE_ADMIN_USERNAME |
Username associated with the admin account. |
Example Usage
Suppose the settings are:
DJANGO_AUTH_RECOVERY_CODES_ADMIN_SENDER_EMAIL = admin@example.comDJANGO_AUTH_RECOVERY_CODE_ADMIN_EMAIL_HOST_USER = noreply@example.comDJANGO_AUTH_RECOVERY_CODE_ADMIN_USERNAME = admin
Then the system will:
- Send generated reports of scheduled recovery code deletions to
admin@example.com. - Use
noreply@example.comas the sender for all automated recovery code emails. - Associate actions with the admin username for auditing purposes.
Audit & Logging Setting Flags
These settings control the auditing, logging, and retention of recovery code activity to ensure traceability and compliance.
| Variable | Description |
|---|---|
DJANGO_AUTH_RECOVERY_CODE_AUDIT_ENABLE_AUTO_CLEANUP |
Enable automatic cleanup of audit logs. |
DJANGO_AUTH_RECOVERY_CODE_AUDIT_RETENTION_DAYS |
Number of days to retain audit logs. |
DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_SCHEDULER_USE_LOGGER |
Log scheduler operations during code purge. |
DJANGO_AUTH_RECOVERY_CODE_STORE_EMAIL_LOG |
Record activity of sent recovery emails. |
Example Usage
Suppose the settings are:
DJANGO_AUTH_RECOVERY_CODE_AUDIT_ENABLE_AUTO_CLEANUP = TrueDJANGO_AUTH_RECOVERY_CODE_AUDIT_RETENTION_DAYS = 90DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_SCHEDULER_USE_LOGGER = TrueDJANGO_AUTH_RECOVERY_CODE_STORE_EMAIL_LOG = True
Then the system will:
- Automatically remove audit logs older than 90 days.
- Record all recovery email activity in the logs.
- Log any operations performed by the purge scheduler for transparency.
These flags help maintain a clean audit trail while ensuring important recovery-related actions are tracked.
Recovery Code Display Settings
These settings control how recovery code batches are displayed in the user interface, including pagination and post-action redirection.
| Variable | Description |
|---|---|
DJANGO_AUTH_RECOVERY_CODE_MAX_VISIBLE |
Maximum number of expired batches, including the current active batch, that a user can view in their history section. |
DJANGO_AUTH_RECOVERY_CODE_PER_PAGE |
Number of recovery codes per page (pagination). |
DJANGO_AUTH_RECOVERY_CODE_REDIRECT_VIEW_AFTER_LOGOUT |
View users are redirected to after recovery actions. |
Additional Explanation for DJANGO_AUTH_RECOVERY_CODE_MAX_VISIBLE
This setting controls how many recovery code batches are displayed to the user, regardless of the total number stored in the database. Each batch contains multiple recovery codes.
Example
Suppose the settings are:
DJANGO_AUTH_RECOVERY_CODE_MAX_VISIBLE = 20DJANGO_AUTH_RECOVERY_CODE_PER_PAGE = 5
Then:
- If a user has 100 recovery codes, only 20 batches will be shown in the interface.
- With 5 batches per page, there will be 4 pages of recovery codes.
- Users can navigate through these pages to see all visible batches.
For details on what a single batch includes, see example: a single recovery code batch view.
Additional Explanation for DJANGO_AUTH_RECOVERY_CODE_REDIRECT_VIEW_AFTER_LOGOUT
This setting controls where the user is redirected after logging out.
By default, the redirect points to the 2FA login page, but it can be changed to any page.
To redirect to another page, use the name reference of the view defined in your urls.py. For example:
path("auth/recovery-codes/login/", views.login_user, name="login_user")
Rate Limiting & Caching
These settings control rate limiting for recovery code requests and how caching is used to improve performance and prevent abuse.
| Variable | Description |
|---|---|
DJANGO_AUTH_RECOVERY_CODES_AUTH_RATE_LIMITER_USE_CACHE |
Enable caching for rate limiting recovery attempts. |
DJANGO_AUTH_RECOVERY_CODES_CACHE_MAX |
Maximum cache value for rate limiter. |
DJANGO_AUTH_RECOVERY_CODES_CACHE_MIN |
Minimum cache value for rate limiter. |
DJANGO_AUTH_RECOVERY_CODES_CACHE_TTL |
Cache expiration time (seconds). |
Example
Suppose the settings are:
DJANGO_AUTH_RECOVERY_CODES_AUTH_RATE_LIMITER_USE_CACHE = TrueDJANGO_AUTH_RECOVERY_CODES_CACHE_MIN = 1DJANGO_AUTH_RECOVERY_CODES_CACHE_MAX = 5DJANGO_AUTH_RECOVERY_CODES_CACHE_TTL = 60
Then the system will:
- Start counting recovery attempts from
CACHE_MIN = 1. - Increment the cached count with each recovery request, up to
CACHE_MAX = 5. - Reset the count automatically after
CACHE_TTL = 60seconds. - Use the cache to enforce rate limiting, preventing abuse and reducing database load.
Cooldown Settings Flags
These settings control the cooldown period applied when users request recovery codes repeatedly. The cooldown helps prevent abuse by increasing the wait time between requests.
| Variable | Description |
|---|---|
DJANGO_AUTH_RECOVERY_CODES_BASE_COOLDOWN |
Base interval for recovery code cooldown (seconds). |
DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_CUTOFF_POINT |
Maximum cooldown threshold (seconds). |
DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_MULTIPLIER |
Multiplier applied to cooldown on repeated attempts. |
Example
Suppose the settings are:
DJANGO_AUTH_RECOVERY_CODES_BASE_COOLDOWN = 30DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_MULTIPLIER = 2DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_CUTOFF_POINT = 300
Then the cooldown progression for repeated recovery code requests would be:
- First attempt: 30 seconds
- Second attempt: 30 × 2 = 60 seconds
- Third attempt: 60 × 2 = 120 seconds
- Fourth attempt: 120 × 2 = 240 seconds
- Fifth attempt: 240 × 2 = 480 seconds → capped at 300 seconds (cutoff)
This ensures the cooldown grows exponentially but never exceeds the defined maximum threshold.
Code Management & Limits
These settings control how recovery codes are managed, including deletion, export, usage limits, and validation.
| Variable | Description |
|---|---|
DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_RETENTION_DAYS |
Number of days before expired recovery codes are deleted. |
DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE |
Number of codes to delete in a single batch operation. |
DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FILE_NAME |
Default filename for exported recovery codes. |
DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FORMAT |
Default export format for recovery codes. Options: 'txt', 'csv', 'pdf'. |
DJANGO_AUTH_RECOVERY_CODES_MAX_LOGIN_ATTEMPTS |
Maximum allowed login attempts using recovery codes. |
DJANGO_AUTH_RECOVERY_KEY |
Secret key used for recovery code validation. |
DJANGO_AUTH_RECOVERY_CODES_PURGE_MAX_DELETIONS_PER_RUN |
Maximum number of expired codes to delete in a single scheduler run. If unset (-1), deletion is unlimited. |
Example Usage
Suppose the settings are:
DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_RETENTION_DAYS = 90DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE = 50DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FILE_NAME = "recovery_codes"DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FORMAT = "txt"DJANGO_AUTH_RECOVERY_CODES_MAX_LOGIN_ATTEMPTS = 5
Then the system will:
- Delete expired recovery codes older than 90 days, in batches of 50.
- Export recovery codes using the default file name
recovery_codesand formattxt. - Limit users to 5 login attempts using recovery codes before locking or enforcing cooldowns.
- Use
DJANGO_AUTH_RECOVERY_KEYto validate recovery codes during login or verification.
Cleanup Configuration Examples
The cleanup behaviour for expired recovery codes can be tuned using:
DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE→ controls how many codes are deleted per database operation.DJANGO_AUTH_RECOVERY_CODES_PURGE_MAX_DELETIONS_PER_RUN→ caps the maximum number of deletions in one scheduler run.
Note: This setting works together with
DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE.
The scheduler will delete codes in batches until either the max deletions per run is reached or all expired codes are removed.
Nonemeans “delete everything in one run,” while an integer enforces a cap per run.
Example setups
Small app / development
DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE=100
DJANGO_AUTH_RECOVERY_CODES_PURGE_MAX_DELETIONS_PER_RUN=
Deletes everything in one run, in small chunks of 100 to avoid heavy queries.
Medium app
DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE=500
DJANGO_AUTH_RECOVERY_CODES_PURGE_MAX_DELETIONS_PER_RUN=5000
Deletes up to 5,000 codes per scheduler run, in batches of 500.
Large app (millions of codes)
DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE=1000
DJANGO_AUTH_RECOVERY_CODES_PURGE_MAX_DELETIONS_PER_RUN=10000
Deletes up to 10,000 codes per run, in batches of 1,000. This spreads load across multiple scheduler runs while keeping each job predictable and efficient.
Cleanup Process Visualised
The diagrams below illustrate how expired recovery codes are deleted.
1. Loop until finished
All expired codes are deleted in a single scheduler run, in batches.
flowchart TD
A[Scheduler Run Starts] --> B{Expired codes exist?}
B -->|Yes| C[Delete batch (size=N)]
C --> D{Expired codes remain?}
D -->|Yes| C
D -->|No| E[Scheduler Run Ends]
B -->|No| E
2. Hybrid: capped deletions per run
Deletes in batches, but stops once the maximum deletions per run is reached. Remaining codes are cleaned in future runs.
flowchart TD
A[Scheduler Run Starts] --> B{Expired codes exist?}
B -->|Yes| C[Delete batch (size=N)]
C --> D[Increase total deleted count]
D --> E{Reached max deletions per run?}
E -->|Yes| F[Stop, resume next run]
E -->|No| G{Expired codes remain?}
G -->|Yes| C
G -->|No| H[Scheduler Run Ends]
B -->|No| H
Recommended Settings by Scale
| Scale | DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE |
DJANGO_AUTH_RECOVERY_CODES_PURGE_MAX_DELETIONS_PER_RUN |
Notes |
|---|---|---|---|
| Small (dev / hobby) | 100 | -1 | Deletes all expired codes in one run, safe for small datasets. |
| Medium (tens of thousands) | 500 | 5000 | Balances DB load by capping deletions to 5k per scheduler run. |
| Large (millions) | 1000 | 10000 | Spreads cleanup across runs, keeps queries efficient and predictable. |
Tip:
Always tune based on your database performance and scheduler frequency. For example, if your scheduler runs every 5 minutes, lower values keep load smoother. If it runs nightly, higher values may be better to catch up faster.
How Cleanup Works Internally
When the scheduler runs, the app looks for expired or invalidated recovery codes and removes them according to your configuration.
The process is:
-
Batch deletion
- Codes are deleted in small chunks (
DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE) instead of all at once. - This prevents long-running SQL queries and reduces database locks.
- Codes are deleted in small chunks (
-
Optional cap per run
- If you set
DJANGO_AUTH_RECOVERY_CODES_PURGE_MAX_DELETIONS_PER_RUN, the scheduler will stop once that many codes have been deleted. - Any remaining codes will be picked up in the next scheduler run.
- This spreads the load across multiple runs and avoids “big bang” deletes.
- If the flag is set to None, it becomes unlimited meaning it will carry on looping until all codes are cleared
- If you set
-
Scheduler resumes automatically
- On the next scheduled run, the same process repeats until all expired codes are gone.
- You don’t need to trigger anything manually.
✅ This design ensures a couple of things in regards to the cleanup is:
- Safe → avoids stressing the database.
- Scalable → works for apps with thousands or millions of codes.
- Automatic → no manual intervention needed.
FAQ — Frequently Asked Questions
Q: What happens if the scheduler crashes during cleanup?
A: The process is idempotent. If a run is interrupted, any remaining expired codes will be picked up on the next scheduled run.
No data loss occurs beyond the intended deletions.
Q: Can I disable automatic cleanup?
A: Yes. Simply avoid scheduling the purge task in your task runner (e.g., Django-Q).
You may then run purge_expired_codes() manually when required.
This is useful for testing or environments with strict operational controls.
Q: How do I know how many codes were deleted?
A: Every purge operation is logged through RecoveryCodeAudit or inspect the email sent to admin after a schedule deletion has occurred.
You can inspect these logs to see the number of codes deleted, who initiated the deletion, and the associated batch metadata.
Q: Will cleanup affect active codes?
A: No. Only codes that are expired or invalidated (based on retention days and status) are eligible for deletion.
Valid codes remain untouched.
Q: What if I have millions of expired codes?
A: Configure both DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE and
DJANGO_AUTH_RECOVERY_CODES_PURGE_MAX_DELETIONS_PER_RUN appropriately.
This ensures cleanup is spread across multiple runs, preventing excessive locking or transaction bloat.
Q: Can I monitor cleanup performance?
A: Yes, you can monitor through the Django-q admin interface or use python manage.py qmonitor.
Because deletions are chunked, monitoring helps you fine-tune batch size and per-run caps for optimal efficiency.
Site Settings Flags
| Variable | Description |
|---|---|
DJANGO_AUTH_RECOVERY_CODES_SITE_NAME |
The name of the site to use with the application . |
Example Usage
Suppose the setting is:
DJANGO_AUTH_RECOVERY_CODES_SITE_NAME = "My Awesome Site"
Then recovery emails and notifications will display the site name "My Awesome Site" to the user, helping them identify the source of the email and also display on the site dashboard and login page.
Custom Email Message Flags
| Variable | Description |
|---|---|
DJANGO_AUTH_RECOVERY_CODE_EMAIL_SUCCESS_MSG |
The message displayed to the user when recovery codes are successfully sent via email. Admins can customise this message. |
Example Usage
Suppose the setting is:
DJANGO_AUTH_RECOVERY_CODE_EMAIL_SUCCESS_MSG = "Your recovery codes email has been successfully delivered."
Then when a user requests recovery codes, they will see the message:
"Your recovery codes email has been successfully delivered."
Admins can change this message to anything they want, for example:
DJANGO_AUTH_RECOVERY_CODE_EMAIL_SUCCESS_MSG = "Check your inbox! Your recovery codes are on their way."
Example Flag Usage
DJANGO_AUTH_RECOVERY_CODES_ADMIN_SENDER_EMAIL=admin@example.com
DJANGO_AUTH_RECOVERY_CODE_ADMIN_EMAIL_HOST_USER=smtp@example.com
DJANGO_AUTH_RECOVERY_CODE_ADMIN_USERNAME=admin
DJANGO_AUTH_RECOVERY_CODE_AUDIT_ENABLE_AUTO_CLEANUP=True
DJANGO_AUTH_RECOVERY_CODE_AUDIT_RETENTION_DAYS=30
DJANGO_AUTH_RECOVERY_CODE_MAX_VISIBLE=20
DJANGO_AUTH_RECOVERY_CODE_PER_PAGE=10
DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_RETENTION_DAYS=90
DJANGO_AUTH_RECOVERY_CODE_REDIRECT_VIEW_AFTER_LOGOUT=recovery_dashboard
DJANGO_AUTH_RECOVERY_CODES_AUTH_RATE_LIMITER_USE_CACHE=True
DJANGO_AUTH_RECOVERY_CODES_CACHE_TTL=3600
DJANGO_AUTH_RECOVERY_CODES_BASE_COOLDOWN=60
DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FORMAT=txt
DJANGO_AUTH_RECOVERY_KEY=supersecretkey
DJANGO_AUTH_RECOVERY_CODES_MAX_DELETIONS_PER_RUN=400
Best Practices for Managing Environment Variables
- **Use a
.envfile to keep secret keys and credentials out of source control.
Default Values & Required Variables
| Variable | Required | Default Value | Notes |
|---|---|---|---|
DJANGO_AUTH_RECOVERY_CODES_ADMIN_SENDER_EMAIL |
✅ Yes | – | Email used to send recovery codes. Must be valid. |
DJANGO_AUTH_RECOVERY_CODE_ADMIN_EMAIL_HOST_USER |
✅ Yes | – | SMTP or host email account. Required for sending emails. |
DJANGO_AUTH_RECOVERY_CODE_ADMIN_USERNAME |
✅ Yes | – | Admin username associated with the email. |
DJANGO_AUTH_RECOVERY_CODE_AUDIT_ENABLE_AUTO_CLEANUP |
❌ No | False |
Automatically clean up audit logs if True. |
DJANGO_AUTH_RECOVERY_CODE_AUDIT_RETENTION_DAYS |
❌ No | 30 |
Number of days to retain audit logs. |
DJANGO_AUTH_RECOVERY_CODE_MAX_VISIBLE |
❌ No | 20 |
Maximum number of expired batches plus the current active batch the user can view under their history section |
DJANGO_AUTH_RECOVERY_CODE_PER_PAGE |
❌ No | 10 |
Pagination setting for code lists. |
DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_RETENTION_DAYS |
❌ No | 90 |
Days before expired codes are deleted. |
DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_SCHEDULER_USE_LOGGER |
❌ No | False |
Enable scheduler logging for purge operations. |
DJANGO_AUTH_RECOVERY_CODE_REDIRECT_VIEW_AFTER_LOGOUT |
❌ No | / |
URL to redirect users after code actions. |
DJANGO_AUTH_RECOVERY_CODE_STORE_EMAIL_LOG |
❌ No | False |
Log sent recovery emails. |
DJANGO_AUTH_RECOVERY_CODES_AUTH_RATE_LIMITER_USE_CACHE |
❌ No | True |
Use cache for rate limiting. |
DJANGO_AUTH_RECOVERY_CODES_BASE_COOLDOWN |
❌ No | 60 |
Base cooldown interval in seconds. |
DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE |
❌ No | 50 |
Number of codes deleted per batch. |
DJANGO_AUTH_RECOVERY_CODES_CACHE_MAX |
❌ No | 1000 |
Maximum value for cache-based limiter. |
DJANGO_AUTH_RECOVERY_CODES_CACHE_MIN |
❌ No | 0 |
Minimum value for cache-based limiter. |
DJANGO_AUTH_RECOVERY_CODES_CACHE_TTL |
❌ No | 3600 |
Cache expiration in seconds. |
DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_CUTOFF_POINT |
❌ No | 3600 |
Maximum cooldown threshold in seconds. |
DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_MULTIPLIER |
❌ No | 2 |
Multiplier for repeated attempts cooldown. |
DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FILE_NAME |
❌ No | recovery_codes |
Default file name for exported codes. |
DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FORMAT |
❌ No | txt |
Default format for exporting recovery codes. Options: 'txt', 'csv', 'pdf'. |
DJANGO_AUTH_RECOVERY_CODES_MAX_LOGIN_ATTEMPTS |
❌ No | 5 |
Maximum login attempts using recovery codes. |
DJANGO_AUTH_RECOVERY_KEY |
✅ Yes | – | Secret key for recovery code validation. Must be kept safe. |
DJANGO_AUTH_RECOVERY_CODES_PURGE_MAX_DELETIONS_PER_RUN |
❌ No | 1000 |
Caps the maximum number of deletions in one scheduler run. |
DJANGO_AUTH_RECOVERY_CODE_EMAIL_SUCCESS_MSG |
❌ No | "Your recovery codes email has been successfully delivered." |
Custom message displayed to users after recovery codes are sent. Admins can change this message. |
Run checks to verify that flags are valid
To ensure that all configurations and flags are correct after adding them to the settings.py file, run the following command before starting the application:
```python
python manage.py check
This command will raise an error if any configuration is incorrect.
If everything is fine, you can then run the server and the task queue:
# Terminal 1
python manage.py runserver
# Terminal 2
python manage.py qcluster
Sending Emails, logging emails to the model, and using Logging for purging codes via Django-q
Django Auth 2FA Recovery provides the ability to email yourself a copy of your raw recovery codes and can only be done once for a given batch, and only if you haven't logged out after generating the code. This is achieved using a lightweight yet powerful library called EmailSender, which is responsible for delivering the message.
In addition to sending, the process can be logged for developers through a companion model named EmailSenderLogger. Together, these ensure that not only are emails dispatched, but the details of each operation can also be recorded for auditing, debugging, or monitoring purposes. N
Note for security purpose, the logger doesn't log context or the header in the logging file because the context contains the raw plain code that is passed to the EmailSender and therefore EmailSenderLogger. Allowing the context to be logged would expose a security risk where the anyone with access to the log files could reconstruct the raw codes, and that paired with the email would give them unauthorised access to the person account.
Using async vs synchronous
The application supports both asynchronous and synchronous email sending for development and production.
In production, emails are sent asynchronously via Django-Q, which places the email in a task queue. Depending on the queue load, this may take a few seconds or minutes to process.
In development, you might want to send emails synchronously to see the results immediately and verify that everything is working correctly.
This behaviour is controlled by the DEBUG setting:
- When
DEBUG = True, emails are sent synchronously. - When
DEBUG = False, emails are sent asynchronously via Django-Q.
This setup allows developers to test email functionality quickly in development but at the same time keep production efficient and non-blocking.
Configuration settings
Whether emails are logged is determined by a configuration flag in your project’s settings.py.
# settings.py
# Storing user emails in the model
DJANGO_AUTH_RECOVERY_CODE_STORE_EMAIL_LOG = True # store in database
DJANGO_AUTH_RECOVERY_CODE_STORE_EMAIL_LOG = False # Don't store in the database
# using logger while purging code
DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_SCHEDULER_USE_LOGGER
True: The application records details of the email process viaEmailSenderLogger.False: No logging takes place.
Hang on a minute, why can I email myself the code only once, and only if I haven’t logged out after generating it?
The way Django Auth Recovery Code works is that it never stores the plain text recovery codes in the database. Instead, it stores only their hash values.
A hash is a one-way function: it takes an input, applies a hashing algorithm, and produces an output that cannot be reversed to recover the original input. This is different from encryption/decryption, where data can be restored to its original form. Hashing is therefore safer for storing sensitive values such as recovery codes.
What does this mean for your codes?
Since the generated codes are stored as hashes, the system cannot send you the hash (as it is meaningless to you) and it cannot retrieve the original plain text version (because it was never stored in the database).
To work around this, the application temporarily stores a copy of the plain text codes in your backend session when they are first generated. This session is unique to your login and user account and cannot be accessed by anyone or any other account. Because it is session-based, the codes are removed once you log out.
What happens if I refresh the page, can I still email myself the code?
Yes. Refreshing the page does not clear the backend session. However, for security reasons, the plain text codes will no longer be displayed in the frontend after the initial page load. As long as you remain logged in, you can still email yourself a copy of the codes.
But if I’m still logged in, why can I only email myself a single copy?
This is a deliberate security measure. Allowing multiple emails of the same batch would unnecessarily increase the risk of exposure. Limiting it to a single email ensures you have one secure copy without duplicating it across your inbox.
Can I email myself a copy if I generate a new batch?
Yes. Generating a new batch creates a new set of plain text codes, which are again stored in your backend session. You may therefore email yourself one copy of each new batch.
Using Logging with the Application
django_auth_recovery_codes includes a built-in logging configuration, so you do not need to create your own in settings.py. This reduces the risk of misconfiguration.
Because the application uses django-q (an asynchronous task manager), the logger is already set up to work with it. Conveniently, everything is preconfigured for you. All you need to do is import the logging configuration and assign it to Django’s LOGGING variable.
# settings.py
from django_auth_recovery_codes.loggers.logger_config import DJANGO_AUTH_RECOVERY_CODES_LOGGING
LOGGING = DJANGO_AUTH_RECOVERY_CODES_LOGGING
The LOGGING variable is the standard Django setting for logging. Assigning the provided configuration ensures that log files are correctly created and stored in a dedicated folder.
What if I don’t want to override my existing LOGGING configuration?
If you already have a logging configuration and prefer not to overwrite it, you can simply merge it with DJANGO_AUTH_RECOVERY_CODES_LOGGING. Since logging configurations are dictionaries, merging them is straightforward:
# settings.py
LOGGING = {**LOGGING, **DJANGO_AUTH_RECOVERY_CODES_LOGGING}
This approach allows you to keep your existing logging settings intact but still allow you to add support for django_auth_recovery_codes.
Downloading Recovery Codes
In addition to emailing your recovery codes, django_auth_recovery_codes also allows you to download them directly. This gives you flexibility in how you choose to back up your codes.
How downloads work
When recovery codes are generated, a plain text copy is stored temporarily in the request.session. This enables you to either:
- Email yourself a copy, or
- Download a copy in one of the following formats:
- Plain text (
.txt) - PDF (
.pdf) - CSV (
.csv)
- Plain text (
The format in which the recovery codes are returned (TXT, PDF, or CSV) is determined by a settings flag. By default, the codes are returned as TXT, but this can be customised using the following setting:
# Default download format
DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FORMAT = 'txt' # options: 'txt', 'csv', 'pdf'
By default, the downloaded file is named recovery_codes (plus the extension) used when using the default format. You can also change the file name using this setting:
# Default download file name
DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FILE_NAME = "recovery_codes"
Just like with emailing, once you log out, the session is cleared and the plain text codes are no longer available.
Important security notes
- You may only download a copy once per batch of recovery codes.
- The downloaded file contains the exact same content as the emailed version (the plain text recovery codes).
- If you lose the downloaded file after logging out, you will not be able to retrieve it. You will need to generate a new batch of recovery codes.
Example usage
When generating recovery codes in the application, you will be presented with options to:
- Email yourself a copy (retrieves codes from
request.session) - Download a copy (also retrieves codes from
request.session)
Both options use the same temporary storage mechanism, which ensures your plain text recovery codes are only ever available for the current session and cannot be recovered after logout.
Quickstart and Walkthrough
Setup
This guide shows you how to set up a fresh Django project and integrate 2FA Recovery Codes using the django_auth_recovery_codes package.
The walkthrough assumes you don’t already have a Django project, which is why we create a new one called test_project.
If you already have an existing Django project, skip to Existing project :
- then follow the steps in this guide that apply to integration (skipping project creation).
- In this walkthrough we will not be using Redis, Memecache to create a cache in the settings.py, we do nothing an allow it to defualt the memory cache
Installation (with Virtual Environment)
1. Create a virtual environment
python -m venv env
envis the folder name for your virtual environment. You can name it anything.
2. Activate the virtual environment
-
Windows (PowerShell)
.\env\Scripts\Activate.ps1
-
Windows (CMD)
.\env\Scripts\activate.bat
-
macOS/Linux
source env/bin/activate
3. Upgrade pip (optional but recommended)
pip install --upgrade pip
4. Install Django (latest version)
pip install django
5. Install the recovery codes package
pip install django_auth_recovery_codes
You can install the package with:
pip install django-auth-recovery-codes
6. Verify installation
python -m django --version
pip show django_auth_recovery_codes
Project Setup
7. Create a new Django project
django-admin startproject test_project
cd test_project
8. Run initial migrations
python manage.py migrate
9. Create a Django superuser
python manage.py createsuperuser
- Follow the prompts to set username, email, and password.
10. Start a new app called home
python manage.py startapp home
11. Add home, django_auth_recovery_codes, and django_q to INSTALLED_APPS
Edit test_project/settings.py:
INSTALLED_APPS = [
...,
# third-party apps
"django_auth_recovery_codes",
"django_q",
# your app
"home",
]
12. Run the development server
python manage.py runserver
Open http://127.0.0.1:8000/admin and log in with your superuser credentials.
Configure URLs
13. In home/urls.py
Create the file if it doesn’t exist:
from django.urls import path
from . import views
urlpatterns = [
path("", view=views.home, name="home"),
]
14. In your main urls.py (same folder as settings.py)
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("django_auth_recovery_codes.urls")), # recovery codes
path("", include("home.urls")), # home app
]
Configure your Settings.py file
15. Add the recovery code settings flags in your settings.py file
# setting up the flags
# ===========================
# 📧 Email / Admin
# ===========================
DJANGO_AUTH_RECOVERY_CODES_ADMIN_SENDER_EMAIL = "your-email-address-here"
DJANGO_AUTH_RECOVERY_CODE_ADMIN_EMAIL_HOST_USER = "your-host-email-address-here"
DJANGO_AUTH_RECOVERY_CODE_ADMIN_USERNAME = "username here"
DJANGO_AUTH_RECOVERY_CODE_STORE_EMAIL_LOG = False
# ===========================
# 🔑 Security / Keys
# ===========================
DJANGO_AUTH_RECOVERY_KEY = "add-recovery-key-here"
# ===========================
# 📜 Audit / Retention
# ===========================
DJANGO_AUTH_RECOVERY_CODE_AUDIT_ENABLE_AUTO_CLEANUP = True
DJANGO_AUTH_RECOVERY_CODE_AUDIT_RETENTION_DAYS = 30
DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_RETENTION_DAYS = 30
DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_SCHEDULER_USE_LOGGER = True
# ===========================
# ⏳ Rate Limiting / Cooldowns
# ===========================
DJANGO_AUTH_RECOVERY_CODES_AUTH_RATE_LIMITER_USE_CACHE = True
DJANGO_AUTH_RECOVERY_CODES_BASE_COOLDOWN = 100 # five minutes minutes lock down
DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_CUTOFF_POINT = 3600
DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_MULTIPLIER = 2
DJANGO_AUTH_RECOVERY_CODES_MAX_LOGIN_ATTEMPTS = 5
# ===========================
# 📦 Caching
# ===========================
DJANGO_AUTH_RECOVERY_CODES_CACHE_MAX = 3600
DJANGO_AUTH_RECOVERY_CODES_CACHE_MIN = 1
DJANGO_AUTH_RECOVERY_CODES_CACHE_TTL = 3600
# ===========================
# 📊 Pagination / Limits
# ===========================
DJANGO_AUTH_RECOVERY_CODE_MAX_VISIBLE = 20
DJANGO_AUTH_RECOVERY_CODE_PER_PAGE = 5
DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE = 400
DJANGO_AUTH_RECOVERY_CODES_MAX_DELETIONS_PER_RUN = -1
# ===========================
# 📂 Files / Naming
# ===========================
DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FILE_NAME = "recovery_codes"
DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FORMAT = "txt"
# ===========================
# 🌍 Site / Redirects
# ===========================
DJANGO_AUTH_RECOVERY_CODES_SITE_NAME = "This is a demo tutorial"
DJANGO_AUTH_RECOVERY_CODE_REDIRECT_VIEW_AFTER_LOGOUT = "logout_user"
# ===========================
# 🌍 REcovery code email sucess message
# ===========================
DJANGO_AUTH_RECOVERY_CODE_EMAIL_SUCCESS_MSG = "Your recovery codes email has been successfully delivered."
# add the email backend testing
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = BASE_DIR / "sent_emails"
Path(EMAIL_FILE_PATH).mkdir(parents=True, exist_ok=True)
# Add the Q_CLUSTER for django-1
Q_CLUSTER = {
'name': 'recovery_codes',
'workers': 2,
'timeout': 300, # 5 minutes max per task
'retry': 600, # retry after 10 minutes if task fails (retry must be greater than timeout)
'recycle': 500,
'compress': True,
'cpu_affinity': 1,
'save_limit': 250,
'queue_limit': 500,
'orm': 'default',
}
# we need to tell EmailSender where to find the templates dir
import django_auth_recovery_codes
from pathlib import Path
# Get the path to the installed package
PACKAGE_DIR = Path(django_auth_recovery_codes.__file__).parent
# Define the templates directory within the package
MYAPP_TEMPLATES_DIR = PACKAGE_DIR / "templates" / "django_auth_recovery_codes"
# we need to add in the logging
from django_auth_recovery_codes.loggers.logger_config import DJANGO_AUTH_RECOVERY_CODES_LOGGING
LOGGING = DJANGO_AUTH_RECOVERY_CODES_LOGGING
Add a Q_CLUSTER
See
For now we use the default
Q_CLUSTER = {
'name': 'recovery_codes',
'workers': 2,
'timeout': 300, # 5 minutes max per task
'retry': 600, # retry after 10 minutes if task fails (retry must be greater than timeout)
'recycle': 500,
'compress': True,
'cpu_affinity': 1,
'save_limit': 250,
'queue_limit': 500,
'orm': 'default',
}
16.Set up the file-based email backend (for testing)
This will create a sent_emails folder where Django saves emails instead of sending them.
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = BASE_DIR / "sent_emails"
17. Run the system checks
Stop the server (Ctrl+C) if it’s running, then run:
python manage.py check
This will raise errors if any settings are misconfigured (e.g., wrong data types).
17a. Generate a recovery code
Run the follwoing command, make sure your virtual environment is active. This will drop you into shell but load all app modules
python manage.py shell
Next run
from django_auth_recovery_codes.utils.security.generator import generate_secure_token
# This will generate a secure cryptographically key which can use for your recovery key in the settings flag
# code_length = 10, default this will generate a secret key that is 100 characters, adjust length as you see fit
generate_secure_token(code_length=10)
Copy the key into your recovery key
DJANGO_AUTH_RECOVERY_KEY =
Run Services
18. Open two terminals
Terminal 1 – run the server:
python manage.py runserver
Terminal 2 – run django-q cluster:
python manage.py qcluster
Create a Home View
19. In home/views.py
from django.http import HttpResponse
def home(request):
return HttpResponse("This is the home page")
Verify the Home Page
Open your browser and go to:
http://127.0.0.1:8000/
You should see: "This is the home page"
markdown
Access the Admin
Since we don’t have a login portal yet, log in via the admin:
http://127.0.0.1:8000/admin/
- Enter the superuser credentials you created with
createsuperuser.
Access the Recovery Codes page dashboard
Once logged in, go to the dashboard via:
http://127.0.0.1:8000/auth/recovery-codes/dashboard/
Code Generation
Choose whether the code should have an expiry date
Once the code is generated
- You should see something that looks like this:
- From here, you can regenerate, email, download, or delete the code.
Verifying Generated Codes
- Once the codes are generated, you have the option to verify if the setup is correct.
- This is a one-time verification test, and the form will remain until it is verified.
- Once verified, it will no longer appear, even on a new batch generation.
- To use, simply select a code and enter it in the form.
Failed Test
- A failed test will look like this:
Successful Test
- A successful test will look like this.
- Once the test is successful, the form will no longer be visible.
Downloaded and Emailed Code
- Once a code is downloaded or emailed, it cannot be used again for the same batch.
Invalidating or Deleting a Code
- The application allows you to invalidate or delete a code.
- Once a code has been invalidated or deleted, it cannot be used again.
Viewing the Code Batch History
- You can view your code history.
- It contains information about the generated code batch, such as the number issued and whether the codes were downloaded or emailed.
Logout of the application
Now click the logout but before you do make sure to download a copy of the recovery codes, you will need this to login.
Once you logout you be redirect to the default login page, see the flag settings to see how to redirect to another page .
- You will no longer be able to access the dashboard since it is login only
- You can verify this by going to the home page
http://127.0.0.1:8000
Failed Attempts and Rate Limiting
Login Form
Failed Attempt Example
- Failed login attempts are limited by the flag
DJANGO_AUTH_RECOVERY_CODES_MAX_LOGIN_ATTEMPTS. - In this example, it has been set to
5. - This means that after 5 failed attempts, the rate limiter activates.
- The cooldown starts at 1 minute and increases with each subsequent failed attempt.
- It will not exceed the cooldown threshold period (e.g., if set to
3600, that is 1 hour).
Successful Login
- Enter the email address you used when creating your superuser.
- Use one of the valid 2FA recovery codes from your downloaded codes.
- Upon success, you will be redirected to the dashboard.
- The code you used will automatically be marked as invalid.
2. Existing Project Setup
If you already have a Django project running, integration is simple:
-
Install the package
pip install django_auth_recovery_codes
-
Update
INSTALLED_APPSinsettings.pyINSTALLED_APPS = [ ..., "django_auth_recovery_codes", "django_q", # required for background jobs ]
-
Add a recovery key and email backend (for testing)
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend" EMAIL_FILE_PATH = BASE_DIR / "sent_emails" DJANGO_AUTH_RECOVERY_KEY = "add-some-key"
-
Include URLs in your main
urls.pyfrom django.urls import path, include urlpatterns = [ ..., path("", include("django_auth_recovery_codes.urls")), ]
-
Run migrations
python manage.py migrate
⚠️ You don’t need to run
makemigrationsfor this package because it already ships with its own migrations. Just runningmigratewill apply them. -
Start services
# Terminal 1 python manage.py runserver # Terminal 2 python manage.py qcluster
Scheduling a Code Removal Using Django-Q
In this section, we walk you through how to safely remove recovery codes using Django-Q. You will learn how to generate codes and schedule their deletion, ensuring they are managed automatically and securely. Make sure this running in a separate window
python manage.py qcluster
Generate and Delete Codes
- Generate your recovery codes.
- Click the Delete Codes button and confirm the action.
Once confirmed, Django-Q will schedule the codes for deletion. This means the codes will be automatically removed according to the scheduled task, rather than immediately, providing a safe and managed cleanup process.
Managing Scheduled Deletion via the Admin
Since we are logged in through the admin, we already have administrator access.
-
Open a new tab and navigate to:
http://127.0.0.1:8000/admin/ -
Once there, click on the Recovery codes link.
You will then see the following view:
Select Recovery code cleanup schedulers:
Scheduling a Delete
Quick Explanation
-
Retention days: The number of days an expired or invalid code remains in the database before being deleted. For example, if set to 30, a code will be deleted 30 days after it expires. The default is controlled via a settings flag but can be overridden in the admin interface.
- For testing, set this to
0to remove codes immediately.
- For testing, set this to
-
Run at: The time the scheduler should run.
-
Schedule type: How frequently the scheduler should run (
Once,Hourly,Daily,Weekly,Monthly,Quarterly,Yearly). -
Use with logger: Records the scheduled deletion in a log file.
-
Delete empty batch: When set to
True, the parent batch (model) is removed if no active codes remain. WhenFalse, the batch will be kept. -
Name: A descriptive name for the scheduler.
-
Next run: The next time the scheduler should run. This must not be earlier than the Run at value. It can also be left blank.
- Note: The scheduler is idempotent. Once configured, it will follow the set rules without needing to be triggered manually. The Next run option simply allows you to run an additional execution if required.
Save the scheduler
View tasks
Once Django-q is running you can view failed, queued, tasks via this section
Summary
Scheduling a Code Removal Using Django-Q
In this section, we walk you through how to safely remove recovery codes using Django-Q. You will learn how to generate codes and schedule their deletion, ensuring they are managed automatically and securely.
Generate and Delete Codes
- Generate your recovery codes.
- Click the Delete Codes button and confirm the action.
Once confirmed, Django-Q will schedule the codes for deletion. This means the codes will be automatically removed according to the scheduled task, rather than immediately, providing a safe and managed cleanup process.
Managing Scheduled Deletion via the Admin
Since we are logged in through the admin, we already have administrator access.
- Open a new tab and navigate to:
[http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/)
- Once there, click on the Recovery codes link.
You will then see the following view:
Select Recovery code cleanup schedulers:
Scheduling a Delete
Quick Explanation
-
Retention days: The number of days an expired or invalid code remains in the database before being deleted.
-
Example: If set to 30, a code will be deleted 30 days after it expires.
-
Default is set in your Django settings but can be overridden in the admin interface.
-
For testing, set this to
0to remove codes immediately. -
Run at: The time the scheduler should run.
-
Schedule type: How frequently the scheduler should run (
Once,Hourly,Daily,Weekly,Monthly,Quarterly,Yearly). -
Use with logger: Records the scheduled deletion in a log file.
-
Delete empty batch:
-
True: Removes the parent batch if no active codes remain. -
False: Keeps the batch even if it is empty. -
Name: A descriptive name for the scheduler.
-
Next run: The next time the scheduler should run. This must not be earlier than Run at, but can be left blank.
-
Note: The scheduler is idempotent. Once configured, it will follow the set rules without needing to be triggered manually. The Next run option simply allows for an additional one-off execution.
Summary
- Generate your recovery codes.
- Click Delete Codes → Django-Q schedules the deletion.
- In the Admin, open Recovery code cleanup schedulers.
- Configure:
- Retention days → how long codes stay before deletion (set
0for immediate removal). - Schedule type → how often deletion runs.
- Run at / Next run → when to start.
- Delete empty batch → remove batch if no codes remain.
✅ That’s it. Django-Q will handle the cleanup automatically. Tasks are added to a queue and picked up by workers, so in most cases the cleanup will happen very quickly. Depending on your worker setup and workload, there may be a short delay, but it will always be processed.
Visual Flow
Generate recovery codes
│
▼
Click **Delete Codes**
│
▼
Django-Q schedules deletion
│
▼
Go to **Admin → Recovery code cleanup schedulers**
│
▼
Configure scheduler:
• Retention days
• Run at / Next run
• Schedule type
• Delete empty batch
│
▼
✅ Codes are cleaned up automatically
You can also run a scheduler to remove the audit reports for Recovery Code by using Recovery code audit schedulers. The audits are store in the Recovery code Audit model. The steps are same as the above steps.
Running Tests
Ensure you have a settings.py file with the default configuration flags as shown in the walkthrough. You may use your own variable values, but all flags must exist, otherwise tests will fail.
These configurations are required for the app to function correctly.
Warning
In the admin.py interface under Recovery Codes, do not manually delete codes.
The system is tied into the RecoveryCodesBatch model.
If you want to remove a code, mark it as invalid or mark it for deletion instead. This is important because the parent batch (RecoveryCodesBatch) tracks how many codes it has generated. This is good meaning it was generated and tied to its parent (RecoveryCodesBatch). Any action on the parent batch affects its children (the codes) which is the intended behaviour, but the reverse is not true which is also the intended behaviour. For example:
- If a batch is deleted, all its child codes are also deleted.
- If a batch is marked as pending deletion, all its child codes are marked similarly.
- If any of the children (codes) are marked as invalid or for deletion, the parent batch is not affected, and thus does not impact the other child codes.
Why this matters
When a user deletes all codes from the frontend:
- The application marks the batch for deletion, which in turn marks its child codes.
- A scheduler later removes any codes marked for deletion.
- Once all child codes are deleted, the empty parent batch is automatically deleted.
If you manually delete child codes in a batch:
- The scheduler cannot correctly clean up the batch.
- You will end up with an empty batch in the database, tied to nothing and effectively “floating” and unused. When the scheduler next runs, it will generate email reports for that batch, indicating that nothing was cleaned which is true, since the batch has no children. This will bury reports about actual code cleanup under a flood of emails making it hard to find your emails.
If you want to delete all codes without waiting for the scheduler, delete the parent batch. This safely removes all associated codes because any action on the parent affects its children.
Note: Deleting a single child code manually does not deactivate the parent batch. Marking a single code as invalid or for deletion will not affect the rest of the batch.
Summary
- Mark individual codes as invalid or for deletion in the admin interface, do not manually delete them.
- To delete all codes immediately, delete the parent batch, this is safer and ensures proper cleanup.
Django-Q Flush Tasks Command
A custom Django management command to safely clear Django-Q tasks and scheduler entries.
- This command allows you to remove failed tasks, scheduler entries, or all tasks from the Django-Q queue.
- This management command allows you to safely flush Django-Q tasks and scheduler entries directly from the command line, with confirmation prompts to prevent accidental data loss.
Why is this needed?
Flushing tasks via the command line allows you to clear all tasks in the queue useful if an error occurs, or if you want to remove invalid or outdated schedulers and start fresh and quickly without going through the UI.
Usage
Run the command using manage.py:
python manage.py flush_tasks
Options
--failed: Clear only the failed tasks.--scheduler: Clear only the scheduler entries.--all: Clear all tasks (failed and scheduled).--yes: Skip confirmation prompts (use with caution).--noinput: Supress the confirmation prompt
Confirmation Prompt
By default, the command asks for confirmation before performing the flush. Example:
python manage.py flush_tasks --all
Are you sure you want to clear all tasks? [y/N]: yes
Cleared 5 task(s).
If you answer N or press Enter, the operation is cancelled.
Quick Start Examples
- Clear failed tasks only:
python manage.py flush_tasks --failed
Are you sure you want to clear failed tasks? [y/N]: yes
Cleared 3 failed task(s).
- Clear scheduler entries only:
python manage.py flush_tasks --scheduler
Are you sure you want to clear scheduler tasks? [y/N]: yes
Cleared 2 scheduler task(s).
- Clear all tasks:
python manage.py flush_tasks --all
Are you sure you want to clear all tasks? [y/N]: yes
Cleared 5 task(s).
Automated Use (No Confirmation)
If you need to flush tasks automatically (e.g., in scripts, cron jobs, or CI/CD pipelines), use the --yes flag to bypass the confirmation prompt:
python manage.py flush_tasks --all --noinput
Cleared 5 task(s).
Using without
--noinputcan be combined with any of the options (--failed,--scheduler,--all).- Use this with caution, as tasks will be removed without user confirmation.
Notes
- Only the management command file is required for command-line usage.
- Helper functions can be included in the same file or imported from a
utils/folder if preferred. - Use the
--yesflag carefully in production environments. - This tool is particularly useful to remove old, stuck, or failed tasks that could interfere with Django-Q operation.
-
Django-Q not installed
- Install via pip:
pip install django-q
- Ensure
django_qis included inINSTALLED_APPSand configured insettings.py.
Notes
- This command works for Django-Q tasks only. It does not affect other background jobs or Celery tasks.
- Always use the confirmation prompt when clearing all tasks to avoid accidental data loss.
- Keep the
flush_tasks.pyfile inmanagement/commandsfor command-line use.
Quickstart Video Walkthrough
The following flags were used in the demo.
You can copy and paste them into your settings.py file and modify them for your own use.
# =======================================
# Adding the flags needed for the app
# =======================================
# ===========================
# 📧 Email / Admin
# ===========================
DJANGO_AUTH_RECOVERY_CODES_ADMIN_SENDER_EMAIL = "your-email-here"
DJANGO_AUTH_RECOVERY_CODE_ADMIN_EMAIL_HOST_USER = "your host email here"
DJANGO_AUTH_RECOVERY_CODE_ADMIN_USERNAME = "your name"
DJANGO_AUTH_RECOVERY_CODE_STORE_EMAIL_LOG = False
# ===========================
# 🔑 Security / Keys
# ===========================
DJANGO_AUTH_RECOVERY_KEY = 'Recovery-key-here'
# ===========================
# 📜 Audit / Retention
# ===========================
DJANGO_AUTH_RECOVERY_CODE_AUDIT_ENABLE_AUTO_CLEANUP = True
DJANGO_AUTH_RECOVERY_CODE_AUDIT_RETENTION_DAYS = 30
DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_RETENTION_DAYS = 30
DJANGO_AUTH_RECOVERY_CODE_PURGE_DELETE_SCHEDULER_USE_LOGGER = True
# ===========================
# ⏳ Rate Limiting / Cooldowns
# ===========================
DJANGO_AUTH_RECOVERY_CODES_AUTH_RATE_LIMITER_USE_CACHE = True
DJANGO_AUTH_RECOVERY_CODES_BASE_COOLDOWN = 300 # 5-minute lock down
DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_CUTOFF_POINT = 3600
DJANGO_AUTH_RECOVERY_CODES_COOLDOWN_MULTIPLIER = 2
DJANGO_AUTH_RECOVERY_CODES_MAX_LOGIN_ATTEMPTS = 5
# ===========================
# 📦 Caching
# ===========================
DJANGO_AUTH_RECOVERY_CODES_CACHE_MAX = 3600
DJANGO_AUTH_RECOVERY_CODES_CACHE_MIN = 1
DJANGO_AUTH_RECOVERY_CODES_CACHE_TTL = 3600
# ===========================
# 📊 Pagination / Limits
# ===========================
DJANGO_AUTH_RECOVERY_CODE_MAX_VISIBLE = 20
DJANGO_AUTH_RECOVERY_CODE_PER_PAGE = 1
DJANGO_AUTH_RECOVERY_CODES_BATCH_DELETE_SIZE = 400
DJANGO_AUTH_RECOVERY_CODES_MAX_DELETIONS_PER_RUN = -1
# ===========================
# 📂 Files / Naming
# ===========================
DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FILE_NAME = "recovery_codes"
DJANGO_AUTH_RECOVERY_CODES_DEFAULT_FORMAT = "txt"
# ===========================
# 🌍 Site / Redirects
# ===========================
DJANGO_AUTH_RECOVERY_CODES_SITE_NAME = "This is a demo tutorial page"
DJANGO_AUTH_RECOVERY_CODE_REDIRECT_VIEW_AFTER_LOGOUT = "logout_user" # redirect to a different page
# ===========================
# 💬 Recovery Code Email Success Message
# ===========================
DJANGO_AUTH_RECOVERY_CODE_EMAIL_SUCCESS_MSG = "Hey, what's up? Your recovery codes have been sent to your email!"
# ===========================
# 🧪 Email Backend (for testing)
# ===========================
# Use Django's file-based email backend for testing purposes
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = BASE_DIR / "sent_emails"
Path(EMAIL_FILE_PATH).mkdir(parents=True, exist_ok=True)
# ===========================
# ⚙️ Django-Q Configuration
# ===========================
# Add the Q Cluster needed for Django-Q task scheduling
Q_CLUSTER = {
'name': 'recovery_codes',
'workers': 2,
'timeout': 300, # 5 minutes max per task
'retry': 600, # retry after 10 minutes if a task fails (retry must be greater than timeout)
'recycle': 500,
'compress': True,
'cpu_affinity': 1,
'save_limit': 250,
'queue_limit': 500,
'orm': 'default',
}
# ===========================
# 🧭 Template Paths
# ===========================
# Add the path templates so that EmailSender knows where to look for the templates
import django_auth_recovery_codes
from pathlib import Path
# Get the path to the installed package
PACKAGE_DIR = Path(django_auth_recovery_codes.__file__).parent
# Define the templates directory within the package
MYAPP_TEMPLATES_DIR = PACKAGE_DIR / "templates" / "django_auth_recovery_codes"
# ===========================
# 🪵 Logging
# ===========================
# Add logging configuration to capture and log errors
from django_auth_recovery_codes.loggers.logger_config import DJANGO_AUTH_RECOVERY_CODES_LOGGING
LOGGING = DJANGO_AUTH_RECOVERY_CODES_LOGGING
Setup
Let’s get started! For this tutorial, we’ll install the package directly from GitHub since it hasn’t been published to PyPI yet:
pip install git+https://github.cUku1/django_2fa_recovery_codes.git
💡 Note: By the time this video goes live, the package will be available on PyPI. Then you can install it the usual way with:
pip install django_auth_recovery_codes
After installation, you’re ready to follow along with the rest of the walkthrough.
Watch the setup walkthrough here
App Demonstration Walkthrough
Check out the app in action in the video below:
▶ Watch the app demonstration walkthrough
View How Emails Are Displayed to the User (Backend)
Note: For demonstration purposes, we are sending the emails to the backend. This means there is no styling applied here. In your own email inbox, they will appear fully styled.
📧 View how the emails are sent to the user
Add Django-Q to Schedule Task Deletion
This section provides a walkthrough on how to set up Django-Q to automatically delete tasks after completion.
⚙️ Watch how to use Django-Q to delete tasks
Some Flag Demonstrations
Here are a few examples of how you can configure different flags in the application demonstrated in video walkthrought.
- The first flag limits the UI to display only 20 records at a time, even if more exist.
- The second flag specifies that only one record is shown per page, resulting in 20 pages each showing one record, in this example.
DJANGO_AUTH_RECOVERY_CODE_MAX_VISIBLE = 20
DJANGO_AUTH_RECOVERY_CODE_PER_PAGE = 5
- You can also customise the message displayed after a user emails themselves a copy of their recovery codes:
DJANGO_AUTH_RECOVERY_CODE_EMAIL_SUCCESS_MSG = "Hey, what's up? Your recovery codes have been sent to your email!"
Plus a few other examples are demonstrated in the video below:
Optional: Add Emails to the Model
You can optionally add emails to the database for enhanced tracking or custom email management.
📨 Watch how to add emails to the database
Flushing the Tasks
Flushing tasks via the command line allows you to clear all tasks in the queue m useful if an error occurs,
or if you want to remove invalid or outdated schedulers and start fresh.
🧹 Watch how to flush the task scheduler
Known Issues
- The app is responsive across all screen sizes.
- However, on medium, small, or extra-small screens the
logoutbutton is not visible because thehamburgermenu is not yet active. - This will be addressed in a future update.
License
- This package is licensed under the MIT License. See the LICENSE file for details.
Credits
-This library was created and maintained by Egbie Uku a.k.a EgbieAndersonUku1.
Support
If you find this project helpful, you can support it:
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_auth_recovery_codes-1.0.3.tar.gz.
File metadata
- Download URL: django_auth_recovery_codes-1.0.3.tar.gz
- Upload date:
- Size: 7.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0c22658ef58d4ab00bf272dea6109d0692792972822f3ba0cbaafb47d8d7798e
|
|
| MD5 |
49d3de6672260b006309aa22d80e82ad
|
|
| BLAKE2b-256 |
4d5bd8abe66ae29617091bff5d7a72ce071da3c88c40cba886f4c31c6a1a733d
|
File details
Details for the file django_auth_recovery_codes-1.0.3-py3-none-any.whl.
File metadata
- Download URL: django_auth_recovery_codes-1.0.3-py3-none-any.whl
- Upload date:
- Size: 10.1 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e51d29218d8030d69c5edbfce04149832c5176448fe89b47f6d9fbe31bb0c66
|
|
| MD5 |
9508fd30e74392dc1a44180aaf59e629
|
|
| BLAKE2b-256 |
1ca2d8ae3819ff575ca651c982ce340270e85ca0e151281c6485567d1cc3bbef
|