Skip to content

Commit

Permalink
Merge pull request #187 from TRIP-Side-Project/dev
Browse files Browse the repository at this point in the history
네이버 API 버그 및 회원 기능 수정에 의한 Main Merge
  • Loading branch information
don9m1n authored Jan 4, 2024
2 parents cf74281 + 31c88fd commit f171598
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 32 deletions.
3 changes: 3 additions & 0 deletions src/main/java/com/api/trip/common/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public enum ErrorCode {
INVALID_IMAGE_TYPE("유효하지 않은 파일 형식입니다.", HttpStatus.BAD_REQUEST),
AWS_FAIL_UPLOAD("AWS S3 업로드 실패!", HttpStatus.CONFLICT),

NOT_MATCH_EMAIL_TOKEN("이메일 인증 토큰이 일치하지 않습니다.", HttpStatus.CONFLICT),
EXPIRED_EMAIL_TOKEN("이메일 인증 토큰을 찾을 수 없습니다.(expired)", HttpStatus.NOT_FOUND),

// 401
LOGOUTED_TOKEN("이미 로그아웃 처리된 토큰입니다.", HttpStatus.UNAUTHORIZED),
SNATCH_TOKEN("Refresh Token 탈취를 감지하여 로그아웃 처리됩니다.", HttpStatus.UNAUTHORIZED),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
// OAuth2User 객체에서 권한 가져옴
JwtToken jwtToken = jwtTokenProvider.createJwtToken(member.getEmail(), member.getRole().getValue());

String targetUrl = UriComponentsBuilder.fromUriString("https://dkoqktaeu3tic.cloudfront.net/home")
String targetUrl = UriComponentsBuilder.fromUriString("https://triptrip.site/home")
.queryParam("accessToken", jwtToken.getAccessToken())
.queryParam("refreshToken", jwtToken.getRefreshToken())
.queryParam("memberId", String.valueOf(member.getId()))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.api.trip.domain.email.repository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;

import java.time.Duration;
import java.util.Optional;

@Slf4j
@Repository
@RequiredArgsConstructor
public class EmailRedisRepository {

private final StringRedisTemplate redisTemplate;
private final static Duration EMAIL_TOKEN_TTL = Duration.ofMinutes(30);

public void setToken(String email, String token) {
String key = getKey(email);
log.debug("set EmailAuth to Redis {}, {}", key, token);
redisTemplate.opsForValue().set(key, token, EMAIL_TOKEN_TTL);
}

public Optional<String> getToken(String email) {
String key = getKey(email);
String token = redisTemplate.opsForValue().get(key);
log.info("get Data from Redis {}, {}", key, token);
return Optional.ofNullable(token);
}

public void deleteToken(String email) {
String key = getKey(email);
log.info("delete Data from Redis {}", key);
redisTemplate.delete(key);
}

public boolean existToken(String email) {
String key = getKey(email);
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}

private String getKey(String email) {
return "EMAIL:" + email;
}

}
28 changes: 20 additions & 8 deletions src/main/java/com/api/trip/domain/email/service/EmailService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import com.api.trip.common.exception.custom_exception.BadRequestException;
import com.api.trip.common.exception.custom_exception.DuplicateException;
import com.api.trip.common.exception.custom_exception.NotFoundException;
import com.api.trip.common.exception.custom_exception.NotMatchException;
import com.api.trip.domain.email.model.EmailAuth;
import com.api.trip.domain.email.repository.EmailRedisRepository;
import com.api.trip.domain.email.repository.EmailAuthRepository;
import com.api.trip.domain.member.controller.dto.EmailResponse;
import com.api.trip.domain.member.controller.dto.FindPasswordRequest;
import com.api.trip.domain.member.model.Member;
import com.api.trip.domain.member.repository.MemberRepository;
Expand All @@ -24,7 +25,6 @@
import org.thymeleaf.spring6.SpringTemplateEngine;

import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.UUID;

@Service
Expand All @@ -36,14 +36,15 @@ public class EmailService {
private final MemberService memberService;
private final JavaMailSender javaMailSender;
private final EmailAuthRepository emailAuthRepository;
private final EmailRedisRepository emailRedisRepository;
private final MemberRepository memberRepository;
private final SpringTemplateEngine templateEngine;

@Value("${spring.mail.link}")
private String host;

@Async
public void send(String email, String authToken) {
public void send(String email) {

// TODO: 비동기 메서드 예외 핸들러 추가
memberRepository.findByEmail(email).ifPresent(it -> {
Expand All @@ -52,9 +53,17 @@ public void send(String email, String authToken) {

MimeMessage message = javaMailSender.createMimeMessage();

// redis에 해당 이메일에 대한 토큰이 있으면 reids에서 삭제
if (emailRedisRepository.getToken(email).isPresent()) {
emailRedisRepository.deleteToken(email);
}

String token = UUID.randomUUID().toString();
emailRedisRepository.setToken(email, token);

try {
Context context = new Context();
context.setVariable("auth_url", "%s/%s/%s".formatted(host, email, authToken));
context.setVariable("auth_url", "%s/%s/%s".formatted(host, email, token));

String html = templateEngine.process("email_auth_mail", context);

Expand Down Expand Up @@ -102,12 +111,15 @@ public void sendNewPassword(FindPasswordRequest findPasswordRequest) {
}

// 인증 메일 검증
public EmailResponse authEmail(String email, String authToken) {
EmailAuth emailAuth = emailAuthRepository.findValidAuthByEmail(email, authToken, LocalDateTime.now())
public boolean authEmail(String email, String authToken) {
String token = emailRedisRepository.getToken(email)
.orElseThrow(() -> new NotFoundException(ErrorCode.NOT_FOUND_EMAIL_TOKEN));

emailAuth.useToken(); // 토큰 사용 -> 만료
return EmailResponse.of(emailAuth.isExpired());
if (!token.matches(authToken)) {
throw new NotMatchException(ErrorCode.NOT_MATCH_EMAIL_TOKEN);
}

return true;
}

public String createEmailAuth(String email) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.api.trip.domain.interestitem.controller;

import com.api.trip.domain.interestitem.controller.dto.CreateInterestItemRequest;
import com.api.trip.domain.interestitem.controller.dto.GetInterestItemsResponse;
import com.api.trip.domain.interestitem.service.InterestItemService;
import com.api.trip.domain.item.controller.dto.CreateItemRequest;
import com.api.trip.domain.item.controller.dto.GetItemResponse;
Expand Down Expand Up @@ -28,8 +29,8 @@ public ResponseEntity<Void> createInterestItem(@RequestBody CreateInterestItemRe
}

@GetMapping
public ResponseEntity<GetItemsResponse> getInterestItems(
@PageableDefault(size = 8) Pageable pageable
public ResponseEntity<GetInterestItemsResponse> getInterestItems(
@PageableDefault(size = 6) Pageable pageable
) {
return ResponseEntity.ok(interestItemService.getInterestItems(pageable));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.api.trip.domain.interestitem.controller.dto;

import com.api.trip.domain.item.controller.dto.GetItemResponse;
import com.api.trip.domain.item.model.Item;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class GetInterestItemResponse {

private Long id;

private String title;

private String shopName;

private Integer minPrice;

private String imageUrl;

public static GetInterestItemResponse of(Item item){
return GetInterestItemResponse.builder()
.id(item.getId())
.title(item.getTitle())
.shopName(item.getShopName())
.minPrice(item.getMinPrice())
.imageUrl(item.getImageUrl())
.build();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.api.trip.domain.interestitem.model.InterestItem;
import com.api.trip.domain.item.controller.dto.GetItemResponse;
import com.api.trip.domain.item.controller.dto.GetItemsResponse;
import com.api.trip.domain.item.model.Item;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.domain.Page;
Expand All @@ -15,11 +14,8 @@
@Builder
public class GetInterestItemsResponse {

private long totalCount;
private int currentPage;
private int totalPage;
private Pagination pagination;
private List<GetItemResponse> itemList;
private List<GetInterestItemResponse> itemList;

@Getter
@Builder
Expand All @@ -33,7 +29,7 @@ private static class Pagination {
private int requestSize;
private int articleSize;

private static Pagination of(Page<Item> page) {
private static Pagination of(Page<InterestItem> page) {
return builder()
.totalPages(page.getTotalPages())
.totalElements(page.getTotalElements())
Expand All @@ -47,11 +43,11 @@ private static Pagination of(Page<Item> page) {
}


public static GetItemsResponse of(Page<InterestItem> page)
public static GetInterestItemsResponse of(Page<InterestItem> page)
{
return GetItemsResponse.builder()

.itemList(page.getContent().stream().map(InterestItem::getItem).map(GetItemResponse::of).collect(Collectors.toList()))
return GetInterestItemsResponse.builder()
.pagination(Pagination.of(page))
.itemList(page.getContent().stream().map(InterestItem::getItem).map(GetInterestItemResponse::of).collect(Collectors.toList()))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void createInterestItem(CreateInterestItemRequest itemRequest) {
}

@Transactional(readOnly = true)
public GetItemsResponse getInterestItems(Pageable pageable) {
public GetInterestItemsResponse getInterestItems(Pageable pageable) {
Member member = memberService.getAuthenticationMember();
Page<InterestItem> page = interestItemRepository.findByMember(member, pageable);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,20 @@ public ResponseEntity<MyPageResponse> myPage() {
@Operation(summary = "인증 메일 전송", description = "인증 링크를 포함한 메일을 발송한다.")
@PostMapping("/send-email/{email}")
public void sendAuthEmail(@PathVariable String email) {
emailService.send(email, emailService.createEmailAuth(email));
emailService.send(email);
}

// 이메일 인증
@Operation(summary = "이메일 인증", description = "인증 메일이 유효한지 검사하고 인증을 처리한다.")
@GetMapping("/auth-email/{email}/{authToken}")
public ResponseEntity<EmailResponse> emailAndAuthToken(@PathVariable String email, @PathVariable String authToken) {
EmailResponse emailResponse = emailService.authEmail(email, authToken);
boolean isAuth = emailService.authEmail(email, authToken);

HttpHeaders headers = new HttpHeaders();
headers.add("message", emailResponse.getMessage());
headers.add("auth-email", String.valueOf(emailResponse.isAuthEmail()));
headers.add("message", "success email auth!");
headers.add("auth-email", String.valueOf(isAuth));

return ResponseEntity.ok().headers(headers).body(emailResponse);
return ResponseEntity.ok().headers(headers).body(EmailResponse.of(isAuth));
}

@Operation(summary = "비밀번호 찾기", description = "임시 비밀번호를 발급한다.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
@Builder
public class EmailResponse {

private String status;
private String message;
private boolean authEmail;

public static EmailResponse of(boolean authEmail) {
return EmailResponse.builder()
.status("https://http.cat/200")
.message("success email auth!")
.authEmail(authEmail)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import com.api.trip.domain.aws.util.MultipartFileUtils;
import com.api.trip.domain.aws.service.AmazonS3Service;
import com.api.trip.domain.comment.repository.CommentRepository;
import com.api.trip.domain.email.repository.EmailAuthRepository;
import com.api.trip.domain.email.repository.EmailRedisRepository;
import com.api.trip.domain.interestitem.repository.InterestItemRepository;
import com.api.trip.domain.interesttag.service.InterestTagService;
import com.api.trip.domain.member.controller.dto.*;
Expand Down Expand Up @@ -52,7 +52,7 @@ public class MemberService {

private final PasswordEncoder passwordEncoder;
private final MemberRepository memberRepository;
private final EmailAuthRepository emailAuthRepository;
private final EmailRedisRepository emailRedisRepository;
private final ArticleRepository articleRepository;
private final CommentRepository commentRepository;
private final InterestItemRepository interestItemRepository;
Expand All @@ -79,9 +79,9 @@ public void join(JoinRequest joinRequest) throws IOException {
throw new DuplicateException(ErrorCode.ALREADY_JOINED);
});

// 이메일 인증이 완료 여부 확인
emailAuthRepository.findTop1ByEmailAndExpiredIsTrueOrderByCreatedAtDesc(joinRequest.getEmail())
.orElseThrow(() -> new NotFoundException(ErrorCode.NOT_FOUND_EMAIL_TOKEN));
if (!emailRedisRepository.existToken(joinRequest.getEmail())) {
throw new NotFoundException(ErrorCode.EXPIRED_EMAIL_TOKEN);
}

MultipartFile profileImg = joinRequest.getProfileImg();

Expand All @@ -107,6 +107,7 @@ public void join(JoinRequest joinRequest) throws IOException {
);

memberRepository.save(member);
emailRedisRepository.deleteToken(joinRequest.getEmail());
}

// 로그인
Expand Down

0 comments on commit f171598

Please sign in to comment.