#같은 로그인으로 두 서비스를 쓰고 있었다
개인 게임 서비스인 UP와 OP는 공통 인증 가입을 사용한다. 여기서 UP만 탈퇴하려는 사람이 있다면 어디까지 지워야 할까. 공통 계정을 먼저 없애면 OP 이용에도 영향을 줄 수 있다. 계정을 남기더라도 UP 데이터가 계속 쌓이면 탈퇴 처리가 끝났다고 말하기 어렵다.
서비스마다 본인 확인부터 따로 만드는 대신, 기존 로그인을 재사용했다. TypeScript로 작성한 인증 쪽 처리기는 누가 어느 서비스를 요청했는지 관리하고, 게임 쪽 처리기는 PostgreSQL에서 자기 데이터만 삭제한 뒤 결과를 돌려준다. 공통 인증 서버가 게임별 테이블 구조까지 알아야 하는 방식은 피했다.
#이메일 주소만으로 대상을 정하지 않기
삭제 요청에 적힌 이메일만으로 계정을 고르지 않기로 했다. 검증된 로그인 계정과 요청을 서버에서 연결하고, 요청하지 않은 서비스나 다른 계정으로 대상이 바뀌지 않게 해야 했다.
이전에 로그인 이후의 서비스 이용 권한을 정리할 때도 공통 로그인과 서비스 이용 상태를 나눠 봤다. 탈퇴에서는 그 구분이 삭제 순서와 연결됐다. 한 서비스의 처리가 끝났다는 결과를 받기 전에 공통 계정부터 지우면 남은 데이터를 찾거나 처리할 연결을 잃을 수 있었다.
UP만 요청하면 OP와 공통 계정을 남기는 조건을 먼저 고정했다. 브라우저에서는 요청번호만 받고, 계정과 대상 게임은 서버가 확인했다. 내부 처리 요청에는 request_id, 로그인에서 확인한 subject, service_id를 묶었다.
내부 요청은 기존 HMAC 서명 형식을 재사용했다. HTTP 메서드·경로·시각·본문의 SHA-256을 서명에 넣고, 게임별 키로 검증한 뒤에만 삭제 처리기로 넘겼다. 다른 서비스로 대상을 바꾸거나 일반 로그인 토큰으로 삭제 처리기를 직접 부르는 경우를 막기 위한 경계다. HMAC의 기본 API는 Node.js crypto 문서에서 볼 수 있다.
계약을 줄여 적으면 다음과 같다. 실제 계정이나 요청값을 담은 호출 예시는 아니다.
입력: request_id + 서버가 확인한 계정 + up
처리: 해당 계정의 UP 데이터 삭제 + 처리 영수증 저장
결과: 삭제 건수 + 미확인 항목 pending
보존: OP 데이터 + 다른 계정 + 공통 로그인
같은 요청을 다시 받아도 이미 처리한 데이터를 중복해서 지우지 않아야 했다. 일부 처리에 실패했거나 아직 결과를 받지 못했다면 전체 완료로 표시하지 않는 것도 같은 계약에 넣었다.
#지운 뒤 다시 생기는 경우
삭제와 처리 영수증 저장은 하나의 PostgreSQL 트랜잭션으로 묶었다. 마지막 영수증 저장이 실패하면 앞의 삭제도 되돌리고, 재시도에는 기존 영수증의 결과를 반환하도록 했다. PostgreSQL 트랜잭션 문서의 전부 반영하거나 전부 취소하는 성질을 여기서 사용했다.
삭제문이 한 번 성공하는 것으로는 부족했다. 남아 있는 세션이나 늦게 도착한 보상 요청이 데이터를 다시 만들 가능성도 살폈다. 서비스별 쓰기 차단을 위한 변경을 준비하고, 재시도와 실패 시 되돌리기까지 검증 범위에 넣었다.
10월 28일 개발 진행 기록에는 가짜 데이터로 UP 요청 시 OP와 다른 계정이 남는지, 같은 요청의 삭제를 건너뛰는지, 실패하면 되돌아가는지를 확인한 결과가 있다. 기존 DB와 연결하지 않은 임시 PostgreSQL에서는 삭제 순서, 동시 재시도, 전체 되돌리기, 삭제 후 쓰기 차단을 확인했다.
이 글이 다루는 최초 API 후보에서는 base-auth core 506개, Games API 149개 테스트가 통과했다. 임시 PostgreSQL 검증 1개도 최종 코드로 통과한 뒤 DB를 종료했다. 앞서 나온 501개·148개는 중간 단계의 결과였다. 이 숫자들은 실제 사용자 데이터를 삭제한 건수가 아니다.
인증 쪽 코드가 만든 서명 요청을 Games 소비자 코드가 검증하고, 가짜 삭제 결과를 돌려주는 왕복 테스트도 통과했다. 네트워크 없이 확인한 코드 연결이다. 운영 API를 호출해 삭제한 결과로 적지는 않았다.
#아직 완료라고 쓰지 않은 부분
게임 DB 처리가 끝나도 광고, 캐시, 로그, 백업의 처리가 남을 수 있다. 결과를 확인할 수 없는 항목은 pending으로 돌려주고, 이를 전체 삭제 완료로 바꾸지 않도록 했다.
마지막 회귀에는 요청 후 새 서비스에 가입한 경우와 같은 초에 재가입한 경우를 추가했다. 이전 삭제 결과를 그대로 적용하지 못하도록 차단했다. 이런 보호 검사를 통과한 것과 재가입을 어떤 조건으로 허용할지 정책을 정한 것은 다르다. 재가입과 쓰기 차단 해제 정책은 아직 남아 있다.
구현·격리 검증 결과는 두 저장소의 별도 작업 공간과 최종 보고서에 남겼다. 준비한 migration은 운영·공유 DB에 적용하지 않았다. 이 최초 후보 시점에는 전용 키와 권한, 프록시 설정 검토, 기존 화면의 요청 진입점도 남아 있었다. 뒤의 UI·보존 기한·Trip 연결은 별도 후속 기록이며 위 테스트 수에 합치지 않았다. 운영 삭제와 실사용자·브라우저 검증은 아직 수행하지 않았다.
이 작업의 첫 질문은 탈퇴 버튼을 어디에 둘지보다 좁았다. 사용자가 요청한 범위만 처리했는지, 남겨야 할 서비스가 그대로 있는지부터 답하려고 했다.