# Territory Managers This document explains the Territory Manager system implemented in Black Canyon Tickets React Rebuild. This system provides role-based access control that restricts users to specific geographic or organizational territories. ## Overview The Territory Manager system introduces a new user role with limited access permissions based on assigned territories. This enables organizations to segment their operations geographically or by business unit while maintaining centralized management. ## User Roles ### Role Hierarchy 1. **Super Admin** - Platform-wide access across all organizations 2. **Org Admin** - Full access within their organization, can assign territories 3. **Territory Manager** - Limited to assigned territories within their organization 4. **Staff** - Organization-wide read access (can be narrowed in future) ### Territory Manager Capabilities - View and manage events only in assigned territories - Create new events (must assign to accessible territory) - View tickets and customers for accessible events - Cannot modify events outside their territories - Cannot assign territories to other users ## Architecture ### Data Model #### Firestore Collections ```typescript // territories/{territoryId} interface Territory { id: string; orgId: string; name: string; // "West Northwest" code: string; // "WNW" description?: string; } // events/{eventId} interface Event { // ... existing fields organizationId: string; territoryId: string; // Required field } // ticket_types/{ticketTypeId} interface TicketType { // ... existing fields territoryId: string; // Inherited from event } // tickets/{ticketId} interface Ticket { // ... existing fields territoryId: string; // Inherited from event } // users/{uid} - Mirror of custom claims for UI interface User { orgId: string; role: 'superadmin' | 'orgAdmin' | 'territoryManager' | 'staff'; territoryIds: string[]; } ``` #### Firebase Custom Claims ```typescript interface CustomClaims { orgId: string; role: 'superadmin' | 'orgAdmin' | 'territoryManager' | 'staff'; territoryIds: string[]; // Empty array for full access roles } ``` ### Security Implementation #### Firestore Security Rules Access is controlled at the database level using custom claims: ```javascript // Events collection allow read: if canReadTerritory(resource.data.orgId, resource.data.territoryId); allow write: if territoryOK(request.resource.data.orgId, request.resource.data.territoryId); function territoryOK(resOrgId, resTerritoryId) { return inOrg(resOrgId) && ( request.auth.token.role in ['superadmin', 'orgAdmin'] || (request.auth.token.role == 'territoryManager' && (resTerritoryId in request.auth.token.territoryIds)) ); } ``` #### API Authorization Cloud Functions validate claims before processing requests: ```typescript // functions/src/claims.ts function canManageClaims(user: AuthorizedUser, targetOrgId: string): boolean { if (user.role === 'superadmin') return true; if (user.role === 'orgAdmin' && user.orgId === targetOrgId) return true; return false; } ``` ## Frontend Implementation ### Components #### TerritoryFilter Role-based filtering component: ```typescript // Territory managers: fixed to assigned territories // Admins: multi-select all org territories // Persists selection in URL params and localStorage ``` #### UserTerritoryManager Admin interface for assigning territories: ```typescript // Only visible to superadmin and orgAdmin // Updates Firebase custom claims // Provides visual feedback for claim changes ``` #### Event Creation Territory selection is mandatory: ```typescript // EventDetailsStep includes territory dropdown // Auto-selects for territory managers with single territory // Validates territory access before save ``` ### Hooks #### useClaims Access Firebase custom claims: ```typescript const { claims, loading, error, refreshClaims } = useClaims(); // claims.orgId, claims.role, claims.territoryIds ``` #### useTerritoryEvents Territory-filtered event access: ```typescript const { events, // Filtered by territory access canAccessEvent, // Check event permissions canModifyEvent, // Check edit permissions createEvent // Validates territory on create } = useTerritoryEvents(); ``` #### useTerritoryFilter Filter state management: ```typescript const { selectedTerritoryIds, isActive, canModifySelection, // False for territory managers setSelectedTerritories } = useTerritoryFilter(); ``` ## Usage Guide ### For Administrators #### Assigning Territories 1. Navigate to Admin panel 2. Use **UserTerritoryManager** component 3. Select user and role 4. Choose territories (required for territory managers) 5. Save - user must re-login to see changes #### Creating Territories ```typescript // Add to MOCK_TERRITORIES for development // In production, create via admin interface const territory = { id: 'territory_004', orgId: 'org_001', name: 'Southwest Region', code: 'SW', description: 'Arizona, Nevada operations' }; ``` ### For Territory Managers #### Event Management - Events list automatically filtered to assigned territories - Create events by selecting accessible territory - Edit/delete only events in assigned territories - Territory filter is read-only #### Dashboard Views - Revenue and analytics scoped to accessible territories - Customer data limited to accessible events - Reporting reflects territorial scope ### For Developers #### Testing Territory Access Run comprehensive test suite: ```bash npm run test:territory # Territory-specific tests npx playwright test tests/territory-access.spec.ts ``` #### Adding New Territory-Scoped Features 1. Update data models to include `territoryId` 2. Apply filtering in query hooks 3. Add territory validation to mutations 4. Update Firestore security rules 5. Add tests for access control ## API Reference ### Cloud Functions #### Update User Claims ```http POST /api/admin/users/:uid/claims Authorization: Bearer Content-Type: application/json { "orgId": "org_001", "role": "territoryManager", "territoryIds": ["territory_001", "territory_002"] } ``` #### Get User Claims (Debug) ```http GET /api/admin/users/:uid/claims Authorization: Bearer ``` ### Frontend API #### Territory Filtering ```typescript // Apply territory filter to queries const events = useTerritoryEvents(); const filteredEvents = events.getFilteredEvents(); // Check specific access const canAccess = events.canAccessEvent(event); const canModify = events.canModifyEvent(event); ``` #### Claims Management ```typescript // Access current user claims const { claims } = useClaims(); if (claims?.role === 'territoryManager') { // Territory manager specific logic } // Refresh claims after admin changes await refreshClaims(); ``` ## Security Considerations ### Custom Claims Best Practices - Claims are authoritative - UI mirrors but never overrides - Claims update immediately in security rules - UI requires re-login to reflect claim changes - Validate claims in all API endpoints ### Access Control Validation - Database rules enforce access at data layer - Frontend hooks provide optimistic filtering - API endpoints validate claims before operations - Test both UI and database rule enforcement ### Territory Assignment Security - Only superadmin/orgAdmin can assign territories - Territory managers cannot escalate privileges - Cross-organization access strictly prohibited - Audit trail maintained in users collection ## Troubleshooting ### Common Issues #### User Claims Not Updating - Claims update immediately in Firestore security rules - UI updates require user to re-login - Check ID token refresh in browser dev tools - Verify Cloud Function deployment #### Territory Filter Not Working - Check URL parameters: `?territories=territory_001,territory_002` - Verify localStorage: `territory-filter-${orgId}` - Ensure user has access to selected territories - Check browser console for access errors #### Events Not Visible - Verify event has correct `territoryId` - Check user's assigned territories in claims - Confirm organization ID matches - Test with admin account for comparison ### Debug Commands ```typescript // Check current claims (browser console) firebase.auth().currentUser?.getIdTokenResult() .then(result => console.log(result.claims)); // Verify territory access const { claims } = useClaims(); const { accessibleTerritoryIds } = useAccessibleTerritories(); console.log({ claims, accessibleTerritoryIds }); ``` ## Future Enhancements ### Planned Features - Dynamic territory creation via UI - Territory-based email notifications - Advanced reporting with territory breakdowns - Bulk territory assignment tools - Territory hierarchy (regions > territories) ### Possible Extensions - Time-based territory access - Territory sharing between users - Territory-specific branding - Integration with external mapping systems - Mobile app territory awareness ## Related Documentation - [Firebase Custom Claims Documentation](https://firebase.google.com/docs/auth/admin/custom-claims) - [Firestore Security Rules Guide](https://firebase.google.com/docs/firestore/security/get-started) - [CLAUDE.md](./CLAUDE.md) - Project overview and development guide - [REBUILD_PLAN.md](./REBUILD_PLAN.md) - Current project status