Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature/user-signup] 유저 기획 변경에 따른 API 재작업 #59

Merged
merged 5 commits into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public enum ExceptionList {
USER_NOT_FOUND("U0001", HttpStatus.NOT_FOUND, "등록된 유저가 아닙니다."),
USER_AUTH_TIME_OUT("U0002", HttpStatus.FORBIDDEN, "인증 시간이 만료되었습니다."),
USER_AUTH_FAIL("U0003", HttpStatus.FORBIDDEN, "인증에 실패하였습니다."),
USER_ALREADY_EXIST("U0004", HttpStatus.CONFLICT, "이미 등록된 이메일입니다."),
NICKNAME_ALREADY_EXIST("U0005", HttpStatus.CONFLICT, "이미 등록된 닉네임입니다."),

TOKEN_NOT_VALID("T0001", HttpStatus.NOT_ACCEPTABLE, "해당 토큰은 유효하지 않습니다."),
TOKEN_EXPIRATION("T0002", HttpStatus.FORBIDDEN, "토큰이 만료되었습니다."),
Expand Down
11 changes: 9 additions & 2 deletions src/main/java/everymeal/server/global/util/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@ public class JwtUtil {
@Value("${jwt.validity.refresh-seconds}")
private Long refreshTokenExpirationMs;

public String generateEmailToken(Long userId, String email, String sendAuthPassword) {
public String generateEmailToken(String email, String sendAuthPassword) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + (accessTokenExpirationMs * 2));
Map<String, Object> claims = new HashMap<>();
claims.put("CLAIM_KEY_IDX", userId);
claims.put("CLAIM_KEY_EMAIL", email);
claims.put("CLAIM_KEY_SEND_AUTH_PASSWORD", sendAuthPassword);
return Jwts.builder()
Expand Down Expand Up @@ -95,6 +94,14 @@ public String getEmailTokenFromAuthCode(String token) {
return null;
}

public String getEmailTokenFromEmail(String token) {
Claims claims = getClaimsFromToken(tokenSubBearer(token), accessSecretKey);
if (claims != null) {
return claims.get("CLAIM_KEY_EMAIL").toString();
}
return null;
}

private Claims getClaimsFromToken(String token, String secretKey) {
try {
Key key = Keys.hmacShaKeyFor(secretKey.getBytes());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ public class University extends BaseEntity {
private List<Store> stores = new ArrayList<>();

@Builder
public University(Long idx, String name, String campusName, Boolean isDeleted) {
this.idx = idx;
public University(String name, String campusName) {
this.name = name;
this.campusName = campusName;
this.isDeleted = Boolean.FALSE;
Expand Down
129 changes: 70 additions & 59 deletions src/main/java/everymeal/server/user/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,17 @@


import everymeal.server.global.dto.response.ApplicationResponse;
import everymeal.server.global.util.authresolver.Auth;
import everymeal.server.global.util.authresolver.AuthUser;
import everymeal.server.global.util.authresolver.entity.AuthenticatedUser;
import everymeal.server.user.controller.dto.request.UserEmailAuthReq;
import everymeal.server.user.controller.dto.request.UserEmailAuthVerifyReq;
import everymeal.server.user.controller.dto.request.UserSingReq;
import everymeal.server.user.controller.dto.request.UserEmailLoginReq;
import everymeal.server.user.controller.dto.request.UserEmailSingReq;
import everymeal.server.user.controller.dto.response.UserEmailAuthRes;
import everymeal.server.user.controller.dto.response.UserLoginRes;
import everymeal.server.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseCookie;
Expand All @@ -26,6 +21,7 @@
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/api/v1/users")
Expand All @@ -36,86 +32,101 @@ public class UserController {

private final UserService userService;

@Operation(summary = "회원가입")
@PostMapping
public ApplicationResponse<Boolean> signUp(@RequestBody UserSingReq request) {
return ApplicationResponse.ok(userService.signUp(request));
}

@Operation(
summary = "로그인",
description = "로그인을 진행합니다. <br> 로그인 성공 시, refresh-token을 쿠키로 반환합니다.")
summary = "회원가입",
description =
"""
회원가입을 진행합니다. <br>
회원가입 성공 시, refresh-token을 쿠키로 반환합니다.
""")
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "로그인 성공",
description = "회원가입 성공",
content = @Content(schema = @Schema(implementation = UserLoginRes.class))),
@ApiResponse(
responseCode = "404",
description = "(U0001)유저를 찾을 수 없습니다.",
content = @Content(schema = @Schema()))
responseCode = "403",
description =
"""
(U0002)인증 시간이 만료되었습니다.<br>
(U0003)인증에 실패하였습니다.<br>
""",
content = @Content(schema = @Schema())),
@ApiResponse(
responseCode = "409",
description =
"""
(U0004)이미 가입된 유저입니다.<br>
(U0005)이미 등록된 닉네임입니다.<br>
""",
content = @Content(schema = @Schema())),
})
@PostMapping("/login")
public ResponseEntity<ApplicationResponse<UserLoginRes>> login(
@RequestBody UserSingReq request) {
UserLoginRes response = userService.login(request);
ResponseCookie cookie =
ResponseCookie.from("refresh-token", response.getRefreshToken())
.httpOnly(true)
.sameSite("None")
.path("/")
.maxAge(60 * 60 * 24 * 30L)
.secure(true)
.build();
response.setRefreshToken(null);
return ResponseEntity.ok()
.header("Set-Cookie", cookie.toString())
.body(ApplicationResponse.ok(response));
@PostMapping("/signup")
public ResponseEntity<ApplicationResponse<UserLoginRes>> signUp(
@RequestBody UserEmailSingReq request) {
UserLoginRes response = userService.signUp(request);
return setRefreshToken(response);
}

@Auth(require = true)
@GetMapping("/auth")
@Operation(
summary = "유저 이메일 인증 여부",
description = "유저가 인증되었는지 여부를 반환합니다. <br> 인증되었다면 true, 아니라면 false를 반환합니다.")
summary = "로그인",
description = "로그인을 진행합니다. <br> 로그인 성공 시, refresh-token을 쿠키로 반환합니다.")
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "로그인 성공",
content = @Content(schema = @Schema(implementation = Boolean.class))),
content = @Content(schema = @Schema(implementation = UserLoginRes.class))),
@ApiResponse(
responseCode = "404",
description = "(U0001)유저를 찾을 수 없습니다.",
content = @Content(schema = @Schema()))
})
@SecurityRequirement(name = "bearerAuth")
public ApplicationResponse<Boolean> isAuth(
@AuthUser @Parameter(hidden = true) AuthenticatedUser authenticatedUser) {
return ApplicationResponse.ok(userService.isAuth(authenticatedUser));
@PostMapping("/login")
public ResponseEntity<ApplicationResponse<UserLoginRes>> login(
@RequestBody UserEmailLoginReq request) {
UserLoginRes response = userService.login(request);
return setRefreshToken(response);
}

@Auth(require = true)
@SecurityRequirement(name = "bearerAuth")
@PostMapping("/email/auth")
@PostMapping("/send/email")
@Operation(
summary = "이메일 인증",
summary = "이메일 전송",
description =
"이메일 인증을 진행합니다. <br> Response에 5분 동안 유효한 JWT 토큰이 담기는데 해당 토큰에는 발송 값이 들어있습니다.")
public ApplicationResponse<UserEmailAuthRes> emailAuth(
@RequestBody UserEmailAuthReq request,
@AuthUser @Parameter(hidden = true) AuthenticatedUser authenticatedUser) {
return ApplicationResponse.ok(userService.emailAuth(request, authenticatedUser));
public ApplicationResponse<UserEmailAuthRes> emailAuth(@RequestBody UserEmailAuthReq request) {
return ApplicationResponse.ok(userService.emailAuth(request));
}

@Auth(require = true)
@SecurityRequirement(name = "bearerAuth")
@PostMapping("/email/auth/verify")
@PostMapping("/email/verify")
dldmsql marked this conversation as resolved.
Show resolved Hide resolved
@Operation(
summary = "이메일 인증 확인",
description = "이메일 인증을 확인합니다. <br> Request에는 이메일 인증 시 발송된 값이 담겨야 합니다.")
public ApplicationResponse<Boolean> verifyEmailAuth(
@RequestBody UserEmailAuthVerifyReq request,
@AuthUser @Parameter(hidden = true) AuthenticatedUser authenticatedUser) {
return ApplicationResponse.ok(userService.verifyEmailAuth(request, authenticatedUser));
public ApplicationResponse<Boolean> verifyEmailAuth(@RequestBody UserEmailLoginReq request) {
return ApplicationResponse.ok(userService.verifyEmailAuth(request));
}

@GetMapping("/email/check")
dldmsql marked this conversation as resolved.
Show resolved Hide resolved
@Operation(
summary = "이미 가입된 유저인지 확인",
description = "이미 가입된 유저인지 확인합니다. <br> 가입된 유저 true, 가입되지 않은 유저 false")
public ApplicationResponse<Boolean> checkUser(
@Schema(description = "이메일", example = "test@gmail.com") @RequestParam String email) {
return ApplicationResponse.ok(userService.checkUser(email));
}

private ResponseEntity<ApplicationResponse<UserLoginRes>> setRefreshToken(
UserLoginRes response) {
ResponseCookie cookie =
ResponseCookie.from("refresh-token", response.refreshToken())
.httpOnly(true)
.sameSite("None")
.path("/")
.maxAge(60 * 60 * 24 * 30L)
.secure(true)
.build();
response.withoutRefreshToken();
return ResponseEntity.ok()
.header("Set-Cookie", cookie.toString())
.body(ApplicationResponse.ok(response));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package everymeal.server.user.controller.dto.request;


import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;

public record UserEmailLoginReq(
@Schema(
description = "이메일 인증 API에서 반환된 토큰 값",
defaultValue = "eyBdvsjfgsdkgb",
example = "eyBdvsjfgsdkgb")
@NotBlank(message = "이메일 인증 토큰을 입력해주세요.")
String emailAuthToken,
@Schema(description = "이메일 유저가 입력한 인증 값", defaultValue = "123456", example = "123456")
@NotBlank(message = "인증번호를 입력해주세요.")
String emailAuthValue) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package everymeal.server.user.controller.dto.request;


import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;

public record UserEmailSingReq(
@Pattern(regexp = "^[^\\\\s]{1,10}$", message = "닉네임은 1~10자리로 입력해주세요.")
@Schema(description = "이메일", example = "test@naver.com")
String nickname,
@Schema(
description = "이메일 인증 API에서 반환된 토큰 값",
defaultValue = "eyBdvsjfgsdkgb",
example = "eyBdvsjfgsdkgb")
@NotBlank(message = "이메일 인증 토큰을 입력해주세요.")
String emailAuthToken,
@Schema(description = "이메일 유저가 입력한 인증 값", defaultValue = "123456", example = "123456")
@NotBlank(message = "인증번호를 입력해주세요.")
String emailAuthValue,
@Schema(description = "선택 대학교 IDX", example = "1", defaultValue = "1")
@NotBlank(message = "대학교를 선택해주세요.")
Long universityIdx,
@Schema(
description = "프로필 이미지 URL",
example =
"https://everymeal.s3.ap-northeast-2.amazonaws.com/1627667445.png")
@NotBlank(message = "프로필 이미지를 선택해주세요.")
String profileImgKey) {}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Builder
public class UserLoginRes {

private String accessToken;

@JsonInclude(Include.NON_NULL)
@Schema(hidden = true)
private String refreshToken;
public record UserLoginRes(
String accessToken,
String nickname,
String profileImg,
@JsonInclude(Include.NON_NULL) @Schema(hidden = true) String refreshToken) {
public UserLoginRes withoutRefreshToken() {
return new UserLoginRes(accessToken, nickname, profileImg, null);
}
}
17 changes: 13 additions & 4 deletions src/main/java/everymeal/server/user/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import everymeal.server.global.entity.BaseEntity;
import everymeal.server.university.entity.University;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
Expand All @@ -23,21 +24,29 @@ public class User extends BaseEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idx;

private String deviceId;

@Column(unique = true)
dldmsql marked this conversation as resolved.
Show resolved Hide resolved
private String nickName;

@Column(unique = true)
dldmsql marked this conversation as resolved.
Show resolved Hide resolved
private String email;
dldmsql marked this conversation as resolved.
Show resolved Hide resolved

private Boolean isDeleted;

private String profileImgUrl;

@ManyToOne private University university;

@Builder
public User(String deviceId, String nickName, String email, University university) {
this.deviceId = deviceId;
public User(
String nickName,
String email,
Boolean isDeleted,
String profileImgUrl,
University university) {
this.nickName = nickName;
this.email = email;
this.isDeleted = Boolean.TRUE;
dldmsql marked this conversation as resolved.
Show resolved Hide resolved
this.profileImgUrl = profileImgUrl;
this.university = university;
}

Expand Down
Loading
Loading