diff --git a/API.md b/API.md index 3e2f5aa..ee0380d 100644 --- a/API.md +++ b/API.md @@ -1182,10 +1182,9 @@ Current version: 1.0 (no version prefix in URLs) For API support or questions: - GitHub Issues: Create an issue in the repository -- Email: keith@firebugit.com +- Email: keithsmith@darksingularity.org --- -**Built by Firebug IT** **Documentation Version: 1.0** -**Last Updated: 2025-01-10** +**Last Updated: 2026-09-05** diff --git a/README.md b/README.md index 06c0eb9..a80da90 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ A powerful Django-based task management system with time tracking, tag organizat ### API & Sync - **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 - **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** ```bash - git clone https://git.firebugit.com/Firebug_IT/KeepItGoingServer.git + git clone https://git.darksingularity.org/DarkSingularity/KeepItGoingServer.git cd KeepItGoingServer ``` @@ -423,9 +423,9 @@ Key environment variables (see `.env.example`): #### 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())"`) - `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`) -- `CSRF_TRUSTED_ORIGINS` - HTTPS origins for CSRF (e.g., `https://tasks.firebugit.com`) -- `SITE_DOMAIN` - Domain name for email links (e.g., `tasks.firebugit.com`) +- `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.darksingularity.org`) +- `SITE_DOMAIN` - Domain name for email links (e.g., `tasks.darksingularity.org`) #### Database - `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_HOST_USER` - SMTP username/email - `EMAIL_HOST_PASSWORD` - SMTP password or API key -- `DEFAULT_FROM_EMAIL` - From email address (e.g., `KeepItGoing `) +- `DEFAULT_FROM_EMAIL` - From email address (e.g., `KeepItGoing `) - `SERVER_EMAIL` - Server error email address #### Email Verification Settings @@ -642,7 +642,7 @@ sudo useradd -m -s /bin/bash keepitgoing sudo su - keepitgoing # Clone repository -git clone https://git.firebugit.com/Firebug_IT/KeepItGoingServer.git +git clone https://git.darksingularity.org/DarkSingularity/KeepItGoingServer.git cd KeepItGoingServer # Create virtual environment @@ -665,9 +665,9 @@ cat > .env << 'EOF' # Django Settings SECRET_KEY=GENERATE_WITH_get_random_secret_key DEBUG=False -ALLOWED_HOSTS=tasks.firebugit.com,localhost,127.0.0.1 -CSRF_TRUSTED_ORIGINS=https://tasks.firebugit.com -SITE_DOMAIN=tasks.firebugit.com +ALLOWED_HOSTS=tasks.darksingularity.org,localhost,127.0.0.1 +CSRF_TRUSTED_ORIGINS=https://tasks.darksingularity.org +SITE_DOMAIN=tasks.darksingularity.org # Database Configuration # Choose ONE of the following based on your database from Step 2: @@ -688,8 +688,8 @@ EMAIL_PORT=587 EMAIL_USE_TLS=True EMAIL_HOST_USER=your-email@gmail.com EMAIL_HOST_PASSWORD=your-app-password -DEFAULT_FROM_EMAIL=KeepItGoing -SERVER_EMAIL=server@firebugit.com +DEFAULT_FROM_EMAIL=KeepItGoing +SERVER_EMAIL=server@darksingularity.org # Email Verification EMAIL_VERIFICATION_TOKEN_EXPIRY_HOURS=24 @@ -919,7 +919,7 @@ upstream keepitgoing { server { listen 80; - server_name tasks.firebugit.com; + server_name tasks.darksingularity.org; # Redirect HTTP to HTTPS return 301 https://$server_name$request_uri; @@ -927,11 +927,11 @@ server { server { listen 443 ssl http2; - server_name tasks.firebugit.com; + server_name tasks.darksingularity.org; # SSL Configuration - ssl_certificate /etc/letsencrypt/live/tasks.firebugit.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/tasks.firebugit.com/privkey.pem; + ssl_certificate /etc/letsencrypt/live/tasks.darksingularity.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/tasks.darksingularity.org/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; @@ -985,7 +985,7 @@ sudo systemctl restart nginx ```bash # Obtain SSL certificate -sudo certbot --nginx -d tasks.firebugit.com +sudo certbot --nginx -d tasks.darksingularity.org # Certbot will automatically configure nginx # Certificate auto-renews via cron @@ -1004,7 +1004,7 @@ sudo ufw status #### Step 10: Initial Admin User and Approval ```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 # If ALLOW_SELF_REGISTRATION=True and new users register: @@ -1115,7 +1115,7 @@ from django.core.mail import send_mail send_mail( 'Test Subject', 'Test message.', - 'noreply@firebugit.com', + 'noreply@darksingularity.org', ['your-email@example.com'], ) # Check for any errors @@ -1198,9 +1198,9 @@ sudo tail -f /var/log/nginx/access.log 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:** - 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 Proprietary - All Rights Reserved -Copyright (c) 2025 Firebug IT +Copyright (c) 2026 Keith Smith ## Support For issues and questions: - Create an issue in the repository -- Email: keith@firebugit.com +- Email: keithsmith@darksingularity.org ## 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) - **Email Verification System**: Users must verify email before logging in - **Admin Approval Workflow**: Admins approve new users via Django admin @@ -1249,7 +1296,3 @@ For issues and questions: - Web interface with dark mode - Responsive design - RESTful API - ---- - -Built with ❤️ by Firebug IT diff --git a/config/settings/base.py b/config/settings/base.py index 584b5fd..ef00d0b 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -44,7 +44,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'tasks.middleware.AllowMobileAppFramingMiddleware', # Allow mobile app iframe embedding + 'tasks.middleware.SecurityHeadersMiddleware', ] ROOT_URLCONF = 'config.urls' diff --git a/config/settings/development.py b/config/settings/development.py index 4352aeb..b715765 100644 --- a/config/settings/development.py +++ b/config/settings/development.py @@ -20,10 +20,10 @@ if not SECRET_KEY: DEBUG = True # 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_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 DATABASES = { diff --git a/tasks/middleware/__init__.py b/tasks/middleware/__init__.py index 4eb4b28..4261a24 100644 --- a/tasks/middleware/__init__.py +++ b/tasks/middleware/__init__.py @@ -1,3 +1,3 @@ -from .mobile_app import AllowMobileAppFramingMiddleware +from .security_headers import SecurityHeadersMiddleware -__all__ = ['AllowMobileAppFramingMiddleware'] +__all__ = ['SecurityHeadersMiddleware'] diff --git a/tasks/middleware/mobile_app.py b/tasks/middleware/mobile_app.py deleted file mode 100644 index b7d6586..0000000 --- a/tasks/middleware/mobile_app.py +++ /dev/null @@ -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 diff --git a/tasks/middleware/security_headers.py b/tasks/middleware/security_headers.py new file mode 100644 index 0000000..681d925 --- /dev/null +++ b/tasks/middleware/security_headers.py @@ -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 diff --git a/templates/base.html b/templates/base.html index c97f441..b146d09 100644 --- a/templates/base.html +++ b/templates/base.html @@ -142,7 +142,7 @@ {% block auth_content %}{% endblock %} {% endif %} {% endblock %}