mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01: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,6 +57,68 @@ 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
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,18 @@ import jakarta.persistence.Table;
 | 
			
		||||
@Table(name = "authorities")
 | 
			
		||||
public class Authority {
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
@ -59,4 +82,18 @@ public class User {
 | 
			
		||||
		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,48 +339,10 @@
 | 
			
		||||
                  </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 href="account" target="_blank">
 | 
			
		||||
                    <button type="button" class="btn btn-sm btn-outline-primary" th:text="#{settings.accountSettings}">Account Settings</button>
 | 
			
		||||
                </a>
 | 
			
		||||
                </div>
 | 
			
		||||
               
 | 
			
		||||
                    
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
 | 
			
		||||
@ -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