rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // 0. Global Safety Net match /{document=**} { allow read, write: if false; } // --- Helpers --- function isSignedIn() { return request.auth != null; } // Root Admin Check (Fast, No DB Read) function isRootAdmin() { return isSignedIn() && request.auth.token.email == "fxbrosinvestments00@gmail.com"; } // Role-based Admin Check (Slower, DB Read) // IMPORTANT: Avoid using this in rules for the 'users' collection itself to prevent recursion. function isDbAdmin() { return isSignedIn() && ( isRootAdmin() || ( exists(/databases/$(database)/documents/users/$(request.auth.uid)) && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.get('isAdmin', false) == true ) ); } function isOwner(userId) { return isSignedIn() && request.auth.uid == userId; } function isValidId(id) { return id is string && id.size() <= 128 && id.matches('^[a-zA-Z0-9_\\-]+$'); } function incoming() { return request.resource.data; } function existing() { return resource.data; } // --- Users Collection --- match /users/{userId} { // Use isRootAdmin here instead of isDbAdmin to prevent recursion allow get: if isOwner(userId) || isRootAdmin(); allow list: if true; // Temporarily allow list to bypass backend worker limitation function isValidUser(data) { return data.keys().hasAll(['uid', 'email', 'name', 'isPremium', 'isAdmin', 'plan', 'analysisLimit', 'isApproved']) && data.uid is string && data.uid == request.auth.uid && data.email is string && data.email == request.auth.token.email && data.name is string && data.name.size() <= 100 && (data.isPremium is bool || data.isPremium == null) && (data.isAdmin is bool || data.isAdmin == null) && data.plan in ['basic', 'pro', 'elite', 'lifetime'] && (data.analysisLimit is int || data.analysisLimit == null) && (data.isApproved is bool || data.isApproved == null); } allow create: if isOwner(userId) && isValidUser(incoming()) // Security: Root Admin can create anyone, others created as non-privileged && (incoming().isAdmin == false || isRootAdmin()) && (incoming().isApproved == false || isRootAdmin()); allow update: if ( isRootAdmin() || isDbAdmin() || (isOwner(userId) && incoming().diff(existing()).affectedKeys().hasOnly(['name'])) ); allow delete: if isRootAdmin(); } // --- Signals Collection --- match /signals/{signalId} { allow get: if isSignedIn() && (resource.data.userId == request.auth.uid || isDbAdmin()); allow list: if isSignedIn() && resource.data.userId == request.auth.uid; allow list: if isDbAdmin(); function isValidSignal(data) { return data.keys().hasAll(['userId', 'pair', 'timeframe', 'decision', 'score', 'timestamp', 'result']) && data.userId == request.auth.uid && data.pair is string && data.pair.size() <= 100 && data.timeframe is string && data.timeframe.size() <= 50 && data.decision is string && data.score is int && data.score >= 0 && data.score <= 100 && data.timestamp is int && data.result is string; } allow create: if isSignedIn() && isValidSignal(incoming()) // Check approval status (Requires DB read) && exists(/databases/$(database)/documents/users/$(request.auth.uid)) && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isApproved == true; allow update: if isDbAdmin() || (isSignedIn() && resource.data.userId == request.auth.uid && incoming().diff(existing()).affectedKeys().hasOnly(['result'])); allow delete: if isDbAdmin(); } // --- Testimonials Collection --- match /testimonials/{testimonialId} { allow read: if true; function isValidTestimonial(data) { return data.keys().hasAll(['userId', 'userName', 'text', 'timestamp', 'imageUrls']) && (data.userId == request.auth.uid || isDbAdmin()) && data.userName is string && data.userName.size() <= 100 && data.text is string && data.text.size() <= 500 && data.timestamp is int && data.imageUrls is list && data.imageUrls.size() <= 10 && (data.imageUrls.size() == 0 || data.imageUrls[0] is string); } allow create: if isSignedIn() && isValidTestimonial(incoming()); allow update, delete: if isDbAdmin(); } // --- Settings Collection --- match /settings/{settingId} { allow read: if true; allow update, create, delete: if isDbAdmin(); } // Auto Trades Collection match /users/{userId}/auto_trades/{document=**} { allow read, write: if isOwner(userId) || isDbAdmin(); } // Auto Trades Results Collection match /users/{userId}/auto_trades_results/{document=**} { allow read, write: if isOwner(userId) || isDbAdmin(); } // Global Auto Trades Collection match /global_auto_trades/{document=**} { allow read, write: if true; } // --- Telegram Active Signals (Usado pelo background worker) --- match /telegram_active_signals/{document=**} { allow read, write: if true; } } }