91 lines
5.0 KiB
Plaintext
91 lines
5.0 KiB
Plaintext
rules_version = '2';
|
|
|
|
// FANUZU — Cloud Storage 보안 규칙
|
|
//
|
|
// 근거
|
|
// - 정책 원장 B-44 「Community 미디어」 — *업로드 → 클라이언트 1차 검증 →
|
|
// Storage 규칙 서버 검증 → 게시*. 상한은 이미지 10MB · 영상 100MB/60초.
|
|
// - firestore.rules 의 `users/{uid}` — `photoUrl` 은 본인이 고칠 수 있는 필드다.
|
|
//
|
|
// 원칙
|
|
// 1) 클라이언트 검증은 **거를 수 있을 뿐 막지는 못한다.** 용량과 형식을 여기서
|
|
// 한 번 더 본다. 앱을 고쳐 올리는 경로가 있어도 규칙은 우회되지 않는다.
|
|
// 2) 남의 경로에는 쓰지 못한다. 경로 첫 칸이 UID 이고 토큰의 UID 와 같아야 한다.
|
|
// 3) 열지 않은 경로는 전부 닫는다(마지막 블록).
|
|
//
|
|
// 다운로드 URL 에 대하여
|
|
// `getDownloadURL()` 이 만드는 URL 은 토큰이 붙은 공개 링크이고 **본 규칙의 read
|
|
// 판정을 지나지 않는다.** 프로필 사진은 프로필 카드·라운지에서 다른 사용자에게
|
|
// 보여야 하므로 그 성격이 맞다. 여기 `read` 는 SDK 로 바이트를 직접 읽는 경로만
|
|
// 가린다.
|
|
|
|
service firebase.storage {
|
|
match /b/{bucket}/o {
|
|
|
|
function isSelf(uid) {
|
|
return request.auth != null && request.auth.uid == uid;
|
|
}
|
|
|
|
// 프로필 사진 — `FirestoreMyRepository.uploadProfilePhoto` 가 쓰는 경로.
|
|
// 사용자당 한 장으로 고정되어 있어 바꿀 때 이전 파일이 남지 않는다.
|
|
match /users/{uid}/profile/{fileName} {
|
|
allow read: if request.auth != null;
|
|
allow write: if isSelf(uid)
|
|
&& request.resource.size <= 10 * 1024 * 1024
|
|
&& request.resource.contentType.matches('image/(jpeg|png|webp|gif)');
|
|
// 사진을 지우는 동선이 아직 없다. 여는 순간 「되돌릴 수 없는 삭제」의
|
|
// 확인 동선까지 필요해지므로 필요해지는 회차에 함께 연다.
|
|
allow delete: if false;
|
|
}
|
|
|
|
// 캠페인 결과보고 집행 사진 · 영수증 — 정책 원장 B-35 · FR-ADM-05 「결과보고는 Admin 수동 운영」.
|
|
// 운영(master·ops)만 올린다. `fn-campaign-report-submit` 이 이 경로 접두와 파일 존재를
|
|
// 다시 본다. 결과보고는 공개 캠페인 상세에 뜨므로 읽기는 열어 둔다. 상한은 B-44 10MB.
|
|
match /campaignReports/{campaignId}/{fileName} {
|
|
allow read: if true;
|
|
allow create: if request.auth != null
|
|
&& request.auth.token.get('role', '') in ['master', 'ops']
|
|
&& request.resource.size <= 10 * 1024 * 1024
|
|
&& request.resource.contentType.matches('image/(jpeg|png|webp|gif)');
|
|
allow update, delete: if false;
|
|
}
|
|
|
|
// 팬덤 라운지 글 사진 — `FirestorePlanetRepository.createPost` 가 쓰는 경로.
|
|
// B-44 「이미지 5 장 10MB」의 1 장 상한 · 형식을 여기서 보고, 장수(5)는
|
|
// firestore.rules 의 `posts.imageUrls.size()` 가 본다. 팬덤 회원 판정도 글 문서
|
|
// 쪽 규칙이 한다 — 회원이 아니면 글이 거부되고 앱이 올린 사진을 지운다.
|
|
// 삭제는 본인만(글 삭제 · 글 저장 실패 시 되돌리기).
|
|
match /users/{uid}/lounge/{fileName} {
|
|
allow read: if request.auth != null;
|
|
// `resource == null` — 덮어쓰기 금지. 글의 URL 이 가리키는 내용이 바뀌면 안 된다.
|
|
// 파일 이름 = 「글 ID(Firestore 자동 ID 20자)-칸 번호(0~4)」. 글 문서의 규칙이
|
|
// 칸마다 같은 경로만 받으므로 다른 이름의 파일은 어디에도 붙지 않는다.
|
|
allow create: if isSelf(uid)
|
|
&& resource == null
|
|
&& fileName.matches('[A-Za-z0-9]{20}-[0-4]')
|
|
&& request.resource.size <= 10 * 1024 * 1024
|
|
&& request.resource.contentType.matches('image/(jpeg|png|webp|gif)');
|
|
allow update: if false;
|
|
allow delete: if isSelf(uid);
|
|
}
|
|
|
|
// 위치 Quest 방문 인증 사진 — 정책 원장 C-03 · PRD-05 「GPS · 사진 · QR 인증」. `fn-quest-verify-place` 가
|
|
// 이 경로 접두(`questProofs/{본인}/{questId}/`)와 파일 존재를 다시 본다. 운영 검토용이라 본인 · 서버만 읽는다.
|
|
// 덮어쓰기 · 삭제 금지 — 제출한 증거가 바뀌면 안 된다. 상한은 B-44 이미지 10MB.
|
|
match /questProofs/{uid}/{questId}/{fileName} {
|
|
allow read: if isSelf(uid);
|
|
allow create: if isSelf(uid)
|
|
&& resource == null
|
|
&& request.resource.size <= 10 * 1024 * 1024
|
|
&& request.resource.contentType.matches('image/(jpeg|png|webp)');
|
|
allow update, delete: if false;
|
|
}
|
|
|
|
// 그 밖의 경로는 닫는다. 인사이드 미디어 · 라운지 영상(B-44 영상 100MB/60초)은
|
|
// 업로드 동선이 들어오는 회차에 같은 방식으로 상한과 함께 연다.
|
|
match /{allPaths=**} {
|
|
allow read, write: if false;
|
|
}
|
|
}
|
|
}
|