This commit addresses the persistent WebSocket disconnection and reconnection
problem experienced with ticket updates. The root cause was identified as the
Django backend not running as an ASGI server, which is essential for WebSocket
functionality, and incorrect WebSocket routing.
The following changes were made:
- **Frontend ():**
- Updated to append the from cookies to the WebSocket URL's
query parameter for authentication, ensuring the token is sent with the
WebSocket connection request.
- **Backend Configuration:**
- **:** Modified to explicitly
start the Daphne ASGI server using instead
of . This ensures the backend runs in ASGI
mode, capable of handling WebSocket connections.
- **:** Removed 'daphne' from
. Daphne is an ASGI server, not a traditional Django
application, and its presence in was causing application
startup failures.
- **:**
- Removed from as it
conflicts with Channels' ASGI server takeover.
- Explicitly set to ensure
the ASGI entry point is correctly referenced.
- **:** Added 'channels'
to , ensuring the Channels application is correctly loaded
within the multi-tenant setup, enabling ASGI functionality.
- **Backend Middleware & Routing:**
- **:** Implemented a custom
to authenticate WebSocket connections using an
from either a query parameter or cookies. This middleware
ensures proper user authentication for WebSocket sessions. Debugging
prints with were added for better visibility.
- **:** Adjusted WebSocket URL regexes
to for robustness, ensuring correct matching
regardless of leading/trailing slashes in the path.
These changes collectively ensure that WebSocket connections are properly
initiated by the frontend, authenticated by the backend, and served by
an ASGI-compliant server, resolving the frequent disconnection/reconnection
issue.
SmoothSchedule - Multi-Tenant Scheduling Platform
A production-ready multi-tenant SaaS platform for resource scheduling and orchestration.
🎯 Features
- ✅ Multi-Tenancy: PostgreSQL schema-per-tenant using django-tenants
- ✅ 8-Tier Role Hierarchy: From SUPERUSER to CUSTOMER with strict permissions
- ✅ Modern Stack: Django 5.2 + React 18 + Vite
- ✅ Docker Ready: Complete production & development Docker Compose setup
- ✅ Cloud Storage: DigitalOcean Spaces (S3-compatible) for static/media files
- ✅ Auto SSL: Let's Encrypt certificates via Traefik reverse proxy
- ✅ Task Queue: Celery + Redis for background jobs
- ✅ Real-time: Django Channels + WebSockets support
- ✅ Production Ready: Fully configured for deployment
📚 Documentation
- PRODUCTION_DEPLOYMENT.md - Manual step-by-step production deployment (start here for fresh deployments)
- QUICK-REFERENCE.md - Common commands and quick start
- PRODUCTION-READY.md - Production deployment status
- DEPLOYMENT.md - Comprehensive deployment guide
- CLAUDE.md - Development guide and architecture
🚀 Quick Start
Local Development
# Start backend (Django in Docker)
cd smoothschedule
docker compose -f docker-compose.local.yml up -d
# Start frontend (React with Vite)
cd ../frontend
npm install
npm run dev
# Access the app
# Frontend: http://platform.lvh.me:5173
# Backend API: http://lvh.me:8000/api
See CLAUDE.md for detailed development instructions.
Production Deployment
For fresh deployments or complete reset, follow PRODUCTION_DEPLOYMENT.md for manual step-by-step instructions.
For routine updates, use the automated script:
# Deploy to production server (code changes only)
./deploy.sh poduck@smoothschedule.com
See PRODUCTION-READY.md for deployment checklist and DEPLOYMENT.md for detailed steps.
🏗️ Architecture
Multi-Tenancy Model
┌─────────────────────────────────────────┐
│ PostgreSQL Database │
├─────────────────────────────────────────┤
│ public (shared schema) │
│ ├─ Tenants │
│ ├─ Domains │
│ ├─ Users │
│ └─ PermissionGrants │
├─────────────────────────────────────────┤
│ tenant_demo (schema for Demo Company) │
│ ├─ Appointments │
│ ├─ Resources │
│ └─ Customers │
├─────────────────────────────────────────┤
│ tenant_acme (schema for Acme Corp) │
│ ├─ Appointments │
│ ├─ Resources │
│ └─ Customers │
└─────────────────────────────────────────┘
Role Hierarchy
| Role | Level | Access Scope |
|---|---|---|
| SUPERUSER | Platform | All tenants (god mode) |
| PLATFORM_MANAGER | Platform | All tenants |
| PLATFORM_SALES | Platform | Demo accounts only |
| PLATFORM_SUPPORT | Platform | Tenant users |
| TENANT_OWNER | Tenant | Own tenant (full access) |
| TENANT_MANAGER | Tenant | Own tenant |
| TENANT_STAFF | Tenant | Own tenant (limited) |
| CUSTOMER | Tenant | Own data only |
Masquerading Matrix
| Hijacker Role | Can Masquerade As |
|---|---|
| SUPERUSER | Anyone |
| PLATFORM_SUPPORT | Tenant users |
| PLATFORM_SALES | Demo accounts (is_temporary=True) |
| TENANT_OWNER | Staff in same tenant |
| Others | No one |
Security Rules:
- Cannot hijack yourself
- Cannot hijack SUPERUSERs (except by other SUPERUSERs)
- Maximum depth: 1 (no hijack chains)
- All attempts logged to
logs/masquerade.log
📁 Project Structure
smoothschedule/
├── config/
│ └── settings.py # Multi-tenancy & security config
├── core/
│ ├── models.py # Tenant, Domain, PermissionGrant
│ ├── permissions.py # Hijack permission matrix
│ ├── middleware.py # Masquerade audit logging
│ └── admin.py # Django admin for core models
├── users/
│ ├── models.py # Custom User with 8-tier roles
│ └── admin.py # User admin with hijack button
├── logs/
│ ├── security.log # General security events
│ └── masquerade.log # Hijack activity (JSON)
└── setup_project.sh # Automated setup script
🔐 Security Features
Audit Logging
All masquerade activity is logged in JSON format:
{
"timestamp": "2024-01-15T10:30:00Z",
"action": "HIJACK_START",
"hijacker_email": "support@smoothschedule.com",
"hijacked_email": "customer@demo.com",
"ip_address": "192.168.1.1",
"session_key": "abc123..."
}
Permission Grants (30-Minute Window)
Time-limited elevated permissions:
from core.models import PermissionGrant
grant = PermissionGrant.create_grant(
grantor=admin_user,
grantee=support_user,
action="view_billing",
reason="Customer requested billing support",
duration_minutes=30,
)
# Check if active
if grant.is_active():
# Perform privileged action
pass
🧪 Testing Masquerading
- Access Django Admin:
http://localhost:8000/admin/ - Create test users with different roles
- Click "Hijack" button next to a user
- Verify audit logs:
docker-compose exec django cat logs/masquerade.log
📊 Admin Interface
- Tenant Management: View tenants, domains, subscription tiers
- User Management: Color-coded roles, masquerade buttons
- Permission Grants: Active/expired/revoked status, bulk revoke
- Domain Verification: AWS Route53 integration status
🛠️ Development
Adding Tenant Apps
Edit config/settings.py:
TENANT_APPS = [
'django.contrib.contenttypes',
'appointments', # Your app
'resources', # Your app
'billing', # Your app
]
Custom Domain Setup
domain = Domain.objects.create(
domain="app.customdomain.com",
tenant=tenant,
is_custom_domain=True,
route53_zone_id="Z1234567890ABC",
)
Then configure Route53 CNAME: app.customdomain.com → smoothschedule.yourhost.com
📖 Key Files Reference
| File | Purpose |
|---|---|
setup_project.sh |
Automated project initialization |
config/settings.py |
Multi-tenancy, middleware, security config |
core/models.py |
Tenant, Domain, PermissionGrant models |
core/permissions.py |
Masquerading permission matrix |
core/middleware.py |
Audit logging for masquerading |
users/models.py |
Custom User with 8-tier roles |
📝 Important Notes
- Django Admin: The ONLY HTML interface (everything else is API)
- Middleware Order:
TenantMainMiddlewaremust be first,MasqueradeAuditMiddlewareafterHijackUserMiddleware - Tenant Isolation: Each tenant's data is in a separate PostgreSQL schema
- Production: Update
SECRET_KEY, database credentials, and AWS keys via environment variables
🐛 Troubleshooting
Cannot create tenant users:
- Error: "Users with role TENANT_STAFF must be assigned to a tenant"
- Solution: Set
user.tenant = tenant_instancebefore saving
Hijack button doesn't appear:
- Check
HIJACK_AUTHORIZATION_CHECKin settings - Verify
HijackUserAdminMixininusers/admin.py - Ensure user has permission per matrix rules
Migrations fail:
- Run shared migrations first:
migrate_schemas --shared - Then run tenant migrations:
migrate_schemas
📄 License
MIT
🤝 Contributing
This is a production skeleton. Extend TENANT_APPS with your business logic.
Built with ❤️ for multi-tenant SaaS perfection