Skip to content

Commit

Permalink
feat: add logging for login
Browse files Browse the repository at this point in the history
  • Loading branch information
0lia committed Nov 9, 2023
1 parent fa49667 commit a7c367a
Show file tree
Hide file tree
Showing 11 changed files with 401 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import com.softserveinc.dokazovi.dto.payload.LoginRequest;
import com.softserveinc.dokazovi.dto.payload.RefreshToken;
import com.softserveinc.dokazovi.dto.payload.RefreshTokenRequest;
import com.softserveinc.dokazovi.entity.LogForLoginEntity;
import com.softserveinc.dokazovi.entity.UserEntity;
import com.softserveinc.dokazovi.entity.enumerations.UserStatus;
import com.softserveinc.dokazovi.exception.BadRequestException;
import com.softserveinc.dokazovi.exception.TokenRefreshException;
import com.softserveinc.dokazovi.security.TokenProvider;
import com.softserveinc.dokazovi.security.UserPrincipal;
import com.softserveinc.dokazovi.service.LogForLoginService;
import com.softserveinc.dokazovi.service.ProviderService;
import com.softserveinc.dokazovi.service.UserService;
import com.softserveinc.dokazovi.service.impl.MailSenderServiceImpl;
Expand All @@ -19,17 +21,23 @@
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

import java.sql.Timestamp;
import java.time.LocalDateTime;

import static com.softserveinc.dokazovi.controller.EndPoints.AUTH;
import static com.softserveinc.dokazovi.controller.EndPoints.AUTH_LOGIN;
import static com.softserveinc.dokazovi.controller.EndPoints.REFRESH_TOKEN;
Expand All @@ -48,6 +56,7 @@ public class AuthController {
private final UserService userService;
private final ProviderService providerService;
private final RefreshTokenService refreshTokenService;
private final LogForLoginService logForLoginService;

/**
* Authenticates user using email and password.
Expand All @@ -60,36 +69,56 @@ public class AuthController {
*/
@PostMapping(AUTH_LOGIN)
public ResponseEntity<AuthResponse> authenticateUser(@Valid @RequestBody LoginRequest loginRequest,
HttpServletResponse response) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getEmail(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
HttpServletResponse response, HttpServletRequest request) {
String status = null;
UserEntity userEntity = userService.findByEmail(loginRequest.getEmail());
if (!userEntity.getEnabled()) {
throw new BadRequestException("Please confirm your email!");
} else if (userEntity.getStatus() != UserStatus.ACTIVE) {
if (userEntity.getStatus() == UserStatus.DELETED) {
throw new BadRequestException("User is blocked!");
try {
if (userEntity == null) {
status = "User doesn't exist";
throw new BadCredentialsException("Bad credentials");
}
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getEmail(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
if (!userEntity.getEnabled()) {
throw new BadRequestException("Please confirm your email!");
} else if (userEntity.getStatus() != UserStatus.ACTIVE) {
if (userEntity.getStatus() == UserStatus.DELETED) {
throw new BadRequestException("User is blocked!");
} else {
throw new BadRequestException("Activate your account!");
}
} else {
throw new BadRequestException("Activate your account!");
RefreshToken refreshToken = refreshTokenService.createRefreshToken(userEntity.getId());
ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", refreshToken.getToken())
.httpOnly(true)
.secure(true)
.domain("dokazovi-fe-release.herokuapp.com")
.sameSite("none")
.build();
response.setHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString());
String token = tokenProvider.createToken(authentication);
AuthResponse authResponse = new AuthResponse(token, refreshToken.getToken());
authResponse.setAccessToken(token);
status = "Successful";
return ResponseEntity.ok(authResponse);
}
} else {
RefreshToken refreshToken = refreshTokenService.createRefreshToken(userEntity.getId());
ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", refreshToken.getToken())
.httpOnly(true)
.secure(true)
.domain("dokazovi-fe-release.herokuapp.com")
.sameSite("none")
.build();
response.setHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString());
String token = tokenProvider.createToken(authentication);
AuthResponse authResponse = new AuthResponse(token, refreshToken.getToken());
authResponse.setAccessToken(token);
return ResponseEntity.ok(authResponse);
} catch (AuthenticationException e) {
if (status == null) {
status = "Failed";
}
throw new BadCredentialsException(e.getMessage());
} finally {
logForLoginService.save(LogForLoginEntity.builder()
.login(loginRequest.getEmail())
.dateOfLogin(Timestamp.valueOf(LocalDateTime.now()))
.ip(request.getRemoteAddr())
.loginStatus(status)
.build());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public final class EndPoints {
public static final String LOG = "/log";
public static final String POST_LOGS = "/post-logs";
public static final String POST_LOG_BY_ID = "/{logId}";
public static final String LOG_FOR_LOGIN = "/log-for-login";

/**
* Method that adds slash after each endpoint while calling
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.softserveinc.dokazovi.controller;

import com.softserveinc.dokazovi.annotations.ApiPageable;
import com.softserveinc.dokazovi.entity.LogForLoginEntity;
import com.softserveinc.dokazovi.service.LogForLoginService;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.Authorization;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;

import static com.softserveinc.dokazovi.controller.EndPoints.LOG_FOR_LOGIN;

@RestController
@RequestMapping(LOG_FOR_LOGIN)
@RequiredArgsConstructor
public class LogForLoginController {
private final LogForLoginService logForLoginService;

@GetMapping()
@PreAuthorize("hasAuthority('EDIT_AUTHOR')")
@ApiPageable
@ApiOperation(value = "get all logs",
authorizations = {@Authorization(value = "Authorization")})
public ResponseEntity<Page<LogForLoginEntity>> getLogList(
@PageableDefault(size = 24, direction = Sort.Direction.DESC) Pageable pageable,
@ApiParam(value = "yyyy-MM-dd")
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@ApiParam(value = "yyyy-MM-dd")
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
@ApiParam(value = "Logs by status", type = "string")
@RequestParam(required = false, defaultValue = "") String status,
@ApiParam(value = "Logs by login", type = "string")
@RequestParam(required = false, defaultValue = "") String login,
@ApiParam(value = "Logs by ip", type = "string")
@RequestParam(required = false, defaultValue = "") String ip
) {
return ResponseEntity
.status(HttpStatus.OK)
.body(logForLoginService.findAllLogs(pageable, startDate, endDate, status, login, ip));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.softserveinc.dokazovi.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import java.sql.Timestamp;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity(name = "log_for_login_entity")
@Table(name = "log_for_login")
public class LogForLoginEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "log_id")
private Integer id;

@NotBlank
private String login;

@CreationTimestamp
private Timestamp dateOfLogin;

@NotBlank
private String ip;

@NotBlank
private String loginStatus;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.softserveinc.dokazovi.repositories;

import com.softserveinc.dokazovi.entity.LogForLoginEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.domain.Pageable;

import java.sql.Timestamp;

public interface LogForLoginRepository extends JpaRepository<LogForLoginEntity, Integer> {
void deleteLogForLoginEntitiesByDateOfLoginBefore(Timestamp minDate);

Page<LogForLoginEntity> findByDateOfLoginBetween(Pageable pageable, Timestamp start, Timestamp end);

Page<LogForLoginEntity> findByLoginStatusContainingIgnoreCase(Pageable pageable, String status);

Page<LogForLoginEntity> findByLoginContainingIgnoreCase(Pageable pageable, String login);

Page<LogForLoginEntity> findAllByIpContaining(Pageable pageable, String ip);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.softserveinc.dokazovi.service;

import com.softserveinc.dokazovi.entity.LogForLoginEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.time.LocalDate;

public interface LogForLoginService {
LogForLoginEntity save(LogForLoginEntity log);

Page<LogForLoginEntity> findAllLogs(Pageable pageable, LocalDate start, LocalDate end,
String status, String login, String ip);

void deleteOutdatedLogs();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.softserveinc.dokazovi.service.impl;

import com.softserveinc.dokazovi.entity.LogForLoginEntity;
import com.softserveinc.dokazovi.repositories.LogForLoginRepository;
import com.softserveinc.dokazovi.service.LogForLoginService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class LogForLoginServiceImpl implements LogForLoginService {

private final LogForLoginRepository logForLoginRepository;

@Override
public LogForLoginEntity save(LogForLoginEntity log) {
return logForLoginRepository.save(log);
}

@Override
public Page<LogForLoginEntity> findAllLogs(Pageable pageable, LocalDate startDate, LocalDate endDate,
String status, String login, String ip) {
if (startDate != null || endDate != null) {
LocalDate startLocalDate = Optional.ofNullable(startDate).orElse(LocalDate.EPOCH);
LocalDate endLocalDate = Optional.ofNullable(endDate).orElse(LocalDate.now());
Timestamp startDateTimestamp = Timestamp.valueOf(startLocalDate.atStartOfDay());
Timestamp endDateTimestamp = Timestamp.valueOf(endLocalDate.atTime(LocalTime.MAX));
return logForLoginRepository.findByDateOfLoginBetween(pageable, startDateTimestamp, endDateTimestamp);
}
if (status != null && status.trim().length() > 0) {
return logForLoginRepository.findByLoginStatusContainingIgnoreCase(pageable, status);
}
if (login != null && login.trim().length() > 0) {
return logForLoginRepository.findByLoginContainingIgnoreCase(pageable, login);
}
if (ip != null && ip.trim().length() > 0) {
return logForLoginRepository.findAllByIpContaining(pageable, ip);
}
return logForLoginRepository.findAll(pageable);
}

@Override
@Transactional
@Scheduled(cron = "0 0 12 * * ?")
public void deleteOutdatedLogs() {
logForLoginRepository.deleteLogForLoginEntitiesByDateOfLoginBefore(
Timestamp.valueOf(LocalDateTime.now().minusMonths(3))
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE LOG_FOR_LOGIN
(
LOG_ID serial not null primary key,
LOGIN VARCHAR,
DATE_OF_LOGIN TIMESTAMP DEFAULT NOW(),
IP VARCHAR,
LOGIN_STATUS VARCHAR
);
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import com.softserveinc.dokazovi.dto.payload.LoginRequest;
import com.softserveinc.dokazovi.dto.payload.RefreshToken;
import com.softserveinc.dokazovi.dto.payload.RefreshTokenRequest;
import com.softserveinc.dokazovi.entity.LogForLoginEntity;
import com.softserveinc.dokazovi.entity.UserEntity;
import com.softserveinc.dokazovi.entity.enumerations.UserStatus;
import com.softserveinc.dokazovi.security.RefreshTokenService;
import com.softserveinc.dokazovi.security.TokenProvider;
import com.softserveinc.dokazovi.security.UserPrincipal;
import com.softserveinc.dokazovi.service.LogForLoginService;
import com.softserveinc.dokazovi.service.ProviderService;
import com.softserveinc.dokazovi.service.UserService;
import com.softserveinc.dokazovi.service.impl.MailSenderServiceImpl;
Expand Down Expand Up @@ -60,6 +62,8 @@ class AuthControllerTest {
private ProviderService providerService;
@Mock
private UserService userService;
@Mock
private LogForLoginService logForLoginService;
@InjectMocks
private AuthController authController;

Expand Down Expand Up @@ -99,6 +103,7 @@ void loginUser() throws Exception {
when(tokenProvider.createToken(any(Authentication.class))).thenReturn(token);
when(userService.findByEmail(anyString())).thenReturn(user);
when(refreshTokenService.createRefreshToken(anyInt())).thenReturn(refreshToken);
when(logForLoginService.save(any(LogForLoginEntity.class))).thenReturn(LogForLoginEntity.builder().build());
String uri = AUTH + AUTH_LOGIN;
mockMvc.perform(MockMvcRequestBuilders.post(uri)
.content(asJsonString(loginRequest))
Expand Down
Loading

0 comments on commit a7c367a

Please sign in to comment.