Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.ezcode.codetest.application.usermanagement.auth.dto.request;

import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class VerifyEmailCodeRequest {

@NotBlank(message = "인증 번호는 공백일 수 없습니다")
private String verificationCode;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.ezcode.codetest.application.usermanagement.auth.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "인증코드 전송 성공")
public record SendEmailCodeResponse(
@Schema(description = "인증코드 전송 성공 메세지")
String message
) {
public static SendEmailCodeResponse from(String message) {
return new SendEmailCodeResponse(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

@Schema(description = "회원가입 응답")
public record SignupResponse(
@Schema(description = "생성된 token")
String token
@Schema(description = "회원 가입 완료 메세지")
String message
) {
public static SignupResponse from(String token) {
return new SignupResponse(token);
public static SignupResponse from(String message) {
return new SignupResponse(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.ezcode.codetest.application.usermanagement.auth.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "이메일 인증 번호 입력 응답")
public record VerifyEmailCodeResponse(
@Schema(description = "인증 번호 성공 응답 메세지")
String message
) {
public static VerifyEmailCodeResponse from(String message) {
return new VerifyEmailCodeResponse(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.ezcode.codetest.application.usermanagement.auth.dto.request.VerifyEmailCodeRequest;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.RefreshTokenResponse;
import org.ezcode.codetest.application.usermanagement.auth.dto.request.SigninRequest;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.SendEmailCodeResponse;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.SigninResponse;
import org.ezcode.codetest.application.usermanagement.auth.dto.request.SignupRequest;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.SignupResponse;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.VerifyEmailCodeResponse;
import org.ezcode.codetest.application.usermanagement.user.dto.response.LogoutResponse;
import org.ezcode.codetest.domain.user.exception.AuthException;
import org.ezcode.codetest.domain.user.exception.UserException;
import org.ezcode.codetest.domain.user.exception.code.AuthExceptionCode;
import org.ezcode.codetest.domain.user.exception.code.UserExceptionCode;
import org.ezcode.codetest.domain.user.model.entity.User;
import org.ezcode.codetest.domain.user.model.entity.UserAuthType;
import org.ezcode.codetest.domain.user.model.enums.AuthType;
import org.ezcode.codetest.domain.user.service.MailService;
import org.ezcode.codetest.domain.user.service.UserDomainService;
import org.ezcode.codetest.common.security.util.JwtUtil;
import org.springframework.data.redis.core.RedisTemplate;
Expand All @@ -28,73 +34,106 @@
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class AuthService {

private final UserDomainService userDomainService;
private final JwtUtil jwtUtil;
private final RedisTemplate<String, String> redisTemplate;

private final MailService mailService;

/*
이메일 회원가입
- 이미 가입된 이메일 거절
- 비밀번호 암호화
*/
@Transactional
public SignupResponse signup(SignupRequest signupRequest) {
public SignupResponse signup(SignupRequest request) {
validateRequest(request);
userRegisterationProcess(request);

userDomainService.checkEmailUnique(signupRequest.getEmail());
return SignupResponse.from("회원가입이 완료되었습니다.");
}

if (!signupRequest.getPassword().equals(signupRequest.getPasswordConfirm())){
//1. 보낸 요청의 비밀번호&비밀번호확인이 일치하는지
private void validateRequest(SignupRequest request) {
userDomainService.checkEmailUnique(request.getEmail());
if (!request.getPassword().equals(request.getPasswordConfirm())){
throw new AuthException(AuthExceptionCode.PASSWORD_NOT_MATCH);
};
}

//2. 이미 다른 방식으로 회원가입한 유저인지 검증
private void userRegisterationProcess(SignupRequest request) {
String encodedPassword = userDomainService.encodePassword(request.getPassword());
User existUser = userDomainService.getUserByEmail(request.getEmail());
if ((existUser == null)) {
createNewUser(request, encodedPassword);
} else {
updateExistingUser(existUser, encodedPassword);
}
}

String encodedPassword = userDomainService.encodePassword(signupRequest.getPassword());
//3. 만약 아예 첫 가입 유저일 때
private void createNewUser(SignupRequest request, String encodedPassword) {
User newUser = User.emailUser(
request.getEmail(),
encodedPassword,
request.getUsername(),
request.getNickname(),
request.getAge()
);

User existUser = userDomainService.getUserByEmail(signupRequest.getEmail());
userDomainService.createUser(newUser);
userDomainService.createUserAuthType(new UserAuthType(newUser, AuthType.EMAIL));

String bearToken;
}

//만약 아예 유저 테이블에 없으면 둘 다 저장
if (existUser == null) {
User newUser = User.emailUser(
signupRequest.getEmail(),
encodedPassword,
signupRequest.getUsername(),
signupRequest.getNickname(),
signupRequest.getAge()
);
UserAuthType userAuthType = new UserAuthType(newUser, AuthType.EMAIL);
userDomainService.createUser(newUser);
userDomainService.createUserAuthType(userAuthType);

bearToken = jwtUtil.createToken(
newUser.getId(),
newUser.getEmail(),
newUser.getRole(),
newUser.getUsername(),newUser.getNickname(),
newUser.getTier());
} else {
//유저 테이블에는 존재하다면 AuthType만 추가
UserAuthType userAuthType = new UserAuthType(existUser, AuthType.EMAIL);
userDomainService.createUserAuthType(userAuthType);

//로컬 가입(이메일)은 안되어있는데 소셜은 되어있는 경우이므로, UUID 비번을 사용자가 지정한 비번으로 변경한다. -> 이후 비번 변경하면 User테이블에서 변경하면됨.
existUser.modifyPassword(encodedPassword);
log.info("유저 타입 저장 완료 {}", userAuthType);

bearToken = jwtUtil.createToken(
existUser.getId(),
existUser.getEmail(),
existUser.getRole(),
existUser.getUsername(),
existUser.getNickname(),
existUser.getTier());
}
//4. 만약 이전에 다른 방식으로 가입했었던(소셜) 회원일 때 -> UserAuthType테이블에 인증 방식만 추가
private void updateExistingUser(User existUser, String encodedPassword) {
//로컬 가입(이메일)은 안되어있는데 소셜은 되어있는 경우이므로, UUID 비번을 사용자가 지정한 비번으로 변경한다.
// -> 이후 비번 변경하면 User테이블에서 변경하면됨.
existUser.modifyPassword(encodedPassword);
UserAuthType userAuthType = new UserAuthType(existUser, AuthType.EMAIL);
userDomainService.createUserAuthType(userAuthType);
}

@Transactional
public SendEmailCodeResponse sendEmailCode(Long userId, String email) {
mailService.sendMail(userId, email);
return SendEmailCodeResponse.from("인증 코드를 전송했습니다.");
}

@Transactional
public VerifyEmailCodeResponse verifyEmailCode(Long userId, VerifyEmailCodeRequest verifyEmailCodeRequest) {
boolean isMatch = mailService.verifyCode(userId, verifyEmailCodeRequest.getVerificationCode());

User user = userDomainService.getUserById(userId);
if (isMatch){
user.setVerified();
return VerifyEmailCodeResponse.from("인증되었습니다");
} else {
throw new UserException(UserExceptionCode.NOT_MATCH_CODE);
}
}

return SignupResponse.from(bearToken);
private String createAccessToken(User user) {
return jwtUtil.createToken(
user.getId(),
user.getEmail(),
user.getRole(),
user.getUsername(),
user.getNickname(),
user.getTier());
}
private String createRefreshToken(User user) {
String refreshToken = jwtUtil.createRefreshToken(user.getId());
redisTemplate.opsForValue().set(
"RefreshToken:" + user.getId(),
refreshToken,
jwtUtil.getExpiration(refreshToken),
TimeUnit.MILLISECONDS);
return refreshToken;
}

/*
Expand All @@ -118,30 +157,12 @@ public SigninResponse signin(@Valid SigninRequest signinRequest) {

userDomainService.userPasswordCheck(signinRequest.getEmail(), signinRequest.getPassword());

log.info("비밀번호 체크 완료");

String bearToken = jwtUtil.createToken(
loginUser.getId(),
loginUser.getEmail(),
loginUser.getRole(),
loginUser.getUsername(),
loginUser.getNickname(),
loginUser.getTier());

log.info("토큰 발급 완료");
String accessToken = createAccessToken(loginUser);

//refresh 토큰 발급
String refreshToken = jwtUtil.createRefreshToken(loginUser.getId());
log.info("refresh token 발급 완료");

//redis에 RefreshToken : {} 형식으로 저장
redisTemplate.opsForValue().set(
"RefreshToken:" + loginUser.getId(),
refreshToken,
jwtUtil.getExpiration(refreshToken),
TimeUnit.MILLISECONDS);
String refreshToken = createRefreshToken(loginUser);

return SigninResponse.from(bearToken, refreshToken);
return SigninResponse.from(accessToken, refreshToken);
}


Expand Down Expand Up @@ -172,16 +193,14 @@ public RefreshTokenResponse refreshToken(String token) {

Long userId = jwtUtil.getUserId(token);

log.info("유저 아이디 가져옴 id : {}", userId);
String savedToken = redisTemplate.opsForValue().get("RefreshToken:" + userId);
log.info("저장된 토큰 가져옴 {}", savedToken);

if (savedToken==null || !savedToken.equals(token)){
log.error("저장된 토큰 없음");
throw new AuthException(AuthExceptionCode.INVALID_REFRESH_TOKEN);
}

User user = userDomainService.getUserById(userId);
log.info("유저 도메인서비스에서 유저 아이디로 유저 찾아옴");

String newAccessToken = jwtUtil.createToken(
user.getId(),
user.getEmail(),
Expand All @@ -193,4 +212,5 @@ public RefreshTokenResponse refreshToken(String token) {

return RefreshTokenResponse.from(newAccessToken);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import java.time.temporal.ChronoUnit;
import java.util.List;

import org.ezcode.codetest.application.usermanagement.auth.dto.request.VerifyEmailCodeRequest;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.SendEmailCodeResponse;
import org.ezcode.codetest.application.usermanagement.auth.dto.response.VerifyEmailCodeResponse;
import org.ezcode.codetest.application.usermanagement.user.model.UsersByWeek;
import org.ezcode.codetest.domain.submission.dto.WeeklySolveCount;
import org.ezcode.codetest.application.usermanagement.user.dto.request.ChangeUserPasswordRequest;
Expand All @@ -13,16 +16,20 @@
import org.ezcode.codetest.application.usermanagement.user.dto.response.WithdrawUserResponse;
import org.ezcode.codetest.domain.submission.service.SubmissionDomainService;
import org.ezcode.codetest.domain.user.exception.AuthException;
import org.ezcode.codetest.domain.user.exception.UserException;
import org.ezcode.codetest.domain.user.exception.code.AuthExceptionCode;
import org.ezcode.codetest.domain.user.exception.code.UserExceptionCode;
import org.ezcode.codetest.domain.user.model.entity.AuthUser;
import org.ezcode.codetest.domain.user.model.entity.User;
import org.ezcode.codetest.domain.user.model.enums.AuthType;
import org.ezcode.codetest.domain.user.service.MailService;
import org.ezcode.codetest.domain.user.service.UserDomainService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

Expand All @@ -34,6 +41,7 @@ public class UserService {
private final UserDomainService userDomainService;
private final SubmissionDomainService submissionDomainService;
private final RedisTemplate<String, String> redisTemplate;
private final MailService mailService;

@Transactional(readOnly = true)
public UserInfoResponse getUserInfo(AuthUser authUser) {
Expand Down Expand Up @@ -123,4 +131,5 @@ public void resetAllUsersTokensWeekly(LocalDateTime startDateTime, LocalDateTime

userDomainService.resetReviewTokensForUsers(UsersByWeek.from(counts, weekLength));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
public enum UserExceptionCode implements ResponseCode {

NOT_ENOUGH_TOKEN(false, HttpStatus.BAD_REQUEST, "리뷰 토큰이 부족합니다."),
;
NOT_MATCH_CODE(false, HttpStatus.BAD_REQUEST, "이메일 인증 코드가 일치하지 않습니다.");

private final boolean success;
private final HttpStatus status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ public class User extends BaseEntity {
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserAuthType> userAuthTypes = new ArrayList<>();

private boolean verified; //이메일 인증 여부



/*
Expand All @@ -88,6 +90,7 @@ public static User emailUser(String email, String password, String username, Str
.tier(Tier.NEWBIE)
.role(UserRole.ADMIN) // 테스트용
.isDeleted(false)
.verified(false)
.build();
}

Expand All @@ -104,13 +107,14 @@ public static User socialUser(String email, String username, String nickname, St
.nickname(nickname) //닉네임 자동 생성
.password(password)
.isDeleted(false)
.verified(false)
.build();
}


@Builder
public User(String email, String password, String username, String nickname,
Integer age, Tier tier, UserRole role, boolean isDeleted) {
Integer age, Tier tier, UserRole role, boolean isDeleted, boolean verified) {
this.email = email;
this.password = password;
this.username = username;
Expand All @@ -119,6 +123,7 @@ public User(String email, String password, String username, String nickname,
this.tier = tier;
this.role = role;
this.isDeleted = isDeleted;
this.verified = verified;
}

/*
Expand Down Expand Up @@ -148,6 +153,10 @@ public void modifyPassword(String newPassword) {
this.password = newPassword;
}

public void setVerified(){
this.verified = true;
}

public void decreaseReviewToken() {
this.reviewToken -= 1;
}
Expand Down
Loading