- Added basic JWT auth logic
- Added user database management - Added password hashing - Added user config
This commit is contained in:
parent
a93288e31c
commit
4dd5093abc
@ -3,33 +3,43 @@ package de.w665.sharepulse.db;
|
|||||||
import com.rethinkdb.RethinkDB;
|
import com.rethinkdb.RethinkDB;
|
||||||
import com.rethinkdb.gen.exc.ReqlOpFailedError;
|
import com.rethinkdb.gen.exc.ReqlOpFailedError;
|
||||||
import com.rethinkdb.net.Connection;
|
import com.rethinkdb.net.Connection;
|
||||||
|
import de.w665.sharepulse.db.repo.UserRepository;
|
||||||
|
import de.w665.sharepulse.model.User;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.annotation.PreDestroy;
|
import jakarta.annotation.PreDestroy;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class RethinkDBService {
|
public class RethinkDBService {
|
||||||
|
|
||||||
private final RethinkDBConfig config;
|
private final RethinkDBConfig config;
|
||||||
|
|
||||||
private final RethinkDB r;
|
private final RethinkDB r;
|
||||||
private final Connection connection;
|
private final Connection connection;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
@Value("${sharepulse.auto-reset-on-startup}")
|
@Value("${sharepulse.auto-reset-on-startup}")
|
||||||
private boolean autoResetOnStartup;
|
private boolean autoResetOnStartup;
|
||||||
|
@Value("${sharepulse.management.user.username}")
|
||||||
|
private String defaultUsername;
|
||||||
|
@Value("${sharepulse.management.user.password}")
|
||||||
|
private String defaultPassword;
|
||||||
|
|
||||||
|
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public RethinkDBService(RethinkDBConfig config, RethinkDBConnector connector) {
|
public RethinkDBService(RethinkDBConfig config, RethinkDBConnector connector, UserRepository userRepository) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
|
|
||||||
// mapping to private vars for easier access
|
// mapping to private vars for easier access
|
||||||
this.r = connector.getR();
|
this.r = connector.getR();
|
||||||
this.connection = connector.getConnection();
|
this.connection = connector.getConnection();
|
||||||
|
this.userRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
@ -81,9 +91,37 @@ public class RethinkDBService {
|
|||||||
log.debug("Table 'expired_file_uploads' cleared successfully.");
|
log.debug("Table 'expired_file_uploads' cleared successfully.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rethinkdb check if table users exists
|
||||||
|
try {
|
||||||
|
r.db(config.getDatabase()).tableCreate("users").run(connection).stream();
|
||||||
|
log.debug("Table 'users' created successfully.");
|
||||||
|
} catch (ReqlOpFailedError e) {
|
||||||
|
log.debug("Table 'users' already exists.");
|
||||||
|
if(autoResetOnStartup) {
|
||||||
|
log.debug("Clearing content...");
|
||||||
|
r.db(config.getDatabase()).table("users").delete().run(connection);
|
||||||
|
log.debug("Table 'users' cleared successfully.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeAdminUser();
|
||||||
|
|
||||||
log.info("Database ready for operation!");
|
log.info("Database ready for operation!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initializeAdminUser() {
|
||||||
|
Optional<User> adminUser = userRepository.retrieveUserByUsername("admin");
|
||||||
|
if(adminUser.isEmpty()) {
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(defaultUsername);
|
||||||
|
user.setPassword(passwordEncoder.encode(defaultPassword));
|
||||||
|
user.setRole("admin");
|
||||||
|
userRepository.insertUser(user);
|
||||||
|
log.debug("Admin user created with default credentials. Username: admin, Password: admin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
public void close() {
|
public void close() {
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
|
@ -1,4 +1,41 @@
|
|||||||
package de.w665.sharepulse.db.repo;
|
package de.w665.sharepulse.db.repo;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
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.User;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Repository
|
||||||
public class UserRepository {
|
public class UserRepository {
|
||||||
|
private final RethinkDB r;
|
||||||
|
private final Connection connection;
|
||||||
|
private final RethinkDBConfig config;
|
||||||
|
@Autowired
|
||||||
|
public UserRepository(RethinkDBConnector connector, RethinkDBConfig config) {
|
||||||
|
this.r = connector.getR();
|
||||||
|
this.connection = connector.getConnection();
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<User> retrieveUserByUsername(String username) {
|
||||||
|
try {
|
||||||
|
User user = r.db(config.getDatabase()).table("users")
|
||||||
|
.filter(r.hashMap("username", username))
|
||||||
|
.run(connection, User.class)
|
||||||
|
.next();
|
||||||
|
return Optional.ofNullable(user);
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertUser(User user) {
|
||||||
|
r.db(config.getDatabase()).table("users").insert(user).run(connection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package de.w665.sharepulse.rest.mappings;
|
package de.w665.sharepulse.rest.mappings;
|
||||||
|
|
||||||
|
import de.w665.sharepulse.rest.ro.AuthenticationRequest;
|
||||||
import de.w665.sharepulse.service.AuthenticationService;
|
import de.w665.sharepulse.service.AuthenticationService;
|
||||||
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;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
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.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -22,11 +24,13 @@ public class AuthenticationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public ResponseEntity<Object> createAuthenticationToken() {
|
public ResponseEntity<Object> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) {
|
||||||
String token = authenticationService.authenticate("test", "test");
|
log.debug("Received AuthenticationRequest for username: " + authenticationRequest.getUsername());
|
||||||
|
String token = authenticationService.authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
|
||||||
|
|
||||||
Map<String, Object> response = new HashMap<>();
|
Map<String, Object> response = new HashMap<>();
|
||||||
response.put("token", token);
|
response.put("token", token);
|
||||||
|
response.put("success", token != null);
|
||||||
|
|
||||||
return new ResponseEntity<>(response, HttpStatus.OK);
|
return new ResponseEntity<>(response, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ public class Upload extends ApiRestController {
|
|||||||
@RequestParam(value = "singleDownload", defaultValue = "false") boolean singleDownload,
|
@RequestParam(value = "singleDownload", defaultValue = "false") boolean singleDownload,
|
||||||
@RequestParam(value = "fileDescription", required = false) String fileDescription) {
|
@RequestParam(value = "fileDescription", required = false) String fileDescription) {
|
||||||
|
|
||||||
// TODO: Handle shortStorage0
|
// TODO: Handle shortStorage
|
||||||
|
|
||||||
if (file.isEmpty()) {
|
if (file.isEmpty()) {
|
||||||
log.debug("User tried to upload an empty file. IP: " + request.getRemoteAddr());
|
log.debug("User tried to upload an empty file. IP: " + request.getRemoteAddr());
|
||||||
@ -53,7 +53,7 @@ public class Upload extends ApiRestController {
|
|||||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("User uploaded file " + file.getOriginalFilename() + " from IP " + request.getRemoteAddr() + " successfully.");
|
log.info("User uploaded file " + file.getOriginalFilename() + " from IP " + request.getRemoteAddr() + " successfully.");
|
||||||
|
|
||||||
Map<String, Object> response = new HashMap<>();
|
Map<String, Object> response = new HashMap<>();
|
||||||
response.put("fileId", fileUpload.getFileId());
|
response.put("fileId", fileUpload.getFileId());
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package de.w665.sharepulse.rest.ro;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class AuthenticationRequest {
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
}
|
@ -1,39 +1,51 @@
|
|||||||
package de.w665.sharepulse.service;
|
package de.w665.sharepulse.service;
|
||||||
|
|
||||||
|
import de.w665.sharepulse.db.repo.UserRepository;
|
||||||
|
import de.w665.sharepulse.model.User;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import io.jsonwebtoken.security.Keys;
|
import io.jsonwebtoken.security.Keys;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class AuthenticationService {
|
public class AuthenticationService {
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
@Value("${secureapi.jwt.secret}")
|
@Value("${secureapi.jwt.secret}")
|
||||||
private String secretString;
|
private String secretString;
|
||||||
|
|
||||||
@Value("${secureapi.jwt.expiration}")
|
@Value("${secureapi.jwt.expiration}")
|
||||||
private long expirationTime; // in milliseconds
|
private long expirationTime; // in milliseconds
|
||||||
|
|
||||||
private SecretKey secretKey;
|
private SecretKey secretKey;
|
||||||
|
|
||||||
|
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||||
|
|
||||||
|
public AuthenticationService(UserRepository userRepository) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
System.out.println("Initializing secret key");
|
log.debug("Initializing secret key");
|
||||||
byte[] encodedKey = Base64.getEncoder().encode(secretString.getBytes()); // encode the secret key
|
byte[] encodedKey = Base64.getEncoder().encode(secretString.getBytes()); // encode the secret key
|
||||||
this.secretKey = Keys.hmacShaKeyFor(encodedKey);
|
this.secretKey = Keys.hmacShaKeyFor(encodedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String authenticate(String username, String password) {
|
public String authenticate(String username, String password) {
|
||||||
// validate user credentials with repository and password hash algorithm
|
Optional<User> user = userRepository.retrieveUserByUsername(username);
|
||||||
|
if (user.isPresent() && passwordEncoder.matches(password, user.get().getPassword())) {
|
||||||
return generateToken(username);
|
return generateToken(username);
|
||||||
// throw new RuntimeException("User authentication failed");
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateToken(String username) {
|
private String generateToken(String username) {
|
||||||
|
@ -5,6 +5,9 @@ sharepulse.fileid.length=6
|
|||||||
sharepulse.fileid.charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
|
sharepulse.fileid.charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
|
||||||
sharepulse.filepassword.length=6
|
sharepulse.filepassword.length=6
|
||||||
sharepulse.filepassword.charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
|
sharepulse.filepassword.charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
|
||||||
|
sharepulse.management.user.username=admin
|
||||||
|
sharepulse.management.user.password=admin
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
rethinkdb.host=localhost
|
rethinkdb.host=localhost
|
||||||
|
Loading…
x
Reference in New Issue
Block a user