fix: profile photo temp dir, Firestore rules field-level lockdown (batch v0.2.10)

- Move temp profile photos to filesDir/photos/ subdirectory with mkdirs
- Update file_paths.xml to scope FileProvider to photos/ subdirectory
- Firestore rules: restrict couple doc updates to only mutable fields (streakCount, lastAnsweredAt, wrappedCoupleKey, kdfSalt, kdfParams, encryptionVersion) — prevents client from overwriting currentQuestionId, activePackId, id
This commit is contained in:
null 2026-06-19 20:33:08 -05:00
parent 9a0b2b6a3d
commit e7b45cc84f
4 changed files with 15 additions and 9 deletions

View File

@ -105,7 +105,9 @@ fun CreateProfileScreen(
uri?.let { viewModel.setPhotoUri(it.toString()) } uri?.let { viewModel.setPhotoUri(it.toString()) }
} }
val cameraFile = remember(context) { File(context.filesDir, "profile_temp.jpg") } val cameraFile = remember(context) {
File(context.filesDir, "photos/profile_temp.jpg").also { it.parentFile?.mkdirs() }
}
val cameraUri = remember(cameraFile) { val cameraUri = remember(cameraFile) {
FileProvider.getUriForFile( FileProvider.getUriForFile(
context, context,

View File

@ -143,7 +143,9 @@ fun EditProfileContent(
uri?.let { viewModel.setPhotoUri(it.toString()) } uri?.let { viewModel.setPhotoUri(it.toString()) }
} }
val cameraFile = remember(context) { File(context.filesDir, "profile_edit_temp.jpg") } val cameraFile = remember(context) {
File(context.filesDir, "photos/profile_edit_temp.jpg").also { it.parentFile?.mkdirs() }
}
val cameraUri = remember(cameraFile) { val cameraUri = remember(cameraFile) {
FileProvider.getUriForFile( FileProvider.getUriForFile(
context, context,

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="profile_photos" path="." /> <files-path name="profile_photos" path="photos/" />
</paths> </paths>

View File

@ -179,16 +179,18 @@ service cloud.firestore {
'wrappedCoupleKey', 'kdfSalt', 'kdfParams', 'encryptionVersion']); 'wrappedCoupleKey', 'kdfSalt', 'kdfParams', 'encryptionVersion']);
// Update: field-level restrictions // Update: field-level restrictions
// - user IDs are immutable (cannot change who is in the couple) // - user IDs, invite code, and createdAt are immutable
// - invite code is immutable (cannot change the code)
// - createdAt is immutable (cannot change when the couple was formed)
// - encryptionVersion is monotonically non-decreasing (cannot downgrade) // - encryptionVersion is monotonically non-decreasing (cannot downgrade)
// - wrappedCoupleKey/kdfSalt/kdfParams: mutable by members (passphrase change) // - only the explicitly listed mutable fields may change; everything else
// - All other fields (including streakCount and lastAnsweredAt): both members can update // (including currentQuestionId, activePackId, id) is server-only
allow update: if isCouplesMember(coupleId) allow update: if isCouplesMember(coupleId)
&& isImmutable(['userIds', 'inviteCode', 'createdAt']) && isImmutable(['userIds', 'inviteCode', 'createdAt'])
&& (resource.data.encryptionVersion == null && (resource.data.encryptionVersion == null
|| request.resource.data.encryptionVersion >= resource.data.encryptionVersion); || request.resource.data.encryptionVersion >= resource.data.encryptionVersion)
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly([
'streakCount', 'lastAnsweredAt',
'wrappedCoupleKey', 'kdfSalt', 'kdfParams', 'encryptionVersion'
]);
// Delete: server-only (admin SDK only). Admin SDK bypasses rules. // Delete: server-only (admin SDK only). Admin SDK bypasses rules.
allow delete: if false; allow delete: if false;