From ca444a3b4f517d5df6c1b376755a44f289149d90 Mon Sep 17 00:00:00 2001 From: pray4skylark Date: Mon, 8 Jun 2026 15:48:22 +0900 Subject: [PATCH] refactor: login by email (loginId kept as internal identifier) --- .../jwt/CurrentUserArgumentResolver.java | 8 ++++---- .../java/com/srsystem/dto/LoginRequest.java | 4 ++-- .../repository/EmployeeRepository.java | 1 + .../com/srsystem/service/AuthService.java | 18 ++++++++--------- .../com/srsystem/service/EmployeeService.java | 3 +-- .../db/migration/V17__email_unique.sql | 3 +++ src/app/(auth)/login/page.tsx | 20 +++++++++---------- src/app/(main)/settings/accounts/page.tsx | 4 ++-- src/services/employeeService.ts | 2 +- src/types/index.ts | 2 +- 10 files changed, 34 insertions(+), 31 deletions(-) create mode 100644 backend/src/main/resources/db/migration/V17__email_unique.sql diff --git a/backend/src/main/java/com/srsystem/config/jwt/CurrentUserArgumentResolver.java b/backend/src/main/java/com/srsystem/config/jwt/CurrentUserArgumentResolver.java index 4d1dae3..6a172d1 100644 --- a/backend/src/main/java/com/srsystem/config/jwt/CurrentUserArgumentResolver.java +++ b/backend/src/main/java/com/srsystem/config/jwt/CurrentUserArgumentResolver.java @@ -34,11 +34,11 @@ public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolve log.warn("No authentication found"); return null; } - String loginId = authentication.getName(); - log.info("Resolving current user for loginId: {}", loginId); - Employee emp = authService.getEmployeeByLoginId(loginId); + String email = authentication.getName(); + log.info("Resolving current user for email: {}", email); + Employee emp = authService.getEmployeeByLoginId(email); if (emp == null) { - log.warn("Employee not found for loginId: {}", loginId); + log.warn("Employee not found for email: {}", email); return null; } log.info("Found employee: empId={}, name={}", emp.getEmpId(), emp.getName()); diff --git a/backend/src/main/java/com/srsystem/dto/LoginRequest.java b/backend/src/main/java/com/srsystem/dto/LoginRequest.java index c6ebaf5..7ddb339 100644 --- a/backend/src/main/java/com/srsystem/dto/LoginRequest.java +++ b/backend/src/main/java/com/srsystem/dto/LoginRequest.java @@ -5,8 +5,8 @@ import com.srsystem.entity.enums.RoleCode; import jakarta.validation.constraints.NotBlank; public record LoginRequest( - @NotBlank(message = "로그인 ID 는 필수입니다") - String loginId, + @NotBlank(message = "이메일은 필수입니다") + String email, @NotBlank(message = "비밀번호는 필수입니다") String password ) { diff --git a/backend/src/main/java/com/srsystem/repository/EmployeeRepository.java b/backend/src/main/java/com/srsystem/repository/EmployeeRepository.java index 898c9cf..3e6bc67 100644 --- a/backend/src/main/java/com/srsystem/repository/EmployeeRepository.java +++ b/backend/src/main/java/com/srsystem/repository/EmployeeRepository.java @@ -16,6 +16,7 @@ import java.util.Optional; public interface EmployeeRepository extends JpaRepository { @Query("SELECT e FROM Employee e WHERE e.loginId = :loginId") Optional findByLoginId(@Param("loginId") String loginId); + Optional findByEmail(String email); List findByNameContainingIgnoreCase(String name); Page findByEmpStatus(EmpStatus status, Pageable pageable); } diff --git a/backend/src/main/java/com/srsystem/service/AuthService.java b/backend/src/main/java/com/srsystem/service/AuthService.java index bb8b0d9..1644b02 100644 --- a/backend/src/main/java/com/srsystem/service/AuthService.java +++ b/backend/src/main/java/com/srsystem/service/AuthService.java @@ -27,15 +27,15 @@ public class AuthService { @Transactional public LoginResponse login(LoginRequest request) { - Employee employee = employeeRepository.findByLoginId(request.loginId()) + Employee employee = employeeRepository.findByEmail(request.email()) .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_CREDENTIALS)); if (!passwordEncoder.matches(request.password(), employee.getPassword())) { throw new BusinessException(ErrorCode.INVALID_CREDENTIALS); } - String accessToken = jwtTokenProvider.generateAccessToken(employee.getLoginId(), employee.getRoleCode().name()); - String refreshToken = jwtTokenProvider.generateRefreshToken(employee.getLoginId()); + String accessToken = jwtTokenProvider.generateAccessToken(employee.getEmail(), employee.getRoleCode().name()); + String refreshToken = jwtTokenProvider.generateRefreshToken(employee.getEmail()); EmployeeSummary summary = employeeService.toSummary(employee); @@ -48,12 +48,12 @@ public class AuthService { throw new BusinessException(ErrorCode.INVALID_CREDENTIALS); } - String loginId = jwtTokenProvider.getLoginId(refreshToken); - Employee employee = employeeRepository.findByLoginId(loginId) + String email = jwtTokenProvider.getLoginId(refreshToken); + Employee employee = employeeRepository.findByEmail(email) .orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND)); - String newAccessToken = jwtTokenProvider.generateAccessToken(employee.getLoginId(), employee.getRoleCode().name()); - String newRefreshToken = jwtTokenProvider.generateRefreshToken(employee.getLoginId()); + String newAccessToken = jwtTokenProvider.generateAccessToken(employee.getEmail(), employee.getRoleCode().name()); + String newRefreshToken = jwtTokenProvider.generateRefreshToken(employee.getEmail()); EmployeeSummary summary = employeeService.toSummary(employee); @@ -61,8 +61,8 @@ public class AuthService { } @Transactional(readOnly = true) - public Employee getEmployeeByLoginId(String loginId) { - return employeeRepository.findByLoginId(loginId) + public Employee getEmployeeByLoginId(String email) { + return employeeRepository.findByEmail(email) .orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND)); } } diff --git a/backend/src/main/java/com/srsystem/service/EmployeeService.java b/backend/src/main/java/com/srsystem/service/EmployeeService.java index bb5b434..fb36d3c 100644 --- a/backend/src/main/java/com/srsystem/service/EmployeeService.java +++ b/backend/src/main/java/com/srsystem/service/EmployeeService.java @@ -76,10 +76,9 @@ public class EmployeeService { @Transactional public Employee create(Employee employee) { - if (employeeRepository.findByLoginId(employee.getLoginId()).isPresent()) { + if (employee.getEmail() != null && employeeRepository.findByEmail(employee.getEmail()).isPresent()) { throw new BusinessException(ErrorCode.DUPLICATE_LOGIN_ID); } - if (employee.getRoleCode() == null) employee.setRoleCode(RoleCode.EMPLOYEE); if (employee.getEmpStatus() == null) employee.setEmpStatus(EmpStatus.ACTIVE); if (employee.getDispatchStatus() == null) employee.setDispatchStatus(DispatchStatus.WAITING); diff --git a/backend/src/main/resources/db/migration/V17__email_unique.sql b/backend/src/main/resources/db/migration/V17__email_unique.sql new file mode 100644 index 0000000..cfa43ae --- /dev/null +++ b/backend/src/main/resources/db/migration/V17__email_unique.sql @@ -0,0 +1,3 @@ +ALTER TABLE tb_employee ADD CONSTRAINT uq_employee_email UNIQUE (email); + +UPDATE tb_employee SET email = CONCAT(login_id, '@sr-system.com') WHERE email IS NULL OR email = ''; diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 9958822..d45b237 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -9,7 +9,7 @@ import type { LoginRequest } from '@/types'; export default function LoginPage() { const router = useRouter(); const login = useAuthStore((s) => s.login); - const [loginId, setLoginId] = useState(''); + const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [showPw, setShowPw] = useState(false); const [loading, setLoading] = useState(false); @@ -17,14 +17,14 @@ export default function LoginPage() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!loginId.trim() || !password.trim()) { - setError('아이디와 비밀번호를 입력해주세요.'); + if (!email.trim() || !password.trim()) { + setError('이메일과 비밀번호를 입력해주세요.'); return; } setLoading(true); setError(''); try { - await login({ loginId, password } as LoginRequest); + await login({ email, password } as LoginRequest); router.push('/'); } catch (err: unknown) { const msg = (err as { message?: string })?.message || '로그인에 실패했습니다.'; @@ -53,14 +53,14 @@ export default function LoginPage() { )}
- + setLoginId(e.target.value)} - placeholder="아이디를 입력하세요" + type="email" + value={email} + onChange={(e) => setEmail(e.target.value)} + placeholder="이메일을 입력하세요" className="w-full px-4 py-2.5 rounded-xl border border-border bg-white text-text-main text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all" - autoComplete="username" + autoComplete="email" />
diff --git a/src/app/(main)/settings/accounts/page.tsx b/src/app/(main)/settings/accounts/page.tsx index 7802405..4ec95fc 100644 --- a/src/app/(main)/settings/accounts/page.tsx +++ b/src/app/(main)/settings/accounts/page.tsx @@ -147,7 +147,7 @@ export default function AccountsPage() { const handleCreate = async (e: React.FormEvent) => { e.preventDefault(); if (!createForm.loginId.trim() || !createForm.name.trim() || !createForm.password.trim()) { - setCreateError('로그인 ID, 이름, 비밀번호는 필수입니다.'); + setCreateError('사번, 이름, 비밀번호는 필수입니다.'); return; } setIsSubmitting(true); @@ -350,7 +350,7 @@ export default function AccountsPage() { {/* Basic Info */}
- + setCreateForm((f) => ({ ...f, loginId: e.target.value }))} className={fieldClass} placeholder="login_id" />
diff --git a/src/services/employeeService.ts b/src/services/employeeService.ts index f000b69..ba23fb6 100644 --- a/src/services/employeeService.ts +++ b/src/services/employeeService.ts @@ -2,7 +2,7 @@ import apiClient from './apiClient'; import type { ApiResponse, Employee, EmployeeSearchResult, Page, DispatchHistory, Project, EmergencyContact } from '@/types'; const employeeService = { - login(credentials: { loginId: string; password: string }) { + login(credentials: { email: string; password: string }) { return apiClient.post>('/auth/login', credentials); }, diff --git a/src/types/index.ts b/src/types/index.ts index 979b32a..62131ec 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -341,7 +341,7 @@ export interface WakeResponse { // Auth // ============================================ export interface LoginRequest { - loginId: string; + email: string; password: string; }