Added userLogin tracking
- Added new table for login tracking - Added entity - Added logic for login tracking
This commit is contained in:
parent
4977dbe98a
commit
d50d6de466
@ -105,6 +105,19 @@ public class RethinkDBService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rethinkdb check if table user_logins exists
|
||||||
|
try {
|
||||||
|
r.db(config.getDatabase()).tableCreate("user_logins").run(connection).stream();
|
||||||
|
log.debug("Table 'user_logins' created successfully.");
|
||||||
|
} catch (ReqlOpFailedError e) {
|
||||||
|
log.debug("Table 'user_logins' already exists.");
|
||||||
|
if(autoResetOnStartup) {
|
||||||
|
log.debug("Clearing content...");
|
||||||
|
r.db(config.getDatabase()).table("user_logins").delete().run(connection);
|
||||||
|
log.debug("Table 'user_logins' cleared successfully.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initializeAdminUser();
|
initializeAdminUser();
|
||||||
|
|
||||||
log.info("Database ready for operation!");
|
log.info("Database ready for operation!");
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package de.w665.sharepulse.db.repo;
|
||||||
|
|
||||||
|
import com.rethinkdb.RethinkDB;
|
||||||
|
import com.rethinkdb.net.Connection;
|
||||||
|
import de.w665.sharepulse.db.RethinkDBConfig;
|
||||||
|
import de.w665.sharepulse.db.RethinkDBConnector;
|
||||||
|
import de.w665.sharepulse.model.UserLogin;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public class UserLoginRepository {
|
||||||
|
private final RethinkDB r;
|
||||||
|
private final Connection connection;
|
||||||
|
private final RethinkDBConfig config;
|
||||||
|
private final String TABLE_NAME = "user_logins";
|
||||||
|
@Autowired
|
||||||
|
public UserLoginRepository(RethinkDBConnector connector, RethinkDBConfig config) {
|
||||||
|
this.r = connector.getR();
|
||||||
|
this.connection = connector.getConnection();
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertUserLogin(UserLogin userLogin) {
|
||||||
|
r.db(config.getDatabase()).table(TABLE_NAME).insert(userLogin).run(connection);
|
||||||
|
}
|
||||||
|
}
|
@ -49,6 +49,7 @@ public class UserRepository {
|
|||||||
|
|
||||||
// If username is changed, this method must be used. Else the user will not be found
|
// If username is changed, this method must be used. Else the user will not be found
|
||||||
public void updateUser(User user, String originalUsername) {
|
public void updateUser(User user, String originalUsername) {
|
||||||
|
// TODO: Refactor this to use the userID instead of the username
|
||||||
r.db(config.getDatabase()).table("users")
|
r.db(config.getDatabase()).table("users")
|
||||||
.filter(r.hashMap("username", originalUsername))
|
.filter(r.hashMap("username", originalUsername))
|
||||||
.update(user)
|
.update(user)
|
||||||
|
@ -13,6 +13,7 @@ import java.util.Date;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class User {
|
public class User {
|
||||||
|
private String id; // ID is auto mapped by RethinkDB
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
private String email;
|
private String email;
|
||||||
|
18
src/main/java/de/w665/sharepulse/model/UserLogin.java
Normal file
18
src/main/java/de/w665/sharepulse/model/UserLogin.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package de.w665.sharepulse.model;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UserLogin {
|
||||||
|
String userId;
|
||||||
|
Date loginTime;
|
||||||
|
String loginIp;
|
||||||
|
}
|
@ -2,6 +2,7 @@ package de.w665.sharepulse.rest.mappings;
|
|||||||
|
|
||||||
import de.w665.sharepulse.rest.ro.AuthenticationRequest;
|
import de.w665.sharepulse.rest.ro.AuthenticationRequest;
|
||||||
import de.w665.sharepulse.service.AuthenticationService;
|
import de.w665.sharepulse.service.AuthenticationService;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -24,9 +25,9 @@ public class AuthenticationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public ResponseEntity<Object> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) {
|
public ResponseEntity<Object> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest, HttpServletRequest request) {
|
||||||
log.debug("Received AuthenticationRequest for username: " + authenticationRequest.getUsername());
|
log.debug("Received AuthenticationRequest for username: " + authenticationRequest.getUsername());
|
||||||
String token = authenticationService.authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
|
String token = authenticationService.authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword(), request.getRemoteAddr());
|
||||||
|
|
||||||
Map<String, Object> response = new HashMap<>();
|
Map<String, Object> response = new HashMap<>();
|
||||||
response.put("token", token);
|
response.put("token", token);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package de.w665.sharepulse.service;
|
package de.w665.sharepulse.service;
|
||||||
|
|
||||||
|
import de.w665.sharepulse.db.repo.UserLoginRepository;
|
||||||
import de.w665.sharepulse.db.repo.UserRepository;
|
import de.w665.sharepulse.db.repo.UserRepository;
|
||||||
import de.w665.sharepulse.model.User;
|
import de.w665.sharepulse.model.User;
|
||||||
|
import de.w665.sharepulse.model.UserLogin;
|
||||||
import io.jsonwebtoken.*;
|
import io.jsonwebtoken.*;
|
||||||
import io.jsonwebtoken.security.Keys;
|
import io.jsonwebtoken.security.Keys;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
@ -20,6 +22,7 @@ import java.util.Optional;
|
|||||||
public class AuthenticationService {
|
public class AuthenticationService {
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
private final UserLoginRepository userLoginRepository;
|
||||||
|
|
||||||
@Value("${secureapi.jwt.secret}")
|
@Value("${secureapi.jwt.secret}")
|
||||||
private String secretString;
|
private String secretString;
|
||||||
@ -29,8 +32,9 @@ public class AuthenticationService {
|
|||||||
|
|
||||||
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||||
|
|
||||||
public AuthenticationService(UserRepository userRepository) {
|
public AuthenticationService(UserRepository userRepository, UserLoginRepository userLoginRepository) {
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
|
this.userLoginRepository = userLoginRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
@ -40,14 +44,16 @@ public class AuthenticationService {
|
|||||||
this.secretKey = Keys.hmacShaKeyFor(encodedKey);
|
this.secretKey = Keys.hmacShaKeyFor(encodedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String authenticate(String username, String password, long... expirationTime/*FOR TESTING VALIDITY*/) {
|
public String authenticate(String username, String password, String remoteAddr, long... expirationTime/*FOR TESTING VALIDITY*/) {
|
||||||
if(expirationTime.length > 0) {
|
if(expirationTime.length > 0) {
|
||||||
this.expirationTime = expirationTime[0];
|
this.expirationTime = expirationTime[0];
|
||||||
}
|
}
|
||||||
Optional<User> user = userRepository.retrieveUserByUsername(username);
|
Optional<User> optionalUser = userRepository.retrieveUserByUsername(username);
|
||||||
if (user.isPresent() && passwordEncoder.matches(password, user.get().getPassword())) {
|
if (optionalUser.isPresent() && passwordEncoder.matches(password, optionalUser.get().getPassword())) {
|
||||||
userRepository.updateLastLoginForUser(user.get().getUsername(), new Date());
|
User user = optionalUser.get();
|
||||||
return generateToken(user.get());
|
userLoginRepository.insertUserLogin(new UserLogin(user.getId(), new Date(), remoteAddr));
|
||||||
|
userRepository.updateLastLoginForUser(user.getUsername(), new Date());
|
||||||
|
return generateToken(user);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,13 @@ public class AuthenticationServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenValidUsernameAndPassword_thenAuthenticateShouldReturnToken() {
|
public void whenValidUsernameAndPassword_thenAuthenticateShouldReturnToken() {
|
||||||
String token = authenticationService.authenticate(username, password);
|
String token = authenticationService.authenticate(username, password, "");
|
||||||
assertNotNull(token, "Token should not be null for valid credentials");
|
assertNotNull(token, "Token should not be null for valid credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void whenValidToken_thenValidateTokenShouldReturnTrue() {
|
public void whenValidToken_thenValidateTokenShouldReturnTrue() {
|
||||||
String token = authenticationService.authenticate(username, password);
|
String token = authenticationService.authenticate(username, password, "");
|
||||||
assertTrue(authenticationService.validateToken(token), "Token validation should return true for a valid token");
|
assertTrue(authenticationService.validateToken(token), "Token validation should return true for a valid token");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ public class AuthenticationServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void whenTokenIsExplicitlyExpired_thenValidateTokenShouldReturnFalse() throws InterruptedException {
|
public void whenTokenIsExplicitlyExpired_thenValidateTokenShouldReturnFalse() throws InterruptedException {
|
||||||
long testExpirationTime = 1; // 1 millisecond
|
long testExpirationTime = 1; // 1 millisecond
|
||||||
String token = authenticationService.authenticate("testUser", "testPass", testExpirationTime);
|
String token = authenticationService.authenticate("testUser", "testPass", "", testExpirationTime);
|
||||||
assertNotNull(token, "Token should not be null");
|
assertNotNull(token, "Token should not be null");
|
||||||
Thread.sleep(2); // Wait for 2 milliseconds to ensure the token has expired (Bad practice but easy)
|
Thread.sleep(2); // Wait for 2 milliseconds to ensure the token has expired (Bad practice but easy)
|
||||||
assertFalse(authenticationService.validateToken(token), "Expired token should not be valid");
|
assertFalse(authenticationService.validateToken(token), "Expired token should not be valid");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user