Skip to content

docs: 소셜 로그인 이메일을 필수로 개정 (06 §2.2) - #28

Merged
cherry-go-round merged 4 commits into
mainfrom
docs/social-login-email-required
Jul 30, 2026
Merged

docs: 소셜 로그인 이메일을 필수로 개정 (06 §2.2)#28
cherry-go-round merged 4 commits into
mainfrom
docs/social-login-email-required

Conversation

@cherry-go-round

Copy link
Copy Markdown
Contributor

요약

프론트에 이메일을 표시하는 화면이 있어 값이 반드시 있어야 하고, 이메일 없는 계정이 존재해서는 안 된다는 결정이다. 그런데 06 §2.2가 *"미동의·미제공 시 null일 수 있다"*로 정하고 있어, 구현보다 이 개정이 먼저 필요하다(CLAUDE.md 9번 — 공용 명세가 이긴다).

변경 사항

문서 변경
06 §2.2 스키마 email VARCHAR(255) NULLNOT NULL, 서술을 필수로 개정
07_ERD social_account.email 주석 nullable필수
08 §3.2 콜백 실패 사유에 "이메일 없는 응답" 추가
08 §3.5 email은 항상 있다를 명시

배경

§3.5의 한 줄이 프론트가 제기한 문제를 닫는다. §1.6null 필드를 직렬화에서 생략하므로, 지금까지는 /me/summary 응답에 email 키 자체가 없을 수 있었다. 그래서 클라이언트가 값 없음을 대비해야 했는데, 이제 그럴 필요가 없다는 것을 계약으로 못박았다.

리뷰 포인트

보장 순서를 명시했다. 이게 이 PR에서 가장 봐 주셨으면 하는 부분이다.

  1. 1차는 공급자 콘솔의 필수 동의 설정 — 사용자가 이메일만 거절하고 진행하는 선택지가 동의 화면에 나오지 않는다. 거부하면 로그인이 취소되어 가입 요청 자체가 오지 않는다. 세 공급자 모두 설정 완료된 상태다.
  2. 서버의 거절과 컬럼 NOT NULL은 방어선 — 위 설정에 의존하는 상태를 코드로 확인하지 않으면 전제가 깨졌을 때 값 없는 계정이 조용히 만들어진다. 전제가 깨지는 경우는 콘솔이 선택 동의로 되돌려지는 것, 공급자 응답 형식이 바뀌는 것, 카카오 비즈 앱 자격이 상실되는 것이다.

처음에는 "미동의 시 로그인 실패"를 사용자가 일상적으로 밟는 흐름처럼 적었는데, 필수 동의 설정에서는 그 경로가 공급자 화면에서 먼저 막힌다는 지적을 받아 고쳤다. 지금 문장은 방어선으로 위치를 잡고 있다.

한 가지 밝혀 둘 것은 카카오·네이버 동의 화면의 정확한 동작을 실측하지는 않았다는 점이다. 필수 동의 항목이 개별 거절을 허용하지 않는다는 전제로 썼다. 다르게 알고 계시면 알려주시면 문장을 고치겠다.

미결 / 후속

구현Team-PinLog/backS15P11A705-152 · back#97이다. 컬럼 NOT NULL 마이그레이션과 정규화 계층의 거절이 여기에 있다. 운영 DB에 데이터가 없어 백필은 필요하지 않다.

이메일 null 허용 경로는 back BI-24에서 테스트로 고정해 둔 상태이며, 이 개정으로 그 계약이 뒤집힌다.

🤖 Generated with Claude Code

프론트에 이메일을 표시하는 화면이 있어 값이 반드시 있어야 하고, 이메일 없는 계정이
존재해서는 안 된다는 결정이다. 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>
@cherry-go-round
cherry-go-round requested a review from colosair July 29, 2026 08:45

@colosair colosair left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_stateCANCELLED, 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와 같은 형태가 됐다.
@TrossYou

Copy link
Copy Markdown
Contributor

프론트(Team-PinLog/front) 리뷰: 대응 불필요, 확인 사항 1건.

  • /me/summary의 email NOT NULL 전환은 기존 프론트 zod 스키마(email: z.string(), nullable 아님)와 이미 일치합니다. 코드 변경 없습니다.
  • 질문: §3.2에 추가하신 "이메일 없는 응답" 실패 사유가 기존 error=OAUTH_FAILED로 묶이나요, 아니면 별도 error 값으로 분리되나요? 분리된다면 프론트 콜백 라우팅에 분기 추가가 필요해서 확인 부탁드립니다.
  • 그 외 응답 구조/인증/CORS 계약 변경은 없는 것으로 확인했습니다.

프론트 리뷰에서 "별도 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)이 일치한다.
@cherry-go-round
cherry-go-round merged commit 1ae32a4 into main Jul 30, 2026
@cherry-go-round
cherry-go-round deleted the docs/social-login-email-required branch July 30, 2026 00:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants