Internal
Public Access
Update README changelog, remove Firebug IT branding, retire dead mobile middleware
README.md: backfill the Changelog with the 2026.9.5, 2026.8.31.1, 2026.8.31, and v1.2.0 releases that were missing (it jumped straight from 1.0.0 to 1.1.0). Also correct the stale "Mobile App Support"/"Mobile App Integration" sections - the native Android app is retired, and the sync protocol they describe now powers the offline PWA instead. Remove Firebug IT branding/contact info across README.md, API.md, and the site footer, and drop the stale tasks.firebugit.com fallback from development.py's ALLOWED_HOSTS/CSRF_TRUSTED_ORIGINS. AllowMobileAppFramingMiddleware detected the native app via a 'com.firebugit.keepitgoing' User-Agent check to allow WebView iframe embedding. With that app retired, replaced it with SecurityHeadersMiddleware, which applies the same X-Frame-Options/CSP headers unconditionally instead of only for non-mobile requests - same protection for real users, dead branch and dead branding gone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6791f008e9
commit
f41939983a
@@ -1182,10 +1182,9 @@ Current version: 1.0 (no version prefix in URLs)
|
|||||||
|
|
||||||
For API support or questions:
|
For API support or questions:
|
||||||
- GitHub Issues: Create an issue in the repository
|
- GitHub Issues: Create an issue in the repository
|
||||||
- Email: keith@firebugit.com
|
- Email: keithsmith@darksingularity.org
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Built by Firebug IT**
|
|
||||||
**Documentation Version: 1.0**
|
**Documentation Version: 1.0**
|
||||||
**Last Updated: 2025-01-10**
|
**Last Updated: 2026-09-05**
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ A powerful Django-based task management system with time tracking, tag organizat
|
|||||||
|
|
||||||
### API & Sync
|
### API & Sync
|
||||||
- **RESTful API**: Full REST API for programmatic access
|
- **RESTful API**: Full REST API for programmatic access
|
||||||
- **Mobile App Support**: Sync protocol for Android app
|
- **Offline-First Sync Protocol**: Bidirectional sync with conflict resolution, powering the offline PWA
|
||||||
- **Real-time Updates**: Task changes sync across devices
|
- **Real-time Updates**: Task changes sync across devices
|
||||||
- **Conflict Resolution**: Handles offline changes and syncing
|
- **Conflict Resolution**: Handles offline changes and syncing
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ A powerful Django-based task management system with time tracking, tag organizat
|
|||||||
|
|
||||||
1. **Clone the repository**
|
1. **Clone the repository**
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.firebugit.com/Firebug_IT/KeepItGoingServer.git
|
git clone https://git.darksingularity.org/DarkSingularity/KeepItGoingServer.git
|
||||||
cd KeepItGoingServer
|
cd KeepItGoingServer
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -423,9 +423,9 @@ Key environment variables (see `.env.example`):
|
|||||||
#### Core Settings
|
#### Core Settings
|
||||||
- `SECRET_KEY` - Django secret key (required in production, generate with `python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"`)
|
- `SECRET_KEY` - Django secret key (required in production, generate with `python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"`)
|
||||||
- `DEBUG` - Enable debug mode (True/False, must be False in production)
|
- `DEBUG` - Enable debug mode (True/False, must be False in production)
|
||||||
- `ALLOWED_HOSTS` - Comma-separated list of allowed hosts (e.g., `tasks.firebugit.com,localhost`)
|
- `ALLOWED_HOSTS` - Comma-separated list of allowed hosts (e.g., `tasks.darksingularity.org,localhost`)
|
||||||
- `CSRF_TRUSTED_ORIGINS` - HTTPS origins for CSRF (e.g., `https://tasks.firebugit.com`)
|
- `CSRF_TRUSTED_ORIGINS` - HTTPS origins for CSRF (e.g., `https://tasks.darksingularity.org`)
|
||||||
- `SITE_DOMAIN` - Domain name for email links (e.g., `tasks.firebugit.com`)
|
- `SITE_DOMAIN` - Domain name for email links (e.g., `tasks.darksingularity.org`)
|
||||||
|
|
||||||
#### Database
|
#### Database
|
||||||
- `DATABASE_URL` - Database connection string (optional, overrides settings)
|
- `DATABASE_URL` - Database connection string (optional, overrides settings)
|
||||||
@@ -441,7 +441,7 @@ Key environment variables (see `.env.example`):
|
|||||||
- `EMAIL_USE_SSL` - Use SSL (True/False, use True for port 465)
|
- `EMAIL_USE_SSL` - Use SSL (True/False, use True for port 465)
|
||||||
- `EMAIL_HOST_USER` - SMTP username/email
|
- `EMAIL_HOST_USER` - SMTP username/email
|
||||||
- `EMAIL_HOST_PASSWORD` - SMTP password or API key
|
- `EMAIL_HOST_PASSWORD` - SMTP password or API key
|
||||||
- `DEFAULT_FROM_EMAIL` - From email address (e.g., `KeepItGoing <noreply@firebugit.com>`)
|
- `DEFAULT_FROM_EMAIL` - From email address (e.g., `KeepItGoing <noreply@darksingularity.org>`)
|
||||||
- `SERVER_EMAIL` - Server error email address
|
- `SERVER_EMAIL` - Server error email address
|
||||||
|
|
||||||
#### Email Verification Settings
|
#### Email Verification Settings
|
||||||
@@ -642,7 +642,7 @@ sudo useradd -m -s /bin/bash keepitgoing
|
|||||||
sudo su - keepitgoing
|
sudo su - keepitgoing
|
||||||
|
|
||||||
# Clone repository
|
# Clone repository
|
||||||
git clone https://git.firebugit.com/Firebug_IT/KeepItGoingServer.git
|
git clone https://git.darksingularity.org/DarkSingularity/KeepItGoingServer.git
|
||||||
cd KeepItGoingServer
|
cd KeepItGoingServer
|
||||||
|
|
||||||
# Create virtual environment
|
# Create virtual environment
|
||||||
@@ -665,9 +665,9 @@ cat > .env << 'EOF'
|
|||||||
# Django Settings
|
# Django Settings
|
||||||
SECRET_KEY=GENERATE_WITH_get_random_secret_key
|
SECRET_KEY=GENERATE_WITH_get_random_secret_key
|
||||||
DEBUG=False
|
DEBUG=False
|
||||||
ALLOWED_HOSTS=tasks.firebugit.com,localhost,127.0.0.1
|
ALLOWED_HOSTS=tasks.darksingularity.org,localhost,127.0.0.1
|
||||||
CSRF_TRUSTED_ORIGINS=https://tasks.firebugit.com
|
CSRF_TRUSTED_ORIGINS=https://tasks.darksingularity.org
|
||||||
SITE_DOMAIN=tasks.firebugit.com
|
SITE_DOMAIN=tasks.darksingularity.org
|
||||||
|
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
# Choose ONE of the following based on your database from Step 2:
|
# Choose ONE of the following based on your database from Step 2:
|
||||||
@@ -688,8 +688,8 @@ EMAIL_PORT=587
|
|||||||
EMAIL_USE_TLS=True
|
EMAIL_USE_TLS=True
|
||||||
EMAIL_HOST_USER=your-email@gmail.com
|
EMAIL_HOST_USER=your-email@gmail.com
|
||||||
EMAIL_HOST_PASSWORD=your-app-password
|
EMAIL_HOST_PASSWORD=your-app-password
|
||||||
DEFAULT_FROM_EMAIL=KeepItGoing <noreply@firebugit.com>
|
DEFAULT_FROM_EMAIL=KeepItGoing <noreply@darksingularity.org>
|
||||||
SERVER_EMAIL=server@firebugit.com
|
SERVER_EMAIL=server@darksingularity.org
|
||||||
|
|
||||||
# Email Verification
|
# Email Verification
|
||||||
EMAIL_VERIFICATION_TOKEN_EXPIRY_HOURS=24
|
EMAIL_VERIFICATION_TOKEN_EXPIRY_HOURS=24
|
||||||
@@ -919,7 +919,7 @@ upstream keepitgoing {
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name tasks.firebugit.com;
|
server_name tasks.darksingularity.org;
|
||||||
|
|
||||||
# Redirect HTTP to HTTPS
|
# Redirect HTTP to HTTPS
|
||||||
return 301 https://$server_name$request_uri;
|
return 301 https://$server_name$request_uri;
|
||||||
@@ -927,11 +927,11 @@ server {
|
|||||||
|
|
||||||
server {
|
server {
|
||||||
listen 443 ssl http2;
|
listen 443 ssl http2;
|
||||||
server_name tasks.firebugit.com;
|
server_name tasks.darksingularity.org;
|
||||||
|
|
||||||
# SSL Configuration
|
# SSL Configuration
|
||||||
ssl_certificate /etc/letsencrypt/live/tasks.firebugit.com/fullchain.pem;
|
ssl_certificate /etc/letsencrypt/live/tasks.darksingularity.org/fullchain.pem;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/tasks.firebugit.com/privkey.pem;
|
ssl_certificate_key /etc/letsencrypt/live/tasks.darksingularity.org/privkey.pem;
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers on;
|
||||||
@@ -985,7 +985,7 @@ sudo systemctl restart nginx
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Obtain SSL certificate
|
# Obtain SSL certificate
|
||||||
sudo certbot --nginx -d tasks.firebugit.com
|
sudo certbot --nginx -d tasks.darksingularity.org
|
||||||
|
|
||||||
# Certbot will automatically configure nginx
|
# Certbot will automatically configure nginx
|
||||||
# Certificate auto-renews via cron
|
# Certificate auto-renews via cron
|
||||||
@@ -1004,7 +1004,7 @@ sudo ufw status
|
|||||||
#### Step 10: Initial Admin User and Approval
|
#### Step 10: Initial Admin User and Approval
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Log in to the web interface at https://tasks.firebugit.com/admin
|
# Log in to the web interface at https://tasks.darksingularity.org/admin
|
||||||
# Use the superuser credentials created in Step 3
|
# Use the superuser credentials created in Step 3
|
||||||
|
|
||||||
# If ALLOW_SELF_REGISTRATION=True and new users register:
|
# If ALLOW_SELF_REGISTRATION=True and new users register:
|
||||||
@@ -1115,7 +1115,7 @@ from django.core.mail import send_mail
|
|||||||
send_mail(
|
send_mail(
|
||||||
'Test Subject',
|
'Test Subject',
|
||||||
'Test message.',
|
'Test message.',
|
||||||
'noreply@firebugit.com',
|
'noreply@darksingularity.org',
|
||||||
['your-email@example.com'],
|
['your-email@example.com'],
|
||||||
)
|
)
|
||||||
# Check for any errors
|
# Check for any errors
|
||||||
@@ -1198,9 +1198,9 @@ sudo tail -f /var/log/nginx/access.log
|
|||||||
sudo journalctl -u keepitgoing -f
|
sudo journalctl -u keepitgoing -f
|
||||||
```
|
```
|
||||||
|
|
||||||
## Mobile App Integration
|
## Offline-First Sync
|
||||||
|
|
||||||
This server works with the KeepItGoing Android app via the sync endpoint.
|
The sync endpoint (`/api/sync/`) powers the offline PWA's background sync. It was originally built for a native Android app, which has since been retired in favor of the PWA, but the protocol itself is unchanged.
|
||||||
|
|
||||||
**Sync Protocol:**
|
**Sync Protocol:**
|
||||||
- Client sends current state and last sync timestamp
|
- Client sends current state and last sync timestamp
|
||||||
@@ -1219,16 +1219,63 @@ This server works with the KeepItGoing Android app via the sync endpoint.
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
Proprietary - All Rights Reserved
|
Proprietary - All Rights Reserved
|
||||||
Copyright (c) 2025 Firebug IT
|
Copyright (c) 2026 Keith Smith
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
For issues and questions:
|
For issues and questions:
|
||||||
- Create an issue in the repository
|
- Create an issue in the repository
|
||||||
- Email: keith@firebugit.com
|
- Email: keithsmith@darksingularity.org
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### 2026.9.5
|
||||||
|
|
||||||
|
**New Features:**
|
||||||
|
- Installable Progressive Web App (PWA): app manifest, service worker, and offline fallback pages
|
||||||
|
- Web Push notifications
|
||||||
|
- Full offline task management: create, edit, complete tasks, manage subtasks and tags, and track time while offline, with automatic sync once back online
|
||||||
|
- Per-task reminder notifications - "before due", "due now", and "overdue" - delivered by email and/or push, timed per task and configurable via the profile's Default Reminder setting
|
||||||
|
|
||||||
|
**Bug Fixes:**
|
||||||
|
- Overdue status now accounts for due time, not just due date
|
||||||
|
- Background sync now runs on every page load instead of being throttled, so the offline cache can no longer serve stale data (e.g. a tag deleted online reappearing offline)
|
||||||
|
- Offline mode now matches the online dashboard: correct subtask nesting, tag sidebar, overdue styling, and full task editing/tags/time tracking
|
||||||
|
- Fixed a crash when editing a task's due date/time
|
||||||
|
- Service worker precache now reliably picks up updated files after a deploy instead of a stale cached copy
|
||||||
|
- "Overdue" and "due now" notifications no longer arrive at the same instant - overdue now waits an hour
|
||||||
|
- Changing the reminder-timing setting now retroactively updates reminders already scheduled on existing tasks
|
||||||
|
|
||||||
|
### 2026.8.31.1
|
||||||
|
|
||||||
|
- Custom recurrence patterns: recurring tasks can repeat on specific days of the week, an interval of weeks (e.g. every other Wednesday), a specific day of the month, or the Nth weekday of the month (e.g. every second Tuesday)
|
||||||
|
- New "Custom" recurrence builder in the task create/edit UI generates the underlying RRULE pattern automatically
|
||||||
|
|
||||||
|
### 2026.8.31
|
||||||
|
|
||||||
|
**New Features:**
|
||||||
|
- Notification system simplified to a single daily task-due email, sent within each user's local morning window
|
||||||
|
- Self-registration can now be enabled/disabled via configuration (disabled by default)
|
||||||
|
|
||||||
|
**Bug Fixes:**
|
||||||
|
- Task sort now defaults to due date instead of raw/unsorted ordering
|
||||||
|
- Priority sort now orders correctly (high to low / low to high)
|
||||||
|
- Recurring tasks no longer create duplicate instances on completion
|
||||||
|
- Daily email timing now correctly accounts for all user timezones
|
||||||
|
- Overdue calculation now uses the user's local timezone
|
||||||
|
- Fixed Celery worker/beat health check issues
|
||||||
|
|
||||||
|
**Infrastructure:**
|
||||||
|
- Docker Compose now builds from the git repository
|
||||||
|
- Replaced pytz with zoneinfo for timezone handling
|
||||||
|
|
||||||
|
### v1.2.0 - Recurring Tasks
|
||||||
|
|
||||||
|
- Fully functional recurring tasks (daily, weekly, biweekly, monthly, yearly)
|
||||||
|
- Automatic creation of next task instance on completion
|
||||||
|
- Recurrence end date support
|
||||||
|
- Hourly background job to ensure no missed recurrences
|
||||||
|
|
||||||
### Version 1.1.0 (2025-01-22)
|
### Version 1.1.0 (2025-01-22)
|
||||||
- **Email Verification System**: Users must verify email before logging in
|
- **Email Verification System**: Users must verify email before logging in
|
||||||
- **Admin Approval Workflow**: Admins approve new users via Django admin
|
- **Admin Approval Workflow**: Admins approve new users via Django admin
|
||||||
@@ -1249,7 +1296,3 @@ For issues and questions:
|
|||||||
- Web interface with dark mode
|
- Web interface with dark mode
|
||||||
- Responsive design
|
- Responsive design
|
||||||
- RESTful API
|
- RESTful API
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Built with ❤️ by Firebug IT
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ MIDDLEWARE = [
|
|||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'tasks.middleware.AllowMobileAppFramingMiddleware', # Allow mobile app iframe embedding
|
'tasks.middleware.SecurityHeadersMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'config.urls'
|
ROOT_URLCONF = 'config.urls'
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ if not SECRET_KEY:
|
|||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
# Development-only hosts
|
# Development-only hosts
|
||||||
ALLOWED_HOSTS = ['localhost', '127.0.0.1', '[::1]', '10.0.2.2', '192.168.1.241', 'tasks.firebugit.com']
|
ALLOWED_HOSTS = ['localhost', '127.0.0.1', '[::1]', '10.0.2.2', '192.168.1.241']
|
||||||
|
|
||||||
# CSRF - for development testing only
|
# CSRF - for development testing only
|
||||||
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', '').split(',') if os.environ.get('CSRF_TRUSTED_ORIGINS') else ['https://tasks.firebugit.com']
|
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', '').split(',') if os.environ.get('CSRF_TRUSTED_ORIGINS') else []
|
||||||
|
|
||||||
# Database - SQLite for development
|
# Database - SQLite for development
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
from .mobile_app import AllowMobileAppFramingMiddleware
|
from .security_headers import SecurityHeadersMiddleware
|
||||||
|
|
||||||
__all__ = ['AllowMobileAppFramingMiddleware']
|
__all__ = ['SecurityHeadersMiddleware']
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
"""
|
|
||||||
Middleware to allow iframe embedding for the KeepItGoing mobile app.
|
|
||||||
|
|
||||||
The mobile app uses Capacitor WebView which embeds the website in an iframe.
|
|
||||||
This middleware detects requests from the mobile app and removes both
|
|
||||||
X-Frame-Options and Content-Security-Policy frame-ancestors headers to allow
|
|
||||||
iframe embedding, while keeping clickjacking protection for regular web browsers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class AllowMobileAppFramingMiddleware:
|
|
||||||
"""
|
|
||||||
Remove frame-blocking headers for requests from KeepItGoing mobile app.
|
|
||||||
|
|
||||||
The mobile app uses a Capacitor WebView. We detect these requests via
|
|
||||||
User-Agent and remove X-Frame-Options and CSP frame-ancestors headers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, get_response):
|
|
||||||
self.get_response = get_response
|
|
||||||
|
|
||||||
def __call__(self, request):
|
|
||||||
response = self.get_response(request)
|
|
||||||
|
|
||||||
# Check if request is from KeepItGoing mobile app
|
|
||||||
user_agent = request.META.get('HTTP_USER_AGENT', '')
|
|
||||||
|
|
||||||
# Detect Capacitor/Android WebView patterns
|
|
||||||
is_mobile_app = (
|
|
||||||
'wv' in user_agent.lower() or # Android WebView
|
|
||||||
'CapacitorHttp' in user_agent or
|
|
||||||
'com.firebugit.keepitgoing' in user_agent or
|
|
||||||
('KeepItGoing' in user_agent and 'Mobile' in user_agent)
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_mobile_app:
|
|
||||||
# Mobile app: Allow iframe embedding - don't add frame-blocking headers
|
|
||||||
# Remove any existing frame headers
|
|
||||||
if 'X-Frame-Options' in response:
|
|
||||||
del response['X-Frame-Options']
|
|
||||||
if 'Content-Security-Policy' in response:
|
|
||||||
del response['Content-Security-Policy']
|
|
||||||
else:
|
|
||||||
# Regular browsers: Add security headers for clickjacking protection
|
|
||||||
if 'X-Frame-Options' not in response:
|
|
||||||
response['X-Frame-Options'] = 'DENY'
|
|
||||||
|
|
||||||
if 'Content-Security-Policy' not in response:
|
|
||||||
response['Content-Security-Policy'] = (
|
|
||||||
"default-src 'self'; "
|
|
||||||
"script-src 'self' 'unsafe-inline'; "
|
|
||||||
"style-src 'self' 'unsafe-inline'; "
|
|
||||||
"img-src 'self' data: https:; "
|
|
||||||
"font-src 'self' data:; "
|
|
||||||
"connect-src 'self'; "
|
|
||||||
"frame-ancestors 'none'; "
|
|
||||||
"base-uri 'self'; "
|
|
||||||
"form-action 'self';"
|
|
||||||
)
|
|
||||||
|
|
||||||
return response
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"""
|
||||||
|
Middleware that applies clickjacking protection headers to every response.
|
||||||
|
|
||||||
|
Previously also allowed iframe embedding for a native mobile app's WebView
|
||||||
|
(detected via User-Agent); that app has been retired in favor of the PWA,
|
||||||
|
so the headers are now applied unconditionally.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityHeadersMiddleware:
|
||||||
|
"""Add X-Frame-Options and a CSP frame-ancestors policy to every response."""
|
||||||
|
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.get_response(request)
|
||||||
|
|
||||||
|
if 'X-Frame-Options' not in response:
|
||||||
|
response['X-Frame-Options'] = 'DENY'
|
||||||
|
|
||||||
|
if 'Content-Security-Policy' not in response:
|
||||||
|
response['Content-Security-Policy'] = (
|
||||||
|
"default-src 'self'; "
|
||||||
|
"script-src 'self' 'unsafe-inline'; "
|
||||||
|
"style-src 'self' 'unsafe-inline'; "
|
||||||
|
"img-src 'self' data: https:; "
|
||||||
|
"font-src 'self' data:; "
|
||||||
|
"connect-src 'self'; "
|
||||||
|
"frame-ancestors 'none'; "
|
||||||
|
"base-uri 'self'; "
|
||||||
|
"form-action 'self';"
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
+1
-1
@@ -142,7 +142,7 @@
|
|||||||
{% block auth_content %}{% endblock %}
|
{% block auth_content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<p>© 2025 Firebug IT. All rights reserved.</p>
|
<p>© 2026 Keith Smith. All rights reserved.</p>
|
||||||
</footer>
|
</footer>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user