security
This commit is contained in:
parent
ad5f057733
commit
e791fee38b
15 changed files with 480 additions and 56 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -116,7 +116,7 @@ watchedFolders/
|
|||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
*.db
|
||||
/build
|
||||
|
||||
/.vscode
|
|
@ -70,6 +70,8 @@ dependencies {
|
|||
implementation group: 'com.google.zxing', name: 'core', version: '3.5.1'
|
||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||
implementation 'org.commonmark:commonmark:0.21.0'
|
||||
// https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core
|
||||
implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0'
|
||||
|
||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||
|
||||
|
|
|
@ -14,9 +14,10 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
|||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import stirling.software.SPDF.utils.GeneralUtils;
|
||||
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
@SpringBootApplication
|
||||
@EnableWebSecurity(debug = true)
|
||||
@EnableWebSecurity()
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
//@EnableScheduling
|
||||
public class SPdfApplication {
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -10,9 +11,22 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket;
|
||||
import io.github.bucket4j.Bucket4j;
|
||||
import io.github.bucket4j.Refill;
|
||||
|
||||
@Configuration
|
||||
public class Beans implements WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
public Bucket createRateLimitBucket() {
|
||||
Refill refill = Refill.of(1000, Duration.ofDays(1));
|
||||
Bandwidth limit = Bandwidth.classic(1000, refill).withInitialTokens(1000);
|
||||
return Bucket4j.builder().addLimit(limit).build();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(localeChangeInterceptor());
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
package stirling.software.SPDF.controller.api;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import stirling.software.SPDF.config.security.UserService;
|
||||
|
||||
@Controller
|
||||
|
@ -25,4 +33,41 @@ public class UserController {
|
|||
userService.saveUser(username, password);
|
||||
return "redirect:/login?registered=true";
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/updateUserSettings")
|
||||
public String updateUserSettings(HttpServletRequest request, Principal principal) {
|
||||
Map<String, String[]> paramMap = request.getParameterMap();
|
||||
Map<String, String> updates = new HashMap<>();
|
||||
|
||||
System.out.println("Received parameter map: " + paramMap);
|
||||
|
||||
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
||||
updates.put(entry.getKey(), entry.getValue()[0]);
|
||||
}
|
||||
|
||||
System.out.println("Processed updates: " + updates);
|
||||
|
||||
// Assuming you have a method in userService to update the settings for a user
|
||||
userService.updateUserSettings(principal.getName(), updates);
|
||||
|
||||
return "redirect:/account"; // Redirect to a page of your choice after updating
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PostMapping("/admin/saveUser")
|
||||
public String saveUser(@RequestParam String username, @RequestParam String password, @RequestParam String role) {
|
||||
userService.saveUser(username, password, role);
|
||||
return "redirect:/addUsers"; // Redirect to account page after adding the user
|
||||
}
|
||||
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@GetMapping("/admin/deleteUser/{username}")
|
||||
public String deleteUser(@PathVariable String username) {
|
||||
userService.deleteUser(username);
|
||||
return "redirect:/addUsers";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -16,16 +18,23 @@ 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.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
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;
|
||||
import stirling.software.SPDF.config.security.UserService;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.repository.UserRepository;
|
||||
@Controller
|
||||
@Tag(name = "General", description = "General APIs")
|
||||
public class GeneralWebController {
|
||||
|
@ -48,7 +57,69 @@ public class GeneralWebController {
|
|||
|
||||
return "login";
|
||||
}
|
||||
@Autowired
|
||||
private UserRepository userRepository; // Assuming you have a repository for user operations
|
||||
|
||||
@Autowired
|
||||
private UserService userService; // Assuming you have a repository for user operations
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@GetMapping("/addUsers")
|
||||
public String showAddUserForm(Model model) {
|
||||
List<User> allUsers = userRepository.findAll();
|
||||
model.addAttribute("users", allUsers);
|
||||
return "addUsers";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GetMapping("/account")
|
||||
public String account(HttpServletRequest request, Model model, Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return "redirect:/";
|
||||
}
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
Object principal = authentication.getPrincipal();
|
||||
|
||||
if (principal instanceof UserDetails) {
|
||||
// Cast the principal object to UserDetails
|
||||
UserDetails userDetails = (UserDetails) principal;
|
||||
|
||||
// Retrieve username and other attributes
|
||||
String username = userDetails.getUsername();
|
||||
|
||||
// Fetch user details from the database
|
||||
Optional<User> user = userRepository.findByUsername(username); // Assuming findByUsername method exists
|
||||
if (!user.isPresent()) {
|
||||
// Handle error appropriately
|
||||
return "redirect:/error"; // Example redirection in case of error
|
||||
}
|
||||
|
||||
// Convert settings map to JSON string
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String settingsJson;
|
||||
try {
|
||||
settingsJson = objectMapper.writeValueAsString(user.get().getSettings());
|
||||
} catch (JsonProcessingException e) {
|
||||
// Handle JSON conversion error
|
||||
e.printStackTrace();
|
||||
return "redirect:/error"; // Example redirection in case of error
|
||||
}
|
||||
|
||||
// Add attributes to the model
|
||||
model.addAttribute("username", username);
|
||||
model.addAttribute("role", user.get().getRolesAsString());
|
||||
model.addAttribute("settings", settingsJson);
|
||||
}
|
||||
} else {
|
||||
return "redirect:/";
|
||||
}
|
||||
return "account";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@GetMapping("/pipeline")
|
||||
@Hidden
|
||||
public String pipelineForm(Model model) {
|
||||
|
|
|
@ -13,7 +13,19 @@ import jakarta.persistence.Table;
|
|||
@Table(name = "authorities")
|
||||
public class Authority {
|
||||
|
||||
@Id
|
||||
public Authority() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public Authority(String authority, User user) {
|
||||
this.authority = authority;
|
||||
this.user = user;
|
||||
user.getAuthorities().add(this);
|
||||
}
|
||||
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
|
|
11
src/main/java/stirling/software/SPDF/model/Role.java
Normal file
11
src/main/java/stirling/software/SPDF/model/Role.java
Normal file
|
@ -0,0 +1,11 @@
|
|||
package stirling.software.SPDF.model;
|
||||
public final class Role {
|
||||
|
||||
public static final String ADMIN = "ROLE_ADMIN";
|
||||
public static final String USER = "ROLE_USER";
|
||||
public static final String LIMITED_API_USER = "ROLE_LIMITED_API_USER";
|
||||
public static final String WEB_ONLY_USER = "ROLE_WEB_ONLY_USER";
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -1,15 +1,22 @@
|
|||
package stirling.software.SPDF.model;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.MapKeyColumn;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class User {
|
||||
|
@ -25,7 +32,23 @@ public class User {
|
|||
private boolean enabled;
|
||||
|
||||
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
|
||||
private Set<Authority> authorities;
|
||||
private Set<Authority> authorities = new HashSet<>();
|
||||
|
||||
@ElementCollection
|
||||
@MapKeyColumn(name = "setting_key")
|
||||
@Column(name = "setting_value")
|
||||
@CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "username"))
|
||||
private Map<String, String> settings = new HashMap<>(); // Key-value pairs of settings.
|
||||
|
||||
|
||||
|
||||
public Map<String, String> getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
public void setSettings(Map<String, String> settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
|
@ -58,5 +81,19 @@ public class User {
|
|||
public void setAuthorities(Set<Authority> authorities) {
|
||||
this.authorities = authorities;
|
||||
}
|
||||
|
||||
public void addAuthorities(Set<Authority> authorities) {
|
||||
this.authorities.addAll(authorities);
|
||||
}
|
||||
public void addAuthority(Authority authorities) {
|
||||
this.authorities.add(authorities);
|
||||
}
|
||||
|
||||
public String getRolesAsString() {
|
||||
return this.authorities.stream()
|
||||
.map(Authority::getAuthority)
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -16,9 +16,9 @@ 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=DEBUG
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
#logging.level.org.springframework.web=DEBUG
|
||||
#logging.level.org.springframework=DEBUG
|
||||
#logging.level.org.springframework.security=DEBUG
|
||||
|
||||
login.enabled=true
|
||||
|
||||
|
|
|
@ -55,10 +55,12 @@ settings.downloadOption.2=Open in new window
|
|||
settings.downloadOption.3=Download file
|
||||
settings.zipThreshold=Zip files when the number of downloaded files exceeds
|
||||
|
||||
settings.accountSettings=Account Settings
|
||||
settings.adminSettings=Admin - View/Add Users
|
||||
settings.userSettings=User Settings
|
||||
settings.changeUsername=New Username
|
||||
settings.changeUsernameButton=Change Username
|
||||
settings.password=Password
|
||||
settings.password=Confirmation Password
|
||||
settings.oldPassword=Old password
|
||||
settings.newPassword=New Password
|
||||
settings.changePasswordButton=Change Password
|
||||
|
|
194
src/main/resources/templates/account.html
Normal file
194
src/main/resources/templates/account.html
Normal file
|
@ -0,0 +1,194 @@
|
|||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{settings.userSettings})}"></th:block>
|
||||
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
|
||||
<!-- User Settings Title -->
|
||||
<h2 class="text-center" th:text="#{settings.accountSettings}">User Settings</h2>
|
||||
<hr>
|
||||
|
||||
<!-- At the top of the user settings -->
|
||||
<h3 class="text-center">Welcome <span th:text="${username}">User</span>!</h3>
|
||||
|
||||
<!-- Change Username Form -->
|
||||
<h4>Change username?</h4>
|
||||
<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>
|
||||
|
||||
<hr> <!-- Separator Line -->
|
||||
|
||||
<!-- Change Password Form -->
|
||||
<h4>Change Password?</h4>
|
||||
<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>
|
||||
|
||||
<hr> <!-- Separator Line -->
|
||||
|
||||
<h4>Sync browser settings with Account</h4>
|
||||
<div class="container mt-4">
|
||||
<h3>Settings Comparison:</h3>
|
||||
<table id="settingsTable" class="table table-bordered table-sm table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Account Setting</th>
|
||||
<th>Web Browser Setting</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- This will be dynamically populated by JavaScript -->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="buttons-container mt-3 text-center">
|
||||
<button id="syncToBrowser" class="btn btn-primary btn-sm">Sync Account to Web Browser</button>
|
||||
<button id="syncToAccount" class="btn btn-secondary btn-sm">Sync Web Browser to Account</button>
|
||||
</div>
|
||||
|
||||
<a th:if="${role == 'ROLE_ADMIN'}" href="addUsers" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" th:text="#{settings.adminSettings}">Admin Settings</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.buttons-container {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script th:inline="javascript">
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const settingsTableBody = document.querySelector("#settingsTable tbody");
|
||||
|
||||
/*<![CDATA[*/
|
||||
var accountSettingsString = /*[[${settings}]]*/ {};
|
||||
/*]]>*/
|
||||
var accountSettings = JSON.parse(accountSettingsString);
|
||||
|
||||
let allKeys = new Set([...Object.keys(accountSettings), ...Object.keys(localStorage)]);
|
||||
|
||||
allKeys.forEach(key => {
|
||||
if(key === 'debug' || key === '0' || key === '1') return; // Ignoring specific keys
|
||||
|
||||
const accountValue = accountSettings[key] || '-';
|
||||
const browserValue = localStorage.getItem(key) || '-';
|
||||
|
||||
const row = settingsTableBody.insertRow();
|
||||
const propertyCell = row.insertCell(0);
|
||||
const accountCell = row.insertCell(1);
|
||||
const browserCell = row.insertCell(2);
|
||||
|
||||
propertyCell.textContent = key;
|
||||
accountCell.textContent = accountValue;
|
||||
browserCell.textContent = browserValue;
|
||||
});
|
||||
|
||||
document.getElementById('syncToBrowser').addEventListener('click', function() {
|
||||
// First, clear the local storage
|
||||
localStorage.clear();
|
||||
|
||||
// Then, set the account settings to local storage
|
||||
for (let key in accountSettings) {
|
||||
if(key !== 'debug' && key !== '0' && key !== '1') { // Only sync non-ignored keys
|
||||
localStorage.setItem(key, accountSettings[key]);
|
||||
}
|
||||
}
|
||||
location.reload(); // Refresh the page after sync
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('syncToAccount').addEventListener('click', function() {
|
||||
let form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.action = "/updateUserSettings"; // Your endpoint URL
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if(key !== 'debug' && key !== '0' && key !== '1') { // Only send non-ignored keys
|
||||
let hiddenField = document.createElement("input");
|
||||
hiddenField.type = "hidden";
|
||||
hiddenField.name = key;
|
||||
hiddenField.value = localStorage.getItem(key);
|
||||
form.appendChild(hiddenField);
|
||||
}
|
||||
}
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Sign Out Button -->
|
||||
<div class="form-group mt-4">
|
||||
<a href="/logout">
|
||||
<button type="button" class="btn btn-danger" th:text="#{settings.signOut}">Sign Out</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
76
src/main/resources/templates/addUsers.html
Normal file
76
src/main/resources/templates/addUsers.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{settings.userSettings})}"></th:block>
|
||||
|
||||
<body>
|
||||
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
|
||||
<!-- User Settings Title -->
|
||||
<h2 class="text-center" th:text="#{settings.accountSettings}">User Settings</h2>
|
||||
<hr>
|
||||
|
||||
<!-- At the top of the user settings -->
|
||||
<h3 class="text-center">Welcome <span th:text="${username}">User</span>!</h3>
|
||||
|
||||
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Roles</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="user : ${users}">
|
||||
<td th:text="${user.username}"></td>
|
||||
<td th:text="${user.getRolesAsString()}"></td>
|
||||
<td>
|
||||
<a th:href="@{'/admin/deleteUser/' + ${user.username}}">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<h2>Add New User</h2>
|
||||
<form action="/admin/saveUser" method="post">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" name="password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="role">Role</label>
|
||||
<select name="role" class="form-control" required>
|
||||
<option value="ROLE_ADMIN">Admin</option>
|
||||
<option value="ROLE_USER">User</option>
|
||||
<option value="ROLE_LIMITED_API_USER">Limited API User</option>
|
||||
<option value="ROLE_WEB_ONLY_USER">Web Only User</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Add other fields as required -->
|
||||
<button type="submit" class="btn btn-primary">Save User</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -339,49 +339,11 @@
|
|||
</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>
|
||||
|
||||
<a href="account" target="_blank">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" th:text="#{settings.accountSettings}">Account Settings</button>
|
||||
</a>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal" th:text="#{close}"></button>
|
||||
|
|
|
@ -23,9 +23,6 @@
|
|||
<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 ">
|
||||
|
||||
|
|
Loading…
Reference in a new issue