This commit is contained in:
Anthony Stirling
2023-08-12 02:29:10 +01:00
parent 83ba1899b7
commit 6f325b5fdb
17 changed files with 718 additions and 3 deletions

View File

@@ -10,11 +10,13 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import jakarta.annotation.PostConstruct;
import stirling.software.SPDF.utils.GeneralUtils;
@SpringBootApplication
@EnableWebSecurity(debug = true)
//@EnableScheduling
public class SPdfApplication {

View File

@@ -5,6 +5,17 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(name = "loginEnabled")
public boolean loginEnabled() {
String appName = System.getProperty("login.enabled");
if (appName == null)
appName = System.getenv("login.enabled");
return (appName != null) ? Boolean.valueOf(appName) : false;
}
@Bean(name = "appName")
public String appName() {
String appName = System.getProperty("APP_HOME_NAME");

View File

@@ -24,8 +24,9 @@ import org.springframework.web.servlet.ModelAndView;
public class CleanUrlInterceptor implements HandlerInterceptor {
private static final List<String> ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints");
private static final List<String> ALLOWED_PARAMS = Arrays.asList("lang", "endpoint", "endpoints", "logout", "error");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
@@ -39,9 +40,12 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
String[] queryParameters = queryString.split("&");
for (String param : queryParameters) {
String[] keyValue = param.split("=");
System.out.print("astirli " + keyValue[0]);
if (keyValue.length != 2) {
continue;
}
System.out.print("astirli2 " + keyValue[0]);
if (ALLOWED_PARAMS.contains(keyValue[0])) {
parameters.put(keyValue[0], keyValue[1]);
}

View File

@@ -0,0 +1,28 @@
package stirling.software.SPDF.controller.api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import stirling.software.SPDF.config.security.UserService;
@Controller
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public String register(@RequestParam String username, @RequestParam String password, Model model) {
if(userService.usernameExists(username)) {
model.addAttribute("error", "Username already exists");
return "register";
}
userService.saveUser(username, password);
return "redirect:/login?registered=true";
}
}

View File

@@ -16,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@@ -24,10 +25,30 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
@Controller
@Tag(name = "General", description = "General APIs")
public class GeneralWebController {
@GetMapping("/login")
public String login(HttpServletRequest request, Model model, Authentication authentication) {
if (authentication != null && authentication.isAuthenticated()) {
return "redirect:/";
}
if (request.getParameter("error") != null) {
model.addAttribute("error", request.getParameter("error"));
}
if (request.getParameter("logout") != null) {
model.addAttribute("logoutMessage", "You have been logged out.");
}
return "login";
}
@GetMapping("/pipeline")
@Hidden
public String pipelineForm(Model model) {

View File

@@ -0,0 +1,52 @@
package stirling.software.SPDF.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Entity
@Table(name = "authorities")
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "authority")
private String authority;
@ManyToOne
@JoinColumn(name = "username")
private User user;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAuthority() {
return authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}

View File

@@ -0,0 +1,62 @@
package stirling.software.SPDF.model;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
@Entity
@Table(name = "users")
public class User {
@Id
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Column(name = "enabled")
private boolean enabled;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
private Set<Authority> authorities;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Set<Authority> getAuthorities() {
return authorities;
}
public void setAuthorities(Set<Authority> authorities) {
this.authorities = authorities;
}
}

View File

@@ -0,0 +1,12 @@
package stirling.software.SPDF.repository;
import java.util.Set;
import org.springframework.data.jpa.repository.JpaRepository;
import stirling.software.SPDF.model.Authority;
public interface AuthorityRepository extends JpaRepository<Authority, Long> {
//Set<Authority> findByUsername(String username);
Set<Authority> findByUser_Username(String username);
}

View File

@@ -0,0 +1,12 @@
package stirling.software.SPDF.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import stirling.software.SPDF.model.User;
public interface UserRepository extends JpaRepository<User, String> {
Optional<User> findByUsername(String username);
}

View File

@@ -16,8 +16,11 @@ server.error.include-stacktrace=always
server.error.include-exception=true
server.error.include-message=always
#logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework=DEBUG
logging.level.org.springframework.security=DEBUG
login.enabled=true
server.servlet.session.tracking-modes=cookie
server.servlet.context-path=${APP_ROOT_PATH:/}
@@ -32,4 +35,12 @@ spring.mvc.async.request-timeout=${ASYNC_CONNECTION_TIMEOUT:300000}
spring.resources.static-locations=file:customFiles/static/
#spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/
#spring.thymeleaf.cache=false
#spring.thymeleaf.cache=false
spring.datasource.url=jdbc:h2:file:./mydatabase;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update

View File

@@ -55,6 +55,15 @@ settings.downloadOption.2=Open in new window
settings.downloadOption.3=Download file
settings.zipThreshold=Zip files when the number of downloaded files exceeds
settings.userSettings=User Settings
settings.changeUsername=New Username
settings.changeUsernameButton=Change Username
settings.password=Password
settings.oldPassword=Old password
settings.newPassword=New Password
settings.changePasswordButton=Change Password
settings.confirmNewPassword=Confirm New Password
settings.signOut=Sign Out
#############
# HOME-PAGE #
#############

View File

@@ -338,6 +338,49 @@
<label class="custom-control-label" for="boredWaiting" th:text="#{bored}"></label>
</div>
</div>
<hr>
<h5 th:text="#{settings.userSettings}">User Settings</h5>
<form action="/change-username" method="post">
<div class="form-group">
<label for="newUsername" th:text="#{settings.changeUsername}">Change Username</label>
<input type="text" class="form-control" name="newUsername" id="newUsername" placeholder="New Username">
</div>
<div class="form-group">
<label for="password" th:text="#{settings.password}">Password</label>
<input type="password" class="form-control" name="password" id="password" placeholder="Password">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" th:text="#{settings.changeUsernameButton}">Change Username</button>
</div>
</form>
<!-- Change Password Form -->
<form action="/change-password" method="post">
<div class="form-group">
<label for="oldPassword" th:text="#{settings.oldPassword}">Old Password</label>
<input type="password" class="form-control" name="oldPassword" id="oldPassword" placeholder="Old Password">
</div>
<div class="form-group">
<label for="newPassword" th:text="#{settings.newPassword}">New Password</label>
<input type="password" class="form-control" name="newPassword" id="newPassword" placeholder="New Password">
</div>
<div class="form-group">
<label for="confirmNewPassword" th:text="#{settings.confirmNewPassword}">Confirm New Password</label>
<input type="password" class="form-control" name="confirmNewPassword" id="confirmNewPassword" placeholder="Confirm New Password">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" th:text="#{settings.changePasswordButton}">Change Password</button>
</div>
</form>
<!-- Sign Out Button -->
<div class="form-group">
<a href="/logout">
<button type="button" class="btn btn-danger" th:text="#{settings.signOut}">Sign Out</button>
</a>
</div>
</div>
<div class="modal-footer">

View File

@@ -23,6 +23,9 @@
<script src="js/homecard.js"></script>
<div class=" container">
<form th:if="${@loginEnabled == true}" action="#" th:action="@{/logout}" method="post">
<input type="submit" value="Logout" />
</form>
<input type="text" id="searchBar" onkeyup="filterCards()" placeholder="Search for features...">
<div class="features-container ">

View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div id="page-container">
<div id="content-wrap">
<div class="container">
<div class="row justify-content-center align-items-center" style="height:100vh;">
<div class="col-md-4">
<div class="login-container card">
<div class="card-header text-center">
<img src="favicon.svg" alt="Logo" class="img-fluid" style="max-width: 100px;"> <!-- Adjust path and style as needed -->
<h4>Stirling-PDF Login</h4>
</div>
<div th:if="${logoutMessage}" class="alert alert-success" th:text="${logoutMessage}"></div>
<div class="card-body">
<form th:action="@{/login}" method="post">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username" class="form-control" required="required" />
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" class="form-control" required="required" />
</div>
<div class="form-group text-center">
<input type="submit" value="Login" class="btn btn-primary" />
</div>
</form>
</div>
<div class="card-footer text-danger text-center">
<div th:if="${error == 'badcredentials'}">
Invalid username or password.
</div>
<div th:if="${error == 'locked'}">
Your account has been locked.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>