SECURITY: enforce profile ownership and immutable identity fields

This commit is contained in:
bponders12-cell 2026-09-01 17:07:07 -04:00
parent 00c86067cc
commit 50330f6867
1 changed files with 30 additions and 6 deletions

View File

@ -9,10 +9,33 @@ service cloud.firestore {
return signedIn() && request.auth.uid == userId;
}
// Every user-owned document is isolated by the Firebase UID. Cloudflare R2
// is protected separately by the Worker using the same Firebase ID token.
function validUserCreate(userId) {
return ownsUser(userId)
&& request.resource.data.id == userId
&& request.resource.data.keys().hasOnly([
'id', 'username', 'displayName', 'profilePhotoUrl', 'bio',
'location', 'createdAt', 'updatedAt', 'privacySettings', 'preferences'
]);
}
function validUserUpdate(userId) {
return ownsUser(userId)
&& request.resource.data.id == resource.data.id
&& request.resource.data.createdAt == resource.data.createdAt
&& request.resource.data.keys().hasOnly([
'id', 'username', 'displayName', 'profilePhotoUrl', 'bio',
'location', 'createdAt', 'updatedAt', 'privacySettings', 'preferences'
]);
}
// Firebase UID is the security boundary. Profile fields are explicitly
// allow-listed so clients cannot turn the account document into a secret
// or entitlement store.
match /users/{userId} {
allow read, write: if ownsUser(userId);
allow create: if validUserCreate(userId);
allow read: if ownsUser(userId);
allow update: if validUserUpdate(userId);
allow delete: if ownsUser(userId);
match /rides/{rideId} {
allow read, write: if ownsUser(userId);
@ -25,7 +48,8 @@ service cloud.firestore {
}
match /motorcycles/{motorcycleId} {
allow read, write: if ownsUser(userId);
allow read, write: if ownsUser(userId)
&& request.resource.data.ownerId == userId;
}
match /trips/{tripId} {
@ -41,8 +65,8 @@ service cloud.firestore {
}
}
// Future public/group collections must be added with explicit field-level
// authorization. Do not use a global recursive wildcard here.
// Future collaborative resources must get explicit membership/field-level
// authorization before they are exposed.
match /groupRides/{groupRideId} {
allow read, write: if false;
}