결제·이커머스 회원가입 폼 검증 구멍 두 곳 동시 수정
목차
결제 플랫폼이랑 이커머스 쪽 회원가입 폼이 둘 다 같은 시점에 깨져 있었다. 원인은 달랐고, 두 코드베이스는 분리돼 있었고, 파일 이름은 하필 둘 다 register였다. 작업 초반 15분은 그냥 "내가 지금 어느 쪽 보고 있지?" 확인하는 데 썼음.
폼 검증 버그는 은근히 오래 살아남는 편이다. 기능이 아예 안 되는 게 아니라, 특정 조건에서만 터지는 경우가 많아서 QA에서도 잘 안 잡힘. 이번에도 신고 몇 건 쌓인 뒤에야 올라온 케이스였다.
뭐가 깨져 있었나
버그가 네 가지였는데, 하나씩 따로 보면 "어? 이게 왜?" 싶고, 합치면 "어떻게 이게 지금까지 살아있었지?" 싶은 것들이다.
이메일 정규식 불일치는 가장 조용하게 오래 살아있던 버그였다. 두 플랫폼이 각자 다른 정규식을 쓰고 있었는데, 이커머스 쪽 패턴이 .co.kr 같은 2단계 ccTLD를 거부했다. TLD를 최대 한 덩어리만 허용하도록 짜여 있어서 example.co.kr로 가입하려는 사람은 그냥 튕겼음. 에러 메시지는 "이메일 형식이 잘못됐습니다"라고만 뜨니 사용자 입장에선 뭐가 문제인지도 모름.
// 변경 전 — 이커머스 (co.kr 거부)
/^[\w.-]+@[\w.-]+\.[a-zA-Z]{2,4}$/
// 변경 후 — 이커머스 (다단계 TLD 허용, 플랫폼 정책에 맞게 분리 유지)
/^[\w.+-]+@[\w-]+(\.[a-zA-Z]{2,})+$/
결제 플랫폼 정규식은 다른 패턴이었고 .co.kr을 이미 허용하고 있었다. 둘을 합칠까 잠깐 고민했는데, 플랫폼마다 이메일 정책이 다르다는 게 확인됐고, 그냥 명시적으로 분리한 채 주석에 차이를 남겼음. "왜 다르지?" 하고 나중에 누군가 합치려다 한쪽 정책을 날리는 쪽이 더 위험하다.
비밀번호 확인 필드 blur 이벤트 누락은 UX 버그에 가깝다. submit 누를 때까지 에러가 보이지 않으니, 사용자가 두 번째 필드까지 다 채우고 제출한 뒤에야 "비밀번호가 다릅니다"를 맞닥뜨림. 그 시점엔 이미 폼 전체를 다시 확인하게 만드는 흐름이 돼버린다. blur에 검증을 붙이면 필드를 떠나는 순간 바로 피드백이 가서 수정 비용이 훨씬 줄어든다. 원리는 단순한데 빠져있으면 생각보다 많이 거슬린다.
약관 동의 서버 검증 누락이 이번에 제일 찜찜한 케이스였다. 프론트에서 체크박스가 disabled면 submit이 막히는 구조였는데, 서버에서 따로 검증이 없었음. 개발자 도구로 disabled 속성 제거하고 요청 날리면 그냥 통과됐다. 실제로 적극적으로 악용될 확률이 높은 케이스는 아니더라도, 약관 동의 여부는 법적 근거로 쓰이는 값이다. 서버가 "이 요청에 동의 플래그가 있냐"를 직접 확인해야 한다.
파트너 가입 분기 사업자번호 필드 노출은 조건부 렌더 로직이 빠진 전형적인 케이스다. 파트너 가입 페이지에서만 보여야 할 필드가 일반 가입 폼에도 같이 나왔음. 필드가 필수값이 아니라 submit 자체는 됐는데, 일반 사용자 입장에선 "사업자번호가 왜 여기에?" 싶을 수밖에 없다. 가입 타입을 파라미터나 컨텍스트로 받아서 렌더링 분기를 명시적으로 나눠줬음.
어떻게 고쳤나
| 항목 | 변경 전 | 변경 후 |
|---|---|---|
| 이메일 검증 | 클라이언트 only | 클라이언트 + 서버 |
| 비밀번호 확인 | submit 시점 | blur + submit |
| 약관 동의 | 프론트 disabled | 프론트 + 서버 재검증 |
| 파트너 분기 | 조건부 렌더 누락 | 가입 타입별 분기 처리 |
서버 검증을 양쪽에 추가하면서 에러 응답 포맷이 플랫폼마다 살짝 달랐다. 결제 쪽은 { error: { code, message } }, 이커머스는 { errors: [{ field, message }] } 구조였음. 백엔드 응답 포맷을 통일하는 건 이번 범위 밖이라 건드리지 않았고, 프론트 폼 에러 처리 쪽에 어댑터 한 겹만 끼워서 둘 다 소화하게 만들었다. 나중에 응답 포맷을 통일할 때 어댑터만 걷어내면 되는 구조로 두는 게 낫다고 판단했음.
폼 검증에서 서버-클라이언트 이중 검증은 기본 원칙인데, 실무에서 자꾸 클라이언트만 남고 서버가 빠지는 데는 이유가 있다. 초기 개발 때 클라이언트 먼저 만들고 서버 검증은 "나중에" 붙이려다 잊히거나, "프론트에서 막으면 어차피 사용자는 못 하잖아"라는 안이함이 쌓인다. 이번 약관 동의 케이스가 딱 그 흐름이었을 거다.
회고
공통화 욕심을 참은 게 맞는 선택이었다. 두 폼이 겉보기에 비슷하고 파일 이름까지 같아서 합치고 싶은 충동이 있었는데, 이메일 정책이나 파트너 분기 로직은 플랫폼마다 실제로 다르다. 억지로 합치면 한쪽 정책이 바뀔 때 둘 다 틀어짐. 차이를 명시적으로 코드에 남기고 이유를 주석으로 박아두는 쪽이 장기적으로 안전하다는 걸 다시 확인했다.
서버 검증 누락이 진짜 위험하다는 건 알면서도 또 만났다. 약관 동의는 그냥 UI 요소가 아니라 법적 동의 기록이다. 이런 필드일수록 클라이언트 상태는 신뢰하지 않고 서버가 직접 검증해야 한다. 이걸 모르는 게 아니라 습관적으로 지나치는 게 문제인데, "disabled면 됐겠지"라는 판단이 나오는 순간 한 번 더 의심하는 게 맞다.
제일 남는 숙제는 자동화 부재다. 회원가입 핵심 케이스에 e2e가 없으니 이번에도 브라우저 열어서 케이스별로 손으로 다 찍었음. 정규식 한 줄 바뀌면 특정 도메인 가입자가 통째로 막힐 수 있는 케이스인데, 그걸 자동으로 잡는 테스트가 없다는 게 계속 찜찜하다. 이런 경험이 쌓일수록 "핵심 케이스 몇 개만이라도" 의 기준이 낮아지는 것 같다. 다음번엔 정규식 케이스, blur 타이밍, 서버 거부 응답 처리 정도만이라도 e2e로 박아둘 것.
폼 검증은 매번 비슷한 함정에 빠진다. "프론트만 막아도 되겠지"라는 방심이 제일 비싸다.
댓글 0
첫 댓글 달아줘.