mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-10-25 11:17:28 +02:00 
			
		
		
		
	security
This commit is contained in:
		
							parent
							
								
									ad5f057733
								
							
						
					
					
						commit
						e791fee38b
					
				
							
								
								
									
										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 New Issue
	
	Block a user