11package com .ai .lawyer .domain .member .controller ;
22
3- import com .ai .lawyer .domain .member .dto .MemberLoginRequest ;
4- import com .ai .lawyer .domain .member .dto .MemberResponse ;
5- import com .ai .lawyer .domain .member .dto .MemberSignupRequest ;
3+ import com .ai .lawyer .domain .member .dto .*;
64import com .ai .lawyer .domain .member .service .MemberService ;
75import io .swagger .v3 .oas .annotations .Operation ;
86import io .swagger .v3 .oas .annotations .responses .ApiResponse ;
@@ -140,6 +138,147 @@ public ResponseEntity<MemberResponse> getMyInfo(Authentication authentication) {
140138 return ResponseEntity .ok (response );
141139 }
142140
141+ @ PostMapping ("/sendEmail" )
142+ @ Operation (summary = "이메일 인증번호 전송" , description = "로그인된 사용자는 자동으로 인증번호를 받고, 비로그인 사용자는 요청 바디의 loginId(이메일)로 인증번호를 받습니다." )
143+ @ ApiResponses ({
144+ @ ApiResponse (responseCode = "200" , description = "이메일 전송 성공" ),
145+ @ ApiResponse (responseCode = "400" , description = "잘못된 요청 (loginId 없음)" )
146+ })
147+ public ResponseEntity <EmailResponse > sendEmail (
148+ @ RequestBody (required = false ) MemberEmailRequestDto requestDto ,
149+ Authentication authentication ,
150+ HttpServletRequest request ) {
151+
152+ String loginId = null ;
153+
154+ // 1. 로그인된 사용자인 경우 JWT 토큰에서 loginId 추출 (우선순위 1)
155+ if (authentication != null && authentication .isAuthenticated () &&
156+ !"anonymousUser" .equals (authentication .getPrincipal ())) {
157+
158+ // JWT 토큰에서 직접 loginid claim 추출
159+ try {
160+ String token = extractAccessTokenFromRequest (request );
161+ if (token != null ) {
162+ loginId = memberService .extractLoginIdFromToken (token );
163+ if (loginId != null ) {
164+ log .info ("JWT 토큰에서 loginId 추출 성공: {}" , loginId );
165+ } else {
166+ log .warn ("JWT 토큰에서 loginId 추출 실패" );
167+ }
168+ }
169+ } catch (Exception e ) {
170+ log .warn ("JWT 토큰에서 loginId 추출 중 오류: {}" , e .getMessage ());
171+ }
172+ }
173+
174+ // 2. 비로그인 사용자인 경우 요청 바디에서 loginId 추출 (우선순위 2)
175+ if (loginId == null ) {
176+ if (requestDto != null && requestDto .getLoginId () != null && !requestDto .getLoginId ().isBlank ()) {
177+ loginId = requestDto .getLoginId ();
178+ log .info ("요청 바디에서 loginId 추출 성공: {}" , loginId );
179+ } else {
180+ log .error ("로그인하지 않은 상태에서 요청 바디에 loginId가 없음" );
181+ throw new IllegalArgumentException ("인증번호를 전송할 이메일 주소가 필요합니다. 로그인하거나 요청 바디에 loginId를 포함해주세요." );
182+ }
183+ }
184+
185+ try {
186+ // 서비스 호출
187+ memberService .sendCodeToEmailByLoginId (loginId );
188+ log .info ("이메일 인증번호 전송 성공: {}" , loginId );
189+ return ResponseEntity .ok (EmailResponse .success ("이메일 전송 성공" , loginId ));
190+
191+ } catch (IllegalArgumentException e ) {
192+ log .error ("이메일 전송 실패 - 존재하지 않는 회원: {}" , loginId );
193+ throw e ;
194+ } catch (Exception e ) {
195+ log .error ("이메일 전송 실패: loginId={}, error={}" , loginId , e .getMessage ());
196+ throw new RuntimeException ("이메일 전송 중 오류가 발생했습니다." );
197+ }
198+ }
199+
200+ @ PostMapping ("/verifyEmail" )
201+ @ Operation (summary = "이메일 인증번호 검증" , description = "로그인된 사용자는 자동으로 인증번호를 검증하고, 비로그인 사용자는 요청 바디의 loginId(이메일)와 함께 인증번호를 검증합니다." )
202+ @ ApiResponses ({
203+ @ ApiResponse (responseCode = "200" , description = "인증번호 검증 성공" ),
204+ @ ApiResponse (responseCode = "400" , description = "잘못된 요청 (인증번호 불일치, loginId 없음)" )
205+ })
206+ public ResponseEntity <EmailResponse > verifyEmail (
207+ @ RequestBody @ Valid EmailVerifyCodeRequestDto requestDto ,
208+ Authentication authentication ,
209+ HttpServletRequest request ) {
210+
211+ String loginId = null ;
212+
213+ // 1. 로그인된 사용자인 경우 JWT 토큰에서 loginId 추출 (우선순위 1)
214+ if (authentication != null && authentication .isAuthenticated () &&
215+ !"anonymousUser" .equals (authentication .getPrincipal ())) {
216+
217+ // JWT 토큰에서 직접 loginid claim 추출
218+ try {
219+ String token = extractAccessTokenFromRequest (request );
220+ if (token != null ) {
221+ loginId = memberService .extractLoginIdFromToken (token );
222+ if (loginId != null ) {
223+ log .info ("JWT 토큰에서 loginId 추출 성공: {}" , loginId );
224+ } else {
225+ log .warn ("JWT 토큰에서 loginId 추출 실패" );
226+ }
227+ }
228+ } catch (Exception e ) {
229+ log .warn ("JWT 토큰에서 loginId 추출 중 오류: {}" , e .getMessage ());
230+ }
231+ }
232+
233+ // 2. 비로그인 사용자인 경우 요청 바디에서 loginId 추출 (우선순위 2)
234+ if (loginId == null ) {
235+ if (requestDto .getLoginId () != null && !requestDto .getLoginId ().isBlank ()) {
236+ loginId = requestDto .getLoginId ();
237+ log .info ("요청 바디에서 loginId 추출 성공: {}" , loginId );
238+ } else {
239+ log .error ("로그인하지 않은 상태에서 요청 바디에 loginId가 없음" );
240+ throw new IllegalArgumentException ("인증번호를 검증할 이메일 주소가 필요합니다. 로그인하거나 요청 바디에 loginId를 포함해주세요." );
241+ }
242+ }
243+
244+ try {
245+ // 서비스 호출 - 인증번호 검증
246+ boolean isValid = memberService .verifyAuthCode (loginId , requestDto .getVerificationCode ());
247+
248+ if (isValid ) {
249+ log .info ("이메일 인증번호 검증 성공: {}" , loginId );
250+ return ResponseEntity .ok (EmailResponse .success ("인증번호 검증 성공" , loginId ));
251+ } else {
252+ log .error ("이메일 인증번호 검증 실패 - 잘못된 인증번호: {}" , loginId );
253+ throw new IllegalArgumentException ("잘못된 인증번호이거나 만료된 인증번호입니다." );
254+ }
255+
256+ } catch (IllegalArgumentException e ) {
257+ log .error ("이메일 인증번호 검증 실패: loginId={}, error={}" , loginId , e .getMessage ());
258+ throw e ;
259+ } catch (Exception e ) {
260+ log .error ("이메일 인증번호 검증 중 오류 발생: loginId={}, error={}" , loginId , e .getMessage ());
261+ throw new RuntimeException ("인증번호 검증 중 오류가 발생했습니다." );
262+ }
263+ }
264+
265+ // ===== 비밀번호 재설정 엔드포인트 =====
266+
267+ @ PostMapping ("/password-reset/reset" )
268+ @ Operation (summary = "비밀번호 재설정" , description = "인증 토큰과 함께 새 비밀번호로 재설정합니다." )
269+ @ ApiResponses ({
270+ @ ApiResponse (responseCode = "200" , description = "비밀번호 재설정 성공" ),
271+ @ ApiResponse (responseCode = "400" , description = "인증되지 않았거나 잘못된 요청" )
272+ })
273+ public ResponseEntity <PasswordResetResponse > resetPassword (@ Valid @ RequestBody ResetPasswordRequestDto request ) {
274+ log .info ("비밀번호 재설정 요청: email={}" , request .getLoginId ());
275+
276+ memberService .resetPassword (request .getLoginId (), request .getNewPassword (), request .getSuccess ());
277+
278+ log .info ("비밀번호 재설정 성공: email={}" , request .getLoginId ());
279+ return ResponseEntity .ok (PasswordResetResponse .success ("비밀번호가 성공적으로 재설정되었습니다." , request .getLoginId ()));
280+ }
281+
143282 /**
144283 * HTTP 쿠키에서 리프레시 토큰을 추출합니다.
145284 * @param request HTTP 요청 객체
@@ -155,4 +294,27 @@ private String extractRefreshTokenFromCookies(HttpServletRequest request) {
155294 }
156295 return null ;
157296 }
297+
298+ /**
299+ * HTTP 쿠키에서 액세스 토큰을 추출합니다.
300+ * @param request HTTP 요청 객체
301+ * @return 액세스 토큰 값 또는 null
302+ */
303+ private String extractAccessTokenFromRequest (HttpServletRequest request ) {
304+ // 1. Authorization 헤더에서 추출 시도
305+ String authHeader = request .getHeader ("Authorization" );
306+ if (authHeader != null && authHeader .startsWith ("Bearer " )) {
307+ return authHeader .substring (7 );
308+ }
309+
310+ // 2. 쿠키에서 추출 시도
311+ if (request .getCookies () != null ) {
312+ for (jakarta .servlet .http .Cookie cookie : request .getCookies ()) {
313+ if ("accessToken" .equals (cookie .getName ())) {
314+ return cookie .getValue ();
315+ }
316+ }
317+ }
318+ return null ;
319+ }
158320}
0 commit comments