Files
QuantScan-Pro-/firestore.rules
T
desartstudio95 3fe6dd4671 feat: Add Firebase security rules testing and error handling
Introduces unit tests for Firestore security rules and adds error handling to data fetching hooks. This enhances the robustness and security of the application by validating access control logic and providing feedback on data retrieval failures.

Includes:
- Setup for Firestore unit testing.
- Basic tests for signal query and user collection access.
- Error logging for `testimonials` and `notifications` data fetching.
- Updates to `firestore.rules` for stricter access checks.
- Adds necessary development dependencies for testing.
2026-05-06 15:07:22 +02:00

123 lines
4.6 KiB
Plaintext

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 isRootAdmin() || isDbAdmin();
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']
&& (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, list: if isSignedIn() && (resource.data.userId == request.auth.uid || 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() <= 20
&& data.timeframe is string && data.timeframe.size() <= 10
&& 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, 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();
}
}
}