Skip to content

받은 기록함이 서버 목록 API 를 쓰지 않고 로컬 stub 으로 동작 — record-boxes 미연동 #607

Open
1 of 1 issue completed

Description

@1hyok

2026-09-08 범위 분리·착수 조건

접근 자격 결정·타 발신자 마스터키 비노출 서버 계약 검증을 #1949로 분리했다. 아래 API/필드 표는 과거 실측 자료이며 최종 구현 계약이 아니다. 이 이슈는 #1949와 기존 BE#93의 확정 결과를 현재 로컬 목록과 대조하고 실제 필요한 연동 범위를 정한다. 미확정 응답·접근 헤더를 구현 계획으로 예약하지 않는다. 목록이 같은 이메일 기준이라는 사실만으로 안전한 사용자 격리가 성립했다고 간주하지 않는다.


📜 Overview (Required)

받은 기록함 목록이 서버 API 를 쓰지 않고 로컬 in-memory stub 으로 동작합니다. 서버에는 이미 목록 조회 API 가 있는데 FE 가 한 번도 호출하지 않습니다.

서버 계약 — GET /api/v1/receiver-auth/record-boxes (Swagger 실측, 2026-07-29)

"수신자 접근 코드로 같은 이메일에 등록된 모든 받은 기록함 목록을 조회합니다."

헤더 X-Auth-Code 가 required 고, 응답 ReceivedRecordBoxResponse 는 카드가 필요로 하는 값을 전부 내려줍니다.

필드 설명
receiverId 수신자 ID
accessCode 기록 열람 시 X-Auth-Code 에 넣을 접근 코드
senderName 발신자 이름
receiverName 수신자 이름
relation 발신자와 수신자의 관계
recordStatus 받은 기록함 보관 상태
viewStatus 열람 상태
verificationStatus 서류 인증 상태
requestedAt · approvedAt 열람 신청일 · 승인일

FE 현황 — record-boxes·RecordBox 전체 검색 결과 호출 0건입니다. 대신 SenderRegistry 가 카드를 메모리에 보관하고, 사용자가 마스터 키를 넣을 때마다 로컬 항목이 쌓입니다. 코드에 판단 근거가 그대로 남아 있습니다.

// 발신자 라벨 등록 API 가 미확정이라 [SenderRegistry] 로컬 stub 에 보관한다.

SenderRegistrationViewModel.kt:16 — 같은 취지의 주석이 ReceivedRecordsViewModel.kt:11 에도 있습니다("백엔드 발신자 리스트 조회 API").

즉 API 가 없다고 보고 로컬로 대체해 둔 것인데 실제로는 서버에 있었습니다. 그래서 목록이 "코드를 넣으면 서버가 주는 것"이 아니라 "코드를 넣을 때마다 로컬에 쌓이는 것"이 되어 있습니다.

이 하나가 여러 증상의 공통 원인입니다.

착수 전 — 코드 체계 (2026-07-29 실서버 실측)

수신자 흐름에 등장하는 값은 두 가지입니다.

서버 필드명 형식 용도
이메일 인증번호 authCode (email/verify 요청) 6자리 숫자 이메일 소유 증명
마스터 키 = 접근 코드 authCode(발급·검증) / accessCode(조회 응답) UUID X-Auth-Code 헤더에 넣는 값

마스터 키와 접근 코드는 같은 값입니다. 실측:

  1. GET /users/receivers/11 → authCode = 59c04a15-… (발신자가 수신자에게 전달하는 마스터 키)
  2. 그 값을 그대로 X-Auth-Code 헤더에 넣어 GET /receiver-auth/record-boxes 호출 → HTTP 200, 목록 1건
  3. 응답 항목의 accessCode 가 요청에 쓴 값과 동일

즉 마스터 키 하나로 "같은 이메일에 등록된 모든 받은 기록함" 목록이 나옵니다. 별도의 세션 코드를 발급받는 단계는 없습니다.

주의할 점:

  • authCode 라는 이름이 6자리 이메일 인증번호와 UUID 마스터 키 양쪽에 쓰입니다. 요청 스키마를 볼 때 형식으로 구분해야 합니다(BE feat:이용자 홈 화면 UI #92·refactor: theme 관련 네이밍 변경으로 인한 리팩토링 #84 에서 사용자 노출 문구 기준으로 같은 혼동을 지적해 둔 상태).

  • X-Auth-Code 를 요구하는 엔드포인트는 13개(record-boxes, after-notes, diary, time-letters, message, daily-question, deep-thought, delivery-verification, presigned-url 등)로, 수신자 콘텐츠 조회 전체가 이 값 하나에 걸려 있습니다.

  • BE Feat/73 #74(email/verify 응답에서 accessCode 삭제)가 반영되면 마스터 키를 얻는 경로는 발신자가 직접 전달하는 것뿐이 됩니다. 이는 당시 응답에 대한 관찰이며, 현재 구현할 인증 헤더·권한 계약은 최종 산출물로 다시 확인합니다.

  • 마스터 키 하나가 그 이메일의 기록함 전체를 엽니다. 서버가 키를 이메일로 확장하기 때문입니다 — ReceiverAuthService.getReceivedRecordBoxes()(BE release):

    Receiver authenticatedReceiver = findReceiverByAuthCode(authCode);
    String email = normalizeEmail(authenticatedReceiver.getEmail());
    List<Receiver> receivers = receiverRepository.findAllByEmailIgnoreCaseOrderByIdDesc(email);

    즉 권한 근거는 키가 아니라 이메일 동일성입니다. 개별 조회(getReceivedRecordBox)도 인증 수신자와 대상 수신자의 이메일이 다르면 NOT_ENOUGH_PERMISSION 을 던지는, 같은 규칙입니다.

    따라서 발신자 A 가 준 키로 발신자 B·C 의 기록함 목록(발신자명·관계·보관/열람 상태)까지 표시됩니다. 연동하면 이 동작이 그대로 화면에 드러나므로 feat(afternote): 수신자 본인확인 이메일 인증 stub → 실 API(receiver-auth/email) 전환 #407·수신자 본인 확인 캐시가 발신자 구분 없이 전역 저장 — 첫 인증 후 이메일 관문 무력화 #597 의 보안 논의와 함께 봐야 합니다. 콘텐츠 실제 열람은 viewStatus·delivery-verification 승인이 별도로 걸립니다.

  • 받은 기록함 발신자 카드가 앱 재시작 시 소실 — SenderRegistry 가 메모리 전용 #599 카드가 앱 재시작 시 소실 — 서버가 목록의 원본이 되면 로컬 영속 저장 자체가 불필요해집니다. 그 이슈에서 검토 중이던 DataStore 저장·마스터 키 평문 보관 문제도 함께 사라집니다.

실기기 재현 — 서버는 승인, 화면은 열람 불가:

Image

📌 Child Issue(Optional)

No response

📍 Note (Optional)

Activity

  1. self-assigned this
    on Jul 29, 2026
  2. added theissue type on Jul 29, 2026
  3. 1hyok commented on Jul 30, 2026

    @1hyok
    CollaboratorAuthor

    확증 — record-boxes 는 화면이 필요한 값을 전부 준다

    열람 승인된 대조군 수신자(receiverId=18)로 2026-07-30 실측. GET receiver-auth/record-boxes 200:

    {"receiverId":18,"accessCode":"bd22c849-…","senderName":"QA407Sender",
     "receiverName":"QA730Receiver","relation":"DAUGHTER","recordStatus":"STORED",
     "viewStatus":"VIEWABLE","verificationStatus":"APPROVED",
     "requestedAt":"2026-07-29T16:58:36","approvedAt":"2026-07-30T04:25:42"}

    앱 전체 흐름(받은 기록함 → 발신자 상세 → 열람)에서 이 API 는 한 번도 호출되지 않는다(logcat OkHttp 전 구간 확인).

    이 미연동이 만드는 개별 증상들이 각각 이슈로 올라와 있는데, 전부 위 응답 한 번으로 해소된다.

    증상 이슈 record-boxes 대응 필드
    발신자 카드가 앱 재시작에 소실 #599 목록 자체가 서버 소유
    여러 수신자가 기기 공유 시 미격리 #598 receiverId·accessCode 별 레코드
    승인일 빈 값 #612 approvedAt
    카드 '마지막 확인' 빈 값 #508 requestedAt

    부수 확인 — 열람 신청 흐름에도 같은 문제가 있다. 서버가 verificationStatus: APPROVED · viewStatus: VIEWABLE 인 상태에서 "열람 신청하기" 로 진입하면 앱이 이메일 인증 → 마스터 키 → 증빙 서류 업로드까지 4단계를 처음부터 다시 요구한다. 승인 상태를 조회하지 않기 때문이다. 뒤로 나갔다 상세로 재진입하면 그제서야 "열람 가능" 으로 바뀐다. 서류를 실제로 재업로드하면 새 PENDING 이 생겨 승인이 풀릴 수 있는 경로다.

  4. 1hyok commented on Aug 2, 2026

    @1hyok
    CollaboratorAuthor

    실서버 승인 상태에서도 화면이 서버를 전혀 보지 않는 것을 실기기로 확인

    2026-08-02, Pixel_7_Claude_QA / debug 빌드(서버 인증서 검증 우회). 대조군을 새로 만들어 승인까지 끝낸 상태입니다 — 수신자 QACapReceiver(id 19), 사후 전달 조건 AFTERNOTE / RECEIVER_REQUEST, 사망확인 서류 id 8 APPROVED.

    서버는 이미 열람을 허용합니다.

    GET /api/v1/receiver-auth/after-notes   (X-Auth-Code)
    <-- 200 {"afternotes":[{"id":8,"title":"인스타그램","category":"SOCIAL",
             "leaveMessage":"캡처용 애프터노트","senderName":"일혁", …}],"totalCount":1}
    

    그런데 앱은 그대로 "열람 불가 / 신청 기록이 없습니다 / 승인 기록이 없습니다" 입니다.

    Image

    결정적인 건 네트워크 로그입니다 — 앱 시작 → 받은 기록함 → 발신자 등록 → 발신자 상세까지 가는 동안 OkHttp 요청이 0건입니다. 실패해서 폴백된 게 아니라 애초에 호출하지 않습니다. record-boxes 미연동이라는 본문 진단 그대로입니다.

    부수 확인 — 같은 흐름에서 앱을 재시작하면 방금 만든 발신자 카드가 사라집니다(#599). 카드가 로컬 전용이라는 것과 같은 뿌리입니다.

    이 stub 때문에 #617(수신 목록 카테고리 enum)·#611(403 원인 미안내)은 앱에서 재현 경로 자체가 막혀 있습니다 — 서버는 200 을 주는데 목록 화면까지 갈 수단이 없습니다. 이 이슈를 먼저 처리해야 그 둘을 확인할 수 있습니다.

  5. 1hyok commented on Aug 25, 2026

    @1hyok
    CollaboratorAuthor

    재현 확인 (develop 4e0a648eb, 2026-08-25 15:12 KST, 서버 정상 응답 상태)

    발신자 등록(받은 기록함 + → 이름 입력 → 등록)을 실행했으나 OkHttp 로그에 대응 요청이 전혀 없습니다. 마지막 네트워크 로그가 15:10:28(앱 시작 시 조회 5건)이고, 15:12 등록 시점에는 요청이 발생하지 않았습니다.

    결과적으로 등록분이 서버에 남지 않고 앱 재시작 시 소실됩니다(#599 에 상세 기록).

    서버는 같은 시각 정상 응답 중이었습니다 — GET /api/v1/daily-questions/today 가 0.08초에 401(인증 필요)을 반환.

  6. 1hyok commented on Aug 25, 2026

    @1hyok
    CollaboratorAuthor

    승인 이후 화면이 이 이슈 때문에 통째로 막힌다 — 실측 (develop 4e0a648eb, 2026-08-25 18:34 KST)

    admin 승인 API 로 이 수신자(receiverId=14)의 사망확인을 APPROVED 로 만들어 두고, 앱에서 수신자 흐름을 다시 탔습니다. 서버·앱 status API 모두 APPROVED 를 반환하는 상태입니다.

    GET /admin/verifications/1 → status: APPROVED
    GET /receiver-auth/delivery-verification/status (X-Auth-Code) → status: APPROVED
    

    그런데 앱은:

    1. 받은 기록함 발신자 카드가 상태 "열람 불가" 고정 (승인 반영 안 됨)
    2. "열람 신청하기" → 마스터키 검증 통과 후 다시 3단계 "증빙 서류 업로드" 화면으로 보냄
    3. 그 업로드는 fix(afternote): 수신자 증빙 업로드 재발 — presigned 요청에 BE 필수 필드 contentLength 누락으로 400 #915 로 불가능 → 완주 자체가 막힘

    원인이 이 이슈다

    받은 기록함이 record-boxes 를 안 쓰고 로컬 stub 으로 도니까, 서버의 verificationStatus=APPROVED 를 읽는 경로가 없다. 그래서 카드는 항상 초기값("열람 불가")이고, 열람 신청은 서버 상태와 무관하게 인증 4단계를 처음부터 태운다.

    FE 인프라는 이미 다 있는데 화면이 호출만 안 한다 — getDeliveryVerificationStatus() 가 service·repository·domain 계층까지 구현돼 있으나 presentation 에서 호출부 0건이고, 테스트에 error("호출되면 안 됨")·error("unused") 로 미사용이 박제돼 있다. (ReceiverAuthApiService.kt:55, MasterKeyViewModelTest.kt:150)

    즉 이 이슈(record-boxes 연동)를 처리하면 #599(카드 소실)·#612(승인일 빈 값)뿐 아니라 승인 이후 화면 진입 자체가 함께 풀린다. 반대로 이걸 안 고치면 admin 이 승인해도 수신자는 앱에서 기록을 영영 못 본다.

    관련

  7. 1 remaining item

  8. changed the title [-]fix(afternote): 받은 기록함이 서버 목록 API 를 쓰지 않고 로컬 stub 으로 동작 — record-boxes 미연동[/-] [+]받은 기록함이 서버 목록 API 를 쓰지 않고 로컬 stub 으로 동작 — record-boxes 미연동[/+] on Aug 28, 2026
  9. added
    area:afternote애프터노트 기능 영역
    and removed on Aug 28, 2026
  10. 1hyok commented on Sep 13, 2026

    @1hyok
    CollaboratorAuthor

    서버 계약이 바뀌었다. 새 계약 인계 (2026-09-13 실측)

    지금 이 이슈에서 코드로 뗄 독립 범위가 없다고 판정했다. 본문 「서버 계약」 표가 가리키는 API 가 서버에서 사라졌고, 마지막 실측 코멘트(2026-08-25)도 구 계약 기준이다. 그대로 착수하면 삭제된 엔드포인트를 구현하게 되므로, 지금 확정 가능한 값만 옮겨 적는다. #1949 가 남은 몫으로 지목한 인계가 이것이다. 코드 변경은 0줄이다.

    판정 경로는 Afternote-BE origin/main 3593aa3(2026-09-09)과 Afternote-FE origin/develop 7d87aa3f4 다.

    1. 새 서버 계약

    목록 조회는 GET /api/v1/received-records 다. 핸들러 인자가 @UserId Long userId 하나뿐이고 헤더 파라미터가 없다. 자격은 액세스 토큰이며 X-Auth-Code 는 계약에서 없어졌다.

    응답 래퍼 키는 recordBoxes 그대로다(ReceivedRecordBoxListResponse.java:11). FE DTO 의 래퍼 구조는 갈아엎을 필요가 없다.

    항목은 아홉 필드다. receiverId·senderName·receiverName·relation·recordStatus·viewStatus·verificationStatus·requestedAt·approvedAt. 근거는 ReceivedRecordBoxResponse.java:13-70.

    • accessCode 가 없다. BE src 전역에서 0건이다.
    • recordStatus 의 BE 타입이 String 에서 enum ReceivedRecordStatus 로 바뀌었다(:33). 허용값은 STORED·EMPTY 둘뿐이다. JSON 표현은 여전히 문자열이라 FE 파싱이 바로 깨지지는 않지만, 값 집합이 닫혔다.
    • requestedAt·approvedAt 은 LocalDateTime 이다(오프셋 없는 ISO-8601, Asia/Seoul). approvedAt 은 인증 상태가 APPROVED 일 때만 채워진다(:99-102).

    상세와 하위 경로는 GET /api/v1/received-records/{receiverId} 아래로 모였다(ReceivedRecordController.java:35 이하). 초대 수락은 POST /api/v1/receiver-invitations/{token}/accept 다(ReceiverInvitationController.java:47).

    POST /users/receivers 는 없다. UserController 의 receivers 매핑은 GET(:201·:214), PATCH(:228·:242·:280), DELETE(:295) 뿐이다.

    목록에서 칸을 골라내는 키가 마스터 키에서 receiverId 로 바뀐다. 마스터 키가 응답에서 없어졌기 때문이다.

    2. FE 가 죽은 계약을 가리키고 있는 자리 (origin/develop 7d87aa3f4)

    • feature/receiver/data/src/main/kotlin/com/afternote/feature/receiver/data/service/ReceiverAuthApiService.kt:65 가 @GET("receiver-auth/record-boxes") 다.
    • feature/receiver/data/src/main/kotlin/com/afternote/feature/receiver/data/dto/ReceiverAuthDto.kt:132 의 accessCode: String 이 non-null 이고 :228 에서 masterKey = accessCode 로 옮겨 담는다. 서버가 이 필드를 안 내려주는 순간 목록 파싱이 예외로 떨어진다.
    • 회귀 가드 feature/receiver/data/src/test/kotlin/com/afternote/feature/receiver/data/dto/ReceivedRecordBoxContractTest.kt 가 같은 필드를 박제해 두었다(:37·:58·:78·:99·:102·:141·:212). DTO 와 함께 갈아야 한다.
    • feature/receiver/data/src/main/kotlin/com/afternote/feature/receiver/data/network/ReceiverAuthInterceptor.kt:32 가 /receiver-auth/ 경로에 X-Auth-Code 를 자동 부착한다.
    • core/network/src/main/kotlin/com/afternote/core/network/service/UserApiService.kt:39 가 @POST("users/receivers") 다.
    • main 소스의 receiver-auth 엔드포인트 선언이 14건이다(receiver 10, mindrecord 2, timeletter 2). 목록 전환 자체는 receiver·core 안에서 닫히지만, receiver-auth 폐기 전체는 mindrecord·timeletter 모듈을 건드린다. 그 두 모듈분은 이 이슈의 담당 범위가 아니다.

    3. 본문과 옛 코멘트에서 이미 사실이 아닌 것

    본문은 2026-09-08 머리말이 「최종 구현 계약이 아니다」라고 밝혀 두었으므로 본문을 고치지 않고 여기에 정정만 적는다.

    4. 착수 순서와 함정

    5. 여전히 막혀 있는 것

    blocked_by 에 걸린 셋 중 열려 있는 것은 Afternote-BE#93 과 #1949 두 건이다(#544 는 닫혔다). 수신자에게 어떤 발신자가 보여야 하는지(초대 수락분만인지 자기 앞으로 온 전부인지)가 PM 답을 기다리고 있다. 화면 완료 조건은 그 답이 온 뒤 확정한다. 이 코멘트는 기록이고 blocked_by 를 풀지 않는다.

  11. 1hyok commented on Sep 20, 2026

    @1hyok
    CollaboratorAuthor

    2026-09-20 최신 계약과 코드를 다시 대조했습니다.

    FE develop f69332d995a2e9890605b3c8075c427b2dd0cc8c의 ReceivedRecordsViewModel은 SenderRegistry.senders를 그대로 노출하고 SenderRegistry는 프로세스 메모리 목록입니다. 발견된 목록 미연동 결함은 여전히 있습니다.

    하지만 아래 본문의 2026-07-29 API·헤더 표는 현재 구현 계약이 아닙니다. 배포 OpenAPI HTTP 200과 BE main 7c961ec3e2eb6fe3d0d87d4ba4e2dea312fdcf7f에서 받은 기록함은 /api/v1/received-records 계열의 JWT 소유권을 사용하며, 옛 receiver-auth/record-boxes와 accessCode 응답을 되살려 연동할 수 없습니다. ReceiverRepository는 acceptedUserId 기준으로 목록을 조회하고 ReceiverInvitationService가 개별 초대 수락으로 그 연결을 만듭니다.

    BE#93의 기존 전체 목록 요구와 새 초대 방식·구 수신자 이행에 관한 결정은 아직 기록되지 않았습니다. FE#1949는 2026-09-19 결정 전 구현 예약을 정리한다는 코멘트와 함께 닫혔으며, 그것을 서버 접근 계약 승인이나 이 이슈의 구현 차단 해제로 해석하지 않습니다. #544의 종료 역시 현재 권한 계약을 확정하지 않습니다.

    이 이슈는 발견된 로컬 목록 결함의 기록으로 유지합니다. 현시점에 임시 Repository·마스터키 보관·미확정 초대 전환을 새로 작성할 독립 범위는 없습니다. 접근 자격, 전체 목록 범위, 구 수신자 이행이 결정되면 당시 서버와 FE의 차이만 다시 판단합니다. 외부 요청이나 새 구현 예약 이슈는 추가하지 않았습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:afternote애프터노트 기능 영역bugSomething isn't working

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions