diff --git a/src/main/java/de/w665/sharepulse/db/RethinkDBService.java b/src/main/java/de/w665/sharepulse/db/RethinkDBService.java index da1a8d0..321af66 100644 --- a/src/main/java/de/w665/sharepulse/db/RethinkDBService.java +++ b/src/main/java/de/w665/sharepulse/db/RethinkDBService.java @@ -3,33 +3,43 @@ package de.w665.sharepulse.db; import com.rethinkdb.RethinkDB; import com.rethinkdb.gen.exc.ReqlOpFailedError; 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.PreDestroy; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; +import java.util.Optional; @Slf4j @Service public class RethinkDBService { private final RethinkDBConfig config; - private final RethinkDB r; private final Connection connection; + private final UserRepository userRepository; @Value("${sharepulse.auto-reset-on-startup}") 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 - public RethinkDBService(RethinkDBConfig config, RethinkDBConnector connector) { + public RethinkDBService(RethinkDBConfig config, RethinkDBConnector connector, UserRepository userRepository) { this.config = config; - // mapping to private vars for easier access this.r = connector.getR(); this.connection = connector.getConnection(); + this.userRepository = userRepository; } @PostConstruct @@ -81,9 +91,37 @@ public class RethinkDBService { 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!"); } + private void initializeAdminUser() { + Optional 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 public void close() { if (connection != null) { diff --git a/src/main/java/de/w665/sharepulse/db/repo/UserRepository.java b/src/main/java/de/w665/sharepulse/db/repo/UserRepository.java index d7ec26b..9113567 100644 --- a/src/main/java/de/w665/sharepulse/db/repo/UserRepository.java +++ b/src/main/java/de/w665/sharepulse/db/repo/UserRepository.java @@ -1,4 +1,41 @@ 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 { + 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 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); + } } diff --git a/src/main/java/de/w665/sharepulse/rest/mappings/AuthenticationController.java b/src/main/java/de/w665/sharepulse/rest/mappings/AuthenticationController.java index 9e5d1c4..3b7d31d 100644 --- a/src/main/java/de/w665/sharepulse/rest/mappings/AuthenticationController.java +++ b/src/main/java/de/w665/sharepulse/rest/mappings/AuthenticationController.java @@ -1,10 +1,12 @@ package de.w665.sharepulse.rest.mappings; +import de.w665.sharepulse.rest.ro.AuthenticationRequest; import de.w665.sharepulse.service.AuthenticationService; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; 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 java.util.HashMap; @@ -22,11 +24,13 @@ public class AuthenticationController { } @PostMapping("/login") - public ResponseEntity createAuthenticationToken() { - String token = authenticationService.authenticate("test", "test"); + public ResponseEntity createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) { + log.debug("Received AuthenticationRequest for username: " + authenticationRequest.getUsername()); + String token = authenticationService.authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); Map response = new HashMap<>(); response.put("token", token); + response.put("success", token != null); return new ResponseEntity<>(response, HttpStatus.OK); } diff --git a/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java b/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java index 6dc208f..d2f9bb7 100644 --- a/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java +++ b/src/main/java/de/w665/sharepulse/rest/mappings/Upload.java @@ -35,7 +35,7 @@ public class Upload extends ApiRestController { @RequestParam(value = "singleDownload", defaultValue = "false") boolean singleDownload, @RequestParam(value = "fileDescription", required = false) String fileDescription) { - // TODO: Handle shortStorage0 + // TODO: Handle shortStorage if (file.isEmpty()) { 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); } - 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 response = new HashMap<>(); response.put("fileId", fileUpload.getFileId()); diff --git a/src/main/java/de/w665/sharepulse/rest/ro/AuthenticationRequest.java b/src/main/java/de/w665/sharepulse/rest/ro/AuthenticationRequest.java new file mode 100644 index 0000000..4a77ff4 --- /dev/null +++ b/src/main/java/de/w665/sharepulse/rest/ro/AuthenticationRequest.java @@ -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; +} diff --git a/src/main/java/de/w665/sharepulse/service/AuthenticationService.java b/src/main/java/de/w665/sharepulse/service/AuthenticationService.java index 2107c10..63ebabc 100644 --- a/src/main/java/de/w665/sharepulse/service/AuthenticationService.java +++ b/src/main/java/de/w665/sharepulse/service/AuthenticationService.java @@ -1,39 +1,51 @@ 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.security.Keys; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import javax.crypto.SecretKey; import java.util.Base64; import java.util.Date; +import java.util.Optional; @Slf4j @Service public class AuthenticationService { + + private final UserRepository userRepository; + @Value("${secureapi.jwt.secret}") private String secretString; - @Value("${secureapi.jwt.expiration}") private long expirationTime; // in milliseconds - private SecretKey secretKey; + private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + public AuthenticationService(UserRepository userRepository) { + this.userRepository = userRepository; + } + @PostConstruct 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 this.secretKey = Keys.hmacShaKeyFor(encodedKey); } public String authenticate(String username, String password) { - // validate user credentials with repository and password hash algorithm - - return generateToken(username); - // throw new RuntimeException("User authentication failed"); + Optional user = userRepository.retrieveUserByUsername(username); + if (user.isPresent() && passwordEncoder.matches(password, user.get().getPassword())) { + return generateToken(username); + } + return null; } private String generateToken(String username) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 08636ac..61997d9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,6 +5,9 @@ sharepulse.fileid.length=6 sharepulse.fileid.charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 sharepulse.filepassword.length=6 sharepulse.filepassword.charset=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 +sharepulse.management.user.username=admin +sharepulse.management.user.password=admin + # Database rethinkdb.host=localhost