You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
비밀번호를 쓰는 세 경로(재발송·본인 변경·재설정)를 같은 사용자 행 잠금으로 직렬화했습니다 (MSG-497 PasswordService 조회 2곳을 잠금 조회로 교체)
문서: 스펙 docs/spec/MSG-499.md(작업 로그 포함), PRD 서버 재료 목록 갱신, SRS FR-AUTH-13 구현됨·rtm 재생성(검증 공백 0건), status.md 갱신
테스트 신규 48건, 전체 스위트 green (Codex 교차 리뷰: 스펙 4라운드 + 구현 2라운드 수렴)
🤔 고민한 내용
"발송 성공"의 의미를 SES 접수까지로 확정: SES는 배달 확인을 동기로 주지 않아(MessageId = 접수), 오타 주소 복구는 재발송 API가 맡습니다. 배달 확인 대기 설계는 불가능해 기각했습니다
접수 UPSERT(마지막 접수 유효) 유지 vs DO NOTHING: 익명 덮어쓰기 변조가 가능하지만, DO NOTHING은 반대로 공격자 선점 행이 진짜 신청을 조용히 삼킵니다. UPSERT를 유지하고 신뢰 경계를 관리자 심사(공문·공식 채널 확인)로 명시했습니다. 검토-승인 사이 변조는 updatedAt 에코 대조(불일치 시 1426)로 막습니다
비관적 락을 세 경로 전부에 건 이유: 재발송만 잠그면 changePassword·resetPassword의 잠금 없는 조회가 lost update로 새 해시를 덮습니다(한쪽 락으로는 못 막음). 실제 생성 SQL은 FOR UPDATE가 아니라 FOR NO KEY UPDATE로 실측됐고(Hibernate 6 PG 방언) 기존 FOR KEY SHARE 경로와 충돌하지 않습니다
커밋 후 발송: 발송을 트랜잭션 안에 두면 SES 왕복이 커넥션을 점유하고, 발송 후 커밋 실패 시 "비밀번호는 나갔는데 계정이 없는" 역방향 구멍이 생깁니다. 커밋 후 발송 + 실패 시 emailSent: false + 재발송 복구로 정리했습니다
👀 리뷰 포인트
Flyway 머지 순서: 이 브랜치는 V48이고 V47은 MSG-498 몫으로 비워뒀습니다. 이 PR이 먼저 머지되면 498은 V49로 재번호해야 합니다 (Flyway가 out-of-order 기본 거부)
AdminOrgAccountItemResponseDto.orgName에 nullable을 표기했습니다 — MSG-496·497이 만든 기존 ORG 계정은 org_name이 없어서인데, 목록 필터가 그 계정들을 포함하는 게 맞는지 봐주세요
발송 실패 ERROR 로그에서 예외 객체를 빼고 타입만 남겼습니다(스택 경유 평문 유출 차단). SES 거부 상세는 SesMailSender 쪽 로그가 갖는데, 진단 정보가 부족하다고 보이면 의견 주세요
병렬 브랜치 경합 확인 (마이그레이션 번호, developCode 대역, Owner A/B 계약)
스펙 대비 구현 대조 (docs/spec/MSG-499.md)
실행 시 드러나는 결함 (트랜잭션, 동시성, 시간대, 인가)
PR 본문 주장 검증
결과 정리
확인 범위: origin/develop 대비 마이그레이션 번호(V46까지, V47 공백 확인)·UserErrorCode 1421~1426 대역·SecurityConfig matcher 순서, docs/spec/MSG-499.md 전체 대 구현 8종 API, PasswordService/OrgAccountIssueService/OrgAccountRequestService의 트랜잭션·잠금 경계, 동시성 테스트 4종(InitialPasswordResendConcurrencyTest 등, 실제 스레드+CyclicBarrier로 실 DB 경합 재현 확인), UTC 코덱과 updatedAt 에코 대조 라운드트립.
발견 없음. 인라인 코멘트도 없음.
마이그레이션 번호(V48)와 developCode 대역(1421~1426)은 origin/develop 최신(V46, 1420까지)과 실제로 충돌하지 않는다 — PR 본문이 밝힌 V47 공백(MSG-498 몫)은 지금 시점엔 실재 경합이 아니라 머지 순서에 따른 재번호 필요성일 뿐이다.
스펙 문서와 구현이 API 8종·에러 코드·잠금 전략·발송 실패 격리까지 세부적으로 일치한다. 스펙에 없는 동작이나 스펙과 어긋나는 처리를 찾지 못했다.
비밀번호 세 경로(재발송·변경·재설정)의 행 잠금 통일은 실 스레드 경합 테스트로 뒷받침되고, updatedAt 낙관적 검증 값의 UTC 코덱 왕복도 @SpringBootTest+@AutoConfigureMockMvc로 실 매퍼를 거쳐 검증된다.
리뷰 포인트로 남긴 AdminOrgAccountItemResponseDto.orgName nullable 건: 목록 필터가 role=ORG AND provider=LOCAL이라 MSG-496/497이 만든 기존 계정도 스펙상 포함 대상이 맞다(스펙 "목록 대상은 role=ORG AND provider=LOCAL 필터다" 문구에 예외 없음). API 8의 목적(재발송 대상 식별, 이메일 검색을 통한 크래시 복구 판정)에는 orgName 유무가 영향을 주지 않으므로 지금 그대로도 기능상 문제는 없어 보인다 — 다만 화면에서 그 계정들의 "기관명" 칸을 빈 값으로 보여줄지는 FE 판단으로 넘기면 될 것 같다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎫 관련 티켓
작업 내용
POST /api/org-account-requests, permitAll — 필터 skip 목록은 건드리지 않음)org_account_requests테이블을 신설했습니다 (Flyway V48 — V47은 병렬 진행 중인 MSG-498이 공유 로컬 DB에 먼저 적용해 재번호, 아래 리뷰 포인트)PasswordService조회 2곳을 잠금 조회로 교체)docs/spec/MSG-499.md(작업 로그 포함), PRD 서버 재료 목록 갱신, SRS FR-AUTH-13 구현됨·rtm 재생성(검증 공백 0건), status.md 갱신🤔 고민한 내용
updatedAt에코 대조(불일치 시 1426)로 막습니다changePassword·resetPassword의 잠금 없는 조회가 lost update로 새 해시를 덮습니다(한쪽 락으로는 못 막음). 실제 생성 SQL은FOR UPDATE가 아니라FOR NO KEY UPDATE로 실측됐고(Hibernate 6 PG 방언) 기존FOR KEY SHARE경로와 충돌하지 않습니다emailSent: false+ 재발송 복구로 정리했습니다👀 리뷰 포인트
AdminOrgAccountItemResponseDto.orgName에 nullable을 표기했습니다 — MSG-496·497이 만든 기존 ORG 계정은org_name이 없어서인데, 목록 필터가 그 계정들을 포함하는 게 맞는지 봐주세요SesMailSender쪽 로그가 갖는데, 진단 정보가 부족하다고 보이면 의견 주세요