Skip to content

Commit 1d1ab0d

Browse files
Implement Firestore security rules
Implement Firestore security rules to protect data access and integrity. Provide instructions for Firebase setup.
1 parent eae01d5 commit 1d1ab0d

File tree

3 files changed

+328
-10
lines changed

3 files changed

+328
-10
lines changed

FIREBASE_SECURITY_SETUP.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
2+
# 🔒 Guide d'implémentation sécurité Firebase
3+
4+
## 📋 Checklist de sécurité critique
5+
6+
### ✅ 1. Appliquer les règles Firestore (URGENT)
7+
8+
**Dans la console Firebase :**
9+
1. Va sur [Firebase Console](https://console.firebase.google.com/)
10+
2. Sélectionne ton projet `refspring-8c3ac`
11+
3. Dans le menu gauche : **Firestore Database**
12+
4. Onglet **Règles**
13+
5. Copie-colle le contenu du fichier `firestore.rules`
14+
6. Clique **Publier**
15+
16+
⚠️ **ATTENTION** : Cela va immédiatement sécuriser ta base de données !
17+
18+
### ✅ 2. Configurer les variables d'environnement
19+
20+
**Créer un fichier `.env.local` (NON committé) :**
21+
```bash
22+
# Firebase Configuration (Production)
23+
VITE_FIREBASE_API_KEY=AIzaSyAlHsC-w7Sx18XKJ6dIcxvqj-AUdqkjqSE
24+
VITE_FIREBASE_AUTH_DOMAIN=refspring-8c3ac.firebaseapp.com
25+
VITE_FIREBASE_DATABASE_URL=https://refspring-8c3ac-default-rtdb.europe-west1.firebasedatabase.app
26+
VITE_FIREBASE_PROJECT_ID=refspring-8c3ac
27+
VITE_FIREBASE_STORAGE_BUCKET=refspring-8c3ac.firebasestorage.app
28+
VITE_FIREBASE_MESSAGING_SENDER_ID=519439687826
29+
VITE_FIREBASE_APP_ID=1:519439687826:web:c0644e224f4ca23b57864b
30+
VITE_FIREBASE_MEASUREMENT_ID=G-QNK35Y7EE4
31+
32+
# App Configuration
33+
VITE_APP_URL=https://refspring.com
34+
VITE_SHORT_LINK_BASE=https://refspring.com/s
35+
VITE_API_BASE_URL=https://api.refspring.com
36+
VITE_DASHBOARD_URL=https://dashboard.refspring.com
37+
38+
# Feature Flags
39+
VITE_ENABLE_FRAUD_DETECTION=true
40+
VITE_ENABLE_REAL_TIME_UPDATES=true
41+
VITE_DEBUG_MODE=false
42+
```
43+
44+
### ✅ 3. Activer l'authentification Firebase
45+
46+
**Dans Firebase Console :**
47+
1. **Authentication****Sign-in method**
48+
2. Activer **Email/Password**
49+
3. Activer **Google** (optionnel)
50+
4. Dans **Settings****Authorized domains**, ajouter :
51+
- `refspring.com`
52+
- `dashboard.refspring.com`
53+
- `localhost` (pour dev)
54+
55+
### ✅ 4. Configurer les index Firestore
56+
57+
**Dans Firestore → Index :**
58+
Créer ces index composites :
59+
60+
```
61+
campaigns:
62+
- userId (Ascending) + createdAt (Descending)
63+
64+
affiliates:
65+
- userId (Ascending) + createdAt (Descending)
66+
- campaignId (Ascending) + createdAt (Descending)
67+
68+
clicks:
69+
- campaignId (Ascending) + timestamp (Descending)
70+
- affiliateId (Ascending) + timestamp (Descending)
71+
72+
conversions:
73+
- campaignId (Ascending) + timestamp (Descending)
74+
- affiliateId (Ascending) + timestamp (Descending)
75+
76+
shortLinks:
77+
- campaignId (Ascending) + createdAt (Descending)
78+
```
79+
80+
## 🚨 Actions immédiates requises
81+
82+
### 1. Règles Firestore (CRITIQUE - À faire MAINTENANT)
83+
- [ ] Copier le fichier `firestore.rules` dans Firebase Console
84+
- [ ] Publier les règles
85+
- [ ] Tester que l'app fonctionne toujours
86+
87+
### 2. Tests de sécurité
88+
- [ ] Tester la création de campagne
89+
- [ ] Tester l'accès aux données d'un autre utilisateur (doit échouer)
90+
- [ ] Vérifier que les clics sont bien enregistrés
91+
92+
### 3. Monitoring
93+
- [ ] Configurer les alertes Firebase
94+
- [ ] Vérifier les logs d'erreurs
95+
96+
## 🔧 Prochaines étapes (Phase 2)
97+
98+
1. **Cloud Functions pour la sécurité** (tracking sécurisé)
99+
2. **Protection anti-fraude** (détection patterns suspects)
100+
3. **Monitoring avancé** (alertes temps réel)
101+
4. **API sécurisée** (webhooks partenaires)
102+
103+
## ⚠️ Points d'attention
104+
105+
- **Backup** : Firebase fait des backups automatiques, mais configure des exports réguliers
106+
- **Monitoring** : Surveille les tentatives d'accès non autorisées
107+
- **Performance** : Les nouvelles règles peuvent ralentir certaines requêtes
108+
- **Tests** : Teste toutes les fonctionnalités après application des règles
109+
110+
## 🆘 En cas de problème
111+
112+
Si l'app ne fonctionne plus après application des règles :
113+
1. Vérifie les logs de la console Firebase
114+
2. Vérifie que tous les utilisateurs sont bien authentifiés
115+
3. Contacte-moi avec les messages d'erreur exacts
116+
117+
**Remember** : Ces règles bloquent TOUT accès non autorisé. C'est le but ! 🛡️

firestore.rules

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
2+
rules_version = '2';
3+
4+
service cloud.firestore {
5+
match /databases/{database}/documents {
6+
7+
// ======================================
8+
// RÈGLES POUR LES CAMPAGNES
9+
// ======================================
10+
match /campaigns/{campaignId} {
11+
// Lecture : uniquement le propriétaire
12+
allow read: if request.auth != null &&
13+
request.auth.uid == resource.data.userId;
14+
15+
// Création : utilisateur authentifié + structure valide
16+
allow create: if request.auth != null &&
17+
request.auth.uid == request.resource.data.userId &&
18+
isValidCampaign(request.resource.data);
19+
20+
// Mise à jour : uniquement le propriétaire + structure valide
21+
allow update: if request.auth != null &&
22+
request.auth.uid == resource.data.userId &&
23+
request.auth.uid == request.resource.data.userId &&
24+
isValidCampaign(request.resource.data);
25+
26+
// Suppression : uniquement le propriétaire
27+
allow delete: if request.auth != null &&
28+
request.auth.uid == resource.data.userId;
29+
}
30+
31+
// ======================================
32+
// RÈGLES POUR LES AFFILIÉS
33+
// ======================================
34+
match /affiliates/{affiliateId} {
35+
// Lecture : propriétaire de la campagne associée
36+
allow read: if request.auth != null &&
37+
request.auth.uid == resource.data.userId;
38+
39+
// Création : utilisateur authentifié + campagne valide + structure valide
40+
allow create: if request.auth != null &&
41+
request.auth.uid == request.resource.data.userId &&
42+
isValidAffiliate(request.resource.data) &&
43+
isOwnerOfCampaign(request.resource.data.campaignId);
44+
45+
// Mise à jour : propriétaire + structure valide
46+
allow update: if request.auth != null &&
47+
request.auth.uid == resource.data.userId &&
48+
request.auth.uid == request.resource.data.userId &&
49+
isValidAffiliate(request.resource.data);
50+
51+
// Suppression : propriétaire
52+
allow delete: if request.auth != null &&
53+
request.auth.uid == resource.data.userId;
54+
}
55+
56+
// ======================================
57+
// RÈGLES POUR LES CLICS (Lecture seule pour les propriétaires)
58+
// ======================================
59+
match /clicks/{clickId} {
60+
// Lecture : propriétaire de la campagne associée
61+
allow read: if request.auth != null &&
62+
isOwnerOfCampaign(resource.data.campaignId);
63+
64+
// Création : limitée aux fonctions serveur (via service account)
65+
allow create: if false; // Sera géré par Cloud Functions
66+
67+
// Pas de mise à jour ou suppression directe
68+
allow update, delete: if false;
69+
}
70+
71+
// ======================================
72+
// RÈGLES POUR LES CONVERSIONS (Très restrictives)
73+
// ======================================
74+
match /conversions/{conversionId} {
75+
// Lecture : propriétaire de la campagne
76+
allow read: if request.auth != null &&
77+
isOwnerOfCampaign(resource.data.campaignId);
78+
79+
// Création : uniquement via Cloud Functions
80+
allow create: if false; // Sera géré par Cloud Functions sécurisées
81+
82+
// Mise à jour : uniquement pour vérification (propriétaire)
83+
allow update: if request.auth != null &&
84+
isOwnerOfCampaign(resource.data.campaignId) &&
85+
onlyUpdatingVerificationStatus(request.resource.data, resource.data);
86+
87+
// Suppression : propriétaire seulement
88+
allow delete: if request.auth != null &&
89+
isOwnerOfCampaign(resource.data.campaignId);
90+
}
91+
92+
// ======================================
93+
// RÈGLES POUR LES LIENS COURTS
94+
// ======================================
95+
match /shortLinks/{linkId} {
96+
// Lecture : propriétaire de la campagne ou accès public pour redirection
97+
allow read: if request.auth != null &&
98+
isOwnerOfCampaign(resource.data.campaignId);
99+
100+
// Lecture publique limitée (juste pour la redirection)
101+
allow get: if true; // Nécessaire pour le tracking public
102+
103+
// Création : propriétaire de la campagne + structure valide
104+
allow create: if request.auth != null &&
105+
isOwnerOfCampaign(request.resource.data.campaignId) &&
106+
isValidShortLink(request.resource.data);
107+
108+
// Mise à jour : propriétaire (pour compteur de clics)
109+
allow update: if request.auth != null &&
110+
isOwnerOfCampaign(resource.data.campaignId) &&
111+
onlyUpdatingClickCount(request.resource.data, resource.data);
112+
113+
// Suppression : propriétaire
114+
allow delete: if request.auth != null &&
115+
isOwnerOfCampaign(resource.data.campaignId);
116+
}
117+
118+
// ======================================
119+
// FONCTIONS DE VALIDATION
120+
// ======================================
121+
122+
// Vérifier si l'utilisateur est propriétaire d'une campagne
123+
function isOwnerOfCampaign(campaignId) {
124+
return exists(/databases/$(database)/documents/campaigns/$(campaignId)) &&
125+
get(/databases/$(database)/documents/campaigns/$(campaignId)).data.userId == request.auth.uid;
126+
}
127+
128+
// Validation structure campagne
129+
function isValidCampaign(data) {
130+
return data.keys().hasAll(['name', 'description', 'targetUrl', 'userId', 'isActive']) &&
131+
data.name is string && data.name.size() > 0 && data.name.size() <= 100 &&
132+
data.description is string && data.description.size() <= 500 &&
133+
data.targetUrl is string && data.targetUrl.matches('https?://.*') &&
134+
data.userId is string &&
135+
data.isActive is bool &&
136+
(!data.keys().hasAny(['createdAt', 'updatedAt']) ||
137+
(data.createdAt is timestamp && data.updatedAt is timestamp));
138+
}
139+
140+
// Validation structure affilié
141+
function isValidAffiliate(data) {
142+
return data.keys().hasAll(['name', 'email', 'commissionRate', 'campaignId', 'userId', 'trackingCode', 'isActive']) &&
143+
data.name is string && data.name.size() > 0 && data.name.size() <= 100 &&
144+
data.email is string && data.email.matches('.*@.*\\..*') &&
145+
data.commissionRate is number && data.commissionRate >= 0 && data.commissionRate <= 100 &&
146+
data.campaignId is string &&
147+
data.userId is string &&
148+
data.trackingCode is string && data.trackingCode.size() > 10 &&
149+
data.isActive is bool;
150+
}
151+
152+
// Validation structure lien court
153+
function isValidShortLink(data) {
154+
return data.keys().hasAll(['shortCode', 'campaignId', 'affiliateId', 'targetUrl']) &&
155+
data.shortCode is string && data.shortCode.size() == 6 &&
156+
data.campaignId is string &&
157+
data.affiliateId is string &&
158+
data.targetUrl is string && data.targetUrl.matches('https?://.*') &&
159+
data.clickCount is number && data.clickCount >= 0;
160+
}
161+
162+
// Vérifier que seul le statut de vérification change
163+
function onlyUpdatingVerificationStatus(newData, oldData) {
164+
return newData.diff(oldData).affectedKeys().hasOnly(['verified']) &&
165+
newData.verified is bool;
166+
}
167+
168+
// Vérifier que seul le compteur de clics change
169+
function onlyUpdatingClickCount(newData, oldData) {
170+
return newData.diff(oldData).affectedKeys().hasOnly(['clickCount']) &&
171+
newData.clickCount is number &&
172+
newData.clickCount >= oldData.clickCount;
173+
}
174+
175+
// ======================================
176+
// PROTECTION CONTRE LES ATTAQUES
177+
// ======================================
178+
179+
// Bloquer toute autre collection non définie
180+
match /{document=**} {
181+
allow read, write: if false;
182+
}
183+
}
184+
}

src/lib/firebase.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,25 @@ import { getFirestore } from "firebase/firestore";
55
import { getAnalytics } from "firebase/analytics";
66

77
const firebaseConfig = {
8-
apiKey: "AIzaSyAlHsC-w7Sx18XKJ6dIcxvqj-AUdqkjqSE",
9-
authDomain: "refspring-8c3ac.firebaseapp.com",
10-
databaseURL: "https://refspring-8c3ac-default-rtdb.europe-west1.firebasedatabase.app",
11-
projectId: "refspring-8c3ac",
12-
storageBucket: "refspring-8c3ac.firebasestorage.app",
13-
messagingSenderId: "519439687826",
14-
appId: "1:519439687826:web:c0644e224f4ca23b57864b",
15-
measurementId: "G-QNK35Y7EE4"
8+
apiKey: import.meta.env.VITE_FIREBASE_API_KEY || "AIzaSyAlHsC-w7Sx18XKJ6dIcxvqj-AUdqkjqSE",
9+
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN || "refspring-8c3ac.firebaseapp.com",
10+
databaseURL: import.meta.env.VITE_FIREBASE_DATABASE_URL || "https://refspring-8c3ac-default-rtdb.europe-west1.firebasedatabase.app",
11+
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID || "refspring-8c3ac",
12+
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET || "refspring-8c3ac.firebasestorage.app",
13+
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID || "519439687826",
14+
appId: import.meta.env.VITE_FIREBASE_APP_ID || "1:519439687826:web:c0644e224f4ca23b57864b",
15+
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID || "G-QNK35Y7EE4"
1616
};
1717

18+
// Validation en mode développement
19+
if (import.meta.env.DEV) {
20+
console.log('🔥 Firebase config loaded:', {
21+
projectId: firebaseConfig.projectId,
22+
authDomain: firebaseConfig.authDomain,
23+
usingEnvVars: !!import.meta.env.VITE_FIREBASE_API_KEY
24+
});
25+
}
26+
1827
// Initialize Firebase
1928
const app = initializeApp(firebaseConfig);
2029

@@ -23,7 +32,15 @@ export const auth = getAuth(app);
2332
export const db = getFirestore(app);
2433
export const googleProvider = new GoogleAuthProvider();
2534

26-
// Analytics (optional, only if in browser)
27-
export const analytics = typeof window !== 'undefined' ? getAnalytics(app) : null;
35+
// Configure Google provider
36+
googleProvider.setCustomParameters({
37+
prompt: 'select_account'
38+
});
39+
40+
// Analytics (optional, only if in browser and measurement ID provided)
41+
export const analytics =
42+
typeof window !== 'undefined' && firebaseConfig.measurementId
43+
? getAnalytics(app)
44+
: null;
2845

2946
export default app;

0 commit comments

Comments
 (0)