rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // Helper functions function isAuthenticated() { return request.auth != null; } function isOwner(userId) { return isAuthenticated() && request.auth.uid == userId; } // Users collection match /users/{userId} { // Users can read their own data allow read: if isOwner(userId); // Users can create their own user document allow create: if isOwner(userId); // Users can update their own data allow update: if isOwner(userId); // No deletes for now allow delete: if false; } // API Keys collection match /apiKeys/{keyId} { // Only the server can create/read API keys (via Admin SDK) // Users cannot directly access API key documents allow read, write: if false; } // MCP API Keys collection match /mcpKeys/{keyId} { // Only the server can create/read/delete MCP keys (via Admin SDK) // Users cannot directly access MCP key documents allow read, write: if false; } // Projects collection match /projects/{projectId} { // Users can read their own projects allow read: if isAuthenticated() && resource.data.userId == request.auth.uid; // Users can create projects allow create: if isAuthenticated() && request.resource.data.userId == request.auth.uid; // Users can update their own projects allow update: if isAuthenticated() && resource.data.userId == request.auth.uid; // Users can delete their own projects allow delete: if isAuthenticated() && resource.data.userId == request.auth.uid; // AI Conversations subcollection match /aiConversations/{conversationId} { // Users can read conversations for their projects allow read: if isAuthenticated() && get(/databases/$(database)/documents/projects/$(projectId)).data.userId == request.auth.uid; // Server creates conversation entries via Admin SDK allow create: if false; // Only server via Admin SDK // No updates to conversation history (immutable) allow update: if false; // No deletes (audit trail) allow delete: if false; } // Vision Board subcollection match /visionBoard/{visionDocId} { // Users can read/write vision board for their projects allow read, write: if isAuthenticated() && get(/databases/$(database)/documents/projects/$(projectId)).data.userId == request.auth.uid; } // Context Sources subcollection (for chat content, files, etc.) match /contextSources/{sourceId} { // Users can read/write context sources for their projects allow read, write: if isAuthenticated() && get(/databases/$(database)/documents/projects/$(projectId)).data.userId == request.auth.uid; } } // Sessions collection match /sessions/{sessionId} { // Users can read their own sessions (by userId or by projectId they own) allow read: if isAuthenticated() && ( resource.data.userId == request.auth.uid || (resource.data.projectId != null && get(/databases/$(database)/documents/projects/$(resource.data.projectId)).data.userId == request.auth.uid) ); // Sessions are created by the server via API (Admin SDK) allow create: if false; // Only server via Admin SDK // Users can update their own sessions allow update: if isAuthenticated() && resource.data.userId == request.auth.uid; // No deletes for sessions (audit trail) allow delete: if false; } // Analyses collection match /analyses/{analysisId} { // Users can read analyses for their projects // Note: This requires fetching the project document to verify ownership allow read: if isAuthenticated() && get(/databases/$(database)/documents/projects/$(resource.data.projectId)).data.userId == request.auth.uid; // Users can create analyses for their projects allow create: if isAuthenticated() && get(/databases/$(database)/documents/projects/$(request.resource.data.projectId)).data.userId == request.auth.uid; // Users can update analyses for their projects allow update: if isAuthenticated() && get(/databases/$(database)/documents/projects/$(resource.data.projectId)).data.userId == request.auth.uid; // No deletes for analyses (audit trail) allow delete: if false; } // Work Completed collection match /workCompleted/{workId} { // Users can read work completed for their projects allow read: if isAuthenticated() && get(/databases/$(database)/documents/projects/$(resource.data.projectId)).data.userId == request.auth.uid; // Server creates work completed entries allow create: if false; // Only server via Admin SDK // Users can update work completed for their projects allow update: if isAuthenticated() && get(/databases/$(database)/documents/projects/$(resource.data.projectId)).data.userId == request.auth.uid; // No deletes allow delete: if false; } // Clients collection match /clients/{clientId} { // Users can read their own clients allow read: if isAuthenticated() && resource.data.ownerId == request.auth.uid; // Users can create clients allow create: if isAuthenticated() && request.resource.data.ownerId == request.auth.uid; // Users can update their own clients allow update: if isAuthenticated() && resource.data.ownerId == request.auth.uid; // Users can delete their own clients allow delete: if isAuthenticated() && resource.data.ownerId == request.auth.uid; } // ChatGPT Imports collection match /chatgptImports/{importId} { // Users can read their own imports allow read: if isAuthenticated() && resource.data.userId == request.auth.uid; // Server creates imports via Admin SDK allow create: if false; // Only server via Admin SDK // Users can update their own imports (e.g., add notes) allow update: if isAuthenticated() && resource.data.userId == request.auth.uid; // Users can delete their own imports allow delete: if isAuthenticated() && resource.data.userId == request.auth.uid; } // User API Keys collection (third-party keys like OpenAI, GitHub) match /userKeys/{keyId} { // Only server can access keys (via Admin SDK) // Keys are encrypted and should never be directly accessible to clients allow read, write: if false; } // Knowledge Items collection (documents, notes, chat imports) match /knowledge_items/{itemId} { // Users can read knowledge items for their projects allow read: if isAuthenticated() && get(/databases/$(database)/documents/projects/$(resource.data.projectId)).data.userId == request.auth.uid; // Server creates knowledge items via Admin SDK allow create: if false; // Only server via Admin SDK // No updates or deletes (immutable) allow update, delete: if false; } // Chat Extractions collection (AI-extracted insights) match /chat_extractions/{extractionId} { // Users can read extractions for their projects allow read: if isAuthenticated() && get(/databases/$(database)/documents/projects/$(resource.data.projectId)).data.userId == request.auth.uid; // Server creates extractions via Admin SDK allow create: if false; // Only server via Admin SDK // No updates or deletes (immutable) allow update, delete: if false; } // Chat Conversations collection (conversation history) match /chat_conversations/{conversationId} { // Users can read conversations for their projects allow read: if isAuthenticated() && get(/databases/$(database)/documents/projects/$(resource.data.projectId)).data.userId == request.auth.uid; // Server creates and updates conversations via Admin SDK allow create, update: if false; // Only server via Admin SDK // No deletes (audit trail) allow delete: if false; } // GitHub Connections collection (OAuth tokens and profile) match /githubConnections/{connectionId} { // Users can read their own GitHub connections allow read: if isAuthenticated() && resource.data.userId == request.auth.uid; // Server creates connections via OAuth callback allow create: if false; // Only server via Admin SDK // Users cannot update or delete (managed by server) allow update, delete: if false; } // Linked Extensions collection (browser extension connections) match /linkedExtensions/{linkId} { // Users can read their own extension links allow read: if isAuthenticated() && resource.data.userId == request.auth.uid; // Server creates links via API allow create: if false; // Only server via Admin SDK // No updates or deletes allow update, delete: if false; } // Default deny all other access match /{document=**} { allow read, write: if false; } } }