20 KiB
Final Implementation Requirements & Constraints
Complete Specification - Ready for Implementation
Status: Complete specification, ready to begin
Critical Issues: 4 major + original gaps
Implementation Time: 7-10 days
Summary of All Requirements
This document consolidates:
- Original tenancy gaps (from audits)
- Free trial signup simplification
- Four critical new issues discovered
- Current database state context
CRITICAL ISSUE A: Plan Allocation & Credits Must Be Strict
Problem
- Inconsistent plan fallback logic in old code
- Some accounts created with 0 credits despite plan having credits
- Enterprise plan being auto-assigned (should never happen)
- Multiple fallback paths causing confusion
Strict Rules (NO EXCEPTIONS)
Rule A1: Free Trial Signup
# /signup route ALWAYS assigns:
plan_slug = "free-trial" # First choice
if not exists:
plan_slug = "free" # ONLY fallback
# NEVER assign: starter, growth, scale, enterprise automatically
Rule A2: Credit Seeding (MANDATORY)
# On account creation, ALWAYS:
account.credits = plan.get_effective_credits_per_month()
account.status = 'trial' # For free-trial/free plans
# Log transaction:
CreditTransaction.create(
account=account,
transaction_type='subscription',
amount=credits,
description='Initial credits from {plan.name}',
metadata={'registration': True, 'plan_slug': plan.slug}
)
Rule A3: Enterprise Plan Protection
# Enterprise plan (slug='enterprise') must NEVER be auto-assigned
# Only Developer/Admin can manually assign enterprise
# Check in serializer:
if plan.slug == 'enterprise' and not user.is_developer():
raise ValidationError("Enterprise plan requires manual assignment")
Rule A4: Paid Plan Assignment
# Paid plans (starter, growth, scale) can ONLY be assigned:
# 1. From /account/upgrade endpoint (inside app)
# 2. After payment confirmation
# NEVER during initial /signup
Implementation Location
- File:
backend/igny8_core/auth/serializers.py:276 - Changes: Already applied, but needs enterprise protection added
CRITICAL ISSUE B: Subscription Date Accuracy
Problem
- Trial accounts have missing or incorrect period dates
- Bank transfer activation doesn't set proper subscription periods
- No clear rule for date calculation
Strict Rules (ZERO AMBIGUITY)
Rule B1: Free Trial Signup
from django.utils import timezone
from datetime import timedelta
# Constants
TRIAL_DAYS = 14 # or 30, must be defined
# On registration:
now = timezone.now()
subscription = Subscription.objects.create(
account=account,
status='trialing',
payment_method='trial', # or None
current_period_start=now,
current_period_end=now + timedelta(days=TRIAL_DAYS),
cancel_at_period_end=False
)
account.status = 'trial'
Rule B2: Bank Transfer Activation
# When admin confirms payment:
now = timezone.now()
# For monthly plan:
if plan.billing_cycle == 'monthly':
period_end = now + timedelta(days=30)
elif plan.billing_cycle == 'annual':
period_end = now + timedelta(days=365)
subscription.payment_method = 'bank_transfer'
subscription.external_payment_id = payment_ref
subscription.status = 'active'
subscription.current_period_start = now
subscription.current_period_end = period_end
subscription.save()
account.status = 'active'
account.credits = plan.get_effective_credits_per_month()
account.save()
Rule B3: Subscription Renewal
# On renewal (manual or webhook):
previous_end = subscription.current_period_end
# Set new period (NO GAP, NO OVERLAP)
subscription.current_period_start = previous_end
if plan.billing_cycle == 'monthly':
subscription.current_period_end = previous_end + timedelta(days=30)
elif plan.billing_cycle == 'annual':
subscription.current_period_end = previous_end + timedelta(days=365)
# Reset credits
account.credits = plan.get_effective_credits_per_month()
account.save()
subscription.save()
Implementation Location
- File:
backend/igny8_core/business/billing/views.py(bank transfer endpoint) - File:
backend/igny8_core/auth/serializers.py:276(registration)
CRITICAL ISSUE C: Superuser Session Contamination
Problem
CRITICAL SECURITY ISSUE:
- New regular users sometimes logged in as superuser
- Frontend picks up admin/developer session from same browser
- Catastrophic for tenancy isolation
Root Cause
Session auth + JWT auth coexistence:
- Admin logs into Django admin → Session cookie created
- Regular user visits frontend → Browser sends session cookie
- Backend authenticates as admin instead of JWT user
- Frontend suddenly has superuser access
Strict Fix (MANDATORY)
Fix C1: Disable Session Auth for API Routes
File: backend/igny8_core/api/authentication.py
# ViewSets should ONLY use:
authentication_classes = [JWTAuthentication] # NO CSRFExemptSessionAuthentication
# Exception: Admin panel can use session
# But /api/* routes must be JWT-only
Fix C2: Middleware Superuser Detection
File: backend/igny8_core/auth/middleware.py:25
Add after account validation:
def process_request(self, request):
# ... existing code ...
# CRITICAL: Detect superuser on non-admin routes
if not request.path.startswith('/admin/'):
if hasattr(request, 'user') and request.user and request.user.is_superuser:
# Non-admin route but superuser authenticated
# This should ONLY happen for JWT with developer role
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if not auth_header.startswith('Bearer '):
# Superuser via session, not JWT - BLOCK IT
from django.contrib.auth import logout
logout(request)
return JsonResponse({
'success': False,
'error': 'Session authentication not allowed for API routes. Please use JWT.'
}, status=403)
Fix C3: Frontend Explicit Logout on Register
File: frontend/src/store/authStore.ts:120
Before registration:
register: async (registerData) => {
// Clear any existing sessions first
try {
await fetch(`${API_BASE_URL}/v1/auth/logout/`, {
method: 'POST',
credentials: 'include' // Clear session cookies
});
} catch (e) {
// Ignore errors, just ensure clean state
}
set({ loading: true });
// ... rest of registration ...
}
Fix C4: Frontend Clear All Auth on Logout
logout: () => {
// Clear cookies
document.cookie.split(";").forEach(c => {
document.cookie = c.trim().split("=")[0] + "=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/";
});
// Clear localStorage
localStorage.clear();
// Clear state
set({ user: null, token: null, refreshToken: null, isAuthenticated: false, loading: false });
},
Implementation Priority
🔥 CRITICAL - Fix before any production deployment
CRITICAL ISSUE D: Docker Build Cache Causing Router Errors
Problem
Symptoms:
useLocation() may be used only in the context of a <Router> componentuseNavigatesimilar errors- Errors appear in: Planner, Writer, Sites modules and subpages
- Resolved by removing containers and rebuilding WITHOUT code change
Root Cause
✅ Not a code issue - Docker build cache issue
- Stale node_modules cached in Docker layers
- Stale build artifacts from previous versions
- React Router hydration mismatch between cached and new code
Strict Fix
Fix D1: Frontend Dockerfile - No Build Cache
File: frontend/Dockerfile.dev
Ensure these lines:
# Copy package files
COPY package*.json ./
# Clean install (no cache)
RUN npm ci --only=production=false
# Remove any cached builds
RUN rm -rf dist/ .vite/ node_modules/.vite/
# Copy source
COPY . .
Fix D2: Docker Compose - No Volume Cache for node_modules
File: docker-compose.app.yml:77
Current:
volumes:
- /data/app/igny8/frontend:/app:rw
Change to:
volumes:
- /data/app/igny8/frontend:/app:rw
# Exclude node_modules from volume mount to prevent cache issues
- /app/node_modules
Fix D3: Build Script - Force Clean Build
File: frontend/rebuild.sh (create this)
#!/bin/bash
# Force clean frontend rebuild
echo "Removing old containers..."
docker rm -f igny8_frontend igny8_marketing_dev igny8_sites
echo "Removing old images..."
docker rmi -f igny8-frontend-dev:latest igny8-marketing-dev:latest igny8-sites-dev:latest
echo "Rebuilding without cache..."
cd /data/app/igny8/frontend
docker build --no-cache -t igny8-frontend-dev:latest -f Dockerfile.dev .
docker build --no-cache -t igny8-marketing-dev:latest -f Dockerfile.marketing.dev .
cd /data/app/igny8/sites
docker build --no-cache -t igny8-sites-dev:latest -f Dockerfile.dev .
echo "Restarting containers..."
cd /data/app/igny8
docker compose -f docker-compose.app.yml up -d igny8_frontend igny8_marketing_dev igny8_sites
echo "Done! Frontend rebuilt fresh."
Fix D4: Deployment Best Practice
# After git push, ALWAYS do:
docker compose -f docker-compose.app.yml down
docker compose -f docker-compose.app.yml build --no-cache
docker compose -f docker-compose.app.yml up -d
# This ensures no stale cache
Why This Fixes Router Errors
- Fresh node_modules every build
- No stale React Router components
- No hydration mismatches
- Clean build artifacts
Updated Implementation Plan with All Issues
Phase 0: Pre-Implementation Checklist ✅
- Analyze database state
- Document all relationships
- Identify all gaps
- Create free trial code changes
- Document all 4 critical issues
Phase 1: Free Trial Signup (Day 1)
Actions:
- ✅ Update RegisterSerializer (already done)
- ✅ Update SignUpForm (already done)
- ⏳ Create free-trial plan:
docker exec igny8_backend python manage.py create_free_trial_plan - ✅ Add enterprise plan protection
- ✅ Create Subscription with correct trial dates
- Test signup flow
Critical Constraints:
- ✅ Must assign free-trial or free ONLY
- ✅ Must seed credits from plan
- ✅ Must create Subscription with trial dates
- ✅ Must log CreditTransaction
Phase 2: Superuser Session Fix (Day 1 - CRITICAL)
Actions:
- Remove CSRFExemptSessionAuthentication from API ViewSets
- Add middleware superuser detection
- Add frontend logout before register
- Add frontend cookie clearing on logout
- Test: Regular user cannot access superuser session
Critical Constraints:
- 🔥 API routes must be JWT-only
- 🔥 Superuser on API route without JWT = logout
- 🔥 Registration clears old sessions first
Phase 3: Docker Build Cache Fix (Day 1 - CRITICAL)
Actions:
- Update frontend Dockerfile to use
npm ci - Add node_modules volume exclusion
- Create rebuild.sh script
- Document deployment procedure
- Test: Router errors don't occur after rebuild
Critical Constraints:
- 🔥 Always use
--no-cachefor frontend builds - 🔥 Exclude node_modules from volume mounts
- 🔥 Clean rebuild after every git deployment
Phase 4: Payment Method Fields (Day 2)
Actions:
- Create migration 0007
- Add payment_method to Account
- Add payment_method, external_payment_id to Subscription
- Make stripe_subscription_id nullable
- Add 'pending_payment' status
- Run migration
Phase 5: Subscription Date Accuracy (Day 2-3)
Actions:
- Update RegisterSerializer to create Subscription with trial dates
- Update bank transfer endpoint with strict date rules
- Add renewal logic with correct date transitions
- Test all date transitions
Critical Constraints:
- ✅ Trial: current_period_end = now + TRIAL_DAYS
- ✅ Activation: current_period_end = now + billing_cycle
- ✅ Renewal: current_period_start = previous_end (NO GAP)
Phase 6: Account Validation Helper (Day 3)
Actions:
- Create validate_account_and_plan() in auth/utils.py
- Update middleware to use helper
- Update API key authentication to use helper
- Test validation blocks suspended/cancelled accounts
Phase 7: Throttling Fix (Day 4)
Actions:
- Remove blanket authenticated bypass
- Add get_cache_key() for per-account throttling
- Test throttling enforces limits per account
Phase 8: Bank Transfer Endpoint (Day 4-5)
Actions:
- Create BillingViewSet
- Add confirm_bank_transfer endpoint
- Add URL routes
- Test payment confirmation flow
Phase 9: Comprehensive Tests (Day 6)
Actions:
- Test free trial signup
- Test credit seeding
- Test subscription dates
- Test superuser isolation
- Test API key validation
- Test throttling
- Test bank transfer
Phase 10: Documentation & Verification (Day 7)
Actions:
- Update all documentation
- Run full system test
- Verify all flows
- Deploy to production
Critical Constraints Summary
A. Plan & Credits (STRICT)
✅ free-trial → free (fallback) → ERROR (nothing else)
✅ Credits always seeded on registration
✅ CreditTransaction always logged
❌ Never auto-assign enterprise
❌ Never allow 0 credits after registration
B. Subscription Dates (PRECISE)
✅ Trial: start=now, end=now+14days
✅ Activation: start=now, end=now+billing_cycle
✅ Renewal: start=previous_end, end=start+billing_cycle
❌ No gaps between periods
❌ No overlapping periods
C. Superuser Isolation (SECURITY)
✅ API routes: JWT auth ONLY
✅ Superuser on /api/* without JWT → logout + error
✅ Registration clears existing sessions
✅ Logout clears all cookies and localStorage
❌ Never allow session auth for API
❌ Never allow superuser contamination
D. Docker Build (STABILITY)
✅ Use npm ci (not npm install)
✅ Exclude node_modules from volume mounts
✅ Always build with --no-cache after git push
✅ Removing containers + rebuild fixes router errors
❌ Don't cache build artifacts between deployments
Verification Matrix
Test 1: Free Trial Signup
# Prerequisites: free-trial plan exists, code deployed
# Action: Visit /signup, fill form, submit
# Expected:
# - Account created with status='trial'
# - Credits = 2000 (or plan.included_credits)
# - Subscription created with trial dates
# - CreditTransaction logged
# - Redirect to /sites
# - User can immediately use app
# Database check:
docker exec igny8_backend python manage.py shell -c "
from igny8_core.auth.models import User;
u = User.objects.latest('id');
assert u.account.status == 'trial';
assert u.account.credits > 0;
assert u.account.plan.slug in ['free-trial', 'free'];
print('✅ Free trial signup working')
"
Test 2: Superuser Isolation
# Prerequisites: Regular user account, admin logged into /admin
# Action: Login as regular user in frontend
# Expected:
# - User sees only their account
# - User does NOT have superuser privileges
# - API calls use JWT, not session
# Test:
# Inspect frontend network tab
# All API calls must have: Authorization: Bearer <jwt_token>
# No sessionid cookies sent to /api/*
Test 3: Docker Build Stability
# Action: Deploy code, rebuild containers
cd /data/app/igny8
docker compose -f docker-compose.app.yml down
docker build --no-cache -t igny8-frontend-dev:latest -f frontend/Dockerfile.dev frontend/
docker compose -f docker-compose.app.yml up -d
# Expected:
# - No useLocation errors
# - No useNavigate errors
# - Planner, Writer, Sites pages load correctly
# - Router context available everywhere
Test 4: Subscription Dates
# Action: Confirm bank transfer for trial account
curl -X POST /api/v1/billing/confirm-bank-transfer/ \
-H "Authorization: Bearer <admin_jwt>" \
-d '{
"account_id": 123,
"external_payment_id": "BT-001",
"amount": "29.99",
"payer_name": "Test User"
}'
# Expected:
# - subscription.status = 'active'
# - subscription.current_period_start = now
# - subscription.current_period_end = now + 30 days
# - account.status = 'active'
# - account.credits = plan monthly credits
Implementation Order (Revised)
Day 1 (CRITICAL)
- ✅ Free trial signup (code changes done, need to create plan)
- 🔥 Superuser session fix (MUST FIX)
- 🔥 Docker build cache fix (MUST FIX)
Day 2
- Payment method fields migration
- Subscription date accuracy updates
Day 3
- Account validation helper
- API key authentication fix
Day 4
- Throttling fix
- Bank transfer endpoint
Day 5-6
- Comprehensive tests
Day 7
- Documentation
- Deployment
- Verification
Rollback Plan (If Any Issue Occurs)
Database Rollback
docker exec igny8_backend python manage.py migrate igny8_core_auth 0006_soft_delete_and_retention
Code Rollback
git revert <commit_hash>
docker compose -f docker-compose.app.yml down
docker compose -f docker-compose.app.yml up -d
Emergency Disable Feature Flags
Add to settings.py:
# Emergency feature flags
TENANCY_ENABLE_FREE_TRIAL = False # Fall back to old signup
TENANCY_VALIDATE_API_KEY = False # Disable validation temporarily
TENANCY_STRICT_JWT_ONLY = False # Allow session auth temporarily
Success Criteria (ALL must pass)
- ✅ Signup creates account with correct credits
- ✅ Subscription has accurate start/end dates
- ✅ Regular users NEVER get superuser access
- ✅ Router errors don't appear after container rebuild
- ✅ API key validates account status
- ✅ Throttling enforces per-account limits
- ✅ Bank transfer confirmation works
- ✅ All tests passing (>80% coverage)
- ✅ Zero authentication bypasses
- ✅ Zero credit seeding failures
Files Reference
Analysis Documents (This Folder)
- CURRENT-STATE-CONTEXT.md - Database state from Docker query
- IMPLEMENTATION-SUMMARY.md - Context gathering summary
- FINAL-IMPLEMENTATION-REQUIREMENTS.md (this file) - Complete spec
- FINAL-IMPLEMENTATION-PLAN-COMPLETE.md - Detailed phase guide
- FREE-TRIAL-SIGNUP-FIX.md - Signup flow specifics
- COMPLETE-IMPLEMENTATION-PLAN.md - Original gap analysis
- Final_Flow_Tenancy.md - Target flow specifications
- Tenancy_Audit_Report.md - Audit findings
- audit_fixes.md - Previous recommendations
- tenancy-implementation-plan.md - Original plan
Code Changes Made (Review Before Deploy)
backend/igny8_core/auth/serializers.py- Free trial registrationfrontend/src/components/auth/SignUpForm.tsx- Simplified signupbackend/igny8_core/auth/management/commands/create_free_trial_plan.py- Plan creation
Code Changes Needed (Not Yet Made)
- Middleware - Superuser detection
- Authentication - Remove session auth from API
- Frontend authStore - Clear sessions before register
- Dockerfile - No-cache build
- docker-compose.app.yml - Exclude node_modules volume
- All Phase 4-10 changes from FINAL-IMPLEMENTATION-PLAN-COMPLETE.md
Hand-off Instructions
To implement this system:
- Review code changes in serializer and frontend
- Start with Day 1 critical fixes:
- Create free-trial plan
- Fix superuser session contamination
- Fix Docker build caching
- Then proceed through Phase 4-10
- Use
FINAL-IMPLEMENTATION-PLAN-COMPLETE.mdas step-by-step guide - Reference
CURRENT-STATE-CONTEXT.mdfor what exists in DB
All specifications are complete, accurate, and ready for implementation.