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
95 changes: 46 additions & 49 deletions src/main/java/inha/gdgoc/domain/auth/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
package inha.gdgoc.domain.auth.controller;

import static inha.gdgoc.domain.auth.controller.message.AuthMessage.ACCESS_TOKEN_REFRESH_SUCCESS;
import static inha.gdgoc.domain.auth.controller.message.AuthMessage.CODE_CREATION_SUCCESS;
import static inha.gdgoc.domain.auth.controller.message.AuthMessage.LOGIN_WITH_PASSWORD_SUCCESS;
import static inha.gdgoc.domain.auth.controller.message.AuthMessage.LOGOUT_SUCCESS;
import static inha.gdgoc.domain.auth.controller.message.AuthMessage.OAUTH_LOGIN_SIGNUP_SUCCESS;
import static inha.gdgoc.domain.auth.controller.message.AuthMessage.PASSWORD_CHANGE_SUCCESS;
import static inha.gdgoc.domain.auth.controller.message.AuthMessage.PASSWORD_RESET_VERIFICATION_SUCCESS;
import static inha.gdgoc.domain.auth.exception.AuthErrorCode.UNAUTHORIZED_USER;
import static inha.gdgoc.domain.auth.exception.AuthErrorCode.USER_NOT_FOUND;

import inha.gdgoc.domain.auth.dto.request.CodeVerificationRequest;
import inha.gdgoc.domain.auth.dto.request.PasswordResetRequest;
import inha.gdgoc.domain.auth.dto.request.SendingCodeRequest;
Expand All @@ -24,29 +14,31 @@
import inha.gdgoc.domain.auth.service.MailService;
import inha.gdgoc.domain.auth.service.RefreshTokenService;
import inha.gdgoc.domain.user.entity.User;
import inha.gdgoc.domain.user.enums.UserRole;
import inha.gdgoc.domain.user.repository.UserRepository;
import inha.gdgoc.global.config.jwt.TokenProvider;
import inha.gdgoc.global.dto.response.ApiResponse;
import inha.gdgoc.global.exception.GlobalErrorCode;
import jakarta.servlet.http.HttpServletResponse;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Optional;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
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;
import org.springframework.web.bind.annotation.*;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Optional;

import static inha.gdgoc.domain.auth.controller.message.AuthMessage.*;
import static inha.gdgoc.domain.auth.exception.AuthErrorCode.UNAUTHORIZED_USER;
import static inha.gdgoc.domain.auth.exception.AuthErrorCode.USER_NOT_FOUND;

@Slf4j
@RequestMapping("/api/v1/auth")
Expand All @@ -61,19 +53,14 @@ public class AuthController {
private final AuthCodeService authCodeService;

@GetMapping("/oauth2/google/callback")
public ResponseEntity<ApiResponse<Map<String, Object>, Void>> handleGoogleCallback(
@RequestParam String code,
HttpServletResponse response
) {
public ResponseEntity<ApiResponse<Map<String, Object>, Void>> handleGoogleCallback(@RequestParam String code, HttpServletResponse response) {
Map<String, Object> data = authService.processOAuthLogin(code, response);

return ResponseEntity.ok(ApiResponse.ok(OAUTH_LOGIN_SIGNUP_SUCCESS, data));
}

@PostMapping("/refresh")
public ResponseEntity<?> refreshAccessToken(
@CookieValue(value = "refresh_token", required = false) String refreshToken
) {
public ResponseEntity<?> refreshAccessToken(@CookieValue(value = "refresh_token", required = false) String refreshToken) {
log.info("리프레시 토큰 요청 받음. 토큰 존재 여부: {}", refreshToken != null);

if (refreshToken == null) {
Expand All @@ -84,19 +71,15 @@ public ResponseEntity<?> refreshAccessToken(
String newAccessToken = refreshTokenService.refreshAccessToken(refreshToken);
AccessTokenResponse accessTokenResponse = new AccessTokenResponse(newAccessToken);

return ResponseEntity.ok(
ApiResponse.ok(ACCESS_TOKEN_REFRESH_SUCCESS, accessTokenResponse, null));
return ResponseEntity.ok(ApiResponse.ok(ACCESS_TOKEN_REFRESH_SUCCESS, accessTokenResponse, null));
} catch (Exception e) {
log.error("리프레시 토큰 처리 중 오류: {}", e.getMessage(), e);
throw new AuthException(AuthErrorCode.INVALID_REFRESH_TOKEN);
}
}

@PostMapping("/login")
public ResponseEntity<ApiResponse<LoginResponse, Void>> login(
@Valid @RequestBody UserLoginRequest req,
HttpServletResponse response
) throws NoSuchAlgorithmException, InvalidKeyException {
public ResponseEntity<ApiResponse<LoginResponse, Void>> login(@Valid @RequestBody UserLoginRequest req, HttpServletResponse response) throws NoSuchAlgorithmException, InvalidKeyException {
String email = req.email().trim();
LoginResponse loginResponse = authService.loginWithPassword(email, req.password(), response);
return ResponseEntity.ok(ApiResponse.ok(LOGIN_WITH_PASSWORD_SUCCESS, loginResponse));
Expand All @@ -109,9 +92,7 @@ public ResponseEntity<ApiResponse<Void, Void>> logout() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

// 1) 익명 방어
if (authentication == null
|| !authentication.isAuthenticated()
|| "anonymousUser".equals(authentication.getName())) {
if (authentication == null || !authentication.isAuthenticated() || "anonymousUser".equals(authentication.getName())) {
throw new AuthException(UNAUTHORIZED_USER);
}

Expand Down Expand Up @@ -142,12 +123,9 @@ public ResponseEntity<ApiResponse<Void, Void>> logout() {
}

@PostMapping("/password-reset/request")
public ResponseEntity<ApiResponse<Void, Void>> responseResponseEntity(
@RequestBody SendingCodeRequest sendingCodeRequest
) {
public ResponseEntity<ApiResponse<Void, Void>> responseResponseEntity(@RequestBody SendingCodeRequest sendingCodeRequest) {
// TODO 서비스로 넘기기
if (userRepository.existsByNameAndEmail(sendingCodeRequest.name(),
sendingCodeRequest.email())) {
if (userRepository.existsByNameAndEmail(sendingCodeRequest.name(), sendingCodeRequest.email())) {
String code = mailService.sendAuthCode(sendingCodeRequest.email());
authCodeService.saveAuthCode(sendingCodeRequest.email(), code);

Expand All @@ -157,9 +135,7 @@ public ResponseEntity<ApiResponse<Void, Void>> responseResponseEntity(
}

@PostMapping("/password-reset/verify")
public ResponseEntity<ApiResponse<CodeVerificationResponse, Void>> verifyCode(
@RequestBody CodeVerificationRequest request
) {
public ResponseEntity<ApiResponse<CodeVerificationResponse, Void>> verifyCode(@RequestBody CodeVerificationRequest request) {
// TODO 서비스 단 DTO 추가
boolean verified = authCodeService.verify(request.email(), request.code());
CodeVerificationResponse response = new CodeVerificationResponse(verified);
Expand All @@ -168,9 +144,7 @@ public ResponseEntity<ApiResponse<CodeVerificationResponse, Void>> verifyCode(
}

@PostMapping("/password-reset/confirm")
public ResponseEntity<ApiResponse<Void, Void>> resetPassword(
@RequestBody PasswordResetRequest passwordResetRequest
) throws NoSuchAlgorithmException, InvalidKeyException {
public ResponseEntity<ApiResponse<Void, Void>> resetPassword(@RequestBody PasswordResetRequest passwordResetRequest) throws NoSuchAlgorithmException, InvalidKeyException {
// TODO 서비스 단으로
Optional<User> user = userRepository.findByEmail(passwordResetRequest.email());
if (user.isEmpty()) {
Expand All @@ -183,4 +157,27 @@ public ResponseEntity<ApiResponse<Void, Void>> resetPassword(

return ResponseEntity.ok(ApiResponse.ok(PASSWORD_CHANGE_SUCCESS));
}

/**
* 요구 권한(role) 이상이면 200, 아니면 403
* 미인증이면 401

* 예) /api/v1/auth/LEAD, /api/v1/auth/ORGANIZER, /api/v1/auth/ADMIN
*/
@GetMapping("/{role}")
public ResponseEntity<ApiResponse<Void, ?>> checkRole(@AuthenticationPrincipal TokenProvider.CustomUserDetails me, @PathVariable UserRole role) {
if (me == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.error(GlobalErrorCode.UNAUTHORIZED_USER.getStatus()
.value(), GlobalErrorCode.UNAUTHORIZED_USER.getMessage(), null));
}

if (UserRole.hasAtLeast(me.getRole(), role)) {
return ResponseEntity.ok(ApiResponse.ok("ROLE_CHECK_PASSED", null));
}

return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ApiResponse.error(GlobalErrorCode.FORBIDDEN_USER.getStatus()
.value(), GlobalErrorCode.FORBIDDEN_USER.getMessage(), null));
}
}
29 changes: 23 additions & 6 deletions src/main/java/inha/gdgoc/domain/user/enums/UserRole.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,33 @@

@Getter
public enum UserRole {
GUEST("GUEST"),
MEMBER("MEMBER"),
CORE("CORE"),
LEAD("LEAD"),
ORGANIZER("ORGANIZER"),
ADMIN("ADMIN");
GUEST("GUEST"), MEMBER("MEMBER"), CORE("CORE"), LEAD("LEAD"), ORGANIZER("ORGANIZER"), ADMIN("ADMIN");

private final String role;

UserRole(String role) {
this.role = role;
}

/**
* 나(me)가 required 이상 권한인지
*/
public static boolean hasAtLeast(UserRole me, UserRole required) {
if (me == null || required == null) return false;
return me.rank() >= required.rank();
}

/**
* 역할 서열(낮음→높음). enum 순서 바뀌어도 여기만 수정하면 됨
*/
public int rank() {
return switch (this) {
case GUEST -> 0;
case MEMBER -> 1;
case CORE -> 2;
case LEAD -> 3;
case ORGANIZER -> 4;
case ADMIN -> 5;
};
}
}
Loading