From 08b64e5574adea7519d164c097510b3f24c7a439 Mon Sep 17 00:00:00 2001 From: bu2000 Date: Mon, 10 Nov 2025 15:52:23 +0900 Subject: [PATCH 1/2] making tempPassword and changing tempPaswword logic complete --- .../config/JwtAuthenticationFilter.java | 2 +- .../algorithmchef/config/SecurityConfig.java | 4 +- .../controller/UserController.java | 45 ++++++++++++ .../dto/user/ChangePasswordRequest.java | 11 +++ .../dto/user/ChangePasswordResponse.java | 12 ++++ .../dto/user/FindPasswordRequest.java | 12 ++++ .../dto/user/FindPasswordResponse.java | 15 ++++ .../dto/user/FindUserIdRequest.java | 12 ++++ .../dto/user/FindUserIdResponse.java | 12 ++++ .../algorithmchef/dto/user/SurveyRequest.java | 23 ++++++ .../webservice/algorithmchef/model/User.java | 1 - .../repository/UserRepository.java | 2 + .../algorithmchef/service/EmailService.java | 71 +++++++++++++++++++ .../algorithmchef/service/UserService.java | 50 +++++++++++++ 14 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/webservice/algorithmchef/dto/user/ChangePasswordRequest.java create mode 100644 src/main/java/com/webservice/algorithmchef/dto/user/ChangePasswordResponse.java create mode 100644 src/main/java/com/webservice/algorithmchef/dto/user/FindPasswordRequest.java create mode 100644 src/main/java/com/webservice/algorithmchef/dto/user/FindPasswordResponse.java create mode 100644 src/main/java/com/webservice/algorithmchef/dto/user/FindUserIdRequest.java create mode 100644 src/main/java/com/webservice/algorithmchef/dto/user/FindUserIdResponse.java create mode 100644 src/main/java/com/webservice/algorithmchef/dto/user/SurveyRequest.java create mode 100644 src/main/java/com/webservice/algorithmchef/service/EmailService.java diff --git a/src/main/java/com/webservice/algorithmchef/config/JwtAuthenticationFilter.java b/src/main/java/com/webservice/algorithmchef/config/JwtAuthenticationFilter.java index d8d4354..53dadd0 100644 --- a/src/main/java/com/webservice/algorithmchef/config/JwtAuthenticationFilter.java +++ b/src/main/java/com/webservice/algorithmchef/config/JwtAuthenticationFilter.java @@ -35,7 +35,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { * πŸ’‘ μž„μ‹œ λΉ„λ°€λ²ˆν˜Έ(status="TEMPORARY") μ‚¬μš©μžκ°€ μ ‘κ·Όν•  수 μžˆλŠ” μœ μΌν•œ 경둜. * 이 경둜 μ™Έμ˜ λͺ¨λ“  API 접근은 403 Forbidden을 λ°˜ν™˜ν•©λ‹ˆλ‹€. */ - private static final String ALLOWED_PATH_FOR_TEMP_USER = "/auth/findPassword"; + private static final String ALLOWED_PATH_FOR_TEMP_USER = "/auth/update-tempPassword"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) diff --git a/src/main/java/com/webservice/algorithmchef/config/SecurityConfig.java b/src/main/java/com/webservice/algorithmchef/config/SecurityConfig.java index 9b6518c..a1bb528 100644 --- a/src/main/java/com/webservice/algorithmchef/config/SecurityConfig.java +++ b/src/main/java/com/webservice/algorithmchef/config/SecurityConfig.java @@ -34,8 +34,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http .authorizeHttpRequests(authorize -> authorize - // μ‹€μ œ μ‚¬μš©ν•˜λŠ” 둜그인/νšŒμ›κ°€μž… 경둜둜 μˆ˜μ •ν•΄μ£Όμ„Έμš”. - .requestMatchers("/auth/login", "/auth/signUp").permitAll() + .requestMatchers("/auth/login", "/auth/signUp", + "/auth/findPassword", "/auth/findUserId").permitAll() .anyRequest().authenticated()); http diff --git a/src/main/java/com/webservice/algorithmchef/controller/UserController.java b/src/main/java/com/webservice/algorithmchef/controller/UserController.java index 4c8c9ee..1cc5ebe 100644 --- a/src/main/java/com/webservice/algorithmchef/controller/UserController.java +++ b/src/main/java/com/webservice/algorithmchef/controller/UserController.java @@ -2,11 +2,21 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.webservice.algorithmchef.dto.user.ChangePasswordRequest; +import com.webservice.algorithmchef.dto.user.ChangePasswordResponse; +import com.webservice.algorithmchef.dto.user.FindPasswordRequest; +import com.webservice.algorithmchef.dto.user.FindPasswordResponse; +import com.webservice.algorithmchef.dto.user.FindUserIdRequest; +import com.webservice.algorithmchef.dto.user.FindUserIdResponse; import com.webservice.algorithmchef.dto.user.UserLoginRequest; import com.webservice.algorithmchef.dto.user.UserLoginResponse; import com.webservice.algorithmchef.dto.user.UserSignUpRequest; @@ -42,4 +52,39 @@ public ResponseEntity signUp(@RequestBody UserSignUpRequest userSignUpRequest return ResponseEntity.badRequest().body(e.getMessage()); } } + + @Transactional + @PatchMapping("/findPassword") + public ResponseEntity findPassword(@RequestBody FindPasswordRequest fPasswordRequest){ + try { + FindPasswordResponse pResponse = userService.findPassword(fPasswordRequest); + return ResponseEntity.ok(pResponse); + }catch(IllegalArgumentException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + + @PatchMapping("/update-tempPassword") + public ResponseEntity updateTempPasswod(@RequestBody ChangePasswordRequest cPasswordRequest, + @AuthenticationPrincipal UserDetails userDetails){ + try { + String userId = userDetails.getUsername(); + ChangePasswordResponse cResponse = userService.updateTempPassword(cPasswordRequest, userId); + return ResponseEntity.ok(cResponse); + }catch(IllegalArgumentException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + + @PostMapping("/findUserId") + public ResponseEntity findUserId(@RequestBody FindUserIdRequest fIdRequest){ + try { + FindUserIdResponse idResponse = userService.findUserId(fIdRequest); + return ResponseEntity.ok(idResponse); + }catch(IllegalArgumentException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + + } diff --git a/src/main/java/com/webservice/algorithmchef/dto/user/ChangePasswordRequest.java b/src/main/java/com/webservice/algorithmchef/dto/user/ChangePasswordRequest.java new file mode 100644 index 0000000..5d7d190 --- /dev/null +++ b/src/main/java/com/webservice/algorithmchef/dto/user/ChangePasswordRequest.java @@ -0,0 +1,11 @@ +package com.webservice.algorithmchef.dto.user; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ChangePasswordRequest { + + private String password; +} diff --git a/src/main/java/com/webservice/algorithmchef/dto/user/ChangePasswordResponse.java b/src/main/java/com/webservice/algorithmchef/dto/user/ChangePasswordResponse.java new file mode 100644 index 0000000..0274ba9 --- /dev/null +++ b/src/main/java/com/webservice/algorithmchef/dto/user/ChangePasswordResponse.java @@ -0,0 +1,12 @@ +package com.webservice.algorithmchef.dto.user; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ChangePasswordResponse { + + private String accessToken; + private String message; +} diff --git a/src/main/java/com/webservice/algorithmchef/dto/user/FindPasswordRequest.java b/src/main/java/com/webservice/algorithmchef/dto/user/FindPasswordRequest.java new file mode 100644 index 0000000..c31f1f8 --- /dev/null +++ b/src/main/java/com/webservice/algorithmchef/dto/user/FindPasswordRequest.java @@ -0,0 +1,12 @@ +package com.webservice.algorithmchef.dto.user; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class FindPasswordRequest { + + private String userId; + private String email; +} diff --git a/src/main/java/com/webservice/algorithmchef/dto/user/FindPasswordResponse.java b/src/main/java/com/webservice/algorithmchef/dto/user/FindPasswordResponse.java new file mode 100644 index 0000000..69f4cfb --- /dev/null +++ b/src/main/java/com/webservice/algorithmchef/dto/user/FindPasswordResponse.java @@ -0,0 +1,15 @@ +package com.webservice.algorithmchef.dto.user; + +import com.webservice.algorithmchef.model.User; + +public class FindPasswordResponse { + + private String userId; + private String message; + + public FindPasswordResponse(User user) { + this.userId = user.getUserId(); + this.message = user.getEmail() + "둜 μž„μ‹œλΉ„λ°€λ²ˆν˜Έ μ „μ†‘ν–ˆμŠ΅λ‹ˆλ‹€."; + } + +} diff --git a/src/main/java/com/webservice/algorithmchef/dto/user/FindUserIdRequest.java b/src/main/java/com/webservice/algorithmchef/dto/user/FindUserIdRequest.java new file mode 100644 index 0000000..926c6ce --- /dev/null +++ b/src/main/java/com/webservice/algorithmchef/dto/user/FindUserIdRequest.java @@ -0,0 +1,12 @@ +package com.webservice.algorithmchef.dto.user; + +import java.time.LocalDateTime; + +import lombok.Getter; + +@Getter +public class FindUserIdRequest { + private String email; + private LocalDateTime birthDate; + +} diff --git a/src/main/java/com/webservice/algorithmchef/dto/user/FindUserIdResponse.java b/src/main/java/com/webservice/algorithmchef/dto/user/FindUserIdResponse.java new file mode 100644 index 0000000..805f9c7 --- /dev/null +++ b/src/main/java/com/webservice/algorithmchef/dto/user/FindUserIdResponse.java @@ -0,0 +1,12 @@ +package com.webservice.algorithmchef.dto.user; + +import lombok.Getter; + +@Getter +public class FindUserIdResponse { + + private String userId; + public FindUserIdResponse(String userId) { + this.userId = userId; + } +} diff --git a/src/main/java/com/webservice/algorithmchef/dto/user/SurveyRequest.java b/src/main/java/com/webservice/algorithmchef/dto/user/SurveyRequest.java new file mode 100644 index 0000000..23f19b6 --- /dev/null +++ b/src/main/java/com/webservice/algorithmchef/dto/user/SurveyRequest.java @@ -0,0 +1,23 @@ +package com.webservice.algorithmchef.dto.user; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class SurveyRequest { + + private List healthGoalIds; + private List allergyIds; + private String dislikedIngredients; + private String likedIngredients; + private String preferredIngredients; + private String preferredCuisinel; + private String spiceLevel; + private boolean allowPushConsumption; + private boolean allowPushComment; + private boolean allowPushNudge; + +} diff --git a/src/main/java/com/webservice/algorithmchef/model/User.java b/src/main/java/com/webservice/algorithmchef/model/User.java index 35869f1..b7d5c1a 100644 --- a/src/main/java/com/webservice/algorithmchef/model/User.java +++ b/src/main/java/com/webservice/algorithmchef/model/User.java @@ -1,6 +1,5 @@ package com.webservice.algorithmchef.model; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collection; import java.util.Collections; diff --git a/src/main/java/com/webservice/algorithmchef/repository/UserRepository.java b/src/main/java/com/webservice/algorithmchef/repository/UserRepository.java index bec3f29..37594ab 100644 --- a/src/main/java/com/webservice/algorithmchef/repository/UserRepository.java +++ b/src/main/java/com/webservice/algorithmchef/repository/UserRepository.java @@ -1,5 +1,6 @@ package com.webservice.algorithmchef.repository; +import java.time.LocalDateTime; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,5 +10,6 @@ public interface UserRepository extends JpaRepository { public Optional findByUserId(String userId); + public Optional findByEmailAndBirthDate(String email,LocalDateTime birthDate); } diff --git a/src/main/java/com/webservice/algorithmchef/service/EmailService.java b/src/main/java/com/webservice/algorithmchef/service/EmailService.java new file mode 100644 index 0000000..8d3161e --- /dev/null +++ b/src/main/java/com/webservice/algorithmchef/service/EmailService.java @@ -0,0 +1,71 @@ +package com.webservice.algorithmchef.service; + + +import java.security.SecureRandom; + +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@Slf4j +@RequiredArgsConstructor +public class EmailService { + + private final JavaMailSender javaMailSender; + + private static final String CHAR_LOWER = "abcdefghijklmnopqrstuvwxyz"; + private static final String CHAR_UPPER = CHAR_LOWER.toUpperCase(); + private static final String NUMBER = "0123456789"; + + private static final String DATA_FOR_RANDOM_STRING = CHAR_LOWER + CHAR_UPPER + NUMBER; + private static final SecureRandom random = new SecureRandom(); + + public String makeTemporaryPassword() { + final int passwordLength = 12; + StringBuilder sb = new StringBuilder(passwordLength); + + for (int i = 0; i < passwordLength; i++) { + int rndIdx = random.nextInt(DATA_FOR_RANDOM_STRING.length()); + char rndChar = DATA_FOR_RANDOM_STRING.charAt(rndIdx); + + sb.append(rndChar); + } + return sb.toString(); + } + + public void sendTemporaryPasswordEmail(String toEmail, String tempPassword) { + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + try { + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); + + helper.setTo(toEmail); + helper.setSubject("[μ•Œκ³ λ¦¬λ“¬ μ…°ν”„] μž„μ‹œ λΉ„λ°€λ²ˆν˜Έ μ•ˆλ‚΄"); + + String htmlContent = "" + + "" + + "

[λ°₯도둑] μž„μ‹œ λΉ„λ°€λ²ˆν˜Έ μ•ˆλ‚΄

" + + "

μš”μ²­ν•˜μ‹  μž„μ‹œ λΉ„λ°€λ²ˆν˜Έμž…λ‹ˆλ‹€. 둜그인 ν›„ λ°˜λ“œμ‹œ λΉ„λ°€λ²ˆν˜Έλ₯Ό λ³€κ²½ν•΄μ£Όμ„Έμš”.

" + + "
" + + tempPassword + + "
" + + "" + + ""; + + helper.setText(htmlContent, true); + + javaMailSender.send(mimeMessage); + log.info("μž„μ‹œ λΉ„λ°€λ²ˆν˜Έ 이메일 λ°œμ†‘ 성곡: {}", toEmail); + + } catch (MessagingException e) { + log.error("μž„μ‹œ λΉ„λ°€λ²ˆν˜Έ 이메일 λ°œμ†‘ μ‹€νŒ¨: {}", e.getMessage()); + throw new RuntimeException("이메일 λ°œμ†‘μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.", e); + } + } + +} diff --git a/src/main/java/com/webservice/algorithmchef/service/UserService.java b/src/main/java/com/webservice/algorithmchef/service/UserService.java index e7a2fc9..ef83bec 100644 --- a/src/main/java/com/webservice/algorithmchef/service/UserService.java +++ b/src/main/java/com/webservice/algorithmchef/service/UserService.java @@ -7,7 +7,14 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.webservice.algorithmchef.dto.user.ChangePasswordRequest; +import com.webservice.algorithmchef.dto.user.ChangePasswordResponse; +import com.webservice.algorithmchef.dto.user.FindPasswordRequest; +import com.webservice.algorithmchef.dto.user.FindPasswordResponse; +import com.webservice.algorithmchef.dto.user.FindUserIdRequest; +import com.webservice.algorithmchef.dto.user.FindUserIdResponse; import com.webservice.algorithmchef.dto.user.UserLoginRequest; import com.webservice.algorithmchef.dto.user.UserLoginResponse; import com.webservice.algorithmchef.dto.user.UserSignUpRequest; @@ -25,6 +32,7 @@ public class UserService implements UserDetailsService{ private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final JwtUtil jwtUtil; + private final EmailService emailService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { @@ -68,5 +76,47 @@ public UserLoginResponse login(UserLoginRequest userLoginRequest) { return new UserLoginResponse(token,UserLoginResponse.LoginStatus.SUCCESS); } } + + @Transactional + public ChangePasswordResponse updateTempPassword(ChangePasswordRequest cRequest,String userId) { + User user = userRepository.findByUserId(userId) + .orElseThrow(() -> new IllegalArgumentException("μ‚¬μš©μžλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.")); + String newPassword = cRequest.getPassword(); + String encodedPassword = passwordEncoder.encode(newPassword); + user.setPassword(encodedPassword); + user.setTemporaryPassword(false); + String token = jwtUtil.createAccessToken(user.getUserId(), user.getRole(), user.isTemporaryPassword()); + String message = "μž„μ‹œ λΉ„λ°€λ²ˆν˜Έ λ³€κ²½λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 메인 ν™”λ©΄μœΌλ‘œ μ΄λ™ν•©λ‹ˆλ‹€."; + return new ChangePasswordResponse(token, message); + } + + @Transactional + public FindUserIdResponse findUserId(FindUserIdRequest findUserIdRequest) { + String email = findUserIdRequest.getEmail(); + LocalDateTime birthDate = findUserIdRequest.getBirthDate(); + User user = userRepository.findByEmailAndBirthDate(email, birthDate) + .orElseThrow(() -> new IllegalArgumentException("ν•΄λ‹Ήν•˜λŠ” 정보와 μΌμΉ˜ν•˜λŠ” μ‚¬μš©μžκ°€ μ—†μŠ΅λ‹ˆλ‹€.")); + String userId = user.getUserId(); + return new FindUserIdResponse(userId); + } + + @Transactional + public FindPasswordResponse findPassword(FindPasswordRequest fPasswordRequest) { + String userId = fPasswordRequest.getUserId(); + String email = fPasswordRequest.getEmail(); + User user = userRepository.findByUserId(userId) + .orElseThrow(() -> new IllegalArgumentException("μ‚¬μš©μžλ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.")); + if(!user.getEmail().equals(email)) { + throw new IllegalArgumentException("λ“±λ‘λœ μ‚¬μš©μžμ˜ 이메일이 μ•„λ‹™λ‹ˆλ‹€."); + } + String tempPassword = emailService.makeTemporaryPassword(); + String encodedPassword = passwordEncoder.encode(tempPassword); + user.setPassword(encodedPassword); + user.setTemporaryPassword(true); + emailService.sendTemporaryPasswordEmail(email,tempPassword); + return new FindPasswordResponse(user); + } + + } From 75fbd8523ed01fa62d7c9d1c508977032831c3b8 Mon Sep 17 00:00:00 2001 From: bu2000 Date: Tue, 11 Nov 2025 21:26:55 +0900 Subject: [PATCH 2/2] make ci.yml --- .github/workflows/ci.yml | 39 +++++++++++++++++++ .../algorithmchef/service/EmailService.java | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8182e6b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: Java CI with Gradle +on: + push: + branches: ["main", "develop"] + + pull_request: + branches: ["main", "develop"] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: "21" + distribution: "temurin" + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + + - name: Build with Gradle (Skip Tests) + env: + JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }} + run: ./gradlew build -x test diff --git a/src/main/java/com/webservice/algorithmchef/service/EmailService.java b/src/main/java/com/webservice/algorithmchef/service/EmailService.java index 8d3161e..55888e9 100644 --- a/src/main/java/com/webservice/algorithmchef/service/EmailService.java +++ b/src/main/java/com/webservice/algorithmchef/service/EmailService.java @@ -49,7 +49,7 @@ public void sendTemporaryPasswordEmail(String toEmail, String tempPassword) { String htmlContent = "" + "" - + "

[λ°₯도둑] μž„μ‹œ λΉ„λ°€λ²ˆν˜Έ μ•ˆλ‚΄

" + + "

[μ•Œκ³ λ¦¬λ“¬ μ…°ν”„] μž„μ‹œ λΉ„λ°€λ²ˆν˜Έ μ•ˆλ‚΄

" + "

μš”μ²­ν•˜μ‹  μž„μ‹œ λΉ„λ°€λ²ˆν˜Έμž…λ‹ˆλ‹€. 둜그인 ν›„ λ°˜λ“œμ‹œ λΉ„λ°€λ²ˆν˜Έλ₯Ό λ³€κ²½ν•΄μ£Όμ„Έμš”.

" + "
" + tempPassword