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
32 changes: 22 additions & 10 deletions src/main/java/project/flipnote/auth/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
package project.flipnote.auth.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
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 jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import project.flipnote.auth.model.EmailVerificationConfirmRequest;
import project.flipnote.auth.model.EmailVerificationRequest;
import project.flipnote.auth.model.TokenPair;
import project.flipnote.auth.model.UserLoginDto;
import project.flipnote.auth.model.UserLoginRequest;
import project.flipnote.auth.model.UserLoginResponse;
import project.flipnote.auth.service.AuthService;
import project.flipnote.common.security.jwt.JwtConstants;
import project.flipnote.common.security.jwt.JwtProperties;
import project.flipnote.common.util.CookieUtil;

@RequiredArgsConstructor
Expand All @@ -23,24 +26,33 @@
public class AuthController {

private final AuthService authService;
private final JwtProperties jwtProperties;

@PostMapping("/login")
public ResponseEntity<UserLoginDto.Response> login(
@Valid @RequestBody UserLoginDto.Request req,
HttpServletResponse servletResponse
public ResponseEntity<UserLoginResponse> login(
@Valid @RequestBody UserLoginRequest req
) {
TokenPair tokenPair = authService.login(req);

CookieUtil.addCookie(servletResponse, JwtConstants.REFRESH_TOKEN, tokenPair.refreshToken(), 30 * 24 * 60 * 60);
long expirationSeconds = jwtProperties.getRefreshTokenExpiration().toSeconds();
Cookie cookie = CookieUtil.createCookie(
JwtConstants.REFRESH_TOKEN,
tokenPair.refreshToken(),
Math.toIntExact(expirationSeconds)
);

return ResponseEntity.ok(UserLoginDto.Response.from(tokenPair.accessToken()));
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.body(UserLoginResponse.from(tokenPair.accessToken()));
}

@PostMapping("/logout")
public ResponseEntity<Void> logout(HttpServletResponse servletResponse) {
CookieUtil.deleteCookie(servletResponse, JwtConstants.REFRESH_TOKEN);
public ResponseEntity<Void> logout() {
Cookie expiredCookie = CookieUtil.createExpiredCookie(JwtConstants.REFRESH_TOKEN);

return ResponseEntity.ok().build();
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, expiredCookie.toString())
.build();
}

@PostMapping("/email")
Expand Down
26 changes: 0 additions & 26 deletions src/main/java/project/flipnote/auth/model/UserLoginDto.java

This file was deleted.

15 changes: 15 additions & 0 deletions src/main/java/project/flipnote/auth/model/UserLoginRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package project.flipnote.auth.model;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import project.flipnote.common.validation.annotation.ValidPassword;

public record UserLoginRequest(

@Email @NotBlank
String email,

@ValidPassword
String password
) {
}
10 changes: 10 additions & 0 deletions src/main/java/project/flipnote/auth/model/UserLoginResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package project.flipnote.auth.model;

public record UserLoginResponse(
String accessToken
) {

public static UserLoginResponse from(String accessToken) {
return new UserLoginResponse(accessToken);
}
}
76 changes: 38 additions & 38 deletions src/main/java/project/flipnote/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
import project.flipnote.auth.model.EmailVerificationConfirmRequest;
import project.flipnote.auth.model.EmailVerificationRequest;
import project.flipnote.auth.model.TokenPair;
import project.flipnote.auth.model.UserLoginDto;
import project.flipnote.auth.model.UserLoginRequest;
import project.flipnote.auth.repository.EmailVerificationRedisRepository;
import project.flipnote.common.exception.BizException;
import project.flipnote.common.security.jwt.JwtComponent;
import project.flipnote.user.entity.User;
import project.flipnote.user.entity.UserStatus;
import project.flipnote.user.repository.UserRepository;

@Slf4j
Expand All @@ -35,26 +36,14 @@ public class AuthService {

private static final SecureRandom random = new SecureRandom();

public TokenPair login(UserLoginDto.Request req) {
User user = findByEmailOrThrow(req);
public TokenPair login(UserLoginRequest req) {
User user = findActiveUserByEmail(req.email());

validatePasswordMatch(req.password(), user.getPassword());
log.error("{}", user.getPhone());

return jwtComponent.generateTokenPair(user.getEmail(), user.getId(), user.getRole().name());
}

private User findByEmailOrThrow(UserLoginDto.Request req) {
return userRepository.findByEmail(req.email())
.orElseThrow(() -> new BizException(AuthErrorCode.INVALID_CREDENTIALS));
}

private void validatePasswordMatch(String rawPassword, String encodedPassword) {
if (!passwordEncoder.matches(rawPassword, encodedPassword)) {
throw new BizException(AuthErrorCode.INVALID_CREDENTIALS);
}
}

public void sendEmailVerificationCode(EmailVerificationRequest req) {
final String email = req.email();

Expand All @@ -68,18 +57,6 @@ public void sendEmailVerificationCode(EmailVerificationRequest req) {
eventPublisher.publishEvent(new EmailVerificationSendEvent(email, code));
}

private void validateEmailIsAvailable(String email) {
if (userRepository.existsByEmail(email)) {
throw new BizException(AuthErrorCode.EXISTING_EMAIL);
}
}

private void validateVerificationCodeNotExists(String email) {
if (emailVerificationRedisRepository.existCode(email)) {
throw new BizException(AuthErrorCode.ALREADY_ISSUED_VERIFICATION_CODE);
}
}

public void confirmEmailVerificationCode(EmailVerificationConfirmRequest req) {
String email = req.email();

Expand All @@ -91,17 +68,6 @@ public void confirmEmailVerificationCode(EmailVerificationConfirmRequest req) {
emailVerificationRedisRepository.markAsVerified(email);
}

private String findVerificationCodeOrThrow(String email) {
return emailVerificationRedisRepository.findCode(email)
.orElseThrow(() -> new BizException(AuthErrorCode.NOT_ISSUED_VERIFICATION_CODE));
}

private void validateVerificationCode(String inputCode, String savedCode) {
if (!Objects.equals(inputCode, savedCode)) {
throw new BizException(AuthErrorCode.INVALID_VERIFICATION_CODE);
}
}

public void validateEmail(String email) {
if (!emailVerificationRedisRepository.isVerified(email)) {
throw new BizException(AuthErrorCode.UNVERIFIED_EMAIL);
Expand All @@ -117,4 +83,38 @@ private String generateVerificationCode(int length) {
int bound = (int)Math.pow(10, length);
return String.valueOf(random.nextInt(origin, bound));
}

private User findActiveUserByEmail(String email) {
return userRepository.findByEmailAndStatus(email, UserStatus.ACTIVE)
.orElseThrow(() -> new BizException(AuthErrorCode.INVALID_CREDENTIALS));
}

private void validatePasswordMatch(String rawPassword, String encodedPassword) {
if (!passwordEncoder.matches(rawPassword, encodedPassword)) {
throw new BizException(AuthErrorCode.INVALID_CREDENTIALS);
}
}

private void validateEmailIsAvailable(String email) {
if (userRepository.existsByEmail(email)) {
throw new BizException(AuthErrorCode.EXISTING_EMAIL);
}
}

private void validateVerificationCodeNotExists(String email) {
if (emailVerificationRedisRepository.existCode(email)) {
throw new BizException(AuthErrorCode.ALREADY_ISSUED_VERIFICATION_CODE);
}
}

private String findVerificationCodeOrThrow(String email) {
return emailVerificationRedisRepository.findCode(email)
.orElseThrow(() -> new BizException(AuthErrorCode.NOT_ISSUED_VERIFICATION_CODE));
}

private void validateVerificationCode(String inputCode, String savedCode) {
if (!Objects.equals(inputCode, savedCode)) {
throw new BizException(AuthErrorCode.INVALID_VERIFICATION_CODE);
}
}
}
13 changes: 6 additions & 7 deletions src/main/java/project/flipnote/common/util/CookieUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CookieUtil {

public static void addCookie(
HttpServletResponse response,
public static Cookie createCookie(
String name,
String value,
int maxAge,
Expand All @@ -20,14 +19,14 @@ public static void addCookie(
cookie.setMaxAge(maxAge);
cookie.setHttpOnly(httpOnly);
cookie.setPath(path);
response.addCookie(cookie);
return cookie;
}

public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
addCookie(response, name, value, maxAge, true, "/");
public static Cookie createCookie(String name, String value, int maxAge) {
return createCookie(name, value, maxAge, true, "/");
}

public static void deleteCookie(HttpServletResponse response, String name) {
addCookie(response, name, "", 0, true, "/");
public static Cookie createExpiredCookie(String name) {
return createCookie(name, "", 0, true, "/");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
import org.springframework.data.jpa.repository.JpaRepository;

import project.flipnote.user.entity.User;
import project.flipnote.user.entity.UserStatus;

public interface UserRepository extends JpaRepository<User, Long> {

boolean existsByEmail(String email);

boolean existsByPhone(String phone);

Optional<User> findByEmail(String email);
Optional<User> findByEmailAndStatus(String email, UserStatus status);
}
Loading
Loading