Repository navigation
Expand file tree
/
Copy pathfirestore.rules
More file actions
123 lines (110 loc) · 5.95 KB
/
Copy pathfirestore.rules
File metadata and controls
123 lines (110 loc) · 5.95 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
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
function isSelf(userId) {
return isSignedIn() && request.auth.uid == userId;
}
// 이 사람이 그 공유 코드의 감시자로 등록돼 있는가.
//
// 「로그인했는가」 만 보면 공유로 표시된 일정은 관계 없는 계정에게도 열린다. 공유 코드를
// 실제로 받아 등록한 사람인지까지 봐야 범위가 가족으로 좁아진다(#174).
function watchesShareCode(shareCode) {
return isSignedIn() && exists(
/databases/$(database)/documents/shareCodeWatchers/$(shareCode)/tokens/$(request.auth.uid)
);
}
// 비공유 일정은 그대로 허용하고, 공유할 때는 등록부의 소유자를 확인한다.
function ownsRequestedShareCode() {
return !('sharedCode' in request.resource.data)
|| request.resource.data.sharedCode == ''
|| (request.resource.data.sharedCode is string
&& getAfter(/databases/$(database)/documents/shareCodes/$(request.resource.data.sharedCode)).data.userId == request.auth.uid);
}
// 사용자 문서. 이메일과 FCM 토큰이 함께 있어 본인만 읽고 쓴다.
// 다른 사람에게 보여야 하는 이름은 publicProfiles 에 따로 둔다(#93).
match /users/{userId} {
allow read, write: if isSelf(userId);
}
// 공개 프로필. 공유 일정 소유자의 이름을 보여 주는 데만 쓴다.
//
// get 만 열고 list 는 막는다. 목록을 열어 두면 문서 이름을 몰라도 컬렉션을 통째로 훑을 수
// 있어 「아는 사람만 본다」 라는 전제가 성립하지 않는다. 소유자 uid 는 이미 읽을 수 있는
// 공유 일정에서만 나오므로 get 하나로 충분하다(#174).
//
// 공유 코드는 여기에 두지 않는다. 사람을 찾는 열쇠라 이름 옆에 둘 값이 아니다.
match /publicProfiles/{userId} {
allow get: if isSignedIn();
allow list: if false;
allow create, update: if isSelf(userId)
&& request.resource.data.keys().hasOnly(['id', 'name'])
&& request.resource.data.id == userId;
allow delete: if isSelf(userId);
}
// 공유 코드 등록부. 코드 자체가 문서 이름이다.
//
// 중복 확인을 질의로 하면 컬렉션을 훑을 권한이 필요하고, 확인과 저장 사이에 같은 코드를
// 두 사람이 가져가는 틈도 남는다. 문서 만들기 한 번으로 두 문제가 함께 사라진다.
// 읽기는 아무에게도 열지 않는다. 코드 목록은 그 자체가 사람을 찾는 열쇠 꾸러미다(#174).
match /shareCodes/{shareCode} {
allow read: if false;
allow create: if isSignedIn()
&& request.resource.data.keys().hasOnly(['userId'])
&& request.resource.data.userId == request.auth.uid;
// 기존 코드도 조회 권한을 열지 않고 같은 값으로 등록·확인할 수 있다.
allow update: if isSignedIn()
&& resource.data.userId == request.auth.uid
&& request.resource.data == resource.data;
// 이전 부분 삭제나 재시도에서 이미 없는 등록부는 성공으로 처리한다.
allow delete: if isSignedIn()
&& (!exists(/databases/$(database)/documents/shareCodes/$(shareCode))
|| resource.data.userId == request.auth.uid);
}
// 일정
match /schedules/{scheduleId} {
// 고정 제출 ID의 첫 transaction은 아직 없는 문서도 확인해야 한다. 목록 권한은 열지 않는다.
allow get: if isSignedIn() && !exists(/databases/$(database)/documents/schedules/$(scheduleId));
// 생성할 때 소유자를 확인하고, 기존 일정은 저장된 소유자를 기준으로 확인한다.
// 일정을 고칠 때도 소유자는 그대로 유지한다.
allow read, delete: if isSelf(resource.data.userId);
allow create: if isSelf(request.resource.data.userId) && ownsRequestedShareCode();
allow update: if isSelf(resource.data.userId)
&& request.resource.data.userId == resource.data.userId
&& ownsRequestedShareCode();
// 공유 일정은 그 코드의 감시자로 등록한 사람만 읽는다.
allow read: if resource.data.sharedCode is string &&
resource.data.sharedCode != "" &&
watchesShareCode(resource.data.sharedCode);
// 공유 일정의 완료 상태만 바꿀 수 있다. 반복 일정은 회차마다 따로 남으므로
// completedDates 도 같은 범위에 든다(#130).
allow update: if resource.data.sharedCode is string &&
resource.data.sharedCode != "" &&
watchesShareCode(resource.data.sharedCode) &&
request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['completed', 'completedDates', 'updatedAt']);
}
// 알림 기록. 계정 삭제 때 정리하는 용도로만 남아 있다.
match /notifications/{notificationId} {
allow read, write: if isSignedIn() && request.auth.uid == resource.data.userId;
}
// 일정 추천 - 모든 인증된 사용자가 읽기 가능
match /scheduleRecommendations/{recommendationId} {
allow read: if isSignedIn();
allow write: if false; // 관리자만 쓰기 가능하도록 제한
}
// 가족 그룹 - 그룹 멤버만 접근 가능
match /familyGroups/{groupId} {
allow read, write: if isSignedIn() &&
(request.auth.uid == resource.data.ownerUserId ||
request.auth.uid in resource.data.memberIds);
}
// 공유코드 감시자. 이 문서가 있다는 것이 곧 「그 코드를 받은 사람」 이라는 표시라,
// 공유 일정 읽기 권한의 근거가 된다. 서버(Cloud Functions)가 관리자 권한으로 읽어
// 알림을 보낸다.
match /shareCodeWatchers/{shareCode}/tokens/{userId} {
allow read, write: if isSelf(userId);
}
}
}