diff --git a/src/main/java/com/eactive/apim/portal/apps/session/repository/UserSessionRepository.java b/src/main/java/com/eactive/apim/portal/apps/session/repository/UserSessionRepository.java index 5ae002e..71cc206 100644 --- a/src/main/java/com/eactive/apim/portal/apps/session/repository/UserSessionRepository.java +++ b/src/main/java/com/eactive/apim/portal/apps/session/repository/UserSessionRepository.java @@ -19,6 +19,10 @@ public interface UserSessionRepository extends JpaRepository :currentSessionId") int forceLogoutOtherSessions(@Param("loginId") String loginId, @Param("currentSessionId") String currentSessionId); + @Modifying + @Query("UPDATE UserSession s SET s.forceLogout = 'Y' WHERE s.loginId = :loginId") + int forceLogoutAllSessions(@Param("loginId") String loginId); + @Modifying @Query("DELETE FROM UserSession s WHERE s.lastAccessTime < :cutoffTime") int deleteExpiredSessions(@Param("cutoffTime") LocalDateTime cutoffTime); diff --git a/src/main/java/com/eactive/apim/portal/apps/session/service/UserSessionService.java b/src/main/java/com/eactive/apim/portal/apps/session/service/UserSessionService.java index a69bf17..38c01c9 100644 --- a/src/main/java/com/eactive/apim/portal/apps/session/service/UserSessionService.java +++ b/src/main/java/com/eactive/apim/portal/apps/session/service/UserSessionService.java @@ -74,6 +74,18 @@ public class UserSessionService { } } + /** + * 해당 사용자의 모든 세션에 강제 로그아웃 플래그 설정. + * 관리자의 권한 변경(위임/회수/소속제외)을 대상 사용자의 활성 세션에 반영하기 위해 사용한다. + */ + @Transactional + public void forceLogoutAllSessions(String loginId) { + int count = userSessionRepository.forceLogoutAllSessions(loginId); + if (count > 0) { + log.info("전체 강제 로그아웃 처리 - loginId: {}, 대상 세션 수: {}", loginId, count); + } + } + /** * 현재 세션의 강제 로그아웃 여부 확인 */ diff --git a/src/main/java/com/eactive/apim/portal/apps/user/controller/AccountController.java b/src/main/java/com/eactive/apim/portal/apps/user/controller/AccountController.java index 8c884fd..4448fee 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/controller/AccountController.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/controller/AccountController.java @@ -1,28 +1,18 @@ package com.eactive.apim.portal.apps.user.controller; import com.eactive.apim.portal.apps.agreements.service.AgreementsFacade; -import com.eactive.apim.portal.apps.user.dto.PasswordChangeRequestDTO; -import com.eactive.apim.portal.apps.user.dto.PortalOrgRegistrationDTO; -import com.eactive.apim.portal.apps.user.dto.PortalUserDTO; -import com.eactive.apim.portal.apps.user.dto.UserAgreementDTO; -import com.eactive.apim.portal.apps.user.dto.ValidationResponse; +import com.eactive.apim.portal.apps.session.service.UserSessionService; +import com.eactive.apim.portal.apps.user.dto.*; import com.eactive.apim.portal.apps.user.facade.OrgRegisterFacade; import com.eactive.apim.portal.apps.user.facade.UserFacade; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; import com.eactive.apim.portal.common.util.SecurityUtil; import com.eactive.apim.portal.invitation.entity.UserInvitation; import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus; import com.eactive.apim.portal.invitation.repository.UserInvitationRepository; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,14 +23,19 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + @Controller @Secured("ROLE_ACCOUNT") @RequiredArgsConstructor @@ -53,6 +48,7 @@ public class AccountController { private final AgreementsFacade agreementsFacade; private final com.eactive.apim.portal.apps.user.facade.AuthFacade authFacade; private final UserInvitationRepository userInvitationRepository; + private final UserSessionService userSessionService; @PostMapping("/confirm_password") @@ -105,6 +101,12 @@ public class AccountController { session.removeAttribute("success"); session.removeAttribute("redirectUrl"); + // 세션 무효화 전에 DB 세션 레코드를 정리한다. + // SecurityContextLogoutHandler 는 HTTP 세션만 invalidate 하고 UserSession DB 레코드는 + // 남기므로(정상 로그아웃의 PortalLogoutSuccessHandler 경로를 우회), 정리하지 않으면 + // 재로그인 시 check-duplicate 가 이 옛 세션을 활성으로 보고 "이미 접속중" 으로 오탐한다. + userSessionService.removeSession(session.getId()); + new SecurityContextLogoutHandler().logout(request, response, SecurityContextHolder.getContext().getAuthentication()); @@ -143,7 +145,7 @@ public class AccountController { if (currentUser.getRoleCode() == RoleCode.ROLE_USER) { java.util.Optional pendingInvitation = userInvitationRepository.findFirstByInvitationMobileAndStatus( - currentUser.getMobileNumber(), InvitationStatus.PENDING); + PhoneNumberUtil.normalize(currentUser.getMobileNumber()), InvitationStatus.PENDING); if (pendingInvitation.isPresent()) { mav.addObject("hasPendingInvitation", true); diff --git a/src/main/java/com/eactive/apim/portal/apps/user/controller/UserRegisterController.java b/src/main/java/com/eactive/apim/portal/apps/user/controller/UserRegisterController.java index c1da5b3..3c1974f 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/controller/UserRegisterController.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/controller/UserRegisterController.java @@ -7,9 +7,11 @@ import com.eactive.apim.portal.apps.user.dto.ValidationResponse; import com.eactive.apim.portal.apps.user.facade.AuthFacade; import com.eactive.apim.portal.apps.user.facade.UserRegisterFacade; import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository; +import com.eactive.apim.portal.apps.user.service.PortalUserAuthService; import com.eactive.apim.portal.apps.user.service.PortalUserService; import com.eactive.apim.portal.apps.user.validator.AgreementValidator; import com.eactive.apim.portal.common.util.EncryptionUtil; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; import com.eactive.apim.portal.common.util.SecurityUtil; import com.eactive.apim.portal.config.PortalProperties; import com.eactive.apim.portal.invitation.entity.UserInvitation; @@ -63,6 +65,7 @@ public class UserRegisterController { private final UserInvitationRepository userInvitationRepository; private final EncryptionUtil encryptionUtil; private final AgreementValidator agreementValidator; + private final PortalUserAuthService portalUserAuthService; @GetMapping("/signup") public String showSignupSelection() { @@ -243,6 +246,11 @@ public class UserRegisterController { String decodedToken = decodeInvitationToken(invitationToken); session.setAttribute("decisionToken", decodedToken); + // 이미 로그인된 상태면 수락 화면으로 직행 + if (SecurityUtil.isAuthenticated()) { + return "redirect:/signup/decision_process"; + } + Optional invitation = userInvitationRepository.findByToken(decodedToken); if (!invitation.isPresent() || invitation.get().getStatus() != UserInvitationEnums.InvitationStatus.PENDING) { @@ -281,7 +289,7 @@ public class UserRegisterController { public String showDecisionProcessPage(HttpSession session, Model model) { session.removeAttribute("decisionToken"); - String mobile = SecurityUtil.getPortalAuthenticatedUser().getMobileNumber(); + String mobile = PhoneNumberUtil.normalize(SecurityUtil.getPortalAuthenticatedUser().getMobileNumber()); Optional invitation = userInvitationRepository.findFirstByInvitationMobileAndStatus(mobile, UserInvitationEnums.InvitationStatus.PENDING); if (!invitation.isPresent()) { @@ -305,6 +313,8 @@ public class UserRegisterController { model.addAttribute("invitationCode", invitation.get().getToken()); model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE")); model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT")); + model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT")); + model.addAttribute("agreementTitle", "법인 회원 전환을 위한 약관 동의"); model.addAttribute("registrationType", "corporate"); return "apps/register/userDecisionProcess"; @@ -323,7 +333,7 @@ public class UserRegisterController { agreementValidator.validate(agreement, bindingResult); - Optional invitation = userInvitationRepository.findFirstByInvitationMobileAndStatus(SecurityUtil.getPortalAuthenticatedUser().getMobileNumber(), UserInvitationEnums.InvitationStatus.PENDING); + Optional invitation = userInvitationRepository.findFirstByInvitationMobileAndStatus(PhoneNumberUtil.normalize(SecurityUtil.getPortalAuthenticatedUser().getMobileNumber()), UserInvitationEnums.InvitationStatus.PENDING); if (action.equalsIgnoreCase("accept") && bindingResult.hasErrors()) { @@ -334,12 +344,19 @@ public class UserRegisterController { model.addAttribute("orgName", org.get().getOrgName()); model.addAttribute("termsOfUse", agreementsFacade.getAgreement("TERMS_OF_USE")); model.addAttribute("privacyCollect", agreementsFacade.getAgreement("PRIVACY_COLLECT")); + model.addAttribute("notificationConsent", agreementsFacade.getAgreement("NOTIFICATION_CONSENT")); + model.addAttribute("agreementTitle", "법인 회원 전환을 위한 약관 동의"); model.addAttribute("registrationType", "corporate"); return "apps/register/userDecisionProcess"; } else { ValidationResponse response = userRegisterFacade.processInvitation(action, invitation.get()); + // 수락 성공 시 재로그인 없이 본인 세션 권한 갱신 (ROLE_USER → ROLE_CORP_USER) + if ("accept".equalsIgnoreCase(action) && response.isValid()) { + portalUserAuthService.reloadCurrentAuthentication(); + } + // 초대 처리 완료 후 세션에서 초대 관련 속성 제거 session.removeAttribute("pendingInvitation"); session.removeAttribute("pendingInvitationToken"); diff --git a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserManFacade.java b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserManFacade.java index e038bb4..ae663dc 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserManFacade.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserManFacade.java @@ -7,6 +7,7 @@ import com.eactive.apim.portal.apps.user.mapper.PortalUserMapper; import com.eactive.apim.portal.common.exception.NotFoundException; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.common.util.DurationParser; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; import com.eactive.apim.portal.common.util.SecurityUtil; import com.eactive.apim.portal.common.util.StringMaskingUtil; import com.eactive.apim.portal.common.validator.CellPhoneValidator; @@ -19,6 +20,8 @@ import com.eactive.apim.portal.invitation.repository.UserInvitationRepository; import com.eactive.apim.portal.portaluser.entity.PortalUser; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.RoleCode; import com.eactive.apim.portal.portaluser.entity.PortalUserEnums.UserStatus; +import com.eactive.apim.portal.apps.session.service.UserSessionService; +import com.eactive.apim.portal.apps.user.service.PortalUserAuthService; import com.eactive.apim.portal.portaluser.repository.PortalUserRepository; import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; import com.eactive.apim.portal.template.service.MessageHandlerService; @@ -54,6 +57,8 @@ public class UserManFacade { private final UserLogRepository userLogRepository; private final PortalPropertyService portalPropertyService; private final CellPhoneValidator cellPhoneValidator; + private final UserSessionService userSessionService; + private final PortalUserAuthService portalUserAuthService; public Page getUsers(PortalOrgDTO userOrg, Pageable pageable) { return portalUserRepository @@ -178,16 +183,7 @@ public class UserManFacade { * 010-XXXX-XXXX(11자리) / 010-XXX-XXXX(10자리). 형식 불명은 원본 반환(검증에서 차단됨). */ private String normalizeMobile(String raw) { - if (raw == null) { - return null; - } - String digits = raw.replaceAll("[^0-9]", ""); - if (digits.length() == 11) { - return digits.substring(0, 3) + "-" + digits.substring(3, 7) + "-" + digits.substring(7); - } else if (digits.length() == 10) { - return digits.substring(0, 3) + "-" + digits.substring(3, 6) + "-" + digits.substring(6); - } - return raw; + return PhoneNumberUtil.normalize(raw); } public String createInvitation(PortalUser sender, String mobile) { @@ -253,6 +249,10 @@ public class UserManFacade { portalUserRepository.save(targetUser); portalUserRepository.save(currentUser); + // 권한 변경 즉시 반영: 대상(타 세션)은 강제 로그아웃, 본인(현재 세션)은 in-place 재인증 + userSessionService.forceLogoutAllSessions(targetUser.getLoginId()); + portalUserAuthService.reloadCurrentAuthentication(); + MessageRecipient recipient = new MessageRecipient(); recipient.setUserId(targetUser.getEmailAddr()); recipient.setPhone(targetUser.getMobileNumber()); @@ -284,6 +284,9 @@ public class UserManFacade { targetUser.setRoleCode(RoleCode.ROLE_CORP_USER); portalUserRepository.save(targetUser); + + // 권한 회수를 대상 사용자의 활성 세션에 반영 (강제 로그아웃 → 재로그인 시 새 권한) + userSessionService.forceLogoutAllSessions(targetUser.getLoginId()); } /** @@ -305,6 +308,9 @@ public class UserManFacade { targetUser.setPortalOrg(null); targetUser.setRoleCode(RoleCode.ROLE_USER); portalUserRepository.save(targetUser); + + // 소속 제외를 대상 사용자의 활성 세션에 반영 (강제 로그아웃 → 재로그인 시 새 권한) + userSessionService.forceLogoutAllSessions(targetUser.getLoginId()); } public void cancelInvitation(PortalAuthenticatedUser user, String invitationId, boolean notifyConsent) { diff --git a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserRegisterFacadeImpl.java b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserRegisterFacadeImpl.java index 436918a..f1800e5 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/facade/UserRegisterFacadeImpl.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/facade/UserRegisterFacadeImpl.java @@ -263,7 +263,7 @@ public class UserRegisterFacadeImpl implements UserRegisterFacade { ValidationResponse response = new ValidationResponse(); response.setValid(true); - response.setMessage("초대가 성공적으로 수락되었습니다. 로그아웃 후 다시 로그인 해 주세요."); + response.setMessage("초대가 성공적으로 수락되었습니다. 법인회원으로 전환되었습니다."); response.setShowPopup(true); response.setPopupType("success"); return response; diff --git a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserAuthService.java b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserAuthService.java index de3b532..2b113fb 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserAuthService.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserAuthService.java @@ -6,6 +6,7 @@ import com.eactive.apim.portal.common.exception.SystemException; import com.eactive.apim.portal.common.exception.UserNotFoundException; import com.eactive.apim.portal.common.user.PortalAuthenticatedUser; import com.eactive.apim.portal.common.util.EncryptionUtil; +import com.eactive.apim.portal.common.util.SecurityUtil; import com.eactive.apim.portal.common.util.StringMaskingUtil; import com.eactive.apim.portal.config.PortalProperties; import com.eactive.apim.portal.portaluser.entity.PortalUser; @@ -30,7 +31,9 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import lombok.RequiredArgsConstructor; import org.apache.xerces.impl.dv.util.Base64; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -58,21 +61,46 @@ public class PortalUserAuthService implements UserDetailsService { // 이메일 소문자 변환 적용 (대소문자 구분 없이 로그인 가능하도록) String normalizedUsername = username != null ? username.toLowerCase() : null; PortalUser portalUser = findByEmailAddr(normalizedUsername); - RoleCode userRole = portalUser.getRoleCode() == null ? RoleCode.ROLE_USER : portalUser.getRoleCode(); - List roles = portalProperties.getPortalSecurity().get(userRole); - PortalAuthenticatedUser authenticatedUser = portalUserMapper.portalUserToAuthenticatedUser(portalUser); - - authenticatedUser.getAuthorities().add(new SimpleGrantedAuthority(userRole.name())); - - roles.forEach(role -> authenticatedUser.getAuthorities().add(new SimpleGrantedAuthority(role))); - - return authenticatedUser; - + return buildAuthenticatedUser(portalUser); } catch (UserNotFoundException e) { throw new UsernameNotFoundException("입력하신 사용자 정보가 올바르지 않습니다. 다시 확인해 주세요."); } } + /** + * DB의 PortalUser 로부터 권한(roleCode + 역할 계층)을 채운 PortalAuthenticatedUser 를 생성한다. + * 로그인과 세션 재로드가 동일한 권한 구성을 사용하도록 공통화한다. + */ + public PortalAuthenticatedUser buildAuthenticatedUser(PortalUser portalUser) { + RoleCode userRole = portalUser.getRoleCode() == null ? RoleCode.ROLE_USER : portalUser.getRoleCode(); + List roles = portalProperties.getPortalSecurity().get(userRole); + PortalAuthenticatedUser authenticatedUser = portalUserMapper.portalUserToAuthenticatedUser(portalUser); + + authenticatedUser.getAuthorities().add(new SimpleGrantedAuthority(userRole.name())); + roles.forEach(role -> authenticatedUser.getAuthorities().add(new SimpleGrantedAuthority(role))); + + return authenticatedUser; + } + + /** + * 현재 로그인한 사용자의 권한을 DB에서 다시 읽어 SecurityContext 의 Authentication 을 교체한다. + * 재로그인 없이 본인 세션에 역할 변경(초대 수락, 관리자 위임으로 인한 본인 강등 등)을 즉시 반영한다. + */ + public void reloadCurrentAuthentication() { + PortalAuthenticatedUser current = SecurityUtil.getPortalAuthenticatedUser(); + if (current == null) { + return; + } + PortalUser fresh = portalUserRepository.findByLoginId(current.getLoginId()) + .orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다.")); + PortalAuthenticatedUser refreshed = buildAuthenticatedUser(fresh); + + UsernamePasswordAuthenticationToken newAuth = + new UsernamePasswordAuthenticationToken(refreshed, null, refreshed.getAuthorities()); + newAuth.setDetails(refreshed); + SecurityContextHolder.getContext().setAuthentication(newAuth); + } + public List findAllUsersByNameAndMobile(String userName, String mobileNumber) { try { if (mobileNumber == null || !mobileNumber.matches("^\\d{2,3}-\\d{3,4}-\\d{4}$")) { diff --git a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserService.java b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserService.java index 6e968d4..f771276 100644 --- a/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserService.java +++ b/src/main/java/com/eactive/apim/portal/apps/user/service/PortalUserService.java @@ -2,6 +2,7 @@ package com.eactive.apim.portal.apps.user.service; import com.eactive.apim.portal.apps.user.dto.PortalUserRegistrationDTO; import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; import com.eactive.apim.portal.portalorg.entity.PortalOrg; import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; import com.eactive.apim.portal.portaluser.entity.PortalUser; @@ -83,7 +84,7 @@ public class PortalUserService { if (mobileNumber == null || mobileNumber.trim().isEmpty()) { return false; } - return portalUserRepository.existsByMobileNumber(mobileNumber.trim()); + return portalUserRepository.existsByMobileNumber(PhoneNumberUtil.normalize(mobileNumber)); } /** diff --git a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java index 1349278..afd52ae 100644 --- a/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java +++ b/src/main/java/com/eactive/apim/portal/config/PortalAuthenticationSuccessHandler.java @@ -4,6 +4,7 @@ import com.eactive.apim.portal.apps.session.service.UserSessionService; import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository; import com.eactive.apim.portal.apps.user.service.PortalUserLogService; import com.eactive.apim.portal.common.util.HttpRequestUtil; +import com.eactive.apim.portal.common.util.PhoneNumberUtil; import com.eactive.apim.portal.common.util.StringRepeatUtil; import com.eactive.apim.portal.invitation.entity.UserInvitation; import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus; @@ -95,9 +96,10 @@ public class PortalAuthenticationSuccessHandler implements AuthenticationSuccess // 초대 코드 확인 - ROLE_USER만 확인 (세션에 저장하여 메인 페이지에서 팝업으로 표시) if (user.getRoleCode() == PortalUserEnums.RoleCode.ROLE_USER) { + // 휴대폰 형식(하이픈 유무)이 달라도 초대와 매칭되도록 정규화 후 조회 Optional pendingInvitation = userInvitationRepository.findFirstByInvitationMobileAndStatus( - user.getMobileNumber(), InvitationStatus.PENDING); + PhoneNumberUtil.normalize(user.getMobileNumber()), InvitationStatus.PENDING); if (pendingInvitation.isPresent()) { UserInvitation invitation = pendingInvitation.get(); diff --git a/src/main/resources/templates/views/apps/register/userAgreementContent.html b/src/main/resources/templates/views/apps/register/userAgreementContent.html index c0922bb..15b174a 100644 --- a/src/main/resources/templates/views/apps/register/userAgreementContent.html +++ b/src/main/resources/templates/views/apps/register/userAgreementContent.html @@ -7,7 +7,7 @@
-

약관 동의

+

약관 동의

필수 동의
diff --git a/src/main/resources/templates/views/apps/register/userDecisionAccept.html b/src/main/resources/templates/views/apps/register/userDecisionAccept.html index e55a118..ac0d3d6 100644 --- a/src/main/resources/templates/views/apps/register/userDecisionAccept.html +++ b/src/main/resources/templates/views/apps/register/userDecisionAccept.html @@ -32,16 +32,6 @@

-
-

- 초대코드: - -

-

- 이메일로 받은 초대코드와 일치하는지 확인해 주세요. -

-
-

diff --git a/src/main/resources/templates/views/apps/register/userDecisionProcess.html b/src/main/resources/templates/views/apps/register/userDecisionProcess.html index 1d1288e..c62de6d 100644 --- a/src/main/resources/templates/views/apps/register/userDecisionProcess.html +++ b/src/main/resources/templates/views/apps/register/userDecisionProcess.html @@ -43,15 +43,6 @@

-
-

- 초대코드: - -

-

- 이메일로 받은 초대코드와 일치하는지 확인해 주세요. -

-
diff --git a/src/test/java/com/eactive/apim/portal/common/util/PhoneNumberUtilTest.java b/src/test/java/com/eactive/apim/portal/common/util/PhoneNumberUtilTest.java new file mode 100644 index 0000000..51052e8 --- /dev/null +++ b/src/test/java/com/eactive/apim/portal/common/util/PhoneNumberUtilTest.java @@ -0,0 +1,70 @@ +package com.eactive.apim.portal.common.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PhoneNumberUtilTest { + + @Test + @DisplayName("휴대폰 11자리 → 010-XXXX-XXXX (멱등)") + void mobile11() { + assertEquals("010-1234-5678", PhoneNumberUtil.normalize("01012345678")); + assertEquals("010-1234-5678", PhoneNumberUtil.normalize("010-1234-5678")); + assertEquals("010-9912-0000", PhoneNumberUtil.normalize("01099120000")); + } + + @Test + @DisplayName("휴대폰 10자리 → 01X-XXX-XXXX") + void mobile10() { + assertEquals("011-123-4567", PhoneNumberUtil.normalize("0111234567")); + } + + @Test + @DisplayName("서울 02 → 9자리 02-XXX-XXXX / 10자리 02-XXXX-XXXX") + void seoul() { + assertEquals("02-123-4567", PhoneNumberUtil.normalize("021234567")); + assertEquals("02-1234-5678", PhoneNumberUtil.normalize("0212345678")); + assertEquals("02-1234-5678", PhoneNumberUtil.normalize("02-1234-5678")); + } + + @Test + @DisplayName("지역/070 → 10자리 0XX-XXX-XXXX / 11자리 0XX-XXXX-XXXX") + void area() { + assertEquals("031-234-5678", PhoneNumberUtil.normalize("0312345678")); + assertEquals("070-1234-5678", PhoneNumberUtil.normalize("07012345678")); + } + + @Test + @DisplayName("안심번호 050X 12자리 → 0XXX-XXXX-XXXX") + void safetyNumber() { + assertEquals("0507-1234-5678", PhoneNumberUtil.normalize("050712345678")); + } + + @Test + @DisplayName("대표번호 15XX/16XX/18XX 8자리 → XXXX-XXXX") + void representative() { + assertEquals("1588-1234", PhoneNumberUtil.normalize("15881234")); + assertEquals("1644-5678", PhoneNumberUtil.normalize("16445678")); + assertEquals("1800-1234", PhoneNumberUtil.normalize("18001234")); + } + + @Test + @DisplayName("미해당 패턴/null/blank 는 원본 유지") + void passthrough() { + assertEquals("123", PhoneNumberUtil.normalize("123")); + assertEquals("abc", PhoneNumberUtil.normalize("abc")); + assertEquals("", PhoneNumberUtil.normalize("")); + assertNull(PhoneNumberUtil.normalize(null)); + } + + @Test + @DisplayName("digitsOnly 는 숫자만 추출, null 유지") + void digitsOnly() { + assertEquals("01012345678", PhoneNumberUtil.digitsOnly("010-1234-5678")); + assertEquals("0212345678", PhoneNumberUtil.digitsOnly("02-1234-5678")); + assertNull(PhoneNumberUtil.digitsOnly(null)); + } +}