package com.smartoffice.auth; import com.smartoffice.common.ApiException; import com.smartoffice.user.Role; import com.smartoffice.user.User; import com.smartoffice.user.UserDto; import com.smartoffice.user.UserRepository; import com.smartoffice.user.UserStatus; import jakarta.transaction.Transactional; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.time.Instant; @Service public class AuthService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final AuthenticationManager authenticationManager; private final JwtService jwtService; private final int maxAttempts; private final int lockMinutes; public AuthService(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthenticationManager authenticationManager, JwtService jwtService, @Value("${app.security.lockout.max-attempts}") int maxAttempts, @Value("${app.security.lockout.lock-minutes}") int lockMinutes) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; this.authenticationManager = authenticationManager; this.jwtService = jwtService; this.maxAttempts = maxAttempts; this.lockMinutes = lockMinutes; } @Transactional public AuthResponse login(LoginRequest request) { User user = userRepository.findByUsername(request.getUsername()) .orElseThrow(() -> new ApiException(401, "Invalid credentials")); if (user.getStatus() == UserStatus.DISABLED) { throw new ApiException(403, "Account disabled"); } if (user.getLockedUntil() != null && user.getLockedUntil().isAfter(Instant.now())) { throw new ApiException(423, "Account locked until " + user.getLockedUntil()); } if (user.getStatus() == UserStatus.LOCKED && user.getLockedUntil() == null) { throw new ApiException(423, "Account locked"); } if (user.getLockedUntil() != null && user.getLockedUntil().isBefore(Instant.now())) { user.setLockedUntil(null); user.setStatus(UserStatus.ACTIVE); user.setFailedLoginAttempts(0); userRepository.save(user); } try { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())); UserPrincipal principal = (UserPrincipal) authentication.getPrincipal(); user.setFailedLoginAttempts(0); user.setLockedUntil(null); user.setLastLoginAt(Instant.now()); user.setStatus(UserStatus.ACTIVE); userRepository.save(user); String token = jwtService.generateToken(principal); return new AuthResponse(token, UserDto.from(user)); } catch (Exception ex) { int attempts = user.getFailedLoginAttempts() + 1; user.setFailedLoginAttempts(attempts); if (attempts >= maxAttempts) { user.setLockedUntil(Instant.now().plusSeconds(lockMinutes * 60L)); user.setStatus(UserStatus.LOCKED); } userRepository.save(user); throw new ApiException(401, "Invalid credentials"); } } @Transactional public UserDto register(RegisterRequest request) { if (userRepository.existsByUsername(request.getUsername())) { throw new ApiException(409, "Username already exists"); } User user = new User(); user.setUsername(request.getUsername()); user.setPasswordHash(passwordEncoder.encode(request.getPassword())); user.setFullName(request.getFullName()); user.setEmail(request.getEmail()); user.setPhone(request.getPhone()); user.setRole(Role.EMPLOYEE); user.setStatus(UserStatus.ACTIVE); userRepository.save(user); return UserDto.from(user); } }