2026-06-19 18:46:25 -05:00
|
|
|
rules_version = '2';
|
|
|
|
|
service firebase.storage {
|
|
|
|
|
match /b/{bucket}/o {
|
|
|
|
|
|
|
|
|
|
// Profile photos: only the owner may write; both the owner and their
|
|
|
|
|
// partner can read via the tokenized download URL. Direct read is
|
|
|
|
|
// scoped to the owner so raw storage paths aren't publicly accessible.
|
|
|
|
|
//
|
|
|
|
|
// Size cap: 5 MB. Content-type must be an image (enforced at upload,
|
|
|
|
|
// not at read, so the header check only blocks mismatched writes).
|
|
|
|
|
match /users/{uid}/profile.jpg {
|
|
|
|
|
allow write: if request.auth != null
|
|
|
|
|
&& request.auth.uid == uid
|
|
|
|
|
&& request.resource.size < 5 * 1024 * 1024
|
|
|
|
|
&& request.resource.contentType.matches('image/.*');
|
|
|
|
|
|
|
|
|
|
// Partners receive the tokenized download URL (generated server-side or
|
|
|
|
|
// at upload time) which bypasses these rules. Direct rule-based read is
|
|
|
|
|
// scoped to the owner only.
|
|
|
|
|
allow read: if request.auth != null && request.auth.uid == uid;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-24 15:20:44 -05:00
|
|
|
// Encrypted chat media: the author writes under their own path (already E2E-encrypted
|
|
|
|
|
// ciphertext, so Storage never holds anything readable). The partner reads via the tokenized
|
|
|
|
|
// download URL, which bypasses these rules — same model as profile photos. 15 MB cap.
|
|
|
|
|
match /users/{uid}/chat_media/{file} {
|
|
|
|
|
allow write: if request.auth != null
|
|
|
|
|
&& request.auth.uid == uid
|
|
|
|
|
&& request.resource.size < 15 * 1024 * 1024;
|
|
|
|
|
allow read: if request.auth != null && request.auth.uid == uid;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-30 20:43:26 -05:00
|
|
|
// Encrypted conversation-backup snapshots. Written by the uploading member under their OWN path
|
|
|
|
|
// (couple-key ciphertext, so Storage holds nothing readable). The PARTNER reads via the tokenized
|
|
|
|
|
// download URL recorded in the couple-gated backup manifest — same model as chat media. Living
|
|
|
|
|
// under users/{uid}/ means the existing onUserDelete `users/{uid}/` cleanup covers backups too.
|
|
|
|
|
// 50 MB cap (a full-history snapshot for a long-lived couple).
|
|
|
|
|
match /users/{uid}/backups/{file} {
|
|
|
|
|
allow write: if request.auth != null
|
|
|
|
|
&& request.auth.uid == uid
|
|
|
|
|
&& request.resource.size < 50 * 1024 * 1024;
|
|
|
|
|
allow read: if request.auth != null && request.auth.uid == uid;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-19 18:46:25 -05:00
|
|
|
// Deny all other paths by default.
|
|
|
|
|
match /{allPaths=**} {
|
|
|
|
|
allow read, write: if false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|