Skip to content

친구·그룹 데이터 접근 계약 (이슈 #1028)

화면 픽스처(앱 저장소): src/react/ui/mobile/fixtures/FeedScreen.fixture.ts

이 문서는 명시적 신원 전달 계약(이슈 #1033, 앱 전체 원칙)의 1단(친구/그룹 읽기)이다.

원칙 (오너 결정 2026-08-31): 다른 계정의 데이터를 응답하는 지점은 전용 접근점으로 분리한다. 서버가 요청 처리 중에 "대상 계정인 것처럼" 신원을 바꾸는 방식(JWT sub 클레임 전환, set_config('request.jwt.claim.sub', …))은 친구·그룹 노출 함수에서 금지한다. 계산이 비싸면 내부 엔진(대상 유저를 명시 인자로 받는 함수)을 본인 경로와 공유한다.

1. 구조

데이터 종류마다 세 조각으로 나눈다.

조각형태역할
내부 엔진…_engine(p_user_id, …) — 실행 권한 전면 회수(클라 호출 불가)계산 본문. auth.uid()를 읽지 않고 대상 유저를 인자로 받는다
본인 접근점기존 RPC, 시그니처·payload 불변engine(auth.uid(), …) 위임
친구/그룹 접근점get_following_* / get_group_member_*가시성 게이트 통과 후 engine(p_user_id, …) 직접 호출
  • 열람자(viewer)와 대상(target)은 항상 별개 인자로 다룬다. 응답에 열람자 종속 값(예: liked_by_me)이 있으면 엔진이 p_viewer를 따로 받는다. 두 역할을 한 값으로 뭉개는 것이 2026-08-26 제3자 노출 사고(20260821790000 교정)의 뿌리였다.
  • 접근점을 분리해 두는 이유: ① 사고 시 친구 접근점의 실행 권한만 회수하는 긴급 차단(08-26 실사용) ② 표면별 노출 축소(친구 일지=제목·개수, 친구 피드=본인 글만) ③ 본인 경로 무접촉 ④ 접속 로그에서 친구 조회 식별.

2. 게이트 정본

  • 친구(팔로우) 가시성: profile_feed_can_view_v1(호출자, 대상) — 본인 또는 팔로우. 실패 42501.
  • 공개 동의 게이트(앱 #1625 Phase 4, 2026-09-15): profile_feed_can_view_v1은 본인이 아니면 owner_shares_profile_v1(대상)도 요구한다 — 서버 요구 목록(onboarding_consent_requirements)에 profile_sharing이 없으면(전환 전) 항상 통과, 있으면 대상이 그 버전의 유효한 동의(철회 안 됨)를 가진 경우만 통과. 검색(search_user_profiles_v1)·팔로우(follow_user_v1, 동의 없음 = 차단과 같은 P0002)·팔로잉 목록(list_following_v1)·프로필 사진 정책(profile_image_readable_v1)도 같은 판정을 쓴다. 동의 철회 = 즉시 비공개, 재동의 = 팔로우 행이 남아 있어 즉시 복구.
  • 민감정보 제공 동의(owner_shares_sensitive_v1(대상)): 열람자가 본인이 아니고 대상에게 sensitive_sharing 동의가 없으면 팔로워 응답에서 건강 관련 필드를 뺀다 — get_session_detailstrip_session_detail_sensitive_v1로 session.condition/note/pre_note/post_condition/body_weight_kg, session_exercises[].note/entry_review, exercise_sets[].note를 null(키 유지), 피드 엔진은 review·notes, 종목 카드 엔진은 review, 팔로위 달력은 계획 note를 null. 두 판정 함수는 엔진 등급(service_role 전용)이라 문에서만 호출한다.
  • 그룹 동료 가시성: group_require_member_v1 + group_member_role_v1 + group_content_visible_v1 + 테이크다운 검사 — 실패는 존재 누출 방지 위해 일괄 P0002.
  • 게이트는 접근점(문)에서만 검사한다. 엔진은 게이트를 갖지 않는다(순수 payload 빌더).

3. 친구·그룹 접근점 장부

접근점화면상태
get_following_calendar_month_v1친구 일지 달력전용 조회 (원래부터 전환 없음)
get_following_exercise_favorites_v1친구 즐겨찾기전용 호출 (전환 없음)
get_following_volume_overview_v1친구 리포트전환 → 엔진 직결로 전환 예정 (Phase 2-3)
get_following_pr_overview_v1친구 랜딩 종합기록전환 → 엔진 직결 예정 (Phase 2-1)
get_following_home_dashboard_v1친구 랜딩 레벨·누적전환 → 엔진 직결 예정 (Phase 2-2)
get_following_profile_feed_v1친구 피드전환+자기글 필터 → target/viewer 분리 엔진 예정 (Phase 2-4)
get_group_member_session_v1그룹 멤버 세션 상세전환 → 엔진 직결 예정 (Phase 2-5)

새 친구/그룹 노출 함수를 추가할 때는 이 표에 등재하고, 본문에 신원 전환 코드를 넣지 않는다. 팔로워에게 새 필드를 여는 payload 변경은 그 필드가 건강 관련(체중·컨디션·메모·리뷰)인지 판단해 위 민감정보 제공 동의 판정을 붙인다(#1625) (게이트 테스트가 prosrcset_config('request.jwt.claim.sub' 부재를 상시 단언한다 — Phase 3).

4. 신원 전환의 잔존 허용 범위

set_config('request.jwt.claim.sub', …) 전환은 schema 전체 52곳 중 친구/그룹 접근점 5곳만 이번 트랙에서 제거한다. 나머지(통계 갱신 잡 처리 process_user_exercise_stats_refresh_jobs*, 잡 적재 트리거 enqueue_*, WodUp/Motra 인입·백필, 카탈로그 병합 정비)는 서버 자신이 배치·트리거·관리 작업을 각 유저 명의로 실행하는 내부 기계로, "다른 계정의 호출에 응답"이 아니므로 이 계약의 대상이 아니다. 단 이 함수들도 클라이언트 실행 권한이 없어야 한다.

5. 노출면 규칙

전환 제거 후에도 남는 규칙: 본인용 읽기모델(payload)에 필드를 추가하면 그 함수를 감싼 친구 접근점의 노출도 함께 넓어진다. 위 장부의 접근점이 위임하는 엔진의 payload를 바꾸는 PR은 친구 화면 노출이 적절한지 함께 판단하고, 부적절하면 친구 접근점에서 깎아낸다 (예: 친구 피드의 자기 글 한정, 친구 일지의 제목·개수 한정).