ByteBrief / firestore.rules
Gigishot's picture
Upload 15 files
afacd41 verified
Raw
History Blame Contribute Delete
7 kB
/**
* Core Philosophy: This ruleset enforces a strict user-ownership model. All data,
* including documents and their generated summaries, is organized hierarchically
* within a user's private data tree. There is no public or shared data access;
* users can only access content they explicitly own.
*
* Data Structure: Data is structured hierarchically under a top-level `users`
* collection: /users/{userId}/documents/{documentId}/summaries/{summaryId}. This
* path-based ownership ensures that queries are naturally scoped to the
* authenticated user's data.
*
* Key Security Decisions:
* - Strict Ownership: All access is gated by checking if the authenticated
* user's UID matches the {userId} in the path.
* - No User Listing: Listing the top-level `/users` collection is disallowed to
* protect user privacy and prevent enumeration attacks.
* - Default Deny: Any operation not explicitly granted is denied.
* - Authorization Independence: Documents and Summaries contain a denormalized
* `userId` field. This allows rules to verify ownership directly from the
* document being accessed, avoiding slow and costly `get()` calls to parent
* documents and ensuring rules are performant and scalable.
*/
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Helper Functions
// --------------------------------
/**
* Verifies that a user is signed into the application.
*/
function isSignedIn() {
return request.auth != null;
}
/**
* Verifies that the currently signed-in user's UID matches the given userId.
* This is the primary function for enforcing user-ownership.
*/
function isOwner(userId) {
return isSignedIn() && request.auth.uid == userId;
}
/**
* Verifies ownership and ensures the document already exists.
* CRITICAL: Use for all update and delete operations to prevent modifying
* or deleting non-existent data.
*/
function isExistingOwner(userId) {
return isOwner(userId) && resource != null;
}
/**
* Validates required relational fields when creating a new User profile document.
* Ensures the document's internal `id` matches the document's path ID (`userId`).
*/
function hasValidUserCreateData(userId) {
let data = request.resource.data;
return data.id == userId;
}
/**
* Ensures critical relational fields are immutable on User profile updates.
* The document's internal `id` cannot be changed after creation.
*/
function hasValidUserUpdateData() {
let incomingData = request.resource.data;
let existingData = resource.data;
return incomingData.id == existingData.id;
}
/**
* Validates required relational fields when creating a new Document.
* Ensures the denormalized `userId` field correctly points to the owner.
*/
function hasValidDocumentCreateData(userId) {
let data = request.resource.data;
return data.userId == userId;
}
/**
* Ensures critical relational fields are immutable on Document updates.
* The `userId` linking the document to its owner cannot be changed.
*/
function hasValidDocumentUpdateData() {
let incomingData = request.resource.data;
let existingData = resource.data;
return incomingData.userId == existingData.userId;
}
/**
* Validates required relational fields when creating a new Summary.
* Ensures denormalized `userId` and `documentId` are consistent with the path.
*/
function hasValidSummaryCreateData(userId, documentId) {
let data = request.resource.data;
return data.userId == userId
&& data.documentId == documentId;
}
/**
* Ensures critical relational fields are immutable on Summary updates.
* The `userId` and `documentId` cannot be changed after creation.
*/
function hasValidSummaryUpdateData() {
let incomingData = request.resource.data;
let existingData = resource.data;
return incomingData.userId == existingData.userId
&& incomingData.documentId == existingData.documentId;
}
// Collection Rules
// --------------------------------
/**
* @description Manages user profile documents. Only the authenticated owner of the
* profile can create, read, update, or delete their own data.
* @path /users/{userId}
* @allow (get) An authenticated user with UID 'user_abc' reads their own profile at /users/user_abc.
* @deny (get) An authenticated user with UID 'user_xyz' tries to read /users/user_abc.
* @principle Restricts access to a user's own data tree (Self-Creation & Ownership).
*/
match /users/{userId} {
allow get: if isOwner(userId);
allow list: if false;
allow create: if isOwner(userId) && hasValidUserCreateData(userId);
allow update: if isExistingOwner(userId) && hasValidUserUpdateData();
allow delete: if isExistingOwner(userId);
}
/**
* @description Manages metadata for documents uploaded by a user. Access is
* restricted to the owner of the parent user profile.
* @path /users/{userId}/documents/{documentId}
* @allow (create) An authenticated user 'user_abc' creates a new document at /users/user_abc/documents/doc_123.
* @deny (list) An authenticated user 'user_xyz' tries to list documents at /users/user_abc/documents.
* @principle Enforces document ownership for all operations and validates relational integrity on create/update.
*/
match /users/{userId}/documents/{documentId} {
allow get: if isOwner(userId);
allow list: if isOwner(userId);
allow create: if isOwner(userId) && hasValidDocumentCreateData(userId);
allow update: if isExistingOwner(userId) && hasValidDocumentUpdateData();
allow delete: if isExistingOwner(userId);
}
/**
* @description Manages AI-generated summaries for a specific document. Access is
* restricted to the owner of the parent user profile.
* @path /users/{userId}/documents/{documentId}/summaries/{summaryId}
* @allow (get) An authenticated user 'user_abc' reads a summary at /users/user_abc/documents/doc_123/summaries/sum_456.
* @deny (update) An authenticated user 'user_xyz' tries to update a summary at /users/user_abc/documents/doc_123/summaries/sum_456.
* @principle Enforces deep hierarchical ownership and validates relational integrity with both the user and the parent document.
*/
match /users/{userId}/documents/{documentId}/summaries/{summaryId} {
allow get: if isOwner(userId);
allow list: if isOwner(userId);
allow create: if isOwner(userId) && hasValidSummaryCreateData(userId, documentId);
allow update: if isExistingOwner(userId) && hasValidSummaryUpdateData();
allow delete: if isExistingOwner(userId);
}
}
}