Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions refrigerator/.idea/modules/refrigerator.main.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions refrigerator/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

implementation 'org.springframework.boot:spring-boot-starter-mail'
}
tasks.named('test') {
useJUnitPlatform()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package moja.refrigerator.controller.user;

import jakarta.servlet.http.HttpServletRequest;
import moja.refrigerator.dto.user.request.PasswordResetRequest;
import moja.refrigerator.dto.user.request.PasswordUpdateRequest;
import moja.refrigerator.dto.user.request.UserCreateRequest;
import moja.refrigerator.dto.user.request.UserUpdateRequest;
import moja.refrigerator.service.user.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

Expand All @@ -25,7 +28,7 @@ public String getMainPage() {
@PostMapping("/auth/join")
public ResponseEntity<?> join(@RequestBody UserCreateRequest request) {
userService.createUser(request);
return ResponseEntity.ok().body("회원 가입이 완료되었습니다.");
return ResponseEntity.status(HttpStatus.CREATED).body("회원 가입이 완료되었습니다.");
}

// 회원 정보 수정
Expand All @@ -34,4 +37,18 @@ public ResponseEntity<?> update(@RequestBody UserUpdateRequest request) {
userService.updateUser(request);
return ResponseEntity.ok().body("회원 정보가 수정되었습니다.");
}

// 비밀번호 재발급
@PostMapping("/password/reset")
public ResponseEntity<?> resetPassword(@RequestBody PasswordResetRequest request) {
userService.resetPassword(request);
return ResponseEntity.ok().body("임시 비밀번호가 이메일로 발송되었습니다.");
}

// 비밀번호 재설정
@PutMapping("/password/update")
public ResponseEntity<?> updatePassword(@RequestBody PasswordUpdateRequest request) {
userService.updatePassword(request);
return ResponseEntity.ok().body("비밀번호가 변경되었습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package moja.refrigerator.dto.user.request;

import lombok.Data;

@Data
public class PasswordResetRequest {
private String userEmail;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package moja.refrigerator.dto.user.request;

import lombok.Data;

@Data
public class PasswordUpdateRequest {
private String currentPw;
private String newPw;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByUserNickname(String userNickname);
Optional<User> findByUserPk(long userPk);
Optional<User> findByUserId(String userId);
Optional<User> findByUserEmail(String userEmail);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package moja.refrigerator.service.email;

public interface EmailService {
void sendTempPassword(String name, String email, String tempPassword);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package moja.refrigerator.service.email;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class EmailServiceImpl implements EmailService {
private final JavaMailSender mailSender;

public EmailServiceImpl(JavaMailSender mailSender) {
this.mailSender = mailSender;
}

@Value("${spring.mail.username}")
private String senderEmail;

@Override
@Transactional
public void sendTempPassword(String name, String email, String tempPassword) {
SimpleMailMessage message = new SimpleMailMessage();

message.setTo(email);
message.setFrom(senderEmail);
message.setSubject("[ReciPick] 임시 비밀번호 발급");
message.setText(String.format("""
안녕하세요. %s님! 임시 비밀번호가 발급되었습니다.

임시 비밀번호: %s

보안을 위해 로그인 후 반드시 비밀번호를 변경해 주세요.
""", name, tempPassword));

mailSender.send(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package moja.refrigerator.service.user;

import moja.refrigerator.dto.user.request.PasswordResetRequest;
import moja.refrigerator.dto.user.request.PasswordUpdateRequest;
import moja.refrigerator.dto.user.request.UserCreateRequest;
import moja.refrigerator.dto.user.request.UserUpdateRequest;

public interface UserService {
void createUser(UserCreateRequest request);
void updateUser(UserUpdateRequest request);
void resetPassword(PasswordResetRequest request);
void updatePassword(PasswordUpdateRequest request);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package moja.refrigerator.service.user;

import lombok.RequiredArgsConstructor;
import moja.refrigerator.aggregate.user.User;
import moja.refrigerator.dto.user.request.PasswordResetRequest;
import moja.refrigerator.dto.user.request.PasswordUpdateRequest;
import moja.refrigerator.dto.user.request.UserCreateRequest;
import moja.refrigerator.dto.user.request.UserUpdateRequest;
import moja.refrigerator.exception.user.DuplicateUserException;
import moja.refrigerator.repository.user.TokenBlacklistRepository;
import moja.refrigerator.repository.user.UserRepository;
import moja.refrigerator.service.email.EmailService;
import org.modelmapper.ModelMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -16,18 +19,15 @@

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder passwordEncoder;
private final ModelMapper modelMapper;

public UserServiceImpl(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder, ModelMapper modelMapper) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.modelMapper = modelMapper;
}
private final EmailService emailService;

@Override
@Transactional
Expand Down Expand Up @@ -74,6 +74,46 @@ public void updateUser(UserUpdateRequest request) {
}
}

@Override
@Transactional
public void resetPassword(PasswordResetRequest request) {
// 이메일로 사용자 찾기
User user = userRepository.findByUserEmail(request.getUserEmail())
.orElseThrow(() -> new UsernameNotFoundException("해당 이메일로 가입된 계정이 없습니다."));

// 임시 비밀번호 생성
String tempPassword = UUID.randomUUID().toString().substring(0, 12);

try {
// 이메일 발송
emailService.sendTempPassword(user.getUserNickname(), user.getUserEmail(), tempPassword);
// 임시 비밀번호로 업데이트
user.setUserPw(passwordEncoder.encode(tempPassword));
} catch (Exception e) {
System.out.println("Detailed error: " + e.getMessage());
throw new RuntimeException("이메일 발송에 실패했습니다.");
}

}

@Override
@Transactional
public void updatePassword(PasswordUpdateRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userId = authentication.getName();

User user = userRepository.findByUserId(userId)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));

// 기존 비밀번호 검증
if (!passwordEncoder.matches(request.getCurrentPw(), user.getUserPw())) {
throw new IllegalArgumentException("기존 비밀번호가 일치하지 않습니다.");
}

// 새 비밀번호 암호화 후 저장
user.setUserPw(passwordEncoder.encode(request.getNewPw()));
}

private void checkDuplicateUser(UserCreateRequest request) {
List<String> errors = new ArrayList<>();
if (userRepository.existsByUserId(request.getUserId())) {
Expand Down