fix: 푸시로 열리는 웹뷰의 학생 토큰 주입과 외부 링크 처리를 고친다 - #40
Open
seongwon030 wants to merge 5 commits into
Open
seongwon030 wants to merge 5 commits into
seongwon030 wants to merge 5 commits into
Conversation
우체통은 로그인 없이 쓰므로 익명 학생 토큰으로 사람을 구분한다. 웹은 앱이 주입한 토큰을 먼저 쓰고(studentFetch), 없으면 자기 토큰을 발급한다. 그러면 앱과 신원이 갈려서 백엔드가 Feedback.studentId로 찾는 편지함이 비어 보인다. 주입이 홈 웹뷰에만 있었다. 답장 푸시는 path=/feedback/letters/<id> 를 보내고 (FeedbackAdminService), use-fcm 은 /webview/clubDetail/ 로 시작하지 않는 path 를 /webview/[slug] 로 넘긴다. 그 화면에는 주입이 없어서, 답장 알림을 탭해 연 편지함이 방금 보낸 편지를 못 찾는다. 주입 스크립트 생성을 utils/webview.ts 로 올려 홈과 공유한다. 가드 기준을 진입 URL이 아니라 모아동 URL로 바꿨다. 기존 홈 코드는 new URL(BASE_URL).origin !== window.location.origin 으로 비교했는데, 홈은 BASE_URL 이 항상 모아동이라 맞지만 webview/[slug] 는 진입 URL 자체가 외부일 수 있어(slug=external) "외부 == 외부" 로 통과해 버린다. 그대로 옮기면 외부 사이트에 베어러 토큰이 주입된다. 그래서 가드가 두 겹이다. RN 쪽에서 진입 URL이 모아동 오리진일 때만 스크립트를 만들고, 스크립트 안에서 실행 시점 origin 을 다시 본다(웹뷰가 나중에 외부로 이동하는 경우). 두 번째 비교는 웹뷰 안에서 한다 - RN 의 URL 폴리필이 호스트 대소문자와 기본 포트를 정규화하지 않아 RN 에서 만든 origin 문자열이 window.location.origin 과 어긋날 수 있다. 주입은 content load 이전에 끝나야 하므로 토큰이 정해질 때까지 웹뷰를 렌더하지 않는다. 외부 URL 진입이면 주입할 일이 없으니 기다리지 않는다. club-detail-screen 에는 넣지 않았다. studentFetch 를 쓰는 페이지를 직접 열지 않는다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 10 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (3)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
두 증상이 한 경로에서 나온다. 홈 웹뷰에서 외부 링크를 탭하면 handleShouldStartLoadWithRequest 가 가로채 /webview/[slug] 로 넘기는데, 그 화면이 목적지를 가리지 않고 appendSessionId 를 붙였다. iOS: session_id 는 웹 Mixpanel 의 distinct_id 다. 그게 쿼리에 실려 제3자 도메인으로 나가 상대 액세스 로그에 남는다. 모아동 오리진일 때만 붙인다. Android: setSupportMultipleWindows 기본값이 true 라 target=_blank 가 onCreateWindow 로 간다. onOpenWindow 핸들러가 없으면 RNCWebChromeClient 가 WebViewClient 도 없는 new WebView(context) 를 만들어 transport 로 넘기는데, 그 뷰는 어떤 계층에도 붙지 않는다. 그래서 링크를 눌러도 아무 일이 안 일어난다. false 로 두면 같은 요청이 onShouldStartLoadWithRequest 를 타 iOS 와 같은 경로가 된다. 둘을 같이 고친다. Android 링크만 살리면 session_id 가 나가는 경로가 Android 로도 번진다. 대상은 preventDefault 없는 생 <a target="_blank"> 들이다(ClubUnionPage 의 인스타·카톡, IntroducePage 의 문의하기). useNavigator 를 거치는 링크는 requestOpenExternalUrl -> WebBrowser 로 나가므로 원래 영향이 없다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3 tasks
request.url.startsWith(baseOrigin) 은 https://moadong.com.evil.com 을 내부 URL 로 승인한다. 그 페이지가 홈 웹뷰에 뜨면 window.ReactNativeWebView.postMessage 로 브리지를 그대로 쓸 수 있다. 이 웹뷰는 onMessage 에서 SUBSCRIBE_TOGGLE, NAVIGATE_WEBVIEW, OPEN_EXTERNAL_URL, SHARE 를 origin 검증 없이 처리한다. 파싱한 origin 끼리 비교한다. #40 에서 추가한 isWebViewOrigin 을 그대로 쓴다. 주입 토큰 쪽은 원래 new URL(...).origin 으로 비교하고 있어 영향이 없었다. setSupportMultipleWindows={false} 로 Android 의 target=_blank 요청도 이 검사에 들어오므로 같이 고친다. CodeRabbit 지적 반영. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
외부 사이트를 앱 화면(WebView)에 띄우고 있었다. 사용자에게는 모아동 헤더가 붙은 채로 뜨고 제목은 기본값 "웹페이지" 라 어느 사이트인지 알 단서가 없다. 주소창도 없다. 기술적으로 더 문제인 건 그 WebView 가 앱 프로세스 안이라는 것이다. onMessage 가 붙어 있어 외부 페이지가 window.ReactNativeWebView.postMessage 로 SUBSCRIBE_TOGGLE, NAVIGATE_WEBVIEW, OPEN_EXTERNAL_URL 을 그대로 호출할 수 있다. WebBrowser.openBrowserAsync 로 넘긴다. iOS SFSafariViewController, Android Chrome Custom Tabs 라 OS 브라우저 프로세스에서 돌고 앱 JS 에 닿지 못한다. 도메인이 표시되고 시스템 브라우저 세션을 공유한다. 새로 도입하는 방식이 아니다. 배너(banner.tsx), 동아리 SNS(useNavigator -> OPEN_EXTERNAL_URL), external-link.tsx 가 이미 같은 함수를 쓴다. slug=external 경로만 예외로 남아 있었다. 열기 실패는 로그만 남긴다. 실패해도 웹뷰 이동은 이미 취소된 상태라 조용히 아무 일도 안 일어나면 원인을 알 수 없다. router 는 이 콜백에서 더 쓰이지 않아 deps 에서 뺀다. handleMessage 의 내부 라우팅에는 그대로 쓴다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
앞 커밋으로 홈의 외부 링크가 OS 브라우저로 가면서, 앱 안에서 이 화면에 url 파라미터를 넘기는 곳이 없어졌다. /webview/[slug] 로 가는 호출 4곳은 모두 path 나 slug 만 넘긴다. 그런데 파라미터를 받는 쪽이 남아 있으면 moadongapp://webview/x?url=https://... 딥링크로 임의 사이트를 이 화면에 띄울 수 있다. 이 화면은 onMessage 가 붙어 있어서 그 페이지가 window.ReactNativeWebView.postMessage 로 앱 브리지를 그대로 쓴다. 사용자에게는 모아동 헤더가 붙은 화면으로 보인다. 목적지를 path 또는 pageConfig 로만 정한다. 등록되지 않은 slug 는 기존 오류 화면으로 간다. 오리진 가드는 그대로 둔다. pageConfig 에 외부 URL 항목(privacy-policy 의 notion 주소)이 남아 있어 baseUrl 이 모아동이 아닐 수 있다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
seongwon030
added a commit
that referenced
this pull request
Sep 30, 2026
…e-unknown-message-reporting #40 과 충돌하는 두 파일을 미리 해소한다. 내용은 겹치지 않고 추가 위치만 겹쳤다. - utils/webview.ts: 양쪽이 파일 끝에 서로 무관한 함수를 붙였다. #40 의 URL/오리진 헬퍼(isWebViewOrigin, buildStudentTokenInjection)를 appendSessionId 뒤에 모으고, 진단용 reportUnknownBridgeMessage 를 맨 끝에 뒀다. - ui/home/home-webview-screen.tsx: import 한 줄만 충돌했다. 두 목록을 합쳤다. 양쪽 변경이 모두 살아 있는 것을 확인했다. #35 쪽은 네 지점의 리포팅 (home / club_detail / webview_slug / use-webview-message-handler), #40 쪽은 토큰 주입·session_id 오리진 가드·origin 비교·외부 링크 브라우저 전환·urlParam 제거. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
앱이 여는 웹뷰의 경계 문제 모음입니다.
utils/webview.ts,ui/home/home-webview-screen.tsx,app/webview/[slug].tsx.1.
ddddf1c— 답장 알림으로 연 편지함이 비어 보인다우체통은 로그인 없이 쓰므로 익명 학생 토큰으로 사람을 구분합니다. 웹은 앱이 주입한 토큰을 먼저 쓰고(
studentFetch.ts:98), 없으면 자기 토큰을 발급합니다. 그러면 앱과 신원이 갈려 편지함이 비어 보입니다.PR #28이 주입을 넣었지만 홈 웹뷰에만 있었습니다:
FeedbackAdminService.java:239-240— 답장 푸시가path=/feedback/letters/<id>전송use-fcm.ts:36-50—/webview/clubDetail/로 시작하지 않는path는/webview/[slug]로[slug].tsx— 주입 없음studentFetch.ts:102-103— localStorage도 비어 있어(주입 경로는 localStorage에 안 씀) 새 신원 발급주입 스크립트 생성을
utils/webview.ts로 올려 공유합니다.가드 기준을 진입 URL이 아니라 모아동 URL로 바꿨습니다. 기존 홈 코드는
new URL(BASE_URL).origin !== window.location.origin인데,[slug]는 진입 URL 자체가 외부일 수 있어 "외부 == 외부"로 통과합니다. 그대로 옮겼으면 외부 사이트에 베어러 토큰이 주입됐습니다.가드가 두 겹입니다 — RN 쪽(진입 URL)과 스크립트 안(실행 시점 origin). 두 번째를 웹뷰 안에서 하는 건 RN의 URL 폴리필이 호스트 대소문자·기본 포트를 정규화하지 않아 RN에서 만든 origin 문자열이
window.location.origin과 어긋날 수 있기 때문입니다(기존 주석의 지식).club-detail-screen.tsx에는 넣지 않았습니다 —studentFetch를 쓰는 페이지를 직접 열지 않습니다.2.
e537a34— 외부 링크에session_id가 붙고 Android에선 안 열린다iOS:
session_id는 웹 Mixpanel의 distinct_id인데[slug].tsx가 목적지를 안 가리고 붙여서 제3자 도메인 로그에 남았습니다. → 모아동 오리진일 때만 붙입니다.Android:
setSupportMultipleWindows기본값true라target='_blank'가onCreateWindow로 갑니다.onOpenWindow핸들러가 없으면RNCWebChromeClient.java:89-114가WebViewClient도 없는new WebView(context)를 만들어 넘기는데 그 뷰가 어떤 계층에도 안 붙습니다 → 무반응. →setSupportMultipleWindows={false}.둘을 같이 고칩니다. Android 링크만 살리면
session_id가 나가는 경로가 Android로도 번집니다.3.
0042397— 내부 URL 판정이 prefix 비교였다 (CodeRabbit 지적)확인한 판정 차이:
moadong.com/clubsmoadong.com.evil.com/pwnmoadong.community-evil.io/instagram.com/x파싱한 origin끼리 비교합니다(
isWebViewOrigin재사용). 고아가 된baseOrigin지역변수는 제거했습니다. 주입 토큰 가드는 원래new URL(...).origin비교라 이 경로로는 유출되지 않았습니다.지적의 나머지("
onMessage에도 origin 검증")는 4번이 해결합니다 — 외부 페이지가onMessage붙은 WebView에 아예 뜨지 않게 됩니다.4.
d386506— 외부 사이트를 앱 화면에 띄우고 있었다외부 URL이
[slug].tsx의<WebView>에 로드됐습니다. 사용자에게는 모아동 헤더가 붙고 제목은 기본값 "웹페이지" 로 떠서 어느 사이트인지 알 단서가 없습니다. 기술적으로는 앱 프로세스 안이라 그 페이지가window.ReactNativeWebView.postMessage로 브리지를 그대로 씁니다.WebBrowser.openBrowserAsync로 넘깁니다(iOSSFSafariViewController, Android Custom Tabs). OS 브라우저 프로세스라 앱 JS에 닿지 못하고, 도메인이 표시되며, 시스템 브라우저 세션을 공유합니다.새로 도입하는 방식이 아닙니다.
banner.tsx:328,external-link.tsx:18,OPEN_EXTERNAL_URL핸들러가 이미 같은 함수를 씁니다.slug: 'external'경로만 예외였습니다.대상:
ClubUnionPage.tsx:76,89(인스타·카톡),ContactSection.tsx:19(문의하기) —preventDefault없는 생<a target="_blank">.검증
tsc --noEmit통과 (exit 0)npm run lint— 0 errors, 5 warnings 전부 기존import/no-named-as-defaultsession_id6가지 (모아동만 부착)intent://그대로)실기기 확인
#39가 선행입니다. iOS는 푸시 탭이 화면 이동을 못 해 이 화면에 도달하지 못합니다.
session_id없음session_id가 그대로 붙어 웹 Mixpanel identify 동작5.
e9d9989— 목적지를url파라미터로 받지 않는다4번으로 앱 안에서 이 화면에
url을 넘기는 곳이 없어졌습니다(/webview/[slug]호출 4곳 모두path나slug만 넘김). 그런데 받는 쪽이 남아 있으면moadongapp://webview/x?url=https://...딥링크로 임의 사이트를 이 화면에 띄울 수 있습니다. 이 화면은onMessage가 붙어 있어 그 페이지가 앱 브리지를 그대로 쓰고, 사용자에겐 모아동 헤더가 붙은 화면으로 보입니다.목적지를
path또는pageConfig로만 정합니다.use-fcm,path=/feedback/letters/L1)moadong.com/feedback/letters/L1path)moadong.com/promotions/...slug만,pageConfig)moadong.com/introduce?url=오리진 가드는 그대로 뒀습니다 —
pageConfig에 외부 URL 항목(privacy-policy의 Notion 주소)이 남아 있어baseUrl이 모아동이 아닐 수 있습니다.남는 것
isUserInitiated가 false인 경로(서버 리다이렉트로 외부 이동)는 그대로 홈 웹뷰에 로드됩니다.pageConfig['privacy-policy']의 Notion 주소는 도달 불가한 죽은 설정입니다 — 개인정보처리방침이 웹으로 이관됐고(be032f8e7), 여기로 들어가는 유일한 길인requestNavigateWebview는 웹에 호출처가 0건입니다. 이 PR과 무관해서 건드리지 않았습니다.