66import com .finsight .finsight .domain .category .domain .service .CategoryService ;
77import com .finsight .finsight .domain .category .persistence .repository .UserCategoryOrderRepository ;
88import com .finsight .finsight .domain .category .persistence .repository .UserCategoryRepository ;
9+ import com .finsight .finsight .domain .mypage .application .dto .request .ChangePasswordRequest ;
910import com .finsight .finsight .domain .mypage .application .dto .request .UpdateProfileRequest ;
1011import com .finsight .finsight .domain .mypage .application .dto .response .LearningReportResponse ;
1112import com .finsight .finsight .domain .mypage .application .dto .response .MypageResponse ;
1819import com .finsight .finsight .domain .storage .persistence .entity .FolderType ;
1920import com .finsight .finsight .domain .storage .persistence .repository .FolderItemRepository ;
2021import com .finsight .finsight .domain .storage .persistence .repository .FolderRepository ;
22+ import com .finsight .finsight .domain .user .domain .constant .AuthType ;
23+ import com .finsight .finsight .domain .user .persistence .entity .UserAuthEntity ;
2124import com .finsight .finsight .domain .user .persistence .entity .UserEntity ;
2225import com .finsight .finsight .domain .user .persistence .repository .UserRepository ;
2326import lombok .RequiredArgsConstructor ;
27+ import org .springframework .security .crypto .password .PasswordEncoder ;
2428import org .springframework .stereotype .Service ;
2529import org .springframework .transaction .annotation .Transactional ;
2630
@@ -46,6 +50,7 @@ public class MypageService {
4650 private final QuizAttemptRepository quizAttemptRepository ;
4751 private final FolderRepository folderRepository ;
4852 private final UserCategoryOrderRepository userCategoryOrderRepository ;
53+ private final PasswordEncoder passwordEncoder ;
4954
5055 public MypageResponse .MemberProfileResponse getUserProfile (Long userId ) {
5156 // db에서 조회에 실패한 경우
@@ -266,6 +271,39 @@ private List<LearningReportResponse.ChecklistStatus> buildChecklistStatus(
266271 return result ;
267272 }
268273
274+ /**
275+ * 비밀번호 변경 (카카오 계정 불가)
276+ */
277+ @ Transactional
278+ public void changePassword (Long userId , ChangePasswordRequest request ) {
279+ UserEntity user = userRepository .findById (userId )
280+ .orElseThrow (() -> new MypageException (MypageErrorCode .MEMBER_NOT_FOUND ));
281+
282+ // 카카오 계정 체크 (EMAIL 타입이 없으면 카카오 전용 계정)
283+ UserAuthEntity auth = user .getUserAuths ().stream ()
284+ .filter (a -> a .getAuthType () == AuthType .EMAIL )
285+ .findFirst ()
286+ .orElseThrow (() -> new MypageException (MypageErrorCode .KAKAO_PASSWORD_NOT_ALLOWED ));
287+
288+ // 현재 비밀번호 확인
289+ if (!passwordEncoder .matches (request .currentPassword (), auth .getPasswordHash ())) {
290+ throw new MypageException (MypageErrorCode .INVALID_CURRENT_PASSWORD );
291+ }
292+
293+ // 새 비밀번호 형식 검증
294+ if (!request .newPassword ().matches ("^(?=.*[a-zA-Z])(?=.*\\ d)[a-zA-Z\\ d]{6,18}$" )) {
295+ throw new MypageException (MypageErrorCode .INVALID_NEW_PASSWORD_FORMAT );
296+ }
297+
298+ // 현재와 동일한지 체크
299+ if (passwordEncoder .matches (request .newPassword (), auth .getPasswordHash ())) {
300+ throw new MypageException (MypageErrorCode .SAME_AS_CURRENT_PASSWORD );
301+ }
302+
303+ // 비밀번호 변경
304+ auth .updatePassword (passwordEncoder .encode (request .newPassword ()));
305+ }
306+
269307 private String getDayOfWeekKorean (DayOfWeek dayOfWeek ) {
270308 return switch (dayOfWeek ) {
271309 case MONDAY -> "월" ;
0 commit comments