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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import com.nowait.applicationadmin.security.exception.UnauthorizedException;
import com.nowait.common.exception.ErrorMessage;
import com.nowait.common.exception.ErrorResponse;
import com.nowait.domaincorerdb.menu.exception.MenuCreationUnauthorizedException;
import com.nowait.domaincorerdb.menu.exception.MenuDeleteUnauthorizedException;
import com.nowait.domaincorerdb.menu.exception.MenuUpdateUnauthorizedException;
import com.nowait.domaincorerdb.menu.exception.MenuViewUnauthorizedException;
import com.nowait.domaincorerdb.order.exception.DuplicateOrderException;
import com.nowait.domaincorerdb.order.exception.OrderItemsEmptyException;
import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException;
Expand Down Expand Up @@ -128,6 +132,34 @@ public ErrorResponse reservationNotFoundException(ReservationNotFoundException e
return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode());
}

@ResponseStatus(value = FORBIDDEN)
@ExceptionHandler(MenuCreationUnauthorizedException.class)
public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedException e) {
log.error("menuCreationUnauthorizedException", e);
return new ErrorResponse(e.getMessage(), MENU_CREATION_UNAUTHORIZED.getCode());
}

@ResponseStatus(value = FORBIDDEN)
@ExceptionHandler(MenuViewUnauthorizedException.class)
public ErrorResponse menuViewUnauthorizedException(MenuViewUnauthorizedException e) {
log.error("menuViewUnauthorizedException", e);
return new ErrorResponse(e.getMessage(), MENU_VIEW_UNAUTHORIZED.getCode());
}

@ResponseStatus(value = FORBIDDEN)
@ExceptionHandler(MenuUpdateUnauthorizedException.class)
public ErrorResponse menuUpdateUnauthorizedException(MenuUpdateUnauthorizedException e) {
log.error("menuUpdateUnauthorizedException", e);
return new ErrorResponse(e.getMessage(), MENU_UPDATE_UNAUTHORIZED.getCode());
}

@ResponseStatus(value = FORBIDDEN)
@ExceptionHandler(MenuDeleteUnauthorizedException.class)
public ErrorResponse menuDeleteUnauthorizedException(MenuDeleteUnauthorizedException e) {
log.error("menuDeleteUnauthorizedException", e);
return new ErrorResponse(e.getMessage(), MENU_DELETE_UNAUTHORIZED.getCode());
}


private static Map<String, String> getErrors(MethodArgumentNotValidException e) {
return e.getBindingResult()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
Expand All @@ -16,6 +17,7 @@
import com.nowait.applicationadmin.menu.dto.MenuUpdateRequest;
import com.nowait.applicationadmin.menu.service.MenuService;
import com.nowait.common.api.ApiUtils;
import com.nowait.domaincorerdb.user.entity.MemberDetails;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand All @@ -36,8 +38,9 @@ public class MenuController {
@PostMapping("/create")
@Operation(summary = "메뉴 생성", description = "새로운 메뉴를 생성합니다.")
@ApiResponse(responseCode = "201", description = "메뉴 생성")
public ResponseEntity<?> createMenu(@Valid @RequestBody MenuCreateRequest request) {
MenuCreateResponse response = menuService.createMenu(request);
public ResponseEntity<?> createMenu(@Valid @RequestBody MenuCreateRequest request,
@AuthenticationPrincipal MemberDetails memberDetails) {
MenuCreateResponse response = menuService.createMenu(request,memberDetails);

return ResponseEntity
.status(HttpStatus.CREATED)
Expand All @@ -51,12 +54,13 @@ public ResponseEntity<?> createMenu(@Valid @RequestBody MenuCreateRequest reques
@GetMapping("/all-menus/stores/{storeId}")
@Operation(summary = "가게의 모든 메뉴 조회", description = "특정 가게의 모든 메뉴를 조회")
@ApiResponse(responseCode = "200", description = "가게의 모든 메뉴 조회")
public ResponseEntity<?> getMenusByStoreId(@PathVariable Long storeId) {
public ResponseEntity<?> getMenusByStoreId(@PathVariable Long storeId,
@AuthenticationPrincipal MemberDetails memberDetails) {
return ResponseEntity
.status(HttpStatus.OK)
.body(
ApiUtils.success(
menuService.getAllMenusByStoreId(storeId)
menuService.getAllMenusByStoreId(storeId,memberDetails)
)
);
}
Expand All @@ -66,13 +70,14 @@ public ResponseEntity<?> getMenusByStoreId(@PathVariable Long storeId) {
@ApiResponse(responseCode = "200", description = "메뉴 상세 조회")
public ResponseEntity<?> getMenuById(
@PathVariable Long storeId,
@PathVariable Long menuId
@PathVariable Long menuId,
@AuthenticationPrincipal MemberDetails memberDetails
) {
return ResponseEntity
.status(HttpStatus.OK)
.body(
ApiUtils.success(
menuService.getMenuById(storeId, menuId)
menuService.getMenuById(storeId, menuId,memberDetails)
)
);
}
Expand All @@ -83,26 +88,27 @@ public ResponseEntity<?> getMenuById(
@ApiResponse(responseCode = "200", description = "메뉴 수정")
public ResponseEntity<?> updateMenu(
@PathVariable Long menuId,
@Valid @RequestBody MenuUpdateRequest request
@Valid @RequestBody MenuUpdateRequest request,
@AuthenticationPrincipal MemberDetails memberDetails
) {
return ResponseEntity
.status(HttpStatus.OK)
.body(
ApiUtils.success(
menuService.updateMenu(menuId, request)
menuService.updateMenu(menuId, request,memberDetails)
)
);
}

@DeleteMapping("/delete/{menuId}")
@Operation(summary = "메뉴 삭제", description = "특정 메뉴를 삭제합니다.")
@ApiResponse(responseCode = "200", description = "메뉴 삭제")
public ResponseEntity<?> deleteMenu(@PathVariable Long menuId) {
public ResponseEntity<?> deleteMenu(@PathVariable Long menuId,@AuthenticationPrincipal MemberDetails memberDetails) {
return ResponseEntity
.status(HttpStatus.OK)
.body(
ApiUtils.success(
menuService.deleteMenu(menuId)
menuService.deleteMenu(menuId,memberDetails)
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,21 @@
import com.nowait.applicationadmin.menu.dto.MenuReadDto;
import com.nowait.applicationadmin.menu.dto.MenuReadResponse;
import com.nowait.applicationadmin.menu.dto.MenuUpdateRequest;
import com.nowait.common.enums.Role;
import com.nowait.domaincorerdb.menu.entity.Menu;
import com.nowait.domaincorerdb.menu.entity.MenuImage;
import com.nowait.domaincorerdb.menu.exception.MenuCreationUnauthorizedException;
import com.nowait.domaincorerdb.menu.exception.MenuDeleteUnauthorizedException;
import com.nowait.domaincorerdb.menu.exception.MenuNotFoundException;
import com.nowait.domaincorerdb.menu.exception.MenuParamEmptyException;
import com.nowait.domaincorerdb.menu.exception.MenuUpdateUnauthorizedException;
import com.nowait.domaincorerdb.menu.exception.MenuViewUnauthorizedException;
import com.nowait.domaincorerdb.menu.repository.MenuImageRepository;
import com.nowait.domaincorerdb.menu.repository.MenuRepository;
import com.nowait.domaincorerdb.order.exception.OrderParameterEmptyException;
import com.nowait.domaincorerdb.user.entity.MemberDetails;
import com.nowait.domaincorerdb.user.entity.User;
import com.nowait.domaincorerdb.user.exception.UserNotFoundException;
import com.nowait.domaincorerdb.user.repository.UserRepository;

import lombok.RequiredArgsConstructor;

Expand All @@ -26,18 +35,31 @@ public class MenuService {

private final MenuRepository menuRepository;
private final MenuImageRepository menuImageRepository;
private final UserRepository userRepository;

@Transactional
public MenuCreateResponse createMenu(MenuCreateRequest request) {
Menu toSave = request.toEntity();
public MenuCreateResponse createMenu(MenuCreateRequest request, MemberDetails memberDetails) {
// 사용자 정보 가져오기
User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new);

// 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인
if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(request.getStoreId())) {
throw new MenuCreationUnauthorizedException();
}
// 메뉴 생성 로직
Menu toSave = request.toEntity();
Menu saved = menuRepository.save(toSave);

return MenuCreateResponse.fromEntity(saved);
}

@Transactional(readOnly = true)
public MenuReadResponse getAllMenusByStoreId(Long storeId) {
public MenuReadResponse getAllMenusByStoreId(Long storeId, MemberDetails memberDetails) {
// 사용자 정보 가져오기
User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new);

// 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인
validateMenuViewAuthorization(user, storeId);
List<Menu> menus = menuRepository.findAllByStoreIdAndDeletedFalse(storeId);

List<MenuReadDto> menuReadResponse = menus.stream()
Expand All @@ -54,28 +76,36 @@ public MenuReadResponse getAllMenusByStoreId(Long storeId) {
}

@Transactional(readOnly = true)
public MenuReadDto getMenuById(Long storeId, Long menuId) {
public MenuReadDto getMenuById(Long storeId, Long menuId, MemberDetails memberDetails) {
if (storeId == null || menuId == null) {
throw new OrderParameterEmptyException();
throw new MenuParamEmptyException();
}

// 사용자 정보 가져오기
User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new);
Menu menu = menuRepository.findByStoreIdAndIdAndDeletedFalse(storeId, menuId)
.orElseThrow(MenuNotFoundException::new);
// 사용자 역할이 SUPER_ADMIN이거나, storeId가 일치하는지 확인
validateMenuViewAuthorization(user, menu.getStoreId());

List<MenuImage> images = menuImageRepository.findByMenu(menu);
List<MenuImageUploadResponse> imageDto = images.stream()
.map(MenuImageUploadResponse::fromEntity)
.toList();

return MenuReadDto.fromEntity(menu, imageDto);
}

}

@Transactional
public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request) {
public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request, MemberDetails memberDetails) {
User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new);
Menu menu = menuRepository.findByIdAndDeletedFalse(menuId)
.orElseThrow(MenuNotFoundException::new);

if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(menu.getStoreId())) {
throw new MenuUpdateUnauthorizedException();
}

menu.updateInfo(
request.getName(),
request.getDescription(),
Expand All @@ -93,10 +123,13 @@ public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request) {
}

@Transactional
public String deleteMenu(Long menuId) {
Menu menu = menuRepository.findById(menuId)
.orElseThrow(MenuNotFoundException::new);
public String deleteMenu(Long menuId, MemberDetails memberDetails) {
User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new);
Menu menu = menuRepository.findById(menuId).orElseThrow(MenuNotFoundException::new);

if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(menu.getStoreId())) {
throw new MenuDeleteUnauthorizedException();
}
menu.markAsDeleted();
menuRepository.save(menu);

Expand All @@ -113,4 +146,10 @@ public Boolean toggleSoldOut(Long menuId) {
return menu.getIsSoldOut();
}

private static void validateMenuViewAuthorization(User user, Long storeId) {
if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) {
throw new MenuViewUnauthorizedException();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -18,30 +19,34 @@ public class ManagerSignupRequestDto {
@NotBlank
@Email(regexp = "^(?=.{1,64}@)[A-Za-z0-9_-]+(\\.[A-Za-z0-9_-]+)*@"
+ "[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$")
@Schema(description = "이메일", example = "abc@gmail.com")
@Schema(description = "이메일(예시)", example = "abc@gmail.com")
private String email;

@NotBlank
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}")
@Schema(description = "비밀번호", example = "1234568!@")
@Schema(description = "비밀번호(예시)", example = "1234568!@")
private String password;

@NotBlank
@Pattern(regexp = "^[a-zA-Z가-힣]{2,12}$")
@Schema(description = "닉네임", example = "가십이")
@Schema(description = "이름(예시)", example = "김노웻")
private String nickname;

@NotBlank
@Schema(description = "로그인타입", example = "LOCAL")
private String socialType;

@NotNull
@Schema(description = "관리자가 속한 storeId", example = "1")
private Long storeId;

public User toEntity() {
return User.builder()
.email(email)
.password(password)
.nickname(nickname)
.socialType(SocialType.LOCAL)
.role(Role.MANAGER)
.storeId(storeId)
.build();

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
}

User user = User.createUserWithId(userId, "sampleEmail", "sampleNickname", "sampleProfileImg"
, SocialType.KAKAO, Role.fromString(roleString));
, SocialType.KAKAO, Role.fromString(roleString),5000L);
Comment on lines 81 to +82
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

하드코딩된 storeId 값을 검토해야 합니다.

모든 JWT 인증 사용자에게 동일한 storeId(5000L)가 할당되고 있습니다. 이는 관리자 접근 제한 기능의 목적에 맞지 않을 수 있습니다.

다음 중 하나를 고려해보세요:

  1. JWT 토큰에서 실제 storeId를 추출하여 사용
  2. 사용자별 storeId를 데이터베이스에서 조회하여 설정
  3. 임시 값이라면 TODO 주석 추가
🤖 Prompt for AI Agents
In
nowait-app-user-api/src/main/java/com/nowait/applicationuser/security/jwt/JwtAuthorizationFilter.java
at lines 81-82, the storeId is hardcoded as 5000L for all users, which may not
suit admin access restrictions. To fix this, either extract the actual storeId
from the JWT token if available, or query the user's storeId from the database
and use that value. If this is a temporary placeholder, add a TODO comment
indicating that the storeId assignment needs proper implementation later.


CustomOAuth2User customOAuth2User = new CustomOAuth2User(user);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public ResponseEntity<?> refreshToken(@RequestBody RefreshTokenRequest request){

long currentAccessTokenExpiration = accessTokenExpiration;
if (role.equals("SUPER_ADMIN")) {
currentAccessTokenExpiration = 7L * 24 * 60 * 60 * 1000L; // 7일
currentAccessTokenExpiration = 100L * 24 * 60 * 60 * 1000L; // 100일
}
Comment on lines 44 to 46
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

액세스 토큰 만료 시간이 너무 긴 것에 대한 보안 우려

SUPER_ADMIN 역할의 액세스 토큰 만료 시간을 100일로 설정하는 것은 보안상 위험할 수 있습니다:

  1. 토큰 탈취 위험: 긴 만료 시간으로 인해 토큰이 탈취되었을 때 장기간 악용될 수 있습니다
  2. 높은 권한 노출: SUPER_ADMIN은 최고 권한을 가진 역할로, 더 짧은 만료 시간이 적절합니다
  3. 보안 모범 사례: 일반적으로 액세스 토큰은 짧은 만료 시간(1-24시간)을 가지고 리프레시 토큰으로 갱신하는 것이 권장됩니다

다음과 같이 더 짧은 만료 시간으로 수정하는 것을 권장합니다:

- currentAccessTokenExpiration = 100L * 24 * 60 * 60 * 1000L; // 100일
+ currentAccessTokenExpiration = 24 * 60 * 60 * 1000L; // 24시간
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (role.equals("SUPER_ADMIN")) {
currentAccessTokenExpiration = 7L * 24 * 60 * 60 * 1000L; // 7일
currentAccessTokenExpiration = 100L * 24 * 60 * 60 * 1000L; // 100일
}
if (role.equals("SUPER_ADMIN")) {
currentAccessTokenExpiration = 24 * 60 * 60 * 1000L; // 24시간
}
🤖 Prompt for AI Agents
In
nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java
around lines 44 to 46, the access token expiration time for the SUPER_ADMIN role
is set to 100 days, which is too long and poses security risks. Change the
expiration time to a much shorter duration, such as between 1 to 24 hours, to
reduce the risk of token theft and misuse. Implement this by updating the
currentAccessTokenExpiration value to a shorter time span consistent with
security best practices for high-privilege roles.


// 리프레시 토큰 유효성 검증
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ public enum ErrorMessage {

// menu
MENU_PARAMETER_EMPTY("메뉴 생성 시 파라미터 정보가 없습니다.", "menu001"),
MENU_NOT_FOUND("해당 메뉴를 찾을 수 없습니다.", "menu001"),
MENU_NOT_FOUND("해당 메뉴를 찾을 수 없습니다.", "menu002"),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

기존 에러 코드 변경에 대한 하위 호환성 검증 필요

MENU_NOT_FOUND의 에러 코드가 "menu001"에서 "menu002"로 변경되었습니다. 이는 클라이언트 코드에서 기존 에러 코드를 참조하고 있다면 호환성 문제를 야기할 수 있습니다.

다음 스크립트를 실행하여 기존 에러 코드 "menu001"을 참조하는 코드가 있는지 확인해주세요:


🏁 Script executed:

#!/bin/bash
# Description: 기존 "menu001" 에러 코드를 참조하는 코드 검색
# Expected: 기존 코드에서 "menu001" 참조가 있다면 업데이트 필요

echo "=== 기존 menu001 에러 코드 참조 검색 ==="
rg -n "menu001" --type java

Length of output: 235


기존 menu001 코드 재사용 확인 및 하위 호환성 보장 필요

스크립트 실행 결과, 아래 위치에서 여전히 menu001이 사용되고 있습니다.
이전에는 menu001MENU_NOT_FOUND에 매핑되었으나, 현재 MENU_NOT_FOUNDmenu002로 변경되어 클라이언트가 menu001을 여전히 “메뉴를 찾을 수 없음”으로 처리 중이라면 잘못된 동작이 발생할 수 있습니다.

  • 확인 위치
    • nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java:31
    MENU_PARAMETER_EMPTY("메뉴 생성 시 파라미터 정보가 없습니다.", "menu001"),
    • nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java:32
    MENU_NOT_FOUND("해당 메뉴를 찾을 수 없습니다.", "menu002"),

제안 사항

  1. 기존 menu001 코드 참조 클라이언트 코드 목록을 파악하고 영향도 분석
  2. 하위 호환을 위해 menu001을 계속 유지하거나, 별도 마이그레이션 가이드 및 릴리즈 노트 제공
  3. 버전 정책(major/minor) 검토 후 적절한 버전 업 조치
🤖 Prompt for AI Agents
In nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java at
lines 31-32, the error code for MENU_NOT_FOUND was changed from "menu001" to
"menu002", but client code still uses "menu001" for this error, causing
compatibility issues. To fix this, either revert MENU_NOT_FOUND to use "menu001"
to maintain backward compatibility or ensure a clear migration path by updating
client references, providing migration guides, and adjusting versioning
accordingly.

MENU_CREATION_UNAUTHORIZED("메뉴 생성 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu003"),
MENU_VIEW_UNAUTHORIZED("메뉴 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu004"),
MENU_UPDATE_UNAUTHORIZED("메뉴 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu005"),
MENU_DELETE_UNAUTHORIZED("메뉴 삭제 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "menu006"),

// store
STORE_PARAMETER_EMPTY("주점 생성 시 파라미터 정보가 없습니다.", "store001"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.nowait.domaincorerdb.menu.exception;

import com.nowait.common.exception.ErrorMessage;

public class MenuCreationUnauthorizedException extends RuntimeException {
public MenuCreationUnauthorizedException() {
super(ErrorMessage.MENU_CREATION_UNAUTHORIZED.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.nowait.domaincorerdb.menu.exception;

import com.nowait.common.exception.ErrorMessage;

public class MenuDeleteUnauthorizedException extends RuntimeException {
public MenuDeleteUnauthorizedException() {
super(ErrorMessage.MENU_DELETE_UNAUTHORIZED.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.nowait.domaincorerdb.menu.exception;

import com.nowait.common.exception.ErrorMessage;

public class MenuUpdateUnauthorizedException extends RuntimeException {
public MenuUpdateUnauthorizedException() {
super(ErrorMessage.MENU_UPDATE_UNAUTHORIZED.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.nowait.domaincorerdb.menu.exception;

import com.nowait.common.exception.ErrorMessage;

public class MenuViewUnauthorizedException extends RuntimeException {
public MenuViewUnauthorizedException() {
super(ErrorMessage.MENU_VIEW_UNAUTHORIZED.getMessage());
}
}
Loading