refactor: login by email (loginId kept as internal identifier)
This commit is contained in:
@@ -34,11 +34,11 @@ public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolve
|
|||||||
log.warn("No authentication found");
|
log.warn("No authentication found");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String loginId = authentication.getName();
|
String email = authentication.getName();
|
||||||
log.info("Resolving current user for loginId: {}", loginId);
|
log.info("Resolving current user for email: {}", email);
|
||||||
Employee emp = authService.getEmployeeByLoginId(loginId);
|
Employee emp = authService.getEmployeeByLoginId(email);
|
||||||
if (emp == null) {
|
if (emp == null) {
|
||||||
log.warn("Employee not found for loginId: {}", loginId);
|
log.warn("Employee not found for email: {}", email);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
log.info("Found employee: empId={}, name={}", emp.getEmpId(), emp.getName());
|
log.info("Found employee: empId={}, name={}", emp.getEmpId(), emp.getName());
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import com.srsystem.entity.enums.RoleCode;
|
|||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
public record LoginRequest(
|
public record LoginRequest(
|
||||||
@NotBlank(message = "로그인 ID 는 필수입니다")
|
@NotBlank(message = "이메일은 필수입니다")
|
||||||
String loginId,
|
String email,
|
||||||
@NotBlank(message = "비밀번호는 필수입니다")
|
@NotBlank(message = "비밀번호는 필수입니다")
|
||||||
String password
|
String password
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import java.util.Optional;
|
|||||||
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
|
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
|
||||||
@Query("SELECT e FROM Employee e WHERE e.loginId = :loginId")
|
@Query("SELECT e FROM Employee e WHERE e.loginId = :loginId")
|
||||||
Optional<Employee> findByLoginId(@Param("loginId") String loginId);
|
Optional<Employee> findByLoginId(@Param("loginId") String loginId);
|
||||||
|
Optional<Employee> findByEmail(String email);
|
||||||
List<Employee> findByNameContainingIgnoreCase(String name);
|
List<Employee> findByNameContainingIgnoreCase(String name);
|
||||||
Page<Employee> findByEmpStatus(EmpStatus status, Pageable pageable);
|
Page<Employee> findByEmpStatus(EmpStatus status, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,15 +27,15 @@ public class AuthService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public LoginResponse login(LoginRequest request) {
|
public LoginResponse login(LoginRequest request) {
|
||||||
Employee employee = employeeRepository.findByLoginId(request.loginId())
|
Employee employee = employeeRepository.findByEmail(request.email())
|
||||||
.orElseThrow(() -> new BusinessException(ErrorCode.INVALID_CREDENTIALS));
|
.orElseThrow(() -> new BusinessException(ErrorCode.INVALID_CREDENTIALS));
|
||||||
|
|
||||||
if (!passwordEncoder.matches(request.password(), employee.getPassword())) {
|
if (!passwordEncoder.matches(request.password(), employee.getPassword())) {
|
||||||
throw new BusinessException(ErrorCode.INVALID_CREDENTIALS);
|
throw new BusinessException(ErrorCode.INVALID_CREDENTIALS);
|
||||||
}
|
}
|
||||||
|
|
||||||
String accessToken = jwtTokenProvider.generateAccessToken(employee.getLoginId(), employee.getRoleCode().name());
|
String accessToken = jwtTokenProvider.generateAccessToken(employee.getEmail(), employee.getRoleCode().name());
|
||||||
String refreshToken = jwtTokenProvider.generateRefreshToken(employee.getLoginId());
|
String refreshToken = jwtTokenProvider.generateRefreshToken(employee.getEmail());
|
||||||
|
|
||||||
EmployeeSummary summary = employeeService.toSummary(employee);
|
EmployeeSummary summary = employeeService.toSummary(employee);
|
||||||
|
|
||||||
@@ -48,12 +48,12 @@ public class AuthService {
|
|||||||
throw new BusinessException(ErrorCode.INVALID_CREDENTIALS);
|
throw new BusinessException(ErrorCode.INVALID_CREDENTIALS);
|
||||||
}
|
}
|
||||||
|
|
||||||
String loginId = jwtTokenProvider.getLoginId(refreshToken);
|
String email = jwtTokenProvider.getLoginId(refreshToken);
|
||||||
Employee employee = employeeRepository.findByLoginId(loginId)
|
Employee employee = employeeRepository.findByEmail(email)
|
||||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||||
|
|
||||||
String newAccessToken = jwtTokenProvider.generateAccessToken(employee.getLoginId(), employee.getRoleCode().name());
|
String newAccessToken = jwtTokenProvider.generateAccessToken(employee.getEmail(), employee.getRoleCode().name());
|
||||||
String newRefreshToken = jwtTokenProvider.generateRefreshToken(employee.getLoginId());
|
String newRefreshToken = jwtTokenProvider.generateRefreshToken(employee.getEmail());
|
||||||
|
|
||||||
EmployeeSummary summary = employeeService.toSummary(employee);
|
EmployeeSummary summary = employeeService.toSummary(employee);
|
||||||
|
|
||||||
@@ -61,8 +61,8 @@ public class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Employee getEmployeeByLoginId(String loginId) {
|
public Employee getEmployeeByLoginId(String email) {
|
||||||
return employeeRepository.findByLoginId(loginId)
|
return employeeRepository.findByEmail(email)
|
||||||
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
.orElseThrow(() -> new BusinessException(ErrorCode.EMPLOYEE_NOT_FOUND));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,10 +76,9 @@ public class EmployeeService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Employee create(Employee employee) {
|
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);
|
throw new BusinessException(ErrorCode.DUPLICATE_LOGIN_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (employee.getRoleCode() == null) employee.setRoleCode(RoleCode.EMPLOYEE);
|
if (employee.getRoleCode() == null) employee.setRoleCode(RoleCode.EMPLOYEE);
|
||||||
if (employee.getEmpStatus() == null) employee.setEmpStatus(EmpStatus.ACTIVE);
|
if (employee.getEmpStatus() == null) employee.setEmpStatus(EmpStatus.ACTIVE);
|
||||||
if (employee.getDispatchStatus() == null) employee.setDispatchStatus(DispatchStatus.WAITING);
|
if (employee.getDispatchStatus() == null) employee.setDispatchStatus(DispatchStatus.WAITING);
|
||||||
|
|||||||
@@ -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 = '';
|
||||||
@@ -9,7 +9,7 @@ import type { LoginRequest } from '@/types';
|
|||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const login = useAuthStore((s) => s.login);
|
const login = useAuthStore((s) => s.login);
|
||||||
const [loginId, setLoginId] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [showPw, setShowPw] = useState(false);
|
const [showPw, setShowPw] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -17,14 +17,14 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!loginId.trim() || !password.trim()) {
|
if (!email.trim() || !password.trim()) {
|
||||||
setError('아이디와 비밀번호를 입력해주세요.');
|
setError('이메일과 비밀번호를 입력해주세요.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
try {
|
try {
|
||||||
await login({ loginId, password } as LoginRequest);
|
await login({ email, password } as LoginRequest);
|
||||||
router.push('/');
|
router.push('/');
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const msg = (err as { message?: string })?.message || '로그인에 실패했습니다.';
|
const msg = (err as { message?: string })?.message || '로그인에 실패했습니다.';
|
||||||
@@ -53,14 +53,14 @@ export default function LoginPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-text-main mb-1.5">아이디</label>
|
<label className="block text-sm font-medium text-text-main mb-1.5">이메일</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="email"
|
||||||
value={loginId}
|
value={email}
|
||||||
onChange={(e) => setLoginId(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
placeholder="아이디를 입력하세요"
|
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"
|
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"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export default function AccountsPage() {
|
|||||||
const handleCreate = async (e: React.FormEvent) => {
|
const handleCreate = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!createForm.loginId.trim() || !createForm.name.trim() || !createForm.password.trim()) {
|
if (!createForm.loginId.trim() || !createForm.name.trim() || !createForm.password.trim()) {
|
||||||
setCreateError('로그인 ID, 이름, 비밀번호는 필수입니다.');
|
setCreateError('사번, 이름, 비밀번호는 필수입니다.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
@@ -350,7 +350,7 @@ export default function AccountsPage() {
|
|||||||
{/* Basic Info */}
|
{/* Basic Info */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className={labelClass}>로그인 ID *</label>
|
<label className={labelClass}>사번 *</label>
|
||||||
<input value={createForm.loginId} onChange={(e) => setCreateForm((f) => ({ ...f, loginId: e.target.value }))} className={fieldClass} placeholder="login_id" />
|
<input value={createForm.loginId} onChange={(e) => setCreateForm((f) => ({ ...f, loginId: e.target.value }))} className={fieldClass} placeholder="login_id" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import apiClient from './apiClient';
|
|||||||
import type { ApiResponse, Employee, EmployeeSearchResult, Page, DispatchHistory, Project, EmergencyContact } from '@/types';
|
import type { ApiResponse, Employee, EmployeeSearchResult, Page, DispatchHistory, Project, EmergencyContact } from '@/types';
|
||||||
|
|
||||||
const employeeService = {
|
const employeeService = {
|
||||||
login(credentials: { loginId: string; password: string }) {
|
login(credentials: { email: string; password: string }) {
|
||||||
return apiClient.post<ApiResponse<{ accessToken: string; refreshToken: string; employee: Employee }>>('/auth/login', credentials);
|
return apiClient.post<ApiResponse<{ accessToken: string; refreshToken: string; employee: Employee }>>('/auth/login', credentials);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -341,7 +341,7 @@ export interface WakeResponse {
|
|||||||
// Auth
|
// Auth
|
||||||
// ============================================
|
// ============================================
|
||||||
export interface LoginRequest {
|
export interface LoginRequest {
|
||||||
loginId: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user