프론트엔드백엔드
게스트가 마이페이지도 못 열던 문제 — 탭별 접근 정책 한 곳에서 정의하기 — POP-SPOT v2.8
동
김동현v2.7 게스트 재설계 후, 게스트가 마이페이지·동행 여권도 차단되던 것을 수정. 지도·마이페이지·동행 여권은 게스트 OK / 코스·음악·메이트는 회원전용. 탭 클릭/sessionStorage 복원/URL 직접 진입 세 곳 모두 같은 함수 호출.
v2.7 에서 게스트 시작 방식을 고쳐놓고 알고 보니 게스트가 마이페이지·동행 여권을 열려고 하는 순간 "로그인이 필요해요" 가 떨어졌다. 게스트 모드가 사실상 무의미해지는 구조.
이 글에서 다루는 것
모르는 단어 한 줄로
| 용어 | 한 줄 설명 |
|---|---|
| sessionStorage | localStorage 와 비슷하지만 탭/창 닫으면 지워지는 임시 저장소 |
| SearchParams | 주소 끝의 <code>?tab=music</code> 같은 쿼리 파라미터 |
| 권한 검사 함수 | "이 사용자가 이 탭을 열 수 있는가?" 를 단 한 곳에서 판단하는 함수 |
무엇이 바뀌었나
| 항목 | v2.7 | v2.8 |
|---|---|---|
| 게스트의 탭 접근 | 마이페이지/동행 여권/메이트 모두 차단 | 마이페이지/동행 여권은 게스트도 OK / 코스·음악·메이트는 회원만 |
| 회원전용 탭 시도 시 | "로그인이 필요합니다" (구분 없음) | 게스트 → "회원 전용 기능", 비로그인 → "로그인 필요" |
| sessionStorage 복원 | 이전 탭이 코스면 게스트도 그대로 진입 가능 | 권한 검사 후 불가면 지도 탭으로 대체 |
| <code>?tab=music</code> URL 진입 | 주소만으로 진입 가능 (우회) | 같은 권한 검사로 차단 |
| 인트로 안내 | 게스트 옵션 안 보임 | 첫/마지막 화면에 "게스트로 7일" 안내 |
왜 이렇게 했음
게스트의 "둘러보기" 의미 — 단순 조회 (지도·팝업 리스트) 는 게스트가 어차피 볼 수 있으니 제한하면 도리어 불친절함. 마이페이지·동행 여권은 내 계정 기준 표시이니 비워있는 상태로 보이면 됨. 대신 "계정이 있어야 내용이 생기는" 기능 (코스 저장, 음악 패스포트, 메이트 메시지) 은 게스트는 차단하게.
메시지를 구분한다 — "로그인이 필요합니다" 는 비로그인 사용자에게 맞는 문구. 게스트 사용자에게는 "회원이어야 한다" 가 있으므로 "회원 전용 기능이에요" 이 정확.
단일 진실의 원천 — 탭 접근 권한을 메인 파일 최상단에 한 번 정의하고, 탭 클릭·sessionStorage 복원·URL 진입 세 군데가 모두 그 함수를 호출하게 하면 한 곳만 고쳐도 세 곳이 모두 동일.
코드로는 어떻게 (필요한 부분만)
파일: popspot-frontend/src/app/page.tsx (메인 페이지 최상단 + 내부 이벤트 핸들러)
typescript
// v2.8 — app/page.tsx 최상단. 권한 결정의 단일 진실 원천
const MEMBER_ONLY = ['course', 'music', 'mate'] as const;
// 위: 회원만 접근 가능한 탭 이름 목록. 그 외 (map, mypage, passport) 은 게스트도 OK
// 'as const' — readonly ['course', 'music', 'mate'] 타입으로 고정. 타입 검사가 더 엄격
function canAccess(tab: string, user: User | null, guest: GuestState) {
// 위: 권한 판단 함수. 세 곳 (탭클릭/storage/URL) 에서 공통으로 호출
if (user) return true;
// 위: 로그인한 사용자는 아무 탭이나 접근 가능
if ((MEMBER_ONLY as readonly string[]).includes(tab)) {
// ^^^^^^^^^^^^^^^^^^^^^ TS 가 readonly tuple 을 string[] 로 볼 수 있도록 넓힘
// ^^^^^^^^^^^^ 실행 시에는 단순 배열 포함 검사
return { ok: false, reason: guest.active ? 'memberOnly' : 'needLogin' };
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 메시지가 달라지는 이유
// memberOnly: 게스트 사용자 → "회원 전용 기능"
// needLogin: 비로그인 → "로그인 필요"
}
return guest.active ? true : { ok: false, reason: 'needLogin' };
// 위: 회원 전용은 아닌 탭 (map 등) — 게스트 활성이면 허용, 아니면 로그인 요구
}
// v2.8 — 호출 지점 1: 탭 클릭 이벤트
const handleTabClick = (tab: string) => {
const r = canAccess(tab, user, guest);
if (r === true) setTab(tab);
// 위: 허가되면 탭 전환
else notify.error(r.reason === 'memberOnly'
? '회원 전용 기능이에요' : '로그인 필요');
// 위: 거부 이유에 따라 메시지 구분·표시. r 은 이제 { ok:false, reason } 구조
};
// v2.8 — 호출 지점 2: 이전 방문 세션의 탭 복원
useEffect(() => {
const saved = sessionStorage.getItem('tab');
// 위: 이전에 마지막에 보고 있던 탭 이름 (세션 유지)
if (saved && canAccess(saved, user, guest) === true) setTab(saved);
else setTab('map');
// 위: 복원하려는 탭에 권한이 있으면 설정, 없으면 기본 'map' 탭으로 대체
// v2.7 에서는 권한 검사 없이 복원해서 게스트가 course 탭으로 잠입되는 우회로가 될 수 있었음
}, []);
// v2.8 — 호출 지점 3: URL 의 ?tab=music 으로 직접 진입
useEffect(() => {
const fromUrl = searchParams.get('tab');
// 위: useSearchParams 훅에서 쿼리 읽기
if (fromUrl && canAccess(fromUrl, user, guest) !== true) {
router.replace('/');
// 위: 권한 없는 URL 은 루트로 교체. replace 는 히스토리 남기지 않음 (뒤로가기로 돌아올 수 없음)
}
}, [searchParams]);
// 세 지점 모두 동일한 canAccess() 를 호출 — 규칙 변경 시 한 곳만 차례 고치면 됨핵심 파일: popspot-frontend/src/app/page.tsx, popspot-frontend/src/app/intro/page.tsx
관련 글
이전 글주소창 권한 위조 / 티켓 도용(IDOR) / X-Forwarded-Host 헤더 위조 한 번에 잡기 — POP-SPOT v2.7 보안 Critical 3 건 (+v2.7.1/2)다음 글sweetalert2 호출 19 곳 통합 + 환경변수 단일화 + 코스 저장 가드 버그 수정 — POP-SPOT v2.6 프론트 결합도 정리
관련 글
인트로 페이지 제거 → 루트가 메인 직행 + AuthGuard 스피너 게이트 제거로 SEO 색인 해결 — POP-SPOT v2.23 (+v2.23.1/2)발견형 서비스인데 모든 방문자가 5 섭션 인트로→ENTER 거쳐야 메인 도달. 이탈을 키우는 구조라 제거. v2.23.1·2 은 SEO 색인 이시합 해결 — /popups/[slug] 이 크롤러를 /login 으로 튕긴게 수정하고, AuthGuard 의 스피너 게이트를 제거해 공개 페이지가 실제 HTML로 서버 렌더링 되도록.전면 보안 감사 + 수정 — IDOR 5 곳 · 저장형 XSS · 메모리/스토리지 누수 · 클린코드 점검 — POP-SPOT v2.22인증·인가·인젝션·암호화·프론트·컴플라이언스 6 영역 병렬 감사. C1 IDOR (Stamp/MyPage/Mate/ChatFile) + C2 저장형 XSS (카카오 로드뷰 오버레이) + H1 인증 없는 업로드 + H2/H3 무한 증가 인메모리 맵 + H4 SSE emitter 누수 + H5 토큰 노출. 동시에 운영 점검 4 건 (신고 어뻐징 · 크롤링 입력 정제 · 이메일 열거 · 백업 하드닝).Spotify OAuth + Web Playback SDK 통합 — 3-tier 재생 엔진 (Premium SDK / iTunes preview / YouTube) — POP-SPOT v2.21 S10~S18Spotify OAuth 백엔드(AES-256-GCM 토큰 암호화) + Web Playback SDK 프론트 통합 + iTunes preview 폴백 + YouTube 폴백의 3-tier 재생 엔진. Premium 은 320kbps 풀트랙, Free/미연결은 30~90초 preview, 외엔 YouTube 폴백. S17/S18 에서 운영 버그 네 건 추가 수정.