Skip to main content
Firebase Security Rules control access to your Firestore database and Firebase Storage. This guide provides comprehensive security rules for StockPro’s collections and best practices for securing your inventory system.
Critical: Deploy security rules before going to production!Without proper security rules, anyone can read, modify, or delete your entire database. This is a critical security vulnerability that must be addressed.

Understanding Firestore Security Rules

Firestore Security Rules use a declarative syntax to control access:
service cloud.firestore {
  match /databases/{database}/documents {
    // Rules go here
  }
}

Key Concepts

  • Rules are NOT filters: If a user can’t read a document, querying for it will fail entirely
  • Rules cascade: Child documents don’t inherit parent rules
  • Authentication required: Most rules should verify request.auth != null
  • Read operations: Split into get (single doc) and list (queries)
  • Write operations: Split into create, update, and delete

StockPro Data Model

Based on the source code analysis, StockPro uses these Firestore collections:
CollectionPurposeCreated In
usuariosUser profiles and rolesagregar_empleado.js:39
productosInventory itemsTo be implemented
ventasSales transactionsTo be implemented
reportesSales reportsTo be implemented

Production Security Rules

Here’s a complete, production-ready security rules configuration for StockPro:
firestore.rules
rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    
    // Helper function: Check if user is authenticated
    function isAuthenticated() {
      return request.auth != null;
    }
    
    // Helper function: Get user document
    function getUserData() {
      return get(/databases/$(database)/documents/usuarios/$(request.auth.uid)).data;
    }
    
    // Helper function: Check if user is admin
    function isAdmin() {
      return isAuthenticated() && getUserData().tipo == 'admin';
    }
    
    // Helper function: Check if user is admin or manager
    function isAdminOrManager() {
      return isAuthenticated() && 
        (getUserData().tipo == 'admin' || getUserData().tipo == 'gerente');
    }
    
    // Helper function: Check if user owns the document
    function isOwner(userId) {
      return isAuthenticated() && request.auth.uid == userId;
    }
    
    // Usuario Collection Rules
    match /usuarios/{userId} {
      // Users can read their own profile
      allow get: if isAuthenticated() && 
        (request.auth.uid == userId || isAdminOrManager());
      
      // Admin and managers can list all users
      allow list: if isAdminOrManager();
      
      // Only admins can create new users
      allow create: if isAdmin() && 
        request.resource.data.keys().hasAll(['uid', 'name', 'email', 'tipo']) &&
        request.resource.data.uid is string &&
        request.resource.data.name is string &&
        request.resource.data.email is string &&
        request.resource.data.tipo in ['admin', 'gerente', 'empleado'];
      
      // Users can update their own profile (except tipo field)
      // Admins can update any profile
      allow update: if isAuthenticated() && (
        (request.auth.uid == userId && 
         !request.resource.data.diff(resource.data).affectedKeys().hasAny(['tipo', 'uid'])) ||
        isAdmin()
      );
      
      // Only admins can delete users
      allow delete: if isAdmin();
    }
    
    // Productos Collection Rules
    match /productos/{productId} {
      // All authenticated users can read products
      allow get, list: if isAuthenticated();
      
      // Admin and managers can create products
      allow create: if isAdminOrManager() &&
        request.resource.data.keys().hasAll(['nombre', 'precio', 'cantidad', 'categoria']) &&
        request.resource.data.nombre is string &&
        request.resource.data.precio is number &&
        request.resource.data.precio > 0 &&
        request.resource.data.cantidad is number &&
        request.resource.data.cantidad >= 0 &&
        request.resource.data.categoria is string;
      
      // Admin and managers can update products
      allow update: if isAdminOrManager() &&
        request.resource.data.precio > 0 &&
        request.resource.data.cantidad >= 0;
      
      // Only admins can delete products
      allow delete: if isAdmin();
    }
    
    // Ventas Collection Rules
    match /ventas/{ventaId} {
      // All authenticated users can read sales
      allow get, list: if isAuthenticated();
      
      // All authenticated users can create sales
      allow create: if isAuthenticated() &&
        request.resource.data.keys().hasAll(['productos', 'total', 'fecha', 'usuario']) &&
        request.resource.data.productos is list &&
        request.resource.data.total is number &&
        request.resource.data.total > 0 &&
        request.resource.data.fecha is timestamp &&
        request.resource.data.usuario == request.auth.uid;
      
      // Sales cannot be updated (immutable for audit trail)
      allow update: if false;
      
      // Only admins can delete sales (for corrections)
      allow delete: if isAdmin();
    }
    
    // Reportes Collection Rules
    match /reportes/{reporteId} {
      // Admin and managers can read reports
      allow get, list: if isAdminOrManager();
      
      // System-generated reports (usually via Cloud Functions)
      // Admin and managers can create manual reports
      allow create: if isAdminOrManager();
      
      // Reports cannot be updated (immutable for audit trail)
      allow update: if false;
      
      // Only admins can delete reports
      allow delete: if isAdmin();
    }
    
    // Deny all other collections by default
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

Deploy Security Rules

Method 1: Firebase Console

1

Navigate to Firestore Rules

Firebase Console → Firestore Database → Rules tab
2

Paste Rules

Copy the security rules above and paste into the editor
3

Publish Rules

Click Publish to deploy the rules

Method 2: Firebase CLI

1

Create Rules File

Create firestore.rules in your project root:
cd ~/workspace/source
touch firestore.rules
Paste the security rules into this file.
2

Initialize Firebase

If not already initialized:
firebase init firestore
Select your Firebase project and accept default file names.
3

Deploy Rules

firebase deploy --only firestore:rules
Rule changes take effect immediately after deployment. Test thoroughly in a development environment first!

User Role Management

StockPro implements a three-tier role system:

Role Definitions

// Full system access
- Create, update, delete users
- Create, update, delete products
- Create, delete sales
- Full access to reports
- Manage security settings

Assigning Roles

Roles are assigned in the usuarios collection when creating users:
public/js/agregar_empleado.js
const usuario = {
  uid: user.uid,
  name: name,
  email: email,
  tipo: "empleado" // or "gerente" or "admin"
};

await addDoc(collection(db, "usuarios"), usuario);
The first admin user must be created manually:
  1. Create user in Firebase Console → Authentication
  2. Add document in Firestore → usuarios collection
  3. Set tipo: "admin"

Testing Security Rules

Firebase Emulator Suite

Test rules locally before deploying:
# Install Firebase emulators
firebase init emulators

# Select Firestore and Authentication
# Accept default ports

# Start emulators
firebase emulators:start
Update public/js/firebase.js for local testing:
import { connectFirestoreEmulator } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-firestore.js";
import { connectAuthEmulator } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-auth.js";

if (location.hostname === 'localhost') {
  connectFirestoreEmulator(db, 'localhost', 8080);
  connectAuthEmulator(auth, 'http://localhost:9099');
}

Rules Playground

Test rules in Firebase Console:
1

Navigate to Rules Playground

Firebase Console → Firestore Database → Rules → Rules Playground
2

Configure Test

  • Location: Select collection path (e.g., /usuarios/user123)
  • Operation: Choose read/write operation
  • Authentication: Simulate user authentication
3

Run Simulation

Click Run to test if the operation is allowed or denied

Unit Tests

Write automated tests for security rules:
firestore.test.js
import { initializeTestEnvironment } from '@firebase/rules-unit-testing';

let testEnv;

beforeAll(async () => {
  testEnv = await initializeTestEnvironment({
    projectId: 'stockpro-test',
    firestore: {
      rules: fs.readFileSync('firestore.rules', 'utf8'),
    },
  });
});

afterAll(async () => {
  await testEnv.cleanup();
});

test('authenticated users can read their own profile', async () => {
  const alice = testEnv.authenticatedContext('alice');
  const db = alice.firestore();
  
  await testEnv.withSecurityRulesDisabled(async (context) => {
    await context.firestore()
      .collection('usuarios')
      .doc('alice')
      .set({ uid: 'alice', name: 'Alice', tipo: 'empleado' });
  });
  
  await assertSucceeds(db.collection('usuarios').doc('alice').get());
});

test('users cannot read other user profiles', async () => {
  const alice = testEnv.authenticatedContext('alice');
  const db = alice.firestore();
  
  await assertFails(db.collection('usuarios').doc('bob').get());
});

Best Practices

1. Principle of Least Privilege

Grant the minimum permissions necessary:
// Bad: Too permissive
allow read, write: if isAuthenticated();

// Good: Specific permissions
allow get: if isOwner(userId);
allow update: if isOwner(userId) && validUpdate();
allow delete: if isAdmin();

2. Validate Data Structure

Always validate incoming data:
allow create: if isAuthenticated() &&
  // Require specific fields
  request.resource.data.keys().hasAll(['name', 'email']) &&
  // Validate data types
  request.resource.data.name is string &&
  request.resource.data.email is string &&
  // Validate field values
  request.resource.data.name.size() > 0 &&
  request.resource.data.email.matches('.*@.*');

3. Protect Sensitive Fields

Prevent users from escalating privileges:
allow update: if isAuthenticated() && 
  request.auth.uid == userId &&
  // Prevent changing role field
  !request.resource.data.diff(resource.data).affectedKeys().hasAny(['tipo', 'uid']);

4. Make Audit Trails Immutable

Prevent tampering with transaction records:
match /ventas/{ventaId} {
  allow create: if isAuthenticated();
  allow update: if false; // Sales cannot be modified
  allow delete: if isAdmin(); // Only admins can delete for corrections
}

5. Use Helper Functions

Create reusable functions for complex logic:
function isAdmin() {
  return isAuthenticated() && getUserData().tipo == 'admin';
}

function validPrice() {
  return request.resource.data.precio is number &&
         request.resource.data.precio > 0;
}

function validInventory() {
  return request.resource.data.cantidad is number &&
         request.resource.data.cantidad >= 0;
}

allow create: if isAdmin() && validPrice() && validInventory();

Common Security Patterns

Owner-Only Access

match /pedidos/{pedidoId} {
  allow read: if request.auth.uid == resource.data.userId;
  allow create: if request.auth.uid == request.resource.data.userId;
}

Role-Based Access

match /configuracion/{docId} {
  allow read: if isAuthenticated();
  allow write: if isAdmin();
}

Time-Based Access

match /promociones/{promoId} {
  allow read: if request.time < resource.data.expiration;
}

Subcollection Access

match /usuarios/{userId}/notificaciones/{notifId} {
  allow read: if request.auth.uid == userId;
  allow create: if isAdmin() || request.auth.uid == userId;
}

Monitoring and Auditing

Enable Firestore Audit Logs

1

Navigate to IAM & Admin

Google Cloud Console → IAM & Admin → Audit Logs
2

Enable Firestore Logs

Find Cloud Firestore API and enable:
  • Admin Read
  • Data Read
  • Data Write
3

View Logs

Navigate to Logging → Logs Explorer to view access logs

Monitor Rule Violations

Set up alerts for denied requests:
# View denied requests in Firebase Console
Firebase Console Firestore Usage tab Security rules evaluations

Troubleshooting

”Missing or insufficient permissions”

Cause: Security rules are denying the operation Debug steps:
  1. Check if user is authenticated: request.auth != null
  2. Verify user role in usuarios collection
  3. Use Rules Playground to simulate the exact operation
  4. Check rule logs for detailed evaluation trace

Rules Work in Emulator but Fail in Production

Cause: Rules not deployed or different rule versions Solution:
firebase deploy --only firestore:rules

“get() is not defined” Error

Cause: Helper function trying to read from Firestore during rule evaluation Solution: Ensure get() calls are properly structured:
function getUserData() {
  return get(/databases/$(database)/documents/usuarios/$(request.auth.uid)).data;
}
Each rule evaluation can perform up to 10 get() calls. Design rules to minimize database reads.

Next Steps

Firebase Setup

Configure Firebase project and credentials

Deployment

Deploy StockPro to production

Build docs developers (and LLMs) love