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
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.webservice.algorithmchef.dto.user;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ChangePasswordRequest {

private String password;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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() + "로 임시비밀번호 전송했습니다.";
}

}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Long> healthGoalIds;
private List<Long> 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;

}
1 change: 0 additions & 1 deletion src/main/java/com/webservice/algorithmchef/model/User.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.webservice.algorithmchef.repository;

import java.time.LocalDateTime;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -9,5 +10,6 @@
public interface UserRepository extends JpaRepository<User, Long> {

public Optional<User> findByUserId(String userId);
public Optional<User> findByEmailAndBirthDate(String email,LocalDateTime birthDate);

}
Original file line number Diff line number Diff line change
@@ -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 = "<html>"
+ "<body style='font-family: Arial, sans-serif;'>"
+ "<h2>[알고리듬 셰프] 임시 비밀번호 안내</h2>"
+ "<p>요청하신 임시 비밀번호입니다. 로그인 후 반드시 비밀번호를 변경해주세요.</p>"
+ "<div style='background-color: #f4f4f4; padding: 15px; border-radius: 5px; font-size: 18px; font-weight: bold; text-align: center;'>"
+ tempPassword
+ "</div>"
+ "</body>"
+ "</html>";

helper.setText(htmlContent, true);

javaMailSender.send(mimeMessage);
log.info("임시 비밀번호 이메일 발송 성공: {}", toEmail);

} catch (MessagingException e) {
log.error("임시 비밀번호 이메일 발송 실패: {}", e.getMessage());
throw new RuntimeException("이메일 발송에 실패했습니다.", e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
}



}