@@ -5,113 +5,141 @@ service cloud.firestore {
55 match / databases/ {database }/ documents {
66
77 // ======================================
8- // RÈGLES FIRESTORE CORRIGÉES ET OPTIMISÉES
8+ // RÈGLES FIRESTORE SÉCURISÉES ET RAPIDES
99 // ======================================
1010
1111 // ======================================
12- // CAMPAGNES - Accès utilisateur seulement
12+ // CAMPAGNES - Propriétaire seulement
1313 // ======================================
1414 match / campaigns/ {campaignId } {
15- // Lecture : utilisateur authentifié ET propriétaire
16- allow read : if request .auth != null &&
17- request .auth.uid == resource.data.userId ;
15+ // Lecture/écriture : utilisateur authentifié ET propriétaire
16+ allow read , write : if request .auth != null &&
17+ request .auth.uid == resource.data.userId ;
1818
1919 // Création : utilisateur authentifié ET défini comme propriétaire
2020 allow create : if request .auth != null &&
21- request .auth.uid == request .resource.data.userId ;
22-
23- // Mise à jour : utilisateur authentifié ET propriétaire existant
24- allow update : if request .auth != null &&
25- request .auth.uid == resource.data.userId ;
26-
27- // Suppression : utilisateur authentifié ET propriétaire
28- allow delete : if request .auth != null &&
29- request .auth.uid == resource.data.userId ;
21+ request .auth.uid == request .resource.data.userId &&
22+ request .resource.data .keys ().hasAll ([' name' , ' description' , ' userId' ]) &&
23+ request .resource.data.userId is string ;
3024 }
3125
3226 // ======================================
33- // AFFILIÉS - Accès utilisateur seulement
27+ // AFFILIÉS - Propriétaire seulement
3428 // ======================================
3529 match / affiliates/ {affiliateId } {
36- // Lecture : utilisateur authentifié ET propriétaire
37- allow read : if request .auth != null &&
38- request .auth.uid == resource.data.userId ;
30+ // Lecture/écriture : utilisateur authentifié ET propriétaire
31+ allow read , write : if request .auth != null &&
32+ request .auth.uid == resource.data.userId ;
3933
4034 // Création : utilisateur authentifié ET défini comme propriétaire
4135 allow create : if request .auth != null &&
42- request .auth.uid == request .resource.data.userId ;
43-
44- // Mise à jour : utilisateur authentifié ET propriétaire existant
45- allow update : if request .auth != null &&
46- request .auth.uid == resource.data.userId ;
47-
48- // Suppression : utilisateur authentifié ET propriétaire
49- allow delete : if request .auth != null &&
50- request .auth.uid == resource.data.userId ;
36+ request .auth.uid == request .resource.data.userId &&
37+ request .resource.data .keys ().hasAll ([' name' , ' email' , ' campaignId' , ' userId' ]) &&
38+ request .resource.data.userId is string &&
39+ request .resource.data.campaignId is string ;
5140 }
5241
5342 // ======================================
54- // CLICS - Plus permissif pour le tracking
43+ // CLICS - Protection anti-falsification
5544 // ======================================
5645 match / clicks/ {clickId } {
57- // Lecture : tous les utilisateurs authentifiés
58- allow read : if request .auth != null ;
46+ // Lecture : utilisateur authentifié ET (propriétaire OU propriétaire de la campagne)
47+ allow read : if request .auth != null && (
48+ request .auth.uid == resource .data.userId ||
49+ exists (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )) &&
50+ get (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )).data.userId == request .auth.uid
51+ );
5952
60- // Création : tous les utilisateurs authentifiés (pour tracking public)
61- allow create : if request .auth != null ;
53+ // Création : validation stricte des données
54+ allow create : if request .auth != null &&
55+ request .resource.data .keys ().hasAll ([' affiliateId' , ' campaignId' , ' timestamp' , ' targetUrl' ]) &&
56+ request .resource.data.affiliateId is string &&
57+ request .resource.data.campaignId is string &&
58+ request .resource.data.timestamp is timestamp &&
59+ request .resource.data.targetUrl is string &&
60+ // Vérifier que l'affilié existe et appartient à la bonne campagne
61+ exists (/ databases/ $(database )/ documents/ affiliates/ $(request .resource.data.affiliateId )) &&
62+ get (/ databases/ $(database )/ documents/ affiliates/ $(request .resource.data.affiliateId )).data.campaignId == request .resource.data.campaignId ;
6263
63- // Mise à jour : propriétaire seulement
64- allow update : if request .auth != null &&
65- request .auth.uid == resource.data.userId ;
64+ // Mise à jour : interdite pour éviter la falsification
65+ allow update : if false ;
6666
67- // Suppression : propriétaire seulement
68- allow delete : if request .auth != null &&
69- request .auth.uid == resource.data.userId ;
67+ // Suppression : propriétaire de la campagne seulement
68+ allow delete : if request .auth != null &&
69+ exists (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )) &&
70+ get (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )).data.userId == request .auth.uid ;
7071 }
7172
7273 // ======================================
73- // CONVERSIONS - Plus permissif pour le tracking
74+ // CONVERSIONS - Protection anti-falsification renforcée
7475 // ======================================
7576 match / conversions/ {conversionId } {
76- // Lecture : tous les utilisateurs authentifiés
77- allow read : if request .auth != null ;
77+ // Lecture : utilisateur authentifié ET propriétaire de la campagne
78+ allow read : if request .auth != null &&
79+ exists (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )) &&
80+ get (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )).data.userId == request .auth.uid ;
7881
79- // Création : tous les utilisateurs authentifiés (pour tracking public)
80- allow create : if request .auth != null ;
82+ // Création : validation ultra-stricte
83+ allow create : if request .auth != null &&
84+ request .resource.data .keys ().hasAll ([' affiliateId' , ' campaignId' , ' amount' , ' commission' , ' timestamp' ]) &&
85+ request .resource.data.affiliateId is string &&
86+ request .resource.data.campaignId is string &&
87+ request .resource.data.amount is number &&
88+ request .resource.data.commission is number &&
89+ request .resource.data.timestamp is timestamp &&
90+ request .resource.data.amount >= 0 &&
91+ request .resource.data.commission >= 0 &&
92+ // Vérifier que l'affilié existe et appartient à la bonne campagne
93+ exists (/ databases/ $(database )/ documents/ affiliates/ $(request .resource.data.affiliateId )) &&
94+ get (/ databases/ $(database )/ documents/ affiliates/ $(request .resource.data.affiliateId )).data.campaignId == request .resource.data.campaignId &&
95+ // Vérifier que l'utilisateur possède la campagne
96+ exists (/ databases/ $(database )/ documents/ campaigns/ $(request .resource.data.campaignId )) &&
97+ get (/ databases/ $(database )/ documents/ campaigns/ $(request .resource.data.campaignId )).data.userId == request .auth.uid ;
8198
82- // Mise à jour : propriétaire seulement
83- allow update : if request .auth != null &&
84- request .auth.uid == resource.data.userId ;
99+ // Mise à jour : seulement pour le statut de vérification par le propriétaire
100+ allow update : if request .auth != null &&
101+ request .resource.data .diff (resource .data ).affectedKeys ().hasOnly ([' verified' ]) &&
102+ exists (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )) &&
103+ get (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )).data.userId == request .auth.uid ;
85104
86- // Suppression : propriétaire seulement
87- allow delete : if request .auth != null &&
88- request .auth.uid == resource.data.userId ;
105+ // Suppression : propriétaire de la campagne seulement
106+ allow delete : if request .auth != null &&
107+ exists (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )) &&
108+ get (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )).data.userId == request .auth.uid ;
89109 }
90110
91111 // ======================================
92112 // LIENS COURTS - Lecture publique nécessaire
93113 // ======================================
94114 match / shortLinks/ {linkId } {
95- // Lecture publique (pour redirection)
115+ // Lecture publique (nécessaire pour redirection)
96116 allow read : if true ;
97117
98- // Création : utilisateur authentifié
99- allow create : if request .auth != null ;
118+ // Création : utilisateur authentifié avec validation
119+ allow create : if request .auth != null &&
120+ request .resource.data .keys ().hasAll ([' shortCode' , ' campaignId' , ' affiliateId' , ' targetUrl' ]) &&
121+ request .resource.data.shortCode is string &&
122+ request .resource.data.campaignId is string &&
123+ request .resource.data.affiliateId is string &&
124+ request .resource.data.targetUrl is string &&
125+ // Vérifier que l'utilisateur possède la campagne
126+ exists (/ databases/ $(database )/ documents/ campaigns/ $(request .resource.data.campaignId )) &&
127+ get (/ databases/ $(database )/ documents/ campaigns/ $(request .resource.data.campaignId )).data.userId == request .auth.uid ;
100128
101- // Mise à jour : propriétaire seulement
102- allow update : if request .auth != null &&
103- request .auth.uid == resource.data.userId ;
129+ // Mise à jour : propriétaire de la campagne seulement
130+ allow update : if request .auth != null &&
131+ exists (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )) &&
132+ get (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )).data.userId == request .auth.uid ;
104133
105- // Suppression : propriétaire seulement
106- allow delete : if request .auth != null &&
107- request .auth.uid == resource.data.userId ;
134+ // Suppression : propriétaire de la campagne seulement
135+ allow delete : if request .auth != null &&
136+ exists (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )) &&
137+ get (/ databases/ $(database )/ documents/ campaigns/ $(resource .data.campaignId )).data.userId == request .auth.uid ;
108138 }
109139
110140 // ======================================
111- // SÉCURITÉ PAR DÉFAUT
141+ // SÉCURITÉ PAR DÉFAUT - Bloquer tout le reste
112142 // ======================================
113-
114- // Bloquer tout le reste
115143 match / {document =** } {
116144 allow read , write : if false ;
117145 }
0 commit comments