Skip to content

Commit

Permalink
Merge pull request #11 from Domitory-CheckMate/feature/10-member
Browse files Browse the repository at this point in the history
[feat] 로그인 및 회원가입 기능
  • Loading branch information
ziiyouth authored Jan 1, 2024
2 parents f104f04 + 7181756 commit e284a18
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

import lombok.RequiredArgsConstructor;
import org.gachon.checkmate.domain.member.dto.request.EmailPostRequestDto;
import org.gachon.checkmate.domain.member.dto.request.MemberSignInRequestDto;
import org.gachon.checkmate.domain.member.dto.request.MemberSignUpRequestDto;
import org.gachon.checkmate.domain.member.dto.request.PasswordResetRequestDto;
import org.gachon.checkmate.domain.member.dto.response.EmailResponseDto;
import org.gachon.checkmate.domain.member.dto.response.MemberSignInResponseDto;
import org.gachon.checkmate.domain.member.dto.response.MemberSignUpResponseDto;
import org.gachon.checkmate.domain.member.service.MemberService;
import org.gachon.checkmate.global.common.SuccessResponse;
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 org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RequestMapping("/api/member")
Expand All @@ -23,4 +25,22 @@ public ResponseEntity<SuccessResponse<?>> sendMail(@RequestBody final EmailPostR
final EmailResponseDto emailResponseDto = memberService.sendMail(emailPostRequestDto);
return SuccessResponse.ok(emailResponseDto);
}

@PostMapping("/signup")
public ResponseEntity<SuccessResponse<?>> signUp(@RequestBody final MemberSignUpRequestDto memberSignUpRequestDto){
final MemberSignUpResponseDto memberSignUpResponseDto = memberService.signUp(memberSignUpRequestDto);
return SuccessResponse.created(memberSignUpResponseDto);
}

@PostMapping("/signin")
public ResponseEntity<SuccessResponse<?>> signIn(@RequestBody final MemberSignInRequestDto memberSignInRequestDto){
final MemberSignInResponseDto memberSignInResponseDto = memberService.signIn(memberSignInRequestDto);
return SuccessResponse.ok(memberSignInResponseDto);
}

@PatchMapping("/reset")
public ResponseEntity<SuccessResponse<?>> setPassword(@RequestBody final PasswordResetRequestDto passwordResetRequestDto){
memberService.setPassword(passwordResetRequestDto);
return SuccessResponse.ok(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.gachon.checkmate.domain.member.dto.request;

public record MemberSignInRequestDto(
String email,
String password
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.gachon.checkmate.domain.member.dto.request;

import org.gachon.checkmate.domain.member.entity.GenderType;
import org.gachon.checkmate.domain.member.entity.MbtiType;

public record MemberSignUpRequestDto(
String email,
String password,
String name,
String school,
String major,
MbtiType mbtiType,
GenderType genderType
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.gachon.checkmate.domain.member.dto.request;

public record PasswordResetRequestDto(
String email,
String newPassword
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.gachon.checkmate.domain.member.dto.response;

import lombok.Builder;

@Builder
public record MemberSignInResponseDto(
Long memberId,
String accessToken,
String refreshToken
) {
public static MemberSignInResponseDto of(Long memberId,
String accessToken,
String refreshToken) {
return MemberSignInResponseDto.builder()
.memberId(memberId)
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.gachon.checkmate.domain.member.dto.response;

import lombok.Builder;

@Builder
public record MemberSignUpResponseDto(
Long memberId,
String name,
String accessToken,
String refreshToken
) {
public static MemberSignUpResponseDto of(Long memberId,
String name,
String accessToken,
String refreshToken) {
return MemberSignUpResponseDto.builder()
.memberId(memberId)
.name(name)
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.gachon.checkmate.domain.member.entity;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public enum ProfileImageType {

PROFILE_1("https://checkmate-dormitory-bucket.s3.ap-northeast-2.amazonaws.com/checkmate-profile1.png"),
PROFILE_2("https://example.com/profile2.jpg"),
PROFILE_3("https://example.com/profile3.jpg");

private final String imageUrl;
}
21 changes: 18 additions & 3 deletions src/main/java/org/gachon/checkmate/domain/member/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import org.gachon.checkmate.domain.checkList.entity.CheckList;
import org.gachon.checkmate.domain.member.converter.GenderTypeConverter;
import org.gachon.checkmate.domain.member.converter.MbtiTypeConverter;
import org.gachon.checkmate.domain.member.converter.RoomTypeConverter;
import org.gachon.checkmate.domain.post.converter.RoomTypeConverter;
import org.gachon.checkmate.domain.post.entity.Post;
import org.gachon.checkmate.domain.scrap.entity.Scrap;
import org.gachon.checkmate.global.common.BaseTimeEntity;
Expand All @@ -30,8 +30,6 @@ public class User extends BaseTimeEntity {
private String profile;
private String school;
private String major;
@Convert(converter = RoomTypeConverter.class)
private RoomType roomType;
@Convert(converter = MbtiTypeConverter.class)
private MbtiType mbtiType;
@Convert(converter = GenderTypeConverter.class)
Expand All @@ -44,4 +42,21 @@ public class User extends BaseTimeEntity {
@OneToMany(mappedBy = "user")
@Builder.Default
private List<Scrap> scrapList = new ArrayList<>();

public static User createUser(String email, String storedPassword, String name, String school, String major, MbtiType mbti, GenderType gender){
return User.builder()
.email(email)
.password(storedPassword)
.name(name)
.profile(ProfileImageType.PROFILE_1.getImageUrl())
.school(school)
.major(major)
.mbtiType(mbti)
.gender(gender)
.build();
}

public void setPassword(String newPassword) {
this.password = newPassword;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.gachon.checkmate.domain.member.repository;

import org.gachon.checkmate.domain.member.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {

Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,25 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.gachon.checkmate.domain.member.dto.request.EmailPostRequestDto;
import org.gachon.checkmate.domain.member.dto.request.MemberSignInRequestDto;
import org.gachon.checkmate.domain.member.dto.request.MemberSignUpRequestDto;
import org.gachon.checkmate.domain.member.dto.request.PasswordResetRequestDto;
import org.gachon.checkmate.domain.member.dto.response.EmailResponseDto;
import org.gachon.checkmate.domain.member.dto.response.MemberSignInResponseDto;
import org.gachon.checkmate.domain.member.dto.response.MemberSignUpResponseDto;
import org.gachon.checkmate.domain.member.entity.User;
import org.gachon.checkmate.domain.member.repository.UserRepository;
import org.gachon.checkmate.global.config.auth.jwt.JwtProvider;
import org.gachon.checkmate.global.config.mail.MailProvider;
import org.gachon.checkmate.global.error.exception.ConflictException;
import org.gachon.checkmate.global.error.exception.EntityNotFoundException;
import org.gachon.checkmate.global.error.exception.UnauthorizedException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static org.gachon.checkmate.global.error.ErrorCode.*;

@Slf4j
@RequiredArgsConstructor
@Transactional
Expand All @@ -17,10 +30,80 @@ public class MemberService {

private final JwtProvider jwtProvider;
private final MailProvider mailProvider;
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;

public EmailResponseDto sendMail(EmailPostRequestDto emailPostRequestDto) {
checkDuplicateEmail(emailPostRequestDto.email());
String authNum = mailProvider.sendMail(emailPostRequestDto.email(), "email");
return new EmailResponseDto(authNum);
}

public MemberSignUpResponseDto signUp(MemberSignUpRequestDto memberSignUpRequestDto) {
Long newMemberId = createMember(memberSignUpRequestDto);
String accessToken = issueNewAccessToken(newMemberId);
String refreshToken = issueNewRefreshToken(newMemberId);
return MemberSignUpResponseDto.of(newMemberId, memberSignUpRequestDto.name(), accessToken, refreshToken);
}

public MemberSignInResponseDto signIn(MemberSignInRequestDto memberSignInRequestDto) {
User user = getUserFromEmail(memberSignInRequestDto.email());
validatePassword(memberSignInRequestDto.password(), user.getPassword());
String accessToken = issueNewAccessToken(user.getId());
String refreshToken = issueNewRefreshToken(user.getId());
return MemberSignInResponseDto.of(user.getId(), accessToken, refreshToken);
}

public void setPassword(PasswordResetRequestDto passwordResetRequestDto){
User user = getUserFromEmail(passwordResetRequestDto.email());
user.setPassword(encodedPassword(passwordResetRequestDto.newPassword()));
}

private void validatePassword(String enteredPassword, String storedPassword) {
if (!authenticatePassword(enteredPassword, storedPassword)) {
throw new UnauthorizedException(INVALID_PASSWORD);
}
}

private void checkDuplicateEmail(String email) {
if (userRepository.existsByEmail(email)) {
throw new ConflictException(DUPLICATE_EMAIL);
}
}

private String issueNewAccessToken(Long memberId) {
return jwtProvider.getIssueToken(memberId, true);
}

private String issueNewRefreshToken(Long memberId) {
return jwtProvider.getIssueToken(memberId, false);
}

private Long createMember(MemberSignUpRequestDto memberSignUpRequestDto) {
User newUser = User.createUser(
memberSignUpRequestDto.email(),
encodedPassword(memberSignUpRequestDto.password()),
memberSignUpRequestDto.name(),
memberSignUpRequestDto.school(),
memberSignUpRequestDto.major(),
memberSignUpRequestDto.mbtiType(),
memberSignUpRequestDto.genderType()
);
return userRepository.save(newUser).getId();
}

private String encodedPassword(String rawPassword) {
return passwordEncoder.encode(rawPassword);
}

private boolean authenticatePassword(String enteredPassword, String storedPassword) {
return passwordEncoder.matches(enteredPassword, storedPassword);
}

private User getUserFromEmail(String email) {
return userRepository.findByEmail(email)
.orElseThrow(() -> new EntityNotFoundException(USER_NOT_FOUND));
}


}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.gachon.checkmate.domain.member.converter;
package org.gachon.checkmate.domain.post.converter;

import jakarta.persistence.Converter;
import org.gachon.checkmate.domain.member.entity.RoomType;
import org.gachon.checkmate.domain.post.entity.RoomType;
import org.gachon.checkmate.global.utils.AbstractEnumCodeAttributeConverter;

@Converter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import jakarta.persistence.*;
import lombok.*;
import org.gachon.checkmate.domain.checkList.entity.PostCheckList;
import org.gachon.checkmate.domain.member.converter.RoomTypeConverter;
import org.gachon.checkmate.domain.member.entity.RoomType;
import org.gachon.checkmate.domain.post.converter.RoomTypeConverter;
import org.gachon.checkmate.domain.member.entity.User;
import org.gachon.checkmate.domain.post.converter.ImportantKeyTypeConverter;
import org.gachon.checkmate.domain.post.converter.SimilarityKeyTypeConverter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.gachon.checkmate.domain.member.entity;
package org.gachon.checkmate.domain.post.entity;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

Expand All @@ -23,7 +25,7 @@ public class SecurityConfig {
private final CorsConfig corsConfig;
private final JwtProvider jwtProvider;

private static final String[] whiteList = {"/", "api/member/email"};
private static final String[] whiteList = {"/", "api/member/email", "api/member/signup", "api/member/signin"};

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
Expand All @@ -47,4 +49,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.addFilterBefore(new ExceptionHandlerFilter(), JwtAuthenticationFilter.class)
.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public enum ErrorCode {
INVALID_REFRESH_TOKEN_VALUE(HttpStatus.UNAUTHORIZED, "리프레시 토큰의 값이 올바르지 않습니다."),
EXPIRED_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "리프레시 토큰이 만료되었습니다. 다시 로그인해 주세요."),
NOT_MATCH_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "일치하지 않는 리프레시 토큰입니다."),
INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다."),

/**
* 403 Forbidden
Expand All @@ -36,6 +37,7 @@ public enum ErrorCode {
*/
ENTITY_NOT_FOUND(HttpStatus.NOT_FOUND, "엔티티를 찾을 수 없습니다."),
CHECK_LIST_NOT_FOUND(HttpStatus.NOT_FOUND, "체크리스트를 찾을 수 없습니다."),
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당하는 유저를 찾을 수 없습니다."),

/**
* 405 Method Not Allowed
Expand All @@ -46,6 +48,7 @@ public enum ErrorCode {
* 409 Conflict
*/
CONFLICT(HttpStatus.CONFLICT, "이미 존재하는 리소스입니다."),
DUPLICATE_EMAIL(HttpStatus.CONFLICT, "이미 가입된 이메일입니다."),

/**
* 500 Internal Server Error
Expand Down

0 comments on commit e284a18

Please sign in to comment.