-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirestore.rules
More file actions
128 lines (110 loc) · 5.83 KB
/
Copy pathfirestore.rules
File metadata and controls
128 lines (110 loc) · 5.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
rules_version = '2';
// Règles de BeVannes. Le client ne peut rien écrire que le jeu n'autorise :
// les points se recalculent ici, à l'identique de lib/domain/score.dart.
service cloud.firestore {
match /databases/{database}/documents {
function connecte() {
return request.auth != null;
}
// Même règle que erreurPseudo() dans lib/data/depot.dart. Les règles
// refusent les classes Unicode (\p{L}) : les lettres accentuées sont
// donc écrites en plages.
function pseudoValide(p) {
return p is string && p.size() >= 3 && p.size() <= 20 && p.matches('^[A-Za-z0-9À-ÖØ-öø-ÿ ._-]+$');
}
// Le numéro du jour envoyé par l'appareil (jours depuis 1970, date
// locale) doit être celui d'aujourd'hui, à l'heure de Paris : entre la
// date UTC et la date UTC deux heures plus tard.
function jourValide(j) {
let t = request.time.toMillis();
return j is int
&& (j == math.floor(t / 86400000) || j == math.floor((t + 7200000) / 86400000));
}
function serieApres(avant, jour) {
return avant.dernierJour == jour - 1 ? avant.serie + 1 : 1;
}
function gainPour(serie) {
return 10 + 2 * (serie - 1 > 5 ? 5 : serie - 1);
}
match /joueurs/{uid} {
// Le classement est visible de tous les joueurs connectés. Aucune
// donnée personnelle ici : l'e-mail reste dans Authentication.
allow read: if connecte();
allow create: if connecte() && request.auth.uid == uid
&& request.resource.data.keys().hasOnly(['pseudo', 'points', 'serie', 'meilleureSerie', 'validations', 'dernierJour', 'creeLe'])
&& pseudoValide(request.resource.data.pseudo)
&& request.resource.data.points == 0
&& request.resource.data.serie == 0
&& request.resource.data.meilleureSerie == 0
&& request.resource.data.validations == 0
&& request.resource.data.dernierJour == -1
&& request.resource.data.creeLe == request.time;
// Deux mises à jour possibles, et seulement celles-là.
allow update: if connecte() && request.auth.uid == uid && (changementPseudo() || validation(uid));
allow delete: if connecte() && request.auth.uid == uid;
function changementPseudo() {
return request.resource.data.diff(resource.data).affectedKeys().hasOnly(['pseudo'])
&& pseudoValide(request.resource.data.pseudo);
}
// Les points d'une validation : la validation du jour doit être créée
// dans la même écriture, et les chiffres doivent tomber juste.
function validation(uid) {
let avant = resource.data;
let apres = request.resource.data;
let jour = apres.dernierJour;
let serie = serieApres(avant, jour);
return apres.diff(avant).affectedKeys().hasOnly(['points', 'serie', 'meilleureSerie', 'validations', 'dernierJour'])
&& jourValide(jour)
&& jour > avant.dernierJour
&& apres.serie == serie
&& apres.points == avant.points + gainPour(serie)
&& apres.validations == avant.validations + 1
&& apres.meilleureSerie == (serie > avant.meilleureSerie ? serie : avant.meilleureSerie)
&& existsAfter(/databases/$(database)/documents/validations/$(string(jour) + '_' + uid));
}
}
match /validations/{id} {
allow read: if connecte();
// Une par joueur et par jour : l'identifiant l'impose.
allow create: if connecte()
&& id == string(request.resource.data.jour) + '_' + request.auth.uid
&& request.resource.data.keys().hasOnly(['uid', 'pseudo', 'jour', 'lieu', 'distance', 'gain', 'serie', 'photo', 'moment'])
&& request.resource.data.uid == request.auth.uid
&& jourValide(request.resource.data.jour)
&& request.resource.data.lieu is string
&& request.resource.data.distance is number
&& request.resource.data.distance >= 0
&& request.resource.data.distance <= 100
&& request.resource.data.photo == 'photos/' + id
&& existsAfter(/databases/$(database)/documents/photos/$(id))
&& request.resource.data.moment == request.time
// Pseudo, série et gain doivent correspondre au joueur tel qu'il sera
// après l'écriture.
&& getAfter(/databases/$(database)/documents/joueurs/$(request.auth.uid)).data.dernierJour == request.resource.data.jour
&& request.resource.data.pseudo == getAfter(/databases/$(database)/documents/joueurs/$(request.auth.uid)).data.pseudo
&& request.resource.data.serie == getAfter(/databases/$(database)/documents/joueurs/$(request.auth.uid)).data.serie
&& request.resource.data.gain == gainPour(request.resource.data.serie);
allow update: if false;
// Pour la suppression du compte.
allow delete: if connecte() && resource.data.uid == request.auth.uid;
}
// Les photos, une par validation, sous le même identifiant.
match /photos/{id} {
// Comme dans BeReal : on ne voit les photos d'un jour qu'après avoir
// soi-même validé ce jour-là. La sienne se voit toujours.
allow read: if connecte()
&& (resource.data.uid == request.auth.uid
|| exists(/databases/$(database)/documents/validations/$(string(resource.data.jour) + '_' + request.auth.uid)));
// Écrite dans la même transaction que sa validation, jamais seule.
allow create: if connecte()
&& id == string(request.resource.data.jour) + '_' + request.auth.uid
&& request.resource.data.keys().hasOnly(['uid', 'jour', 'jpeg'])
&& request.resource.data.uid == request.auth.uid
&& request.resource.data.jpeg is bytes
&& request.resource.data.jpeg.size() < 900 * 1024
&& existsAfter(/databases/$(database)/documents/validations/$(id));
allow update: if false;
allow delete: if connecte() && resource.data.uid == request.auth.uid;
}
}
}