-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfirebase.json
More file actions
120 lines (120 loc) · 7.63 KB
/
Copy pathfirebase.json
File metadata and controls
120 lines (120 loc) · 7.63 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
{
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"//": "경기별 페이지. 순서가 중요합니다 — 아래 ** 규칙이 먼저면 /m/ 도 index.html 로 삼켜져서 이 함수까지 오지 않습니다. Firebase 는 rewrites 를 위에서부터 먼저 맞는 것 하나만 씁니다.",
"//왜 함수인가": "/?m=A 와 /?m=B 가 서버에서 바이트까지 같은 HTML 이라 구글에는 한 페이지로 보입니다. 카톡·디시 크롤러도 자바스크립트를 안 돌려서 경기별 미리보기를 못 만듭니다. 경기마다 다른 <head> 와 읽을 본문을 서버가 만들어 줘야 합니다.",
"source": "/m/**",
"function": {
"functionId": "page",
"region": "asia-northeast3"
}
},
{
"source": "**",
"destination": "/index.html"
}
],
"headers": [
{
"source": "**/*.@(html|js|json)",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache, must-revalidate"
}
]
},
{
"//": "⚠️ 루트는 '/' 라서 확장자가 없고, 위의 **/*.@(html|js|json) 에 안 걸립니다. 그래서 index.html 만 Firebase 기본값인 max-age=3600 으로 나갔습니다 — 배포해도 돌아온 방문자에게는 최대 한 시간 동안 옛 화면이 보입니다.",
"//더 나쁜 것": "index.html 과 config.js 의 캐시 수명이 서로 달라집니다. 새 index.html 이 옛 config.js 를 만나면 'does not provide an export named ...' 로 모듈이 통째로 죽고 화면이 하얗게 뜹니다. 실제로 TRAFFIC_URL 을 추가했을 때 그렇게 죽었습니다. 두 파일은 항상 같이 갱신돼야 합니다.",
"source": "/",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache, must-revalidate"
}
]
},
{
"//": "사용자가 쓴 이름과 글을 innerHTML 로 그리는 앱입니다. 저장형 XSS 가 한 곳이라도 남았을 때 실제 피해를 막는 마지막 그물이 여기입니다.",
"//script-src": "인라인 스크립트는 해시로 허용합니다. 'unsafe-inline' 을 쓰면 onfocus= 같은 속성 핸들러까지 열려서 정작 막고 싶은 게 안 막힙니다. ⚠️ index.html 의 <script> 를 고치면 이 해시도 바뀝니다 — ./security/run.sh 가 어긋난 걸 잡아주고, python3 security/csp.py --write 로 갱신합니다.",
"//apis.google.com": "signInWithPopup 이 gapi(apis.google.com/js/api.js)를 받아야 팝업을 띄웁니다. 없으면 팝업이 통째로 막혀서 모든 구글 로그인이 리다이렉트로 떨어집니다 — 동작은 하지만 페이지가 한 번 나갔다 옵니다. 구글이 직접 운영하는 도메인이고 여기 실린 스크립트는 우리 데이터에 접근할 수단이 없습니다. connect-src 에는 넣지 않습니다: 스크립트를 받는 것과 데이터를 보내는 것은 별개이고, 출구는 계속 좁게 둡니다.",
"//connect-src": "토큰이 새어 나가는 출구를 막습니다. 스크립트가 실행되더라도 아무 데로나 보낼 수 없습니다. gstatic 은 소스맵(.js.map) 때문에 넣었습니다 — 개발자도구를 열었을 때만 받아오는 파일이라 방문자에게는 영향이 없지만, 콘솔이 차단 경고로 도배되면 진짜 에러가 묻힙니다. 정적 CDN 이라 데이터를 보내 보관할 수 있는 종류의 출구가 아닙니다.",
"//frame-ancestors": "클릭재킹. 이 앱은 주요 동작이 전부 버튼 한 번이라 특히 잘 맞는 공격입니다.",
"//worker-src": "웹 푸시용 서비스워커(/firebase-messaging-sw.js). default-src 'none' 이라 명시하지 않으면 등록 자체가 막히고, 알림은 조용히 안 켜집니다 — 콘솔을 안 보면 원인을 못 찾습니다. 'self' 뿐이라 우리 파일만 워커가 될 수 있습니다.",
"//manifest-src": "manifest.json 도 default-src 'none' 에 걸립니다. 이게 막히면 아이폰에서 '홈 화면에 추가' 가 웹앱으로 안 잡히고, 그러면 iOS 알림이 통째로 불가능해집니다.",
"//푸시 connect-src": "FCM 등록(fcmregistrations.googleapis.com)과 설치 ID(firebaseinstallations.googleapis.com)는 이미 https://*.googleapis.com 에 포함됩니다. 따로 열 것이 없습니다.",
"source": "**",
"headers": [
{
"key": "Content-Security-Policy",
"value": "default-src 'none'; script-src 'self' 'sha256-EXadNym9V7m/XG66nbaniEos+IIoWDncsneD1VbsXyo=' 'sha256-5AlTMaup9jwygbIBpY/gYl3DHIWPDTY0LxeuHZOUc7g=' https://www.gstatic.com https://apis.google.com https://t1.kakaocdn.net https://www.google.com https://www.recaptcha.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://static.lolesports.com https://ddragon.leagueoflegends.com https://*.kakaocdn.net https://firebasestorage.googleapis.com; font-src 'self'; connect-src 'self' https://www.gstatic.com https://*.googleapis.com https://asia-northeast3-test-adsense-vfgjfy.cloudfunctions.net https://*.kakao.com https://*.kakaocdn.net https://www.google.com https://www.recaptcha.net; frame-src 'self' https://*.firebaseapp.com https://www.google.com https://www.recaptcha.net; worker-src 'self'; manifest-src 'self'; form-action 'none'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "Referrer-Policy",
"value": "strict-origin-when-cross-origin"
},
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains"
},
{
"key": "Permissions-Policy",
"value": "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
}
]
}
],
"target": "main"
},
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"//storage": "커뮤니티 글에 붙는 이미지 하나만 받습니다. 크기·형식은 규칙이 막고, 무엇이 찍혀 있는지(저작권)는 신고함이 막습니다 — storage.rules 참고.",
"storage": {
"rules": "storage.rules"
},
"functions": [
{
"source": "functions",
"codebase": "default"
},
{
"//": "LCK 경기 동기화 스케줄 함수. 파이썬입니다 — sync_matches.py 의 KDA·레이더 계산을 Node 로 다시 쓰지 않으려고 언어를 그대로 뒀습니다. 로컬 CLI(functions-sync/sync_matches.py)와 같은 파일을 씁니다. ⚠️ 배포하려면 functions-sync/venv 가 **이 런타임과 같은 버전**으로 만들어져 있어야 합니다: python3.14 -m venv functions-sync/venv && functions-sync/venv/bin/pip install -r functions-sync/requirements.txt",
"source": "functions-sync",
"codebase": "sync",
"runtime": "python314"
}
],
"//emulators": "5000 은 macOS ControlCenter(AirPlay)가 잡고 있어 피합니다. 응답 헤더를 실제로 확인하려면 hosting 에뮬레이터로 띄워야 합니다 — python http.server 에는 firebase.json 의 헤더가 붙지 않습니다.",
"emulators": {
"hosting": {
"port": 5055
},
"auth": {
"port": 9099
},
"firestore": {
"port": 8080
},
"ui": {
"enabled": false
}
}
}