docs: 소셜 로그인 이메일을 필수로 개정 (06 §2.2) - #28
Conversation
프론트에 이메일을 표시하는 화면이 있어 값이 반드시 있어야 하고, 이메일 없는 계정이 존재해서는 안 된다는 결정이다. 06 §2.2가 "미동의·미제공 시 null일 수 있다"로 정하고 있어 구현보다 이 개정이 먼저 필요하다. - 06 §2.2: 스키마 email NULL → NOT NULL, 서술을 필수로 개정 - 07_ERD: social_account.email 주석 nullable → 필수 - 08 §3.2: 콜백 실패 사유에 이메일 없는 응답을 추가 - 08 §3.5: email이 항상 있다는 것을 명시 §3.5의 한 줄이 프론트가 문제 제기한 지점을 닫는다. §1.6이 null 필드를 직렬화에서 생략하므로 지금까지는 응답에 email 키 자체가 없을 수 있었고, 그래서 클라이언트가 값 없음을 대비해야 했다. 보장 순서를 명시했다. 1차는 공급자 콘솔의 필수 동의 설정이다 — 사용자가 이메일만 거절하고 진행하는 선택지가 동의 화면에 없으므로, 거부하면 로그인이 취소되어 가입 요청 자체가 오지 않는다. 서버의 거절과 컬럼 NOT NULL은 그 설정에 의존하는 상태를 코드로 확인하는 방어선이며, 전제가 깨지는 경우(콘솔이 선택 동의로 되돌려짐, 공급자 응답 형식 변경, 카카오 비즈 앱 자격 상실)에 값 없는 계정이 조용히 만들어지는 것을 막는다. 즉 사용자가 일상적으로 밟는 흐름이 아니다. 구현은 Team-PinLog/back S15P11A705-152 (back#97) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
colosair
left a comment
There was a problem hiding this comment.
AI 파트 관점에서 영향 없음을 확인했습니다. 승인합니다.
확인한 것
social_account.email은 AI 파생 데이터 경로 어디에도 들어가지 않습니다. Context 임베딩·Keyword 판정·Feed 후보·개인 검색 전부 member 식별자만 쓰고 이메일을 읽지 않습니다. 06 §1.3 쓰기 매트릭스에서도 ai.* 다섯 테이블과 무관합니다.
07_ERD·08 §3.2·§3.5 변경도 같습니다.
한 가지 — 탈퇴 경로에서 두 조항이 함께 걸립니다
08 §3.6이 탈퇴 시 email을 마스킹하도록 정하고 있고(06:104도 같음), 이 PR이 그 컬럼을 NOT NULL로 만듭니다. 마스킹을 null 할당으로 구현하면 제약 위반으로 탈퇴가 실패합니다.
값을 치환하는 방식이면 충돌이 없으므로 이 PR 자체는 문제없습니다. 다만 구현(S15P11A705-152·back#97)이 이 두 조항을 함께 봐야 한다는 것이 문서에서 바로 드러나지 않아 적어 둡니다. 계약에 반영할지는 판단에 맡기겠습니다 — 구현 티켓 쪽에서 다루는 편이 나을 수도 있습니다.
탈퇴 경로에는 AI 파생 데이터 무효화(
ai.context_ai_state→CANCELLED,context_embedding.is_deleted)도 함께 붙습니다.S15P11A705-124가 back#80으로 병합됐고 탈퇴 지점만 남아 back#34에 인계돼 있습니다. 마스킹과 같은 트랜잭션에서 돌게 됩니다.
리뷰 포인트에 대해
카카오·네이버 동의 화면의 개별 거절 가능 여부는 저도 실측한 적이 없어 확인해 드릴 수 없습니다. 다만 문장을 *"일상적 흐름"*이 아니라 방어선으로 위치시킨 판단은 맞다고 봅니다 — 전제가 깨지는 세 경우(콘솔이 선택 동의로 되돌려짐 · 응답 형식 변경 · 비즈 앱 자격 상실)를 명시해 둔 것이 그 근거를 남깁니다.
BI-24의 기존 계약이 이 개정으로 뒤집힌다는 점을 PR 본문이 스스로 밝힌 것도 좋습니다. 그 테스트를 함께 고치는 것이 구현 범위에 들어가야 합니다.
앞 커밋에서 "nullable"을 "필수"로 바꿨는데, 이 ERD는 **nullable인 것만 표기하고 기본은 필수**로 두는 관례를 쓴다. road_address·phone·place_url·display_name에만 nullable이 붙어 있고, 필수를 명시하는 것은 published_at "발행 시 필수 (CHECK)"처럼 조건부일 때다. email은 무조건 필수이므로 표기를 지우는 것이 관례에 맞다. 같은 테이블의 provider_user_id와 같은 형태가 됐다.
|
프론트(Team-PinLog/front) 리뷰: 대응 불필요, 확인 사항 1건.
|
프론트 리뷰에서 "별도 error 값으로 분리되는지" 질문이 왔다. 분리되면 콜백 라우팅에 분기를 더해야 하므로 계약이 답을 갖고 있어야 한다. 답은 묶인다이고, 이것은 새 결정이 아니라 기존 결정이다. back의 OAuthLoginFailureHandler javadoc이 이미 "실패 사유를 그대로 노출하지 않는다. 클라이언트는 고정된 code 하나만 보고 재로그인을 유도하고, 원인은 traceId로 로그에서 찾는다"로 정하고 있다. 별도 값을 두면 그 결정을 뒤집는 것이다. 사유를 가르지 않는 근거도 함께 적었다. 이 실패는 사용자가 우리 화면에서 고칠 수 있는 것이 아니다(동의는 공급자 쪽에 있다). 게다가 필수 동의 설정에서는 사용자가 이 경로에 도달하지 않으므로, 값을 가르면 클라이언트에 발생하지 않는 분기가 남는다. 프론트는 분기 추가가 필요하지 않다.
email을 NOT NULL로 바꾸면서 탈퇴 시 마스킹과 충돌할 여지가 생겼다. 계약에 문제가 둘 있었다. 1) 마스킹의 결과값이 정의되지 않았다 "마스킹합니다"만 있고 무엇으로 바뀌는지 정한 곳이 한 곳도 없었다. email이 nullable일 때는 구현자가 NULL로 해석할 수 있었고, NOT NULL이 되면 그 해석이 곧 제약 위반이 된다. 즉 이 개정이 기존 공백을 위험한 공백으로 바꾼다. 치환이라는 것을 §2.2에 명시했다. provider_user_id도 NOT NULL이면서 마스킹 대상이므로 이 저장소는 이미 "마스킹 = 치환"을 전제하고 있었고, 그 전제를 글로 옮긴 것이다. 치환값이 회원끼리 겹쳐도 된다는 것도 적었다. 유니크가 활성행만 대상이고 마스킹 시점에는 deleted_at이 이미 채워져 인덱스 밖이다. 이걸 적지 않으면 구현자가 유니크를 피하려고 회원별로 다른 값을 만들려 한다. 구체적 치환값은 탈퇴 구현의 몫으로 남겼다. 계약이 정하는 것은 NULL이 아니라는 것과 원본을 되돌릴 수 없어야 한다는 것이다. 2) §6.9 탈퇴 흐름에 email이 빠져 있었다 §2.2와 08 §3.6은 둘 다 마스킹한다고 적는데 §6.9는 provider_user_id만 적고 있었다. 탈퇴 구현자가 실제로 보고 따라가는 순서도라 이쪽이 더 위험하다. 이제 네 곳(06 §2.2, 06 §6.9, 07_ERD, 08 §3.6)이 일치한다.
요약
프론트에 이메일을 표시하는 화면이 있어 값이 반드시 있어야 하고, 이메일 없는 계정이 존재해서는 안 된다는 결정이다. 그런데
06 §2.2가 *"미동의·미제공 시null일 수 있다"*로 정하고 있어, 구현보다 이 개정이 먼저 필요하다(CLAUDE.md9번 — 공용 명세가 이긴다).변경 사항
06 §2.2email VARCHAR(255) NULL→NOT NULL, 서술을 필수로 개정07_ERDsocial_account.email주석nullable→필수08 §3.208 §3.5email은 항상 있다를 명시배경
§3.5의 한 줄이 프론트가 제기한 문제를 닫는다.
§1.6이null필드를 직렬화에서 생략하므로, 지금까지는/me/summary응답에email키 자체가 없을 수 있었다. 그래서 클라이언트가 값 없음을 대비해야 했는데, 이제 그럴 필요가 없다는 것을 계약으로 못박았다.리뷰 포인트
보장 순서를 명시했다. 이게 이 PR에서 가장 봐 주셨으면 하는 부분이다.
NOT NULL은 방어선 — 위 설정에 의존하는 상태를 코드로 확인하지 않으면 전제가 깨졌을 때 값 없는 계정이 조용히 만들어진다. 전제가 깨지는 경우는 콘솔이 선택 동의로 되돌려지는 것, 공급자 응답 형식이 바뀌는 것, 카카오 비즈 앱 자격이 상실되는 것이다.처음에는 "미동의 시 로그인 실패"를 사용자가 일상적으로 밟는 흐름처럼 적었는데, 필수 동의 설정에서는 그 경로가 공급자 화면에서 먼저 막힌다는 지적을 받아 고쳤다. 지금 문장은 방어선으로 위치를 잡고 있다.
한 가지 밝혀 둘 것은 카카오·네이버 동의 화면의 정확한 동작을 실측하지는 않았다는 점이다. 필수 동의 항목이 개별 거절을 허용하지 않는다는 전제로 썼다. 다르게 알고 계시면 알려주시면 문장을 고치겠다.
미결 / 후속
구현은
Team-PinLog/back의 S15P11A705-152 · back#97이다. 컬럼NOT NULL마이그레이션과 정규화 계층의 거절이 여기에 있다. 운영 DB에 데이터가 없어 백필은 필요하지 않다.이메일
null허용 경로는 back BI-24에서 테스트로 고정해 둔 상태이며, 이 개정으로 그 계약이 뒤집힌다.🤖 Generated with Claude Code